From 248aa65195ac22d91d63a00870f7528d91c6c5a6 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Tue, 26 Mar 2024 13:56:10 -0700 Subject: [PATCH 01/12] Reinstate product fruits. --- client/src/App/App.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/App/App.jsx b/client/src/App/App.jsx index 5453a25a3..d15af2f2d 100644 --- a/client/src/App/App.jsx +++ b/client/src/App/App.jsx @@ -163,7 +163,8 @@ export function App({ /> } > - Date: Tue, 26 Mar 2024 15:28:12 -0700 Subject: [PATCH 02/12] Web-est bug fixes. --- .../job-totals.table.totals.component.jsx | 403 +++++++++--------- .../jobs-available-table.container.jsx | 100 +++-- server/job/job-totals-USA.js | 3 +- 3 files changed, 249 insertions(+), 257 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 c38b5c642..ac18af0b5 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 @@ -1,228 +1,205 @@ -import {Table} from "antd"; -import Dinero from "dinero.js"; -import React, {useMemo} from "react"; -import {useTranslation} from "react-i18next"; +import { Table } from 'antd'; +import Dinero from 'dinero.js'; +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from 'react-redux'; +import { createStructuredSelector } from 'reselect'; +import { selectBodyshop } from '../../redux/user/user.selectors'; import InstanceRenderManager from '../../utils/instanceRenderMgr'; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop, }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobTotalsTableTotals); +export default connect(mapStateToProps, mapDispatchToProps)(JobTotalsTableTotals); -export function JobTotalsTableTotals({bodyshop, job}) { - const {t} = useTranslation(); +export function JobTotalsTableTotals({ bodyshop, job }) { + const { t } = useTranslation(); - const data = useMemo(() => { - return [ - { - key: t("jobs.labels.subtotal"), - total: job.job_totals.totals.subtotal, + const data = useMemo(() => { + return [ + { + key: t('jobs.labels.subtotal'), + total: job.job_totals.totals.subtotal, + bold: true, + }, + ...InstanceRenderManager({ + imex: [ + { + key: t('jobs.labels.local_tax_amt'), + total: job.job_totals.totals.local_tax, + }, + { + key: t('jobs.labels.state_tax_amt'), + total: job.job_totals.totals.state_tax, + }, + ...(bodyshop.region_config === 'CA_BC' + ? [ + { + key: t('jobs.fields.ca_bc_pvrt'), + total: job.job_totals.additional.pvrt, + }, + ] + : []), + { + key: t('jobs.labels.federal_tax_amt'), + total: job.job_totals.totals.federal_tax, + }, + ], + promanager: 'USE_ROME', + rome: job.job_totals.totals.us_sales_tax_breakdown + ? [ + { + key: `${bodyshop.md_responsibility_centers.taxes.tax_ty1?.tax_type1 || 'T1'} - ${[ + job.cieca_pft.ty1_rate1, + job.cieca_pft.ty1_rate2, + job.cieca_pft.ty1_rate3, + job.cieca_pft.ty1_rate4, + job.cieca_pft.ty1_rate5, + ] + .filter((i) => i > 0) + .join(', ')}%`, + total: job.job_totals.totals.us_sales_tax_breakdown.ty1Tax, + }, + { + key: `${bodyshop.md_responsibility_centers.taxes.tax_ty2?.tax_type2 || 'T2'} - ${[ + job.cieca_pft.ty2_rate1, + job.cieca_pft.ty2_rate2, + job.cieca_pft.ty2_rate3, + job.cieca_pft.ty2_rate4, + job.cieca_pft.ty2_rate5, + ] + .filter((i) => i > 0) + .join(', ')}%`, + total: job.job_totals.totals.us_sales_tax_breakdown.ty2Tax, + }, + { + key: `${bodyshop.md_responsibility_centers.taxes.tax_ty3?.tax_type3 || 'T3'} - ${[ + job.cieca_pft.ty3_rate1, + job.cieca_pft.ty3_rate2, + job.cieca_pft.ty3_rate3, + job.cieca_pft.ty3_rate4, + job.cieca_pft.ty3_rate5, + ] + .filter((i) => i > 0) + .join(', ')}%`, + total: job.job_totals.totals.us_sales_tax_breakdown.ty3Tax, + }, + { + key: `${bodyshop.md_responsibility_centers.taxes.tax_ty4?.tax_type4 || 'T4'} - ${[ + job.cieca_pft.ty4_rate1, + job.cieca_pft.ty4_rate2, + job.cieca_pft.ty4_rate3, + job.cieca_pft.ty4_rate4, + job.cieca_pft.ty4_rate5, + ] + .filter((i) => i > 0) + .join(', ')}%`, + total: job.job_totals.totals.us_sales_tax_breakdown.ty4Tax, + }, + { + key: `${bodyshop.md_responsibility_centers.taxes.tax_ty5?.tax_type5 || 'TT'} - ${[ + job.cieca_pft.ty5_rate1, + job.cieca_pft.ty5_rate2, + job.cieca_pft.ty5_rate3, + job.cieca_pft.ty5_rate4, + job.cieca_pft.ty5_rate5, + ] + .filter((i) => i > 0) + .join(', ')}%`, + total: job.job_totals.totals.us_sales_tax_breakdown.ty5Tax, + }, + { + key: t('jobs.labels.total_sales_tax'), bold: true, - }, -...InstanceRenderManager({imex: [ { - key: t("jobs.labels.local_tax_amt"), - total: job.job_totals.totals.local_tax, -}, -{ - key: t("jobs.labels.state_tax_amt"), - total: job.job_totals.totals.state_tax, -}, -...(bodyshop.region_config === "CA_BC" - ? [ - { - key: t("jobs.fields.ca_bc_pvrt"), - total: job.job_totals.additional.pvrt, - }, - ] - : []), -{ - key: t("jobs.labels.federal_tax_amt"), - total: job.job_totals.totals.federal_tax, -},], - promanager: "USE_ROME", -rome: [(job.job_totals.totals.us_sales_tax_breakdown - ? [ - { - key: `${ - bodyshop.md_responsibility_centers.taxes.tax_ty1?.tax_type1 || - "T1" - } - ${[ - job.cieca_pft.ty1_rate1, - job.cieca_pft.ty1_rate2, - job.cieca_pft.ty1_rate3, - job.cieca_pft.ty1_rate4, - job.cieca_pft.ty1_rate5, - ] - .filter((i) => i > 0) - .join(", ")}%`, - total: job.job_totals.totals.us_sales_tax_breakdown.ty1Tax, - }, - { - key: `${ - bodyshop.md_responsibility_centers.taxes.tax_ty2?.tax_type2 || - "T2" - } - ${[ - job.cieca_pft.ty2_rate1, - job.cieca_pft.ty2_rate2, - job.cieca_pft.ty2_rate3, - job.cieca_pft.ty2_rate4, - job.cieca_pft.ty2_rate5, - ] - .filter((i) => i > 0) - .join(", ")}%`, - total: job.job_totals.totals.us_sales_tax_breakdown.ty2Tax, - }, - { - key: `${ - bodyshop.md_responsibility_centers.taxes.tax_ty3?.tax_type3 || - "T3" - } - ${[ - job.cieca_pft.ty3_rate1, - job.cieca_pft.ty3_rate2, - job.cieca_pft.ty3_rate3, - job.cieca_pft.ty3_rate4, - job.cieca_pft.ty3_rate5, - ] - .filter((i) => i > 0) - .join(", ")}%`, - total: job.job_totals.totals.us_sales_tax_breakdown.ty3Tax, - }, - { - key: `${ - bodyshop.md_responsibility_centers.taxes.tax_ty4?.tax_type4 || - "T4" - } - ${[ - job.cieca_pft.ty4_rate1, - job.cieca_pft.ty4_rate2, - job.cieca_pft.ty4_rate3, - job.cieca_pft.ty4_rate4, - job.cieca_pft.ty4_rate5, - ] - .filter((i) => i > 0) - .join(", ")}%`, - total: job.job_totals.totals.us_sales_tax_breakdown.ty4Tax, - }, - { - key: `${ - bodyshop.md_responsibility_centers.taxes.tax_ty5?.tax_type5 || - "TT" - } - ${[ - job.cieca_pft.ty5_rate1, - job.cieca_pft.ty5_rate2, - job.cieca_pft.ty5_rate3, - job.cieca_pft.ty5_rate4, - job.cieca_pft.ty5_rate5, - ] - .filter((i) => i > 0) - .join(", ")}%`, - total: job.job_totals.totals.us_sales_tax_breakdown.ty5Tax, - }, - { - key: t("jobs.labels.total_sales_tax"), - bold: true, - total: Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty1Tax) - .add( - Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty2Tax) - ) - .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) - ).toJSON(), - }, - ].filter((item) => item.total.amount !== 0) - : [ - { - key: t("jobs.labels.state_tax_amt"), - total: job.job_totals.totals.state_tax, - }, - ])] -}), - + total: Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty1Tax) + .add(Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty2Tax)) + .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)) + .toJSON(), + }, + ].filter((item) => item.total.amount !== 0) + : [ + { + key: t('jobs.labels.state_tax_amt'), + total: job.job_totals.totals.state_tax, + }, + ], + }), - { - key: t("jobs.labels.total_repairs"), - total: job.job_totals.totals.total_repairs, - bold: true, - }, - { - key: t("jobs.fields.ded_amt"), - total: job.job_totals.totals.custPayable.deductible, - }, - // { - // key: t("jobs.fields.federal_tax_payable"), - // total: job.job_totals.totals.custPayable.federal_tax, - // }, - { - key: t("jobs.fields.other_amount_payable"), - total: job.job_totals.totals.custPayable.other_customer_amount, - }, - { - key: t("jobs.fields.depreciation_taxes"), - total: job.job_totals.totals.custPayable.dep_taxes, - }, + { + key: t('jobs.labels.total_repairs'), + total: job.job_totals.totals.total_repairs, + bold: true, + }, + { + key: t('jobs.fields.ded_amt'), + total: job.job_totals.totals.custPayable.deductible, + }, + // { + // key: t("jobs.fields.federal_tax_payable"), + // total: job.job_totals.totals.custPayable.federal_tax, + // }, + { + key: t('jobs.fields.other_amount_payable'), + total: job.job_totals.totals.custPayable.other_customer_amount, + }, + { + key: t('jobs.fields.depreciation_taxes'), + total: job.job_totals.totals.custPayable.dep_taxes, + }, - { - key: t("jobs.labels.total_cust_payable"), - total: job.job_totals.totals.custPayable.total, - bold: true, - }, - { - key: t("jobs.labels.net_repairs"), - total: job.job_totals.totals.net_repairs, - bold: true, - }, - ]; - }, [job.job_totals, job.cieca_pft, t, bodyshop.md_responsibility_centers]); - - const columns = [ - { - //title: t("joblines.fields.part_type"), - dataIndex: "key", - key: "key", - width: "80%", - onCell: (record, rowIndex) => { - return {style: {fontWeight: record.bold && "bold"}}; - }, - }, - { - title: t("joblines.fields.total"), - dataIndex: "total", - key: "total", - align: "right", - render: (text, record) => Dinero(record.total).toFormat(), - width: "20%", - onCell: (record, rowIndex) => { - return {style: {fontWeight: record.bold && "bold"}}; - }, - }, + { + key: t('jobs.labels.total_cust_payable'), + total: job.job_totals.totals.custPayable.total, + bold: true, + }, + { + key: t('jobs.labels.net_repairs'), + total: job.job_totals.totals.net_repairs, + bold: true, + }, ]; + }, [job.job_totals, job.cieca_pft, t, bodyshop.md_responsibility_centers]); - return ( - - ); + const columns = [ + { + //title: t("joblines.fields.part_type"), + dataIndex: 'key', + key: 'key', + width: '80%', + onCell: (record, rowIndex) => { + return { style: { fontWeight: record.bold && 'bold' } }; + }, + }, + { + title: t('joblines.fields.total'), + dataIndex: 'total', + key: 'total', + align: 'right', + render: (text, record) => Dinero(record.total).toFormat(), + width: '20%', + onCell: (record, rowIndex) => { + return { style: { fontWeight: record.bold && 'bold' } }; + }, + }, + ]; + + return ( +
+ ); } 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 5b801c045..d0177354f 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 @@ -600,11 +600,12 @@ async function CheckTaxRates(estData, bodyshop) { estData.joblines.data.forEach((line) => { if (line.misc_amt && line.misc_amt !== 0) { line.act_price = line.act_price + line.misc_amt; - line.tax_part = !!line.misc_tax; + line.tax_part = !!line.misc_tax; + line.notes += ` | Misc amount override.`; + } //WEB EST SPECIFIC CLEAN UP InstanceRenderManager({executeFunction: true, args:[], promanager: () => { - if (line.mod_lbr_ty === "LAET" || line.mod_lbr_ty === "LAUT") { line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`; line.mod_lbr_ty = "LAR"; @@ -615,52 +616,65 @@ async function CheckTaxRates(estData, bodyshop) { //Group by line no // For everything but the first one, strip out the price number in - InstanceRenderManager({executeFunction:true, args:[], promanager: () => { - - - - const groupedByLineRef = _.groupBy(estData.joblines.data, "line_ref"); - Object.keys(groupedByLineRef).forEach((lineRef) => { - groupedByLineRef[lineRef].forEach((line, index) => { - //Let the first one keep it - if (index === 0) return; - //Web Est seems to have additional costs with UNQ_SEQ 0. Keep them all? - if (line.unq_seq === 0) return; - const indexInEstData = estData.joblines.data.findIndex( - (l) => l.unq_seq === line.unq_seq - ); - estData.joblines.data[ - indexInEstData - ].notes += ` | Scrubbed due to the line_ref issue. (prev act price = ${estData.joblines.data[indexInEstData].act_price})`; - estData.joblines.data[indexInEstData].act_price = 0; - estData.joblines.data[indexInEstData].db_price = 0; - }); - }) - }}) + // InstanceRenderManager({executeFunction:true, args:[], promanager: () => { + // const groupedByLineRef = _.groupBy(estData.joblines.data, "line_ref"); + // Object.keys(groupedByLineRef).forEach((lineRef) => { + // let index0ActPrice; + // groupedByLineRef[lineRef].forEach((line, index) => { + // //Let the first one keep it + // if (index === 0){ + // index0ActPrice = line.act_price; + // return;} + // //Web Est seems to have additional costs with UNQ_SEQ 0. Keep them all? + // if (line.unq_seq === 0) return; + // if(index0ActPrice !== line.act_price){ + // line.notes += ` | Price override.`; + // return; + // } + // const indexInEstData = estData.joblines.data.findIndex( + // (l) => l.unq_seq === line.unq_seq + // ); + // estData.joblines.data[ + // indexInEstData + // ].notes += ` | Scrubbed due to the line_ref issue. (prev act price = ${estData.joblines.data[indexInEstData].act_price})`; + // estData.joblines.data[indexInEstData].act_price = 0; + // estData.joblines.data[indexInEstData].db_price = 0; + // }); + // }) + // }}) +InstanceRenderManager({ + executeFunction: true, + args: [], + promanager: null, //Require to prevent auto firing of Rome. + rome: () => { //Generate the list of duplicated UNQ_SEQ that will feed into the next section to scrub the lines. - const unqSeqHash = _.groupBy(estData.joblines.data, "unq_seq"); - const duplicatedUnqSeq = Object.keys(unqSeqHash).filter( - (key) => unqSeqHash[key].length > 1 - ); + const unqSeqHash = _.groupBy(estData.joblines.data, 'unq_seq'); + const duplicatedUnqSeq = Object.keys(unqSeqHash).filter((key) => unqSeqHash[key].length > 1); duplicatedUnqSeq.forEach((unq_seq) => { - //Keys are strings, convert to int. - const int_unq_seq = parseInt(unq_seq); + //Keys are strings, convert to int. + const int_unq_seq = parseInt(unq_seq); - //When line splitting, the first line is always the non-refinish line. We will keep it as is. - //We will cleanse the second line, which is always the next line. - const nonRefLineIndex = estData.joblines.data.findIndex( - (line) => line.unq_seq === int_unq_seq - ); - estData.joblines.data[nonRefLineIndex + 1] = { - ...estData.joblines.data[nonRefLineIndex + 1], - part_type: null, - act_price: 0, - db_price: 0, - prt_dsmk_p: 0, - prt_dsmk_m: 0, - }; + //When line splitting, the first line is always the non-refinish line. We will keep it as is. + //We will cleanse the second line, which is always the next line. + const nonRefLineIndex = estData.joblines.data.findIndex( + (line) => line.unq_seq === int_unq_seq + ); + estData.joblines.data[nonRefLineIndex + 1] = { + ...estData.joblines.data[nonRefLineIndex + 1], + part_type: null, + act_price: 0, + db_price: 0, + prt_dsmk_p: 0, + prt_dsmk_m: 0, + }; }); + }, +}); + + + + } diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index f13ce3546..b8b7f75b5 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -3,6 +3,7 @@ const queries = require("../graphql-client/queries"); const adminClient = require("../graphql-client/graphql-client").client; const _ = require("lodash"); const logger = require("../utils/logger"); +const InstanceMgr = require("../utils/instanceMgr").default; //****************************************************** */ //****************************************************** */ @@ -992,7 +993,7 @@ function CalculateTaxesTotals(job, otherTotals) { ); let taxableAmountInThisThreshold; - if (thresholdAmount === 9999.99) { + if (thresholdAmount === 9999.99 || InstanceMgr({debug:true,imex:false, rome: false, promanager:thresholdAmount === 0 && parseInt(tyCounter) === 1 }) ) { // // THis is the last threshold. Tax the entire remaining amount. taxableAmountInThisThreshold = remainingTaxableAmounts[taxTierKey]; remainingTaxableAmounts[taxTierKey] = Dinero(); From 7f930fcd5bb3ed2b6c26a3bcd031ea89bac98234 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 27 Mar 2024 08:15:50 -0700 Subject: [PATCH 03/12] Product fruit updates. --- client/src/App/App.jsx | 2 ++ .../pages/manage/manage.page.component.jsx | 33 ++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/client/src/App/App.jsx b/client/src/App/App.jsx index d15af2f2d..2f4583c67 100644 --- a/client/src/App/App.jsx +++ b/client/src/App/App.jsx @@ -165,6 +165,8 @@ export function App({ > ({ export function Manage({conflict, bodyshop,enableJoyRide,joyRideSteps,setJoyRideFinished}) { const {t} = useTranslation(); const [chatVisible] = useState(false); - +const [tours, setTours] = useState([]) useEffect(() => { @@ -526,9 +526,7 @@ export function Manage({conflict, bodyshop,enableJoyRide,joyRideSteps,setJoyRide imex: t('titles.imexonline'), rome: t('titles.romeonline'), promanager: t('titles.promanager'), - })} - ${ - import.meta.env.VITE_APP_GIT_SHA_DATE - }`} + })} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
@@ -536,6 +534,31 @@ export function Manage({conflict, bodyshop,enableJoyRide,joyRideSteps,setJoyRide Disclaimer & Notices + {InstanceRenderManager({ + promanager: ( + + + + + {tours.map((tour) => ( + window.productFruits.api.tours.tryStartTour(tour.id)} + > + {tour.name} + + ))} + + + + ), + })} From bf5d1b69d4ae6d368dc7a061acb45ad2b1ae3f93 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 27 Mar 2024 08:57:08 -0700 Subject: [PATCH 04/12] Replace CI for Rome Test env. --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 79a9f7217..3e6588cfd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -325,16 +325,16 @@ workflows: secret: ${HASURA_TEST_SECRET} filters: branches: - only: test + only: test-AIO - test-rome-app-build: filters: branches: - only: rome/test + only: test-AIO - test-rome-hasura-migrate: secret: ${HASURA_ROME_TEST_SECRET} filters: branches: - only: rome/test + only: test-AIO #- admin-app-build: #filters: #branches: From 090c55fb3de6baec77d4271e6788d376d399c47d Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 27 Mar 2024 08:59:46 -0700 Subject: [PATCH 05/12] CI changes. --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3e6588cfd..92962447b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -146,7 +146,7 @@ jobs: test-rome-hasura-migrate: docker: - - image: cimg/node:16.15.0 + - image: cimg/node:18.18.2 parameters: secret: type: string @@ -179,14 +179,14 @@ jobs: - run: npm run build:test:rome - aws-s3/sync: - from: build + from: dist to: "s3://rome-online-test/" arguments: "--exclude '*.map'" test-hasura-migrate: docker: - - image: cimg/node:16.15.0 + - image: cimg/node:18.18.2 parameters: secret: type: string @@ -245,7 +245,7 @@ jobs: region: AWS_REGION - aws-s3/sync: - from: build + from: dist to: "s3://imex-online-test-beta/" arguments: "--exclude '*.map'" From 3eaa23beacf684592e752fefb60b955788341a1c Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 27 Mar 2024 09:05:37 -0700 Subject: [PATCH 06/12] CI credentials for ROme. --- .circleci/config.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 92962447b..4233aced8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -178,12 +178,16 @@ jobs: - run: npm run build:test:rome + - aws-cli/setup: + aws_access_key_id: AWS_ACCESS_KEY_ID + aws_secret_access_key: AWS_SECRET_ACCESS_KEY + region: AWS_REGION + - aws-s3/sync: from: dist to: "s3://rome-online-test/" arguments: "--exclude '*.map'" - test-hasura-migrate: docker: - image: cimg/node:18.18.2 From b1dfd23fd48cb34c70c22f40e60237ace504d715 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 27 Mar 2024 09:54:40 -0700 Subject: [PATCH 07/12] minor RO fixes. --- .../components/bill-form/bill-form.lines.component.jsx | 4 ++++ client/src/components/header/header.component.jsx | 9 +++++++-- client/src/utils/instanceRenderMgr.js | 6 +++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/client/src/components/bill-form/bill-form.lines.component.jsx b/client/src/components/bill-form/bill-form.lines.component.jsx index ee02d3acc..d800ccf00 100644 --- a/client/src/components/bill-form/bill-form.lines.component.jsx +++ b/client/src/components/bill-form/bill-form.lines.component.jsx @@ -681,6 +681,8 @@ export function BillEnterModalLinesComponent({ }, ...InstanceRenderManager({ + rome:[], + promanager:[], imex: [ { title: t("billlines.fields.federal_tax_applicable"), @@ -722,6 +724,8 @@ export function BillEnterModalLinesComponent({ }, ...InstanceRenderManager({ + rome:[], + promanager:[], imex: [ { title: t("billlines.fields.local_tax_applicable"), diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 20dae0de5..59acdd8c4 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -602,7 +602,11 @@ function Header({ }, ]; - menuItems.push({ + InstanceRenderManager({ + executeFunction:true, + args: [], + imex: + () => { menuItems.push({ key: 'beta-switch', style: { marginLeft: 'auto' }, label: ( @@ -618,7 +622,8 @@ function Header({ ), - }); + });} +}) return ( diff --git a/client/src/utils/instanceRenderMgr.js b/client/src/utils/instanceRenderMgr.js index 92c77f4f9..36f8f115b 100644 --- a/client/src/utils/instanceRenderMgr.js +++ b/client/src/utils/instanceRenderMgr.js @@ -25,7 +25,11 @@ export default function InstanceRenderManager({ propToReturn = imex; break; case 'ROME': - propToReturn = rome; //TODO:AIO Implement USE_IMEX + if (rome === 'USE_IMEX') { + propToReturn = imex; + } else { + propToReturn = rome; + } break; case 'PROMANAGER': //Return the rome prop if USE_ROME. From 136ef63c14ded75c56d07a21cda179edac203b46 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 27 Mar 2024 13:04:06 -0700 Subject: [PATCH 08/12] Add PPC. --- server/routes/jobRoutes.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/routes/jobRoutes.js b/server/routes/jobRoutes.js index 6c11b4b19..3c5334b2e 100644 --- a/server/routes/jobRoutes.js +++ b/server/routes/jobRoutes.js @@ -1,6 +1,7 @@ const express = require('express'); const router = express.Router(); const job = require('../job/job'); +const ppc = require('../ccc/partspricechange') const {partsScan} = require('../parts-scan/parts-scan'); const eventAuthorizationMiddleware = require('../middleware/eventAuthorizationMIddleware'); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); @@ -14,5 +15,6 @@ router.post('/costing', validateFirebaseIdTokenMiddleware, withUserGraphQLClient router.post('/lifecycle', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, lifecycle); router.post('/costingmulti', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, costingmulti); router.post('/partsscan', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, partsScan); +router.post('/ppc', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, ppc.generatePpc); module.exports = router; From b161530381d78c53f7cdf2918d74ca0ba9801d31 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 27 Mar 2024 15:13:47 -0700 Subject: [PATCH 09/12] Minor Test Changes. --- client/package-lock.json | 176 +++++++++++++++++- client/package.json | 2 +- .../bill-form/bill-form.component.jsx | 2 +- .../jobs-available-table.container.jsx | 4 +- .../jobs-detail-rates.component.jsx | 5 +- job-totals-testing-util.js | 4 +- 6 files changed, 183 insertions(+), 10 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 5fc873078..13a72ca36 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -85,7 +85,7 @@ "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-react": "^7.23.3", - "@dotenvx/dotenvx": "^0.15.0", + "@dotenvx/dotenvx": "^0.15.4", "@emotion/babel-plugin": "^11.11.0", "@emotion/react": "^11.11.3", "@sentry/webpack-plugin": "^2.14.2", @@ -111,6 +111,9 @@ }, "engines": { "node": "18.18.2" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.6.1" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2512,8 +2515,9 @@ }, "node_modules/@dotenvx/dotenvx": { "version": "0.15.4", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-0.15.4.tgz", + "integrity": "sha512-3DBVbsdowmbM+MtjECtUSV/lmoSVSOZtGw6f5wHeeW5t3dwXYyAk8vRBayS16zBmk4WBRRlOpoowA5Wmk08MIg==", "dev": true, - "license": "MIT", "dependencies": { "@inquirer/prompts": "^3.3.0", "chalk": "^4.1.2", @@ -5671,6 +5675,161 @@ "node": ">= 8.0.0" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", + "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", + "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", + "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", + "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", + "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", + "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", + "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", + "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.1.tgz", + "integrity": "sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", + "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", + "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", + "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.12.0", "cpu": [ @@ -23242,6 +23401,19 @@ "node": ">=8" } }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", + "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/run-async": { "version": "3.0.0", "dev": true, diff --git a/client/package.json b/client/package.json index 135f59420..686a6c6e4 100644 --- a/client/package.json +++ b/client/package.json @@ -129,7 +129,7 @@ "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-react": "^7.23.3", - "@dotenvx/dotenvx": "^0.15.0", + "@dotenvx/dotenvx": "^0.15.4", "@emotion/babel-plugin": "^11.11.0", "@emotion/react": "^11.11.3", "@sentry/webpack-plugin": "^2.14.2", diff --git a/client/src/components/bill-form/bill-form.component.jsx b/client/src/components/bill-form/bill-form.component.jsx index fcf167d73..2cdcddf72 100644 --- a/client/src/components/bill-form/bill-form.component.jsx +++ b/client/src/components/bill-form/bill-form.component.jsx @@ -394,7 +394,7 @@ export function BillFormComponent({ if (!!totals) return (
- + { if (line.mod_lbr_ty === "LAET" || line.mod_lbr_ty === "LAUT") { - line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`; + // line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`; line.mod_lbr_ty = "LAR"; } }}) diff --git a/client/src/components/jobs-detail-rates/jobs-detail-rates.component.jsx b/client/src/components/jobs-detail-rates/jobs-detail-rates.component.jsx index a64582c7c..c1f98a648 100644 --- a/client/src/components/jobs-detail-rates/jobs-detail-rates.component.jsx +++ b/client/src/components/jobs-detail-rates/jobs-detail-rates.component.jsx @@ -128,7 +128,10 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) { - + { + InstanceRenderManager({imex: }) + } + diff --git a/job-totals-testing-util.js b/job-totals-testing-util.js index f9c79250a..68220f54e 100644 --- a/job-totals-testing-util.js +++ b/job-totals-testing-util.js @@ -19,8 +19,8 @@ require("dotenv").config({ }); async function RunTheTest() { - const bodyshopids = ["52b7357c-0edd-4c95-85c3-dfdbcdfad9ac"]; - const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjlhNTE5MDc0NmU5M2JhZTI0OWIyYWE3YzJhYTRlMzA2M2UzNDFlYzciLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUm9tZSBEZXZlbG9wbWVudCIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciJdLCJ4LWhhc3VyYS11c2VyLWlkIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiJ9LCJpb2FkbWluIjp0cnVlLCJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vcm9tZS1wcm9kLTEiLCJhdWQiOiJyb21lLXByb2QtMSIsImF1dGhfdGltZSI6MTY5NTkxNDQ5NywidXNlcl9pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIiLCJzdWIiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwiaWF0IjoxNjk2NjAzMTUwLCJleHAiOjE2OTY2MDY3NTAsImVtYWlsIjoicGF0cmlja0Byb21lLmRldiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJwYXRyaWNrQHJvbWUuZGV2Il19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.YYSEG1_Iwoqrelj0Fz5f04b78ABrueaFHVG1bBi-2c9kfkfrSiobgSs4jmYRlUHx1pRY58sFoNWvjci3cpFLwdaFSRAei5LwVFHllXlT8sMmWpxOMD4xU_fLRX9_hGM4SySlsBLAekytU5wCrtYF-BwEubYwPc7nkfi61BbaX1rBxVU3FAX123ToO7zN6VIzbTQRlrpBPBsCa3LWjhi1y-2V9vRsshOMMyezmKNMwknGvuoLwEeh9HYM4O0gDbtLYosFb5zsMRSPdrq4wjECge_psxF6QJ5p2JpKFAVyoYjK6lavM4QXZhTx05ssOj7pRz13NbYYX9of2pabhWjDSw`; + const bodyshopids = ["b501bb82-22b2-493a-8a0f-152938194869"]; + const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImJhNjI1OTZmNTJmNTJlZDQ0MDQ5Mzk2YmU3ZGYzNGQyYzY0ZjQ1M2UiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUm9tZSBEZXZlbG9wbWVudCIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciJdLCJ4LWhhc3VyYS11c2VyLWlkIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiJ9LCJpb2FkbWluIjp0cnVlLCJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vcm9tZS1wcm9kLTEiLCJhdWQiOiJyb21lLXByb2QtMSIsImF1dGhfdGltZSI6MTcxMDk1MTg1MCwidXNlcl9pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIiLCJzdWIiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwiaWF0IjoxNzExNTczODI1LCJleHAiOjE3MTE1Nzc0MjUsImVtYWlsIjoicGF0cmlja0Byb21lLmRldiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJwYXRyaWNrQHJvbWUuZGV2Il19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.0kBySA9tJznLYj8TtncHGVWJO0IcmLKP2G1UyyXwaj45kTa25bjT9RWjM-NslX_zjOvrvmQZzisFAb6M1Jf6geNjOMLIqb8bhihhzEZK4CcRfvjT6cpZxnOO2Dp_1Y5OePbvOBS_GlfdsovVWa84OLuhYC5G_3QwHT8_2Cttz4CbrC6M_vd7QsGODJYBbVKMhOdZhzpNq7AbOUh3749WRjLMMobpnZDrmQlsyg3PAqtX1FHO25WQS2rma9QahGDSY736JfbkuZJ2XbNn0axEGpK7RQLUcuRkFUlfKqYplNbR_e1Q3kEfRAZpxBPXZysrDcbDNhbkWCoTmJ3fle55OA`; const {jobs} = await client.request( gql` query GET_JOBS($bodyshopids: [uuid!]!) { From e1df64d5920883b4718b8eaaebf3225dd6c900f6 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 27 Mar 2024 15:35:07 -0700 Subject: [PATCH 10/12] Reformat all project files to use the prettier config file. --- .prettierrc.js | 14 +- _reference/Test_CDK_Acct Config.json | 2 +- _reference/test-ecoystem.config.js | 34 +- client/craco.config.js | 79 +- client/cypress.config.js | 28 +- .../e2e/01-General Render/01-home.cy.js | 37 +- .../cypress/e2e/1-getting-started/todo.cy.js | 221 +- .../e2e/2-advanced-examples/actions.cy.js | 459 +- .../e2e/2-advanced-examples/aliasing.cy.js | 54 +- .../e2e/2-advanced-examples/assertions.cy.js | 314 +- .../e2e/2-advanced-examples/connectors.cy.js | 167 +- .../e2e/2-advanced-examples/cookies.cy.js | 112 +- .../e2e/2-advanced-examples/cypress_api.cy.js | 330 +- .../e2e/2-advanced-examples/files.cy.js | 130 +- .../2-advanced-examples/local_storage.cy.js | 94 +- .../e2e/2-advanced-examples/location.cy.js | 54 +- .../e2e/2-advanced-examples/misc.cy.js | 162 +- .../e2e/2-advanced-examples/navigation.cy.js | 86 +- .../network_requests.cy.js | 290 +- .../e2e/2-advanced-examples/querying.cy.js | 160 +- .../spies_stubs_clocks.cy.js | 315 +- .../e2e/2-advanced-examples/traversal.cy.js | 178 +- .../e2e/2-advanced-examples/utilities.cy.js | 174 +- .../e2e/2-advanced-examples/viewport.cy.js | 96 +- .../e2e/2-advanced-examples/waiting.cy.js | 50 +- .../e2e/2-advanced-examples/window.cy.js | 34 +- client/cypress/fixtures/profile.json | 2 +- client/cypress/plugins/index.js | 6 +- client/cypress/support/e2e.js | 2 +- client/cypress/tsconfig.json | 8 +- client/dev-dist/registerSW.js | 3 +- client/dev-dist/sw.js | 70 +- client/dev-dist/workbox-b5f7729d.js | 6336 ++++++------ client/src/App/App.container.jsx | 69 +- client/src/App/App.jsx | 410 +- client/src/App/App.styles.scss | 1 - client/src/App/themeProvider.js | 67 +- .../src/assets/promanager/ios/Contents.json | 2 +- client/src/components/PrivateRoute.jsx | 22 +- client/src/components/_test/test.page.jsx | 41 +- .../accounting-payables-table.component.jsx | 386 +- .../accounting-payments-table.component.jsx | 392 +- ...accounting-receivables-table.component.jsx | 418 +- .../src/components/alert/alert.component.jsx | 4 +- .../components/alert/alert.component.test.js | 24 +- .../allocations-assignment.component.jsx | 111 +- .../allocations-assignment.component.test.js | 52 +- .../allocations-assignment.container.jsx | 80 +- .../allocations-bulk-assignment.component.jsx | 104 +- .../allocations-bulk-assignment.container.jsx | 81 +- .../allocations-employee-label.component.jsx | 22 +- .../allocations-employee-label.container.jsx | 47 +- .../audit-trail-list.component.jsx | 150 +- .../audit-trail-list.container.jsx | 60 +- .../email-audit-trail-list.component.jsx | 114 +- .../audit-trail-values.component.jsx | 46 +- .../barcode-popup/barcode-popup.component.jsx | 30 +- .../bill-cm-returns-table.component.jsx | 242 +- .../bill-cm-returns-table.styles.scss | 2 +- .../bill-delete-button.component.jsx | 141 +- .../bill-detail-edit-component.jsx | 419 +- .../bill-detail-edit-return.component.jsx | 329 +- .../bill-detail-edit.container.jsx | 60 +- .../bill-enter-modal.container.jsx | 831 +- .../bill-form-lines-extended.component.jsx | 224 +- ...form-lines.extended.formitem.component.jsx | 468 +- .../bill-form/bill-form.component.jsx | 258 +- .../bill-form/bill-form.container.jsx | 120 +- .../bill-form/bill-form.lines.component.jsx | 1507 ++- .../bill-form/bill-form.totals.utility.js | 69 +- .../bill-inventory-table.component.jsx | 290 +- .../bill-inventory-table.styles.scss | 2 +- .../bill-line-search-select.component.jsx | 149 +- .../bill-mark-exported-button.component.jsx | 156 +- .../bill-print-button.component.jsx | 68 +- .../bill-reexport-button.component.jsx | 121 +- .../billline-add-inventory.component.jsx | 257 +- .../bills-list-table.component.jsx | 409 +- .../bills-vendors-list.component.jsx | 211 +- .../breadcrumbs/breadcrumbs.component.jsx | 105 +- .../ca-bc-etf-table-modal.container.jsx | 154 +- .../ca-bc-etf-table.modal.component.jsx | 62 +- .../ca-bc-pvrt-calculator.component.jsx | 83 +- .../card-payment-modal.component..jsx | 631 +- .../card-payment-modal.container..jsx | 79 +- .../chat-affix/chat-affix.container.jsx | 169 +- .../chat-archive-button.component.jsx | 46 +- .../chat-conversation-list.component.jsx | 181 +- ...chat-conversation-title-tags.component.jsx | 98 +- .../chat-conversation-title.component.jsx | 30 +- .../chat-conversation.component.jsx | 35 +- .../chat-conversation.container.jsx | 140 +- .../chat-label/chat-label.component.jsx | 117 +- .../chat-media-selector.component.jsx | 148 +- .../chat-message-list.component.jsx | 181 +- .../chat-new-conversation.component.jsx | 82 +- .../chat-open-button.component.jsx | 69 +- .../chat-popup/chat-popup.component.jsx | 199 +- .../chat-presets/chat-presets.component.jsx | 52 +- .../chat-print-button.component.jsx | 70 +- .../chat-send-message.component.jsx | 176 +- .../chat-tag-ro/chat-tag-ro.component.jsx | 70 +- .../chat-tag-ro/chat-tag-ro.container.jsx | 102 +- .../checkbox/checkbox.component.jsx | 36 +- .../config-form-components.component.jsx | 26 +- .../config-form-types.js | 10 +- .../rate/rate.component.jsx | 34 +- .../slider/slider.component.jsx | 34 +- .../text/text.component.jsx | 34 +- .../textarea/textarea.component.jsx | 34 +- .../conflict/conflict.component.jsx | 56 +- .../contract-cars/contract-cars.component.jsx | 263 +- .../contract-cars/contract-cars.container.jsx | 58 +- .../contract-convert-to-ro.component.jsx | 746 +- .../contract-courtesy-car-block.component.jsx | 50 +- .../contract-form-job-prefill.component.jsx | 76 +- .../contract-form/contract-form.component.jsx | 641 +- .../contract-job-block.component.jsx | 48 +- .../contract-jobs/contract-jobs.component.jsx | 330 +- .../contract-jobs/contract-jobs.container.jsx | 58 +- ...ntract-license-decode-button.component.jsx | 196 +- .../contract-status-select.component.jsx | 52 +- .../contracts-find-modal.component.jsx | 54 +- .../contracts-find-modal.container.jsx | 274 +- .../contracts-list.component.jsx | 372 +- ...ontracts-rates-change-button.component.jsx | 56 +- .../courtesy-car-contract-list.component.jsx | 176 +- .../courtesy-car-form.component.jsx | 638 +- .../courtesy-car-fuel-select.component.jsx | 120 +- ...ourtesy-car-readiness-select.component.jsx | 56 +- .../courtesy-car-return-modal.component.jsx | 86 +- .../courtesy-car-return-modal.container.jsx | 141 +- .../courtesy-car-status-select.component.jsx | 66 +- .../courtesy-cars-list.component.jsx | 529 +- .../csi-response-form.container.jsx | 90 +- .../csi-response-list-paginated.component.jsx | 230 +- .../job-lifecycle-dashboard.component.jsx | 305 +- .../monthly-employee-efficiency.component.jsx | 235 +- .../monthly-job-costing.component.jsx | 276 +- .../monthly-labor-sales.component.jsx | 239 +- .../monthly-parts-sales.component.jsx | 232 +- .../monthly-revenue-graph.component.jsx | 117 +- .../projected-monthly-sales.component.jsx | 63 +- .../refresh-required.component.jsx | 40 +- .../scheduled-in-today.component.jsx | 199 +- .../scheduled-out-today.component.jsx | 786 +- .../total-production-dollars.component.jsx | 38 +- .../total-production-hours.component.jsx | 88 +- .../dashboard-grid.component.jsx | 521 +- .../dashboard-grid/dashboard-grid.utils.js | 2 +- .../data-label/data-label.component.jsx | 78 +- .../dms-allocations-summary-ap.component.jsx | 267 +- .../dms-allocations-summary.component.jsx | 242 +- .../dms-cdk-makes/dms-cdk-makes.component.jsx | 185 +- .../dms-cdk-makes.refetch.component.jsx | 52 +- .../dms-customer-selector.component.jsx | 268 +- .../dms-log-events.component.jsx | 82 +- .../dms-post-form/dms-post-form.component.jsx | 780 +- .../document-editor.component.jsx | 177 +- .../document-editor.container.jsx | 87 +- .../documents-local-upload.component.jsx | 116 +- .../documents-local-upload.utility.js | 132 +- .../documents-upload.component.jsx | 196 +- .../documents-upload.utility.js | 330 +- .../email-documents.component.jsx | 111 +- .../email-overlay/email-overlay.component.jsx | 447 +- .../email-overlay/email-overlay.container.jsx | 424 +- .../email-test/email-test-component.jsx | 184 +- .../employee-search-select.component.jsx | 64 +- .../employee-team-search-select.component.jsx | 52 +- .../error-boundary.component.jsx | 240 +- client/src/components/eula/eula.component.jsx | 469 +- client/src/components/eula/eula.styles.scss | 2 +- .../export-logs-count-display.component.jsx | 38 +- .../feature-wrapper.component.jsx | 28 +- .../form-date-picker.component.jsx | 210 +- .../form-date-time-picker.component.jsx | 73 +- .../form-fields-changed-alert.component.jsx | 108 +- ...form-input-number-calculator.component.jsx | 193 +- .../colorpicker-form-item.component.jsx | 28 +- .../currency-form-item.component.jsx | 24 +- .../email-form-item.component.jsx | 36 +- .../labor-type-form-item.component.jsx | 12 +- .../part-type-form-item.component.jsx | 12 +- .../phone-form-item.component.jsx | 40 +- .../read-only-form-item.component.jsx | 48 +- .../form-list-move-arrows.component.jsx | 34 +- .../global-loading-bar.component.jsx | 90 +- .../global-search-os.component.jsx | 378 +- .../global-search/global-search.component.jsx | 339 +- .../components/header/header.component.jsx | 447 +- .../components/header/header.container.jsx | 36 +- .../help-rescue/help-rescue.component.jsx | 94 +- .../indefinite-loading.component.jsx | 78 +- .../inventory-bill-ro.component.jsx | 109 +- .../inventory-line-delete.component.jsx | 113 +- .../inventory-list.component.jsx | 380 +- .../inventory-list.container.jsx | 81 +- .../inventory-upsert-modal.component.jsx | 95 +- .../inventory-upsert-modal.container.jsx | 198 +- .../job-3rd-party-modal.component.jsx | 375 +- .../job-at-change/job-at-change.component.jsx | 101 +- .../schedule-event.color.component.jsx | 112 +- .../schedule-event.component.jsx | 672 +- .../schedule-event.container.jsx | 133 +- .../schedule-event.note.component.jsx | 119 +- .../job-audit-trail.component.jsx | 268 +- .../job-bills-total.component.jsx | 535 +- .../job-calculate-totals.component.jsx | 106 +- .../job-checklist-form.component.jsx | 565 +- .../job-checklist-template-list.component.jsx | 138 +- .../job-checklist-display.component.jsx | 14 +- .../job-checklist/job-checklist.component.jsx | 26 +- .../job-costing-modal.component.jsx | 43 +- .../job-costing-modal.container.jsx | 103 +- .../job-costing-modal.pie.component.jsx | 108 +- .../job-costing-parts-table.component.jsx | 210 +- .../job-costing-statistics.component.jsx | 86 +- .../job-create-iou.component.jsx | 165 +- .../job-damage-visual.component.jsx | 1197 +-- .../job-detail-cards.component.jsx | 250 +- .../job-detail-cards.damage.component.jsx | 22 +- .../job-detail-cards.dates.component.jsx | 367 +- .../job-detail-cards.documents.component.jsx | 54 +- .../job-detail-cards.insurance.component.jsx | 44 +- .../job-detail-cards.notes.component.jsx | 60 +- .../job-detail-cards.parts.component.jsx | 193 +- .../job-detail-cards.template.component.jsx | 58 +- .../job-detail-cards.totals.component.jsx | 58 +- .../job-detail-cards.vehicle.component.jsx | 30 +- .../job-lines-expander.component.jsx | 243 +- .../job-lines-part-price-change.component.jsx | 165 +- .../job-detail-lines/job-lines.component.jsx | 485 +- .../job-detail-lines/job-lines.container.jsx | 63 +- .../job-employee-assignments.component.jsx | 354 +- .../job-employee-assignments.container.jsx | 154 +- .../job-lifecycle/job-lifecycle.component.jsx | 489 +- .../job-line-bulk-assign.component.jsx | 241 +- .../job-line-convert-to-labor.component.jsx | 386 +- .../job-line-dispatch-button.component.jsx | 278 +- .../job-line-location-popup.component.jsx | 144 +- .../job-line-note-popup.component.jsx | 136 +- .../job-line-status-popup.component.jsx | 133 +- .../job-line-team-assignmnent.component.jsx | 178 +- .../job-lines-bill-reference.component.jsx | 26 +- .../job-lines-preset-button.component.jsx | 79 +- .../job-lines-upsert-modal.component.jsx | 468 +- .../job-lines-upsert-modal.container.jsx | 246 +- .../job-parts-queue-count.component.jsx | 132 +- .../job-payments/job-payments.component.jsx | 426 +- .../job-profile-data-warning.component.jsx | 21 +- ...b-reconciliation-bills-table.component.jsx | 198 +- .../job-reconciliation-modal.component.jsx | 106 +- .../job-reconciliation.modal.container.jsx | 93 +- ...b-reconciliation-parts-table.component.jsx | 229 +- .../job-reconciliation-totals.component.jsx | 183 +- .../job-reconciliation-totals.utility.js | 179 +- .../job-remove-from-parts-queue.component.jsx | 62 +- .../job-scoreboard-add-button.component.jsx | 340 +- .../job-search-select.component.jsx | 196 +- .../job-send-parts-price-change.component.jsx | 54 +- .../job-sync-button.component.jsx | 38 +- .../job-totals-table.component.jsx | 148 +- .../job-totals.table.labor.component.jsx | 368 +- .../job-totals.table.other.component.jsx | 191 +- .../job-totals.table.parts.component.jsx | 228 +- .../job-totals.table.totals.component.jsx | 154 +- .../jobs-admin-change.status.component.jsx | 98 +- .../jobs-admin-class.component.jsx | 133 +- .../jobs-admin-dates.component.jsx | 292 +- .../jobs-admin-delete-intake.component.jsx | 119 +- .../jobs-admin-mark-reexport.component.jsx | 271 +- ...jobs-admin-owner-reassociate.component.jsx | 111 +- .../jobs-admin-remove-ar.component.jsx | 98 +- .../jobs-admin-unvoid.component.jsx | 106 +- ...bs-admin-vehicle-reassociate.component.jsx | 111 +- .../jobs-available-scan.component.jsx | 279 +- ...jobs-available-supplement.estlines.util.js | 121 +- .../jobs-available-supplement.headerfields.js | 460 +- .../jobs-available-table.component.jsx | 438 +- .../jobs-available-table.container.jsx | 1108 +- .../jobs-change-status.component.jsx | 190 +- .../jobs-close-auto-allocate.component.jsx | 156 +- .../jobs-close-export-button.component.jsx | 433 +- .../jobs-close-lines.component.jsx | 384 +- .../jobs-convert-button.component.jsx | 477 +- .../jobs-create-jobs-info.component.jsx | 639 +- .../jobs-create-owner-info.component.jsx | 79 +- .../jobs-create-owner-info.container.jsx | 29 +- .../jobs-create-owner-info.new.component.jsx | 298 +- ...obs-create-owner-info.search.component.jsx | 289 +- .../jobs-create-vehicle-info.component.jsx | 119 +- .../jobs-create-vehicle-info.container.jsx | 32 +- ...jobs-create-vehicle-info.new.component.jsx | 336 +- ...eate-vehicle-info.predefined.component.jsx | 153 +- ...s-create-vehicle-info.search.component.jsx | 243 +- .../predefined-vehicles.js | 7976 +++++++-------- ...jobs-detail-change-estimator.component.jsx | 56 +- ...bs-detail-change-filehandler.component.jsx | 64 +- .../jobs-detail-checklists.component.jsx | 31 +- .../jobs-detail-dates.component.jsx | 251 +- .../jobs-detail-general.component.jsx | 507 +- ...il-header-actions.addtoproduction.util.jsx | 72 +- .../jobs-detail-header-actions.component.jsx | 2000 ++-- ...bs-detail-header-actions.duplicate.util.js | 206 +- ...etail-header-actions.toggle-production.jsx | 114 +- .../jobs-detail-header.component.jsx | 542 +- .../jobs-detail-labor.component.jsx | 181 +- .../jobs-detail-labor.container.jsx | 44 +- .../jobs-detail-pli.component.jsx | 92 +- .../jobs-detail-pli.container.jsx | 106 +- ...s-detail-rates-change-button.component.jsx | 60 +- .../jobs-detail-rates.component.jsx | 158 +- .../jobs-detail-rates.labor.component.jsx | 829 +- .../jobs-detail-rates.materials.component.jsx | 249 +- .../jobs-detail-rates.other.component.jsx | 181 +- .../jobs-detail-rates.parts.component.jsx | 2217 ++--- ...etail-rates.profile-override.component.jsx | 65 +- .../jobs-detail-rates.taxes.component.jsx | 293 +- .../jobs-detail-totals.component.jsx | 18 +- .../job-documents.utility.js | 23 +- ...bs-document-gallery.download.component.jsx | 238 +- ...bs-document-gallery.reassign.component.jsx | 282 +- .../jobs-documents-gallery.component.jsx | 440 +- .../jobs-documents-gallery.container.jsx | 49 +- ...obs-documents-gallery.delete.component.jsx | 101 +- ...s-documents-gallery.external.component.jsx | 86 +- ...-documents-gallery.selectall.component.jsx | 111 +- ...jobs-documents-local-gallery.container.jsx | 349 +- ...cuments-local-gallery.delete.component.jsx | 124 +- .../jobs-documents-local-gallery.download.jsx | 108 +- ...ments-local-gallery.external.component.jsx | 114 +- ...ments-local-gallery.reassign.component.jsx | 152 +- ...ents-local-gallery.selectall.component.jsx | 63 +- .../jobs-export-all-button.component.jsx | 358 +- .../jobs-find-modal.component.jsx | 551 +- .../jobs-find-modal.container.jsx | 162 +- .../jobs-list-paginated.component.jsx | 493 +- .../jobs-list/jobs-list.component.jsx | 758 +- .../jobs-mark-pst-exempt.component.jsx | 88 +- .../jobs-notes/jobs-notes.container.jsx | 110 +- .../jobs-notes/jobs.notes.component.jsx | 352 +- .../jobs-ready-list.component.jsx | 722 +- .../jobs-related-ros.component.jsx | 44 +- ...-allocations-adjustment-edit.component.jsx | 280 +- .../labor-allocations-table.component.jsx | 468 +- ...or-allocations-table.payroll.component.jsx | 575 +- .../labor-allocations-table.utility.js | 69 +- .../layout-form-row.component.jsx | 128 +- .../loading-skeleton.component.jsx | 12 +- .../loading-spinner.component.jsx | 40 +- .../manage-sign-in-button.component.jsx | 36 +- .../components/no-shop/no-shop.component.jsx | 16 +- .../not-found/not-found.component.jsx | 20 +- .../note-upsert-modal.component.jsx | 180 +- .../note-upsert-modal.container.jsx | 238 +- .../notes-preset-button.component.jsx | 71 +- .../owner-detail-form.component.jsx | 186 +- .../owner-detail-form.container.jsx | 179 +- .../owner-detail-jobs.component.jsx | 254 +- .../owner-detail-update-jobs.component.jsx | 92 +- .../owner-find-modal.component.jsx | 216 +- .../owner-find-modal.container.jsx | 119 +- .../owner-name-display.component.jsx | 48 +- .../owner-search-select.component.jsx | 148 +- .../owner-tag-popover.component.jsx | 140 +- .../owners-list/owners-list.component.jsx | 251 +- .../owners-list/owners-list.container.jsx | 61 +- .../partner-ping/partner-ping.component.jsx | 103 +- .../parts-dispatch-expander.component.jsx | 140 +- .../parts-dispatch-table.component.jsx | 251 +- .../parts-order-backorder-eta.component.jsx | 142 +- .../parts-order-cm-received.component.jsx | 112 +- .../parts-order-delete-line.component.jsx | 82 +- ...-order-line-backorder-button.component.jsx | 170 +- .../parts-order-list-table.component.jsx | 864 +- ...rts-order-modal-price-change.component.jsx | 169 +- .../parts-order-modal.component.jsx | 547 +- .../parts-order-modal.container.jsx | 652 +- .../parts-queue-card.component.jsx | 114 +- .../parts-queue-job-lines.component.jsx | 334 +- .../parts-queue.list.component.jsx | 626 +- .../parts-receive-modal.component.jsx | 230 +- .../parts-receive-modal.container.jsx | 186 +- .../parts-status-pie.component.jsx | 152 +- .../payable-export-all-button.component.jsx | 394 +- .../payable-export-button.component.jsx | 385 +- ...yable-mark-selected-exported.component.jsx | 176 +- .../payment-expanded-row.component.jsx | 279 +- .../payment-export-button.component.jsx | 373 +- .../payment-form/payment-form.component.jsx | 269 +- .../payment-form.totalpayments.component.jsx | 74 +- .../payment-mark-export-button-component.jsx | 154 +- ...yment-mark-selected-exported.component.jsx | 177 +- .../payment-modal/payment-modal.container.jsx | 326 +- .../payment-reexport-button.component.jsx | 105 +- .../payments-export-all-button.component.jsx | 366 +- .../payments-generate-link.component.jsx | 260 +- .../payment-list-paginated.component.jsx | 560 +- .../phonebook-form.component.jsx | 298 +- .../phonebook-form.container.jsx | 254 +- .../print-center-item.component.jsx | 133 +- .../print-center-jobs-labels.component.jsx | 212 +- .../print-center-jobs.component.jsx | 220 +- .../print-center-modal.component.jsx | 30 +- .../print-center-modal.container.jsx | 62 +- .../print-center-speed-print.component.jsx | 127 +- .../print-wrapper/print-wrapper.component.jsx | 72 +- .../production-board-filters.component.jsx | 72 +- ...ard-kanban-card-color-legend.component.jsx | 82 +- ...production-board-kanban-card.component.jsx | 370 +- ...n-board-kanban.card-settings.component.jsx | 268 +- .../production-board-kanban.component.jsx | 529 +- .../production-board-kanban.container.jsx | 159 +- .../production-board-kanban.utils.js | 163 +- .../production-list-columns.add.component.jsx | 126 +- ...roduction-list-columns.alert.component.jsx | 136 +- ...on-list-columns.bodypriority.component.jsx | 104 +- ...duction-list-columns.comment.component.jsx | 140 +- .../production-list-columns.data.jsx | 1133 +-- ...production-list-columns.date.component.jsx | 194 +- ...-list-columns.detailpriority.component.jsx | 105 +- ...n-list-columns.empassignment.component.jsx | 319 +- ...n-list-columns.lastcontacted.component.jsx | 260 +- ...n-list-columns.paintpriority.component.jsx | 104 +- ...n-list-columns.partsreceived.component.jsx | 56 +- ...-list-columns.productionnote.component.jsx | 193 +- ...roduction-list-columns.status.category.jsx | 102 +- ...oduction-list-columns.status.component.jsx | 113 +- ...ution-list-columns.touchtime.component.jsx | 45 +- .../production-list-detail.component.jsx | 297 +- ...tion-list-save-config-button.component.jsx | 163 +- .../production-list-print.component.jsx | 220 +- ...ction-list-table-view-select.component.jsx | 278 +- .../production-list-table.component.jsx | 585 +- .../production-list-table.container.jsx | 122 +- ...uction-list-table.resizeable.component.jsx | 46 +- .../production-remove-button.component.jsx | 74 +- .../production-sublets-manage.component.jsx | 195 +- .../profile-my/profile-my.component.jsx | 237 +- .../profile-shops/profile-shops.component.jsx | 124 +- .../profile-shops/profile-shops.container.jsx | 107 +- .../qbo-authorize/qbo-authorize.component.jsx | 81 +- .../components/rbac-wrapper/rbac-defaults.js | 110 +- .../rbac-wrapper/rbac-wrapper.component.jsx | 71 +- ...center-modal-filters-sorters-component.jsx | 778 +- .../report-center-modal-utils.js | 209 +- .../report-center-modal.component.jsx | 578 +- .../report-center-modal.container.jsx | 60 +- .../schedule-ats-summary.component.jsx | 74 +- .../schedule-block-day.component.jsx | 108 +- ...hedule-calendar-header-graph.component.jsx | 137 +- .../schedule-calendar-header.component.jsx | 379 +- .../schedule-calendar-util.js | 48 +- .../scheduler-calendar-wrapper.component.jsx | 61 +- .../schedule-calendar.component.jsx | 284 +- .../schedule-calendar.container.jsx | 120 +- .../schedule-day-view.component.jsx | 20 +- .../schedule-day-view.container.jsx | 91 +- ...e-existing-appointments-list.component.jsx | 71 +- .../schedule-job-modal.component.jsx | 357 +- .../schedule-job-modal.container.jsx | 437 +- .../schedule-manual-event.component.jsx | 287 +- .../schedule-production-list.component.jsx | 111 +- .../schedule-verify-integrity.component.jsx | 91 +- .../scoreboard-chart/chart-custom-tooltip.jsx | 56 +- .../scoreboard-chart.component.jsx | 263 +- .../scoreboard-day-stats.component.jsx | 76 +- .../scoreboard-display.component.jsx | 195 +- .../scoreboard-entry-edit.component.jsx | 206 +- .../scoreboard-jobs-list.component.jsx | 327 +- .../scoreboard-last-days.component.jsx | 47 +- .../scorebard-remove-button.component.jsx | 76 +- .../scoreboard-targets-table.component.jsx | 147 +- .../scoreboard-targets-table.util.js | 64 +- .../chart-custom-tooltip.jsx | 44 +- ...scoreboard-timetickets.chart.component.jsx | 62 +- .../scoreboard-timetickets.component.jsx | 667 +- ...scoreboard-timetickets.stats.component.jsx | 1124 +-- ...rd-timetickets.targets-table.component.jsx | 500 +- .../scoreboard-timetickets.bar.component.jsx | 116 +- .../scoreboard-timetickets.component.jsx | 552 +- ...scoreboard-timetickets.stats.component.jsx | 233 +- .../shop-csi-config-form.component.jsx | 34 +- .../shop-csi-config.component.jsx | 83 +- .../shop-employees-add-vacation.component.jsx | 214 +- .../shop-employees-form.component.jsx | 755 +- .../shop-employees-list.component.jsx | 254 +- .../shop-employees.container.jsx | 43 +- .../shop-info/shop-info.component.jsx | 211 +- .../shop-info/shop-info.container.jsx | 143 +- .../shop-info/shop-info.general.component.jsx | 2933 +++--- .../shop-info/shop-info.intake.component.jsx | 658 +- .../shop-info.laborrates.component.jsx | 660 +- .../shop-info.orderstatus.component.jsx | 169 +- .../shop-info/shop-info.parts-scan.jsx | 142 +- .../shop-info/shop-info.rbac.component.jsx | 1611 ++- ...p-info.responsibilitycenters.component.jsx | 8868 ++++++++--------- ....responsibilitycenters.taxes.component.jsx | 4933 ++++----- .../shop-info.rostatus.component.jsx | 774 +- .../shop-info.scheduling.component.jsx | 625 +- .../shop-info.speedprint.component.jsx | 181 +- .../shop-info.task-presets.component.jsx | 442 +- .../shop-sub-status.component.jsx | 50 +- .../shop-employee-teams-member.component.jsx | 8 +- .../shop-employee-teams.form.component.jsx | 771 +- .../shop-teams/shop-employee-teams.list.jsx | 127 +- .../shop-teams/shop-teams.container.jsx | 57 +- .../shop-template-add.component.jsx | 137 +- .../shop-template-delete.component.jsx | 80 +- ...-template-editor-save-button.component.jsx | 96 +- .../shop-template-test-render.component.jsx | 112 +- .../shop-templates-list.container.jsx | 125 +- .../shop-users-auth-edit.component.jsx | 84 +- .../shop-users/shop-users.component.jsx | 126 +- .../sign-in-form/sign-in-form.component.jsx | 185 +- .../tech-header/tech-header.component.jsx | 42 +- .../tech-job-clock-in-form.component.jsx | 152 +- .../tech-job-clock-in-form.container.jsx | 271 +- .../tech-job-clock-out-button.component.jsx | 529 +- .../tech-job-clock-out-delete.component.jsx | 79 +- .../tech-job-clocked-in-list.component.jsx | 151 +- .../tech-job-print-tickets.component.jsx | 241 +- .../tech-job-statistics.component.jsx | 205 +- .../tech-login/tech-login.component.jsx | 136 +- .../tech-lookup-jobs-drawer.component.jsx | 230 +- .../tech-lookup-jobs-list.component.jsx | 384 +- .../tech-sider/tech-sider.component.jsx | 267 +- .../time-tickets-dates-selector.component.jsx | 76 +- .../time-ticket-calculator.component.jsx | 225 +- .../time-ticket-enter-button.component.jsx | 42 +- .../time-ticket-list-team-pay.component.jsx | 456 +- .../time-ticket-list.component.jsx | 725 +- .../time-ticket-modal.component.jsx | 706 +- .../time-ticket-modal.container.jsx | 483 +- .../time-ticket-shift-active.component.jsx | 120 +- .../time-ticket-shift-form.component.jsx | 79 +- .../time-ticket-shift-form.container.jsx | 219 +- .../time-ticket-shift.container.jsx | 130 +- .../time-ticket-task-modal.component.jsx | 311 +- .../time-ticket-task-modal.container.jsx | 257 +- ...ime-tickets-attendance-table.component.jsx | 64 +- .../time-tickets-commit-toggle.component.jsx | 160 +- .../time-tickets-commit.component.jsx | 152 +- .../time-tickets-payroll-table.component.jsx | 64 +- ...me-tickets-summary-employees.component.jsx | 598 +- .../tt-approvals-list.component.jsx | 418 +- .../tt-approvals-list.container.jsx | 88 +- .../tt-approve-button.component.jsx | 163 +- .../update-alert/update-alert.component.jsx | 63 +- .../user-request-pw-reset.styles.scss | 1 - .../user-request-reset-pw.component.jsx | 155 +- .../user-validate-pw-reset.component.jsx | 238 +- .../user-validate-pw-reset.styles.scss | 1 - .../vehicle-detail-form.component.jsx | 258 +- .../vehicle-detail-form.container.jsx | 190 +- .../vehicle-detail-jobs.component.jsx | 237 +- .../vehicle-detail-update-jobs.component.jsx | 80 +- .../vehicle-search-select.component.jsx | 150 +- .../vehicle-tag-popover.component.jsx | 126 +- .../vehicle-vin-display.component.jsx | 24 +- .../vehicles-list/vehicles-list.component.jsx | 229 +- .../vehicles-list/vehicles-list.container.jsx | 61 +- .../vendor-search-select.component.jsx | 40 +- .../vendors-form/vendors-form.component.jsx | 449 +- .../vendors-form/vendors-form.container.jsx | 252 +- .../vendors-list/vendors-list.component.jsx | 191 +- .../vendors-list/vendors-list.container.jsx | 64 +- .../vendors-phonebook-add.component.jsx | 139 +- client/src/firebase/firebase.utils.js | 115 +- client/src/graphql/accounting.queries.js | 270 +- client/src/graphql/allocations.queries.js | 26 +- client/src/graphql/apollo-error-handling.js | 29 +- client/src/graphql/appointments.queries.js | 716 +- client/src/graphql/associations.queries.js | 95 +- client/src/graphql/audit_trail.queries.js | 72 +- client/src/graphql/available-jobs.queries.js | 166 +- client/src/graphql/bill-lines.queries.js | 37 +- client/src/graphql/bills.queries.js | 418 +- client/src/graphql/bodyshop.queries.js | 640 +- client/src/graphql/cccontracts.queries.js | 393 +- client/src/graphql/conversations.queries.js | 182 +- client/src/graphql/courtesy-car.queries.js | 284 +- client/src/graphql/csi.queries.js | 135 +- client/src/graphql/dms.queries.js | 18 +- client/src/graphql/documents.queries.js | 191 +- client/src/graphql/employee_teams.queries.js | 157 +- client/src/graphql/employees.queries.js | 212 +- client/src/graphql/inventory.queries.js | 279 +- .../src/graphql/job-conversations.queries.js | 61 +- client/src/graphql/jobs-lines.queries.js | 570 +- client/src/graphql/jobs.queries.js | 4593 +++++---- client/src/graphql/messages.queries.js | 21 +- client/src/graphql/metadata.queries.js | 24 +- client/src/graphql/notes.queries.js | 124 +- client/src/graphql/owners.queries.js | 294 +- client/src/graphql/parts-dispatch.queries.js | 119 +- client/src/graphql/parts-orders.queries.js | 700 +- .../src/graphql/payment_response.queries.js | 70 +- client/src/graphql/payments.queries.js | 301 +- client/src/graphql/phonebook.queries.js | 134 +- client/src/graphql/schema.js | 2 +- client/src/graphql/scoreboard.queries.js | 188 +- client/src/graphql/search.queries.js | 118 +- client/src/graphql/templates.queries.js | 97 +- client/src/graphql/timetickets.queries.js | 752 +- client/src/graphql/tt-approvals.queries.js | 149 +- client/src/graphql/user.queries.js | 124 +- client/src/graphql/vehicles.queries.js | 230 +- client/src/graphql/vendors.queries.js | 188 +- client/src/index.css | 16 +- client/src/index.jsx | 106 +- .../accounting-payables.container.jsx | 123 +- .../accounting-payments.container.jsx | 125 +- .../accounting-qbo/accounting-qbo.page.jsx | 61 +- .../accounting-receivables.container.jsx | 131 +- .../src/pages/bills/bills.page.component.jsx | 581 +- .../src/pages/bills/bills.page.container.jsx | 117 +- .../contract-create.page.component.jsx | 110 +- .../contract-create.page.container.jsx | 254 +- .../contract-detail.page.component.jsx | 211 +- .../contract-detail.page.container.jsx | 272 +- .../contracts/contracts.page.component.jsx | 16 +- .../contracts/contracts.page.container.jsx | 115 +- .../courtesy-car-create.page.container.jsx | 137 +- .../courtesy-car-detail.page.component.jsx | 37 +- .../courtesy-car-detail.page.container.jsx | 332 +- .../courtesy-cars.page.component.jsx | 10 +- .../courtesy-cars.page.container.jsx | 69 +- client/src/pages/csi/csi.container.page.jsx | 442 +- .../pages/dashboard/dashboard.container.jsx | 59 +- .../src/pages/disclaimer/disclaimer.page.jsx | 34 +- .../dms-payables/dms-payables.container.jsx | 252 +- client/src/pages/dms/dms.container.jsx | 344 +- .../export-logs.page.component.jsx | 353 +- .../export-logs.page.container.jsx | 58 +- client/src/pages/help/help.page.jsx | 10 +- client/src/pages/inventory/inventory.page.jsx | 46 +- .../src/pages/jobs-admin/jobs-admin.page.jsx | 208 +- .../src/pages/jobs-all/jobs-all.container.jsx | 109 +- .../jobs-available.page.container.jsx | 105 +- .../jobs-checklist-view.page.jsx | 204 +- .../pages/jobs-close/jobs-close.component.jsx | 944 +- .../pages/jobs-close/jobs-close.container.jsx | 131 +- .../jobs-create/jobs-create.component.jsx | 307 +- .../jobs-create/jobs-create.container.jsx | 538 +- .../jobs-delivery.page.container.jsx | 127 +- .../jobs-detail.page.component.jsx | 690 +- .../jobs-detail.page.container.jsx | 186 +- .../jobs-intake.page.container.jsx | 144 +- .../src/pages/jobs-ready/jobs-ready.page.jsx | 48 +- client/src/pages/jobs/jobs.page.jsx | 48 +- client/src/pages/landing/landing.page.jsx | 18 +- .../manage-root.page.component.jsx | 27 +- .../manage-root.page.container.jsx | 43 +- .../pages/manage/manage.page.component.jsx | 1054 +- .../pages/manage/manage.page.container.jsx | 43 +- .../owners-detail.page.component.jsx | 26 +- .../owners-detail.page.container.jsx | 111 +- .../pages/owners/owners.page.component.jsx | 2 +- .../pages/owners/owners.page.container.jsx | 44 +- .../parts-queue.page.container.jsx | 48 +- .../payments-all.container.page.jsx | 119 +- .../phonebook/phonebook.page.component.jsx | 377 +- .../phonebook/phonebook.page.container.jsx | 110 +- .../production-board.component.jsx | 2 +- .../production-board.container.jsx | 73 +- .../production-list.component.jsx | 2 +- .../production-list.container.jsx | 50 +- .../pages/profile/profile.container.page.jsx | 38 +- client/src/pages/profile/profile.page.jsx | 18 +- .../reset-password.component.jsx | 13 +- .../schedule/schedule.page.component.jsx | 2 +- .../schedule/schedule.page.container.jsx | 46 +- .../scoreboard/scoreboard.page.container.jsx | 157 +- .../pages/shift-clock/shift-clock.page.jsx | 8 +- .../shop-csi/shop-csi.container.page.jsx | 112 +- .../shop-vendor.page.component.jsx | 68 +- .../shop-vendor.page.container.jsx | 71 +- client/src/pages/shop/shop.page.component.jsx | 148 +- client/src/pages/sign-in/sign-in.page.jsx | 10 +- .../tech-assigned-prod-jobs.component.jsx | 445 +- .../tech-dispatched-parts.page.jsx | 231 +- .../tech-job-clock.component.jsx | 38 +- .../tech-lookup/tech-lookup.container.jsx | 34 +- .../tech-shift-clock.component.jsx | 30 +- client/src/pages/tech/tech.page.component.jsx | 177 +- client/src/pages/tech/tech.page.container.jsx | 46 +- .../temporary-docs.component.jsx | 62 +- .../temporary-docs.container.jsx | 69 +- .../time-tickets/time-tickets.container.jsx | 84 +- .../tt-approvals.page.container.jsx | 64 +- .../vehicles-detail.page.component.jsx | 30 +- .../vehicles-detail.page.container.jsx | 141 +- .../vehicles/vehicles.page.component.jsx | 2 +- .../vehicles/vehicles.page.container.jsx | 38 +- .../redux/application/application.actions.js | 64 +- .../redux/application/application.reducer.js | 200 +- .../redux/application/application.sagas.js | 500 +- .../application/application.selectors.js | 66 +- .../redux/application/application.types.js | 32 +- client/src/redux/email/email.actions.js | 24 +- client/src/redux/email/email.reducer.js | 60 +- client/src/redux/email/email.sagas.js | 14 +- client/src/redux/email/email.selectors.js | 12 +- client/src/redux/email/email.types.js | 10 +- client/src/redux/media/media.actions.js | 50 +- client/src/redux/media/media.reducer.js | 86 +- client/src/redux/media/media.sagas.js | 190 +- client/src/redux/media/media.selectors.js | 2 +- client/src/redux/media/media.types.js | 16 +- .../src/redux/messaging/messaging.actions.js | 30 +- .../src/redux/messaging/messaging.reducer.js | 92 +- client/src/redux/messaging/messaging.sagas.js | 155 +- .../redux/messaging/messaging.selectors.js | 30 +- client/src/redux/messaging/messaging.types.js | 14 +- client/src/redux/modals/modals.actions.js | 12 +- client/src/redux/modals/modals.reducer.js | 88 +- client/src/redux/modals/modals.sagas.js | 8 +- client/src/redux/modals/modals.selectors.js | 92 +- client/src/redux/modals/modals.types.js | 4 +- client/src/redux/root.reducer.js | 32 +- client/src/redux/root.saga.js | 34 +- client/src/redux/store.js | 32 +- client/src/redux/tech/tech.actions.js | 16 +- client/src/redux/tech/tech.reducer.js | 72 +- client/src/redux/tech/tech.sagas.js | 46 +- client/src/redux/tech/tech.selectors.js | 17 +- client/src/redux/tech/tech.types.js | 8 +- client/src/redux/user/user.actions.js | 89 +- client/src/redux/user/user.reducer.js | 236 +- client/src/redux/user/user.sagas.js | 550 +- client/src/redux/user/user.selectors.js | 42 +- client/src/redux/user/user.types.js | 69 +- client/src/reportWebVitals.js | 18 +- client/src/setupTests.js | 6 +- client/src/translations/en_us/common.json | 6604 ++++++------ client/src/translations/es/common.json | 6602 ++++++------ client/src/translations/fr/common.json | 6602 ++++++------ client/src/translations/i18n.js | 28 +- client/src/utils/AuditTrailMappings.js | 100 +- client/src/utils/Ciecaselect.jsx | 128 +- client/src/utils/CleanAxios.js | 30 +- client/src/utils/CurrencyFormatter.jsx | 22 +- client/src/utils/DateFormatter.jsx | 34 +- client/src/utils/DatePickerRanges.js | 111 +- client/src/utils/GraphQLClient.js | 227 +- client/src/utils/PhoneFormatter.jsx | 4 +- client/src/utils/RegisterSw.js | 101 +- client/src/utils/RenderTemplate.js | 766 +- client/src/utils/SSSUtils.js | 59 +- client/src/utils/TemplateConstants.js | 4745 +++++---- client/src/utils/TemplateSpecial.jsx | 2 +- client/src/utils/TestingHelpers.js | 281 +- client/src/utils/aamva.js | 843 +- client/src/utils/arrayHelper.js | 2 +- client/src/utils/asyncConfirm.js | 8 +- client/src/utils/betaHandler.js | 53 +- client/src/utils/create-recent-item.js | 10 +- client/src/utils/criticalPartsScan.js | 12 +- client/src/utils/day.js | 60 +- client/src/utils/eulaize.js | 27 +- client/src/utils/fcm-handler.js | 136 +- client/src/utils/formatbytes.js | 14 +- client/src/utils/graphQLmodifier.js | 587 +- client/src/utils/handleBeta.js | 51 +- client/src/utils/instanceRenderMgr.js | 31 +- client/src/utils/jobReadOnly.js | 4 +- client/src/utils/localmedia.js | 8 +- client/src/utils/prompt.js | 79 +- client/src/utils/sorters.js | 18 +- client/src/utils/undefinedtonull.js | 16 +- client/src/utils/useEffectDebugger.js | 8 +- client/src/utils/useKeyboardShortcut.jsx | 209 +- client/src/utils/useLocalStorage.js | 73 +- client/src/utils/usetraceupdate.jsx | 28 +- client/vite.config.js | 140 +- ecosystem.config.js | 34 +- firebase/firebase.json | 4 +- firebase/functions/index.js | 45 +- job-totals-testing-util.js | 181 +- libs/awsUtils.js | 78 +- os-loader.js | 197 +- package.json | 3 +- server.js | 124 +- server/accounting/pbs/pbs-ap-allocations.js | 438 +- server/accounting/pbs/pbs-constants.js | 23 +- server/accounting/pbs/pbs-job-export.js | 1196 +-- server/accounting/qb-receivables-lines.js | 1793 ++-- server/accounting/qbo/qbo-authorize.js | 35 +- server/accounting/qbo/qbo-callback.js | 120 +- server/accounting/qbo/qbo-payables.js | 673 +- server/accounting/qbo/qbo-payments.js | 864 +- server/accounting/qbo/qbo-receivables.js | 1324 ++- server/accounting/qbo/qbo.js | 15 +- server/accounting/qbxml/qbxml-payables.js | 229 +- server/accounting/qbxml/qbxml-payments.js | 325 +- server/accounting/qbxml/qbxml-receivables.js | 479 +- server/accounting/qbxml/qbxml-utils.js | 64 +- server/accounting/qbxml/qbxmlObject.json | 5 +- server/admin/adminops.js | 147 +- server/ccc/partspricechange.js | 59 +- server/cdk/cdk-calculate-allocations.js | 755 +- server/cdk/cdk-get-makes.js | 179 +- server/cdk/cdk-job-export.js | 2131 ++-- server/cdk/cdk-wsdl.js | 105 +- server/csi/csi.js | 2 +- server/csi/lookup.js | 25 +- server/csi/submit.js | 35 +- server/data/arms.js | 1872 ++-- server/data/autohouse.js | 1823 ++-- server/data/claimscorp.js | 1413 ++- server/data/data.js | 2 +- server/data/kaizen.js | 1303 +-- server/email/sendemail.js | 509 +- server/firebase/firebase-handler.js | 298 +- server/graphql-client/graphql-client.js | 11 +- server/graphql-client/queries.js | 10 +- server/intellipay/aws-secrets-manager.js | 73 +- server/intellipay/intellipay.js | 304 +- server/ioevent/ioevent.js | 64 +- server/job/job-costing.js | 1635 ++- server/job/job-lifecycle.js | 191 +- server/job/job-status-transition.js | 121 +- server/job/job-totals-USA.js | 1935 ++-- server/job/job-totals.js | 1193 ++- server/job/job.js | 22 +- server/media/media.js | 235 +- .../eventAuthorizationMIddleware.js | 12 +- server/middleware/validateAdminMiddleware.js | 20 +- .../validateFirebaseIdTokenMiddleware.js | 87 +- .../withUserGraphQLClientMiddleware.js | 20 +- server/mixdata/mixdata.js | 182 +- server/opensearch/os-handler.js | 438 +- server/parts-scan/parts-scan.js | 74 +- server/payroll/calculate-totals.js | 182 +- server/payroll/claim-task.js | 165 +- server/payroll/pay-all.js | 526 +- server/render/inlinecss.js | 31 +- server/routes/accountingRoutes.js | 10 +- server/routes/adminRoutes.js | 20 +- server/routes/cdkRoutes.js | 6 +- server/routes/csiRoutes.js | 2 +- server/routes/dataRoutes.js | 10 +- server/routes/intellipayRoutes.js | 12 +- server/routes/jobRoutes.js | 28 +- server/routes/mediaRoutes.js | 12 +- server/routes/miscellaneousRoutes.js | 42 +- server/routes/mixDataRoutes.js | 8 +- server/routes/notificationsRoutes.js | 8 +- server/routes/payrollRoutes.js | 5 +- server/routes/qboRoutes.js | 14 +- server/routes/renderRoutes.js | 6 +- server/routes/schedulingRoutes.js | 6 +- server/routes/smsRoutes.js | 20 +- server/routes/techRoutes.js | 6 +- server/routes/utilRoutes.js | 8 +- server/scheduling/scheduling-job.js | 510 +- server/sms/receive.js | 252 +- server/sms/send.js | 191 +- server/sms/status.js | 77 +- server/stripe/payment.js | 65 +- server/tasks/tasks.js | 125 +- server/tech/tech.js | 71 +- server/utils/adminEmail.js | 12 +- server/utils/calculateStatusDuration.js | 147 +- server/utils/durationToHumanReadable.js | 34 +- server/utils/getLifecycleStatusColor.js | 14 +- server/utils/instanceMgr.js | 19 +- server/utils/logger.js | 32 +- server/utils/utils.js | 9 +- server/web-sockets/web-socket.js | 397 +- setadmin.js | 38 +- 873 files changed, 111387 insertions(+), 125473 deletions(-) diff --git a/.prettierrc.js b/.prettierrc.js index 4f012c6d9..f2c6fc849 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,16 +1,18 @@ -exports.default = { +const config = { printWidth: 120, useTabs: false, tabWidth: 2, - trailingComma: 'es5', + trailingComma: "none", semi: true, singleQuote: false, bracketSpacing: true, - arrowParens: 'always', + arrowParens: "always", jsxSingleQuote: false, bracketSameLine: false, - endOfLine: 'lf', - importOrder: ['^@core/(.*)$', '^@server/(.*)$', '^@ui/(.*)$', '^[./]'], + endOfLine: "lf", + importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"], importOrderSeparation: true, - importOrderSortSpecifiers: true, + importOrderSortSpecifiers: true }; + +module.exports = config; diff --git a/_reference/Test_CDK_Acct Config.json b/_reference/Test_CDK_Acct Config.json index 44a140906..d8e7da4fc 100644 --- a/_reference/Test_CDK_Acct Config.json +++ b/_reference/Test_CDK_Acct Config.json @@ -567,4 +567,4 @@ "description": "Exempt" } ] -} \ No newline at end of file +} diff --git a/_reference/test-ecoystem.config.js b/_reference/test-ecoystem.config.js index 37bae797c..1bf66e16c 100644 --- a/_reference/test-ecoystem.config.js +++ b/_reference/test-ecoystem.config.js @@ -1,20 +1,20 @@ module.exports = { - apps: [ - { - name: "IO Test API", - cwd: "./io", - script: "./server.js", - env: { - NODE_ENV: "test", - }, - }, + apps: [ + { + name: "IO Test API", + cwd: "./io", + script: "./server.js", + env: { + NODE_ENV: "test" + } + }, - { - name: "Bitbucket Webhook", - script: "./webhook/index.js", - env: { - NODE_ENV: "production", - }, - }, - ], + { + name: "Bitbucket Webhook", + script: "./webhook/index.js", + env: { + NODE_ENV: "production" + } + } + ] }; diff --git a/client/craco.config.js b/client/craco.config.js index 54f46cdb6..87abf5bcf 100644 --- a/client/craco.config.js +++ b/client/craco.config.js @@ -1,10 +1,10 @@ // craco.config.js const TerserPlugin = require("terser-webpack-plugin"); const CracoLessPlugin = require("craco-less"); -const {convertLegacyToken} = require('@ant-design/compatible/lib'); -const {theme} = require('antd/lib'); +const { convertLegacyToken } = require("@ant-design/compatible/lib"); +const { theme } = require("antd/lib"); -const {defaultAlgorithm, defaultSeed} = theme; +const { defaultAlgorithm, defaultSeed } = theme; const mapToken = defaultAlgorithm(defaultSeed); const v4Token = convertLegacyToken(mapToken); @@ -12,43 +12,42 @@ const v4Token = convertLegacyToken(mapToken); // TODO, At the moment we are using less in the Dashboard. Once we remove this we can remove the less processor entirely. module.exports = { - plugins: [ - - { - plugin: CracoLessPlugin, - options: { - lessLoaderOptions: { - lessOptions: { - modifyVars: {...v4Token}, - javascriptEnabled: true, - }, - }, - }, + plugins: [ + { + plugin: CracoLessPlugin, + options: { + lessLoaderOptions: { + lessOptions: { + modifyVars: { ...v4Token }, + javascriptEnabled: true + } + } + } + } + ], + webpack: { + configure: (webpackConfig) => { + return { + ...webpackConfig, + // Required for Dev Server + devServer: { + ...webpackConfig.devServer, + allowedHosts: "all" }, - ], - webpack: { - configure: (webpackConfig) => { - return { - ...webpackConfig, - // Required for Dev Server - devServer: { - ...webpackConfig.devServer, - allowedHosts: 'all', - }, - optimization: { - ...webpackConfig.optimization, - // Workaround for CircleCI bug caused by the number of CPUs shown - // https://github.com/facebook/create-react-app/issues/8320 - minimizer: webpackConfig.optimization.minimizer.map((item) => { - if (item instanceof TerserPlugin) { - item.options.parallel = 2; - } + optimization: { + ...webpackConfig.optimization, + // Workaround for CircleCI bug caused by the number of CPUs shown + // https://github.com/facebook/create-react-app/issues/8320 + minimizer: webpackConfig.optimization.minimizer.map((item) => { + if (item instanceof TerserPlugin) { + item.options.parallel = 2; + } - return item; - }), - }, - }; - }, - }, - devtool: "source-map", + return item; + }) + } + }; + } + }, + devtool: "source-map" }; diff --git a/client/cypress.config.js b/client/cypress.config.js index f227b0b40..1f2d28ce6 100644 --- a/client/cypress.config.js +++ b/client/cypress.config.js @@ -1,17 +1,17 @@ -const {defineConfig} = require('cypress') +const { defineConfig } = require("cypress"); module.exports = defineConfig({ - experimentalStudio: true, - env: { - FIREBASE_USERNAME: 'cypress@imex.test', - FIREBASE_PASSWORD: 'cypress', + experimentalStudio: true, + env: { + FIREBASE_USERNAME: "cypress@imex.test", + FIREBASE_PASSWORD: "cypress" + }, + e2e: { + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + setupNodeEvents(on, config) { + return require("./cypress/plugins/index.js")(on, config); }, - e2e: { - // We've imported your old cypress plugins here. - // You may want to clean this up later by importing these. - setupNodeEvents(on, config) { - return require('./cypress/plugins/index.js')(on, config) - }, - baseUrl: 'http://localhost:3000', - }, -}) + baseUrl: "http://localhost:3000" + } +}); diff --git a/client/cypress/e2e/01-General Render/01-home.cy.js b/client/cypress/e2e/01-General Render/01-home.cy.js index 120baa84f..44202154f 100644 --- a/client/cypress/e2e/01-General Render/01-home.cy.js +++ b/client/cypress/e2e/01-General Render/01-home.cy.js @@ -1,24 +1,19 @@ /// -const {FIREBASE_USERNAME, FIREBASE_PASSWORcD} = Cypress.env(); +const { FIREBASE_USERNAME, FIREBASE_PASSWORcD } = Cypress.env(); describe("Renders the General Page", () => { - beforeEach(() => { - cy.visit("/"); - }); - it("Renders Correctly", () => { - }); - it("Has the Slogan", () => { - cy.findByText("A whole x22new kind of shop management system.").should( - "exist" - ); - /* ==== Generated with Cypress Studio ==== */ - cy.get( - ".ant-menu-item-active > .ant-menu-title-content > .header0-item-block" - ).click(); - cy.get("#email").clear(); - cy.get("#email").type("patrick@imex.dev"); - cy.get("#password").clear(); - cy.get("#password").type("patrick123{enter}"); - cy.get(".ant-form > .ant-btn").click(); - /* ==== End Cypress Studio ==== */ - }); + beforeEach(() => { + cy.visit("/"); + }); + it("Renders Correctly", () => {}); + it("Has the Slogan", () => { + cy.findByText("A whole x22new kind of shop management system.").should("exist"); + /* ==== Generated with Cypress Studio ==== */ + cy.get(".ant-menu-item-active > .ant-menu-title-content > .header0-item-block").click(); + cy.get("#email").clear(); + cy.get("#email").type("patrick@imex.dev"); + cy.get("#password").clear(); + cy.get("#password").type("patrick123{enter}"); + cy.get(".ant-form > .ant-btn").click(); + /* ==== End Cypress Studio ==== */ + }); }); diff --git a/client/cypress/e2e/1-getting-started/todo.cy.js b/client/cypress/e2e/1-getting-started/todo.cy.js index 28c0f35e1..87e609ced 100644 --- a/client/cypress/e2e/1-getting-started/todo.cy.js +++ b/client/cypress/e2e/1-getting-started/todo.cy.js @@ -11,133 +11,114 @@ // please read our getting started guide: // https://on.cypress.io/introduction-to-cypress -describe('example to-do app', () => { +describe("example to-do app", () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit("https://example.cypress.io/todo"); + }); + + it("displays two todo items by default", () => { + // We use the `cy.get()` command to get all elements that match the selector. + // Then, we use `should` to assert that there are two matched items, + // which are the two default items. + cy.get(".todo-list li").should("have.length", 2); + + // We can go even further and check that the default todos each contain + // the correct text. We use the `first` and `last` functions + // to get just the first and last matched elements individually, + // and then perform an assertion with `should`. + cy.get(".todo-list li").first().should("have.text", "Pay electric bill"); + cy.get(".todo-list li").last().should("have.text", "Walk the dog"); + }); + + it("can add new todo items", () => { + // We'll store our item text in a variable so we can reuse it + const newItem = "Feed the cat"; + + // Let's get the input element and use the `type` command to + // input our new list item. After typing the content of our item, + // we need to type the enter key as well in order to submit the input. + // This input has a data-test attribute so we'll use that to select the + // element in accordance with best practices: + // https://on.cypress.io/selecting-elements + cy.get("[data-test=new-todo]").type(`${newItem}{enter}`); + + // Now that we've typed our new item, let's check that it actually was added to the list. + // Since it's the newest item, it should exist as the last element in the list. + // In addition, with the two default items, we should have a total of 3 elements in the list. + // Since assertions yield the element that was asserted on, + // we can chain both of these assertions together into a single statement. + cy.get(".todo-list li").should("have.length", 3).last().should("have.text", newItem); + }); + + it("can check off an item as completed", () => { + // In addition to using the `get` command to get an element by selector, + // we can also use the `contains` command to get an element by its contents. + // However, this will yield the
+ + ))} + +
element in various ways - .should('have.text', 'Column content') - .should('contain', 'Column content') - .should('have.html', 'Column content') - // chai-jquery uses "is()" to check if element matches selector - .should('match', 'td') - // to match text content against a regular expression - // first need to invoke jQuery method text() - // and then match using regular expression - .invoke('text') - .should('match', /column content/i) + describe("Implicit Assertions", () => { + it(".should() - make an assertion about the current subject", () => { + // https://on.cypress.io/should + cy.get(".assertion-table") + .find("tbody tr:last") + .should("have.class", "success") + .find("td") + .first() + // checking the text of the element in various ways + .should("have.text", "Column content") + .should("contain", "Column content") + .should("have.html", "Column content") + // chai-jquery uses "is()" to check if element matches selector + .should("match", "td") + // to match text content against a regular expression + // first need to invoke jQuery method text() + // and then match using regular expression + .invoke("text") + .should("match", /column content/i); - // a better way to check element's text content against a regular expression - // is to use "cy.contains" - // https://on.cypress.io/contains - cy.get('.assertion-table') - .find('tbody tr:last') - // finds first element with text content matching regular expression - .contains('td', /column content/i) - .should('be.visible') + // a better way to check element's text content against a regular expression + // is to use "cy.contains" + // https://on.cypress.io/contains + cy.get(".assertion-table") + .find("tbody tr:last") + // finds first element with text content matching regular expression + .contains("td", /column content/i) + .should("be.visible"); - // for more information about asserting element's text - // see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-element’s-text-contents + // for more information about asserting element's text + // see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-element’s-text-contents + }); + + it(".and() - chain multiple assertions together", () => { + // https://on.cypress.io/and + cy.get(".assertions-link").should("have.class", "active").and("have.attr", "href").and("include", "cypress.io"); + }); + }); + + describe("Explicit Assertions", () => { + // https://on.cypress.io/assertions + it("expect - make an assertion about a specified subject", () => { + // We can use Chai's BDD style assertions + expect(true).to.be.true; + const o = { foo: "bar" }; + + expect(o).to.equal(o); + expect(o).to.deep.equal({ foo: "bar" }); + // matching text using regular expression + expect("FooBar").to.match(/bar$/i); + }); + + it("pass your own callback function to should()", () => { + // Pass a function to should that can have any number + // of explicit assertions within it. + // The ".should(cb)" function will be retried + // automatically until it passes all your explicit assertions or times out. + cy.get(".assertions-p") + .find("p") + .should(($p) => { + // https://on.cypress.io/$ + // return an array of texts from all of the p's + // @ts-ignore TS6133 unused variable + const texts = $p.map((i, el) => Cypress.$(el).text()); + + // jquery map returns jquery object + // and .get() convert this to simple array + const paragraphs = texts.get(); + + // array should have length of 3 + expect(paragraphs, "has 3 paragraphs").to.have.length(3); + + // use second argument to expect(...) to provide clear + // message with each assertion + expect(paragraphs, "has expected text in each paragraph").to.deep.eq([ + "Some text from first p", + "More text from second p", + "And even more text from third p" + ]); + }); + }); + + it("finds element by class name regex", () => { + cy.get(".docs-header") + .find("div") + // .should(cb) callback function will be retried + .should(($div) => { + expect($div).to.have.length(1); + + const className = $div[0].className; + + expect(className).to.match(/heading-/); }) + // .then(cb) callback is not retried, + // it either passes or fails + .then(($div) => { + expect($div, "text content").to.have.text("Introduction"); + }); + }); - it('.and() - chain multiple assertions together', () => { - // https://on.cypress.io/and - cy.get('.assertions-link') - .should('have.class', 'active') - .and('have.attr', 'href') - .and('include', 'cypress.io') - }) - }) + it("can throw any error", () => { + cy.get(".docs-header") + .find("div") + .should(($div) => { + if ($div.length !== 1) { + // you can throw your own errors + throw new Error("Did not find 1 element"); + } - describe('Explicit Assertions', () => { - // https://on.cypress.io/assertions - it('expect - make an assertion about a specified subject', () => { - // We can use Chai's BDD style assertions - expect(true).to.be.true - const o = {foo: 'bar'} + const className = $div[0].className; - expect(o).to.equal(o) - expect(o).to.deep.equal({foo: 'bar'}) - // matching text using regular expression - expect('FooBar').to.match(/bar$/i) - }) + if (!className.match(/heading-/)) { + throw new Error(`Could not find class "heading-" in ${className}`); + } + }); + }); - it('pass your own callback function to should()', () => { - // Pass a function to should that can have any number - // of explicit assertions within it. - // The ".should(cb)" function will be retried - // automatically until it passes all your explicit assertions or times out. - cy.get('.assertions-p') - .find('p') - .should(($p) => { - // https://on.cypress.io/$ - // return an array of texts from all of the p's - // @ts-ignore TS6133 unused variable - const texts = $p.map((i, el) => Cypress.$(el).text()) + it("matches unknown text between two elements", () => { + /** + * Text from the first element. + * @type {string} + */ + let text; - // jquery map returns jquery object - // and .get() convert this to simple array - const paragraphs = texts.get() + /** + * Normalizes passed text, + * useful before comparing text with spaces and different capitalization. + * @param {string} s Text to normalize + */ + const normalizeText = (s) => s.replace(/\s/g, "").toLowerCase(); - // array should have length of 3 - expect(paragraphs, 'has 3 paragraphs').to.have.length(3) + cy.get(".two-elements") + .find(".first") + .then(($first) => { + // save text from the first element + text = normalizeText($first.text()); + }); - // use second argument to expect(...) to provide clear - // message with each assertion - expect(paragraphs, 'has expected text in each paragraph').to.deep.eq([ - 'Some text from first p', - 'More text from second p', - 'And even more text from third p', - ]) - }) - }) + cy.get(".two-elements") + .find(".second") + .should(($div) => { + // we can massage text before comparing + const secondText = normalizeText($div.text()); - it('finds element by class name regex', () => { - cy.get('.docs-header') - .find('div') - // .should(cb) callback function will be retried - .should(($div) => { - expect($div).to.have.length(1) + expect(secondText, "second text").to.equal(text); + }); + }); - const className = $div[0].className + it("assert - assert shape of an object", () => { + const person = { + name: "Joe", + age: 20 + }; - expect(className).to.match(/heading-/) - }) - // .then(cb) callback is not retried, - // it either passes or fails - .then(($div) => { - expect($div, 'text content').to.have.text('Introduction') - }) - }) + assert.isObject(person, "value is object"); + }); - it('can throw any error', () => { - cy.get('.docs-header') - .find('div') - .should(($div) => { - if ($div.length !== 1) { - // you can throw your own errors - throw new Error('Did not find 1 element') - } + it("retries the should callback until assertions pass", () => { + cy.get("#random-number").should(($div) => { + const n = parseFloat($div.text()); - const className = $div[0].className - - if (!className.match(/heading-/)) { - throw new Error(`Could not find class "heading-" in ${className}`) - } - }) - }) - - it('matches unknown text between two elements', () => { - /** - * Text from the first element. - * @type {string} - */ - let text - - /** - * Normalizes passed text, - * useful before comparing text with spaces and different capitalization. - * @param {string} s Text to normalize - */ - const normalizeText = (s) => s.replace(/\s/g, '').toLowerCase() - - cy.get('.two-elements') - .find('.first') - .then(($first) => { - // save text from the first element - text = normalizeText($first.text()) - }) - - cy.get('.two-elements') - .find('.second') - .should(($div) => { - // we can massage text before comparing - const secondText = normalizeText($div.text()) - - expect(secondText, 'second text').to.equal(text) - }) - }) - - it('assert - assert shape of an object', () => { - const person = { - name: 'Joe', - age: 20, - } - - assert.isObject(person, 'value is object') - }) - - it('retries the should callback until assertions pass', () => { - cy.get('#random-number') - .should(($div) => { - const n = parseFloat($div.text()) - - expect(n).to.be.gte(1).and.be.lte(10) - }) - }) - }) -}) + expect(n).to.be.gte(1).and.be.lte(10); + }); + }); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/connectors.cy.js b/client/cypress/e2e/2-advanced-examples/connectors.cy.js index 45909bd9f..3cd60d308 100644 --- a/client/cypress/e2e/2-advanced-examples/connectors.cy.js +++ b/client/cypress/e2e/2-advanced-examples/connectors.cy.js @@ -1,97 +1,96 @@ /// -context('Connectors', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/commands/connectors') - }) +context("Connectors", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/connectors"); + }); - it('.each() - iterate over an array of elements', () => { - // https://on.cypress.io/each - cy.get('.connectors-each-ul>li') - .each(($el, index, $list) => { - console.log($el, index, $list) - }) - }) + it(".each() - iterate over an array of elements", () => { + // https://on.cypress.io/each + cy.get(".connectors-each-ul>li").each(($el, index, $list) => { + console.log($el, index, $list); + }); + }); - it('.its() - get properties on the current subject', () => { - // https://on.cypress.io/its - cy.get('.connectors-its-ul>li') - // calls the 'length' property yielding that value - .its('length') - .should('be.gt', 2) - }) + it(".its() - get properties on the current subject", () => { + // https://on.cypress.io/its + cy.get(".connectors-its-ul>li") + // calls the 'length' property yielding that value + .its("length") + .should("be.gt", 2); + }); - it('.invoke() - invoke a function on the current subject', () => { - // our div is hidden in our script.js - // $('.connectors-div').hide() + it(".invoke() - invoke a function on the current subject", () => { + // our div is hidden in our script.js + // $('.connectors-div').hide() - // https://on.cypress.io/invoke - cy.get('.connectors-div').should('be.hidden') - // call the jquery method 'show' on the 'div.container' - .invoke('show') - .should('be.visible') - }) + // https://on.cypress.io/invoke + cy.get(".connectors-div") + .should("be.hidden") + // call the jquery method 'show' on the 'div.container' + .invoke("show") + .should("be.visible"); + }); - it('.spread() - spread an array as individual args to callback function', () => { - // https://on.cypress.io/spread - const arr = ['foo', 'bar', 'baz'] + it(".spread() - spread an array as individual args to callback function", () => { + // https://on.cypress.io/spread + const arr = ["foo", "bar", "baz"]; - cy.wrap(arr).spread((foo, bar, baz) => { - expect(foo).to.eq('foo') - expect(bar).to.eq('bar') - expect(baz).to.eq('baz') + cy.wrap(arr).spread((foo, bar, baz) => { + expect(foo).to.eq("foo"); + expect(bar).to.eq("bar"); + expect(baz).to.eq("baz"); + }); + }); + + describe(".then()", () => { + it("invokes a callback function with the current subject", () => { + // https://on.cypress.io/then + cy.get(".connectors-list > li").then(($lis) => { + expect($lis, "3 items").to.have.length(3); + expect($lis.eq(0), "first item").to.contain("Walk the dog"); + expect($lis.eq(1), "second item").to.contain("Feed the cat"); + expect($lis.eq(2), "third item").to.contain("Write JavaScript"); + }); + }); + + it("yields the returned value to the next command", () => { + cy.wrap(1) + .then((num) => { + expect(num).to.equal(1); + + return 2; }) - }) + .then((num) => { + expect(num).to.equal(2); + }); + }); - describe('.then()', () => { - it('invokes a callback function with the current subject', () => { - // https://on.cypress.io/then - cy.get('.connectors-list > li') - .then(($lis) => { - expect($lis, '3 items').to.have.length(3) - expect($lis.eq(0), 'first item').to.contain('Walk the dog') - expect($lis.eq(1), 'second item').to.contain('Feed the cat') - expect($lis.eq(2), 'third item').to.contain('Write JavaScript') - }) + it("yields the original subject without return", () => { + cy.wrap(1) + .then((num) => { + expect(num).to.equal(1); + // note that nothing is returned from this callback }) + .then((num) => { + // this callback receives the original unchanged value 1 + expect(num).to.equal(1); + }); + }); - it('yields the returned value to the next command', () => { - cy.wrap(1) - .then((num) => { - expect(num).to.equal(1) - - return 2 - }) - .then((num) => { - expect(num).to.equal(2) - }) + it("yields the value yielded by the last Cypress command inside", () => { + cy.wrap(1) + .then((num) => { + expect(num).to.equal(1); + // note how we run a Cypress command + // the result yielded by this Cypress command + // will be passed to the second ".then" + cy.wrap(2); }) - - it('yields the original subject without return', () => { - cy.wrap(1) - .then((num) => { - expect(num).to.equal(1) - // note that nothing is returned from this callback - }) - .then((num) => { - // this callback receives the original unchanged value 1 - expect(num).to.equal(1) - }) - }) - - it('yields the value yielded by the last Cypress command inside', () => { - cy.wrap(1) - .then((num) => { - expect(num).to.equal(1) - // note how we run a Cypress command - // the result yielded by this Cypress command - // will be passed to the second ".then" - cy.wrap(2) - }) - .then((num) => { - // this callback receives the value yielded by "cy.wrap(2)" - expect(num).to.equal(2) - }) - }) - }) -}) + .then((num) => { + // this callback receives the value yielded by "cy.wrap(2)" + expect(num).to.equal(2); + }); + }); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/cookies.cy.js b/client/cypress/e2e/2-advanced-examples/cookies.cy.js index b62341a36..390ee76e2 100644 --- a/client/cypress/e2e/2-advanced-examples/cookies.cy.js +++ b/client/cypress/e2e/2-advanced-examples/cookies.cy.js @@ -1,77 +1,79 @@ /// -context('Cookies', () => { - beforeEach(() => { - Cypress.Cookies.debug(true) +context("Cookies", () => { + beforeEach(() => { + Cypress.Cookies.debug(true); - cy.visit('https://example.cypress.io/commands/cookies') + cy.visit("https://example.cypress.io/commands/cookies"); - // clear cookies again after visiting to remove - // any 3rd party cookies picked up such as cloudflare - cy.clearCookies() - }) + // clear cookies again after visiting to remove + // any 3rd party cookies picked up such as cloudflare + cy.clearCookies(); + }); - it('cy.getCookie() - get a browser cookie', () => { - // https://on.cypress.io/getcookie - cy.get('#getCookie .set-a-cookie').click() + it("cy.getCookie() - get a browser cookie", () => { + // https://on.cypress.io/getcookie + cy.get("#getCookie .set-a-cookie").click(); - // cy.getCookie() yields a cookie object - cy.getCookie('token').should('have.property', 'value', '123ABC') - }) + // cy.getCookie() yields a cookie object + cy.getCookie("token").should("have.property", "value", "123ABC"); + }); - it('cy.getCookies() - get browser cookies', () => { - // https://on.cypress.io/getcookies - cy.getCookies().should('be.empty') + it("cy.getCookies() - get browser cookies", () => { + // https://on.cypress.io/getcookies + cy.getCookies().should("be.empty"); - cy.get('#getCookies .set-a-cookie').click() + cy.get("#getCookies .set-a-cookie").click(); - // cy.getCookies() yields an array of cookies - cy.getCookies().should('have.length', 1).should((cookies) => { - // each cookie has these properties - expect(cookies[0]).to.have.property('name', 'token') - expect(cookies[0]).to.have.property('value', '123ABC') - expect(cookies[0]).to.have.property('httpOnly', false) - expect(cookies[0]).to.have.property('secure', false) - expect(cookies[0]).to.have.property('domain') - expect(cookies[0]).to.have.property('path') - }) - }) + // cy.getCookies() yields an array of cookies + cy.getCookies() + .should("have.length", 1) + .should((cookies) => { + // each cookie has these properties + expect(cookies[0]).to.have.property("name", "token"); + expect(cookies[0]).to.have.property("value", "123ABC"); + expect(cookies[0]).to.have.property("httpOnly", false); + expect(cookies[0]).to.have.property("secure", false); + expect(cookies[0]).to.have.property("domain"); + expect(cookies[0]).to.have.property("path"); + }); + }); - it('cy.setCookie() - set a browser cookie', () => { - // https://on.cypress.io/setcookie - cy.getCookies().should('be.empty') + it("cy.setCookie() - set a browser cookie", () => { + // https://on.cypress.io/setcookie + cy.getCookies().should("be.empty"); - cy.setCookie('foo', 'bar') + cy.setCookie("foo", "bar"); - // cy.getCookie() yields a cookie object - cy.getCookie('foo').should('have.property', 'value', 'bar') - }) + // cy.getCookie() yields a cookie object + cy.getCookie("foo").should("have.property", "value", "bar"); + }); - it('cy.clearCookie() - clear a browser cookie', () => { - // https://on.cypress.io/clearcookie - cy.getCookie('token').should('be.null') + it("cy.clearCookie() - clear a browser cookie", () => { + // https://on.cypress.io/clearcookie + cy.getCookie("token").should("be.null"); - cy.get('#clearCookie .set-a-cookie').click() + cy.get("#clearCookie .set-a-cookie").click(); - cy.getCookie('token').should('have.property', 'value', '123ABC') + cy.getCookie("token").should("have.property", "value", "123ABC"); - // cy.clearCookies() yields null - cy.clearCookie('token').should('be.null') + // cy.clearCookies() yields null + cy.clearCookie("token").should("be.null"); - cy.getCookie('token').should('be.null') - }) + cy.getCookie("token").should("be.null"); + }); - it('cy.clearCookies() - clear browser cookies', () => { - // https://on.cypress.io/clearcookies - cy.getCookies().should('be.empty') + it("cy.clearCookies() - clear browser cookies", () => { + // https://on.cypress.io/clearcookies + cy.getCookies().should("be.empty"); - cy.get('#clearCookies .set-a-cookie').click() + cy.get("#clearCookies .set-a-cookie").click(); - cy.getCookies().should('have.length', 1) + cy.getCookies().should("have.length", 1); - // cy.clearCookies() yields null - cy.clearCookies() + // cy.clearCookies() yields null + cy.clearCookies(); - cy.getCookies().should('be.empty') - }) -}) + cy.getCookies().should("be.empty"); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/cypress_api.cy.js b/client/cypress/e2e/2-advanced-examples/cypress_api.cy.js index 913d58f4a..1cd9f975e 100644 --- a/client/cypress/e2e/2-advanced-examples/cypress_api.cy.js +++ b/client/cypress/e2e/2-advanced-examples/cypress_api.cy.js @@ -1,202 +1,208 @@ /// -context('Cypress.Commands', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/cypress-api') - }) +context("Cypress.Commands", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); - // https://on.cypress.io/custom-commands + // https://on.cypress.io/custom-commands - it('.add() - create a custom command', () => { - Cypress.Commands.add('console', { - prevSubject: true, - }, (subject, method) => { - // the previous subject is automatically received - // and the commands arguments are shifted + it(".add() - create a custom command", () => { + Cypress.Commands.add( + "console", + { + prevSubject: true + }, + (subject, method) => { + // the previous subject is automatically received + // and the commands arguments are shifted - // allow us to change the console method used - method = method || 'log' + // allow us to change the console method used + method = method || "log"; - // log the subject to the console - // @ts-ignore TS7017 - console[method]('The subject is', subject) + // log the subject to the console + // @ts-ignore TS7017 + console[method]("The subject is", subject); - // whatever we return becomes the new subject - // we don't want to change the subject so - // we return whatever was passed in - return subject - }) + // whatever we return becomes the new subject + // we don't want to change the subject so + // we return whatever was passed in + return subject; + } + ); - // @ts-ignore TS2339 - cy.get('button').console('info').then(($button) => { - // subject is still $button - }) - }) -}) + // @ts-ignore TS2339 + cy.get("button") + .console("info") + .then(($button) => { + // subject is still $button + }); + }); +}); -context('Cypress.Cookies', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/cypress-api') - }) +context("Cypress.Cookies", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); - // https://on.cypress.io/cookies - it('.debug() - enable or disable debugging', () => { - Cypress.Cookies.debug(true) + // https://on.cypress.io/cookies + it(".debug() - enable or disable debugging", () => { + Cypress.Cookies.debug(true); - // Cypress will now log in the console when - // cookies are set or cleared - cy.setCookie('fakeCookie', '123ABC') - cy.clearCookie('fakeCookie') - cy.setCookie('fakeCookie', '123ABC') - cy.clearCookie('fakeCookie') - cy.setCookie('fakeCookie', '123ABC') - }) + // Cypress will now log in the console when + // cookies are set or cleared + cy.setCookie("fakeCookie", "123ABC"); + cy.clearCookie("fakeCookie"); + cy.setCookie("fakeCookie", "123ABC"); + cy.clearCookie("fakeCookie"); + cy.setCookie("fakeCookie", "123ABC"); + }); - it('.preserveOnce() - preserve cookies by key', () => { - // normally cookies are reset after each test - cy.getCookie('fakeCookie').should('not.be.ok') + it(".preserveOnce() - preserve cookies by key", () => { + // normally cookies are reset after each test + cy.getCookie("fakeCookie").should("not.be.ok"); - // preserving a cookie will not clear it when - // the next test starts - cy.setCookie('lastCookie', '789XYZ') - Cypress.Cookies.preserveOnce('lastCookie') - }) + // preserving a cookie will not clear it when + // the next test starts + cy.setCookie("lastCookie", "789XYZ"); + Cypress.Cookies.preserveOnce("lastCookie"); + }); - it('.defaults() - set defaults for all cookies', () => { - // now any cookie with the name 'session_id' will - // not be cleared before each new test runs - Cypress.Cookies.defaults({ - preserve: 'session_id', - }) - }) -}) + it(".defaults() - set defaults for all cookies", () => { + // now any cookie with the name 'session_id' will + // not be cleared before each new test runs + Cypress.Cookies.defaults({ + preserve: "session_id" + }); + }); +}); -context('Cypress.arch', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/cypress-api') - }) +context("Cypress.arch", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); - it('Get CPU architecture name of underlying OS', () => { - // https://on.cypress.io/arch - expect(Cypress.arch).to.exist - }) -}) + it("Get CPU architecture name of underlying OS", () => { + // https://on.cypress.io/arch + expect(Cypress.arch).to.exist; + }); +}); -context('Cypress.config()', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/cypress-api') - }) +context("Cypress.config()", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); - it('Get and set configuration options', () => { - // https://on.cypress.io/config - let myConfig = Cypress.config() + it("Get and set configuration options", () => { + // https://on.cypress.io/config + let myConfig = Cypress.config(); - expect(myConfig).to.have.property('animationDistanceThreshold', 5) - expect(myConfig).to.have.property('baseUrl', null) - expect(myConfig).to.have.property('defaultCommandTimeout', 4000) - expect(myConfig).to.have.property('requestTimeout', 5000) - expect(myConfig).to.have.property('responseTimeout', 30000) - expect(myConfig).to.have.property('viewportHeight', 660) - expect(myConfig).to.have.property('viewportWidth', 1000) - expect(myConfig).to.have.property('pageLoadTimeout', 60000) - expect(myConfig).to.have.property('waitForAnimations', true) + expect(myConfig).to.have.property("animationDistanceThreshold", 5); + expect(myConfig).to.have.property("baseUrl", null); + expect(myConfig).to.have.property("defaultCommandTimeout", 4000); + expect(myConfig).to.have.property("requestTimeout", 5000); + expect(myConfig).to.have.property("responseTimeout", 30000); + expect(myConfig).to.have.property("viewportHeight", 660); + expect(myConfig).to.have.property("viewportWidth", 1000); + expect(myConfig).to.have.property("pageLoadTimeout", 60000); + expect(myConfig).to.have.property("waitForAnimations", true); - expect(Cypress.config('pageLoadTimeout')).to.eq(60000) + expect(Cypress.config("pageLoadTimeout")).to.eq(60000); - // this will change the config for the rest of your tests! - Cypress.config('pageLoadTimeout', 20000) + // this will change the config for the rest of your tests! + Cypress.config("pageLoadTimeout", 20000); - expect(Cypress.config('pageLoadTimeout')).to.eq(20000) + expect(Cypress.config("pageLoadTimeout")).to.eq(20000); - Cypress.config('pageLoadTimeout', 60000) - }) -}) + Cypress.config("pageLoadTimeout", 60000); + }); +}); -context('Cypress.dom', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/cypress-api') - }) +context("Cypress.dom", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); - // https://on.cypress.io/dom - it('.isHidden() - determine if a DOM element is hidden', () => { - let hiddenP = Cypress.$('.dom-p p.hidden').get(0) - let visibleP = Cypress.$('.dom-p p.visible').get(0) + // https://on.cypress.io/dom + it(".isHidden() - determine if a DOM element is hidden", () => { + let hiddenP = Cypress.$(".dom-p p.hidden").get(0); + let visibleP = Cypress.$(".dom-p p.visible").get(0); - // our first paragraph has css class 'hidden' - expect(Cypress.dom.isHidden(hiddenP)).to.be.true - expect(Cypress.dom.isHidden(visibleP)).to.be.false - }) -}) + // our first paragraph has css class 'hidden' + expect(Cypress.dom.isHidden(hiddenP)).to.be.true; + expect(Cypress.dom.isHidden(visibleP)).to.be.false; + }); +}); -context('Cypress.env()', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/cypress-api') - }) +context("Cypress.env()", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); - // We can set environment variables for highly dynamic values + // We can set environment variables for highly dynamic values - // https://on.cypress.io/environment-variables - it('Get environment variables', () => { - // https://on.cypress.io/env - // set multiple environment variables - Cypress.env({ - host: 'veronica.dev.local', - api_server: 'http://localhost:8888/v1/', - }) + // https://on.cypress.io/environment-variables + it("Get environment variables", () => { + // https://on.cypress.io/env + // set multiple environment variables + Cypress.env({ + host: "veronica.dev.local", + api_server: "http://localhost:8888/v1/" + }); - // get environment variable - expect(Cypress.env('host')).to.eq('veronica.dev.local') + // get environment variable + expect(Cypress.env("host")).to.eq("veronica.dev.local"); - // set environment variable - Cypress.env('api_server', 'http://localhost:8888/v2/') - expect(Cypress.env('api_server')).to.eq('http://localhost:8888/v2/') + // set environment variable + Cypress.env("api_server", "http://localhost:8888/v2/"); + expect(Cypress.env("api_server")).to.eq("http://localhost:8888/v2/"); - // get all environment variable - expect(Cypress.env()).to.have.property('host', 'veronica.dev.local') - expect(Cypress.env()).to.have.property('api_server', 'http://localhost:8888/v2/') - }) -}) + // get all environment variable + expect(Cypress.env()).to.have.property("host", "veronica.dev.local"); + expect(Cypress.env()).to.have.property("api_server", "http://localhost:8888/v2/"); + }); +}); -context('Cypress.log', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/cypress-api') - }) +context("Cypress.log", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); - it('Control what is printed to the Command Log', () => { - // https://on.cypress.io/cypress-log - }) -}) + it("Control what is printed to the Command Log", () => { + // https://on.cypress.io/cypress-log + }); +}); -context('Cypress.platform', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/cypress-api') - }) +context("Cypress.platform", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); - it('Get underlying OS name', () => { - // https://on.cypress.io/platform - expect(Cypress.platform).to.be.exist - }) -}) + it("Get underlying OS name", () => { + // https://on.cypress.io/platform + expect(Cypress.platform).to.be.exist; + }); +}); -context('Cypress.version', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/cypress-api') - }) +context("Cypress.version", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); - it('Get current version of Cypress being run', () => { - // https://on.cypress.io/version - expect(Cypress.version).to.be.exist - }) -}) + it("Get current version of Cypress being run", () => { + // https://on.cypress.io/version + expect(Cypress.version).to.be.exist; + }); +}); -context('Cypress.spec', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/cypress-api') - }) +context("Cypress.spec", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/cypress-api"); + }); - it('Get current spec information', () => { - // https://on.cypress.io/spec - // wrap the object so we can inspect it easily by clicking in the command log - cy.wrap(Cypress.spec).should('include.keys', ['name', 'relative', 'absolute']) - }) -}) + it("Get current spec information", () => { + // https://on.cypress.io/spec + // wrap the object so we can inspect it easily by clicking in the command log + cy.wrap(Cypress.spec).should("include.keys", ["name", "relative", "absolute"]); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/files.cy.js b/client/cypress/e2e/2-advanced-examples/files.cy.js index 0c47da8bb..ad1bef656 100644 --- a/client/cypress/e2e/2-advanced-examples/files.cy.js +++ b/client/cypress/e2e/2-advanced-examples/files.cy.js @@ -3,86 +3,84 @@ /// JSON fixture file can be loaded directly using // the built-in JavaScript bundler // @ts-ignore -const requiredExample = require('../../fixtures/example') +const requiredExample = require("../../fixtures/example"); -context('Files', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/commands/files') - }) +context("Files", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/files"); + }); - beforeEach(() => { - // load example.json fixture file and store - // in the test context object - cy.fixture('example.json').as('example') - }) + beforeEach(() => { + // load example.json fixture file and store + // in the test context object + cy.fixture("example.json").as("example"); + }); - it('cy.fixture() - load a fixture', () => { - // https://on.cypress.io/fixture + it("cy.fixture() - load a fixture", () => { + // https://on.cypress.io/fixture - // Instead of writing a response inline you can - // use a fixture file's content. + // Instead of writing a response inline you can + // use a fixture file's content. - // when application makes an Ajax request matching "GET **/comments/*" - // Cypress will intercept it and reply with the object in `example.json` fixture - cy.intercept('GET', '**/comments/*', {fixture: 'example.json'}).as('getComment') + // when application makes an Ajax request matching "GET **/comments/*" + // Cypress will intercept it and reply with the object in `example.json` fixture + cy.intercept("GET", "**/comments/*", { fixture: "example.json" }).as("getComment"); - // we have code that gets a comment when - // the button is clicked in scripts.js - cy.get('.fixture-btn').click() + // we have code that gets a comment when + // the button is clicked in scripts.js + cy.get(".fixture-btn").click(); - cy.wait('@getComment').its('response.body') - .should('have.property', 'name') - .and('include', 'Using fixtures to represent data') - }) + cy.wait("@getComment") + .its("response.body") + .should("have.property", "name") + .and("include", "Using fixtures to represent data"); + }); - it('cy.fixture() or require - load a fixture', function () { - // we are inside the "function () { ... }" - // callback and can use test context object "this" - // "this.example" was loaded in "beforeEach" function callback - expect(this.example, 'fixture in the test context') - .to.deep.equal(requiredExample) + it("cy.fixture() or require - load a fixture", function () { + // we are inside the "function () { ... }" + // callback and can use test context object "this" + // "this.example" was loaded in "beforeEach" function callback + expect(this.example, "fixture in the test context").to.deep.equal(requiredExample); - // or use "cy.wrap" and "should('deep.equal', ...)" assertion - cy.wrap(this.example) - .should('deep.equal', requiredExample) - }) + // or use "cy.wrap" and "should('deep.equal', ...)" assertion + cy.wrap(this.example).should("deep.equal", requiredExample); + }); - it('cy.readFile() - read file contents', () => { - // https://on.cypress.io/readfile + it("cy.readFile() - read file contents", () => { + // https://on.cypress.io/readfile - // You can read a file and yield its contents - // The filePath is relative to your project's root. - cy.readFile('cypress.json').then((json) => { - expect(json).to.be.an('object') - }) - }) + // You can read a file and yield its contents + // The filePath is relative to your project's root. + cy.readFile("cypress.json").then((json) => { + expect(json).to.be.an("object"); + }); + }); - it('cy.writeFile() - write to a file', () => { - // https://on.cypress.io/writefile + it("cy.writeFile() - write to a file", () => { + // https://on.cypress.io/writefile - // You can write to a file + // You can write to a file - // Use a response from a request to automatically - // generate a fixture file for use later - cy.request('https://jsonplaceholder.cypress.io/users') - .then((response) => { - cy.writeFile('cypress/fixtures/users.json', response.body) - }) + // Use a response from a request to automatically + // generate a fixture file for use later + cy.request("https://jsonplaceholder.cypress.io/users").then((response) => { + cy.writeFile("cypress/fixtures/users.json", response.body); + }); - cy.fixture('users').should((users) => { - expect(users[0].name).to.exist - }) + cy.fixture("users").should((users) => { + expect(users[0].name).to.exist; + }); - // JavaScript arrays and objects are stringified - // and formatted into text. - cy.writeFile('cypress/fixtures/profile.json', { - id: 8739, - name: 'Jane', - email: 'jane@example.com', - }) + // JavaScript arrays and objects are stringified + // and formatted into text. + cy.writeFile("cypress/fixtures/profile.json", { + id: 8739, + name: "Jane", + email: "jane@example.com" + }); - cy.fixture('profile').should((profile) => { - expect(profile.name).to.eq('Jane') - }) - }) -}) + cy.fixture("profile").should((profile) => { + expect(profile.name).to.eq("Jane"); + }); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/local_storage.cy.js b/client/cypress/e2e/2-advanced-examples/local_storage.cy.js index b44a014c9..7007d02bf 100644 --- a/client/cypress/e2e/2-advanced-examples/local_storage.cy.js +++ b/client/cypress/e2e/2-advanced-examples/local_storage.cy.js @@ -1,52 +1,58 @@ /// -context('Local Storage', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/commands/local-storage') - }) - // Although local storage is automatically cleared - // in between tests to maintain a clean state - // sometimes we need to clear the local storage manually +context("Local Storage", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/local-storage"); + }); + // Although local storage is automatically cleared + // in between tests to maintain a clean state + // sometimes we need to clear the local storage manually - it('cy.clearLocalStorage() - clear all data in local storage', () => { - // https://on.cypress.io/clearlocalstorage - cy.get('.ls-btn').click().should(() => { - expect(localStorage.getItem('prop1')).to.eq('red') - expect(localStorage.getItem('prop2')).to.eq('blue') - expect(localStorage.getItem('prop3')).to.eq('magenta') - }) + it("cy.clearLocalStorage() - clear all data in local storage", () => { + // https://on.cypress.io/clearlocalstorage + cy.get(".ls-btn") + .click() + .should(() => { + expect(localStorage.getItem("prop1")).to.eq("red"); + expect(localStorage.getItem("prop2")).to.eq("blue"); + expect(localStorage.getItem("prop3")).to.eq("magenta"); + }); - // clearLocalStorage() yields the localStorage object - cy.clearLocalStorage().should((ls) => { - expect(ls.getItem('prop1')).to.be.null - expect(ls.getItem('prop2')).to.be.null - expect(ls.getItem('prop3')).to.be.null - }) + // clearLocalStorage() yields the localStorage object + cy.clearLocalStorage().should((ls) => { + expect(ls.getItem("prop1")).to.be.null; + expect(ls.getItem("prop2")).to.be.null; + expect(ls.getItem("prop3")).to.be.null; + }); - cy.get('.ls-btn').click().should(() => { - expect(localStorage.getItem('prop1')).to.eq('red') - expect(localStorage.getItem('prop2')).to.eq('blue') - expect(localStorage.getItem('prop3')).to.eq('magenta') - }) + cy.get(".ls-btn") + .click() + .should(() => { + expect(localStorage.getItem("prop1")).to.eq("red"); + expect(localStorage.getItem("prop2")).to.eq("blue"); + expect(localStorage.getItem("prop3")).to.eq("magenta"); + }); - // Clear key matching string in Local Storage - cy.clearLocalStorage('prop1').should((ls) => { - expect(ls.getItem('prop1')).to.be.null - expect(ls.getItem('prop2')).to.eq('blue') - expect(ls.getItem('prop3')).to.eq('magenta') - }) + // Clear key matching string in Local Storage + cy.clearLocalStorage("prop1").should((ls) => { + expect(ls.getItem("prop1")).to.be.null; + expect(ls.getItem("prop2")).to.eq("blue"); + expect(ls.getItem("prop3")).to.eq("magenta"); + }); - cy.get('.ls-btn').click().should(() => { - expect(localStorage.getItem('prop1')).to.eq('red') - expect(localStorage.getItem('prop2')).to.eq('blue') - expect(localStorage.getItem('prop3')).to.eq('magenta') - }) + cy.get(".ls-btn") + .click() + .should(() => { + expect(localStorage.getItem("prop1")).to.eq("red"); + expect(localStorage.getItem("prop2")).to.eq("blue"); + expect(localStorage.getItem("prop3")).to.eq("magenta"); + }); - // Clear keys matching regex in Local Storage - cy.clearLocalStorage(/prop1|2/).should((ls) => { - expect(ls.getItem('prop1')).to.be.null - expect(ls.getItem('prop2')).to.be.null - expect(ls.getItem('prop3')).to.eq('magenta') - }) - }) -}) + // Clear keys matching regex in Local Storage + cy.clearLocalStorage(/prop1|2/).should((ls) => { + expect(ls.getItem("prop1")).to.be.null; + expect(ls.getItem("prop2")).to.be.null; + expect(ls.getItem("prop3")).to.eq("magenta"); + }); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/location.cy.js b/client/cypress/e2e/2-advanced-examples/location.cy.js index 165cdec94..f5e6230d6 100644 --- a/client/cypress/e2e/2-advanced-examples/location.cy.js +++ b/client/cypress/e2e/2-advanced-examples/location.cy.js @@ -1,32 +1,32 @@ /// -context('Location', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/commands/location') - }) +context("Location", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/location"); + }); - it('cy.hash() - get the current URL hash', () => { - // https://on.cypress.io/hash - cy.hash().should('be.empty') - }) + it("cy.hash() - get the current URL hash", () => { + // https://on.cypress.io/hash + cy.hash().should("be.empty"); + }); - it('cy.location() - get window.location', () => { - // https://on.cypress.io/location - cy.location().should((location) => { - expect(location.hash).to.be.empty - expect(location.href).to.eq('https://example.cypress.io/commands/location') - expect(location.host).to.eq('example.cypress.io') - expect(location.hostname).to.eq('example.cypress.io') - expect(location.origin).to.eq('https://example.cypress.io') - expect(location.pathname).to.eq('/commands/location') - expect(location.port).to.eq('') - expect(location.protocol).to.eq('https:') - expect(location.search).to.be.empty - }) - }) + it("cy.location() - get window.location", () => { + // https://on.cypress.io/location + cy.location().should((location) => { + expect(location.hash).to.be.empty; + expect(location.href).to.eq("https://example.cypress.io/commands/location"); + expect(location.host).to.eq("example.cypress.io"); + expect(location.hostname).to.eq("example.cypress.io"); + expect(location.origin).to.eq("https://example.cypress.io"); + expect(location.pathname).to.eq("/commands/location"); + expect(location.port).to.eq(""); + expect(location.protocol).to.eq("https:"); + expect(location.search).to.be.empty; + }); + }); - it('cy.url() - get the current URL', () => { - // https://on.cypress.io/url - cy.url().should('eq', 'https://example.cypress.io/commands/location') - }) -}) + it("cy.url() - get the current URL", () => { + // https://on.cypress.io/url + cy.url().should("eq", "https://example.cypress.io/commands/location"); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/misc.cy.js b/client/cypress/e2e/2-advanced-examples/misc.cy.js index 905261824..9ef98ae41 100644 --- a/client/cypress/e2e/2-advanced-examples/misc.cy.js +++ b/client/cypress/e2e/2-advanced-examples/misc.cy.js @@ -1,106 +1,98 @@ /// -context('Misc', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/commands/misc') - }) +context("Misc", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/misc"); + }); - it('.end() - end the command chain', () => { - // https://on.cypress.io/end + it(".end() - end the command chain", () => { + // https://on.cypress.io/end - // cy.end is useful when you want to end a chain of commands - // and force Cypress to re-query from the root element - cy.get('.misc-table').within(() => { - // ends the current chain and yields null - cy.contains('Cheryl').click().end() + // cy.end is useful when you want to end a chain of commands + // and force Cypress to re-query from the root element + cy.get(".misc-table").within(() => { + // ends the current chain and yields null + cy.contains("Cheryl").click().end(); - // queries the entire table again - cy.contains('Charles').click() - }) - }) + // queries the entire table again + cy.contains("Charles").click(); + }); + }); - it('cy.exec() - execute a system command', () => { - // execute a system command. - // so you can take actions necessary for - // your test outside the scope of Cypress. - // https://on.cypress.io/exec + it("cy.exec() - execute a system command", () => { + // execute a system command. + // so you can take actions necessary for + // your test outside the scope of Cypress. + // https://on.cypress.io/exec - // we can use Cypress.platform string to - // select appropriate command - // https://on.cypress/io/platform - cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`) + // we can use Cypress.platform string to + // select appropriate command + // https://on.cypress/io/platform + cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`); - // on CircleCI Windows build machines we have a failure to run bash shell - // https://github.com/cypress-io/cypress/issues/5169 - // so skip some of the tests by passing flag "--env circle=true" - const isCircleOnWindows = Cypress.platform === 'win32' && Cypress.env('circle') + // on CircleCI Windows build machines we have a failure to run bash shell + // https://github.com/cypress-io/cypress/issues/5169 + // so skip some of the tests by passing flag "--env circle=true" + const isCircleOnWindows = Cypress.platform === "win32" && Cypress.env("circle"); - if (isCircleOnWindows) { - cy.log('Skipping test on CircleCI') + if (isCircleOnWindows) { + cy.log("Skipping test on CircleCI"); - return - } + return; + } - // cy.exec problem on Shippable CI - // https://github.com/cypress-io/cypress/issues/6718 - const isShippable = Cypress.platform === 'linux' && Cypress.env('shippable') + // cy.exec problem on Shippable CI + // https://github.com/cypress-io/cypress/issues/6718 + const isShippable = Cypress.platform === "linux" && Cypress.env("shippable"); - if (isShippable) { - cy.log('Skipping test on ShippableCI') + if (isShippable) { + cy.log("Skipping test on ShippableCI"); - return - } + return; + } - cy.exec('echo Jane Lane') - .its('stdout').should('contain', 'Jane Lane') + cy.exec("echo Jane Lane").its("stdout").should("contain", "Jane Lane"); - if (Cypress.platform === 'win32') { - cy.exec('print cypress.json') - .its('stderr').should('be.empty') - } else { - cy.exec('cat cypress.json') - .its('stderr').should('be.empty') + if (Cypress.platform === "win32") { + cy.exec("print cypress.json").its("stderr").should("be.empty"); + } else { + cy.exec("cat cypress.json").its("stderr").should("be.empty"); - cy.exec('pwd') - .its('code').should('eq', 0) - } - }) + cy.exec("pwd").its("code").should("eq", 0); + } + }); - it('cy.focused() - get the DOM element that has focus', () => { - // https://on.cypress.io/focused - cy.get('.misc-form').find('#name').click() - cy.focused().should('have.id', 'name') + it("cy.focused() - get the DOM element that has focus", () => { + // https://on.cypress.io/focused + cy.get(".misc-form").find("#name").click(); + cy.focused().should("have.id", "name"); - cy.get('.misc-form').find('#description').click() - cy.focused().should('have.id', 'description') - }) + cy.get(".misc-form").find("#description").click(); + cy.focused().should("have.id", "description"); + }); - context('Cypress.Screenshot', function () { - it('cy.screenshot() - take a screenshot', () => { - // https://on.cypress.io/screenshot - cy.screenshot('my-image') - }) + context("Cypress.Screenshot", function () { + it("cy.screenshot() - take a screenshot", () => { + // https://on.cypress.io/screenshot + cy.screenshot("my-image"); + }); - it('Cypress.Screenshot.defaults() - change default config of screenshots', function () { - Cypress.Screenshot.defaults({ - blackout: ['.foo'], - capture: 'viewport', - clip: {x: 0, y: 0, width: 200, height: 200}, - scale: false, - disableTimersAndAnimations: true, - screenshotOnRunFailure: true, - onBeforeScreenshot() { - }, - onAfterScreenshot() { - }, - }) - }) - }) + it("Cypress.Screenshot.defaults() - change default config of screenshots", function () { + Cypress.Screenshot.defaults({ + blackout: [".foo"], + capture: "viewport", + clip: { x: 0, y: 0, width: 200, height: 200 }, + scale: false, + disableTimersAndAnimations: true, + screenshotOnRunFailure: true, + onBeforeScreenshot() {}, + onAfterScreenshot() {} + }); + }); + }); - it('cy.wrap() - wrap an object', () => { - // https://on.cypress.io/wrap - cy.wrap({foo: 'bar'}) - .should('have.property', 'foo') - .and('include', 'bar') - }) -}) + it("cy.wrap() - wrap an object", () => { + // https://on.cypress.io/wrap + cy.wrap({ foo: "bar" }).should("have.property", "foo").and("include", "bar"); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/navigation.cy.js b/client/cypress/e2e/2-advanced-examples/navigation.cy.js index 55303f309..a1993ee36 100644 --- a/client/cypress/e2e/2-advanced-examples/navigation.cy.js +++ b/client/cypress/e2e/2-advanced-examples/navigation.cy.js @@ -1,56 +1,56 @@ /// -context('Navigation', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io') - cy.get('.navbar-nav').contains('Commands').click() - cy.get('.dropdown-menu').contains('Navigation').click() - }) +context("Navigation", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io"); + cy.get(".navbar-nav").contains("Commands").click(); + cy.get(".dropdown-menu").contains("Navigation").click(); + }); - it('cy.go() - go back or forward in the browser\'s history', () => { - // https://on.cypress.io/go + it("cy.go() - go back or forward in the browser's history", () => { + // https://on.cypress.io/go - cy.location('pathname').should('include', 'navigation') + cy.location("pathname").should("include", "navigation"); - cy.go('back') - cy.location('pathname').should('not.include', 'navigation') + cy.go("back"); + cy.location("pathname").should("not.include", "navigation"); - cy.go('forward') - cy.location('pathname').should('include', 'navigation') + cy.go("forward"); + cy.location("pathname").should("include", "navigation"); - // clicking back - cy.go(-1) - cy.location('pathname').should('not.include', 'navigation') + // clicking back + cy.go(-1); + cy.location("pathname").should("not.include", "navigation"); - // clicking forward - cy.go(1) - cy.location('pathname').should('include', 'navigation') - }) + // clicking forward + cy.go(1); + cy.location("pathname").should("include", "navigation"); + }); - it('cy.reload() - reload the page', () => { - // https://on.cypress.io/reload - cy.reload() + it("cy.reload() - reload the page", () => { + // https://on.cypress.io/reload + cy.reload(); - // reload the page without using the cache - cy.reload(true) - }) + // reload the page without using the cache + cy.reload(true); + }); - it('cy.visit() - visit a remote url', () => { - // https://on.cypress.io/visit + it("cy.visit() - visit a remote url", () => { + // https://on.cypress.io/visit - // Visit any sub-domain of your current domain + // Visit any sub-domain of your current domain - // Pass options to the visit - cy.visit('https://example.cypress.io/commands/navigation', { - timeout: 50000, // increase total time for the visit to resolve - onBeforeLoad(contentWindow) { - // contentWindow is the remote page's window object - expect(typeof contentWindow === 'object').to.be.true - }, - onLoad(contentWindow) { - // contentWindow is the remote page's window object - expect(typeof contentWindow === 'object').to.be.true - }, - }) - }) -}) + // Pass options to the visit + cy.visit("https://example.cypress.io/commands/navigation", { + timeout: 50000, // increase total time for the visit to resolve + onBeforeLoad(contentWindow) { + // contentWindow is the remote page's window object + expect(typeof contentWindow === "object").to.be.true; + }, + onLoad(contentWindow) { + // contentWindow is the remote page's window object + expect(typeof contentWindow === "object").to.be.true; + } + }); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/network_requests.cy.js b/client/cypress/e2e/2-advanced-examples/network_requests.cy.js index 02c2ae17a..693e5395d 100644 --- a/client/cypress/e2e/2-advanced-examples/network_requests.cy.js +++ b/client/cypress/e2e/2-advanced-examples/network_requests.cy.js @@ -1,163 +1,165 @@ /// -context('Network Requests', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/commands/network-requests') +context("Network Requests", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/network-requests"); + }); + + // Manage HTTP requests in your app + + it("cy.request() - make an XHR request", () => { + // https://on.cypress.io/request + cy.request("https://jsonplaceholder.cypress.io/comments").should((response) => { + expect(response.status).to.eq(200); + // the server sometimes gets an extra comment posted from another machine + // which gets returned as 1 extra object + expect(response.body).to.have.property("length").and.be.oneOf([500, 501]); + expect(response).to.have.property("headers"); + expect(response).to.have.property("duration"); + }); + }); + + it("cy.request() - verify response using BDD syntax", () => { + cy.request("https://jsonplaceholder.cypress.io/comments").then((response) => { + // https://on.cypress.io/assertions + expect(response).property("status").to.equal(200); + expect(response).property("body").to.have.property("length").and.be.oneOf([500, 501]); + expect(response).to.include.keys("headers", "duration"); + }); + }); + + it("cy.request() with query parameters", () => { + // will execute request + // https://jsonplaceholder.cypress.io/comments?postId=1&id=3 + cy.request({ + url: "https://jsonplaceholder.cypress.io/comments", + qs: { + postId: 1, + id: 3 + } }) + .its("body") + .should("be.an", "array") + .and("have.length", 1) + .its("0") // yields first element of the array + .should("contain", { + postId: 1, + id: 3 + }); + }); - // Manage HTTP requests in your app + it("cy.request() - pass result to the second request", () => { + // first, let's find out the userId of the first user we have + cy.request("https://jsonplaceholder.cypress.io/users?_limit=1") + .its("body") // yields the response object + .its("0") // yields the first element of the returned list + // the above two commands its('body').its('0') + // can be written as its('body.0') + // if you do not care about TypeScript checks + .then((user) => { + expect(user).property("id").to.be.a("number"); + // make a new post on behalf of the user + cy.request("POST", "https://jsonplaceholder.cypress.io/posts", { + userId: user.id, + title: "Cypress Test Runner", + body: "Fast, easy and reliable testing for anything that runs in a browser." + }); + }) + // note that the value here is the returned value of the 2nd request + // which is the new post object + .then((response) => { + expect(response).property("status").to.equal(201); // new entity created + expect(response).property("body").to.contain({ + title: "Cypress Test Runner" + }); - it('cy.request() - make an XHR request', () => { - // https://on.cypress.io/request - cy.request('https://jsonplaceholder.cypress.io/comments') - .should((response) => { - expect(response.status).to.eq(200) - // the server sometimes gets an extra comment posted from another machine - // which gets returned as 1 extra object - expect(response.body).to.have.property('length').and.be.oneOf([500, 501]) - expect(response).to.have.property('headers') - expect(response).to.have.property('duration') - }) - }) + // we don't know the exact post id - only that it will be > 100 + // since JSONPlaceholder has built-in 100 posts + expect(response.body).property("id").to.be.a("number").and.to.be.gt(100); - it('cy.request() - verify response using BDD syntax', () => { - cy.request('https://jsonplaceholder.cypress.io/comments') - .then((response) => { - // https://on.cypress.io/assertions - expect(response).property('status').to.equal(200) - expect(response).property('body').to.have.property('length').and.be.oneOf([500, 501]) - expect(response).to.include.keys('headers', 'duration') - }) - }) + // we don't know the user id here - since it was in above closure + // so in this test just confirm that the property is there + expect(response.body).property("userId").to.be.a("number"); + }); + }); - it('cy.request() with query parameters', () => { - // will execute request - // https://jsonplaceholder.cypress.io/comments?postId=1&id=3 - cy.request({ - url: 'https://jsonplaceholder.cypress.io/comments', - qs: { - postId: 1, - id: 3, - }, + it("cy.request() - save response in the shared test context", () => { + // https://on.cypress.io/variables-and-aliases + cy.request("https://jsonplaceholder.cypress.io/users?_limit=1") + .its("body") + .its("0") // yields the first element of the returned list + .as("user") // saves the object in the test context + .then(function () { + // NOTE 👀 + // By the time this callback runs the "as('user')" command + // has saved the user object in the test context. + // To access the test context we need to use + // the "function () { ... }" callback form, + // otherwise "this" points at a wrong or undefined object! + cy.request("POST", "https://jsonplaceholder.cypress.io/posts", { + userId: this.user.id, + title: "Cypress Test Runner", + body: "Fast, easy and reliable testing for anything that runs in a browser." }) - .its('body') - .should('be.an', 'array') - .and('have.length', 1) - .its('0') // yields first element of the array - .should('contain', { - postId: 1, - id: 3, - }) - }) + .its("body") + .as("post"); // save the new post from the response + }) + .then(function () { + // When this callback runs, both "cy.request" API commands have finished + // and the test context has "user" and "post" objects set. + // Let's verify them. + expect(this.post, "post has the right user id").property("userId").to.equal(this.user.id); + }); + }); - it('cy.request() - pass result to the second request', () => { - // first, let's find out the userId of the first user we have - cy.request('https://jsonplaceholder.cypress.io/users?_limit=1') - .its('body') // yields the response object - .its('0') // yields the first element of the returned list - // the above two commands its('body').its('0') - // can be written as its('body.0') - // if you do not care about TypeScript checks - .then((user) => { - expect(user).property('id').to.be.a('number') - // make a new post on behalf of the user - cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', { - userId: user.id, - title: 'Cypress Test Runner', - body: 'Fast, easy and reliable testing for anything that runs in a browser.', - }) - }) - // note that the value here is the returned value of the 2nd request - // which is the new post object - .then((response) => { - expect(response).property('status').to.equal(201) // new entity created - expect(response).property('body').to.contain({ - title: 'Cypress Test Runner', - }) + it("cy.intercept() - route responses to matching requests", () => { + // https://on.cypress.io/intercept - // we don't know the exact post id - only that it will be > 100 - // since JSONPlaceholder has built-in 100 posts - expect(response.body).property('id').to.be.a('number') - .and.to.be.gt(100) + let message = "whoa, this comment does not exist"; - // we don't know the user id here - since it was in above closure - // so in this test just confirm that the property is there - expect(response.body).property('userId').to.be.a('number') - }) - }) + // Listen to GET to comments/1 + cy.intercept("GET", "**/comments/*").as("getComment"); - it('cy.request() - save response in the shared test context', () => { - // https://on.cypress.io/variables-and-aliases - cy.request('https://jsonplaceholder.cypress.io/users?_limit=1') - .its('body').its('0') // yields the first element of the returned list - .as('user') // saves the object in the test context - .then(function () { - // NOTE 👀 - // By the time this callback runs the "as('user')" command - // has saved the user object in the test context. - // To access the test context we need to use - // the "function () { ... }" callback form, - // otherwise "this" points at a wrong or undefined object! - cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', { - userId: this.user.id, - title: 'Cypress Test Runner', - body: 'Fast, easy and reliable testing for anything that runs in a browser.', - }) - .its('body').as('post') // save the new post from the response - }) - .then(function () { - // When this callback runs, both "cy.request" API commands have finished - // and the test context has "user" and "post" objects set. - // Let's verify them. - expect(this.post, 'post has the right user id').property('userId').to.equal(this.user.id) - }) - }) + // we have code that gets a comment when + // the button is clicked in scripts.js + cy.get(".network-btn").click(); - it('cy.intercept() - route responses to matching requests', () => { - // https://on.cypress.io/intercept + // https://on.cypress.io/wait + cy.wait("@getComment").its("response.statusCode").should("be.oneOf", [200, 304]); - let message = 'whoa, this comment does not exist' + // Listen to POST to comments + cy.intercept("POST", "**/comments").as("postComment"); - // Listen to GET to comments/1 - cy.intercept('GET', '**/comments/*').as('getComment') + // we have code that posts a comment when + // the button is clicked in scripts.js + cy.get(".network-post").click(); + cy.wait("@postComment").should(({ request, response }) => { + expect(request.body).to.include("email"); + expect(request.headers).to.have.property("content-type"); + expect(response && response.body).to.have.property("name", "Using POST in cy.intercept()"); + }); - // we have code that gets a comment when - // the button is clicked in scripts.js - cy.get('.network-btn').click() + // Stub a response to PUT comments/ **** + cy.intercept( + { + method: "PUT", + url: "**/comments/*" + }, + { + statusCode: 404, + body: { error: message }, + headers: { "access-control-allow-origin": "*" }, + delayMs: 500 + } + ).as("putComment"); - // https://on.cypress.io/wait - cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304]) + // we have code that puts a comment when + // the button is clicked in scripts.js + cy.get(".network-put").click(); - // Listen to POST to comments - cy.intercept('POST', '**/comments').as('postComment') + cy.wait("@putComment"); - // we have code that posts a comment when - // the button is clicked in scripts.js - cy.get('.network-post').click() - cy.wait('@postComment').should(({request, response}) => { - expect(request.body).to.include('email') - expect(request.headers).to.have.property('content-type') - expect(response && response.body).to.have.property('name', 'Using POST in cy.intercept()') - }) - - // Stub a response to PUT comments/ **** - cy.intercept({ - method: 'PUT', - url: '**/comments/*', - }, { - statusCode: 404, - body: {error: message}, - headers: {'access-control-allow-origin': '*'}, - delayMs: 500, - }).as('putComment') - - // we have code that puts a comment when - // the button is clicked in scripts.js - cy.get('.network-put').click() - - cy.wait('@putComment') - - // our 404 statusCode logic in scripts.js executed - cy.get('.network-put-comment').should('contain', message) - }) -}) + // our 404 statusCode logic in scripts.js executed + cy.get(".network-put-comment").should("contain", message); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/querying.cy.js b/client/cypress/e2e/2-advanced-examples/querying.cy.js index 809ccd675..35cf7c24a 100644 --- a/client/cypress/e2e/2-advanced-examples/querying.cy.js +++ b/client/cypress/e2e/2-advanced-examples/querying.cy.js @@ -1,114 +1,100 @@ /// -context('Querying', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/commands/querying') - }) +context("Querying", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/querying"); + }); - // The most commonly used query is 'cy.get()', you can - // think of this like the '$' in jQuery + // The most commonly used query is 'cy.get()', you can + // think of this like the '$' in jQuery - it('cy.get() - query DOM elements', () => { - // https://on.cypress.io/get + it("cy.get() - query DOM elements", () => { + // https://on.cypress.io/get - cy.get('#query-btn').should('contain', 'Button') + cy.get("#query-btn").should("contain", "Button"); - cy.get('.query-btn').should('contain', 'Button') + cy.get(".query-btn").should("contain", "Button"); - cy.get('#querying .well>button:first').should('contain', 'Button') - // ↲ - // Use CSS selectors just like jQuery + cy.get("#querying .well>button:first").should("contain", "Button"); + // ↲ + // Use CSS selectors just like jQuery - cy.get('[data-test-id="test-example"]').should('have.class', 'example') + cy.get('[data-test-id="test-example"]').should("have.class", "example"); - // 'cy.get()' yields jQuery object, you can get its attribute - // by invoking `.attr()` method - cy.get('[data-test-id="test-example"]') - .invoke('attr', 'data-test-id') - .should('equal', 'test-example') + // 'cy.get()' yields jQuery object, you can get its attribute + // by invoking `.attr()` method + cy.get('[data-test-id="test-example"]').invoke("attr", "data-test-id").should("equal", "test-example"); - // or you can get element's CSS property - cy.get('[data-test-id="test-example"]') - .invoke('css', 'position') - .should('equal', 'static') + // or you can get element's CSS property + cy.get('[data-test-id="test-example"]').invoke("css", "position").should("equal", "static"); - // or use assertions directly during 'cy.get()' - // https://on.cypress.io/assertions - cy.get('[data-test-id="test-example"]') - .should('have.attr', 'data-test-id', 'test-example') - .and('have.css', 'position', 'static') - }) + // or use assertions directly during 'cy.get()' + // https://on.cypress.io/assertions + cy.get('[data-test-id="test-example"]') + .should("have.attr", "data-test-id", "test-example") + .and("have.css", "position", "static"); + }); - it('cy.contains() - query DOM elements with matching content', () => { - // https://on.cypress.io/contains - cy.get('.query-list') - .contains('bananas') - .should('have.class', 'third') + it("cy.contains() - query DOM elements with matching content", () => { + // https://on.cypress.io/contains + cy.get(".query-list").contains("bananas").should("have.class", "third"); - // we can pass a regexp to `.contains()` - cy.get('.query-list') - .contains(/^b\w+/) - .should('have.class', 'third') + // we can pass a regexp to `.contains()` + cy.get(".query-list").contains(/^b\w+/).should("have.class", "third"); - cy.get('.query-list') - .contains('apples') - .should('have.class', 'first') + cy.get(".query-list").contains("apples").should("have.class", "first"); - // passing a selector to contains will - // yield the selector containing the text - cy.get('#querying') - .contains('ul', 'oranges') - .should('have.class', 'query-list') + // passing a selector to contains will + // yield the selector containing the text + cy.get("#querying").contains("ul", "oranges").should("have.class", "query-list"); - cy.get('.query-button') - .contains('Save Form') - .should('have.class', 'btn') - }) + cy.get(".query-button").contains("Save Form").should("have.class", "btn"); + }); - it('.within() - query DOM elements within a specific element', () => { - // https://on.cypress.io/within - cy.get('.query-form').within(() => { - cy.get('input:first').should('have.attr', 'placeholder', 'Email') - cy.get('input:last').should('have.attr', 'placeholder', 'Password') - }) - }) + it(".within() - query DOM elements within a specific element", () => { + // https://on.cypress.io/within + cy.get(".query-form").within(() => { + cy.get("input:first").should("have.attr", "placeholder", "Email"); + cy.get("input:last").should("have.attr", "placeholder", "Password"); + }); + }); - it('cy.root() - query the root DOM element', () => { - // https://on.cypress.io/root + it("cy.root() - query the root DOM element", () => { + // https://on.cypress.io/root - // By default, root is the document - cy.root().should('match', 'html') + // By default, root is the document + cy.root().should("match", "html"); - cy.get('.query-ul').within(() => { - // In this within, the root is now the ul DOM element - cy.root().should('have.class', 'query-ul') - }) - }) + cy.get(".query-ul").within(() => { + // In this within, the root is now the ul DOM element + cy.root().should("have.class", "query-ul"); + }); + }); - it('best practices - selecting elements', () => { - // https://on.cypress.io/best-practices#Selecting-Elements - cy.get('[data-cy=best-practices-selecting-elements]').within(() => { - // Worst - too generic, no context - cy.get('button').click() + it("best practices - selecting elements", () => { + // https://on.cypress.io/best-practices#Selecting-Elements + cy.get("[data-cy=best-practices-selecting-elements]").within(() => { + // Worst - too generic, no context + cy.get("button").click(); - // Bad. Coupled to styling. Highly subject to change. - cy.get('.btn.btn-large').click() + // Bad. Coupled to styling. Highly subject to change. + cy.get(".btn.btn-large").click(); - // Average. Coupled to the `name` attribute which has HTML semantics. - cy.get('[name=submission]').click() + // Average. Coupled to the `name` attribute which has HTML semantics. + cy.get("[name=submission]").click(); - // Better. But still coupled to styling or JS event listeners. - cy.get('#main').click() + // Better. But still coupled to styling or JS event listeners. + cy.get("#main").click(); - // Slightly better. Uses an ID but also ensures the element - // has an ARIA role attribute - cy.get('#main[role=button]').click() + // Slightly better. Uses an ID but also ensures the element + // has an ARIA role attribute + cy.get("#main[role=button]").click(); - // Much better. But still coupled to text content that may change. - cy.contains('Submit').click() + // Much better. But still coupled to text content that may change. + cy.contains("Submit").click(); - // Best. Insulated from all changes. - cy.get('[data-cy=submit]').click() - }) - }) -}) + // Best. Insulated from all changes. + cy.get("[data-cy=submit]").click(); + }); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/spies_stubs_clocks.cy.js b/client/cypress/e2e/2-advanced-examples/spies_stubs_clocks.cy.js index 4f2479f15..7c86af8fa 100644 --- a/client/cypress/e2e/2-advanced-examples/spies_stubs_clocks.cy.js +++ b/client/cypress/e2e/2-advanced-examples/spies_stubs_clocks.cy.js @@ -2,205 +2,202 @@ // remove no check once Cypress.sinon is typed // https://github.com/cypress-io/cypress/issues/6720 -context('Spies, Stubs, and Clock', () => { - it('cy.spy() - wrap a method in a spy', () => { - // https://on.cypress.io/spy - cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') +context("Spies, Stubs, and Clock", () => { + it("cy.spy() - wrap a method in a spy", () => { + // https://on.cypress.io/spy + cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); - const obj = { - foo() { - }, - } + const obj = { + foo() {} + }; - const spy = cy.spy(obj, 'foo').as('anyArgs') + const spy = cy.spy(obj, "foo").as("anyArgs"); - obj.foo() + obj.foo(); - expect(spy).to.be.called - }) + expect(spy).to.be.called; + }); - it('cy.spy() retries until assertions pass', () => { - cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') + it("cy.spy() retries until assertions pass", () => { + cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); - const obj = { - /** - * Prints the argument passed - * @param x {any} - */ - foo(x) { - console.log('obj.foo called with', x) - }, - } + const obj = { + /** + * Prints the argument passed + * @param x {any} + */ + foo(x) { + console.log("obj.foo called with", x); + } + }; - cy.spy(obj, 'foo').as('foo') + cy.spy(obj, "foo").as("foo"); - setTimeout(() => { - obj.foo('first') - }, 500) + setTimeout(() => { + obj.foo("first"); + }, 500); - setTimeout(() => { - obj.foo('second') - }, 2500) + setTimeout(() => { + obj.foo("second"); + }, 2500); - cy.get('@foo').should('have.been.calledTwice') - }) + cy.get("@foo").should("have.been.calledTwice"); + }); - it('cy.stub() - create a stub and/or replace a function with stub', () => { - // https://on.cypress.io/stub - cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') + it("cy.stub() - create a stub and/or replace a function with stub", () => { + // https://on.cypress.io/stub + cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); - const obj = { - /** - * prints both arguments to the console - * @param a {string} - * @param b {string} - */ - foo(a, b) { - console.log('a', a, 'b', b) - }, - } + const obj = { + /** + * prints both arguments to the console + * @param a {string} + * @param b {string} + */ + foo(a, b) { + console.log("a", a, "b", b); + } + }; - const stub = cy.stub(obj, 'foo').as('foo') + const stub = cy.stub(obj, "foo").as("foo"); - obj.foo('foo', 'bar') + obj.foo("foo", "bar"); - expect(stub).to.be.called - }) + expect(stub).to.be.called; + }); - it('cy.clock() - control time in the browser', () => { - // https://on.cypress.io/clock + it("cy.clock() - control time in the browser", () => { + // https://on.cypress.io/clock - // create the date in UTC so its always the same - // no matter what local timezone the browser is running in - const now = new Date(Date.UTC(2017, 2, 14)).getTime() + // create the date in UTC so its always the same + // no matter what local timezone the browser is running in + const now = new Date(Date.UTC(2017, 2, 14)).getTime(); - cy.clock(now) - cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') - cy.get('#clock-div').click() - .should('have.text', '1489449600') - }) + cy.clock(now); + cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); + cy.get("#clock-div").click().should("have.text", "1489449600"); + }); - it('cy.tick() - move time in the browser', () => { - // https://on.cypress.io/tick + it("cy.tick() - move time in the browser", () => { + // https://on.cypress.io/tick - // create the date in UTC so its always the same - // no matter what local timezone the browser is running in - const now = new Date(Date.UTC(2017, 2, 14)).getTime() + // create the date in UTC so its always the same + // no matter what local timezone the browser is running in + const now = new Date(Date.UTC(2017, 2, 14)).getTime(); - cy.clock(now) - cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') - cy.get('#tick-div').click() - .should('have.text', '1489449600') + cy.clock(now); + cy.visit("https://example.cypress.io/commands/spies-stubs-clocks"); + cy.get("#tick-div").click().should("have.text", "1489449600"); - cy.tick(10000) // 10 seconds passed - cy.get('#tick-div').click() - .should('have.text', '1489449610') - }) + cy.tick(10000); // 10 seconds passed + cy.get("#tick-div").click().should("have.text", "1489449610"); + }); - it('cy.stub() matches depending on arguments', () => { - // see all possible matchers at - // https://sinonjs.org/releases/latest/matchers/ - const greeter = { - /** - * Greets a person - * @param {string} name - */ - greet(name) { - return `Hello, ${name}!` - }, - } + it("cy.stub() matches depending on arguments", () => { + // see all possible matchers at + // https://sinonjs.org/releases/latest/matchers/ + const greeter = { + /** + * Greets a person + * @param {string} name + */ + greet(name) { + return `Hello, ${name}!`; + } + }; - cy.stub(greeter, 'greet') - .callThrough() // if you want non-matched calls to call the real method - .withArgs(Cypress.sinon.match.string).returns('Hi') - .withArgs(Cypress.sinon.match.number).throws(new Error('Invalid name')) + cy.stub(greeter, "greet") + .callThrough() // if you want non-matched calls to call the real method + .withArgs(Cypress.sinon.match.string) + .returns("Hi") + .withArgs(Cypress.sinon.match.number) + .throws(new Error("Invalid name")); - expect(greeter.greet('World')).to.equal('Hi') - // @ts-ignore - expect(() => greeter.greet(42)).to.throw('Invalid name') - expect(greeter.greet).to.have.been.calledTwice + expect(greeter.greet("World")).to.equal("Hi"); + // @ts-ignore + expect(() => greeter.greet(42)).to.throw("Invalid name"); + expect(greeter.greet).to.have.been.calledTwice; - // non-matched calls goes the actual method - // @ts-ignore - expect(greeter.greet()).to.equal('Hello, undefined!') - }) + // non-matched calls goes the actual method + // @ts-ignore + expect(greeter.greet()).to.equal("Hello, undefined!"); + }); - it('matches call arguments using Sinon matchers', () => { - // see all possible matchers at - // https://sinonjs.org/releases/latest/matchers/ - const calculator = { - /** - * returns the sum of two arguments - * @param a {number} - * @param b {number} - */ - add(a, b) { - return a + b - }, - } + it("matches call arguments using Sinon matchers", () => { + // see all possible matchers at + // https://sinonjs.org/releases/latest/matchers/ + const calculator = { + /** + * returns the sum of two arguments + * @param a {number} + * @param b {number} + */ + add(a, b) { + return a + b; + } + }; - const spy = cy.spy(calculator, 'add').as('add') + const spy = cy.spy(calculator, "add").as("add"); - expect(calculator.add(2, 3)).to.equal(5) + expect(calculator.add(2, 3)).to.equal(5); - // if we want to assert the exact values used during the call - expect(spy).to.be.calledWith(2, 3) + // if we want to assert the exact values used during the call + expect(spy).to.be.calledWith(2, 3); - // let's confirm "add" method was called with two numbers - expect(spy).to.be.calledWith(Cypress.sinon.match.number, Cypress.sinon.match.number) + // let's confirm "add" method was called with two numbers + expect(spy).to.be.calledWith(Cypress.sinon.match.number, Cypress.sinon.match.number); - // alternatively, provide the value to match - expect(spy).to.be.calledWith(Cypress.sinon.match(2), Cypress.sinon.match(3)) + // alternatively, provide the value to match + expect(spy).to.be.calledWith(Cypress.sinon.match(2), Cypress.sinon.match(3)); - // match any value - expect(spy).to.be.calledWith(Cypress.sinon.match.any, 3) + // match any value + expect(spy).to.be.calledWith(Cypress.sinon.match.any, 3); - // match any value from a list - expect(spy).to.be.calledWith(Cypress.sinon.match.in([1, 2, 3]), 3) + // match any value from a list + expect(spy).to.be.calledWith(Cypress.sinon.match.in([1, 2, 3]), 3); - /** - * Returns true if the given number is event - * @param {number} x - */ - const isEven = (x) => x % 2 === 0 + /** + * Returns true if the given number is event + * @param {number} x + */ + const isEven = (x) => x % 2 === 0; - // expect the value to pass a custom predicate function - // the second argument to "sinon.match(predicate, message)" is - // shown if the predicate does not pass and assertion fails - expect(spy).to.be.calledWith(Cypress.sinon.match(isEven, 'isEven'), 3) + // expect the value to pass a custom predicate function + // the second argument to "sinon.match(predicate, message)" is + // shown if the predicate does not pass and assertion fails + expect(spy).to.be.calledWith(Cypress.sinon.match(isEven, "isEven"), 3); - /** - * Returns a function that checks if a given number is larger than the limit - * @param {number} limit - * @returns {(x: number) => boolean} - */ - const isGreaterThan = (limit) => (x) => x > limit + /** + * Returns a function that checks if a given number is larger than the limit + * @param {number} limit + * @returns {(x: number) => boolean} + */ + const isGreaterThan = (limit) => (x) => x > limit; - /** - * Returns a function that checks if a given number is less than the limit - * @param {number} limit - * @returns {(x: number) => boolean} - */ - const isLessThan = (limit) => (x) => x < limit + /** + * Returns a function that checks if a given number is less than the limit + * @param {number} limit + * @returns {(x: number) => boolean} + */ + const isLessThan = (limit) => (x) => x < limit; - // you can combine several matchers using "and", "or" - expect(spy).to.be.calledWith( - Cypress.sinon.match.number, - Cypress.sinon.match(isGreaterThan(2), '> 2').and(Cypress.sinon.match(isLessThan(4), '< 4')), - ) + // you can combine several matchers using "and", "or" + expect(spy).to.be.calledWith( + Cypress.sinon.match.number, + Cypress.sinon.match(isGreaterThan(2), "> 2").and(Cypress.sinon.match(isLessThan(4), "< 4")) + ); - expect(spy).to.be.calledWith( - Cypress.sinon.match.number, - Cypress.sinon.match(isGreaterThan(200), '> 200').or(Cypress.sinon.match(3)), - ) + expect(spy).to.be.calledWith( + Cypress.sinon.match.number, + Cypress.sinon.match(isGreaterThan(200), "> 200").or(Cypress.sinon.match(3)) + ); - // matchers can be used from BDD assertions - cy.get('@add').should('have.been.calledWith', - Cypress.sinon.match.number, Cypress.sinon.match(3)) + // matchers can be used from BDD assertions + cy.get("@add").should("have.been.calledWith", Cypress.sinon.match.number, Cypress.sinon.match(3)); - // you can alias matchers for shorter test code - const {match: M} = Cypress.sinon + // you can alias matchers for shorter test code + const { match: M } = Cypress.sinon; - cy.get('@add').should('have.been.calledWith', M.number, M(3)) - }) -}) + cy.get("@add").should("have.been.calledWith", M.number, M(3)); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/traversal.cy.js b/client/cypress/e2e/2-advanced-examples/traversal.cy.js index 35d8108d2..e6b56fde1 100644 --- a/client/cypress/e2e/2-advanced-examples/traversal.cy.js +++ b/client/cypress/e2e/2-advanced-examples/traversal.cy.js @@ -1,121 +1,97 @@ /// -context('Traversal', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/commands/traversal') - }) +context("Traversal", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/traversal"); + }); - it('.children() - get child DOM elements', () => { - // https://on.cypress.io/children - cy.get('.traversal-breadcrumb') - .children('.active') - .should('contain', 'Data') - }) + it(".children() - get child DOM elements", () => { + // https://on.cypress.io/children + cy.get(".traversal-breadcrumb").children(".active").should("contain", "Data"); + }); - it('.closest() - get closest ancestor DOM element', () => { - // https://on.cypress.io/closest - cy.get('.traversal-badge') - .closest('ul') - .should('have.class', 'list-group') - }) + it(".closest() - get closest ancestor DOM element", () => { + // https://on.cypress.io/closest + cy.get(".traversal-badge").closest("ul").should("have.class", "list-group"); + }); - it('.eq() - get a DOM element at a specific index', () => { - // https://on.cypress.io/eq - cy.get('.traversal-list>li') - .eq(1).should('contain', 'siamese') - }) + it(".eq() - get a DOM element at a specific index", () => { + // https://on.cypress.io/eq + cy.get(".traversal-list>li").eq(1).should("contain", "siamese"); + }); - it('.filter() - get DOM elements that match the selector', () => { - // https://on.cypress.io/filter - cy.get('.traversal-nav>li') - .filter('.active').should('contain', 'About') - }) + it(".filter() - get DOM elements that match the selector", () => { + // https://on.cypress.io/filter + cy.get(".traversal-nav>li").filter(".active").should("contain", "About"); + }); - it('.find() - get descendant DOM elements of the selector', () => { - // https://on.cypress.io/find - cy.get('.traversal-pagination') - .find('li').find('a') - .should('have.length', 7) - }) + it(".find() - get descendant DOM elements of the selector", () => { + // https://on.cypress.io/find + cy.get(".traversal-pagination").find("li").find("a").should("have.length", 7); + }); - it('.first() - get first DOM element', () => { - // https://on.cypress.io/first - cy.get('.traversal-table td') - .first().should('contain', '1') - }) + it(".first() - get first DOM element", () => { + // https://on.cypress.io/first + cy.get(".traversal-table td").first().should("contain", "1"); + }); - it('.last() - get last DOM element', () => { - // https://on.cypress.io/last - cy.get('.traversal-buttons .btn') - .last().should('contain', 'Submit') - }) + it(".last() - get last DOM element", () => { + // https://on.cypress.io/last + cy.get(".traversal-buttons .btn").last().should("contain", "Submit"); + }); - it('.next() - get next sibling DOM element', () => { - // https://on.cypress.io/next - cy.get('.traversal-ul') - .contains('apples').next().should('contain', 'oranges') - }) + it(".next() - get next sibling DOM element", () => { + // https://on.cypress.io/next + cy.get(".traversal-ul").contains("apples").next().should("contain", "oranges"); + }); - it('.nextAll() - get all next sibling DOM elements', () => { - // https://on.cypress.io/nextall - cy.get('.traversal-next-all') - .contains('oranges') - .nextAll().should('have.length', 3) - }) + it(".nextAll() - get all next sibling DOM elements", () => { + // https://on.cypress.io/nextall + cy.get(".traversal-next-all").contains("oranges").nextAll().should("have.length", 3); + }); - it('.nextUntil() - get next sibling DOM elements until next el', () => { - // https://on.cypress.io/nextuntil - cy.get('#veggies') - .nextUntil('#nuts').should('have.length', 3) - }) + it(".nextUntil() - get next sibling DOM elements until next el", () => { + // https://on.cypress.io/nextuntil + cy.get("#veggies").nextUntil("#nuts").should("have.length", 3); + }); - it('.not() - remove DOM elements from set of DOM elements', () => { - // https://on.cypress.io/not - cy.get('.traversal-disabled .btn') - .not('[disabled]').should('not.contain', 'Disabled') - }) + it(".not() - remove DOM elements from set of DOM elements", () => { + // https://on.cypress.io/not + cy.get(".traversal-disabled .btn").not("[disabled]").should("not.contain", "Disabled"); + }); - it('.parent() - get parent DOM element from DOM elements', () => { - // https://on.cypress.io/parent - cy.get('.traversal-mark') - .parent().should('contain', 'Morbi leo risus') - }) + it(".parent() - get parent DOM element from DOM elements", () => { + // https://on.cypress.io/parent + cy.get(".traversal-mark").parent().should("contain", "Morbi leo risus"); + }); - it('.parents() - get parent DOM elements from DOM elements', () => { - // https://on.cypress.io/parents - cy.get('.traversal-cite') - .parents().should('match', 'blockquote') - }) + it(".parents() - get parent DOM elements from DOM elements", () => { + // https://on.cypress.io/parents + cy.get(".traversal-cite").parents().should("match", "blockquote"); + }); - it('.parentsUntil() - get parent DOM elements from DOM elements until el', () => { - // https://on.cypress.io/parentsuntil - cy.get('.clothes-nav') - .find('.active') - .parentsUntil('.clothes-nav') - .should('have.length', 2) - }) + it(".parentsUntil() - get parent DOM elements from DOM elements until el", () => { + // https://on.cypress.io/parentsuntil + cy.get(".clothes-nav").find(".active").parentsUntil(".clothes-nav").should("have.length", 2); + }); - it('.prev() - get previous sibling DOM element', () => { - // https://on.cypress.io/prev - cy.get('.birds').find('.active') - .prev().should('contain', 'Lorikeets') - }) + it(".prev() - get previous sibling DOM element", () => { + // https://on.cypress.io/prev + cy.get(".birds").find(".active").prev().should("contain", "Lorikeets"); + }); - it('.prevAll() - get all previous sibling DOM elements', () => { - // https://on.cypress.io/prevall - cy.get('.fruits-list').find('.third') - .prevAll().should('have.length', 2) - }) + it(".prevAll() - get all previous sibling DOM elements", () => { + // https://on.cypress.io/prevall + cy.get(".fruits-list").find(".third").prevAll().should("have.length", 2); + }); - it('.prevUntil() - get all previous sibling DOM elements until el', () => { - // https://on.cypress.io/prevuntil - cy.get('.foods-list').find('#nuts') - .prevUntil('#veggies').should('have.length', 3) - }) + it(".prevUntil() - get all previous sibling DOM elements until el", () => { + // https://on.cypress.io/prevuntil + cy.get(".foods-list").find("#nuts").prevUntil("#veggies").should("have.length", 3); + }); - it('.siblings() - get all sibling DOM elements', () => { - // https://on.cypress.io/siblings - cy.get('.traversal-pills .active') - .siblings().should('have.length', 2) - }) -}) + it(".siblings() - get all sibling DOM elements", () => { + // https://on.cypress.io/siblings + cy.get(".traversal-pills .active").siblings().should("have.length", 2); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/utilities.cy.js b/client/cypress/e2e/2-advanced-examples/utilities.cy.js index d7d8d678f..c0c9e8196 100644 --- a/client/cypress/e2e/2-advanced-examples/utilities.cy.js +++ b/client/cypress/e2e/2-advanced-examples/utilities.cy.js @@ -1,110 +1,108 @@ /// -context('Utilities', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/utilities') - }) +context("Utilities", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/utilities"); + }); - it('Cypress._ - call a lodash method', () => { - // https://on.cypress.io/_ - cy.request('https://jsonplaceholder.cypress.io/users') - .then((response) => { - let ids = Cypress._.chain(response.body).map('id').take(3).value() + it("Cypress._ - call a lodash method", () => { + // https://on.cypress.io/_ + cy.request("https://jsonplaceholder.cypress.io/users").then((response) => { + let ids = Cypress._.chain(response.body).map("id").take(3).value(); - expect(ids).to.deep.eq([1, 2, 3]) - }) - }) + expect(ids).to.deep.eq([1, 2, 3]); + }); + }); - it('Cypress.$ - call a jQuery method', () => { - // https://on.cypress.io/$ - let $li = Cypress.$('.utility-jquery li:first') + it("Cypress.$ - call a jQuery method", () => { + // https://on.cypress.io/$ + let $li = Cypress.$(".utility-jquery li:first"); - cy.wrap($li) - .should('not.have.class', 'active') - .click() - .should('have.class', 'active') - }) + cy.wrap($li).should("not.have.class", "active").click().should("have.class", "active"); + }); - it('Cypress.Blob - blob utilities and base64 string conversion', () => { - // https://on.cypress.io/blob - cy.get('.utility-blob').then(($div) => { - // https://github.com/nolanlawson/blob-util#imgSrcToDataURL - // get the dataUrl string for the javascript-logo - return Cypress.Blob.imgSrcToDataURL('https://example.cypress.io/assets/img/javascript-logo.png', undefined, 'anonymous') - .then((dataUrl) => { - // create an element and set its src to the dataUrl - let img = Cypress.$('', {src: dataUrl}) + it("Cypress.Blob - blob utilities and base64 string conversion", () => { + // https://on.cypress.io/blob + cy.get(".utility-blob").then(($div) => { + // https://github.com/nolanlawson/blob-util#imgSrcToDataURL + // get the dataUrl string for the javascript-logo + return Cypress.Blob.imgSrcToDataURL( + "https://example.cypress.io/assets/img/javascript-logo.png", + undefined, + "anonymous" + ).then((dataUrl) => { + // create an element and set its src to the dataUrl + let img = Cypress.$("", { src: dataUrl }); - // need to explicitly return cy here since we are initially returning - // the Cypress.Blob.imgSrcToDataURL promise to our test - // append the image - $div.append(img) + // need to explicitly return cy here since we are initially returning + // the Cypress.Blob.imgSrcToDataURL promise to our test + // append the image + $div.append(img); - cy.get('.utility-blob img').click() - .should('have.attr', 'src', dataUrl) - }) - }) - }) + cy.get(".utility-blob img").click().should("have.attr", "src", dataUrl); + }); + }); + }); - it('Cypress.minimatch - test out glob patterns against strings', () => { - // https://on.cypress.io/minimatch - let matching = Cypress.minimatch('/users/1/comments', '/users/*/comments', { - matchBase: true, - }) + it("Cypress.minimatch - test out glob patterns against strings", () => { + // https://on.cypress.io/minimatch + let matching = Cypress.minimatch("/users/1/comments", "/users/*/comments", { + matchBase: true + }); - expect(matching, 'matching wildcard').to.be.true + expect(matching, "matching wildcard").to.be.true; - matching = Cypress.minimatch('/users/1/comments/2', '/users/*/comments', { - matchBase: true, - }) + matching = Cypress.minimatch("/users/1/comments/2", "/users/*/comments", { + matchBase: true + }); - expect(matching, 'comments').to.be.false + expect(matching, "comments").to.be.false; - // ** matches against all downstream path segments - matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/**', { - matchBase: true, - }) + // ** matches against all downstream path segments + matching = Cypress.minimatch("/foo/bar/baz/123/quux?a=b&c=2", "/foo/**", { + matchBase: true + }); - expect(matching, 'comments').to.be.true + expect(matching, "comments").to.be.true; - // whereas * matches only the next path segment + // whereas * matches only the next path segment - matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/*', { - matchBase: false, - }) + matching = Cypress.minimatch("/foo/bar/baz/123/quux?a=b&c=2", "/foo/*", { + matchBase: false + }); - expect(matching, 'comments').to.be.false - }) + expect(matching, "comments").to.be.false; + }); - it('Cypress.Promise - instantiate a bluebird promise', () => { - // https://on.cypress.io/promise - let waited = false + it("Cypress.Promise - instantiate a bluebird promise", () => { + // https://on.cypress.io/promise + let waited = false; - /** - * @return Bluebird - */ - function waitOneSecond() { - // return a promise that resolves after 1 second - // @ts-ignore TS2351 (new Cypress.Promise) - return new Cypress.Promise((resolve, reject) => { - setTimeout(() => { - // set waited to true - waited = true + /** + * @return Bluebird + */ + function waitOneSecond() { + // return a promise that resolves after 1 second + // @ts-ignore TS2351 (new Cypress.Promise) + return new Cypress.Promise((resolve, reject) => { + setTimeout(() => { + // set waited to true + waited = true; - // resolve with 'foo' string - resolve('foo') - }, 1000) - }) - } + // resolve with 'foo' string + resolve("foo"); + }, 1000); + }); + } - cy.then(() => { - // return a promise to cy.then() that - // is awaited until it resolves - // @ts-ignore TS7006 - return waitOneSecond().then((str) => { - expect(str).to.eq('foo') - expect(waited).to.be.true - }) - }) - }) -}) + cy.then(() => { + // return a promise to cy.then() that + // is awaited until it resolves + // @ts-ignore TS7006 + return waitOneSecond().then((str) => { + expect(str).to.eq("foo"); + expect(waited).to.be.true; + }); + }); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/viewport.cy.js b/client/cypress/e2e/2-advanced-examples/viewport.cy.js index e640a1554..1d73b915d 100644 --- a/client/cypress/e2e/2-advanced-examples/viewport.cy.js +++ b/client/cypress/e2e/2-advanced-examples/viewport.cy.js @@ -1,59 +1,59 @@ /// -context('Viewport', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/commands/viewport') - }) +context("Viewport", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/viewport"); + }); - it('cy.viewport() - set the viewport size and dimension', () => { - // https://on.cypress.io/viewport + it("cy.viewport() - set the viewport size and dimension", () => { + // https://on.cypress.io/viewport - cy.get('#navbar').should('be.visible') - cy.viewport(320, 480) + cy.get("#navbar").should("be.visible"); + cy.viewport(320, 480); - // the navbar should have collapse since our screen is smaller - cy.get('#navbar').should('not.be.visible') - cy.get('.navbar-toggle').should('be.visible').click() - cy.get('.nav').find('a').should('be.visible') + // the navbar should have collapse since our screen is smaller + cy.get("#navbar").should("not.be.visible"); + cy.get(".navbar-toggle").should("be.visible").click(); + cy.get(".nav").find("a").should("be.visible"); - // lets see what our app looks like on a super large screen - cy.viewport(2999, 2999) + // lets see what our app looks like on a super large screen + cy.viewport(2999, 2999); - // cy.viewport() accepts a set of preset sizes - // to easily set the screen to a device's width and height + // cy.viewport() accepts a set of preset sizes + // to easily set the screen to a device's width and height - // We added a cy.wait() between each viewport change so you can see - // the change otherwise it is a little too fast to see :) + // We added a cy.wait() between each viewport change so you can see + // the change otherwise it is a little too fast to see :) - cy.viewport('macbook-15') - cy.wait(200) - cy.viewport('macbook-13') - cy.wait(200) - cy.viewport('macbook-11') - cy.wait(200) - cy.viewport('ipad-2') - cy.wait(200) - cy.viewport('ipad-mini') - cy.wait(200) - cy.viewport('iphone-6+') - cy.wait(200) - cy.viewport('iphone-6') - cy.wait(200) - cy.viewport('iphone-5') - cy.wait(200) - cy.viewport('iphone-4') - cy.wait(200) - cy.viewport('iphone-3') - cy.wait(200) + cy.viewport("macbook-15"); + cy.wait(200); + cy.viewport("macbook-13"); + cy.wait(200); + cy.viewport("macbook-11"); + cy.wait(200); + cy.viewport("ipad-2"); + cy.wait(200); + cy.viewport("ipad-mini"); + cy.wait(200); + cy.viewport("iphone-6+"); + cy.wait(200); + cy.viewport("iphone-6"); + cy.wait(200); + cy.viewport("iphone-5"); + cy.wait(200); + cy.viewport("iphone-4"); + cy.wait(200); + cy.viewport("iphone-3"); + cy.wait(200); - // cy.viewport() accepts an orientation for all presets - // the default orientation is 'portrait' - cy.viewport('ipad-2', 'portrait') - cy.wait(200) - cy.viewport('iphone-4', 'landscape') - cy.wait(200) + // cy.viewport() accepts an orientation for all presets + // the default orientation is 'portrait' + cy.viewport("ipad-2", "portrait"); + cy.wait(200); + cy.viewport("iphone-4", "landscape"); + cy.wait(200); - // The viewport will be reset back to the default dimensions - // in between tests (the default can be set in cypress.json) - }) -}) + // The viewport will be reset back to the default dimensions + // in between tests (the default can be set in cypress.json) + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/waiting.cy.js b/client/cypress/e2e/2-advanced-examples/waiting.cy.js index b33f679d2..49915ae75 100644 --- a/client/cypress/e2e/2-advanced-examples/waiting.cy.js +++ b/client/cypress/e2e/2-advanced-examples/waiting.cy.js @@ -1,31 +1,31 @@ /// -context('Waiting', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/commands/waiting') - }) - // BE CAREFUL of adding unnecessary wait times. - // https://on.cypress.io/best-practices#Unnecessary-Waiting +context("Waiting", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/waiting"); + }); + // BE CAREFUL of adding unnecessary wait times. + // https://on.cypress.io/best-practices#Unnecessary-Waiting - // https://on.cypress.io/wait - it('cy.wait() - wait for a specific amount of time', () => { - cy.get('.wait-input1').type('Wait 1000ms after typing') - cy.wait(1000) - cy.get('.wait-input2').type('Wait 1000ms after typing') - cy.wait(1000) - cy.get('.wait-input3').type('Wait 1000ms after typing') - cy.wait(1000) - }) + // https://on.cypress.io/wait + it("cy.wait() - wait for a specific amount of time", () => { + cy.get(".wait-input1").type("Wait 1000ms after typing"); + cy.wait(1000); + cy.get(".wait-input2").type("Wait 1000ms after typing"); + cy.wait(1000); + cy.get(".wait-input3").type("Wait 1000ms after typing"); + cy.wait(1000); + }); - it('cy.wait() - wait for a specific route', () => { - // Listen to GET to comments/1 - cy.intercept('GET', '**/comments/*').as('getComment') + it("cy.wait() - wait for a specific route", () => { + // Listen to GET to comments/1 + cy.intercept("GET", "**/comments/*").as("getComment"); - // we have code that gets a comment when - // the button is clicked in scripts.js - cy.get('.network-btn').click() + // we have code that gets a comment when + // the button is clicked in scripts.js + cy.get(".network-btn").click(); - // wait for GET comments/1 - cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304]) - }) -}) + // wait for GET comments/1 + cy.wait("@getComment").its("response.statusCode").should("be.oneOf", [200, 304]); + }); +}); diff --git a/client/cypress/e2e/2-advanced-examples/window.cy.js b/client/cypress/e2e/2-advanced-examples/window.cy.js index d8b3b237e..9740ba049 100644 --- a/client/cypress/e2e/2-advanced-examples/window.cy.js +++ b/client/cypress/e2e/2-advanced-examples/window.cy.js @@ -1,22 +1,22 @@ /// -context('Window', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/commands/window') - }) +context("Window", () => { + beforeEach(() => { + cy.visit("https://example.cypress.io/commands/window"); + }); - it('cy.window() - get the global window object', () => { - // https://on.cypress.io/window - cy.window().should('have.property', 'top') - }) + it("cy.window() - get the global window object", () => { + // https://on.cypress.io/window + cy.window().should("have.property", "top"); + }); - it('cy.document() - get the document object', () => { - // https://on.cypress.io/document - cy.document().should('have.property', 'charset').and('eq', 'UTF-8') - }) + it("cy.document() - get the document object", () => { + // https://on.cypress.io/document + cy.document().should("have.property", "charset").and("eq", "UTF-8"); + }); - it('cy.title() - get the title', () => { - // https://on.cypress.io/title - cy.title().should('include', 'Kitchen Sink') - }) -}) + it("cy.title() - get the title", () => { + // https://on.cypress.io/title + cy.title().should("include", "Kitchen Sink"); + }); +}); diff --git a/client/cypress/fixtures/profile.json b/client/cypress/fixtures/profile.json index b6c355ca5..a95e88f9c 100644 --- a/client/cypress/fixtures/profile.json +++ b/client/cypress/fixtures/profile.json @@ -2,4 +2,4 @@ "id": 8739, "name": "Jane", "email": "jane@example.com" -} \ No newline at end of file +} diff --git a/client/cypress/plugins/index.js b/client/cypress/plugins/index.js index fe51f29d4..8229063ad 100644 --- a/client/cypress/plugins/index.js +++ b/client/cypress/plugins/index.js @@ -17,6 +17,6 @@ */ // eslint-disable-next-line no-unused-vars module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config -} + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +}; diff --git a/client/cypress/support/e2e.js b/client/cypress/support/e2e.js index d68db96df..d076cec9f 100644 --- a/client/cypress/support/e2e.js +++ b/client/cypress/support/e2e.js @@ -14,7 +14,7 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import "./commands"; // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/client/cypress/tsconfig.json b/client/cypress/tsconfig.json index 8e6ac9683..36de33dee 100644 --- a/client/cypress/tsconfig.json +++ b/client/cypress/tsconfig.json @@ -2,11 +2,7 @@ "compilerOptions": { "allowJs": true, "baseUrl": "../node_modules", - "types": [ - "cypress" - ] + "types": ["cypress"] }, - "include": [ - "**/*.*" - ] + "include": ["**/*.*"] } diff --git a/client/dev-dist/registerSW.js b/client/dev-dist/registerSW.js index 1d5625f45..f17aa66d6 100644 --- a/client/dev-dist/registerSW.js +++ b/client/dev-dist/registerSW.js @@ -1 +1,2 @@ -if('serviceWorker' in navigator) navigator.serviceWorker.register('/dev-sw.js?dev-sw', { scope: '/', type: 'classic' }) \ No newline at end of file +if ("serviceWorker" in navigator) + navigator.serviceWorker.register("/dev-sw.js?dev-sw", { scope: "/", type: "classic" }); diff --git a/client/dev-dist/sw.js b/client/dev-dist/sw.js index d14df90f7..0cc1553d3 100644 --- a/client/dev-dist/sw.js +++ b/client/dev-dist/sw.js @@ -21,22 +21,20 @@ if (!self.define) { const singleRequire = (uri, parentUri) => { uri = new URL(uri + ".js", parentUri).href; - return registry[uri] || ( - - new Promise(resolve => { - if ("document" in self) { - const script = document.createElement("script"); - script.src = uri; - script.onload = resolve; - document.head.appendChild(script); - } else { - nextDefineUri = uri; - importScripts(uri); - resolve(); - } - }) - - .then(() => { + return ( + registry[uri] || + new Promise((resolve) => { + if ("document" in self) { + const script = document.createElement("script"); + script.src = uri; + script.onload = resolve; + document.head.appendChild(script); + } else { + nextDefineUri = uri; + importScripts(uri); + resolve(); + } + }).then(() => { let promise = registry[uri]; if (!promise) { throw new Error(`Module ${uri} didn’t register its module`); @@ -53,21 +51,20 @@ if (!self.define) { return; } let exports = {}; - const require = depUri => singleRequire(depUri, uri); + const require = (depUri) => singleRequire(depUri, uri); const specialDeps = { module: { uri }, exports, require }; - registry[uri] = Promise.all(depsNames.map( - depName => specialDeps[depName] || require(depName) - )).then(deps => { + registry[uri] = Promise.all(depsNames.map((depName) => specialDeps[depName] || require(depName))).then((deps) => { factory(...deps); return exports; }); }; } -define(['./workbox-b5f7729d'], (function (workbox) { 'use strict'; +define(["./workbox-b5f7729d"], function (workbox) { + "use strict"; self.skipWaiting(); workbox.clientsClaim(); @@ -77,16 +74,23 @@ define(['./workbox-b5f7729d'], (function (workbox) { 'use strict'; * requests for URLs in the manifest. * See https://goo.gl/S9QRab */ - workbox.precacheAndRoute([{ - "url": "registerSW.js", - "revision": "3ca0b8505b4bec776b69afdba2768812" - }, { - "url": "index.html", - "revision": "0.sa702m4aq68" - }], {}); + workbox.precacheAndRoute( + [ + { + url: "registerSW.js", + revision: "3ca0b8505b4bec776b69afdba2768812" + }, + { + url: "index.html", + revision: "0.sa702m4aq68" + } + ], + {} + ); workbox.cleanupOutdatedCaches(); - workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { - allowlist: [/^\/$/] - })); - -})); + workbox.registerRoute( + new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { + allowlist: [/^\/$/] + }) + ); +}); diff --git a/client/dev-dist/workbox-b5f7729d.js b/client/dev-dist/workbox-b5f7729d.js index 077fa26dd..a0a2a61d8 100644 --- a/client/dev-dist/workbox-b5f7729d.js +++ b/client/dev-dist/workbox-b5f7729d.js @@ -1,782 +1,780 @@ -define(['exports'], (function (exports) { 'use strict'; +define(["exports"], function (exports) { + "use strict"; - // @ts-ignore - try { - self['workbox:core:7.0.0'] && _(); - } catch (e) {} + // @ts-ignore + try { + self["workbox:core:7.0.0"] && _(); + } catch (e) {} - /* + /* Copyright 2019 Google LLC Use of this source code is governed by an MIT-style license that can be found in the LICENSE file or at https://opensource.org/licenses/MIT. */ - /** - * Claim any currently available clients once the service worker - * becomes active. This is normally used in conjunction with `skipWaiting()`. - * - * @memberof workbox-core - */ - function clientsClaim() { - self.addEventListener('activate', () => self.clients.claim()); + /** + * Claim any currently available clients once the service worker + * becomes active. This is normally used in conjunction with `skipWaiting()`. + * + * @memberof workbox-core + */ + function clientsClaim() { + self.addEventListener("activate", () => self.clients.claim()); + } + + /* + Copyright 2019 Google LLC + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + const logger = (() => { + // Don't overwrite this value if it's already set. + // See https://github.com/GoogleChrome/workbox/pull/2284#issuecomment-560470923 + if (!("__WB_DISABLE_DEV_LOGS" in globalThis)) { + self.__WB_DISABLE_DEV_LOGS = false; } - - /* - Copyright 2019 Google LLC - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - const logger = (() => { - // Don't overwrite this value if it's already set. - // See https://github.com/GoogleChrome/workbox/pull/2284#issuecomment-560470923 - if (!('__WB_DISABLE_DEV_LOGS' in globalThis)) { - self.__WB_DISABLE_DEV_LOGS = false; + let inGroup = false; + const methodToColorMap = { + debug: `#7f8c8d`, + log: `#2ecc71`, + warn: `#f39c12`, + error: `#c0392b`, + groupCollapsed: `#3498db`, + groupEnd: null // No colored prefix on groupEnd + }; + const print = function (method, args) { + if (self.__WB_DISABLE_DEV_LOGS) { + return; } - let inGroup = false; - const methodToColorMap = { - debug: `#7f8c8d`, - log: `#2ecc71`, - warn: `#f39c12`, - error: `#c0392b`, - groupCollapsed: `#3498db`, - groupEnd: null // No colored prefix on groupEnd - }; - const print = function (method, args) { - if (self.__WB_DISABLE_DEV_LOGS) { + if (method === "groupCollapsed") { + // Safari doesn't print all console.groupCollapsed() arguments: + // https://bugs.webkit.org/show_bug.cgi?id=182754 + if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) { + console[method](...args); return; } - if (method === 'groupCollapsed') { - // Safari doesn't print all console.groupCollapsed() arguments: - // https://bugs.webkit.org/show_bug.cgi?id=182754 - if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) { - console[method](...args); - return; - } - } - const styles = [`background: ${methodToColorMap[method]}`, `border-radius: 0.5em`, `color: white`, `font-weight: bold`, `padding: 2px 0.5em`]; - // When in a group, the workbox prefix is not displayed. - const logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')]; - console[method](...logPrefix, ...args); - if (method === 'groupCollapsed') { - inGroup = true; - } - if (method === 'groupEnd') { - inGroup = false; - } + } + const styles = [ + `background: ${methodToColorMap[method]}`, + `border-radius: 0.5em`, + `color: white`, + `font-weight: bold`, + `padding: 2px 0.5em` + ]; + // When in a group, the workbox prefix is not displayed. + const logPrefix = inGroup ? [] : ["%cworkbox", styles.join(";")]; + console[method](...logPrefix, ...args); + if (method === "groupCollapsed") { + inGroup = true; + } + if (method === "groupEnd") { + inGroup = false; + } + }; + // eslint-disable-next-line @typescript-eslint/ban-types + const api = {}; + const loggerMethods = Object.keys(methodToColorMap); + for (const key of loggerMethods) { + const method = key; + api[method] = (...args) => { + print(method, args); }; - // eslint-disable-next-line @typescript-eslint/ban-types - const api = {}; - const loggerMethods = Object.keys(methodToColorMap); - for (const key of loggerMethods) { - const method = key; - api[method] = (...args) => { - print(method, args); - }; - } - return api; - })(); - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - const messages = { - 'invalid-value': ({ - paramName, - validValueDescription, - value - }) => { - if (!paramName || !validValueDescription) { - throw new Error(`Unexpected input to 'invalid-value' error.`); - } - return `The '${paramName}' parameter was given a value with an ` + `unexpected value. ${validValueDescription} Received a value of ` + `${JSON.stringify(value)}.`; - }, - 'not-an-array': ({ - moduleName, - className, - funcName, - paramName - }) => { - if (!moduleName || !className || !funcName || !paramName) { - throw new Error(`Unexpected input to 'not-an-array' error.`); - } - return `The parameter '${paramName}' passed into ` + `'${moduleName}.${className}.${funcName}()' must be an array.`; - }, - 'incorrect-type': ({ - expectedType, - paramName, - moduleName, - className, - funcName - }) => { - if (!expectedType || !paramName || !moduleName || !funcName) { - throw new Error(`Unexpected input to 'incorrect-type' error.`); - } - const classNameStr = className ? `${className}.` : ''; - return `The parameter '${paramName}' passed into ` + `'${moduleName}.${classNameStr}` + `${funcName}()' must be of type ${expectedType}.`; - }, - 'incorrect-class': ({ - expectedClassName, - paramName, - moduleName, - className, - funcName, - isReturnValueProblem - }) => { - if (!expectedClassName || !moduleName || !funcName) { - throw new Error(`Unexpected input to 'incorrect-class' error.`); - } - const classNameStr = className ? `${className}.` : ''; - if (isReturnValueProblem) { - return `The return value from ` + `'${moduleName}.${classNameStr}${funcName}()' ` + `must be an instance of class ${expectedClassName}.`; - } - return `The parameter '${paramName}' passed into ` + `'${moduleName}.${classNameStr}${funcName}()' ` + `must be an instance of class ${expectedClassName}.`; - }, - 'missing-a-method': ({ - expectedMethod, - paramName, - moduleName, - className, - funcName - }) => { - if (!expectedMethod || !paramName || !moduleName || !className || !funcName) { - throw new Error(`Unexpected input to 'missing-a-method' error.`); - } - return `${moduleName}.${className}.${funcName}() expected the ` + `'${paramName}' parameter to expose a '${expectedMethod}' method.`; - }, - 'add-to-cache-list-unexpected-type': ({ - entry - }) => { - return `An unexpected entry was passed to ` + `'workbox-precaching.PrecacheController.addToCacheList()' The entry ` + `'${JSON.stringify(entry)}' isn't supported. You must supply an array of ` + `strings with one or more characters, objects with a url property or ` + `Request objects.`; - }, - 'add-to-cache-list-conflicting-entries': ({ - firstEntry, - secondEntry - }) => { - if (!firstEntry || !secondEntry) { - throw new Error(`Unexpected input to ` + `'add-to-cache-list-duplicate-entries' error.`); - } - return `Two of the entries passed to ` + `'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` + `${firstEntry} but different revision details. Workbox is ` + `unable to cache and version the asset correctly. Please remove one ` + `of the entries.`; - }, - 'plugin-error-request-will-fetch': ({ - thrownErrorMessage - }) => { - if (!thrownErrorMessage) { - throw new Error(`Unexpected input to ` + `'plugin-error-request-will-fetch', error.`); - } - return `An error was thrown by a plugins 'requestWillFetch()' method. ` + `The thrown error message was: '${thrownErrorMessage}'.`; - }, - 'invalid-cache-name': ({ - cacheNameId, - value - }) => { - if (!cacheNameId) { - throw new Error(`Expected a 'cacheNameId' for error 'invalid-cache-name'`); - } - return `You must provide a name containing at least one character for ` + `setCacheDetails({${cacheNameId}: '...'}). Received a value of ` + `'${JSON.stringify(value)}'`; - }, - 'unregister-route-but-not-found-with-method': ({ - method - }) => { - if (!method) { - throw new Error(`Unexpected input to ` + `'unregister-route-but-not-found-with-method' error.`); - } - return `The route you're trying to unregister was not previously ` + `registered for the method type '${method}'.`; - }, - 'unregister-route-route-not-registered': () => { - return `The route you're trying to unregister was not previously ` + `registered.`; - }, - 'queue-replay-failed': ({ - name - }) => { - return `Replaying the background sync queue '${name}' failed.`; - }, - 'duplicate-queue-name': ({ - name - }) => { - return `The Queue name '${name}' is already being used. ` + `All instances of backgroundSync.Queue must be given unique names.`; - }, - 'expired-test-without-max-age': ({ - methodName, - paramName - }) => { - return `The '${methodName}()' method can only be used when the ` + `'${paramName}' is used in the constructor.`; - }, - 'unsupported-route-type': ({ - moduleName, - className, - funcName, - paramName - }) => { - return `The supplied '${paramName}' parameter was an unsupported type. ` + `Please check the docs for ${moduleName}.${className}.${funcName} for ` + `valid input types.`; - }, - 'not-array-of-class': ({ - value, - expectedClass, - moduleName, - className, - funcName, - paramName - }) => { - return `The supplied '${paramName}' parameter must be an array of ` + `'${expectedClass}' objects. Received '${JSON.stringify(value)},'. ` + `Please check the call to ${moduleName}.${className}.${funcName}() ` + `to fix the issue.`; - }, - 'max-entries-or-age-required': ({ - moduleName, - className, - funcName - }) => { - return `You must define either config.maxEntries or config.maxAgeSeconds` + `in ${moduleName}.${className}.${funcName}`; - }, - 'statuses-or-headers-required': ({ - moduleName, - className, - funcName - }) => { - return `You must define either config.statuses or config.headers` + `in ${moduleName}.${className}.${funcName}`; - }, - 'invalid-string': ({ - moduleName, - funcName, - paramName - }) => { - if (!paramName || !moduleName || !funcName) { - throw new Error(`Unexpected input to 'invalid-string' error.`); - } - return `When using strings, the '${paramName}' parameter must start with ` + `'http' (for cross-origin matches) or '/' (for same-origin matches). ` + `Please see the docs for ${moduleName}.${funcName}() for ` + `more info.`; - }, - 'channel-name-required': () => { - return `You must provide a channelName to construct a ` + `BroadcastCacheUpdate instance.`; - }, - 'invalid-responses-are-same-args': () => { - return `The arguments passed into responsesAreSame() appear to be ` + `invalid. Please ensure valid Responses are used.`; - }, - 'expire-custom-caches-only': () => { - return `You must provide a 'cacheName' property when using the ` + `expiration plugin with a runtime caching strategy.`; - }, - 'unit-must-be-bytes': ({ - normalizedRangeHeader - }) => { - if (!normalizedRangeHeader) { - throw new Error(`Unexpected input to 'unit-must-be-bytes' error.`); - } - return `The 'unit' portion of the Range header must be set to 'bytes'. ` + `The Range header provided was "${normalizedRangeHeader}"`; - }, - 'single-range-only': ({ - normalizedRangeHeader - }) => { - if (!normalizedRangeHeader) { - throw new Error(`Unexpected input to 'single-range-only' error.`); - } - return `Multiple ranges are not supported. Please use a single start ` + `value, and optional end value. The Range header provided was ` + `"${normalizedRangeHeader}"`; - }, - 'invalid-range-values': ({ - normalizedRangeHeader - }) => { - if (!normalizedRangeHeader) { - throw new Error(`Unexpected input to 'invalid-range-values' error.`); - } - return `The Range header is missing both start and end values. At least ` + `one of those values is needed. The Range header provided was ` + `"${normalizedRangeHeader}"`; - }, - 'no-range-header': () => { - return `No Range header was found in the Request provided.`; - }, - 'range-not-satisfiable': ({ - size, - start, - end - }) => { - return `The start (${start}) and end (${end}) values in the Range are ` + `not satisfiable by the cached response, which is ${size} bytes.`; - }, - 'attempt-to-cache-non-get-request': ({ - url, - method - }) => { - return `Unable to cache '${url}' because it is a '${method}' request and ` + `only 'GET' requests can be cached.`; - }, - 'cache-put-with-no-response': ({ - url - }) => { - return `There was an attempt to cache '${url}' but the response was not ` + `defined.`; - }, - 'no-response': ({ - url, - error - }) => { - let message = `The strategy could not generate a response for '${url}'.`; - if (error) { - message += ` The underlying error is ${error}.`; - } - return message; - }, - 'bad-precaching-response': ({ - url, - status - }) => { - return `The precaching request for '${url}' failed` + (status ? ` with an HTTP status of ${status}.` : `.`); - }, - 'non-precached-url': ({ - url - }) => { - return `createHandlerBoundToURL('${url}') was called, but that URL is ` + `not precached. Please pass in a URL that is precached instead.`; - }, - 'add-to-cache-list-conflicting-integrities': ({ - url - }) => { - return `Two of the entries passed to ` + `'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` + `${url} with different integrity values. Please remove one of them.`; - }, - 'missing-precache-entry': ({ - cacheName, - url - }) => { - return `Unable to find a precached response in ${cacheName} for ${url}.`; - }, - 'cross-origin-copy-response': ({ - origin - }) => { - return `workbox-core.copyResponse() can only be used with same-origin ` + `responses. It was passed a response with origin ${origin}.`; - }, - 'opaque-streams-source': ({ - type - }) => { - const message = `One of the workbox-streams sources resulted in an ` + `'${type}' response.`; - if (type === 'opaqueredirect') { - return `${message} Please do not use a navigation request that results ` + `in a redirect as a source.`; - } - return `${message} Please ensure your sources are CORS-enabled.`; - } - }; - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - const generatorFunction = (code, details = {}) => { - const message = messages[code]; - if (!message) { - throw new Error(`Unable to find message for code '${code}'.`); - } - return message(details); - }; - const messageGenerator = generatorFunction; - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * Workbox errors should be thrown with this class. - * This allows use to ensure the type easily in tests, - * helps developers identify errors from workbox - * easily and allows use to optimise error - * messages correctly. - * - * @private - */ - class WorkboxError extends Error { - /** - * - * @param {string} errorCode The error code that - * identifies this particular error. - * @param {Object=} details Any relevant arguments - * that will help developers identify issues should - * be added as a key on the context object. - */ - constructor(errorCode, details) { - const message = messageGenerator(errorCode, details); - super(message); - this.name = errorCode; - this.details = details; - } } + return api; + })(); - /* + /* Copyright 2018 Google LLC Use of this source code is governed by an MIT-style license that can be found in the LICENSE file or at https://opensource.org/licenses/MIT. */ - /* - * This method throws if the supplied value is not an array. - * The destructed values are required to produce a meaningful error for users. - * The destructed and restructured object is so it's clear what is - * needed. + const messages = { + "invalid-value": ({ paramName, validValueDescription, value }) => { + if (!paramName || !validValueDescription) { + throw new Error(`Unexpected input to 'invalid-value' error.`); + } + return ( + `The '${paramName}' parameter was given a value with an ` + + `unexpected value. ${validValueDescription} Received a value of ` + + `${JSON.stringify(value)}.` + ); + }, + "not-an-array": ({ moduleName, className, funcName, paramName }) => { + if (!moduleName || !className || !funcName || !paramName) { + throw new Error(`Unexpected input to 'not-an-array' error.`); + } + return ( + `The parameter '${paramName}' passed into ` + `'${moduleName}.${className}.${funcName}()' must be an array.` + ); + }, + "incorrect-type": ({ expectedType, paramName, moduleName, className, funcName }) => { + if (!expectedType || !paramName || !moduleName || !funcName) { + throw new Error(`Unexpected input to 'incorrect-type' error.`); + } + const classNameStr = className ? `${className}.` : ""; + return ( + `The parameter '${paramName}' passed into ` + + `'${moduleName}.${classNameStr}` + + `${funcName}()' must be of type ${expectedType}.` + ); + }, + "incorrect-class": ({ expectedClassName, paramName, moduleName, className, funcName, isReturnValueProblem }) => { + if (!expectedClassName || !moduleName || !funcName) { + throw new Error(`Unexpected input to 'incorrect-class' error.`); + } + const classNameStr = className ? `${className}.` : ""; + if (isReturnValueProblem) { + return ( + `The return value from ` + + `'${moduleName}.${classNameStr}${funcName}()' ` + + `must be an instance of class ${expectedClassName}.` + ); + } + return ( + `The parameter '${paramName}' passed into ` + + `'${moduleName}.${classNameStr}${funcName}()' ` + + `must be an instance of class ${expectedClassName}.` + ); + }, + "missing-a-method": ({ expectedMethod, paramName, moduleName, className, funcName }) => { + if (!expectedMethod || !paramName || !moduleName || !className || !funcName) { + throw new Error(`Unexpected input to 'missing-a-method' error.`); + } + return ( + `${moduleName}.${className}.${funcName}() expected the ` + + `'${paramName}' parameter to expose a '${expectedMethod}' method.` + ); + }, + "add-to-cache-list-unexpected-type": ({ entry }) => { + return ( + `An unexpected entry was passed to ` + + `'workbox-precaching.PrecacheController.addToCacheList()' The entry ` + + `'${JSON.stringify(entry)}' isn't supported. You must supply an array of ` + + `strings with one or more characters, objects with a url property or ` + + `Request objects.` + ); + }, + "add-to-cache-list-conflicting-entries": ({ firstEntry, secondEntry }) => { + if (!firstEntry || !secondEntry) { + throw new Error(`Unexpected input to ` + `'add-to-cache-list-duplicate-entries' error.`); + } + return ( + `Two of the entries passed to ` + + `'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` + + `${firstEntry} but different revision details. Workbox is ` + + `unable to cache and version the asset correctly. Please remove one ` + + `of the entries.` + ); + }, + "plugin-error-request-will-fetch": ({ thrownErrorMessage }) => { + if (!thrownErrorMessage) { + throw new Error(`Unexpected input to ` + `'plugin-error-request-will-fetch', error.`); + } + return ( + `An error was thrown by a plugins 'requestWillFetch()' method. ` + + `The thrown error message was: '${thrownErrorMessage}'.` + ); + }, + "invalid-cache-name": ({ cacheNameId, value }) => { + if (!cacheNameId) { + throw new Error(`Expected a 'cacheNameId' for error 'invalid-cache-name'`); + } + return ( + `You must provide a name containing at least one character for ` + + `setCacheDetails({${cacheNameId}: '...'}). Received a value of ` + + `'${JSON.stringify(value)}'` + ); + }, + "unregister-route-but-not-found-with-method": ({ method }) => { + if (!method) { + throw new Error(`Unexpected input to ` + `'unregister-route-but-not-found-with-method' error.`); + } + return ( + `The route you're trying to unregister was not previously ` + `registered for the method type '${method}'.` + ); + }, + "unregister-route-route-not-registered": () => { + return `The route you're trying to unregister was not previously ` + `registered.`; + }, + "queue-replay-failed": ({ name }) => { + return `Replaying the background sync queue '${name}' failed.`; + }, + "duplicate-queue-name": ({ name }) => { + return ( + `The Queue name '${name}' is already being used. ` + + `All instances of backgroundSync.Queue must be given unique names.` + ); + }, + "expired-test-without-max-age": ({ methodName, paramName }) => { + return `The '${methodName}()' method can only be used when the ` + `'${paramName}' is used in the constructor.`; + }, + "unsupported-route-type": ({ moduleName, className, funcName, paramName }) => { + return ( + `The supplied '${paramName}' parameter was an unsupported type. ` + + `Please check the docs for ${moduleName}.${className}.${funcName} for ` + + `valid input types.` + ); + }, + "not-array-of-class": ({ value, expectedClass, moduleName, className, funcName, paramName }) => { + return ( + `The supplied '${paramName}' parameter must be an array of ` + + `'${expectedClass}' objects. Received '${JSON.stringify(value)},'. ` + + `Please check the call to ${moduleName}.${className}.${funcName}() ` + + `to fix the issue.` + ); + }, + "max-entries-or-age-required": ({ moduleName, className, funcName }) => { + return ( + `You must define either config.maxEntries or config.maxAgeSeconds` + `in ${moduleName}.${className}.${funcName}` + ); + }, + "statuses-or-headers-required": ({ moduleName, className, funcName }) => { + return `You must define either config.statuses or config.headers` + `in ${moduleName}.${className}.${funcName}`; + }, + "invalid-string": ({ moduleName, funcName, paramName }) => { + if (!paramName || !moduleName || !funcName) { + throw new Error(`Unexpected input to 'invalid-string' error.`); + } + return ( + `When using strings, the '${paramName}' parameter must start with ` + + `'http' (for cross-origin matches) or '/' (for same-origin matches). ` + + `Please see the docs for ${moduleName}.${funcName}() for ` + + `more info.` + ); + }, + "channel-name-required": () => { + return `You must provide a channelName to construct a ` + `BroadcastCacheUpdate instance.`; + }, + "invalid-responses-are-same-args": () => { + return ( + `The arguments passed into responsesAreSame() appear to be ` + + `invalid. Please ensure valid Responses are used.` + ); + }, + "expire-custom-caches-only": () => { + return ( + `You must provide a 'cacheName' property when using the ` + `expiration plugin with a runtime caching strategy.` + ); + }, + "unit-must-be-bytes": ({ normalizedRangeHeader }) => { + if (!normalizedRangeHeader) { + throw new Error(`Unexpected input to 'unit-must-be-bytes' error.`); + } + return ( + `The 'unit' portion of the Range header must be set to 'bytes'. ` + + `The Range header provided was "${normalizedRangeHeader}"` + ); + }, + "single-range-only": ({ normalizedRangeHeader }) => { + if (!normalizedRangeHeader) { + throw new Error(`Unexpected input to 'single-range-only' error.`); + } + return ( + `Multiple ranges are not supported. Please use a single start ` + + `value, and optional end value. The Range header provided was ` + + `"${normalizedRangeHeader}"` + ); + }, + "invalid-range-values": ({ normalizedRangeHeader }) => { + if (!normalizedRangeHeader) { + throw new Error(`Unexpected input to 'invalid-range-values' error.`); + } + return ( + `The Range header is missing both start and end values. At least ` + + `one of those values is needed. The Range header provided was ` + + `"${normalizedRangeHeader}"` + ); + }, + "no-range-header": () => { + return `No Range header was found in the Request provided.`; + }, + "range-not-satisfiable": ({ size, start, end }) => { + return ( + `The start (${start}) and end (${end}) values in the Range are ` + + `not satisfiable by the cached response, which is ${size} bytes.` + ); + }, + "attempt-to-cache-non-get-request": ({ url, method }) => { + return `Unable to cache '${url}' because it is a '${method}' request and ` + `only 'GET' requests can be cached.`; + }, + "cache-put-with-no-response": ({ url }) => { + return `There was an attempt to cache '${url}' but the response was not ` + `defined.`; + }, + "no-response": ({ url, error }) => { + let message = `The strategy could not generate a response for '${url}'.`; + if (error) { + message += ` The underlying error is ${error}.`; + } + return message; + }, + "bad-precaching-response": ({ url, status }) => { + return `The precaching request for '${url}' failed` + (status ? ` with an HTTP status of ${status}.` : `.`); + }, + "non-precached-url": ({ url }) => { + return ( + `createHandlerBoundToURL('${url}') was called, but that URL is ` + + `not precached. Please pass in a URL that is precached instead.` + ); + }, + "add-to-cache-list-conflicting-integrities": ({ url }) => { + return ( + `Two of the entries passed to ` + + `'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` + + `${url} with different integrity values. Please remove one of them.` + ); + }, + "missing-precache-entry": ({ cacheName, url }) => { + return `Unable to find a precached response in ${cacheName} for ${url}.`; + }, + "cross-origin-copy-response": ({ origin }) => { + return ( + `workbox-core.copyResponse() can only be used with same-origin ` + + `responses. It was passed a response with origin ${origin}.` + ); + }, + "opaque-streams-source": ({ type }) => { + const message = `One of the workbox-streams sources resulted in an ` + `'${type}' response.`; + if (type === "opaqueredirect") { + return `${message} Please do not use a navigation request that results ` + `in a redirect as a source.`; + } + return `${message} Please ensure your sources are CORS-enabled.`; + } + }; + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + const generatorFunction = (code, details = {}) => { + const message = messages[code]; + if (!message) { + throw new Error(`Unable to find message for code '${code}'.`); + } + return message(details); + }; + const messageGenerator = generatorFunction; + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * Workbox errors should be thrown with this class. + * This allows use to ensure the type easily in tests, + * helps developers identify errors from workbox + * easily and allows use to optimise error + * messages correctly. + * + * @private + */ + class WorkboxError extends Error { + /** + * + * @param {string} errorCode The error code that + * identifies this particular error. + * @param {Object=} details Any relevant arguments + * that will help developers identify issues should + * be added as a key on the context object. */ - const isArray = (value, details) => { - if (!Array.isArray(value)) { - throw new WorkboxError('not-an-array', details); - } - }; - const hasMethod = (object, expectedMethod, details) => { - const type = typeof object[expectedMethod]; - if (type !== 'function') { - details['expectedMethod'] = expectedMethod; - throw new WorkboxError('missing-a-method', details); - } - }; - const isType = (object, expectedType, details) => { - if (typeof object !== expectedType) { - details['expectedType'] = expectedType; - throw new WorkboxError('incorrect-type', details); - } - }; - const isInstance = (object, + constructor(errorCode, details) { + const message = messageGenerator(errorCode, details); + super(message); + this.name = errorCode; + this.details = details; + } + } + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /* + * This method throws if the supplied value is not an array. + * The destructed values are required to produce a meaningful error for users. + * The destructed and restructured object is so it's clear what is + * needed. + */ + const isArray = (value, details) => { + if (!Array.isArray(value)) { + throw new WorkboxError("not-an-array", details); + } + }; + const hasMethod = (object, expectedMethod, details) => { + const type = typeof object[expectedMethod]; + if (type !== "function") { + details["expectedMethod"] = expectedMethod; + throw new WorkboxError("missing-a-method", details); + } + }; + const isType = (object, expectedType, details) => { + if (typeof object !== expectedType) { + details["expectedType"] = expectedType; + throw new WorkboxError("incorrect-type", details); + } + }; + const isInstance = ( + object, // Need the general type to do the check later. // eslint-disable-next-line @typescript-eslint/ban-types - expectedClass, details) => { - if (!(object instanceof expectedClass)) { - details['expectedClassName'] = expectedClass.name; - throw new WorkboxError('incorrect-class', details); - } - }; - const isOneOf = (value, validValues, details) => { - if (!validValues.includes(value)) { - details['validValueDescription'] = `Valid values are ${JSON.stringify(validValues)}.`; - throw new WorkboxError('invalid-value', details); - } - }; - const isArrayOfClass = (value, + expectedClass, + details + ) => { + if (!(object instanceof expectedClass)) { + details["expectedClassName"] = expectedClass.name; + throw new WorkboxError("incorrect-class", details); + } + }; + const isOneOf = (value, validValues, details) => { + if (!validValues.includes(value)) { + details["validValueDescription"] = `Valid values are ${JSON.stringify(validValues)}.`; + throw new WorkboxError("invalid-value", details); + } + }; + const isArrayOfClass = ( + value, // Need general type to do check later. expectedClass, // eslint-disable-line - details) => { - const error = new WorkboxError('not-array-of-class', details); - if (!Array.isArray(value)) { + details + ) => { + const error = new WorkboxError("not-array-of-class", details); + if (!Array.isArray(value)) { + throw error; + } + for (const item of value) { + if (!(item instanceof expectedClass)) { throw error; } - for (const item of value) { - if (!(item instanceof expectedClass)) { - throw error; - } - } - }; - const finalAssertExports = { - hasMethod, - isArray, - isInstance, - isOneOf, - isType, - isArrayOfClass - }; - - // @ts-ignore - try { - self['workbox:routing:7.0.0'] && _(); - } catch (e) {} - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * The default HTTP method, 'GET', used when there's no specific method - * configured for a route. - * - * @type {string} - * - * @private - */ - const defaultMethod = 'GET'; - /** - * The list of valid HTTP methods associated with requests that could be routed. - * - * @type {Array} - * - * @private - */ - const validMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT']; - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * @param {function()|Object} handler Either a function, or an object with a - * 'handle' method. - * @return {Object} An object with a handle method. - * - * @private - */ - const normalizeHandler = handler => { - if (handler && typeof handler === 'object') { - { - finalAssertExports.hasMethod(handler, 'handle', { - moduleName: 'workbox-routing', - className: 'Route', - funcName: 'constructor', - paramName: 'handler' - }); - } - return handler; - } else { - { - finalAssertExports.isType(handler, 'function', { - moduleName: 'workbox-routing', - className: 'Route', - funcName: 'constructor', - paramName: 'handler' - }); - } - return { - handle: handler - }; - } - }; - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * A `Route` consists of a pair of callback functions, "match" and "handler". - * The "match" callback determine if a route should be used to "handle" a - * request by returning a non-falsy value if it can. The "handler" callback - * is called when there is a match and should return a Promise that resolves - * to a `Response`. - * - * @memberof workbox-routing - */ - class Route { - /** - * Constructor for Route class. - * - * @param {workbox-routing~matchCallback} match - * A callback function that determines whether the route matches a given - * `fetch` event by returning a non-falsy value. - * @param {workbox-routing~handlerCallback} handler A callback - * function that returns a Promise resolving to a Response. - * @param {string} [method='GET'] The HTTP method to match the Route - * against. - */ - constructor(match, handler, method = defaultMethod) { - { - finalAssertExports.isType(match, 'function', { - moduleName: 'workbox-routing', - className: 'Route', - funcName: 'constructor', - paramName: 'match' - }); - if (method) { - finalAssertExports.isOneOf(method, validMethods, { - paramName: 'method' - }); - } - } - // These values are referenced directly by Router so cannot be - // altered by minificaton. - this.handler = normalizeHandler(handler); - this.match = match; - this.method = method; - } - /** - * - * @param {workbox-routing-handlerCallback} handler A callback - * function that returns a Promise resolving to a Response - */ - setCatchHandler(handler) { - this.catchHandler = normalizeHandler(handler); - } } + }; + const finalAssertExports = { + hasMethod, + isArray, + isInstance, + isOneOf, + isType, + isArrayOfClass + }; - /* + // @ts-ignore + try { + self["workbox:routing:7.0.0"] && _(); + } catch (e) {} + + /* Copyright 2018 Google LLC Use of this source code is governed by an MIT-style license that can be found in the LICENSE file or at https://opensource.org/licenses/MIT. */ - /** - * RegExpRoute makes it easy to create a regular expression based - * {@link workbox-routing.Route}. - * - * For same-origin requests the RegExp only needs to match part of the URL. For - * requests against third-party servers, you must define a RegExp that matches - * the start of the URL. - * - * @memberof workbox-routing - * @extends workbox-routing.Route - */ - class RegExpRoute extends Route { - /** - * If the regular expression contains - * [capture groups]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references}, - * the captured values will be passed to the - * {@link workbox-routing~handlerCallback} `params` - * argument. - * - * @param {RegExp} regExp The regular expression to match against URLs. - * @param {workbox-routing~handlerCallback} handler A callback - * function that returns a Promise resulting in a Response. - * @param {string} [method='GET'] The HTTP method to match the Route - * against. - */ - constructor(regExp, handler, method) { - { - finalAssertExports.isInstance(regExp, RegExp, { - moduleName: 'workbox-routing', - className: 'RegExpRoute', - funcName: 'constructor', - paramName: 'pattern' - }); - } - const match = ({ - url - }) => { - const result = regExp.exec(url.href); - // Return immediately if there's no match. - if (!result) { - return; - } - // Require that the match start at the first character in the URL string - // if it's a cross-origin request. - // See https://github.com/GoogleChrome/workbox/issues/281 for the context - // behind this behavior. - if (url.origin !== location.origin && result.index !== 0) { - { - logger.debug(`The regular expression '${regExp.toString()}' only partially matched ` + `against the cross-origin URL '${url.toString()}'. RegExpRoute's will only ` + `handle cross-origin requests if they match the entire URL.`); - } - return; - } - // If the route matches, but there aren't any capture groups defined, then - // this will return [], which is truthy and therefore sufficient to - // indicate a match. - // If there are capture groups, then it will return their values. - return result.slice(1); - }; - super(match, handler, method); - } - } + /** + * The default HTTP method, 'GET', used when there's no specific method + * configured for a route. + * + * @type {string} + * + * @private + */ + const defaultMethod = "GET"; + /** + * The list of valid HTTP methods associated with requests that could be routed. + * + * @type {Array} + * + * @private + */ + const validMethods = ["DELETE", "GET", "HEAD", "PATCH", "POST", "PUT"]; - /* + /* Copyright 2018 Google LLC Use of this source code is governed by an MIT-style license that can be found in the LICENSE file or at https://opensource.org/licenses/MIT. */ - const getFriendlyURL = url => { - const urlObj = new URL(String(url), location.href); - // See https://github.com/GoogleChrome/workbox/issues/2323 - // We want to include everything, except for the origin if it's same-origin. - return urlObj.href.replace(new RegExp(`^${location.origin}`), ''); - }; - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * The Router can be used to process a `FetchEvent` using one or more - * {@link workbox-routing.Route}, responding with a `Response` if - * a matching route exists. - * - * If no route matches a given a request, the Router will use a "default" - * handler if one is defined. - * - * Should the matching Route throw an error, the Router will use a "catch" - * handler if one is defined to gracefully deal with issues and respond with a - * Request. - * - * If a request matches multiple routes, the **earliest** registered route will - * be used to respond to the request. - * - * @memberof workbox-routing - */ - class Router { - /** - * Initializes a new Router. - */ - constructor() { - this._routes = new Map(); - this._defaultHandlerMap = new Map(); - } - /** - * @return {Map>} routes A `Map` of HTTP - * method name ('GET', etc.) to an array of all the corresponding `Route` - * instances that are registered. - */ - get routes() { - return this._routes; - } - /** - * Adds a fetch event listener to respond to events when a route matches - * the event's request. - */ - addFetchListener() { - // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705 - self.addEventListener('fetch', event => { - const { - request - } = event; - const responsePromise = this.handleRequest({ - request, - event - }); - if (responsePromise) { - event.respondWith(responsePromise); - } + /** + * @param {function()|Object} handler Either a function, or an object with a + * 'handle' method. + * @return {Object} An object with a handle method. + * + * @private + */ + const normalizeHandler = (handler) => { + if (handler && typeof handler === "object") { + { + finalAssertExports.hasMethod(handler, "handle", { + moduleName: "workbox-routing", + className: "Route", + funcName: "constructor", + paramName: "handler" }); } - /** - * Adds a message event listener for URLs to cache from the window. - * This is useful to cache resources loaded on the page prior to when the - * service worker started controlling it. - * - * The format of the message data sent from the window should be as follows. - * Where the `urlsToCache` array may consist of URL strings or an array of - * URL string + `requestInit` object (the same as you'd pass to `fetch()`). - * - * ``` - * { - * type: 'CACHE_URLS', - * payload: { - * urlsToCache: [ - * './script1.js', - * './script2.js', - * ['./script3.js', {mode: 'no-cors'}], - * ], - * }, - * } - * ``` - */ - addCacheListener() { - // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705 - self.addEventListener('message', event => { - // event.data is type 'any' - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (event.data && event.data.type === 'CACHE_URLS') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { - payload - } = event.data; - { - logger.debug(`Caching URLs from the window`, payload.urlsToCache); - } - const requestPromises = Promise.all(payload.urlsToCache.map(entry => { - if (typeof entry === 'string') { + return handler; + } else { + { + finalAssertExports.isType(handler, "function", { + moduleName: "workbox-routing", + className: "Route", + funcName: "constructor", + paramName: "handler" + }); + } + return { + handle: handler + }; + } + }; + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * A `Route` consists of a pair of callback functions, "match" and "handler". + * The "match" callback determine if a route should be used to "handle" a + * request by returning a non-falsy value if it can. The "handler" callback + * is called when there is a match and should return a Promise that resolves + * to a `Response`. + * + * @memberof workbox-routing + */ + class Route { + /** + * Constructor for Route class. + * + * @param {workbox-routing~matchCallback} match + * A callback function that determines whether the route matches a given + * `fetch` event by returning a non-falsy value. + * @param {workbox-routing~handlerCallback} handler A callback + * function that returns a Promise resolving to a Response. + * @param {string} [method='GET'] The HTTP method to match the Route + * against. + */ + constructor(match, handler, method = defaultMethod) { + { + finalAssertExports.isType(match, "function", { + moduleName: "workbox-routing", + className: "Route", + funcName: "constructor", + paramName: "match" + }); + if (method) { + finalAssertExports.isOneOf(method, validMethods, { + paramName: "method" + }); + } + } + // These values are referenced directly by Router so cannot be + // altered by minificaton. + this.handler = normalizeHandler(handler); + this.match = match; + this.method = method; + } + /** + * + * @param {workbox-routing-handlerCallback} handler A callback + * function that returns a Promise resolving to a Response + */ + setCatchHandler(handler) { + this.catchHandler = normalizeHandler(handler); + } + } + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * RegExpRoute makes it easy to create a regular expression based + * {@link workbox-routing.Route}. + * + * For same-origin requests the RegExp only needs to match part of the URL. For + * requests against third-party servers, you must define a RegExp that matches + * the start of the URL. + * + * @memberof workbox-routing + * @extends workbox-routing.Route + */ + class RegExpRoute extends Route { + /** + * If the regular expression contains + * [capture groups]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references}, + * the captured values will be passed to the + * {@link workbox-routing~handlerCallback} `params` + * argument. + * + * @param {RegExp} regExp The regular expression to match against URLs. + * @param {workbox-routing~handlerCallback} handler A callback + * function that returns a Promise resulting in a Response. + * @param {string} [method='GET'] The HTTP method to match the Route + * against. + */ + constructor(regExp, handler, method) { + { + finalAssertExports.isInstance(regExp, RegExp, { + moduleName: "workbox-routing", + className: "RegExpRoute", + funcName: "constructor", + paramName: "pattern" + }); + } + const match = ({ url }) => { + const result = regExp.exec(url.href); + // Return immediately if there's no match. + if (!result) { + return; + } + // Require that the match start at the first character in the URL string + // if it's a cross-origin request. + // See https://github.com/GoogleChrome/workbox/issues/281 for the context + // behind this behavior. + if (url.origin !== location.origin && result.index !== 0) { + { + logger.debug( + `The regular expression '${regExp.toString()}' only partially matched ` + + `against the cross-origin URL '${url.toString()}'. RegExpRoute's will only ` + + `handle cross-origin requests if they match the entire URL.` + ); + } + return; + } + // If the route matches, but there aren't any capture groups defined, then + // this will return [], which is truthy and therefore sufficient to + // indicate a match. + // If there are capture groups, then it will return their values. + return result.slice(1); + }; + super(match, handler, method); + } + } + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + const getFriendlyURL = (url) => { + const urlObj = new URL(String(url), location.href); + // See https://github.com/GoogleChrome/workbox/issues/2323 + // We want to include everything, except for the origin if it's same-origin. + return urlObj.href.replace(new RegExp(`^${location.origin}`), ""); + }; + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * The Router can be used to process a `FetchEvent` using one or more + * {@link workbox-routing.Route}, responding with a `Response` if + * a matching route exists. + * + * If no route matches a given a request, the Router will use a "default" + * handler if one is defined. + * + * Should the matching Route throw an error, the Router will use a "catch" + * handler if one is defined to gracefully deal with issues and respond with a + * Request. + * + * If a request matches multiple routes, the **earliest** registered route will + * be used to respond to the request. + * + * @memberof workbox-routing + */ + class Router { + /** + * Initializes a new Router. + */ + constructor() { + this._routes = new Map(); + this._defaultHandlerMap = new Map(); + } + /** + * @return {Map>} routes A `Map` of HTTP + * method name ('GET', etc.) to an array of all the corresponding `Route` + * instances that are registered. + */ + get routes() { + return this._routes; + } + /** + * Adds a fetch event listener to respond to events when a route matches + * the event's request. + */ + addFetchListener() { + // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705 + self.addEventListener("fetch", (event) => { + const { request } = event; + const responsePromise = this.handleRequest({ + request, + event + }); + if (responsePromise) { + event.respondWith(responsePromise); + } + }); + } + /** + * Adds a message event listener for URLs to cache from the window. + * This is useful to cache resources loaded on the page prior to when the + * service worker started controlling it. + * + * The format of the message data sent from the window should be as follows. + * Where the `urlsToCache` array may consist of URL strings or an array of + * URL string + `requestInit` object (the same as you'd pass to `fetch()`). + * + * ``` + * { + * type: 'CACHE_URLS', + * payload: { + * urlsToCache: [ + * './script1.js', + * './script2.js', + * ['./script3.js', {mode: 'no-cors'}], + * ], + * }, + * } + * ``` + */ + addCacheListener() { + // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705 + self.addEventListener("message", (event) => { + // event.data is type 'any' + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (event.data && event.data.type === "CACHE_URLS") { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { payload } = event.data; + { + logger.debug(`Caching URLs from the window`, payload.urlsToCache); + } + const requestPromises = Promise.all( + payload.urlsToCache.map((entry) => { + if (typeof entry === "string") { entry = [entry]; } const request = new Request(...entry); @@ -787,2351 +785,1955 @@ define(['exports'], (function (exports) { 'use strict'; // TODO(philipwalton): TypeScript errors without this typecast for // some reason (probably a bug). The real type here should work but // doesn't: `Array | undefined>`. - })); // TypeScript - event.waitUntil(requestPromises); - // If a MessageChannel was used, reply to the message on success. - if (event.ports && event.ports[0]) { - void requestPromises.then(() => event.ports[0].postMessage(true)); - } + }) + ); // TypeScript + event.waitUntil(requestPromises); + // If a MessageChannel was used, reply to the message on success. + if (event.ports && event.ports[0]) { + void requestPromises.then(() => event.ports[0].postMessage(true)); } + } + }); + } + /** + * Apply the routing rules to a FetchEvent object to get a Response from an + * appropriate Route's handler. + * + * @param {Object} options + * @param {Request} options.request The request to handle. + * @param {ExtendableEvent} options.event The event that triggered the + * request. + * @return {Promise|undefined} A promise is returned if a + * registered route can handle the request. If there is no matching + * route and there's no `defaultHandler`, `undefined` is returned. + */ + handleRequest({ request, event }) { + { + finalAssertExports.isInstance(request, Request, { + moduleName: "workbox-routing", + className: "Router", + funcName: "handleRequest", + paramName: "options.request" }); } - /** - * Apply the routing rules to a FetchEvent object to get a Response from an - * appropriate Route's handler. - * - * @param {Object} options - * @param {Request} options.request The request to handle. - * @param {ExtendableEvent} options.event The event that triggered the - * request. - * @return {Promise|undefined} A promise is returned if a - * registered route can handle the request. If there is no matching - * route and there's no `defaultHandler`, `undefined` is returned. - */ - handleRequest({ - request, - event - }) { + const url = new URL(request.url, location.href); + if (!url.protocol.startsWith("http")) { { - finalAssertExports.isInstance(request, Request, { - moduleName: 'workbox-routing', - className: 'Router', - funcName: 'handleRequest', - paramName: 'options.request' - }); + logger.debug(`Workbox Router only supports URLs that start with 'http'.`); } - const url = new URL(request.url, location.href); - if (!url.protocol.startsWith('http')) { - { - logger.debug(`Workbox Router only supports URLs that start with 'http'.`); - } - return; - } - const sameOrigin = url.origin === location.origin; - const { - params, - route - } = this.findMatchingRoute({ - event, - request, - sameOrigin, - url - }); - let handler = route && route.handler; - const debugMessages = []; - { - if (handler) { - debugMessages.push([`Found a route to handle this request:`, route]); - if (params) { - debugMessages.push([`Passing the following params to the route's handler:`, params]); - } - } - } - // If we don't have a handler because there was no matching route, then - // fall back to defaultHandler if that's defined. - const method = request.method; - if (!handler && this._defaultHandlerMap.has(method)) { - { - debugMessages.push(`Failed to find a matching route. Falling ` + `back to the default handler for ${method}.`); - } - handler = this._defaultHandlerMap.get(method); - } - if (!handler) { - { - // No handler so Workbox will do nothing. If logs is set of debug - // i.e. verbose, we should print out this information. - logger.debug(`No route found for: ${getFriendlyURL(url)}`); - } - return; - } - { - // We have a handler, meaning Workbox is going to handle the route. - // print the routing details to the console. - logger.groupCollapsed(`Router is responding to: ${getFriendlyURL(url)}`); - debugMessages.forEach(msg => { - if (Array.isArray(msg)) { - logger.log(...msg); - } else { - logger.log(msg); - } - }); - logger.groupEnd(); - } - // Wrap in try and catch in case the handle method throws a synchronous - // error. It should still callback to the catch handler. - let responsePromise; - try { - responsePromise = handler.handle({ - url, - request, - event, - params - }); - } catch (err) { - responsePromise = Promise.reject(err); - } - // Get route's catch handler, if it exists - const catchHandler = route && route.catchHandler; - if (responsePromise instanceof Promise && (this._catchHandler || catchHandler)) { - responsePromise = responsePromise.catch(async err => { - // If there's a route catch handler, process that first - if (catchHandler) { - { - // Still include URL here as it will be async from the console group - // and may not make sense without the URL - logger.groupCollapsed(`Error thrown when responding to: ` + ` ${getFriendlyURL(url)}. Falling back to route's Catch Handler.`); - logger.error(`Error thrown by:`, route); - logger.error(err); - logger.groupEnd(); - } - try { - return await catchHandler.handle({ - url, - request, - event, - params - }); - } catch (catchErr) { - if (catchErr instanceof Error) { - err = catchErr; - } - } - } - if (this._catchHandler) { - { - // Still include URL here as it will be async from the console group - // and may not make sense without the URL - logger.groupCollapsed(`Error thrown when responding to: ` + ` ${getFriendlyURL(url)}. Falling back to global Catch Handler.`); - logger.error(`Error thrown by:`, route); - logger.error(err); - logger.groupEnd(); - } - return this._catchHandler.handle({ - url, - request, - event - }); - } - throw err; - }); - } - return responsePromise; - } - /** - * Checks a request and URL (and optionally an event) against the list of - * registered routes, and if there's a match, returns the corresponding - * route along with any params generated by the match. - * - * @param {Object} options - * @param {URL} options.url - * @param {boolean} options.sameOrigin The result of comparing `url.origin` - * against the current origin. - * @param {Request} options.request The request to match. - * @param {Event} options.event The corresponding event. - * @return {Object} An object with `route` and `params` properties. - * They are populated if a matching route was found or `undefined` - * otherwise. - */ - findMatchingRoute({ - url, - sameOrigin, - request, - event - }) { - const routes = this._routes.get(request.method) || []; - for (const route of routes) { - let params; - // route.match returns type any, not possible to change right now. - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const matchResult = route.match({ - url, - sameOrigin, - request, - event - }); - if (matchResult) { - { - // Warn developers that using an async matchCallback is almost always - // not the right thing to do. - if (matchResult instanceof Promise) { - logger.warn(`While routing ${getFriendlyURL(url)}, an async ` + `matchCallback function was used. Please convert the ` + `following route to use a synchronous matchCallback function:`, route); - } - } - // See https://github.com/GoogleChrome/workbox/issues/2079 - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - params = matchResult; - if (Array.isArray(params) && params.length === 0) { - // Instead of passing an empty array in as params, use undefined. - params = undefined; - } else if (matchResult.constructor === Object && - // eslint-disable-line - Object.keys(matchResult).length === 0) { - // Instead of passing an empty object in as params, use undefined. - params = undefined; - } else if (typeof matchResult === 'boolean') { - // For the boolean value true (rather than just something truth-y), - // don't set params. - // See https://github.com/GoogleChrome/workbox/pull/2134#issuecomment-513924353 - params = undefined; - } - // Return early if have a match. - return { - route, - params - }; - } - } - // If no match was found above, return and empty object. - return {}; - } - /** - * Define a default `handler` that's called when no routes explicitly - * match the incoming request. - * - * Each HTTP method ('GET', 'POST', etc.) gets its own default handler. - * - * Without a default handler, unmatched requests will go against the - * network as if there were no service worker present. - * - * @param {workbox-routing~handlerCallback} handler A callback - * function that returns a Promise resulting in a Response. - * @param {string} [method='GET'] The HTTP method to associate with this - * default handler. Each method has its own default. - */ - setDefaultHandler(handler, method = defaultMethod) { - this._defaultHandlerMap.set(method, normalizeHandler(handler)); - } - /** - * If a Route throws an error while handling a request, this `handler` - * will be called and given a chance to provide a response. - * - * @param {workbox-routing~handlerCallback} handler A callback - * function that returns a Promise resulting in a Response. - */ - setCatchHandler(handler) { - this._catchHandler = normalizeHandler(handler); - } - /** - * Registers a route with the router. - * - * @param {workbox-routing.Route} route The route to register. - */ - registerRoute(route) { - { - finalAssertExports.isType(route, 'object', { - moduleName: 'workbox-routing', - className: 'Router', - funcName: 'registerRoute', - paramName: 'route' - }); - finalAssertExports.hasMethod(route, 'match', { - moduleName: 'workbox-routing', - className: 'Router', - funcName: 'registerRoute', - paramName: 'route' - }); - finalAssertExports.isType(route.handler, 'object', { - moduleName: 'workbox-routing', - className: 'Router', - funcName: 'registerRoute', - paramName: 'route' - }); - finalAssertExports.hasMethod(route.handler, 'handle', { - moduleName: 'workbox-routing', - className: 'Router', - funcName: 'registerRoute', - paramName: 'route.handler' - }); - finalAssertExports.isType(route.method, 'string', { - moduleName: 'workbox-routing', - className: 'Router', - funcName: 'registerRoute', - paramName: 'route.method' - }); - } - if (!this._routes.has(route.method)) { - this._routes.set(route.method, []); - } - // Give precedence to all of the earlier routes by adding this additional - // route to the end of the array. - this._routes.get(route.method).push(route); - } - /** - * Unregisters a route with the router. - * - * @param {workbox-routing.Route} route The route to unregister. - */ - unregisterRoute(route) { - if (!this._routes.has(route.method)) { - throw new WorkboxError('unregister-route-but-not-found-with-method', { - method: route.method - }); - } - const routeIndex = this._routes.get(route.method).indexOf(route); - if (routeIndex > -1) { - this._routes.get(route.method).splice(routeIndex, 1); - } else { - throw new WorkboxError('unregister-route-route-not-registered'); - } - } - } - - /* - Copyright 2019 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - let defaultRouter; - /** - * Creates a new, singleton Router instance if one does not exist. If one - * does already exist, that instance is returned. - * - * @private - * @return {Router} - */ - const getOrCreateDefaultRouter = () => { - if (!defaultRouter) { - defaultRouter = new Router(); - // The helpers that use the default Router assume these listeners exist. - defaultRouter.addFetchListener(); - defaultRouter.addCacheListener(); - } - return defaultRouter; - }; - - /* - Copyright 2019 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * Easily register a RegExp, string, or function with a caching - * strategy to a singleton Router instance. - * - * This method will generate a Route for you if needed and - * call {@link workbox-routing.Router#registerRoute}. - * - * @param {RegExp|string|workbox-routing.Route~matchCallback|workbox-routing.Route} capture - * If the capture param is a `Route`, all other arguments will be ignored. - * @param {workbox-routing~handlerCallback} [handler] A callback - * function that returns a Promise resulting in a Response. This parameter - * is required if `capture` is not a `Route` object. - * @param {string} [method='GET'] The HTTP method to match the Route - * against. - * @return {workbox-routing.Route} The generated `Route`. - * - * @memberof workbox-routing - */ - function registerRoute(capture, handler, method) { - let route; - if (typeof capture === 'string') { - const captureUrl = new URL(capture, location.href); - { - if (!(capture.startsWith('/') || capture.startsWith('http'))) { - throw new WorkboxError('invalid-string', { - moduleName: 'workbox-routing', - funcName: 'registerRoute', - paramName: 'capture' - }); - } - // We want to check if Express-style wildcards are in the pathname only. - // TODO: Remove this log message in v4. - const valueToCheck = capture.startsWith('http') ? captureUrl.pathname : capture; - // See https://github.com/pillarjs/path-to-regexp#parameters - const wildcards = '[*:?+]'; - if (new RegExp(`${wildcards}`).exec(valueToCheck)) { - logger.debug(`The '$capture' parameter contains an Express-style wildcard ` + `character (${wildcards}). Strings are now always interpreted as ` + `exact matches; use a RegExp for partial or wildcard matches.`); - } - } - const matchCallback = ({ - url - }) => { - { - if (url.pathname === captureUrl.pathname && url.origin !== captureUrl.origin) { - logger.debug(`${capture} only partially matches the cross-origin URL ` + `${url.toString()}. This route will only handle cross-origin requests ` + `if they match the entire URL.`); - } - } - return url.href === captureUrl.href; - }; - // If `capture` is a string then `handler` and `method` must be present. - route = new Route(matchCallback, handler, method); - } else if (capture instanceof RegExp) { - // If `capture` is a `RegExp` then `handler` and `method` must be present. - route = new RegExpRoute(capture, handler, method); - } else if (typeof capture === 'function') { - // If `capture` is a function then `handler` and `method` must be present. - route = new Route(capture, handler, method); - } else if (capture instanceof Route) { - route = capture; - } else { - throw new WorkboxError('unsupported-route-type', { - moduleName: 'workbox-routing', - funcName: 'registerRoute', - paramName: 'capture' - }); - } - const defaultRouter = getOrCreateDefaultRouter(); - defaultRouter.registerRoute(route); - return route; - } - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - const _cacheNameDetails = { - googleAnalytics: 'googleAnalytics', - precache: 'precache-v2', - prefix: 'workbox', - runtime: 'runtime', - suffix: typeof registration !== 'undefined' ? registration.scope : '' - }; - const _createCacheName = cacheName => { - return [_cacheNameDetails.prefix, cacheName, _cacheNameDetails.suffix].filter(value => value && value.length > 0).join('-'); - }; - const eachCacheNameDetail = fn => { - for (const key of Object.keys(_cacheNameDetails)) { - fn(key); - } - }; - const cacheNames = { - updateDetails: details => { - eachCacheNameDetail(key => { - if (typeof details[key] === 'string') { - _cacheNameDetails[key] = details[key]; - } - }); - }, - getGoogleAnalyticsName: userCacheName => { - return userCacheName || _createCacheName(_cacheNameDetails.googleAnalytics); - }, - getPrecacheName: userCacheName => { - return userCacheName || _createCacheName(_cacheNameDetails.precache); - }, - getPrefix: () => { - return _cacheNameDetails.prefix; - }, - getRuntimeName: userCacheName => { - return userCacheName || _createCacheName(_cacheNameDetails.runtime); - }, - getSuffix: () => { - return _cacheNameDetails.suffix; - } - }; - - /* - Copyright 2020 Google LLC - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * A utility method that makes it easier to use `event.waitUntil` with - * async functions and return the result. - * - * @param {ExtendableEvent} event - * @param {Function} asyncFn - * @return {Function} - * @private - */ - function waitUntil(event, asyncFn) { - const returnPromise = asyncFn(); - event.waitUntil(returnPromise); - return returnPromise; - } - - // @ts-ignore - try { - self['workbox:precaching:7.0.0'] && _(); - } catch (e) {} - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - // Name of the search parameter used to store revision info. - const REVISION_SEARCH_PARAM = '__WB_REVISION__'; - /** - * Converts a manifest entry into a versioned URL suitable for precaching. - * - * @param {Object|string} entry - * @return {string} A URL with versioning info. - * - * @private - * @memberof workbox-precaching - */ - function createCacheKey(entry) { - if (!entry) { - throw new WorkboxError('add-to-cache-list-unexpected-type', { - entry - }); - } - // If a precache manifest entry is a string, it's assumed to be a versioned - // URL, like '/app.abcd1234.js'. Return as-is. - if (typeof entry === 'string') { - const urlObject = new URL(entry, location.href); - return { - cacheKey: urlObject.href, - url: urlObject.href - }; - } - const { - revision, - url - } = entry; - if (!url) { - throw new WorkboxError('add-to-cache-list-unexpected-type', { - entry - }); - } - // If there's just a URL and no revision, then it's also assumed to be a - // versioned URL. - if (!revision) { - const urlObject = new URL(url, location.href); - return { - cacheKey: urlObject.href, - url: urlObject.href - }; - } - // Otherwise, construct a properly versioned URL using the custom Workbox - // search parameter along with the revision info. - const cacheKeyURL = new URL(url, location.href); - const originalURL = new URL(url, location.href); - cacheKeyURL.searchParams.set(REVISION_SEARCH_PARAM, revision); - return { - cacheKey: cacheKeyURL.href, - url: originalURL.href - }; - } - - /* - Copyright 2020 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * A plugin, designed to be used with PrecacheController, to determine the - * of assets that were updated (or not updated) during the install event. - * - * @private - */ - class PrecacheInstallReportPlugin { - constructor() { - this.updatedURLs = []; - this.notUpdatedURLs = []; - this.handlerWillStart = async ({ - request, - state - }) => { - // TODO: `state` should never be undefined... - if (state) { - state.originalRequest = request; - } - }; - this.cachedResponseWillBeUsed = async ({ - event, - state, - cachedResponse - }) => { - if (event.type === 'install') { - if (state && state.originalRequest && state.originalRequest instanceof Request) { - // TODO: `state` should never be undefined... - const url = state.originalRequest.url; - if (cachedResponse) { - this.notUpdatedURLs.push(url); - } else { - this.updatedURLs.push(url); - } - } - } - return cachedResponse; - }; - } - } - - /* - Copyright 2020 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * A plugin, designed to be used with PrecacheController, to translate URLs into - * the corresponding cache key, based on the current revision info. - * - * @private - */ - class PrecacheCacheKeyPlugin { - constructor({ - precacheController - }) { - this.cacheKeyWillBeUsed = async ({ - request, - params - }) => { - // Params is type any, can't change right now. - /* eslint-disable */ - const cacheKey = (params === null || params === void 0 ? void 0 : params.cacheKey) || this._precacheController.getCacheKeyForURL(request.url); - /* eslint-enable */ - return cacheKey ? new Request(cacheKey, { - headers: request.headers - }) : request; - }; - this._precacheController = precacheController; - } - } - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * @param {string} groupTitle - * @param {Array} deletedURLs - * - * @private - */ - const logGroup = (groupTitle, deletedURLs) => { - logger.groupCollapsed(groupTitle); - for (const url of deletedURLs) { - logger.log(url); - } - logger.groupEnd(); - }; - /** - * @param {Array} deletedURLs - * - * @private - * @memberof workbox-precaching - */ - function printCleanupDetails(deletedURLs) { - const deletionCount = deletedURLs.length; - if (deletionCount > 0) { - logger.groupCollapsed(`During precaching cleanup, ` + `${deletionCount} cached ` + `request${deletionCount === 1 ? ' was' : 's were'} deleted.`); - logGroup('Deleted Cache Requests', deletedURLs); - logger.groupEnd(); - } - } - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * @param {string} groupTitle - * @param {Array} urls - * - * @private - */ - function _nestedGroup(groupTitle, urls) { - if (urls.length === 0) { return; } - logger.groupCollapsed(groupTitle); - for (const url of urls) { - logger.log(url); - } - logger.groupEnd(); - } - /** - * @param {Array} urlsToPrecache - * @param {Array} urlsAlreadyPrecached - * - * @private - * @memberof workbox-precaching - */ - function printInstallDetails(urlsToPrecache, urlsAlreadyPrecached) { - const precachedCount = urlsToPrecache.length; - const alreadyPrecachedCount = urlsAlreadyPrecached.length; - if (precachedCount || alreadyPrecachedCount) { - let message = `Precaching ${precachedCount} file${precachedCount === 1 ? '' : 's'}.`; - if (alreadyPrecachedCount > 0) { - message += ` ${alreadyPrecachedCount} ` + `file${alreadyPrecachedCount === 1 ? ' is' : 's are'} already cached.`; + const sameOrigin = url.origin === location.origin; + const { params, route } = this.findMatchingRoute({ + event, + request, + sameOrigin, + url + }); + let handler = route && route.handler; + const debugMessages = []; + { + if (handler) { + debugMessages.push([`Found a route to handle this request:`, route]); + if (params) { + debugMessages.push([`Passing the following params to the route's handler:`, params]); + } } - logger.groupCollapsed(message); - _nestedGroup(`View newly precached URLs.`, urlsToPrecache); - _nestedGroup(`View previously precached URLs.`, urlsAlreadyPrecached); + } + // If we don't have a handler because there was no matching route, then + // fall back to defaultHandler if that's defined. + const method = request.method; + if (!handler && this._defaultHandlerMap.has(method)) { + { + debugMessages.push( + `Failed to find a matching route. Falling ` + `back to the default handler for ${method}.` + ); + } + handler = this._defaultHandlerMap.get(method); + } + if (!handler) { + { + // No handler so Workbox will do nothing. If logs is set of debug + // i.e. verbose, we should print out this information. + logger.debug(`No route found for: ${getFriendlyURL(url)}`); + } + return; + } + { + // We have a handler, meaning Workbox is going to handle the route. + // print the routing details to the console. + logger.groupCollapsed(`Router is responding to: ${getFriendlyURL(url)}`); + debugMessages.forEach((msg) => { + if (Array.isArray(msg)) { + logger.log(...msg); + } else { + logger.log(msg); + } + }); logger.groupEnd(); } - } - - /* - Copyright 2019 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - let supportStatus; - /** - * A utility function that determines whether the current browser supports - * constructing a new `Response` from a `response.body` stream. - * - * @return {boolean} `true`, if the current browser can successfully - * construct a `Response` from a `response.body` stream, `false` otherwise. - * - * @private - */ - function canConstructResponseFromBodyStream() { - if (supportStatus === undefined) { - const testResponse = new Response(''); - if ('body' in testResponse) { - try { - new Response(testResponse.body); - supportStatus = true; - } catch (error) { - supportStatus = false; - } - } - supportStatus = false; - } - return supportStatus; - } - - /* - Copyright 2019 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * Allows developers to copy a response and modify its `headers`, `status`, - * or `statusText` values (the values settable via a - * [`ResponseInit`]{@link https://developer.mozilla.org/en-US/docs/Web/API/Response/Response#Syntax} - * object in the constructor). - * To modify these values, pass a function as the second argument. That - * function will be invoked with a single object with the response properties - * `{headers, status, statusText}`. The return value of this function will - * be used as the `ResponseInit` for the new `Response`. To change the values - * either modify the passed parameter(s) and return it, or return a totally - * new object. - * - * This method is intentionally limited to same-origin responses, regardless of - * whether CORS was used or not. - * - * @param {Response} response - * @param {Function} modifier - * @memberof workbox-core - */ - async function copyResponse(response, modifier) { - let origin = null; - // If response.url isn't set, assume it's cross-origin and keep origin null. - if (response.url) { - const responseURL = new URL(response.url); - origin = responseURL.origin; - } - if (origin !== self.location.origin) { - throw new WorkboxError('cross-origin-copy-response', { - origin + // Wrap in try and catch in case the handle method throws a synchronous + // error. It should still callback to the catch handler. + let responsePromise; + try { + responsePromise = handler.handle({ + url, + request, + event, + params }); + } catch (err) { + responsePromise = Promise.reject(err); } - const clonedResponse = response.clone(); - // Create a fresh `ResponseInit` object by cloning the headers. - const responseInit = { - headers: new Headers(clonedResponse.headers), - status: clonedResponse.status, - statusText: clonedResponse.statusText - }; - // Apply any user modifications. - const modifiedResponseInit = modifier ? modifier(responseInit) : responseInit; - // Create the new response from the body stream and `ResponseInit` - // modifications. Note: not all browsers support the Response.body stream, - // so fall back to reading the entire body into memory as a blob. - const body = canConstructResponseFromBodyStream() ? clonedResponse.body : await clonedResponse.blob(); - return new Response(body, modifiedResponseInit); - } - - /* - Copyright 2020 Google LLC - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - function stripParams(fullURL, ignoreParams) { - const strippedURL = new URL(fullURL); - for (const param of ignoreParams) { - strippedURL.searchParams.delete(param); - } - return strippedURL.href; - } - /** - * Matches an item in the cache, ignoring specific URL params. This is similar - * to the `ignoreSearch` option, but it allows you to ignore just specific - * params (while continuing to match on the others). - * - * @private - * @param {Cache} cache - * @param {Request} request - * @param {Object} matchOptions - * @param {Array} ignoreParams - * @return {Promise} - */ - async function cacheMatchIgnoreParams(cache, request, ignoreParams, matchOptions) { - const strippedRequestURL = stripParams(request.url, ignoreParams); - // If the request doesn't include any ignored params, match as normal. - if (request.url === strippedRequestURL) { - return cache.match(request, matchOptions); - } - // Otherwise, match by comparing keys - const keysOptions = Object.assign(Object.assign({}, matchOptions), { - ignoreSearch: true - }); - const cacheKeys = await cache.keys(request, keysOptions); - for (const cacheKey of cacheKeys) { - const strippedCacheKeyURL = stripParams(cacheKey.url, ignoreParams); - if (strippedRequestURL === strippedCacheKeyURL) { - return cache.match(cacheKey, matchOptions); - } - } - return; - } - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * The Deferred class composes Promises in a way that allows for them to be - * resolved or rejected from outside the constructor. In most cases promises - * should be used directly, but Deferreds can be necessary when the logic to - * resolve a promise must be separate. - * - * @private - */ - class Deferred { - /** - * Creates a promise and exposes its resolve and reject functions as methods. - */ - constructor() { - this.promise = new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }); - } - } - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - // Callbacks to be executed whenever there's a quota error. - // Can't change Function type right now. - // eslint-disable-next-line @typescript-eslint/ban-types - const quotaErrorCallbacks = new Set(); - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * Runs all of the callback functions, one at a time sequentially, in the order - * in which they were registered. - * - * @memberof workbox-core - * @private - */ - async function executeQuotaErrorCallbacks() { - { - logger.log(`About to run ${quotaErrorCallbacks.size} ` + `callbacks to clean up caches.`); - } - for (const callback of quotaErrorCallbacks) { - await callback(); - { - logger.log(callback, 'is complete.'); - } - } - { - logger.log('Finished running callbacks.'); - } - } - - /* - Copyright 2019 Google LLC - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * Returns a promise that resolves and the passed number of milliseconds. - * This utility is an async/await-friendly version of `setTimeout`. - * - * @param {number} ms - * @return {Promise} - * @private - */ - function timeout(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - - // @ts-ignore - try { - self['workbox:strategies:7.0.0'] && _(); - } catch (e) {} - - /* - Copyright 2020 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - function toRequest(input) { - return typeof input === 'string' ? new Request(input) : input; - } - /** - * A class created every time a Strategy instance instance calls - * {@link workbox-strategies.Strategy~handle} or - * {@link workbox-strategies.Strategy~handleAll} that wraps all fetch and - * cache actions around plugin callbacks and keeps track of when the strategy - * is "done" (i.e. all added `event.waitUntil()` promises have resolved). - * - * @memberof workbox-strategies - */ - class StrategyHandler { - /** - * Creates a new instance associated with the passed strategy and event - * that's handling the request. - * - * The constructor also initializes the state that will be passed to each of - * the plugins handling this request. - * - * @param {workbox-strategies.Strategy} strategy - * @param {Object} options - * @param {Request|string} options.request A request to run this strategy for. - * @param {ExtendableEvent} options.event The event associated with the - * request. - * @param {URL} [options.url] - * @param {*} [options.params] The return value from the - * {@link workbox-routing~matchCallback} (if applicable). - */ - constructor(strategy, options) { - this._cacheKeys = {}; - /** - * The request the strategy is performing (passed to the strategy's - * `handle()` or `handleAll()` method). - * @name request - * @instance - * @type {Request} - * @memberof workbox-strategies.StrategyHandler - */ - /** - * The event associated with this request. - * @name event - * @instance - * @type {ExtendableEvent} - * @memberof workbox-strategies.StrategyHandler - */ - /** - * A `URL` instance of `request.url` (if passed to the strategy's - * `handle()` or `handleAll()` method). - * Note: the `url` param will be present if the strategy was invoked - * from a workbox `Route` object. - * @name url - * @instance - * @type {URL|undefined} - * @memberof workbox-strategies.StrategyHandler - */ - /** - * A `param` value (if passed to the strategy's - * `handle()` or `handleAll()` method). - * Note: the `param` param will be present if the strategy was invoked - * from a workbox `Route` object and the - * {@link workbox-routing~matchCallback} returned - * a truthy value (it will be that value). - * @name params - * @instance - * @type {*|undefined} - * @memberof workbox-strategies.StrategyHandler - */ - { - finalAssertExports.isInstance(options.event, ExtendableEvent, { - moduleName: 'workbox-strategies', - className: 'StrategyHandler', - funcName: 'constructor', - paramName: 'options.event' - }); - } - Object.assign(this, options); - this.event = options.event; - this._strategy = strategy; - this._handlerDeferred = new Deferred(); - this._extendLifetimePromises = []; - // Copy the plugins list (since it's mutable on the strategy), - // so any mutations don't affect this handler instance. - this._plugins = [...strategy.plugins]; - this._pluginStateMap = new Map(); - for (const plugin of this._plugins) { - this._pluginStateMap.set(plugin, {}); - } - this.event.waitUntil(this._handlerDeferred.promise); - } - /** - * Fetches a given request (and invokes any applicable plugin callback - * methods) using the `fetchOptions` (for non-navigation requests) and - * `plugins` defined on the `Strategy` object. - * - * The following plugin lifecycle methods are invoked when using this method: - * - `requestWillFetch()` - * - `fetchDidSucceed()` - * - `fetchDidFail()` - * - * @param {Request|string} input The URL or request to fetch. - * @return {Promise} - */ - async fetch(input) { - const { - event - } = this; - let request = toRequest(input); - if (request.mode === 'navigate' && event instanceof FetchEvent && event.preloadResponse) { - const possiblePreloadResponse = await event.preloadResponse; - if (possiblePreloadResponse) { + // Get route's catch handler, if it exists + const catchHandler = route && route.catchHandler; + if (responsePromise instanceof Promise && (this._catchHandler || catchHandler)) { + responsePromise = responsePromise.catch(async (err) => { + // If there's a route catch handler, process that first + if (catchHandler) { { - logger.log(`Using a preloaded navigation response for ` + `'${getFriendlyURL(request.url)}'`); + // Still include URL here as it will be async from the console group + // and may not make sense without the URL + logger.groupCollapsed( + `Error thrown when responding to: ` + ` ${getFriendlyURL(url)}. Falling back to route's Catch Handler.` + ); + logger.error(`Error thrown by:`, route); + logger.error(err); + logger.groupEnd(); + } + try { + return await catchHandler.handle({ + url, + request, + event, + params + }); + } catch (catchErr) { + if (catchErr instanceof Error) { + err = catchErr; + } } - return possiblePreloadResponse; } - } - // If there is a fetchDidFail plugin, we need to save a clone of the - // original request before it's either modified by a requestWillFetch - // plugin or before the original request's body is consumed via fetch(). - const originalRequest = this.hasCallback('fetchDidFail') ? request.clone() : null; - try { - for (const cb of this.iterateCallbacks('requestWillFetch')) { - request = await cb({ - request: request.clone(), + if (this._catchHandler) { + { + // Still include URL here as it will be async from the console group + // and may not make sense without the URL + logger.groupCollapsed( + `Error thrown when responding to: ` + ` ${getFriendlyURL(url)}. Falling back to global Catch Handler.` + ); + logger.error(`Error thrown by:`, route); + logger.error(err); + logger.groupEnd(); + } + return this._catchHandler.handle({ + url, + request, event }); } - } catch (err) { - if (err instanceof Error) { - throw new WorkboxError('plugin-error-request-will-fetch', { - thrownErrorMessage: err.message - }); - } - } - // The request can be altered by plugins with `requestWillFetch` making - // the original request (most likely from a `fetch` event) different - // from the Request we make. Pass both to `fetchDidFail` to aid debugging. - const pluginFilteredRequest = request.clone(); - try { - let fetchResponse; - // See https://github.com/GoogleChrome/workbox/issues/1796 - fetchResponse = await fetch(request, request.mode === 'navigate' ? undefined : this._strategy.fetchOptions); - if ("development" !== 'production') { - logger.debug(`Network request for ` + `'${getFriendlyURL(request.url)}' returned a response with ` + `status '${fetchResponse.status}'.`); - } - for (const callback of this.iterateCallbacks('fetchDidSucceed')) { - fetchResponse = await callback({ - event, - request: pluginFilteredRequest, - response: fetchResponse - }); - } - return fetchResponse; - } catch (error) { - { - logger.log(`Network request for ` + `'${getFriendlyURL(request.url)}' threw an error.`, error); - } - // `originalRequest` will only exist if a `fetchDidFail` callback - // is being used (see above). - if (originalRequest) { - await this.runCallbacks('fetchDidFail', { - error: error, - event, - originalRequest: originalRequest.clone(), - request: pluginFilteredRequest.clone() - }); - } - throw error; - } - } - /** - * Calls `this.fetch()` and (in the background) runs `this.cachePut()` on - * the response generated by `this.fetch()`. - * - * The call to `this.cachePut()` automatically invokes `this.waitUntil()`, - * so you do not have to manually call `waitUntil()` on the event. - * - * @param {Request|string} input The request or URL to fetch and cache. - * @return {Promise} - */ - async fetchAndCachePut(input) { - const response = await this.fetch(input); - const responseClone = response.clone(); - void this.waitUntil(this.cachePut(input, responseClone)); - return response; - } - /** - * Matches a request from the cache (and invokes any applicable plugin - * callback methods) using the `cacheName`, `matchOptions`, and `plugins` - * defined on the strategy object. - * - * The following plugin lifecycle methods are invoked when using this method: - * - cacheKeyWillByUsed() - * - cachedResponseWillByUsed() - * - * @param {Request|string} key The Request or URL to use as the cache key. - * @return {Promise} A matching response, if found. - */ - async cacheMatch(key) { - const request = toRequest(key); - let cachedResponse; - const { - cacheName, - matchOptions - } = this._strategy; - const effectiveRequest = await this.getCacheKey(request, 'read'); - const multiMatchOptions = Object.assign(Object.assign({}, matchOptions), { - cacheName + throw err; }); - cachedResponse = await caches.match(effectiveRequest, multiMatchOptions); + } + return responsePromise; + } + /** + * Checks a request and URL (and optionally an event) against the list of + * registered routes, and if there's a match, returns the corresponding + * route along with any params generated by the match. + * + * @param {Object} options + * @param {URL} options.url + * @param {boolean} options.sameOrigin The result of comparing `url.origin` + * against the current origin. + * @param {Request} options.request The request to match. + * @param {Event} options.event The corresponding event. + * @return {Object} An object with `route` and `params` properties. + * They are populated if a matching route was found or `undefined` + * otherwise. + */ + findMatchingRoute({ url, sameOrigin, request, event }) { + const routes = this._routes.get(request.method) || []; + for (const route of routes) { + let params; + // route.match returns type any, not possible to change right now. + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const matchResult = route.match({ + url, + sameOrigin, + request, + event + }); + if (matchResult) { + { + // Warn developers that using an async matchCallback is almost always + // not the right thing to do. + if (matchResult instanceof Promise) { + logger.warn( + `While routing ${getFriendlyURL(url)}, an async ` + + `matchCallback function was used. Please convert the ` + + `following route to use a synchronous matchCallback function:`, + route + ); + } + } + // See https://github.com/GoogleChrome/workbox/issues/2079 + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + params = matchResult; + if (Array.isArray(params) && params.length === 0) { + // Instead of passing an empty array in as params, use undefined. + params = undefined; + } else if ( + matchResult.constructor === Object && + // eslint-disable-line + Object.keys(matchResult).length === 0 + ) { + // Instead of passing an empty object in as params, use undefined. + params = undefined; + } else if (typeof matchResult === "boolean") { + // For the boolean value true (rather than just something truth-y), + // don't set params. + // See https://github.com/GoogleChrome/workbox/pull/2134#issuecomment-513924353 + params = undefined; + } + // Return early if have a match. + return { + route, + params + }; + } + } + // If no match was found above, return and empty object. + return {}; + } + /** + * Define a default `handler` that's called when no routes explicitly + * match the incoming request. + * + * Each HTTP method ('GET', 'POST', etc.) gets its own default handler. + * + * Without a default handler, unmatched requests will go against the + * network as if there were no service worker present. + * + * @param {workbox-routing~handlerCallback} handler A callback + * function that returns a Promise resulting in a Response. + * @param {string} [method='GET'] The HTTP method to associate with this + * default handler. Each method has its own default. + */ + setDefaultHandler(handler, method = defaultMethod) { + this._defaultHandlerMap.set(method, normalizeHandler(handler)); + } + /** + * If a Route throws an error while handling a request, this `handler` + * will be called and given a chance to provide a response. + * + * @param {workbox-routing~handlerCallback} handler A callback + * function that returns a Promise resulting in a Response. + */ + setCatchHandler(handler) { + this._catchHandler = normalizeHandler(handler); + } + /** + * Registers a route with the router. + * + * @param {workbox-routing.Route} route The route to register. + */ + registerRoute(route) { + { + finalAssertExports.isType(route, "object", { + moduleName: "workbox-routing", + className: "Router", + funcName: "registerRoute", + paramName: "route" + }); + finalAssertExports.hasMethod(route, "match", { + moduleName: "workbox-routing", + className: "Router", + funcName: "registerRoute", + paramName: "route" + }); + finalAssertExports.isType(route.handler, "object", { + moduleName: "workbox-routing", + className: "Router", + funcName: "registerRoute", + paramName: "route" + }); + finalAssertExports.hasMethod(route.handler, "handle", { + moduleName: "workbox-routing", + className: "Router", + funcName: "registerRoute", + paramName: "route.handler" + }); + finalAssertExports.isType(route.method, "string", { + moduleName: "workbox-routing", + className: "Router", + funcName: "registerRoute", + paramName: "route.method" + }); + } + if (!this._routes.has(route.method)) { + this._routes.set(route.method, []); + } + // Give precedence to all of the earlier routes by adding this additional + // route to the end of the array. + this._routes.get(route.method).push(route); + } + /** + * Unregisters a route with the router. + * + * @param {workbox-routing.Route} route The route to unregister. + */ + unregisterRoute(route) { + if (!this._routes.has(route.method)) { + throw new WorkboxError("unregister-route-but-not-found-with-method", { + method: route.method + }); + } + const routeIndex = this._routes.get(route.method).indexOf(route); + if (routeIndex > -1) { + this._routes.get(route.method).splice(routeIndex, 1); + } else { + throw new WorkboxError("unregister-route-route-not-registered"); + } + } + } + + /* + Copyright 2019 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + let defaultRouter; + /** + * Creates a new, singleton Router instance if one does not exist. If one + * does already exist, that instance is returned. + * + * @private + * @return {Router} + */ + const getOrCreateDefaultRouter = () => { + if (!defaultRouter) { + defaultRouter = new Router(); + // The helpers that use the default Router assume these listeners exist. + defaultRouter.addFetchListener(); + defaultRouter.addCacheListener(); + } + return defaultRouter; + }; + + /* + Copyright 2019 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * Easily register a RegExp, string, or function with a caching + * strategy to a singleton Router instance. + * + * This method will generate a Route for you if needed and + * call {@link workbox-routing.Router#registerRoute}. + * + * @param {RegExp|string|workbox-routing.Route~matchCallback|workbox-routing.Route} capture + * If the capture param is a `Route`, all other arguments will be ignored. + * @param {workbox-routing~handlerCallback} [handler] A callback + * function that returns a Promise resulting in a Response. This parameter + * is required if `capture` is not a `Route` object. + * @param {string} [method='GET'] The HTTP method to match the Route + * against. + * @return {workbox-routing.Route} The generated `Route`. + * + * @memberof workbox-routing + */ + function registerRoute(capture, handler, method) { + let route; + if (typeof capture === "string") { + const captureUrl = new URL(capture, location.href); + { + if (!(capture.startsWith("/") || capture.startsWith("http"))) { + throw new WorkboxError("invalid-string", { + moduleName: "workbox-routing", + funcName: "registerRoute", + paramName: "capture" + }); + } + // We want to check if Express-style wildcards are in the pathname only. + // TODO: Remove this log message in v4. + const valueToCheck = capture.startsWith("http") ? captureUrl.pathname : capture; + // See https://github.com/pillarjs/path-to-regexp#parameters + const wildcards = "[*:?+]"; + if (new RegExp(`${wildcards}`).exec(valueToCheck)) { + logger.debug( + `The '$capture' parameter contains an Express-style wildcard ` + + `character (${wildcards}). Strings are now always interpreted as ` + + `exact matches; use a RegExp for partial or wildcard matches.` + ); + } + } + const matchCallback = ({ url }) => { { - if (cachedResponse) { - logger.debug(`Found a cached response in '${cacheName}'.`); - } else { - logger.debug(`No cached response found in '${cacheName}'.`); + if (url.pathname === captureUrl.pathname && url.origin !== captureUrl.origin) { + logger.debug( + `${capture} only partially matches the cross-origin URL ` + + `${url.toString()}. This route will only handle cross-origin requests ` + + `if they match the entire URL.` + ); } } - for (const callback of this.iterateCallbacks('cachedResponseWillBeUsed')) { - cachedResponse = (await callback({ + return url.href === captureUrl.href; + }; + // If `capture` is a string then `handler` and `method` must be present. + route = new Route(matchCallback, handler, method); + } else if (capture instanceof RegExp) { + // If `capture` is a `RegExp` then `handler` and `method` must be present. + route = new RegExpRoute(capture, handler, method); + } else if (typeof capture === "function") { + // If `capture` is a function then `handler` and `method` must be present. + route = new Route(capture, handler, method); + } else if (capture instanceof Route) { + route = capture; + } else { + throw new WorkboxError("unsupported-route-type", { + moduleName: "workbox-routing", + funcName: "registerRoute", + paramName: "capture" + }); + } + const defaultRouter = getOrCreateDefaultRouter(); + defaultRouter.registerRoute(route); + return route; + } + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + const _cacheNameDetails = { + googleAnalytics: "googleAnalytics", + precache: "precache-v2", + prefix: "workbox", + runtime: "runtime", + suffix: typeof registration !== "undefined" ? registration.scope : "" + }; + const _createCacheName = (cacheName) => { + return [_cacheNameDetails.prefix, cacheName, _cacheNameDetails.suffix] + .filter((value) => value && value.length > 0) + .join("-"); + }; + const eachCacheNameDetail = (fn) => { + for (const key of Object.keys(_cacheNameDetails)) { + fn(key); + } + }; + const cacheNames = { + updateDetails: (details) => { + eachCacheNameDetail((key) => { + if (typeof details[key] === "string") { + _cacheNameDetails[key] = details[key]; + } + }); + }, + getGoogleAnalyticsName: (userCacheName) => { + return userCacheName || _createCacheName(_cacheNameDetails.googleAnalytics); + }, + getPrecacheName: (userCacheName) => { + return userCacheName || _createCacheName(_cacheNameDetails.precache); + }, + getPrefix: () => { + return _cacheNameDetails.prefix; + }, + getRuntimeName: (userCacheName) => { + return userCacheName || _createCacheName(_cacheNameDetails.runtime); + }, + getSuffix: () => { + return _cacheNameDetails.suffix; + } + }; + + /* + Copyright 2020 Google LLC + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * A utility method that makes it easier to use `event.waitUntil` with + * async functions and return the result. + * + * @param {ExtendableEvent} event + * @param {Function} asyncFn + * @return {Function} + * @private + */ + function waitUntil(event, asyncFn) { + const returnPromise = asyncFn(); + event.waitUntil(returnPromise); + return returnPromise; + } + + // @ts-ignore + try { + self["workbox:precaching:7.0.0"] && _(); + } catch (e) {} + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + // Name of the search parameter used to store revision info. + const REVISION_SEARCH_PARAM = "__WB_REVISION__"; + /** + * Converts a manifest entry into a versioned URL suitable for precaching. + * + * @param {Object|string} entry + * @return {string} A URL with versioning info. + * + * @private + * @memberof workbox-precaching + */ + function createCacheKey(entry) { + if (!entry) { + throw new WorkboxError("add-to-cache-list-unexpected-type", { + entry + }); + } + // If a precache manifest entry is a string, it's assumed to be a versioned + // URL, like '/app.abcd1234.js'. Return as-is. + if (typeof entry === "string") { + const urlObject = new URL(entry, location.href); + return { + cacheKey: urlObject.href, + url: urlObject.href + }; + } + const { revision, url } = entry; + if (!url) { + throw new WorkboxError("add-to-cache-list-unexpected-type", { + entry + }); + } + // If there's just a URL and no revision, then it's also assumed to be a + // versioned URL. + if (!revision) { + const urlObject = new URL(url, location.href); + return { + cacheKey: urlObject.href, + url: urlObject.href + }; + } + // Otherwise, construct a properly versioned URL using the custom Workbox + // search parameter along with the revision info. + const cacheKeyURL = new URL(url, location.href); + const originalURL = new URL(url, location.href); + cacheKeyURL.searchParams.set(REVISION_SEARCH_PARAM, revision); + return { + cacheKey: cacheKeyURL.href, + url: originalURL.href + }; + } + + /* + Copyright 2020 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * A plugin, designed to be used with PrecacheController, to determine the + * of assets that were updated (or not updated) during the install event. + * + * @private + */ + class PrecacheInstallReportPlugin { + constructor() { + this.updatedURLs = []; + this.notUpdatedURLs = []; + this.handlerWillStart = async ({ request, state }) => { + // TODO: `state` should never be undefined... + if (state) { + state.originalRequest = request; + } + }; + this.cachedResponseWillBeUsed = async ({ event, state, cachedResponse }) => { + if (event.type === "install") { + if (state && state.originalRequest && state.originalRequest instanceof Request) { + // TODO: `state` should never be undefined... + const url = state.originalRequest.url; + if (cachedResponse) { + this.notUpdatedURLs.push(url); + } else { + this.updatedURLs.push(url); + } + } + } + return cachedResponse; + }; + } + } + + /* + Copyright 2020 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * A plugin, designed to be used with PrecacheController, to translate URLs into + * the corresponding cache key, based on the current revision info. + * + * @private + */ + class PrecacheCacheKeyPlugin { + constructor({ precacheController }) { + this.cacheKeyWillBeUsed = async ({ request, params }) => { + // Params is type any, can't change right now. + /* eslint-disable */ + const cacheKey = + (params === null || params === void 0 ? void 0 : params.cacheKey) || + this._precacheController.getCacheKeyForURL(request.url); + /* eslint-enable */ + return cacheKey + ? new Request(cacheKey, { + headers: request.headers + }) + : request; + }; + this._precacheController = precacheController; + } + } + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * @param {string} groupTitle + * @param {Array} deletedURLs + * + * @private + */ + const logGroup = (groupTitle, deletedURLs) => { + logger.groupCollapsed(groupTitle); + for (const url of deletedURLs) { + logger.log(url); + } + logger.groupEnd(); + }; + /** + * @param {Array} deletedURLs + * + * @private + * @memberof workbox-precaching + */ + function printCleanupDetails(deletedURLs) { + const deletionCount = deletedURLs.length; + if (deletionCount > 0) { + logger.groupCollapsed( + `During precaching cleanup, ` + + `${deletionCount} cached ` + + `request${deletionCount === 1 ? " was" : "s were"} deleted.` + ); + logGroup("Deleted Cache Requests", deletedURLs); + logger.groupEnd(); + } + } + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * @param {string} groupTitle + * @param {Array} urls + * + * @private + */ + function _nestedGroup(groupTitle, urls) { + if (urls.length === 0) { + return; + } + logger.groupCollapsed(groupTitle); + for (const url of urls) { + logger.log(url); + } + logger.groupEnd(); + } + /** + * @param {Array} urlsToPrecache + * @param {Array} urlsAlreadyPrecached + * + * @private + * @memberof workbox-precaching + */ + function printInstallDetails(urlsToPrecache, urlsAlreadyPrecached) { + const precachedCount = urlsToPrecache.length; + const alreadyPrecachedCount = urlsAlreadyPrecached.length; + if (precachedCount || alreadyPrecachedCount) { + let message = `Precaching ${precachedCount} file${precachedCount === 1 ? "" : "s"}.`; + if (alreadyPrecachedCount > 0) { + message += + ` ${alreadyPrecachedCount} ` + `file${alreadyPrecachedCount === 1 ? " is" : "s are"} already cached.`; + } + logger.groupCollapsed(message); + _nestedGroup(`View newly precached URLs.`, urlsToPrecache); + _nestedGroup(`View previously precached URLs.`, urlsAlreadyPrecached); + logger.groupEnd(); + } + } + + /* + Copyright 2019 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + let supportStatus; + /** + * A utility function that determines whether the current browser supports + * constructing a new `Response` from a `response.body` stream. + * + * @return {boolean} `true`, if the current browser can successfully + * construct a `Response` from a `response.body` stream, `false` otherwise. + * + * @private + */ + function canConstructResponseFromBodyStream() { + if (supportStatus === undefined) { + const testResponse = new Response(""); + if ("body" in testResponse) { + try { + new Response(testResponse.body); + supportStatus = true; + } catch (error) { + supportStatus = false; + } + } + supportStatus = false; + } + return supportStatus; + } + + /* + Copyright 2019 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * Allows developers to copy a response and modify its `headers`, `status`, + * or `statusText` values (the values settable via a + * [`ResponseInit`]{@link https://developer.mozilla.org/en-US/docs/Web/API/Response/Response#Syntax} + * object in the constructor). + * To modify these values, pass a function as the second argument. That + * function will be invoked with a single object with the response properties + * `{headers, status, statusText}`. The return value of this function will + * be used as the `ResponseInit` for the new `Response`. To change the values + * either modify the passed parameter(s) and return it, or return a totally + * new object. + * + * This method is intentionally limited to same-origin responses, regardless of + * whether CORS was used or not. + * + * @param {Response} response + * @param {Function} modifier + * @memberof workbox-core + */ + async function copyResponse(response, modifier) { + let origin = null; + // If response.url isn't set, assume it's cross-origin and keep origin null. + if (response.url) { + const responseURL = new URL(response.url); + origin = responseURL.origin; + } + if (origin !== self.location.origin) { + throw new WorkboxError("cross-origin-copy-response", { + origin + }); + } + const clonedResponse = response.clone(); + // Create a fresh `ResponseInit` object by cloning the headers. + const responseInit = { + headers: new Headers(clonedResponse.headers), + status: clonedResponse.status, + statusText: clonedResponse.statusText + }; + // Apply any user modifications. + const modifiedResponseInit = modifier ? modifier(responseInit) : responseInit; + // Create the new response from the body stream and `ResponseInit` + // modifications. Note: not all browsers support the Response.body stream, + // so fall back to reading the entire body into memory as a blob. + const body = canConstructResponseFromBodyStream() ? clonedResponse.body : await clonedResponse.blob(); + return new Response(body, modifiedResponseInit); + } + + /* + Copyright 2020 Google LLC + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + function stripParams(fullURL, ignoreParams) { + const strippedURL = new URL(fullURL); + for (const param of ignoreParams) { + strippedURL.searchParams.delete(param); + } + return strippedURL.href; + } + /** + * Matches an item in the cache, ignoring specific URL params. This is similar + * to the `ignoreSearch` option, but it allows you to ignore just specific + * params (while continuing to match on the others). + * + * @private + * @param {Cache} cache + * @param {Request} request + * @param {Object} matchOptions + * @param {Array} ignoreParams + * @return {Promise} + */ + async function cacheMatchIgnoreParams(cache, request, ignoreParams, matchOptions) { + const strippedRequestURL = stripParams(request.url, ignoreParams); + // If the request doesn't include any ignored params, match as normal. + if (request.url === strippedRequestURL) { + return cache.match(request, matchOptions); + } + // Otherwise, match by comparing keys + const keysOptions = Object.assign(Object.assign({}, matchOptions), { + ignoreSearch: true + }); + const cacheKeys = await cache.keys(request, keysOptions); + for (const cacheKey of cacheKeys) { + const strippedCacheKeyURL = stripParams(cacheKey.url, ignoreParams); + if (strippedRequestURL === strippedCacheKeyURL) { + return cache.match(cacheKey, matchOptions); + } + } + return; + } + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * The Deferred class composes Promises in a way that allows for them to be + * resolved or rejected from outside the constructor. In most cases promises + * should be used directly, but Deferreds can be necessary when the logic to + * resolve a promise must be separate. + * + * @private + */ + class Deferred { + /** + * Creates a promise and exposes its resolve and reject functions as methods. + */ + constructor() { + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + } + } + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + // Callbacks to be executed whenever there's a quota error. + // Can't change Function type right now. + // eslint-disable-next-line @typescript-eslint/ban-types + const quotaErrorCallbacks = new Set(); + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * Runs all of the callback functions, one at a time sequentially, in the order + * in which they were registered. + * + * @memberof workbox-core + * @private + */ + async function executeQuotaErrorCallbacks() { + { + logger.log(`About to run ${quotaErrorCallbacks.size} ` + `callbacks to clean up caches.`); + } + for (const callback of quotaErrorCallbacks) { + await callback(); + { + logger.log(callback, "is complete."); + } + } + { + logger.log("Finished running callbacks."); + } + } + + /* + Copyright 2019 Google LLC + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * Returns a promise that resolves and the passed number of milliseconds. + * This utility is an async/await-friendly version of `setTimeout`. + * + * @param {number} ms + * @return {Promise} + * @private + */ + function timeout(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + // @ts-ignore + try { + self["workbox:strategies:7.0.0"] && _(); + } catch (e) {} + + /* + Copyright 2020 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + function toRequest(input) { + return typeof input === "string" ? new Request(input) : input; + } + /** + * A class created every time a Strategy instance instance calls + * {@link workbox-strategies.Strategy~handle} or + * {@link workbox-strategies.Strategy~handleAll} that wraps all fetch and + * cache actions around plugin callbacks and keeps track of when the strategy + * is "done" (i.e. all added `event.waitUntil()` promises have resolved). + * + * @memberof workbox-strategies + */ + class StrategyHandler { + /** + * Creates a new instance associated with the passed strategy and event + * that's handling the request. + * + * The constructor also initializes the state that will be passed to each of + * the plugins handling this request. + * + * @param {workbox-strategies.Strategy} strategy + * @param {Object} options + * @param {Request|string} options.request A request to run this strategy for. + * @param {ExtendableEvent} options.event The event associated with the + * request. + * @param {URL} [options.url] + * @param {*} [options.params] The return value from the + * {@link workbox-routing~matchCallback} (if applicable). + */ + constructor(strategy, options) { + this._cacheKeys = {}; + /** + * The request the strategy is performing (passed to the strategy's + * `handle()` or `handleAll()` method). + * @name request + * @instance + * @type {Request} + * @memberof workbox-strategies.StrategyHandler + */ + /** + * The event associated with this request. + * @name event + * @instance + * @type {ExtendableEvent} + * @memberof workbox-strategies.StrategyHandler + */ + /** + * A `URL` instance of `request.url` (if passed to the strategy's + * `handle()` or `handleAll()` method). + * Note: the `url` param will be present if the strategy was invoked + * from a workbox `Route` object. + * @name url + * @instance + * @type {URL|undefined} + * @memberof workbox-strategies.StrategyHandler + */ + /** + * A `param` value (if passed to the strategy's + * `handle()` or `handleAll()` method). + * Note: the `param` param will be present if the strategy was invoked + * from a workbox `Route` object and the + * {@link workbox-routing~matchCallback} returned + * a truthy value (it will be that value). + * @name params + * @instance + * @type {*|undefined} + * @memberof workbox-strategies.StrategyHandler + */ + { + finalAssertExports.isInstance(options.event, ExtendableEvent, { + moduleName: "workbox-strategies", + className: "StrategyHandler", + funcName: "constructor", + paramName: "options.event" + }); + } + Object.assign(this, options); + this.event = options.event; + this._strategy = strategy; + this._handlerDeferred = new Deferred(); + this._extendLifetimePromises = []; + // Copy the plugins list (since it's mutable on the strategy), + // so any mutations don't affect this handler instance. + this._plugins = [...strategy.plugins]; + this._pluginStateMap = new Map(); + for (const plugin of this._plugins) { + this._pluginStateMap.set(plugin, {}); + } + this.event.waitUntil(this._handlerDeferred.promise); + } + /** + * Fetches a given request (and invokes any applicable plugin callback + * methods) using the `fetchOptions` (for non-navigation requests) and + * `plugins` defined on the `Strategy` object. + * + * The following plugin lifecycle methods are invoked when using this method: + * - `requestWillFetch()` + * - `fetchDidSucceed()` + * - `fetchDidFail()` + * + * @param {Request|string} input The URL or request to fetch. + * @return {Promise} + */ + async fetch(input) { + const { event } = this; + let request = toRequest(input); + if (request.mode === "navigate" && event instanceof FetchEvent && event.preloadResponse) { + const possiblePreloadResponse = await event.preloadResponse; + if (possiblePreloadResponse) { + { + logger.log(`Using a preloaded navigation response for ` + `'${getFriendlyURL(request.url)}'`); + } + return possiblePreloadResponse; + } + } + // If there is a fetchDidFail plugin, we need to save a clone of the + // original request before it's either modified by a requestWillFetch + // plugin or before the original request's body is consumed via fetch(). + const originalRequest = this.hasCallback("fetchDidFail") ? request.clone() : null; + try { + for (const cb of this.iterateCallbacks("requestWillFetch")) { + request = await cb({ + request: request.clone(), + event + }); + } + } catch (err) { + if (err instanceof Error) { + throw new WorkboxError("plugin-error-request-will-fetch", { + thrownErrorMessage: err.message + }); + } + } + // The request can be altered by plugins with `requestWillFetch` making + // the original request (most likely from a `fetch` event) different + // from the Request we make. Pass both to `fetchDidFail` to aid debugging. + const pluginFilteredRequest = request.clone(); + try { + let fetchResponse; + // See https://github.com/GoogleChrome/workbox/issues/1796 + fetchResponse = await fetch(request, request.mode === "navigate" ? undefined : this._strategy.fetchOptions); + if ("development" !== "production") { + logger.debug( + `Network request for ` + + `'${getFriendlyURL(request.url)}' returned a response with ` + + `status '${fetchResponse.status}'.` + ); + } + for (const callback of this.iterateCallbacks("fetchDidSucceed")) { + fetchResponse = await callback({ + event, + request: pluginFilteredRequest, + response: fetchResponse + }); + } + return fetchResponse; + } catch (error) { + { + logger.log(`Network request for ` + `'${getFriendlyURL(request.url)}' threw an error.`, error); + } + // `originalRequest` will only exist if a `fetchDidFail` callback + // is being used (see above). + if (originalRequest) { + await this.runCallbacks("fetchDidFail", { + error: error, + event, + originalRequest: originalRequest.clone(), + request: pluginFilteredRequest.clone() + }); + } + throw error; + } + } + /** + * Calls `this.fetch()` and (in the background) runs `this.cachePut()` on + * the response generated by `this.fetch()`. + * + * The call to `this.cachePut()` automatically invokes `this.waitUntil()`, + * so you do not have to manually call `waitUntil()` on the event. + * + * @param {Request|string} input The request or URL to fetch and cache. + * @return {Promise} + */ + async fetchAndCachePut(input) { + const response = await this.fetch(input); + const responseClone = response.clone(); + void this.waitUntil(this.cachePut(input, responseClone)); + return response; + } + /** + * Matches a request from the cache (and invokes any applicable plugin + * callback methods) using the `cacheName`, `matchOptions`, and `plugins` + * defined on the strategy object. + * + * The following plugin lifecycle methods are invoked when using this method: + * - cacheKeyWillByUsed() + * - cachedResponseWillByUsed() + * + * @param {Request|string} key The Request or URL to use as the cache key. + * @return {Promise} A matching response, if found. + */ + async cacheMatch(key) { + const request = toRequest(key); + let cachedResponse; + const { cacheName, matchOptions } = this._strategy; + const effectiveRequest = await this.getCacheKey(request, "read"); + const multiMatchOptions = Object.assign(Object.assign({}, matchOptions), { + cacheName + }); + cachedResponse = await caches.match(effectiveRequest, multiMatchOptions); + { + if (cachedResponse) { + logger.debug(`Found a cached response in '${cacheName}'.`); + } else { + logger.debug(`No cached response found in '${cacheName}'.`); + } + } + for (const callback of this.iterateCallbacks("cachedResponseWillBeUsed")) { + cachedResponse = + (await callback({ cacheName, matchOptions, cachedResponse, request: effectiveRequest, event: this.event })) || undefined; - } - return cachedResponse; } - /** - * Puts a request/response pair in the cache (and invokes any applicable - * plugin callback methods) using the `cacheName` and `plugins` defined on - * the strategy object. - * - * The following plugin lifecycle methods are invoked when using this method: - * - cacheKeyWillByUsed() - * - cacheWillUpdate() - * - cacheDidUpdate() - * - * @param {Request|string} key The request or URL to use as the cache key. - * @param {Response} response The response to cache. - * @return {Promise} `false` if a cacheWillUpdate caused the response - * not be cached, and `true` otherwise. - */ - async cachePut(key, response) { - const request = toRequest(key); - // Run in the next task to avoid blocking other cache reads. - // https://github.com/w3c/ServiceWorker/issues/1397 - await timeout(0); - const effectiveRequest = await this.getCacheKey(request, 'write'); - { - if (effectiveRequest.method && effectiveRequest.method !== 'GET') { - throw new WorkboxError('attempt-to-cache-non-get-request', { - url: getFriendlyURL(effectiveRequest.url), - method: effectiveRequest.method - }); - } - // See https://github.com/GoogleChrome/workbox/issues/2818 - const vary = response.headers.get('Vary'); - if (vary) { - logger.debug(`The response for ${getFriendlyURL(effectiveRequest.url)} ` + `has a 'Vary: ${vary}' header. ` + `Consider setting the {ignoreVary: true} option on your strategy ` + `to ensure cache matching and deletion works as expected.`); - } - } - if (!response) { - { - logger.error(`Cannot cache non-existent response for ` + `'${getFriendlyURL(effectiveRequest.url)}'.`); - } - throw new WorkboxError('cache-put-with-no-response', { - url: getFriendlyURL(effectiveRequest.url) + return cachedResponse; + } + /** + * Puts a request/response pair in the cache (and invokes any applicable + * plugin callback methods) using the `cacheName` and `plugins` defined on + * the strategy object. + * + * The following plugin lifecycle methods are invoked when using this method: + * - cacheKeyWillByUsed() + * - cacheWillUpdate() + * - cacheDidUpdate() + * + * @param {Request|string} key The request or URL to use as the cache key. + * @param {Response} response The response to cache. + * @return {Promise} `false` if a cacheWillUpdate caused the response + * not be cached, and `true` otherwise. + */ + async cachePut(key, response) { + const request = toRequest(key); + // Run in the next task to avoid blocking other cache reads. + // https://github.com/w3c/ServiceWorker/issues/1397 + await timeout(0); + const effectiveRequest = await this.getCacheKey(request, "write"); + { + if (effectiveRequest.method && effectiveRequest.method !== "GET") { + throw new WorkboxError("attempt-to-cache-non-get-request", { + url: getFriendlyURL(effectiveRequest.url), + method: effectiveRequest.method }); } - const responseToCache = await this._ensureResponseSafeToCache(response); - if (!responseToCache) { - { - logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' ` + `will not be cached.`, responseToCache); - } - return false; + // See https://github.com/GoogleChrome/workbox/issues/2818 + const vary = response.headers.get("Vary"); + if (vary) { + logger.debug( + `The response for ${getFriendlyURL(effectiveRequest.url)} ` + + `has a 'Vary: ${vary}' header. ` + + `Consider setting the {ignoreVary: true} option on your strategy ` + + `to ensure cache matching and deletion works as expected.` + ); } - const { + } + if (!response) { + { + logger.error(`Cannot cache non-existent response for ` + `'${getFriendlyURL(effectiveRequest.url)}'.`); + } + throw new WorkboxError("cache-put-with-no-response", { + url: getFriendlyURL(effectiveRequest.url) + }); + } + const responseToCache = await this._ensureResponseSafeToCache(response); + if (!responseToCache) { + { + logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' ` + `will not be cached.`, responseToCache); + } + return false; + } + const { cacheName, matchOptions } = this._strategy; + const cache = await self.caches.open(cacheName); + const hasCacheUpdateCallback = this.hasCallback("cacheDidUpdate"); + const oldResponse = hasCacheUpdateCallback + ? await cacheMatchIgnoreParams( + // TODO(philipwalton): the `__WB_REVISION__` param is a precaching + // feature. Consider into ways to only add this behavior if using + // precaching. + cache, + effectiveRequest.clone(), + ["__WB_REVISION__"], + matchOptions + ) + : null; + { + logger.debug( + `Updating the '${cacheName}' cache with a new Response ` + `for ${getFriendlyURL(effectiveRequest.url)}.` + ); + } + try { + await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache); + } catch (error) { + if (error instanceof Error) { + // See https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-QuotaExceededError + if (error.name === "QuotaExceededError") { + await executeQuotaErrorCallbacks(); + } + throw error; + } + } + for (const callback of this.iterateCallbacks("cacheDidUpdate")) { + await callback({ cacheName, - matchOptions - } = this._strategy; - const cache = await self.caches.open(cacheName); - const hasCacheUpdateCallback = this.hasCallback('cacheDidUpdate'); - const oldResponse = hasCacheUpdateCallback ? await cacheMatchIgnoreParams( - // TODO(philipwalton): the `__WB_REVISION__` param is a precaching - // feature. Consider into ways to only add this behavior if using - // precaching. - cache, effectiveRequest.clone(), ['__WB_REVISION__'], matchOptions) : null; - { - logger.debug(`Updating the '${cacheName}' cache with a new Response ` + `for ${getFriendlyURL(effectiveRequest.url)}.`); - } - try { - await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache); - } catch (error) { - if (error instanceof Error) { - // See https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-QuotaExceededError - if (error.name === 'QuotaExceededError') { - await executeQuotaErrorCallbacks(); - } - throw error; - } - } - for (const callback of this.iterateCallbacks('cacheDidUpdate')) { - await callback({ - cacheName, - oldResponse, - newResponse: responseToCache.clone(), - request: effectiveRequest, - event: this.event - }); - } - return true; + oldResponse, + newResponse: responseToCache.clone(), + request: effectiveRequest, + event: this.event + }); } - /** - * Checks the list of plugins for the `cacheKeyWillBeUsed` callback, and - * executes any of those callbacks found in sequence. The final `Request` - * object returned by the last plugin is treated as the cache key for cache - * reads and/or writes. If no `cacheKeyWillBeUsed` plugin callbacks have - * been registered, the passed request is returned unmodified - * - * @param {Request} request - * @param {string} mode - * @return {Promise} - */ - async getCacheKey(request, mode) { - const key = `${request.url} | ${mode}`; - if (!this._cacheKeys[key]) { - let effectiveRequest = request; - for (const callback of this.iterateCallbacks('cacheKeyWillBeUsed')) { - effectiveRequest = toRequest(await callback({ + return true; + } + /** + * Checks the list of plugins for the `cacheKeyWillBeUsed` callback, and + * executes any of those callbacks found in sequence. The final `Request` + * object returned by the last plugin is treated as the cache key for cache + * reads and/or writes. If no `cacheKeyWillBeUsed` plugin callbacks have + * been registered, the passed request is returned unmodified + * + * @param {Request} request + * @param {string} mode + * @return {Promise} + */ + async getCacheKey(request, mode) { + const key = `${request.url} | ${mode}`; + if (!this._cacheKeys[key]) { + let effectiveRequest = request; + for (const callback of this.iterateCallbacks("cacheKeyWillBeUsed")) { + effectiveRequest = toRequest( + await callback({ mode, request: effectiveRequest, event: this.event, // params has a type any can't change right now. params: this.params // eslint-disable-line - })); - } - this._cacheKeys[key] = effectiveRequest; + }) + ); } - return this._cacheKeys[key]; + this._cacheKeys[key] = effectiveRequest; } - /** - * Returns true if the strategy has at least one plugin with the given - * callback. - * - * @param {string} name The name of the callback to check for. - * @return {boolean} - */ - hasCallback(name) { - for (const plugin of this._strategy.plugins) { - if (name in plugin) { - return true; - } - } - return false; - } - /** - * Runs all plugin callbacks matching the given name, in order, passing the - * given param object (merged ith the current plugin state) as the only - * argument. - * - * Note: since this method runs all plugins, it's not suitable for cases - * where the return value of a callback needs to be applied prior to calling - * the next callback. See - * {@link workbox-strategies.StrategyHandler#iterateCallbacks} - * below for how to handle that case. - * - * @param {string} name The name of the callback to run within each plugin. - * @param {Object} param The object to pass as the first (and only) param - * when executing each callback. This object will be merged with the - * current plugin state prior to callback execution. - */ - async runCallbacks(name, param) { - for (const callback of this.iterateCallbacks(name)) { - // TODO(philipwalton): not sure why `any` is needed. It seems like - // this should work with `as WorkboxPluginCallbackParam[C]`. - await callback(param); + return this._cacheKeys[key]; + } + /** + * Returns true if the strategy has at least one plugin with the given + * callback. + * + * @param {string} name The name of the callback to check for. + * @return {boolean} + */ + hasCallback(name) { + for (const plugin of this._strategy.plugins) { + if (name in plugin) { + return true; } } - /** - * Accepts a callback and returns an iterable of matching plugin callbacks, - * where each callback is wrapped with the current handler state (i.e. when - * you call each callback, whatever object parameter you pass it will - * be merged with the plugin's current state). - * - * @param {string} name The name fo the callback to run - * @return {Array} - */ - *iterateCallbacks(name) { - for (const plugin of this._strategy.plugins) { - if (typeof plugin[name] === 'function') { - const state = this._pluginStateMap.get(plugin); - const statefulCallback = param => { - const statefulParam = Object.assign(Object.assign({}, param), { - state - }); - // TODO(philipwalton): not sure why `any` is needed. It seems like - // this should work with `as WorkboxPluginCallbackParam[C]`. - return plugin[name](statefulParam); - }; - yield statefulCallback; - } + return false; + } + /** + * Runs all plugin callbacks matching the given name, in order, passing the + * given param object (merged ith the current plugin state) as the only + * argument. + * + * Note: since this method runs all plugins, it's not suitable for cases + * where the return value of a callback needs to be applied prior to calling + * the next callback. See + * {@link workbox-strategies.StrategyHandler#iterateCallbacks} + * below for how to handle that case. + * + * @param {string} name The name of the callback to run within each plugin. + * @param {Object} param The object to pass as the first (and only) param + * when executing each callback. This object will be merged with the + * current plugin state prior to callback execution. + */ + async runCallbacks(name, param) { + for (const callback of this.iterateCallbacks(name)) { + // TODO(philipwalton): not sure why `any` is needed. It seems like + // this should work with `as WorkboxPluginCallbackParam[C]`. + await callback(param); + } + } + /** + * Accepts a callback and returns an iterable of matching plugin callbacks, + * where each callback is wrapped with the current handler state (i.e. when + * you call each callback, whatever object parameter you pass it will + * be merged with the plugin's current state). + * + * @param {string} name The name fo the callback to run + * @return {Array} + */ + *iterateCallbacks(name) { + for (const plugin of this._strategy.plugins) { + if (typeof plugin[name] === "function") { + const state = this._pluginStateMap.get(plugin); + const statefulCallback = (param) => { + const statefulParam = Object.assign(Object.assign({}, param), { + state + }); + // TODO(philipwalton): not sure why `any` is needed. It seems like + // this should work with `as WorkboxPluginCallbackParam[C]`. + return plugin[name](statefulParam); + }; + yield statefulCallback; } } - /** - * Adds a promise to the - * [extend lifetime promises]{@link https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises} - * of the event event associated with the request being handled (usually a - * `FetchEvent`). - * - * Note: you can await - * {@link workbox-strategies.StrategyHandler~doneWaiting} - * to know when all added promises have settled. - * - * @param {Promise} promise A promise to add to the extend lifetime promises - * of the event that triggered the request. - */ - waitUntil(promise) { - this._extendLifetimePromises.push(promise); - return promise; + } + /** + * Adds a promise to the + * [extend lifetime promises]{@link https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises} + * of the event event associated with the request being handled (usually a + * `FetchEvent`). + * + * Note: you can await + * {@link workbox-strategies.StrategyHandler~doneWaiting} + * to know when all added promises have settled. + * + * @param {Promise} promise A promise to add to the extend lifetime promises + * of the event that triggered the request. + */ + waitUntil(promise) { + this._extendLifetimePromises.push(promise); + return promise; + } + /** + * Returns a promise that resolves once all promises passed to + * {@link workbox-strategies.StrategyHandler~waitUntil} + * have settled. + * + * Note: any work done after `doneWaiting()` settles should be manually + * passed to an event's `waitUntil()` method (not this handler's + * `waitUntil()` method), otherwise the service worker thread my be killed + * prior to your work completing. + */ + async doneWaiting() { + let promise; + while ((promise = this._extendLifetimePromises.shift())) { + await promise; } - /** - * Returns a promise that resolves once all promises passed to - * {@link workbox-strategies.StrategyHandler~waitUntil} - * have settled. - * - * Note: any work done after `doneWaiting()` settles should be manually - * passed to an event's `waitUntil()` method (not this handler's - * `waitUntil()` method), otherwise the service worker thread my be killed - * prior to your work completing. - */ - async doneWaiting() { - let promise; - while (promise = this._extendLifetimePromises.shift()) { - await promise; - } - } - /** - * Stops running the strategy and immediately resolves any pending - * `waitUntil()` promises. - */ - destroy() { - this._handlerDeferred.resolve(null); - } - /** - * This method will call cacheWillUpdate on the available plugins (or use - * status === 200) to determine if the Response is safe and valid to cache. - * - * @param {Request} options.request - * @param {Response} options.response - * @return {Promise} - * - * @private - */ - async _ensureResponseSafeToCache(response) { - let responseToCache = response; - let pluginsUsed = false; - for (const callback of this.iterateCallbacks('cacheWillUpdate')) { - responseToCache = (await callback({ + } + /** + * Stops running the strategy and immediately resolves any pending + * `waitUntil()` promises. + */ + destroy() { + this._handlerDeferred.resolve(null); + } + /** + * This method will call cacheWillUpdate on the available plugins (or use + * status === 200) to determine if the Response is safe and valid to cache. + * + * @param {Request} options.request + * @param {Response} options.response + * @return {Promise} + * + * @private + */ + async _ensureResponseSafeToCache(response) { + let responseToCache = response; + let pluginsUsed = false; + for (const callback of this.iterateCallbacks("cacheWillUpdate")) { + responseToCache = + (await callback({ request: this.request, response: responseToCache, event: this.event })) || undefined; - pluginsUsed = true; - if (!responseToCache) { - break; - } + pluginsUsed = true; + if (!responseToCache) { + break; } - if (!pluginsUsed) { - if (responseToCache && responseToCache.status !== 200) { - responseToCache = undefined; - } - { - if (responseToCache) { - if (responseToCache.status !== 200) { - if (responseToCache.status === 0) { - logger.warn(`The response for '${this.request.url}' ` + `is an opaque response. The caching strategy that you're ` + `using will not cache opaque responses by default.`); - } else { - logger.debug(`The response for '${this.request.url}' ` + `returned a status code of '${response.status}' and won't ` + `be cached as a result.`); - } + } + if (!pluginsUsed) { + if (responseToCache && responseToCache.status !== 200) { + responseToCache = undefined; + } + { + if (responseToCache) { + if (responseToCache.status !== 200) { + if (responseToCache.status === 0) { + logger.warn( + `The response for '${this.request.url}' ` + + `is an opaque response. The caching strategy that you're ` + + `using will not cache opaque responses by default.` + ); + } else { + logger.debug( + `The response for '${this.request.url}' ` + + `returned a status code of '${response.status}' and won't ` + + `be cached as a result.` + ); } } } } - return responseToCache; } + return responseToCache; } + } - /* + /* Copyright 2020 Google LLC Use of this source code is governed by an MIT-style license that can be found in the LICENSE file or at https://opensource.org/licenses/MIT. */ + /** + * An abstract base class that all other strategy classes must extend from: + * + * @memberof workbox-strategies + */ + class Strategy { /** - * An abstract base class that all other strategy classes must extend from: + * Creates a new instance of the strategy and sets all documented option + * properties as public instance properties. * - * @memberof workbox-strategies + * Note: if a custom strategy class extends the base Strategy class and does + * not need more than these properties, it does not need to define its own + * constructor. + * + * @param {Object} [options] + * @param {string} [options.cacheName] Cache name to store and retrieve + * requests. Defaults to the cache names provided by + * {@link workbox-core.cacheNames}. + * @param {Array} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins} + * to use in conjunction with this caching strategy. + * @param {Object} [options.fetchOptions] Values passed along to the + * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) + * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796) + * `fetch()` requests made by this strategy. + * @param {Object} [options.matchOptions] The + * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions} + * for any `cache.match()` or `cache.put()` calls made by this strategy. */ - class Strategy { + constructor(options = {}) { /** - * Creates a new instance of the strategy and sets all documented option - * properties as public instance properties. - * - * Note: if a custom strategy class extends the base Strategy class and does - * not need more than these properties, it does not need to define its own - * constructor. - * - * @param {Object} [options] - * @param {string} [options.cacheName] Cache name to store and retrieve + * Cache name to store and retrieve * requests. Defaults to the cache names provided by * {@link workbox-core.cacheNames}. - * @param {Array} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins} - * to use in conjunction with this caching strategy. - * @param {Object} [options.fetchOptions] Values passed along to the - * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) - * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796) - * `fetch()` requests made by this strategy. - * @param {Object} [options.matchOptions] The + * + * @type {string} + */ + this.cacheName = cacheNames.getRuntimeName(options.cacheName); + /** + * The list + * [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins} + * used by this strategy. + * + * @type {Array} + */ + this.plugins = options.plugins || []; + /** + * Values passed along to the + * [`init`]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters} + * of all fetch() requests made by this strategy. + * + * @type {Object} + */ + this.fetchOptions = options.fetchOptions; + /** + * The * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions} * for any `cache.match()` or `cache.put()` calls made by this strategy. + * + * @type {Object} */ - constructor(options = {}) { - /** - * Cache name to store and retrieve - * requests. Defaults to the cache names provided by - * {@link workbox-core.cacheNames}. - * - * @type {string} - */ - this.cacheName = cacheNames.getRuntimeName(options.cacheName); - /** - * The list - * [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins} - * used by this strategy. - * - * @type {Array} - */ - this.plugins = options.plugins || []; - /** - * Values passed along to the - * [`init`]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters} - * of all fetch() requests made by this strategy. - * - * @type {Object} - */ - this.fetchOptions = options.fetchOptions; - /** - * The - * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions} - * for any `cache.match()` or `cache.put()` calls made by this strategy. - * - * @type {Object} - */ - this.matchOptions = options.matchOptions; - } - /** - * Perform a request strategy and returns a `Promise` that will resolve with - * a `Response`, invoking all relevant plugin callbacks. - * - * When a strategy instance is registered with a Workbox - * {@link workbox-routing.Route}, this method is automatically - * called when the route matches. - * - * Alternatively, this method can be used in a standalone `FetchEvent` - * listener by passing it to `event.respondWith()`. - * - * @param {FetchEvent|Object} options A `FetchEvent` or an object with the - * properties listed below. - * @param {Request|string} options.request A request to run this strategy for. - * @param {ExtendableEvent} options.event The event associated with the - * request. - * @param {URL} [options.url] - * @param {*} [options.params] - */ - handle(options) { - const [responseDone] = this.handleAll(options); - return responseDone; - } - /** - * Similar to {@link workbox-strategies.Strategy~handle}, but - * instead of just returning a `Promise` that resolves to a `Response` it - * it will return an tuple of `[response, done]` promises, where the former - * (`response`) is equivalent to what `handle()` returns, and the latter is a - * Promise that will resolve once any promises that were added to - * `event.waitUntil()` as part of performing the strategy have completed. - * - * You can await the `done` promise to ensure any extra work performed by - * the strategy (usually caching responses) completes successfully. - * - * @param {FetchEvent|Object} options A `FetchEvent` or an object with the - * properties listed below. - * @param {Request|string} options.request A request to run this strategy for. - * @param {ExtendableEvent} options.event The event associated with the - * request. - * @param {URL} [options.url] - * @param {*} [options.params] - * @return {Array} A tuple of [response, done] - * promises that can be used to determine when the response resolves as - * well as when the handler has completed all its work. - */ - handleAll(options) { - // Allow for flexible options to be passed. - if (options instanceof FetchEvent) { - options = { - event: options, - request: options.request - }; - } - const event = options.event; - const request = typeof options.request === 'string' ? new Request(options.request) : options.request; - const params = 'params' in options ? options.params : undefined; - const handler = new StrategyHandler(this, { - event, - request, - params - }); - const responseDone = this._getResponse(handler, request, event); - const handlerDone = this._awaitComplete(responseDone, handler, request, event); - // Return an array of promises, suitable for use with Promise.all(). - return [responseDone, handlerDone]; - } - async _getResponse(handler, request, event) { - await handler.runCallbacks('handlerWillStart', { - event, - request - }); - let response = undefined; - try { - response = await this._handle(request, handler); - // The "official" Strategy subclasses all throw this error automatically, - // but in case a third-party Strategy doesn't, ensure that we have a - // consistent failure when there's no response or an error response. - if (!response || response.type === 'error') { - throw new WorkboxError('no-response', { - url: request.url - }); - } - } catch (error) { - if (error instanceof Error) { - for (const callback of handler.iterateCallbacks('handlerDidError')) { - response = await callback({ - error, - event, - request - }); - if (response) { - break; - } - } - } - if (!response) { - throw error; - } else { - logger.log(`While responding to '${getFriendlyURL(request.url)}', ` + `an ${error instanceof Error ? error.toString() : ''} error occurred. Using a fallback response provided by ` + `a handlerDidError plugin.`); - } - } - for (const callback of handler.iterateCallbacks('handlerWillRespond')) { - response = await callback({ - event, - request, - response - }); - } - return response; - } - async _awaitComplete(responseDone, handler, request, event) { - let response; - let error; - try { - response = await responseDone; - } catch (error) { - // Ignore errors, as response errors should be caught via the `response` - // promise above. The `done` promise will only throw for errors in - // promises passed to `handler.waitUntil()`. - } - try { - await handler.runCallbacks('handlerDidRespond', { - event, - request, - response - }); - await handler.doneWaiting(); - } catch (waitUntilError) { - if (waitUntilError instanceof Error) { - error = waitUntilError; - } - } - await handler.runCallbacks('handlerDidComplete', { - event, - request, - response, - error: error - }); - handler.destroy(); - if (error) { - throw error; - } - } + this.matchOptions = options.matchOptions; } /** - * Classes extending the `Strategy` based class should implement this method, - * and leverage the {@link workbox-strategies.StrategyHandler} - * arg to perform all fetching and cache logic, which will ensure all relevant - * cache, cache options, fetch options and plugins are used (per the current - * strategy instance). + * Perform a request strategy and returns a `Promise` that will resolve with + * a `Response`, invoking all relevant plugin callbacks. * - * @name _handle - * @instance - * @abstract - * @function - * @param {Request} request - * @param {workbox-strategies.StrategyHandler} handler - * @return {Promise} + * When a strategy instance is registered with a Workbox + * {@link workbox-routing.Route}, this method is automatically + * called when the route matches. * - * @memberof workbox-strategies.Strategy + * Alternatively, this method can be used in a standalone `FetchEvent` + * listener by passing it to `event.respondWith()`. + * + * @param {FetchEvent|Object} options A `FetchEvent` or an object with the + * properties listed below. + * @param {Request|string} options.request A request to run this strategy for. + * @param {ExtendableEvent} options.event The event associated with the + * request. + * @param {URL} [options.url] + * @param {*} [options.params] */ - - /* - Copyright 2020 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ + handle(options) { + const [responseDone] = this.handleAll(options); + return responseDone; + } /** - * A {@link workbox-strategies.Strategy} implementation - * specifically designed to work with - * {@link workbox-precaching.PrecacheController} - * to both cache and fetch precached assets. + * Similar to {@link workbox-strategies.Strategy~handle}, but + * instead of just returning a `Promise` that resolves to a `Response` it + * it will return an tuple of `[response, done]` promises, where the former + * (`response`) is equivalent to what `handle()` returns, and the latter is a + * Promise that will resolve once any promises that were added to + * `event.waitUntil()` as part of performing the strategy have completed. * - * Note: an instance of this class is created automatically when creating a - * `PrecacheController`; it's generally not necessary to create this yourself. + * You can await the `done` promise to ensure any extra work performed by + * the strategy (usually caching responses) completes successfully. * - * @extends workbox-strategies.Strategy - * @memberof workbox-precaching + * @param {FetchEvent|Object} options A `FetchEvent` or an object with the + * properties listed below. + * @param {Request|string} options.request A request to run this strategy for. + * @param {ExtendableEvent} options.event The event associated with the + * request. + * @param {URL} [options.url] + * @param {*} [options.params] + * @return {Array} A tuple of [response, done] + * promises that can be used to determine when the response resolves as + * well as when the handler has completed all its work. */ - class PrecacheStrategy extends Strategy { - /** - * - * @param {Object} [options] - * @param {string} [options.cacheName] Cache name to store and retrieve - * requests. Defaults to the cache names provided by - * {@link workbox-core.cacheNames}. - * @param {Array} [options.plugins] {@link https://developers.google.com/web/tools/workbox/guides/using-plugins|Plugins} - * to use in conjunction with this caching strategy. - * @param {Object} [options.fetchOptions] Values passed along to the - * {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters|init} - * of all fetch() requests made by this strategy. - * @param {Object} [options.matchOptions] The - * {@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions|CacheQueryOptions} - * for any `cache.match()` or `cache.put()` calls made by this strategy. - * @param {boolean} [options.fallbackToNetwork=true] Whether to attempt to - * get the response from the network if there's a precache miss. - */ - constructor(options = {}) { - options.cacheName = cacheNames.getPrecacheName(options.cacheName); - super(options); - this._fallbackToNetwork = options.fallbackToNetwork === false ? false : true; - // Redirected responses cannot be used to satisfy a navigation request, so - // any redirected response must be "copied" rather than cloned, so the new - // response doesn't contain the `redirected` flag. See: - // https://bugs.chromium.org/p/chromium/issues/detail?id=669363&desc=2#c1 - this.plugins.push(PrecacheStrategy.copyRedirectedCacheableResponsesPlugin); + handleAll(options) { + // Allow for flexible options to be passed. + if (options instanceof FetchEvent) { + options = { + event: options, + request: options.request + }; } - /** - * @private - * @param {Request|string} request A request to run this strategy for. - * @param {workbox-strategies.StrategyHandler} handler The event that - * triggered the request. - * @return {Promise} - */ - async _handle(request, handler) { - const response = await handler.cacheMatch(request); - if (response) { - return response; - } - // If this is an `install` event for an entry that isn't already cached, - // then populate the cache. - if (handler.event && handler.event.type === 'install') { - return await this._handleInstall(request, handler); - } - // Getting here means something went wrong. An entry that should have been - // precached wasn't found in the cache. - return await this._handleFetch(request, handler); - } - async _handleFetch(request, handler) { - let response; - const params = handler.params || {}; - // Fall back to the network if we're configured to do so. - if (this._fallbackToNetwork) { - { - logger.warn(`The precached response for ` + `${getFriendlyURL(request.url)} in ${this.cacheName} was not ` + `found. Falling back to the network.`); - } - const integrityInManifest = params.integrity; - const integrityInRequest = request.integrity; - const noIntegrityConflict = !integrityInRequest || integrityInRequest === integrityInManifest; - // Do not add integrity if the original request is no-cors - // See https://github.com/GoogleChrome/workbox/issues/3096 - response = await handler.fetch(new Request(request, { - integrity: request.mode !== 'no-cors' ? integrityInRequest || integrityInManifest : undefined - })); - // It's only "safe" to repair the cache if we're using SRI to guarantee - // that the response matches the precache manifest's expectations, - // and there's either a) no integrity property in the incoming request - // or b) there is an integrity, and it matches the precache manifest. - // See https://github.com/GoogleChrome/workbox/issues/2858 - // Also if the original request users no-cors we don't use integrity. - // See https://github.com/GoogleChrome/workbox/issues/3096 - if (integrityInManifest && noIntegrityConflict && request.mode !== 'no-cors') { - this._useDefaultCacheabilityPluginIfNeeded(); - const wasCached = await handler.cachePut(request, response.clone()); - { - if (wasCached) { - logger.log(`A response for ${getFriendlyURL(request.url)} ` + `was used to "repair" the precache.`); - } - } - } - } else { - // This shouldn't normally happen, but there are edge cases: - // https://github.com/GoogleChrome/workbox/issues/1441 - throw new WorkboxError('missing-precache-entry', { - cacheName: this.cacheName, + const event = options.event; + const request = typeof options.request === "string" ? new Request(options.request) : options.request; + const params = "params" in options ? options.params : undefined; + const handler = new StrategyHandler(this, { + event, + request, + params + }); + const responseDone = this._getResponse(handler, request, event); + const handlerDone = this._awaitComplete(responseDone, handler, request, event); + // Return an array of promises, suitable for use with Promise.all(). + return [responseDone, handlerDone]; + } + async _getResponse(handler, request, event) { + await handler.runCallbacks("handlerWillStart", { + event, + request + }); + let response = undefined; + try { + response = await this._handle(request, handler); + // The "official" Strategy subclasses all throw this error automatically, + // but in case a third-party Strategy doesn't, ensure that we have a + // consistent failure when there's no response or an error response. + if (!response || response.type === "error") { + throw new WorkboxError("no-response", { url: request.url }); } - { - const cacheKey = params.cacheKey || (await handler.getCacheKey(request, 'read')); - // Workbox is going to handle the route. - // print the routing details to the console. - logger.groupCollapsed(`Precaching is responding to: ` + getFriendlyURL(request.url)); - logger.log(`Serving the precached url: ${getFriendlyURL(cacheKey instanceof Request ? cacheKey.url : cacheKey)}`); - logger.groupCollapsed(`View request details here.`); - logger.log(request); - logger.groupEnd(); - logger.groupCollapsed(`View response details here.`); - logger.log(response); - logger.groupEnd(); - logger.groupEnd(); - } - return response; - } - async _handleInstall(request, handler) { - this._useDefaultCacheabilityPluginIfNeeded(); - const response = await handler.fetch(request); - // Make sure we defer cachePut() until after we know the response - // should be cached; see https://github.com/GoogleChrome/workbox/issues/2737 - const wasCached = await handler.cachePut(request, response.clone()); - if (!wasCached) { - // Throwing here will lead to the `install` handler failing, which - // we want to do if *any* of the responses aren't safe to cache. - throw new WorkboxError('bad-precaching-response', { - url: request.url, - status: response.status - }); - } - return response; - } - /** - * This method is complex, as there a number of things to account for: - * - * The `plugins` array can be set at construction, and/or it might be added to - * to at any time before the strategy is used. - * - * At the time the strategy is used (i.e. during an `install` event), there - * needs to be at least one plugin that implements `cacheWillUpdate` in the - * array, other than `copyRedirectedCacheableResponsesPlugin`. - * - * - If this method is called and there are no suitable `cacheWillUpdate` - * plugins, we need to add `defaultPrecacheCacheabilityPlugin`. - * - * - If this method is called and there is exactly one `cacheWillUpdate`, then - * we don't have to do anything (this might be a previously added - * `defaultPrecacheCacheabilityPlugin`, or it might be a custom plugin). - * - * - If this method is called and there is more than one `cacheWillUpdate`, - * then we need to check if one is `defaultPrecacheCacheabilityPlugin`. If so, - * we need to remove it. (This situation is unlikely, but it could happen if - * the strategy is used multiple times, the first without a `cacheWillUpdate`, - * and then later on after manually adding a custom `cacheWillUpdate`.) - * - * See https://github.com/GoogleChrome/workbox/issues/2737 for more context. - * - * @private - */ - _useDefaultCacheabilityPluginIfNeeded() { - let defaultPluginIndex = null; - let cacheWillUpdatePluginCount = 0; - for (const [index, plugin] of this.plugins.entries()) { - // Ignore the copy redirected plugin when determining what to do. - if (plugin === PrecacheStrategy.copyRedirectedCacheableResponsesPlugin) { - continue; - } - // Save the default plugin's index, in case it needs to be removed. - if (plugin === PrecacheStrategy.defaultPrecacheCacheabilityPlugin) { - defaultPluginIndex = index; - } - if (plugin.cacheWillUpdate) { - cacheWillUpdatePluginCount++; - } - } - if (cacheWillUpdatePluginCount === 0) { - this.plugins.push(PrecacheStrategy.defaultPrecacheCacheabilityPlugin); - } else if (cacheWillUpdatePluginCount > 1 && defaultPluginIndex !== null) { - // Only remove the default plugin; multiple custom plugins are allowed. - this.plugins.splice(defaultPluginIndex, 1); - } - // Nothing needs to be done if cacheWillUpdatePluginCount is 1 - } - } - PrecacheStrategy.defaultPrecacheCacheabilityPlugin = { - async cacheWillUpdate({ - response - }) { - if (!response || response.status >= 400) { - return null; - } - return response; - } - }; - PrecacheStrategy.copyRedirectedCacheableResponsesPlugin = { - async cacheWillUpdate({ - response - }) { - return response.redirected ? await copyResponse(response) : response; - } - }; - - /* - Copyright 2019 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * Performs efficient precaching of assets. - * - * @memberof workbox-precaching - */ - class PrecacheController { - /** - * Create a new PrecacheController. - * - * @param {Object} [options] - * @param {string} [options.cacheName] The cache to use for precaching. - * @param {string} [options.plugins] Plugins to use when precaching as well - * as responding to fetch events for precached assets. - * @param {boolean} [options.fallbackToNetwork=true] Whether to attempt to - * get the response from the network if there's a precache miss. - */ - constructor({ - cacheName, - plugins = [], - fallbackToNetwork = true - } = {}) { - this._urlsToCacheKeys = new Map(); - this._urlsToCacheModes = new Map(); - this._cacheKeysToIntegrities = new Map(); - this._strategy = new PrecacheStrategy({ - cacheName: cacheNames.getPrecacheName(cacheName), - plugins: [...plugins, new PrecacheCacheKeyPlugin({ - precacheController: this - })], - fallbackToNetwork - }); - // Bind the install and activate methods to the instance. - this.install = this.install.bind(this); - this.activate = this.activate.bind(this); - } - /** - * @type {workbox-precaching.PrecacheStrategy} The strategy created by this controller and - * used to cache assets and respond to fetch events. - */ - get strategy() { - return this._strategy; - } - /** - * Adds items to the precache list, removing any duplicates and - * stores the files in the - * {@link workbox-core.cacheNames|"precache cache"} when the service - * worker installs. - * - * This method can be called multiple times. - * - * @param {Array} [entries=[]] Array of entries to precache. - */ - precache(entries) { - this.addToCacheList(entries); - if (!this._installAndActiveListenersAdded) { - self.addEventListener('install', this.install); - self.addEventListener('activate', this.activate); - this._installAndActiveListenersAdded = true; - } - } - /** - * This method will add items to the precache list, removing duplicates - * and ensuring the information is valid. - * - * @param {Array} entries - * Array of entries to precache. - */ - addToCacheList(entries) { - { - finalAssertExports.isArray(entries, { - moduleName: 'workbox-precaching', - className: 'PrecacheController', - funcName: 'addToCacheList', - paramName: 'entries' - }); - } - const urlsToWarnAbout = []; - for (const entry of entries) { - // See https://github.com/GoogleChrome/workbox/issues/2259 - if (typeof entry === 'string') { - urlsToWarnAbout.push(entry); - } else if (entry && entry.revision === undefined) { - urlsToWarnAbout.push(entry.url); - } - const { - cacheKey, - url - } = createCacheKey(entry); - const cacheMode = typeof entry !== 'string' && entry.revision ? 'reload' : 'default'; - if (this._urlsToCacheKeys.has(url) && this._urlsToCacheKeys.get(url) !== cacheKey) { - throw new WorkboxError('add-to-cache-list-conflicting-entries', { - firstEntry: this._urlsToCacheKeys.get(url), - secondEntry: cacheKey + } catch (error) { + if (error instanceof Error) { + for (const callback of handler.iterateCallbacks("handlerDidError")) { + response = await callback({ + error, + event, + request }); - } - if (typeof entry !== 'string' && entry.integrity) { - if (this._cacheKeysToIntegrities.has(cacheKey) && this._cacheKeysToIntegrities.get(cacheKey) !== entry.integrity) { - throw new WorkboxError('add-to-cache-list-conflicting-integrities', { - url - }); - } - this._cacheKeysToIntegrities.set(cacheKey, entry.integrity); - } - this._urlsToCacheKeys.set(url, cacheKey); - this._urlsToCacheModes.set(url, cacheMode); - if (urlsToWarnAbout.length > 0) { - const warningMessage = `Workbox is precaching URLs without revision ` + `info: ${urlsToWarnAbout.join(', ')}\nThis is generally NOT safe. ` + `Learn more at https://bit.ly/wb-precache`; - { - logger.warn(warningMessage); + if (response) { + break; } } } + if (!response) { + throw error; + } else { + logger.log( + `While responding to '${getFriendlyURL(request.url)}', ` + + `an ${error instanceof Error ? error.toString() : ""} error occurred. Using a fallback response provided by ` + + `a handlerDidError plugin.` + ); + } } - /** - * Precaches new and updated assets. Call this method from the service worker - * install event. - * - * Note: this method calls `event.waitUntil()` for you, so you do not need - * to call it yourself in your event handlers. - * - * @param {ExtendableEvent} event - * @return {Promise} - */ - install(event) { - // waitUntil returns Promise - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return waitUntil(event, async () => { - const installReportPlugin = new PrecacheInstallReportPlugin(); - this.strategy.plugins.push(installReportPlugin); - // Cache entries one at a time. - // See https://github.com/GoogleChrome/workbox/issues/2528 - for (const [url, cacheKey] of this._urlsToCacheKeys) { - const integrity = this._cacheKeysToIntegrities.get(cacheKey); - const cacheMode = this._urlsToCacheModes.get(url); - const request = new Request(url, { - integrity, - cache: cacheMode, - credentials: 'same-origin' - }); - await Promise.all(this.strategy.handleAll({ - params: { - cacheKey - }, - request, - event - })); - } - const { - updatedURLs, - notUpdatedURLs - } = installReportPlugin; - { - printInstallDetails(updatedURLs, notUpdatedURLs); - } - return { - updatedURLs, - notUpdatedURLs - }; + for (const callback of handler.iterateCallbacks("handlerWillRespond")) { + response = await callback({ + event, + request, + response }); } - /** - * Deletes assets that are no longer present in the current precache manifest. - * Call this method from the service worker activate event. - * - * Note: this method calls `event.waitUntil()` for you, so you do not need - * to call it yourself in your event handlers. - * - * @param {ExtendableEvent} event - * @return {Promise} - */ - activate(event) { - // waitUntil returns Promise - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return waitUntil(event, async () => { - const cache = await self.caches.open(this.strategy.cacheName); - const currentlyCachedRequests = await cache.keys(); - const expectedCacheKeys = new Set(this._urlsToCacheKeys.values()); - const deletedURLs = []; - for (const request of currentlyCachedRequests) { - if (!expectedCacheKeys.has(request.url)) { - await cache.delete(request); - deletedURLs.push(request.url); - } - } - { - printCleanupDetails(deletedURLs); - } - return { - deletedURLs - }; + return response; + } + async _awaitComplete(responseDone, handler, request, event) { + let response; + let error; + try { + response = await responseDone; + } catch (error) { + // Ignore errors, as response errors should be caught via the `response` + // promise above. The `done` promise will only throw for errors in + // promises passed to `handler.waitUntil()`. + } + try { + await handler.runCallbacks("handlerDidRespond", { + event, + request, + response }); - } - /** - * Returns a mapping of a precached URL to the corresponding cache key, taking - * into account the revision information for the URL. - * - * @return {Map} A URL to cache key mapping. - */ - getURLsToCacheKeys() { - return this._urlsToCacheKeys; - } - /** - * Returns a list of all the URLs that have been precached by the current - * service worker. - * - * @return {Array} The precached URLs. - */ - getCachedURLs() { - return [...this._urlsToCacheKeys.keys()]; - } - /** - * Returns the cache key used for storing a given URL. If that URL is - * unversioned, like `/index.html', then the cache key will be the original - * URL with a search parameter appended to it. - * - * @param {string} url A URL whose cache key you want to look up. - * @return {string} The versioned URL that corresponds to a cache key - * for the original URL, or undefined if that URL isn't precached. - */ - getCacheKeyForURL(url) { - const urlObject = new URL(url, location.href); - return this._urlsToCacheKeys.get(urlObject.href); - } - /** - * @param {string} url A cache key whose SRI you want to look up. - * @return {string} The subresource integrity associated with the cache key, - * or undefined if it's not set. - */ - getIntegrityForCacheKey(cacheKey) { - return this._cacheKeysToIntegrities.get(cacheKey); - } - /** - * This acts as a drop-in replacement for - * [`cache.match()`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/match) - * with the following differences: - * - * - It knows what the name of the precache is, and only checks in that cache. - * - It allows you to pass in an "original" URL without versioning parameters, - * and it will automatically look up the correct cache key for the currently - * active revision of that URL. - * - * E.g., `matchPrecache('index.html')` will find the correct precached - * response for the currently active service worker, even if the actual cache - * key is `'/index.html?__WB_REVISION__=1234abcd'`. - * - * @param {string|Request} request The key (without revisioning parameters) - * to look up in the precache. - * @return {Promise} - */ - async matchPrecache(request) { - const url = request instanceof Request ? request.url : request; - const cacheKey = this.getCacheKeyForURL(url); - if (cacheKey) { - const cache = await self.caches.open(this.strategy.cacheName); - return cache.match(cacheKey); + await handler.doneWaiting(); + } catch (waitUntilError) { + if (waitUntilError instanceof Error) { + error = waitUntilError; } - return undefined; } - /** - * Returns a function that looks up `url` in the precache (taking into - * account revision information), and returns the corresponding `Response`. - * - * @param {string} url The precached URL which will be used to lookup the - * `Response`. - * @return {workbox-routing~handlerCallback} - */ - createHandlerBoundToURL(url) { - const cacheKey = this.getCacheKeyForURL(url); - if (!cacheKey) { - throw new WorkboxError('non-precached-url', { - url - }); - } - return options => { - options.request = new Request(url); - options.params = Object.assign({ - cacheKey - }, options.params); - return this.strategy.handle(options); - }; + await handler.runCallbacks("handlerDidComplete", { + event, + request, + response, + error: error + }); + handler.destroy(); + if (error) { + throw error; } } + } + /** + * Classes extending the `Strategy` based class should implement this method, + * and leverage the {@link workbox-strategies.StrategyHandler} + * arg to perform all fetching and cache logic, which will ensure all relevant + * cache, cache options, fetch options and plugins are used (per the current + * strategy instance). + * + * @name _handle + * @instance + * @abstract + * @function + * @param {Request} request + * @param {workbox-strategies.StrategyHandler} handler + * @return {Promise} + * + * @memberof workbox-strategies.Strategy + */ - /* - Copyright 2019 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - let precacheController; - /** - * @return {PrecacheController} - * @private - */ - const getOrCreatePrecacheController = () => { - if (!precacheController) { - precacheController = new PrecacheController(); - } - return precacheController; - }; - - /* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * Removes any URL search parameters that should be ignored. - * - * @param {URL} urlObject The original URL. - * @param {Array} ignoreURLParametersMatching RegExps to test against - * each search parameter name. Matches mean that the search parameter should be - * ignored. - * @return {URL} The URL with any ignored search parameters removed. - * - * @private - * @memberof workbox-precaching - */ - function removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching = []) { - // Convert the iterable into an array at the start of the loop to make sure - // deletion doesn't mess up iteration. - for (const paramName of [...urlObject.searchParams.keys()]) { - if (ignoreURLParametersMatching.some(regExp => regExp.test(paramName))) { - urlObject.searchParams.delete(paramName); - } - } - return urlObject; - } - - /* - Copyright 2019 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ - /** - * Generator function that yields possible variations on the original URL to - * check, one at a time. - * - * @param {string} url - * @param {Object} options - * - * @private - * @memberof workbox-precaching - */ - function* generateURLVariations(url, { - ignoreURLParametersMatching = [/^utm_/, /^fbclid$/], - directoryIndex = 'index.html', - cleanURLs = true, - urlManipulation - } = {}) { - const urlObject = new URL(url, location.href); - urlObject.hash = ''; - yield urlObject.href; - const urlWithoutIgnoredParams = removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching); - yield urlWithoutIgnoredParams.href; - if (directoryIndex && urlWithoutIgnoredParams.pathname.endsWith('/')) { - const directoryURL = new URL(urlWithoutIgnoredParams.href); - directoryURL.pathname += directoryIndex; - yield directoryURL.href; - } - if (cleanURLs) { - const cleanURL = new URL(urlWithoutIgnoredParams.href); - cleanURL.pathname += '.html'; - yield cleanURL.href; - } - if (urlManipulation) { - const additionalURLs = urlManipulation({ - url: urlObject - }); - for (const urlToAttempt of additionalURLs) { - yield urlToAttempt.href; - } - } - } - - /* + /* Copyright 2020 Google LLC Use of this source code is governed by an MIT-style license that can be found in the LICENSE file or at https://opensource.org/licenses/MIT. */ + /** + * A {@link workbox-strategies.Strategy} implementation + * specifically designed to work with + * {@link workbox-precaching.PrecacheController} + * to both cache and fetch precached assets. + * + * Note: an instance of this class is created automatically when creating a + * `PrecacheController`; it's generally not necessary to create this yourself. + * + * @extends workbox-strategies.Strategy + * @memberof workbox-precaching + */ + class PrecacheStrategy extends Strategy { /** - * A subclass of {@link workbox-routing.Route} that takes a - * {@link workbox-precaching.PrecacheController} - * instance and uses it to match incoming requests and handle fetching - * responses from the precache. * - * @memberof workbox-precaching - * @extends workbox-routing.Route + * @param {Object} [options] + * @param {string} [options.cacheName] Cache name to store and retrieve + * requests. Defaults to the cache names provided by + * {@link workbox-core.cacheNames}. + * @param {Array} [options.plugins] {@link https://developers.google.com/web/tools/workbox/guides/using-plugins|Plugins} + * to use in conjunction with this caching strategy. + * @param {Object} [options.fetchOptions] Values passed along to the + * {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters|init} + * of all fetch() requests made by this strategy. + * @param {Object} [options.matchOptions] The + * {@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions|CacheQueryOptions} + * for any `cache.match()` or `cache.put()` calls made by this strategy. + * @param {boolean} [options.fallbackToNetwork=true] Whether to attempt to + * get the response from the network if there's a precache miss. */ - class PrecacheRoute extends Route { - /** - * @param {PrecacheController} precacheController A `PrecacheController` - * instance used to both match requests and respond to fetch events. - * @param {Object} [options] Options to control how requests are matched - * against the list of precached URLs. - * @param {string} [options.directoryIndex=index.html] The `directoryIndex` will - * check cache entries for a URLs ending with '/' to see if there is a hit when - * appending the `directoryIndex` value. - * @param {Array} [options.ignoreURLParametersMatching=[/^utm_/, /^fbclid$/]] An - * array of regex's to remove search params when looking for a cache match. - * @param {boolean} [options.cleanURLs=true] The `cleanURLs` option will - * check the cache for the URL with a `.html` added to the end of the end. - * @param {workbox-precaching~urlManipulation} [options.urlManipulation] - * This is a function that should take a URL and return an array of - * alternative URLs that should be checked for precache matches. - */ - constructor(precacheController, options) { - const match = ({ - request - }) => { - const urlsToCacheKeys = precacheController.getURLsToCacheKeys(); - for (const possibleURL of generateURLVariations(request.url, options)) { - const cacheKey = urlsToCacheKeys.get(possibleURL); - if (cacheKey) { - const integrity = precacheController.getIntegrityForCacheKey(cacheKey); - return { - cacheKey, - integrity - }; + constructor(options = {}) { + options.cacheName = cacheNames.getPrecacheName(options.cacheName); + super(options); + this._fallbackToNetwork = options.fallbackToNetwork === false ? false : true; + // Redirected responses cannot be used to satisfy a navigation request, so + // any redirected response must be "copied" rather than cloned, so the new + // response doesn't contain the `redirected` flag. See: + // https://bugs.chromium.org/p/chromium/issues/detail?id=669363&desc=2#c1 + this.plugins.push(PrecacheStrategy.copyRedirectedCacheableResponsesPlugin); + } + /** + * @private + * @param {Request|string} request A request to run this strategy for. + * @param {workbox-strategies.StrategyHandler} handler The event that + * triggered the request. + * @return {Promise} + */ + async _handle(request, handler) { + const response = await handler.cacheMatch(request); + if (response) { + return response; + } + // If this is an `install` event for an entry that isn't already cached, + // then populate the cache. + if (handler.event && handler.event.type === "install") { + return await this._handleInstall(request, handler); + } + // Getting here means something went wrong. An entry that should have been + // precached wasn't found in the cache. + return await this._handleFetch(request, handler); + } + async _handleFetch(request, handler) { + let response; + const params = handler.params || {}; + // Fall back to the network if we're configured to do so. + if (this._fallbackToNetwork) { + { + logger.warn( + `The precached response for ` + + `${getFriendlyURL(request.url)} in ${this.cacheName} was not ` + + `found. Falling back to the network.` + ); + } + const integrityInManifest = params.integrity; + const integrityInRequest = request.integrity; + const noIntegrityConflict = !integrityInRequest || integrityInRequest === integrityInManifest; + // Do not add integrity if the original request is no-cors + // See https://github.com/GoogleChrome/workbox/issues/3096 + response = await handler.fetch( + new Request(request, { + integrity: request.mode !== "no-cors" ? integrityInRequest || integrityInManifest : undefined + }) + ); + // It's only "safe" to repair the cache if we're using SRI to guarantee + // that the response matches the precache manifest's expectations, + // and there's either a) no integrity property in the incoming request + // or b) there is an integrity, and it matches the precache manifest. + // See https://github.com/GoogleChrome/workbox/issues/2858 + // Also if the original request users no-cors we don't use integrity. + // See https://github.com/GoogleChrome/workbox/issues/3096 + if (integrityInManifest && noIntegrityConflict && request.mode !== "no-cors") { + this._useDefaultCacheabilityPluginIfNeeded(); + const wasCached = await handler.cachePut(request, response.clone()); + { + if (wasCached) { + logger.log(`A response for ${getFriendlyURL(request.url)} ` + `was used to "repair" the precache.`); } } - { - logger.debug(`Precaching did not find a match for ` + getFriendlyURL(request.url)); - } - return; - }; - super(match, precacheController.strategy); + } + } else { + // This shouldn't normally happen, but there are edge cases: + // https://github.com/GoogleChrome/workbox/issues/1441 + throw new WorkboxError("missing-precache-entry", { + cacheName: this.cacheName, + url: request.url + }); } + { + const cacheKey = params.cacheKey || (await handler.getCacheKey(request, "read")); + // Workbox is going to handle the route. + // print the routing details to the console. + logger.groupCollapsed(`Precaching is responding to: ` + getFriendlyURL(request.url)); + logger.log( + `Serving the precached url: ${getFriendlyURL(cacheKey instanceof Request ? cacheKey.url : cacheKey)}` + ); + logger.groupCollapsed(`View request details here.`); + logger.log(request); + logger.groupEnd(); + logger.groupCollapsed(`View response details here.`); + logger.log(response); + logger.groupEnd(); + logger.groupEnd(); + } + return response; + } + async _handleInstall(request, handler) { + this._useDefaultCacheabilityPluginIfNeeded(); + const response = await handler.fetch(request); + // Make sure we defer cachePut() until after we know the response + // should be cached; see https://github.com/GoogleChrome/workbox/issues/2737 + const wasCached = await handler.cachePut(request, response.clone()); + if (!wasCached) { + // Throwing here will lead to the `install` handler failing, which + // we want to do if *any* of the responses aren't safe to cache. + throw new WorkboxError("bad-precaching-response", { + url: request.url, + status: response.status + }); + } + return response; } - - /* - Copyright 2019 Google LLC - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. - */ /** - * Add a `fetch` listener to the service worker that will - * respond to - * [network requests]{@link https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#Custom_responses_to_requests} - * with precached assets. + * This method is complex, as there a number of things to account for: * - * Requests for assets that aren't precached, the `FetchEvent` will not be - * responded to, allowing the event to fall through to other `fetch` event - * listeners. + * The `plugins` array can be set at construction, and/or it might be added to + * to at any time before the strategy is used. * - * @param {Object} [options] See the {@link workbox-precaching.PrecacheRoute} - * options. + * At the time the strategy is used (i.e. during an `install` event), there + * needs to be at least one plugin that implements `cacheWillUpdate` in the + * array, other than `copyRedirectedCacheableResponsesPlugin`. * - * @memberof workbox-precaching + * - If this method is called and there are no suitable `cacheWillUpdate` + * plugins, we need to add `defaultPrecacheCacheabilityPlugin`. + * + * - If this method is called and there is exactly one `cacheWillUpdate`, then + * we don't have to do anything (this might be a previously added + * `defaultPrecacheCacheabilityPlugin`, or it might be a custom plugin). + * + * - If this method is called and there is more than one `cacheWillUpdate`, + * then we need to check if one is `defaultPrecacheCacheabilityPlugin`. If so, + * we need to remove it. (This situation is unlikely, but it could happen if + * the strategy is used multiple times, the first without a `cacheWillUpdate`, + * and then later on after manually adding a custom `cacheWillUpdate`.) + * + * See https://github.com/GoogleChrome/workbox/issues/2737 for more context. + * + * @private */ - function addRoute(options) { - const precacheController = getOrCreatePrecacheController(); - const precacheRoute = new PrecacheRoute(precacheController, options); - registerRoute(precacheRoute); + _useDefaultCacheabilityPluginIfNeeded() { + let defaultPluginIndex = null; + let cacheWillUpdatePluginCount = 0; + for (const [index, plugin] of this.plugins.entries()) { + // Ignore the copy redirected plugin when determining what to do. + if (plugin === PrecacheStrategy.copyRedirectedCacheableResponsesPlugin) { + continue; + } + // Save the default plugin's index, in case it needs to be removed. + if (plugin === PrecacheStrategy.defaultPrecacheCacheabilityPlugin) { + defaultPluginIndex = index; + } + if (plugin.cacheWillUpdate) { + cacheWillUpdatePluginCount++; + } + } + if (cacheWillUpdatePluginCount === 0) { + this.plugins.push(PrecacheStrategy.defaultPrecacheCacheabilityPlugin); + } else if (cacheWillUpdatePluginCount > 1 && defaultPluginIndex !== null) { + // Only remove the default plugin; multiple custom plugins are allowed. + this.plugins.splice(defaultPluginIndex, 1); + } + // Nothing needs to be done if cacheWillUpdatePluginCount is 1 } + } + PrecacheStrategy.defaultPrecacheCacheabilityPlugin = { + async cacheWillUpdate({ response }) { + if (!response || response.status >= 400) { + return null; + } + return response; + } + }; + PrecacheStrategy.copyRedirectedCacheableResponsesPlugin = { + async cacheWillUpdate({ response }) { + return response.redirected ? await copyResponse(response) : response; + } + }; - /* + /* Copyright 2019 Google LLC Use of this source code is governed by an MIT-style license that can be found in the LICENSE file or at https://opensource.org/licenses/MIT. */ + /** + * Performs efficient precaching of assets. + * + * @memberof workbox-precaching + */ + class PrecacheController { + /** + * Create a new PrecacheController. + * + * @param {Object} [options] + * @param {string} [options.cacheName] The cache to use for precaching. + * @param {string} [options.plugins] Plugins to use when precaching as well + * as responding to fetch events for precached assets. + * @param {boolean} [options.fallbackToNetwork=true] Whether to attempt to + * get the response from the network if there's a precache miss. + */ + constructor({ cacheName, plugins = [], fallbackToNetwork = true } = {}) { + this._urlsToCacheKeys = new Map(); + this._urlsToCacheModes = new Map(); + this._cacheKeysToIntegrities = new Map(); + this._strategy = new PrecacheStrategy({ + cacheName: cacheNames.getPrecacheName(cacheName), + plugins: [ + ...plugins, + new PrecacheCacheKeyPlugin({ + precacheController: this + }) + ], + fallbackToNetwork + }); + // Bind the install and activate methods to the instance. + this.install = this.install.bind(this); + this.activate = this.activate.bind(this); + } + /** + * @type {workbox-precaching.PrecacheStrategy} The strategy created by this controller and + * used to cache assets and respond to fetch events. + */ + get strategy() { + return this._strategy; + } /** * Adds items to the precache list, removing any duplicates and * stores the files in the @@ -3140,252 +2742,694 @@ define(['exports'], (function (exports) { 'use strict'; * * This method can be called multiple times. * - * Please note: This method **will not** serve any of the cached files for you. - * It only precaches files. To respond to a network request you call - * {@link workbox-precaching.addRoute}. - * - * If you have a single array of files to precache, you can just call - * {@link workbox-precaching.precacheAndRoute}. - * * @param {Array} [entries=[]] Array of entries to precache. - * - * @memberof workbox-precaching */ - function precache(entries) { - const precacheController = getOrCreatePrecacheController(); - precacheController.precache(entries); + precache(entries) { + this.addToCacheList(entries); + if (!this._installAndActiveListenersAdded) { + self.addEventListener("install", this.install); + self.addEventListener("activate", this.activate); + this._installAndActiveListenersAdded = true; + } } + /** + * This method will add items to the precache list, removing duplicates + * and ensuring the information is valid. + * + * @param {Array} entries + * Array of entries to precache. + */ + addToCacheList(entries) { + { + finalAssertExports.isArray(entries, { + moduleName: "workbox-precaching", + className: "PrecacheController", + funcName: "addToCacheList", + paramName: "entries" + }); + } + const urlsToWarnAbout = []; + for (const entry of entries) { + // See https://github.com/GoogleChrome/workbox/issues/2259 + if (typeof entry === "string") { + urlsToWarnAbout.push(entry); + } else if (entry && entry.revision === undefined) { + urlsToWarnAbout.push(entry.url); + } + const { cacheKey, url } = createCacheKey(entry); + const cacheMode = typeof entry !== "string" && entry.revision ? "reload" : "default"; + if (this._urlsToCacheKeys.has(url) && this._urlsToCacheKeys.get(url) !== cacheKey) { + throw new WorkboxError("add-to-cache-list-conflicting-entries", { + firstEntry: this._urlsToCacheKeys.get(url), + secondEntry: cacheKey + }); + } + if (typeof entry !== "string" && entry.integrity) { + if ( + this._cacheKeysToIntegrities.has(cacheKey) && + this._cacheKeysToIntegrities.get(cacheKey) !== entry.integrity + ) { + throw new WorkboxError("add-to-cache-list-conflicting-integrities", { + url + }); + } + this._cacheKeysToIntegrities.set(cacheKey, entry.integrity); + } + this._urlsToCacheKeys.set(url, cacheKey); + this._urlsToCacheModes.set(url, cacheMode); + if (urlsToWarnAbout.length > 0) { + const warningMessage = + `Workbox is precaching URLs without revision ` + + `info: ${urlsToWarnAbout.join(", ")}\nThis is generally NOT safe. ` + + `Learn more at https://bit.ly/wb-precache`; + { + logger.warn(warningMessage); + } + } + } + } + /** + * Precaches new and updated assets. Call this method from the service worker + * install event. + * + * Note: this method calls `event.waitUntil()` for you, so you do not need + * to call it yourself in your event handlers. + * + * @param {ExtendableEvent} event + * @return {Promise} + */ + install(event) { + // waitUntil returns Promise + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return waitUntil(event, async () => { + const installReportPlugin = new PrecacheInstallReportPlugin(); + this.strategy.plugins.push(installReportPlugin); + // Cache entries one at a time. + // See https://github.com/GoogleChrome/workbox/issues/2528 + for (const [url, cacheKey] of this._urlsToCacheKeys) { + const integrity = this._cacheKeysToIntegrities.get(cacheKey); + const cacheMode = this._urlsToCacheModes.get(url); + const request = new Request(url, { + integrity, + cache: cacheMode, + credentials: "same-origin" + }); + await Promise.all( + this.strategy.handleAll({ + params: { + cacheKey + }, + request, + event + }) + ); + } + const { updatedURLs, notUpdatedURLs } = installReportPlugin; + { + printInstallDetails(updatedURLs, notUpdatedURLs); + } + return { + updatedURLs, + notUpdatedURLs + }; + }); + } + /** + * Deletes assets that are no longer present in the current precache manifest. + * Call this method from the service worker activate event. + * + * Note: this method calls `event.waitUntil()` for you, so you do not need + * to call it yourself in your event handlers. + * + * @param {ExtendableEvent} event + * @return {Promise} + */ + activate(event) { + // waitUntil returns Promise + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return waitUntil(event, async () => { + const cache = await self.caches.open(this.strategy.cacheName); + const currentlyCachedRequests = await cache.keys(); + const expectedCacheKeys = new Set(this._urlsToCacheKeys.values()); + const deletedURLs = []; + for (const request of currentlyCachedRequests) { + if (!expectedCacheKeys.has(request.url)) { + await cache.delete(request); + deletedURLs.push(request.url); + } + } + { + printCleanupDetails(deletedURLs); + } + return { + deletedURLs + }; + }); + } + /** + * Returns a mapping of a precached URL to the corresponding cache key, taking + * into account the revision information for the URL. + * + * @return {Map} A URL to cache key mapping. + */ + getURLsToCacheKeys() { + return this._urlsToCacheKeys; + } + /** + * Returns a list of all the URLs that have been precached by the current + * service worker. + * + * @return {Array} The precached URLs. + */ + getCachedURLs() { + return [...this._urlsToCacheKeys.keys()]; + } + /** + * Returns the cache key used for storing a given URL. If that URL is + * unversioned, like `/index.html', then the cache key will be the original + * URL with a search parameter appended to it. + * + * @param {string} url A URL whose cache key you want to look up. + * @return {string} The versioned URL that corresponds to a cache key + * for the original URL, or undefined if that URL isn't precached. + */ + getCacheKeyForURL(url) { + const urlObject = new URL(url, location.href); + return this._urlsToCacheKeys.get(urlObject.href); + } + /** + * @param {string} url A cache key whose SRI you want to look up. + * @return {string} The subresource integrity associated with the cache key, + * or undefined if it's not set. + */ + getIntegrityForCacheKey(cacheKey) { + return this._cacheKeysToIntegrities.get(cacheKey); + } + /** + * This acts as a drop-in replacement for + * [`cache.match()`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/match) + * with the following differences: + * + * - It knows what the name of the precache is, and only checks in that cache. + * - It allows you to pass in an "original" URL without versioning parameters, + * and it will automatically look up the correct cache key for the currently + * active revision of that URL. + * + * E.g., `matchPrecache('index.html')` will find the correct precached + * response for the currently active service worker, even if the actual cache + * key is `'/index.html?__WB_REVISION__=1234abcd'`. + * + * @param {string|Request} request The key (without revisioning parameters) + * to look up in the precache. + * @return {Promise} + */ + async matchPrecache(request) { + const url = request instanceof Request ? request.url : request; + const cacheKey = this.getCacheKeyForURL(url); + if (cacheKey) { + const cache = await self.caches.open(this.strategy.cacheName); + return cache.match(cacheKey); + } + return undefined; + } + /** + * Returns a function that looks up `url` in the precache (taking into + * account revision information), and returns the corresponding `Response`. + * + * @param {string} url The precached URL which will be used to lookup the + * `Response`. + * @return {workbox-routing~handlerCallback} + */ + createHandlerBoundToURL(url) { + const cacheKey = this.getCacheKeyForURL(url); + if (!cacheKey) { + throw new WorkboxError("non-precached-url", { + url + }); + } + return (options) => { + options.request = new Request(url); + options.params = Object.assign( + { + cacheKey + }, + options.params + ); + return this.strategy.handle(options); + }; + } + } - /* + /* Copyright 2019 Google LLC Use of this source code is governed by an MIT-style license that can be found in the LICENSE file or at https://opensource.org/licenses/MIT. */ - /** - * This method will add entries to the precache list and add a route to - * respond to fetch events. - * - * This is a convenience method that will call - * {@link workbox-precaching.precache} and - * {@link workbox-precaching.addRoute} in a single call. - * - * @param {Array} entries Array of entries to precache. - * @param {Object} [options] See the - * {@link workbox-precaching.PrecacheRoute} options. - * - * @memberof workbox-precaching - */ - function precacheAndRoute(entries, options) { - precache(entries); - addRoute(options); + let precacheController; + /** + * @return {PrecacheController} + * @private + */ + const getOrCreatePrecacheController = () => { + if (!precacheController) { + precacheController = new PrecacheController(); } + return precacheController; + }; - /* + /* Copyright 2018 Google LLC Use of this source code is governed by an MIT-style license that can be found in the LICENSE file or at https://opensource.org/licenses/MIT. */ - const SUBSTRING_TO_FIND = '-precache-'; - /** - * Cleans up incompatible precaches that were created by older versions of - * Workbox, by a service worker registered under the current scope. - * - * This is meant to be called as part of the `activate` event. - * - * This should be safe to use as long as you don't include `substringToFind` - * (defaulting to `-precache-`) in your non-precache cache names. - * - * @param {string} currentPrecacheName The cache name currently in use for - * precaching. This cache won't be deleted. - * @param {string} [substringToFind='-precache-'] Cache names which include this - * substring will be deleted (excluding `currentPrecacheName`). - * @return {Array} A list of all the cache names that were deleted. - * - * @private - * @memberof workbox-precaching - */ - const deleteOutdatedCaches = async (currentPrecacheName, substringToFind = SUBSTRING_TO_FIND) => { - const cacheNames = await self.caches.keys(); - const cacheNamesToDelete = cacheNames.filter(cacheName => { - return cacheName.includes(substringToFind) && cacheName.includes(self.registration.scope) && cacheName !== currentPrecacheName; - }); - await Promise.all(cacheNamesToDelete.map(cacheName => self.caches.delete(cacheName))); - return cacheNamesToDelete; - }; + /** + * Removes any URL search parameters that should be ignored. + * + * @param {URL} urlObject The original URL. + * @param {Array} ignoreURLParametersMatching RegExps to test against + * each search parameter name. Matches mean that the search parameter should be + * ignored. + * @return {URL} The URL with any ignored search parameters removed. + * + * @private + * @memberof workbox-precaching + */ + function removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching = []) { + // Convert the iterable into an array at the start of the loop to make sure + // deletion doesn't mess up iteration. + for (const paramName of [...urlObject.searchParams.keys()]) { + if (ignoreURLParametersMatching.some((regExp) => regExp.test(paramName))) { + urlObject.searchParams.delete(paramName); + } + } + return urlObject; + } - /* + /* Copyright 2019 Google LLC Use of this source code is governed by an MIT-style license that can be found in the LICENSE file or at https://opensource.org/licenses/MIT. */ + /** + * Generator function that yields possible variations on the original URL to + * check, one at a time. + * + * @param {string} url + * @param {Object} options + * + * @private + * @memberof workbox-precaching + */ + function* generateURLVariations( + url, + { + ignoreURLParametersMatching = [/^utm_/, /^fbclid$/], + directoryIndex = "index.html", + cleanURLs = true, + urlManipulation + } = {} + ) { + const urlObject = new URL(url, location.href); + urlObject.hash = ""; + yield urlObject.href; + const urlWithoutIgnoredParams = removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching); + yield urlWithoutIgnoredParams.href; + if (directoryIndex && urlWithoutIgnoredParams.pathname.endsWith("/")) { + const directoryURL = new URL(urlWithoutIgnoredParams.href); + directoryURL.pathname += directoryIndex; + yield directoryURL.href; + } + if (cleanURLs) { + const cleanURL = new URL(urlWithoutIgnoredParams.href); + cleanURL.pathname += ".html"; + yield cleanURL.href; + } + if (urlManipulation) { + const additionalURLs = urlManipulation({ + url: urlObject + }); + for (const urlToAttempt of additionalURLs) { + yield urlToAttempt.href; + } + } + } + + /* + Copyright 2020 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * A subclass of {@link workbox-routing.Route} that takes a + * {@link workbox-precaching.PrecacheController} + * instance and uses it to match incoming requests and handle fetching + * responses from the precache. + * + * @memberof workbox-precaching + * @extends workbox-routing.Route + */ + class PrecacheRoute extends Route { /** - * Adds an `activate` event listener which will clean up incompatible - * precaches that were created by older versions of Workbox. - * - * @memberof workbox-precaching + * @param {PrecacheController} precacheController A `PrecacheController` + * instance used to both match requests and respond to fetch events. + * @param {Object} [options] Options to control how requests are matched + * against the list of precached URLs. + * @param {string} [options.directoryIndex=index.html] The `directoryIndex` will + * check cache entries for a URLs ending with '/' to see if there is a hit when + * appending the `directoryIndex` value. + * @param {Array} [options.ignoreURLParametersMatching=[/^utm_/, /^fbclid$/]] An + * array of regex's to remove search params when looking for a cache match. + * @param {boolean} [options.cleanURLs=true] The `cleanURLs` option will + * check the cache for the URL with a `.html` added to the end of the end. + * @param {workbox-precaching~urlManipulation} [options.urlManipulation] + * This is a function that should take a URL and return an array of + * alternative URLs that should be checked for precache matches. */ - function cleanupOutdatedCaches() { - // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705 - self.addEventListener('activate', event => { - const cacheName = cacheNames.getPrecacheName(); - event.waitUntil(deleteOutdatedCaches(cacheName).then(cachesDeleted => { + constructor(precacheController, options) { + const match = ({ request }) => { + const urlsToCacheKeys = precacheController.getURLsToCacheKeys(); + for (const possibleURL of generateURLVariations(request.url, options)) { + const cacheKey = urlsToCacheKeys.get(possibleURL); + if (cacheKey) { + const integrity = precacheController.getIntegrityForCacheKey(cacheKey); + return { + cacheKey, + integrity + }; + } + } + { + logger.debug(`Precaching did not find a match for ` + getFriendlyURL(request.url)); + } + return; + }; + super(match, precacheController.strategy); + } + } + + /* + Copyright 2019 Google LLC + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * Add a `fetch` listener to the service worker that will + * respond to + * [network requests]{@link https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#Custom_responses_to_requests} + * with precached assets. + * + * Requests for assets that aren't precached, the `FetchEvent` will not be + * responded to, allowing the event to fall through to other `fetch` event + * listeners. + * + * @param {Object} [options] See the {@link workbox-precaching.PrecacheRoute} + * options. + * + * @memberof workbox-precaching + */ + function addRoute(options) { + const precacheController = getOrCreatePrecacheController(); + const precacheRoute = new PrecacheRoute(precacheController, options); + registerRoute(precacheRoute); + } + + /* + Copyright 2019 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * Adds items to the precache list, removing any duplicates and + * stores the files in the + * {@link workbox-core.cacheNames|"precache cache"} when the service + * worker installs. + * + * This method can be called multiple times. + * + * Please note: This method **will not** serve any of the cached files for you. + * It only precaches files. To respond to a network request you call + * {@link workbox-precaching.addRoute}. + * + * If you have a single array of files to precache, you can just call + * {@link workbox-precaching.precacheAndRoute}. + * + * @param {Array} [entries=[]] Array of entries to precache. + * + * @memberof workbox-precaching + */ + function precache(entries) { + const precacheController = getOrCreatePrecacheController(); + precacheController.precache(entries); + } + + /* + Copyright 2019 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * This method will add entries to the precache list and add a route to + * respond to fetch events. + * + * This is a convenience method that will call + * {@link workbox-precaching.precache} and + * {@link workbox-precaching.addRoute} in a single call. + * + * @param {Array} entries Array of entries to precache. + * @param {Object} [options] See the + * {@link workbox-precaching.PrecacheRoute} options. + * + * @memberof workbox-precaching + */ + function precacheAndRoute(entries, options) { + precache(entries); + addRoute(options); + } + + /* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + const SUBSTRING_TO_FIND = "-precache-"; + /** + * Cleans up incompatible precaches that were created by older versions of + * Workbox, by a service worker registered under the current scope. + * + * This is meant to be called as part of the `activate` event. + * + * This should be safe to use as long as you don't include `substringToFind` + * (defaulting to `-precache-`) in your non-precache cache names. + * + * @param {string} currentPrecacheName The cache name currently in use for + * precaching. This cache won't be deleted. + * @param {string} [substringToFind='-precache-'] Cache names which include this + * substring will be deleted (excluding `currentPrecacheName`). + * @return {Array} A list of all the cache names that were deleted. + * + * @private + * @memberof workbox-precaching + */ + const deleteOutdatedCaches = async (currentPrecacheName, substringToFind = SUBSTRING_TO_FIND) => { + const cacheNames = await self.caches.keys(); + const cacheNamesToDelete = cacheNames.filter((cacheName) => { + return ( + cacheName.includes(substringToFind) && + cacheName.includes(self.registration.scope) && + cacheName !== currentPrecacheName + ); + }); + await Promise.all(cacheNamesToDelete.map((cacheName) => self.caches.delete(cacheName))); + return cacheNamesToDelete; + }; + + /* + Copyright 2019 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. + */ + /** + * Adds an `activate` event listener which will clean up incompatible + * precaches that were created by older versions of Workbox. + * + * @memberof workbox-precaching + */ + function cleanupOutdatedCaches() { + // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705 + self.addEventListener("activate", (event) => { + const cacheName = cacheNames.getPrecacheName(); + event.waitUntil( + deleteOutdatedCaches(cacheName).then((cachesDeleted) => { { if (cachesDeleted.length > 0) { logger.log(`The following out-of-date precaches were cleaned up ` + `automatically:`, cachesDeleted); } } - })); - }); - } + }) + ); + }); + } - /* + /* Copyright 2018 Google LLC Use of this source code is governed by an MIT-style license that can be found in the LICENSE file or at https://opensource.org/licenses/MIT. */ + /** + * NavigationRoute makes it easy to create a + * {@link workbox-routing.Route} that matches for browser + * [navigation requests]{@link https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests}. + * + * It will only match incoming Requests whose + * {@link https://fetch.spec.whatwg.org/#concept-request-mode|mode} + * is set to `navigate`. + * + * You can optionally only apply this route to a subset of navigation requests + * by using one or both of the `denylist` and `allowlist` parameters. + * + * @memberof workbox-routing + * @extends workbox-routing.Route + */ + class NavigationRoute extends Route { /** - * NavigationRoute makes it easy to create a - * {@link workbox-routing.Route} that matches for browser - * [navigation requests]{@link https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests}. + * If both `denylist` and `allowlist` are provided, the `denylist` will + * take precedence and the request will not match this route. * - * It will only match incoming Requests whose - * {@link https://fetch.spec.whatwg.org/#concept-request-mode|mode} - * is set to `navigate`. + * The regular expressions in `allowlist` and `denylist` + * are matched against the concatenated + * [`pathname`]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/pathname} + * and [`search`]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/search} + * portions of the requested URL. * - * You can optionally only apply this route to a subset of navigation requests - * by using one or both of the `denylist` and `allowlist` parameters. + * *Note*: These RegExps may be evaluated against every destination URL during + * a navigation. Avoid using + * [complex RegExps](https://github.com/GoogleChrome/workbox/issues/3077), + * or else your users may see delays when navigating your site. * - * @memberof workbox-routing - * @extends workbox-routing.Route + * @param {workbox-routing~handlerCallback} handler A callback + * function that returns a Promise resulting in a Response. + * @param {Object} options + * @param {Array} [options.denylist] If any of these patterns match, + * the route will not handle the request (even if a allowlist RegExp matches). + * @param {Array} [options.allowlist=[/./]] If any of these patterns + * match the URL's pathname and search parameter, the route will handle the + * request (assuming the denylist doesn't match). */ - class NavigationRoute extends Route { - /** - * If both `denylist` and `allowlist` are provided, the `denylist` will - * take precedence and the request will not match this route. - * - * The regular expressions in `allowlist` and `denylist` - * are matched against the concatenated - * [`pathname`]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/pathname} - * and [`search`]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/search} - * portions of the requested URL. - * - * *Note*: These RegExps may be evaluated against every destination URL during - * a navigation. Avoid using - * [complex RegExps](https://github.com/GoogleChrome/workbox/issues/3077), - * or else your users may see delays when navigating your site. - * - * @param {workbox-routing~handlerCallback} handler A callback - * function that returns a Promise resulting in a Response. - * @param {Object} options - * @param {Array} [options.denylist] If any of these patterns match, - * the route will not handle the request (even if a allowlist RegExp matches). - * @param {Array} [options.allowlist=[/./]] If any of these patterns - * match the URL's pathname and search parameter, the route will handle the - * request (assuming the denylist doesn't match). - */ - constructor(handler, { - allowlist = [/./], - denylist = [] - } = {}) { - { - finalAssertExports.isArrayOfClass(allowlist, RegExp, { - moduleName: 'workbox-routing', - className: 'NavigationRoute', - funcName: 'constructor', - paramName: 'options.allowlist' - }); - finalAssertExports.isArrayOfClass(denylist, RegExp, { - moduleName: 'workbox-routing', - className: 'NavigationRoute', - funcName: 'constructor', - paramName: 'options.denylist' - }); - } - super(options => this._match(options), handler); - this._allowlist = allowlist; - this._denylist = denylist; + constructor(handler, { allowlist = [/./], denylist = [] } = {}) { + { + finalAssertExports.isArrayOfClass(allowlist, RegExp, { + moduleName: "workbox-routing", + className: "NavigationRoute", + funcName: "constructor", + paramName: "options.allowlist" + }); + finalAssertExports.isArrayOfClass(denylist, RegExp, { + moduleName: "workbox-routing", + className: "NavigationRoute", + funcName: "constructor", + paramName: "options.denylist" + }); } - /** - * Routes match handler. - * - * @param {Object} options - * @param {URL} options.url - * @param {Request} options.request - * @return {boolean} - * - * @private - */ - _match({ - url, - request - }) { - if (request && request.mode !== 'navigate') { - return false; - } - const pathnameAndSearch = url.pathname + url.search; - for (const regExp of this._denylist) { - if (regExp.test(pathnameAndSearch)) { - { - logger.log(`The navigation route ${pathnameAndSearch} is not ` + `being used, since the URL matches this denylist pattern: ` + `${regExp.toString()}`); - } - return false; - } - } - if (this._allowlist.some(regExp => regExp.test(pathnameAndSearch))) { - { - logger.debug(`The navigation route ${pathnameAndSearch} ` + `is being used.`); - } - return true; - } - { - logger.log(`The navigation route ${pathnameAndSearch} is not ` + `being used, since the URL being navigated to doesn't ` + `match the allowlist.`); - } + super((options) => this._match(options), handler); + this._allowlist = allowlist; + this._denylist = denylist; + } + /** + * Routes match handler. + * + * @param {Object} options + * @param {URL} options.url + * @param {Request} options.request + * @return {boolean} + * + * @private + */ + _match({ url, request }) { + if (request && request.mode !== "navigate") { return false; } + const pathnameAndSearch = url.pathname + url.search; + for (const regExp of this._denylist) { + if (regExp.test(pathnameAndSearch)) { + { + logger.log( + `The navigation route ${pathnameAndSearch} is not ` + + `being used, since the URL matches this denylist pattern: ` + + `${regExp.toString()}` + ); + } + return false; + } + } + if (this._allowlist.some((regExp) => regExp.test(pathnameAndSearch))) { + { + logger.debug(`The navigation route ${pathnameAndSearch} ` + `is being used.`); + } + return true; + } + { + logger.log( + `The navigation route ${pathnameAndSearch} is not ` + + `being used, since the URL being navigated to doesn't ` + + `match the allowlist.` + ); + } + return false; } + } - /* + /* Copyright 2019 Google LLC Use of this source code is governed by an MIT-style license that can be found in the LICENSE file or at https://opensource.org/licenses/MIT. */ - /** - * Helper function that calls - * {@link PrecacheController#createHandlerBoundToURL} on the default - * {@link PrecacheController} instance. - * - * If you are creating your own {@link PrecacheController}, then call the - * {@link PrecacheController#createHandlerBoundToURL} on that instance, - * instead of using this function. - * - * @param {string} url The precached URL which will be used to lookup the - * `Response`. - * @param {boolean} [fallbackToNetwork=true] Whether to attempt to get the - * response from the network if there's a precache miss. - * @return {workbox-routing~handlerCallback} - * - * @memberof workbox-precaching - */ - function createHandlerBoundToURL(url) { - const precacheController = getOrCreatePrecacheController(); - return precacheController.createHandlerBoundToURL(url); - } + /** + * Helper function that calls + * {@link PrecacheController#createHandlerBoundToURL} on the default + * {@link PrecacheController} instance. + * + * If you are creating your own {@link PrecacheController}, then call the + * {@link PrecacheController#createHandlerBoundToURL} on that instance, + * instead of using this function. + * + * @param {string} url The precached URL which will be used to lookup the + * `Response`. + * @param {boolean} [fallbackToNetwork=true] Whether to attempt to get the + * response from the network if there's a precache miss. + * @return {workbox-routing~handlerCallback} + * + * @memberof workbox-precaching + */ + function createHandlerBoundToURL(url) { + const precacheController = getOrCreatePrecacheController(); + return precacheController.createHandlerBoundToURL(url); + } - exports.NavigationRoute = NavigationRoute; - exports.cleanupOutdatedCaches = cleanupOutdatedCaches; - exports.clientsClaim = clientsClaim; - exports.createHandlerBoundToURL = createHandlerBoundToURL; - exports.precacheAndRoute = precacheAndRoute; - exports.registerRoute = registerRoute; - -})); + exports.NavigationRoute = NavigationRoute; + exports.cleanupOutdatedCaches = cleanupOutdatedCaches; + exports.clientsClaim = clientsClaim; + exports.createHandlerBoundToURL = createHandlerBoundToURL; + exports.precacheAndRoute = precacheAndRoute; + exports.registerRoute = registerRoute; +}); diff --git a/client/src/App/App.container.jsx b/client/src/App/App.container.jsx index 13c751c58..e4df37c61 100644 --- a/client/src/App/App.container.jsx +++ b/client/src/App/App.container.jsx @@ -1,59 +1,58 @@ -import {ApolloProvider} from "@apollo/client"; -import {SplitFactoryProvider, SplitSdk,} from '@splitsoftware/splitio-react'; -import {ConfigProvider} from "antd"; +import { ApolloProvider } from "@apollo/client"; +import { SplitFactoryProvider, SplitSdk } from "@splitsoftware/splitio-react"; +import { ConfigProvider } from "antd"; import enLocale from "antd/es/locale/en_US"; import dayjs from "../utils/day"; -import 'dayjs/locale/en'; +import "dayjs/locale/en"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component"; import client from "../utils/GraphQLClient"; import App from "./App"; import * as Sentry from "@sentry/react"; import themeProvider from "./themeProvider"; -import { Userpilot } from 'userpilot' +import { Userpilot } from "userpilot"; // Initialize Userpilot -if(import.meta.env.DEV){ - Userpilot.initialize('NX-69145f08'); +if (import.meta.env.DEV) { + Userpilot.initialize("NX-69145f08"); } dayjs.locale("en"); const config = { - core: { - authorizationKey: import.meta.env.VITE_APP_SPLIT_API, - key: "anon", - }, + core: { + authorizationKey: import.meta.env.VITE_APP_SPLIT_API, + key: "anon" + } }; export const factory = SplitSdk(config); - function AppContainer() { - const {t} = useTranslation(); + const { t } = useTranslation(); - return ( - - - - - - - - - ); + return ( + + + + + + + + + ); } export default Sentry.withProfiler(AppContainer); diff --git a/client/src/App/App.jsx b/client/src/App/App.jsx index 2f4583c67..50bde585a 100644 --- a/client/src/App/App.jsx +++ b/client/src/App/App.jsx @@ -17,245 +17,225 @@ import TechPageContainer from "../pages/tech/tech.page.container"; import { setOnline } from "../redux/application/application.actions"; import { selectOnline } from "../redux/application/application.selectors"; import { checkUserSession } from "../redux/user/user.actions"; -import { - selectBodyshop, - selectCurrentEula, - selectCurrentUser, -} from "../redux/user/user.selectors"; +import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors"; import PrivateRoute from "../components/PrivateRoute"; import "./App.styles.scss"; import handleBeta from "../utils/betaHandler"; import Eula from "../components/eula/eula.component"; import InstanceRenderMgr from "../utils/instanceRenderMgr"; -import { ProductFruits } from 'react-product-fruits'; +import { ProductFruits } from "react-product-fruits"; -const ResetPassword = lazy(() => - import("../pages/reset-password/reset-password.component") -); +const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component")); const ManagePage = lazy(() => import("../pages/manage/manage.page.container")); const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page")); const CsiPage = lazy(() => import("../pages/csi/csi.container.page")); -const MobilePaymentContainer = lazy(() => - import("../pages/mobile-payment/mobile-payment.container") -); +const MobilePaymentContainer = lazy(() => import("../pages/mobile-payment/mobile-payment.container")); const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - online: selectOnline, - bodyshop: selectBodyshop, - currentEula: selectCurrentEula, + currentUser: selectCurrentUser, + online: selectOnline, + bodyshop: selectBodyshop, + currentEula: selectCurrentEula }); const mapDispatchToProps = (dispatch) => ({ - checkUserSession: () => dispatch(checkUserSession()), - setOnline: (isOnline) => dispatch(setOnline(isOnline)), + checkUserSession: () => dispatch(checkUserSession()), + setOnline: (isOnline) => dispatch(setOnline(isOnline)) }); -export function App({ - bodyshop, - checkUserSession, - currentUser, - online, - setOnline, - currentEula, -}) { - const client = useSplitClient().client; - const [listenersAdded, setListenersAdded] = useState(false); - const { t } = useTranslation(); +export function App({ bodyshop, checkUserSession, currentUser, online, setOnline, currentEula }) { + const client = useSplitClient().client; + const [listenersAdded, setListenersAdded] = useState(false); + const { t } = useTranslation(); - useEffect(() => { - if (!navigator.onLine) { - setOnline(false); - } - - checkUserSession(); - }, [checkUserSession, setOnline]); - - //const b = Grid.useBreakpoint(); - // console.log("Breakpoints:", b); - - // Associate event listeners, memoize to prevent multiple listeners being added - useEffect(() => { - const offlineListener = (e) => { - setOnline(false); - }; - - const onlineListener = (e) => { - setOnline(true); - }; - - if (!listenersAdded) { - console.log("Added events for offline and online"); - window.addEventListener("offline", offlineListener); - window.addEventListener("online", onlineListener); - setListenersAdded(true); - } - - return () => { - window.removeEventListener("offline", offlineListener); - window.removeEventListener("online", onlineListener); - }; - }, [setOnline, listenersAdded]); - - useEffect(() => { - if (currentUser.authorized && bodyshop) { - client.setAttribute("imexshopid", bodyshop.imexshopid); - - if ( - client.getTreatment("LogRocket_Tracking") === "on" || - window.location.hostname === - InstanceRenderMgr({ - imex: "beta.imex.online", - rome: "beta.romeonline.io", - }) - ) { - console.log("LR Start"); - LogRocket.init( - InstanceRenderMgr({ - imex: "gvfvfw/bodyshopapp", - rome: "rome-online/rome-online", - promanager: "", //TODO:AIO Add in log rocket for promanager instances. - }) - ); - } - } - }, [bodyshop, client, currentUser.authorized]); - - if (currentUser.authorized === null) { - return ; + useEffect(() => { + if (!navigator.onLine) { + setOnline(false); } - handleBeta(); + checkUserSession(); + }, [checkUserSession, setOnline]); - if (!online) - return ( - { - window.location.reload(); - }} - > - {t("general.actions.refresh")} - - } - /> + //const b = Grid.useBreakpoint(); + // console.log("Breakpoints:", b); + + // Associate event listeners, memoize to prevent multiple listeners being added + useEffect(() => { + const offlineListener = (e) => { + setOnline(false); + }; + + const onlineListener = (e) => { + setOnline(true); + }; + + if (!listenersAdded) { + console.log("Added events for offline and online"); + window.addEventListener("offline", offlineListener); + window.addEventListener("online", onlineListener); + setListenersAdded(true); + } + + return () => { + window.removeEventListener("offline", offlineListener); + window.removeEventListener("online", onlineListener); + }; + }, [setOnline, listenersAdded]); + + useEffect(() => { + if (currentUser.authorized && bodyshop) { + client.setAttribute("imexshopid", bodyshop.imexshopid); + + if ( + client.getTreatment("LogRocket_Tracking") === "on" || + window.location.hostname === + InstanceRenderMgr({ + imex: "beta.imex.online", + rome: "beta.romeonline.io" + }) + ) { + console.log("LR Start"); + LogRocket.init( + InstanceRenderMgr({ + imex: "gvfvfw/bodyshopapp", + rome: "rome-online/rome-online", + promanager: "" //TODO:AIO Add in log rocket for promanager instances. + }) ); - - if (currentEula && !currentUser.eulaIsAccepted) { - return ; + } } + }, [bodyshop, client, currentUser.authorized]); - // Any route that is not assigned and matched will default to the Landing Page component + if (currentUser.authorized === null) { + return ; + } + + handleBeta(); + + if (!online) return ( - - - } - > - - - - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - > - } /> - - - - - } - > - } /> - - - } - > - } /> - - - + { + window.location.reload(); + }} + > + {t("general.actions.refresh")} + + } + /> ); + + if (currentEula && !currentUser.eulaIsAccepted) { + return ; + } + + // Any route that is not assigned and matched will default to the Landing Page component + return ( + + } + > + + + + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + > + } /> + + + + + } + > + } /> + + }> + } /> + + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(App); diff --git a/client/src/App/App.styles.scss b/client/src/App/App.styles.scss index 047cf2541..dd79912c1 100644 --- a/client/src/App/App.styles.scss +++ b/client/src/App/App.styles.scss @@ -154,7 +154,6 @@ font-style: italic; } - .ant-table-tbody > tr.ant-table-row:nth-child(2n) > td { background-color: #f4f4f4; } diff --git a/client/src/App/themeProvider.js b/client/src/App/themeProvider.js index f8947ccc0..18bcaaa1c 100644 --- a/client/src/App/themeProvider.js +++ b/client/src/App/themeProvider.js @@ -1,8 +1,8 @@ -import {defaultsDeep} from "lodash"; -import {theme} from "antd"; -import InstanceRenderMgr from '../utils/instanceRenderMgr' +import { defaultsDeep } from "lodash"; +import { theme } from "antd"; +import InstanceRenderMgr from "../utils/instanceRenderMgr"; -const {defaultAlgorithm, darkAlgorithm} = theme; +const { defaultAlgorithm, darkAlgorithm } = theme; let isDarkMode = false; @@ -13,28 +13,28 @@ let isDarkMode = false; const defaultTheme = { components: { Table: { - rowHoverBg: '#e7f3ff', - rowSelectedBg: '#e6f7ff', - headerSortHoverBg: 'transparent', + rowHoverBg: "#e7f3ff", + rowSelectedBg: "#e6f7ff", + headerSortHoverBg: "transparent" }, Menu: { - darkItemHoverBg: '#1890ff', - itemHoverBg: '#1890ff', - horizontalItemHoverBg: '#1890ff', - }, + darkItemHoverBg: "#1890ff", + itemHoverBg: "#1890ff", + horizontalItemHoverBg: "#1890ff" + } }, token: { - colorPrimary: InstanceRenderMgr({ - imex: '#1890ff', - rome: '#326ade', - promanager:"#1d69a6" + colorPrimary: InstanceRenderMgr({ + imex: "#1890ff", + rome: "#326ade", + promanager: "#1d69a6" }), colorInfo: InstanceRenderMgr({ - imex: '#1890ff', - rome: '#326ade', - promanager:"#1d69a6" - }), - }, + imex: "#1890ff", + rome: "#326ade", + promanager: "#1d69a6" + }) + } }; /** @@ -42,16 +42,16 @@ const defaultTheme = { * @type {{components: {Menu: {itemHoverBg: string, darkItemHoverBg: string, horizontalItemHoverBg: string}}, token: {colorPrimary: string}}} */ const devTheme = { - components: { - Menu: { - darkItemHoverBg: '#a51d1d', - itemHoverBg: '#a51d1d', - horizontalItemHoverBg: '#a51d1d', - } - }, - token: { - colorPrimary: '#a51d1d' + components: { + Menu: { + darkItemHoverBg: "#a51d1d", + itemHoverBg: "#a51d1d", + horizontalItemHoverBg: "#a51d1d" } + }, + token: { + colorPrimary: "#a51d1d" + } }; /** @@ -60,11 +60,10 @@ const devTheme = { */ const prodTheme = {}; -const currentTheme = import.meta.env.DEV ? devTheme - : prodTheme; +const currentTheme = import.meta.env.DEV ? devTheme : prodTheme; const finaltheme = { - algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm, - ...defaultsDeep(currentTheme, defaultTheme) -} + algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm, + ...defaultsDeep(currentTheme, defaultTheme) +}; export default finaltheme; diff --git a/client/src/assets/promanager/ios/Contents.json b/client/src/assets/promanager/ios/Contents.json index bd04914ae..0767e12cf 100644 --- a/client/src/assets/promanager/ios/Contents.json +++ b/client/src/assets/promanager/ios/Contents.json @@ -131,4 +131,4 @@ "author": "iconkitchen", "version": 1 } -} \ No newline at end of file +} diff --git a/client/src/components/PrivateRoute.jsx b/client/src/components/PrivateRoute.jsx index ebb7ece93..9286ae2d6 100644 --- a/client/src/components/PrivateRoute.jsx +++ b/client/src/components/PrivateRoute.jsx @@ -1,17 +1,17 @@ -import React, {useEffect} from "react"; -import {Outlet, useLocation, useNavigate} from "react-router-dom"; +import React, { useEffect } from "react"; +import { Outlet, useLocation, useNavigate } from "react-router-dom"; -function PrivateRoute({component: Component, isAuthorized, ...rest}) { - const location = useLocation(); - const navigate = useNavigate(); +function PrivateRoute({ component: Component, isAuthorized, ...rest }) { + const location = useLocation(); + const navigate = useNavigate(); - useEffect(() => { - if (!isAuthorized) { - navigate(`/signin?redirect=${location.pathname}`); - } - }, [isAuthorized, navigate, location]); + useEffect(() => { + if (!isAuthorized) { + navigate(`/signin?redirect=${location.pathname}`); + } + }, [isAuthorized, navigate, location]); - return ; + return ; } export default PrivateRoute; diff --git a/client/src/components/_test/test.page.jsx b/client/src/components/_test/test.page.jsx index df1d9b41b..10af3b10b 100644 --- a/client/src/components/_test/test.page.jsx +++ b/client/src/components/_test/test.page.jsx @@ -1,31 +1,30 @@ -import {Button} from "antd"; +import { Button } from "antd"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {setModalContext} from "../../redux/modals/modals.actions"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { setModalContext } from "../../redux/modals/modals.actions"; const mapStateToProps = createStructuredSelector({}); const mapDispatchToProps = (dispatch) => ({ - setRefundPaymentContext: (context) => - dispatch(setModalContext({context: context, modal: "refund_payment"})), + setRefundPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "refund_payment" })) }); -function Test({setRefundPaymentContext, refundPaymentModal}) { - console.log("refundPaymentModal", refundPaymentModal); - return ( -
- -
- ); +function Test({ setRefundPaymentContext, refundPaymentModal }) { + console.log("refundPaymentModal", refundPaymentModal); + return ( +
+ +
+ ); } export default connect(mapStateToProps, mapDispatchToProps)(Test); diff --git a/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx b/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx index bb3201fcc..f501b3dbb 100644 --- a/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx +++ b/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx @@ -1,15 +1,16 @@ -import {Card, Checkbox, Input, Space, Table} from "antd";import queryString from "query-string"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect } from "react-redux"; -import {Link} from "react-router-dom"; +import { Card, Checkbox, Input, Space, Table } from "antd"; +import queryString from "query-string"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; import { createStructuredSelector } from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {DateFormatter} from "../../utils/DateFormatter"; +import { DateFormatter } from "../../utils/DateFormatter"; import { pageLimit } from "../../utils/config"; -import {alphaSort, dateSort} from "../../utils/sorters"; +import { alphaSort, dateSort } from "../../utils/sorters"; import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component"; import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component"; import PayableExportButton from "../payable-export-button/payable-export-button.component"; @@ -17,216 +18,179 @@ import BillMarkSelectedExported from "../payable-mark-selected-exported/payable- import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(AccountingPayablesTableComponent); +export default connect(mapStateToProps, mapDispatchToProps)(AccountingPayablesTableComponent); -export function AccountingPayablesTableComponent({ - bodyshop, - loading, - bills, - refetch, - }) { - const {t} = useTranslation(); - const [selectedBills, setSelectedBills] = useState([]); - const [transInProgress, setTransInProgress] = useState(false); - const [state, setState] = useState({ - sortedInfo: {}, - search: "", - }); +export function AccountingPayablesTableComponent({ bodyshop, loading, bills, refetch }) { + const { t } = useTranslation(); + const [selectedBills, setSelectedBills] = useState([]); + const [transInProgress, setTransInProgress] = useState(false); + const [state, setState] = useState({ + sortedInfo: {}, + search: "" + }); - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - const columns = [ - { - title: t("bills.fields.vendorname"), - dataIndex: "vendorname", - key: "vendorname", - sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name), - sortOrder: - state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order, - render: (text, record) => ( - - {record.vendor.name} - - ), - }, - { - title: t("bills.fields.invoice_number"), - dataIndex: "invoice_number", - key: "invoice_number", - sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), - sortOrder: - state.sortedInfo.columnKey === "invoice_number" && - state.sortedInfo.order, - render: (text, record) => ( - - {record.invoice_number} - - ), - }, - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - render: (text, record) => ( - {record.job.ro_number} - ), - }, - { - title: t("bills.fields.date"), - dataIndex: "date", - key: "date", - - sorter: (a, b) => dateSort(a.date, b.date), - sortOrder: - state.sortedInfo.columnKey === "date" && state.sortedInfo.order, - render: (text, record) => {record.date}, - }, - { - title: t("bills.fields.total"), - dataIndex: "total", - key: "total", - - sorter: (a, b) => a.total - b.total, - sortOrder: - state.sortedInfo.columnKey === "total" && state.sortedInfo.order, - render: (text, record) => ( - {record.total} - ), - }, - { - title: t("bills.fields.is_credit_memo"), - dataIndex: "is_credit_memo", - key: "is_credit_memo", - sorter: (a, b) => a.is_credit_memo - b.is_credit_memo, - sortOrder: - state.sortedInfo.columnKey === "is_credit_memo" && - state.sortedInfo.order, - render: (text, record) => ( - - ), - }, - { - title: t("exportlogs.labels.attempts"), - dataIndex: "attempts", - key: "attempts", - - render: (text, record) => ( - - ), - }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", - - - render: (text, record) => ( - - ), - }, - ]; - - const handleSearch = (e) => { - setState({...state, search: e.target.value}); - logImEXEvent("accounting_payables_table_search"); - }; - - const dataSource = state.search - ? bills.filter( - (v) => - (v.vendor.name || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (v.invoice_number || "") - .toLowerCase() - .includes(state.search.toLowerCase()) - ) - : bills; - - return ( - - - - {bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && ( - - )} - - - } + const columns = [ + { + title: t("bills.fields.vendorname"), + dataIndex: "vendorname", + key: "vendorname", + sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name), + sortOrder: state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order, + render: (text, record) => ( + - - setSelectedBills(selectedRows.map((i) => i.id)), - onSelect: (record, selected, selectedRows, nativeEvent) => { - setSelectedBills(selectedRows.map((i) => i.id)); - }, - getCheckboxProps: (record) => ({ - disabled: record.exported, - }), - selectedRowKeys: selectedBills, - type: "checkbox", - }} - /> - - ); + {record.vendor.name} + + ) + }, + { + title: t("bills.fields.invoice_number"), + dataIndex: "invoice_number", + key: "invoice_number", + sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), + sortOrder: state.sortedInfo.columnKey === "invoice_number" && state.sortedInfo.order, + render: (text, record) => ( + + {record.invoice_number} + + ) + }, + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => {record.job.ro_number} + }, + { + title: t("bills.fields.date"), + dataIndex: "date", + key: "date", + + sorter: (a, b) => dateSort(a.date, b.date), + sortOrder: state.sortedInfo.columnKey === "date" && state.sortedInfo.order, + render: (text, record) => {record.date} + }, + { + title: t("bills.fields.total"), + dataIndex: "total", + key: "total", + + sorter: (a, b) => a.total - b.total, + sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order, + render: (text, record) => {record.total} + }, + { + title: t("bills.fields.is_credit_memo"), + dataIndex: "is_credit_memo", + key: "is_credit_memo", + sorter: (a, b) => a.is_credit_memo - b.is_credit_memo, + sortOrder: state.sortedInfo.columnKey === "is_credit_memo" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: t("exportlogs.labels.attempts"), + dataIndex: "attempts", + key: "attempts", + + render: (text, record) => + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + + render: (text, record) => ( + + ) + } + ]; + + const handleSearch = (e) => { + setState({ ...state, search: e.target.value }); + logImEXEvent("accounting_payables_table_search"); + }; + + const dataSource = state.search + ? bills.filter( + (v) => + (v.vendor.name || "").toLowerCase().includes(state.search.toLowerCase()) || + (v.invoice_number || "").toLowerCase().includes(state.search.toLowerCase()) + ) + : bills; + + return ( + + + + {bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && } + + + } + > +
setSelectedBills(selectedRows.map((i) => i.id)), + onSelect: (record, selected, selectedRows, nativeEvent) => { + setSelectedBills(selectedRows.map((i) => i.id)); + }, + getCheckboxProps: (record) => ({ + disabled: record.exported + }), + selectedRowKeys: selectedBills, + type: "checkbox" + }} + /> + + ); } diff --git a/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx b/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx index 9f1c37651..f6040fab2 100644 --- a/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx +++ b/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx @@ -1,242 +1,198 @@ -import {Card, Input, Space, Table} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { Card, Input, Space, Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {DateFormatter, DateTimeFormatter} from "../../utils/DateFormatter"; -import {pageLimit } from "../../utils/config"; -import {alphaSort, dateSort} from "../../utils/sorters"; +import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter"; +import { pageLimit } from "../../utils/config"; +import { alphaSort, dateSort } from "../../utils/sorters"; import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component"; -import OwnerNameDisplay, { - OwnerNameDisplayFunction, -} from "../owner-name-display/owner-name-display.component"; +import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import PaymentExportButton from "../payment-export-button/payment-export-button.component"; import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component"; import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component"; import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(AccountingPayablesTableComponent); +export default connect(mapStateToProps, mapDispatchToProps)(AccountingPayablesTableComponent); -export function AccountingPayablesTableComponent({ - bodyshop, - loading, - payments, - refetch, - }) { - const {t} = useTranslation(); - const [selectedPayments, setSelectedPayments] = useState([]); - const [transInProgress, setTransInProgress] = useState(false); - const [state, setState] = useState({ - sortedInfo: {}, - search: "", - }); +export function AccountingPayablesTableComponent({ bodyshop, loading, payments, refetch }) { + const { t } = useTranslation(); + const [selectedPayments, setSelectedPayments] = useState([]); + const [transInProgress, setTransInProgress] = useState(false); + const [state, setState] = useState({ + sortedInfo: {}, + search: "" + }); - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - render: (text, record) => ( - {record.job.ro_number} - ), - }, - { - title: t("payments.fields.date"), - dataIndex: "date", - key: "date", - sorter: (a, b) => dateSort(a.date, b.date), - sortOrder: - state.sortedInfo.columnKey === "date" && state.sortedInfo.order, - render: (text, record) => {record.date}, - }, + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => {record.job.ro_number} + }, + { + title: t("payments.fields.date"), + dataIndex: "date", + key: "date", + sorter: (a, b) => dateSort(a.date, b.date), + sortOrder: state.sortedInfo.columnKey === "date" && state.sortedInfo.order, + render: (text, record) => {record.date} + }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - ellipsis: true, - sorter: (a, b) => - alphaSort( - OwnerNameDisplayFunction(a.job), - OwnerNameDisplayFunction(b.job) - ), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, - render: (text, record) => { - return record.job.owner ? ( - - - - ) : ( - - + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a.job), OwnerNameDisplayFunction(b.job)), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + render: (text, record) => { + return record.job.owner ? ( + + + + ) : ( + + - ); - }, - }, - { - title: t("payments.fields.amount"), - dataIndex: "amount", - key: "amount", - sorter: (a, b) => a.amount - b.amount, - sortOrder: - state.sortedInfo.columnKey === "amount" && state.sortedInfo.order,render: (text, record) => ( - {record.amount} - ), - }, - { - title: t("payments.fields.memo"), - dataIndex: "memo", - key: "memo", - }, - { - title: t("payments.fields.transactionid"), - dataIndex: "transactionid", - key: "transactionid", - }, - { - title: t("payments.fields.created_at"), - dataIndex: "created_at", - key: "created_at",sorter: (a, b) => dateSort(a.created_at, b.created_at), - sortOrder: - state.sortedInfo.columnKey === "created_at" && state.sortedInfo.order, - render: (text, record) => ( - {record.created_at} - ), - }, - //{ - // title: t("payments.fields.exportedat"), - // dataIndex: "exportedat", - // key: "exportedat", - // render: (text, record) => ( - // {record.exportedat} - // ), - //}, - { - title: t("exportlogs.labels.attempts"), - dataIndex: "attempts", - key: "attempts", + ); + } + }, + { + title: t("payments.fields.amount"), + dataIndex: "amount", + key: "amount", + sorter: (a, b) => a.amount - b.amount, + sortOrder: state.sortedInfo.columnKey === "amount" && state.sortedInfo.order, + render: (text, record) => {record.amount} + }, + { + title: t("payments.fields.memo"), + dataIndex: "memo", + key: "memo" + }, + { + title: t("payments.fields.transactionid"), + dataIndex: "transactionid", + key: "transactionid" + }, + { + title: t("payments.fields.created_at"), + dataIndex: "created_at", + key: "created_at", + sorter: (a, b) => dateSort(a.created_at, b.created_at), + sortOrder: state.sortedInfo.columnKey === "created_at" && state.sortedInfo.order, + render: (text, record) => {record.created_at} + }, + //{ + // title: t("payments.fields.exportedat"), + // dataIndex: "exportedat", + // key: "exportedat", + // render: (text, record) => ( + // {record.exportedat} + // ), + //}, + { + title: t("exportlogs.labels.attempts"), + dataIndex: "attempts", + key: "attempts", - render: (text, record) => ( - - ), - }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", + render: (text, record) => + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => ( + + ) + } + ]; - render: (text, record) => ( - - ), - }, - ]; + const handleSearch = (e) => { + setState({ ...state, search: e.target.value }); + logImEXEvent("account_payments_table_search"); + }; - const handleSearch = (e) => { - setState({...state, search: e.target.value}); - logImEXEvent("account_payments_table_search"); - }; + const dataSource = state.search + ? payments.filter( + (v) => + (v.paymentnum || "").toLowerCase().includes(state.search.toLowerCase()) || + ((v.job && v.job.ro_number) || "").toLowerCase().includes(state.search.toLowerCase()) || + ((v.job && v.job.ownr_fn) || "").toLowerCase().includes(state.search.toLowerCase()) || + ((v.job && v.job.ownr_ln) || "").toLowerCase().includes(state.search.toLowerCase()) || + ((v.job && v.job.ownr_co_nm) || "").toLowerCase().includes(state.search.toLowerCase()) + ) + : payments; - const dataSource = state.search - ? payments.filter( - (v) => - (v.paymentnum || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - ((v.job && v.job.ro_number) || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - ((v.job && v.job.ownr_fn) || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - ((v.job && v.job.ownr_ln) || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - ((v.job && v.job.ownr_co_nm) || "") - .toLowerCase() - .includes(state.search.toLowerCase()) - ) - : payments; - - return ( - - - - {bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && ( - - )} - - - } - > -
- setSelectedPayments(selectedRows.map((i) => i.id)), - onSelect: (record, selected, selectedRows, nativeEvent) => { - setSelectedPayments(selectedRows.map((i) => i.id)); - }, - getCheckboxProps: (record) => ({ - disabled: record.exported, - }), - selectedRowKeys: selectedPayments, - type: "checkbox", - }} - /> - - ); + return ( + + + + {bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && } + + + } + > +
setSelectedPayments(selectedRows.map((i) => i.id)), + onSelect: (record, selected, selectedRows, nativeEvent) => { + setSelectedPayments(selectedRows.map((i) => i.id)); + }, + getCheckboxProps: (record) => ({ + disabled: record.exported + }), + selectedRowKeys: selectedPayments, + type: "checkbox" + }} + /> + + ); } diff --git a/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx b/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx index 1fe3cbf4d..83b139827 100644 --- a/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx +++ b/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx @@ -1,258 +1,212 @@ -import {Button, Card, Input, Space, Table} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {Link} from "react-router-dom"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { Button, Card, Input, Space, Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {alphaSort, dateSort, statusSort } from "../../utils/sorters"; +import { alphaSort, dateSort, statusSort } from "../../utils/sorters"; import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component"; import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {DateFormatter} from "../../utils/DateFormatter"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { DateFormatter } from "../../utils/DateFormatter"; import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component"; -import OwnerNameDisplay, { - OwnerNameDisplayFunction, -} from "../owner-name-display/owner-name-display.component"; +import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(AccountingReceivablesTableComponent); +export default connect(mapStateToProps, mapDispatchToProps)(AccountingReceivablesTableComponent); -export function AccountingReceivablesTableComponent({ - bodyshop, - loading, - jobs, - refetch, - }) { - const {t} = useTranslation(); - const [selectedJobs, setSelectedJobs] = useState([]); - const [transInProgress, setTransInProgress] = useState(false); +export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, refetch }) { + const { t } = useTranslation(); + const [selectedJobs, setSelectedJobs] = useState([]); + const [transInProgress, setTransInProgress] = useState(false); - const [state, setState] = useState({ - sortedInfo: {}, - search: "", - }); + const [state, setState] = useState({ + sortedInfo: {}, + search: "" + }); - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - render: (text, record) => ( - {record.ro_number} - ), - }, + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => {record.ro_number} + }, - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", - sorter: (a, b) => statusSort(a, b, bodyshop.md_ro_statuses.statuses), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.date_invoiced"), - dataIndex: "date_invoiced", - key: "date_invoiced", - sorter: (a, b) => dateSort(a.date_invoiced, b.date_invoiced), - sortOrder: - state.sortedInfo.columnKey === "date_invoiced" && - state.sortedInfo.order, - render: (text, record) => ( - {record.date_invoiced} - ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - sorter: (a, b) => - alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, - render: (text, record) => { - return record.owner ? ( - - - - ) : ( - - + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => statusSort(a, b, bodyshop.md_ro_statuses.statuses), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order + }, + { + title: t("jobs.fields.date_invoiced"), + dataIndex: "date_invoiced", + key: "date_invoiced", + sorter: (a, b) => dateSort(a.date_invoiced, b.date_invoiced), + sortOrder: state.sortedInfo.columnKey === "date_invoiced" && state.sortedInfo.order, + render: (text, record) => {record.date_invoiced} + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + render: (text, record) => { + return record.owner ? ( + + + + ) : ( + + - ); - }, - }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - ellipsis: true, - sorter: (a, b) => + ); + } + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + sorter: (a, b) => alphaSort( - `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${ - a.v_model_desc || "" - }`, + `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`, `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` ), - sortOrder: - state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,render: (text, record) => { - return record.vehicleid ? ( - - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - - ) : ( - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - ); - }, - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - ellipsis: true, - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.clm_total"), - dataIndex: "clm_total", - key: "clm_total", - sorter: (a, b) => a.clm_total - b.clm_total, - sortOrder: - state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, - render: (text, record) => { - return {record.clm_total}; - }, - }, - { - title: t("exportlogs.labels.attempts"), - dataIndex: "attempts", - key: "attempts", - render: (text, record) => ( - - ), - }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", + sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, + render: (text, record) => { + return record.vehicleid ? ( + + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + + ) : ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + ); + } + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + ellipsis: true, + sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), + sortOrder: state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order + }, + { + title: t("jobs.fields.clm_total"), + dataIndex: "clm_total", + key: "clm_total", + sorter: (a, b) => a.clm_total - b.clm_total, + sortOrder: state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, + render: (text, record) => { + return {record.clm_total}; + } + }, + { + title: t("exportlogs.labels.attempts"), + dataIndex: "attempts", + key: "attempts", + render: (text, record) => + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", - render: (text, record) => ( - - - - - - - ), - }, - ]; + render: (text, record) => ( + + + + + + + ) + } + ]; - const handleSearch = (e) => { - setState({...state, search: e.target.value}); - logImEXEvent("accounting_receivables_search"); - }; + const handleSearch = (e) => { + setState({ ...state, search: e.target.value }); + logImEXEvent("accounting_receivables_search"); + }; - const dataSource = state.search - ? jobs.filter( - (v) => - (v.ro_number || "") - .toString() - .toLowerCase() - .includes(state.search.toLowerCase()) || - (v.ownr_fn || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (v.ownr_ln || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (v.ownr_co_nm || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (v.v_model_desc || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (v.v_make_desc || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (v.clm_no || "").toLowerCase().includes(state.search.toLowerCase()) - ) - : jobs; + const dataSource = state.search + ? jobs.filter( + (v) => + (v.ro_number || "").toString().toLowerCase().includes(state.search.toLowerCase()) || + (v.ownr_fn || "").toLowerCase().includes(state.search.toLowerCase()) || + (v.ownr_ln || "").toLowerCase().includes(state.search.toLowerCase()) || + (v.ownr_co_nm || "").toLowerCase().includes(state.search.toLowerCase()) || + (v.v_model_desc || "").toLowerCase().includes(state.search.toLowerCase()) || + (v.v_make_desc || "").toLowerCase().includes(state.search.toLowerCase()) || + (v.clm_no || "").toLowerCase().includes(state.search.toLowerCase()) + ) + : jobs; - return ( - - {!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && ( - - )} - {bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && ( - - )} - - - } - > -
- setSelectedJobs(selectedRows.map((i) => i.id)), - onSelect: (record, selected, selectedRows, nativeEvent) => { - setSelectedJobs(selectedRows.map((i) => i.id)); - }, - getCheckboxProps: (record) => ({ - disabled: record.exported, - }), - selectedRowKeys: selectedJobs, - type: "checkbox", - }} + return ( + + {!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && ( + - - ); + )} + {bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && } + + + } + > +
setSelectedJobs(selectedRows.map((i) => i.id)), + onSelect: (record, selected, selectedRows, nativeEvent) => { + setSelectedJobs(selectedRows.map((i) => i.id)); + }, + getCheckboxProps: (record) => ({ + disabled: record.exported + }), + selectedRowKeys: selectedJobs, + type: "checkbox" + }} + /> + + ); } diff --git a/client/src/components/alert/alert.component.jsx b/client/src/components/alert/alert.component.jsx index 5af5fce31..f02edd614 100644 --- a/client/src/components/alert/alert.component.jsx +++ b/client/src/components/alert/alert.component.jsx @@ -1,6 +1,6 @@ -import {Alert} from "antd"; +import { Alert } from "antd"; import React from "react"; export default function AlertComponent(props) { - return ; + return ; } diff --git a/client/src/components/alert/alert.component.test.js b/client/src/components/alert/alert.component.test.js index 32114c305..b7c14b48a 100644 --- a/client/src/components/alert/alert.component.test.js +++ b/client/src/components/alert/alert.component.test.js @@ -1,19 +1,19 @@ -import {shallow} from "enzyme"; +import { shallow } from "enzyme"; import React from "react"; import Alert from "./alert.component"; describe("Alert component", () => { - let wrapper; - beforeEach(() => { - const mockProps = { - type: "error", - message: "Test error message.", - }; + let wrapper; + beforeEach(() => { + const mockProps = { + type: "error", + message: "Test error message." + }; - wrapper = shallow(); - }); + wrapper = shallow(); + }); - it("should render Alert component", () => { - expect(wrapper).toMatchSnapshot(); - }); + it("should render Alert component", () => { + expect(wrapper).toMatchSnapshot(); + }); }); diff --git a/client/src/components/allocations-assignment/allocations-assignment.component.jsx b/client/src/components/allocations-assignment/allocations-assignment.component.jsx index 1d1804341..929d9eb75 100644 --- a/client/src/components/allocations-assignment/allocations-assignment.component.jsx +++ b/client/src/components/allocations-assignment/allocations-assignment.component.jsx @@ -1,72 +1,67 @@ -import {Button, InputNumber, Popover, Select} from "antd"; +import { Button, InputNumber, Popover, Select } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop + bodyshop: selectBodyshop }); export function AllocationsAssignmentComponent({ - bodyshop, - handleAssignment, - assignment, - setAssignment, - visibilityState, - maxHours - }) { - const {t} = useTranslation(); + bodyshop, + handleAssignment, + assignment, + setAssignment, + visibilityState, + maxHours +}) { + const { t } = useTranslation(); - const onChange = e => { - setAssignment({...assignment, employeeid: e}); - }; + const onChange = (e) => { + setAssignment({ ...assignment, employeeid: e }); + }; - const [visibility, setVisibility] = visibilityState; + const [visibility, setVisibility] = visibilityState; - const popContent = ( -
- - setAssignment({...assignment, hours: e})} - /> + const popContent = ( +
+ + setAssignment({ ...assignment, hours: e })} + /> - - -
- ); + + +
+ ); - return ( - - - - ); + return ( + + + + ); } export default connect(mapStateToProps, null)(AllocationsAssignmentComponent); diff --git a/client/src/components/allocations-assignment/allocations-assignment.component.test.js b/client/src/components/allocations-assignment/allocations-assignment.component.test.js index 8aad7efe6..fe9c3628b 100644 --- a/client/src/components/allocations-assignment/allocations-assignment.component.test.js +++ b/client/src/components/allocations-assignment/allocations-assignment.component.test.js @@ -1,35 +1,35 @@ -import {mount} from "enzyme"; +import { mount } from "enzyme"; import React from "react"; -import {MockBodyshop} from "../../utils/TestingHelpers"; -import {AllocationsAssignmentComponent} from "./allocations-assignment.component"; +import { MockBodyshop } from "../../utils/TestingHelpers"; +import { AllocationsAssignmentComponent } from "./allocations-assignment.component"; describe("AllocationsAssignmentComponent component", () => { - let wrapper; + let wrapper; - beforeEach(() => { - const mockProps = { - bodyshop: MockBodyshop, - handleAssignment: jest.fn(), - assignment: {}, - setAssignment: jest.fn(), - visibilityState: [false, jest.fn()], - maxHours: 4, - }; + beforeEach(() => { + const mockProps = { + bodyshop: MockBodyshop, + handleAssignment: jest.fn(), + assignment: {}, + setAssignment: jest.fn(), + visibilityState: [false, jest.fn()], + maxHours: 4 + }; - wrapper = mount(); - }); + wrapper = mount(); + }); - it("should render AllocationsAssignmentComponent component", () => { - expect(wrapper).toMatchSnapshot(); - }); + it("should render AllocationsAssignmentComponent component", () => { + expect(wrapper).toMatchSnapshot(); + }); - it("should render a list of employees", () => { - const empList = wrapper.find("#employeeSelector"); - expect(empList.children()).to.have.lengthOf(2); - }); + it("should render a list of employees", () => { + const empList = wrapper.find("#employeeSelector"); + expect(empList.children()).to.have.lengthOf(2); + }); - it("should create an allocation on save", () => { - wrapper.find("Button").simulate("click"); - expect(wrapper).toMatchSnapshot(); - }); + it("should create an allocation on save", () => { + wrapper.find("Button").simulate("click"); + expect(wrapper).toMatchSnapshot(); + }); }); diff --git a/client/src/components/allocations-assignment/allocations-assignment.container.jsx b/client/src/components/allocations-assignment/allocations-assignment.container.jsx index 5153066cb..43d4b306c 100644 --- a/client/src/components/allocations-assignment/allocations-assignment.container.jsx +++ b/client/src/components/allocations-assignment/allocations-assignment.container.jsx @@ -1,47 +1,43 @@ -import React, {useState} from "react"; +import React, { useState } from "react"; import AllocationsAssignmentComponent from "./allocations-assignment.component"; -import {useMutation} from "@apollo/client"; -import {INSERT_ALLOCATION} from "../../graphql/allocations.queries"; -import {useTranslation} from "react-i18next"; -import {notification} from "antd"; +import { useMutation } from "@apollo/client"; +import { INSERT_ALLOCATION } from "../../graphql/allocations.queries"; +import { useTranslation } from "react-i18next"; +import { notification } from "antd"; -export default function AllocationsAssignmentContainer({ - jobLineId, - hours, - refetch, - }) { - const visibilityState = useState(false); - const {t} = useTranslation(); - const [assignment, setAssignment] = useState({ - joblineid: jobLineId, - hours: parseFloat(hours), - employeeid: null, - }); - const [insertAllocation] = useMutation(INSERT_ALLOCATION); +export default function AllocationsAssignmentContainer({ jobLineId, hours, refetch }) { + const visibilityState = useState(false); + const { t } = useTranslation(); + const [assignment, setAssignment] = useState({ + joblineid: jobLineId, + hours: parseFloat(hours), + employeeid: null + }); + const [insertAllocation] = useMutation(INSERT_ALLOCATION); - const handleAssignment = () => { - insertAllocation({variables: {alloc: {...assignment}}}) - .then((r) => { - notification["success"]({ - message: t("allocations.successes.save"), - }); - visibilityState[1](false); - if (refetch) refetch(); - }) - .catch((error) => { - notification["error"]({ - message: t("employees.errors.saving", {message: error.message}), - }); - }); - }; + const handleAssignment = () => { + insertAllocation({ variables: { alloc: { ...assignment } } }) + .then((r) => { + notification["success"]({ + message: t("allocations.successes.save") + }); + visibilityState[1](false); + if (refetch) refetch(); + }) + .catch((error) => { + notification["error"]({ + message: t("employees.errors.saving", { message: error.message }) + }); + }); + }; - return ( - - ); + return ( + + ); } diff --git a/client/src/components/allocations-bulk-assignment/allocations-bulk-assignment.component.jsx b/client/src/components/allocations-bulk-assignment/allocations-bulk-assignment.component.jsx index 2af0d75d0..301887fb7 100644 --- a/client/src/components/allocations-bulk-assignment/allocations-bulk-assignment.component.jsx +++ b/client/src/components/allocations-bulk-assignment/allocations-bulk-assignment.component.jsx @@ -1,68 +1,62 @@ -import {Button, Popover, Select} from "antd"; +import { Button, Popover, Select } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); export default connect( - mapStateToProps, - null + mapStateToProps, + null )(function AllocationsBulkAssignmentComponent({ - disabled, - bodyshop, - handleAssignment, - assignment, - setAssignment, - visibilityState, - }) { - const {t} = useTranslation(); + disabled, + bodyshop, + handleAssignment, + assignment, + setAssignment, + visibilityState +}) { + const { t } = useTranslation(); - const onChange = (e) => { - setAssignment({...assignment, employeeid: e}); - }; + const onChange = (e) => { + setAssignment({ ...assignment, employeeid: e }); + }; - const [visibility, setVisibility] = visibilityState; + const [visibility, setVisibility] = visibilityState; - const popContent = ( -
- + const popContent = ( +
+ - - -
- ); + + +
+ ); - return ( - - - - ); + return ( + + + + ); }); diff --git a/client/src/components/allocations-bulk-assignment/allocations-bulk-assignment.container.jsx b/client/src/components/allocations-bulk-assignment/allocations-bulk-assignment.container.jsx index aad3ea8d2..ea68dd129 100644 --- a/client/src/components/allocations-bulk-assignment/allocations-bulk-assignment.container.jsx +++ b/client/src/components/allocations-bulk-assignment/allocations-bulk-assignment.container.jsx @@ -1,47 +1,44 @@ -import React, {useState} from "react"; +import React, { useState } from "react"; import AllocationsBulkAssignment from "./allocations-bulk-assignment.component"; -import {useMutation} from "@apollo/client"; -import {INSERT_ALLOCATION} from "../../graphql/allocations.queries"; -import {useTranslation} from "react-i18next"; -import {notification} from "antd"; +import { useMutation } from "@apollo/client"; +import { INSERT_ALLOCATION } from "../../graphql/allocations.queries"; +import { useTranslation } from "react-i18next"; +import { notification } from "antd"; -export default function AllocationsBulkAssignmentContainer({ - jobLines, - refetch, - }) { - const visibilityState = useState(false); - const {t} = useTranslation(); - const [assignment, setAssignment] = useState({ - employeeid: null, +export default function AllocationsBulkAssignmentContainer({ jobLines, refetch }) { + const visibilityState = useState(false); + const { t } = useTranslation(); + const [assignment, setAssignment] = useState({ + employeeid: null + }); + const [insertAllocation] = useMutation(INSERT_ALLOCATION); + + const handleAssignment = () => { + const allocs = jobLines.reduce((acc, value) => { + acc.push({ + joblineid: value.id, + hours: parseFloat(value.mod_lb_hrs) || 0, + employeeid: assignment.employeeid + }); + return acc; + }, []); + + insertAllocation({ variables: { alloc: allocs } }).then((r) => { + notification["success"]({ + message: t("employees.successes.save") + }); + visibilityState[1](false); + if (refetch) refetch(); }); - const [insertAllocation] = useMutation(INSERT_ALLOCATION); + }; - const handleAssignment = () => { - const allocs = jobLines.reduce((acc, value) => { - acc.push({ - joblineid: value.id, - hours: parseFloat(value.mod_lb_hrs) || 0, - employeeid: assignment.employeeid, - }); - return acc; - }, []); - - insertAllocation({variables: {alloc: allocs}}).then((r) => { - notification["success"]({ - message: t("employees.successes.save"), - }); - visibilityState[1](false); - if (refetch) refetch(); - }); - }; - - return ( - 0 ? false : true} - handleAssignment={handleAssignment} - assignment={assignment} - setAssignment={setAssignment} - visibilityState={visibilityState} - /> - ); + return ( + 0 ? false : true} + handleAssignment={handleAssignment} + assignment={assignment} + setAssignment={setAssignment} + visibilityState={visibilityState} + /> + ); } diff --git a/client/src/components/allocations-employee-label/allocations-employee-label.component.jsx b/client/src/components/allocations-employee-label/allocations-employee-label.component.jsx index 9f1447348..0adc01964 100644 --- a/client/src/components/allocations-employee-label/allocations-employee-label.component.jsx +++ b/client/src/components/allocations-employee-label/allocations-employee-label.component.jsx @@ -1,20 +1,14 @@ import Icon from "@ant-design/icons"; import React from "react"; -import {MdRemoveCircleOutline} from "react-icons/md"; +import { MdRemoveCircleOutline } from "react-icons/md"; -export default function AllocationsLabelComponent({allocation, handleClick}) { - return ( -
+export default function AllocationsLabelComponent({ allocation, handleClick }) { + return ( +
- {`${allocation.employee.first_name || ""} ${ - allocation.employee.last_name || "" - } (${allocation.hours || ""})`} + {`${allocation.employee.first_name || ""} ${allocation.employee.last_name || ""} (${allocation.hours || ""})`} - -
- ); + +
+ ); } diff --git a/client/src/components/allocations-employee-label/allocations-employee-label.container.jsx b/client/src/components/allocations-employee-label/allocations-employee-label.container.jsx index e573e806b..44fe5d43e 100644 --- a/client/src/components/allocations-employee-label/allocations-employee-label.container.jsx +++ b/client/src/components/allocations-employee-label/allocations-employee-label.container.jsx @@ -1,32 +1,27 @@ import React from "react"; -import {useMutation} from "@apollo/client"; -import {DELETE_ALLOCATION} from "../../graphql/allocations.queries"; +import { useMutation } from "@apollo/client"; +import { DELETE_ALLOCATION } from "../../graphql/allocations.queries"; import AllocationsLabelComponent from "./allocations-employee-label.component"; -import {notification} from "antd"; -import {useTranslation} from "react-i18next"; +import { notification } from "antd"; +import { useTranslation } from "react-i18next"; -export default function AllocationsLabelContainer({allocation, refetch}) { - const [deleteAllocation] = useMutation(DELETE_ALLOCATION); - const {t} = useTranslation(); +export default function AllocationsLabelContainer({ allocation, refetch }) { + const [deleteAllocation] = useMutation(DELETE_ALLOCATION); + const { t } = useTranslation(); - const handleClick = (e) => { - e.preventDefault(); - deleteAllocation({variables: {id: allocation.id}}) - .then((r) => { - notification["success"]({ - message: t("allocations.successes.deleted"), - }); - if (refetch) refetch(); - }) - .catch((error) => { - notification["error"]({message: t("allocations.errors.deleting")}); - }); - }; + const handleClick = (e) => { + e.preventDefault(); + deleteAllocation({ variables: { id: allocation.id } }) + .then((r) => { + notification["success"]({ + message: t("allocations.successes.deleted") + }); + if (refetch) refetch(); + }) + .catch((error) => { + notification["error"]({ message: t("allocations.errors.deleting") }); + }); + }; - return ( - - ); + return ; } diff --git a/client/src/components/audit-trail-list/audit-trail-list.component.jsx b/client/src/components/audit-trail-list/audit-trail-list.component.jsx index 3f6decd80..9722005d6 100644 --- a/client/src/components/audit-trail-list/audit-trail-list.component.jsx +++ b/client/src/components/audit-trail-list/audit-trail-list.component.jsx @@ -1,85 +1,75 @@ -import React, {useState} from "react"; -import {Table} from "antd"; -import {alphaSort} from "../../utils/sorters"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; -import {useTranslation} from "react-i18next"; +import React, { useState } from "react"; +import { Table } from "antd"; +import { alphaSort } from "../../utils/sorters"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; +import { useTranslation } from "react-i18next"; import AuditTrailValuesComponent from "../audit-trail-values/audit-trail-values.component"; -import {pageLimit} from "../../utils/config"; +import { pageLimit } from "../../utils/config"; -export default function AuditTrailListComponent({loading, data}) { - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {}, - }); - const {t} = useTranslation(); - const columns = [ - { - title: t("audit.fields.created"), - dataIndex: " created", - key: " created", - width: "10%", - render: (text, record) => ( - {record.created} - ), - sorter: (a, b) => a.created - b.created, - sortOrder: - state.sortedInfo.columnKey === "created" && state.sortedInfo.order, - }, - { - title: t("audit.fields.operation"), - dataIndex: "operation", - key: "operation", - width: "10%", - sorter: (a, b) => alphaSort(a.operation, b.operation), - sortOrder: - state.sortedInfo.columnKey === "operation" && state.sortedInfo.order, - }, - { - title: t("audit.fields.values"), - dataIndex: " old_val", - key: " old_val", - width: "10%", - render: (text, record) => ( - - ), - }, - { - title: t("audit.fields.useremail"), - dataIndex: "useremail", - key: "useremail", - width: "10%", - sorter: (a, b) => alphaSort(a.useremail, b.useremail), - sortOrder: - state.sortedInfo.columnKey === "useremail" && state.sortedInfo.order, - }, - ]; +export default function AuditTrailListComponent({ loading, data }) { + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: {} + }); + const { t } = useTranslation(); + const columns = [ + { + title: t("audit.fields.created"), + dataIndex: " created", + key: " created", + width: "10%", + render: (text, record) => {record.created}, + sorter: (a, b) => a.created - b.created, + sortOrder: state.sortedInfo.columnKey === "created" && state.sortedInfo.order + }, + { + title: t("audit.fields.operation"), + dataIndex: "operation", + key: "operation", + width: "10%", + sorter: (a, b) => alphaSort(a.operation, b.operation), + sortOrder: state.sortedInfo.columnKey === "operation" && state.sortedInfo.order + }, + { + title: t("audit.fields.values"), + dataIndex: " old_val", + key: " old_val", + width: "10%", + render: (text, record) => + }, + { + title: t("audit.fields.useremail"), + dataIndex: "useremail", + key: "useremail", + width: "10%", + sorter: (a, b) => alphaSort(a.useremail, b.useremail), + sortOrder: state.sortedInfo.columnKey === "useremail" && state.sortedInfo.order + } + ]; - const formItemLayout = { - labelCol: { - xs: {span: 12}, - sm: {span: 5}, - }, - wrapperCol: { - xs: {span: 24}, - sm: {span: 12}, - }, - }; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const formItemLayout = { + labelCol: { + xs: { span: 12 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 12 } + } + }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - return ( -
- ); + return ( +
+ ); } diff --git a/client/src/components/audit-trail-list/audit-trail-list.container.jsx b/client/src/components/audit-trail-list/audit-trail-list.container.jsx index cd5672943..6361bc240 100644 --- a/client/src/components/audit-trail-list/audit-trail-list.container.jsx +++ b/client/src/components/audit-trail-list/audit-trail-list.container.jsx @@ -1,40 +1,34 @@ import React from "react"; import AuditTrailListComponent from "./audit-trail-list.component"; -import {useQuery} from "@apollo/client"; -import {QUERY_AUDIT_TRAIL} from "../../graphql/audit_trail.queries"; +import { useQuery } from "@apollo/client"; +import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries"; import AlertComponent from "../alert/alert.component"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import EmailAuditTrailListComponent from "./email-audit-trail-list.component"; -import {Card, Row} from "antd"; +import { Card, Row } from "antd"; -export default function AuditTrailListContainer({recordId}) { - const {loading, error, data} = useQuery(QUERY_AUDIT_TRAIL, { - variables: {id: recordId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); +export default function AuditTrailListContainer({ recordId }) { + const { loading, error, data } = useQuery(QUERY_AUDIT_TRAIL, { + variables: { id: recordId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - logImEXEvent("audittrail_view", {recordId}); - return ( -
- {error ? ( - - ) : ( - - - - - - - - - )} -
- ); + logImEXEvent("audittrail_view", { recordId }); + return ( +
+ {error ? ( + + ) : ( + + + + + + + + + )} +
+ ); } diff --git a/client/src/components/audit-trail-list/email-audit-trail-list.component.jsx b/client/src/components/audit-trail-list/email-audit-trail-list.component.jsx index 015b0d962..5f462d500 100644 --- a/client/src/components/audit-trail-list/email-audit-trail-list.component.jsx +++ b/client/src/components/audit-trail-list/email-audit-trail-list.component.jsx @@ -1,64 +1,60 @@ -import {Table} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; -import {alphaSort} from "../../utils/sorters"; -import {pageLimit} from "../../utils/config"; +import { Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; +import { alphaSort } from "../../utils/sorters"; +import { pageLimit } from "../../utils/config"; -export default function EmailAuditTrailListComponent({loading, data}) { - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {}, - }); - const {t} = useTranslation(); - const columns = [ - { - title: t("audit.fields.created"), - dataIndex: " created", - key: " created", - width: "10%", - render: (text, record) => ( - {record.created} - ), - sorter: (a, b) => a.created - b.created, - sortOrder: - state.sortedInfo.columnKey === "created" && state.sortedInfo.order, - }, +export default function EmailAuditTrailListComponent({ loading, data }) { + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: {} + }); + const { t } = useTranslation(); + const columns = [ + { + title: t("audit.fields.created"), + dataIndex: " created", + key: " created", + width: "10%", + render: (text, record) => {record.created}, + sorter: (a, b) => a.created - b.created, + sortOrder: state.sortedInfo.columnKey === "created" && state.sortedInfo.order + }, - { - title: t("audit.fields.useremail"), - dataIndex: "useremail", - key: "useremail", - width: "10%", - sorter: (a, b) => alphaSort(a.useremail, b.useremail), - sortOrder: - state.sortedInfo.columnKey === "useremail" && state.sortedInfo.order, - }, - ]; + { + title: t("audit.fields.useremail"), + dataIndex: "useremail", + key: "useremail", + width: "10%", + sorter: (a, b) => alphaSort(a.useremail, b.useremail), + sortOrder: state.sortedInfo.columnKey === "useremail" && state.sortedInfo.order + } + ]; - const formItemLayout = { - labelCol: { - xs: {span: 12}, - sm: {span: 5}, - }, - wrapperCol: { - xs: {span: 24}, - sm: {span: 12}, - }, - }; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const formItemLayout = { + labelCol: { + xs: { span: 12 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 12 } + } + }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - return ( -
- ); + return ( +
+ ); } diff --git a/client/src/components/audit-trail-values/audit-trail-values.component.jsx b/client/src/components/audit-trail-values/audit-trail-values.component.jsx index 3838a60dd..b3c531927 100644 --- a/client/src/components/audit-trail-values/audit-trail-values.component.jsx +++ b/client/src/components/audit-trail-values/audit-trail-values.component.jsx @@ -1,30 +1,30 @@ import React from "react"; -import {List} from "antd"; +import { List } from "antd"; import Icon from "@ant-design/icons"; -import {FaArrowRight} from "react-icons/fa"; +import { FaArrowRight } from "react-icons/fa"; -export default function AuditTrailValuesComponent({oldV, newV}) { - if (!oldV && !newV) return
; - - if (!oldV && newV) - return ( - - {Object.keys(newV).map((key, idx) => ( - - {key}: {JSON.stringify(newV[key])} - - ))} - - ); +export default function AuditTrailValuesComponent({ oldV, newV }) { + if (!oldV && !newV) return
; + if (!oldV && newV) return ( - - {Object.keys(oldV).map((key, idx) => ( - - {key}: {oldV[key]} - {JSON.stringify(newV[key])} - - ))} - + + {Object.keys(newV).map((key, idx) => ( + + {key}: {JSON.stringify(newV[key])} + + ))} + ); + + return ( + + {Object.keys(oldV).map((key, idx) => ( + + {key}: {oldV[key]} + {JSON.stringify(newV[key])} + + ))} + + ); } diff --git a/client/src/components/barcode-popup/barcode-popup.component.jsx b/client/src/components/barcode-popup/barcode-popup.component.jsx index 457de3b49..c6dbb4ee3 100644 --- a/client/src/components/barcode-popup/barcode-popup.component.jsx +++ b/client/src/components/barcode-popup/barcode-popup.component.jsx @@ -1,23 +1,15 @@ -import {Popover, Tag} from "antd"; +import { Popover, Tag } from "antd"; import React from "react"; import Barcode from "react-barcode"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; -export default function BarcodePopupComponent({value, children}) { - const {t} = useTranslation(); - return ( -
- - } - > - {children ? children : {t("general.labels.barcode")}} - -
- ); +export default function BarcodePopupComponent({ value, children }) { + const { t } = useTranslation(); + return ( +
+ }> + {children ? children : {t("general.labels.barcode")}} + +
+ ); } diff --git a/client/src/components/bill-cm-returns-table/bill-cm-returns-table.component.jsx b/client/src/components/bill-cm-returns-table/bill-cm-returns-table.component.jsx index 7e2e43dd2..5eb8bb4fc 100644 --- a/client/src/components/bill-cm-returns-table/bill-cm-returns-table.component.jsx +++ b/client/src/components/bill-cm-returns-table/bill-cm-returns-table.component.jsx @@ -1,136 +1,128 @@ -import {Checkbox, Form, Skeleton, Typography} from "antd"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; +import { Checkbox, Form, Skeleton, Typography } from "antd"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component"; import "./bill-cm-returns-table.styles.scss"; -export default function BillCmdReturnsTableComponent({ - form, - returnLoading, - returnData, - }) { - const {t} = useTranslation(); +export default function BillCmdReturnsTableComponent({ form, returnLoading, returnData }) { + const { t } = useTranslation(); - useEffect(() => { - if (returnData) { - form.setFieldsValue({ - outstanding_returns: returnData.parts_order_lines, - }); + useEffect(() => { + if (returnData) { + form.setFieldsValue({ + outstanding_returns: returnData.parts_order_lines + }); + } + }, [returnData, form]); + + return ( + + prev.jobid !== cur.jobid || prev.is_credit_memo !== cur.is_credit_memo || prev.vendorid !== cur.vendorid + } + noStyle + > + {() => { + const isReturn = form.getFieldValue("is_credit_memo"); + + if (!isReturn) { + return null; } - }, [returnData, form]); - return ( - - prev.jobid !== cur.jobid || - prev.is_credit_memo !== cur.is_credit_memo || - prev.vendorid !== cur.vendorid - } - noStyle - > - {() => { - const isReturn = form.getFieldValue("is_credit_memo"); + if (returnLoading) return ; - if (!isReturn) { - return null; - } + return ( + + {(fields, { add, remove, move }) => { + return ( + <> + {t("bills.labels.creditsnotreceived")} +
+ + + + + + + + + + + + {fields.map((field, index) => ( + + - if (returnLoading) return ; + + + + - return ( - - {(fields, {add, remove, move}) => { - return ( - <> - - {t("bills.labels.creditsnotreceived")} - -
{t("parts_orders.fields.line_desc")}{t("parts_orders.fields.part_type")}{t("parts_orders.fields.quantity")}{t("parts_orders.fields.act_price")}{t("parts_orders.fields.cost")}{t("parts_orders.labels.mark_as_received")}
+ + + + + + + + + + + + + + + + + + + +
- - - - - - - - - - - - {fields.map((field, index) => ( - - - - - - - - - - - ))} - -
{t("parts_orders.fields.line_desc")}{t("parts_orders.fields.part_type")}{t("parts_orders.fields.quantity")}{t("parts_orders.fields.act_price")}{t("parts_orders.fields.cost")}{t("parts_orders.labels.mark_as_received")}
- - - - - - - - - - - - - - - - - - - - - - - -
- - ); - }} - - ); +
+ + + +
+ + ); }} - - ); + + ); + }} + + ); } diff --git a/client/src/components/bill-cm-returns-table/bill-cm-returns-table.styles.scss b/client/src/components/bill-cm-returns-table/bill-cm-returns-table.styles.scss index 2b1a11fc7..743ef7ac0 100644 --- a/client/src/components/bill-cm-returns-table/bill-cm-returns-table.styles.scss +++ b/client/src/components/bill-cm-returns-table/bill-cm-returns-table.styles.scss @@ -16,4 +16,4 @@ tr:hover { background-color: #f5f5f5; } -} \ No newline at end of file +} diff --git a/client/src/components/bill-delete-button/bill-delete-button.component.jsx b/client/src/components/bill-delete-button/bill-delete-button.component.jsx index 13eb94527..55c9a9072 100644 --- a/client/src/components/bill-delete-button/bill-delete-button.component.jsx +++ b/client/src/components/bill-delete-button/bill-delete-button.component.jsx @@ -1,97 +1,88 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {Button, notification, Popconfirm} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {DELETE_BILL} from "../../graphql/bills.queries"; +import { DeleteFilled } from "@ant-design/icons"; +import { useMutation } from "@apollo/client"; +import { Button, notification, Popconfirm } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { DELETE_BILL } from "../../graphql/bills.queries"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; -import {insertAuditTrail} from "../../redux/application/application.actions"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; const mapStateToProps = createStructuredSelector({}); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({ jobid, operation, type }) => - dispatch(insertAuditTrail({ jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); export default connect(mapStateToProps, mapDispatchToProps)(BillDeleteButton); export function BillDeleteButton({ bill, jobid, callback, insertAuditTrail }) { - const [loading, setLoading] = useState(false); - const { t } = useTranslation(); - const [deleteBill] = useMutation(DELETE_BILL); + const [loading, setLoading] = useState(false); + const { t } = useTranslation(); + const [deleteBill] = useMutation(DELETE_BILL); - const handleDelete = async () => { - setLoading(true); - const result = await deleteBill({ - variables: {billId: bill.id}, - update(cache, {errors}) { - if (errors) return; - cache.modify({ - fields: { - bills(existingBills, {readField}) { - return existingBills.filter( - (billref) => bill.id !== readField("id", billref) - ); - }, - search_bills(existingBills, {readField}) { - return existingBills.filter( - (billref) => bill.id !== readField("id", billref) - ); - }, - }, - }); + const handleDelete = async () => { + setLoading(true); + const result = await deleteBill({ + variables: { billId: bill.id }, + update(cache, { errors }) { + if (errors) return; + cache.modify({ + fields: { + bills(existingBills, { readField }) { + return existingBills.filter((billref) => bill.id !== readField("id", billref)); }, + search_bills(existingBills, { readField }) { + return existingBills.filter((billref) => bill.id !== readField("id", billref)); + } + } }); + } + }); - if (!!!result.errors) { - notification["success"]({ message: t("bills.successes.deleted") }); - insertAuditTrail({ - jobid: jobid, - operation: AuditTrailMapping.billdeleted(bill.invoice_number), - type: "billdeleted", + if (!!!result.errors) { + notification["success"]({ message: t("bills.successes.deleted") }); + insertAuditTrail({ + jobid: jobid, + operation: AuditTrailMapping.billdeleted(bill.invoice_number), + type: "billdeleted" }); - if (callback && typeof callback === "function") callback(bill.id); - } else { - //Check if it's an fkey violation. - const error = JSON.stringify(result.errors); + if (callback && typeof callback === "function") callback(bill.id); + } else { + //Check if it's an fkey violation. + const error = JSON.stringify(result.errors); - if (error.toLowerCase().includes("inventory_billid_fkey")) { - notification["error"]({ - message: t("bills.errors.deleting", { - error: t("bills.errors.existinginventoryline"), - }), - }); - } else { - notification["error"]({ - message: t("bills.errors.deleting", { - error: JSON.stringify(result.errors), - }), - }); - } - } + if (error.toLowerCase().includes("inventory_billid_fkey")) { + notification["error"]({ + message: t("bills.errors.deleting", { + error: t("bills.errors.existinginventoryline") + }) + }); + } else { + notification["error"]({ + message: t("bills.errors.deleting", { + error: JSON.stringify(result.errors) + }) + }); + } + } - setLoading(false); - }; + setLoading(false); + }; - return ( - }> - - - - - ); + return ( + }> + + + + + ); } diff --git a/client/src/components/bill-detail-edit/bill-detail-edit-component.jsx b/client/src/components/bill-detail-edit/bill-detail-edit-component.jsx index bc763be77..f5ce3d3d4 100644 --- a/client/src/components/bill-detail-edit/bill-detail-edit-component.jsx +++ b/client/src/components/bill-detail-edit/bill-detail-edit-component.jsx @@ -1,17 +1,17 @@ -import {useMutation, useQuery} from "@apollo/client"; -import {Button, Divider, Form, Popconfirm, Space} from "antd"; +import { useMutation, useQuery } from "@apollo/client"; +import { Button, Divider, Form, Popconfirm, Space } from "antd"; import dayjs from "../../utils/day"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {DELETE_BILL_LINE, INSERT_NEW_BILL_LINES, UPDATE_BILL_LINE} from "../../graphql/bill-lines.queries"; -import {QUERY_BILL_BY_PK, UPDATE_BILL} from "../../graphql/bills.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { DELETE_BILL_LINE, INSERT_NEW_BILL_LINES, UPDATE_BILL_LINE } from "../../graphql/bill-lines.queries"; +import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AlertComponent from "../alert/alert.component"; import BillFormContainer from "../bill-form/bill-form.container"; @@ -22,227 +22,212 @@ import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-galler import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import BillDetailEditReturn from "./bill-detail-edit-return.component"; -import {PageHeader} from "@ant-design/pro-layout"; +import { PageHeader } from "@ant-design/pro-layout"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setPartsOrderContext: (context) => - dispatch(setModalContext({context: context, modal: "partsOrder"})), - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(BillDetailEditcontainer); +export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditcontainer); -export function BillDetailEditcontainer({setPartsOrderContext, insertAuditTrail, bodyshop,}) { - const search = queryString.parse(useLocation().search); +export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail, bodyshop }) { + const search = queryString.parse(useLocation().search); - const {t} = useTranslation(); - const [form] = Form.useForm(); - const [open, setOpen] = useState(false); - const [updateLoading, setUpdateLoading] = useState(false); - const [update_bill] = useMutation(UPDATE_BILL); - const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES); - const [updateBillLine] = useMutation(UPDATE_BILL_LINE); - const [deleteBillLine] = useMutation(DELETE_BILL_LINE); + const { t } = useTranslation(); + const [form] = Form.useForm(); + const [open, setOpen] = useState(false); + const [updateLoading, setUpdateLoading] = useState(false); + const [update_bill] = useMutation(UPDATE_BILL); + const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES); + const [updateBillLine] = useMutation(UPDATE_BILL_LINE); + const [deleteBillLine] = useMutation(DELETE_BILL_LINE); - const {loading, error, data, refetch} = useQuery(QUERY_BILL_BY_PK, { - variables: {billid: search.billid}, - skip: !!!search.billid, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, { + variables: { billid: search.billid }, + skip: !!!search.billid, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - // ... rest of the code remains the same + // ... rest of the code remains the same - const handleSave = () => { - //It's got a previously deducted bill line! - if ( - data.bills_by_pk.billlines.filter((b) => b.deductedfromlbr).length > 0 || - form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length > - 0 - ) - setOpen(true); - else { - form.submit(); - } - }; + const handleSave = () => { + //It's got a previously deducted bill line! + if ( + data.bills_by_pk.billlines.filter((b) => b.deductedfromlbr).length > 0 || + form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length > 0 + ) + setOpen(true); + else { + form.submit(); + } + }; - const handleFinish = async (values) => { - setUpdateLoading(true); - //let adjustmentsToInsert = {}; + const handleFinish = async (values) => { + setUpdateLoading(true); + //let adjustmentsToInsert = {}; - const {billlines, upload, ...bill} = values; - const updates = []; - updates.push( - update_bill({ - variables: {billId: search.billid, bill: bill}, - }) - ); - - billlines.forEach((l) => { - delete l.selected; - }); - - //Find bill lines that were deleted. - const deletedJobLines = []; - - data.bills_by_pk.billlines.forEach((a) => { - const matchingRecord = billlines.find((b) => b.id === a.id); - if (!matchingRecord) { - deletedJobLines.push(a); - } - }); - - deletedJobLines.forEach((d) => { - updates.push(deleteBillLine({variables: {id: d.id}})); - }); - - billlines.forEach((billline) => { - const {deductedfromlbr, inventories, jobline, ...il} = billline; - delete il.__typename; - - if (il.id) { - updates.push( - updateBillLine({ - variables: { - billLineId: il.id, - billLine: { - ...il, - deductedfromlbr: deductedfromlbr, - joblineid: il.joblineid === "noline" ? null : il.joblineid, - }, - }, - }) - ); - } else { - //It's a new line, have to insert it. - updates.push( - insertBillLine({ - variables: { - billLines: [ - { - ...il, - deductedfromlbr: deductedfromlbr, - billid: search.billid, - joblineid: il.joblineid === "noline" ? null : il.joblineid, - }, - ], - }, - }) - ); - } - }); - - await Promise.all(updates); - - insertAuditTrail({ - jobid: bill.jobid, - billid: search.billid, - operation: AuditTrailMapping.billupdated(bill.invoice_number), - type: "billupdated", - }); - - await refetch(); - form.setFieldsValue(transformData(data)); - form.resetFields(); - setOpen(false); - setUpdateLoading(false); - }; - - if (error) return ; - if (!search.billid) return <>; //
{t("bills.labels.noneselected")}
; - - const exported = data && data.bills_by_pk && data.bills_by_pk.exported; - - return ( - <> - {loading && } - {data && ( - <> - - - - form.submit()} - onCancel={() => setOpen(false)} - okButtonProps={{loading: updateLoading}} - title={t("bills.labels.editadjwarning")} - > - - - - - - } - /> -
- - {t("general.labels.media")} - {bodyshop.uselocalmediaserver ? ( - - ) : ( - - )} - - - )} - + const { billlines, upload, ...bill } = values; + const updates = []; + updates.push( + update_bill({ + variables: { billId: search.billid, bill: bill } + }) ); + + billlines.forEach((l) => { + delete l.selected; + }); + + //Find bill lines that were deleted. + const deletedJobLines = []; + + data.bills_by_pk.billlines.forEach((a) => { + const matchingRecord = billlines.find((b) => b.id === a.id); + if (!matchingRecord) { + deletedJobLines.push(a); + } + }); + + deletedJobLines.forEach((d) => { + updates.push(deleteBillLine({ variables: { id: d.id } })); + }); + + billlines.forEach((billline) => { + const { deductedfromlbr, inventories, jobline, ...il } = billline; + delete il.__typename; + + if (il.id) { + updates.push( + updateBillLine({ + variables: { + billLineId: il.id, + billLine: { + ...il, + deductedfromlbr: deductedfromlbr, + joblineid: il.joblineid === "noline" ? null : il.joblineid + } + } + }) + ); + } else { + //It's a new line, have to insert it. + updates.push( + insertBillLine({ + variables: { + billLines: [ + { + ...il, + deductedfromlbr: deductedfromlbr, + billid: search.billid, + joblineid: il.joblineid === "noline" ? null : il.joblineid + } + ] + } + }) + ); + } + }); + + await Promise.all(updates); + + insertAuditTrail({ + jobid: bill.jobid, + billid: search.billid, + operation: AuditTrailMapping.billupdated(bill.invoice_number), + type: "billupdated" + }); + + await refetch(); + form.setFieldsValue(transformData(data)); + form.resetFields(); + setOpen(false); + setUpdateLoading(false); + }; + + if (error) return ; + if (!search.billid) return <>; //
{t("bills.labels.noneselected")}
; + + const exported = data && data.bills_by_pk && data.bills_by_pk.exported; + + return ( + <> + {loading && } + {data && ( + <> + + + + form.submit()} + onCancel={() => setOpen(false)} + okButtonProps={{ loading: updateLoading }} + title={t("bills.labels.editadjwarning")} + > + + + + + + } + /> +
+ + {t("general.labels.media")} + {bodyshop.uselocalmediaserver ? ( + + ) : ( + + )} + + + )} + + ); } const transformData = (data) => { - return data - ? { - ...data.bills_by_pk, + return data + ? { + ...data.bills_by_pk, - billlines: data.bills_by_pk.billlines.map((i) => { - return { - ...i, - joblineid: !!i.joblineid ? i.joblineid : "noline", - applicable_taxes: { - federal: - (i.applicable_taxes && i.applicable_taxes.federal) || false, - state: (i.applicable_taxes && i.applicable_taxes.state) || false, - local: (i.applicable_taxes && i.applicable_taxes.local) || false, - }, - }; - }), - date: data.bills_by_pk ? dayjs(data.bills_by_pk.date) : null, - } - : {}; + billlines: data.bills_by_pk.billlines.map((i) => { + return { + ...i, + joblineid: !!i.joblineid ? i.joblineid : "noline", + applicable_taxes: { + federal: (i.applicable_taxes && i.applicable_taxes.federal) || false, + state: (i.applicable_taxes && i.applicable_taxes.state) || false, + local: (i.applicable_taxes && i.applicable_taxes.local) || false + } + }; + }), + date: data.bills_by_pk ? dayjs(data.bills_by_pk.date) : null + } + : {}; }; diff --git a/client/src/components/bill-detail-edit/bill-detail-edit-return.component.jsx b/client/src/components/bill-detail-edit/bill-detail-edit-return.component.jsx index 62ac07258..44833c1b5 100644 --- a/client/src/components/bill-detail-edit/bill-detail-edit-return.component.jsx +++ b/client/src/components/bill-detail-edit/bill-detail-edit-return.component.jsx @@ -1,189 +1,168 @@ -import {Button, Checkbox, Form, Modal} from "antd"; +import { Button, Checkbox, Form, Modal } from "antd"; import queryString from "query-string"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setPartsOrderContext: (context) => - dispatch(setModalContext({context: context, modal: "partsOrder"})), - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(BillDetailEditReturn); +export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditReturn); -export function BillDetailEditReturn({ - setPartsOrderContext, - insertAuditTrail, - bodyshop, - data, - disabled, - }) { - const search = queryString.parse(useLocation().search); - const history = useNavigate(); - const {t} = useTranslation(); - const [form] = Form.useForm(); - const [open, setOpen] = useState(false); +export function BillDetailEditReturn({ setPartsOrderContext, insertAuditTrail, bodyshop, data, disabled }) { + const search = queryString.parse(useLocation().search); + const history = useNavigate(); + const { t } = useTranslation(); + const [form] = Form.useForm(); + const [open, setOpen] = useState(false); - const handleFinish = ({billlines}) => { - const selectedLines = billlines.filter((l) => l.selected).map((l) => l.id); + const handleFinish = ({ billlines }) => { + const selectedLines = billlines.filter((l) => l.selected).map((l) => l.id); - setPartsOrderContext({ - actions: {}, - context: { - jobId: data.bills_by_pk.jobid, - vendorId: data.bills_by_pk.vendorid, - returnFromBill: data.bills_by_pk.id, - invoiceNumber: data.bills_by_pk.invoice_number, - linesToOrder: data.bills_by_pk.billlines - .filter((l) => selectedLines.includes(l.id)) - .map((i) => { - return { - line_desc: i.line_desc, - // db_price: i.actual_price, - act_price: i.actual_price, - cost: i.actual_cost, - quantity: i.quantity, - joblineid: i.joblineid, - oem_partno: i.jobline && i.jobline.oem_partno, - part_type: i.jobline && i.jobline.part_type, - }; - }), - isReturn: true, - }, - }); - delete search.billid; + setPartsOrderContext({ + actions: {}, + context: { + jobId: data.bills_by_pk.jobid, + vendorId: data.bills_by_pk.vendorid, + returnFromBill: data.bills_by_pk.id, + invoiceNumber: data.bills_by_pk.invoice_number, + linesToOrder: data.bills_by_pk.billlines + .filter((l) => selectedLines.includes(l.id)) + .map((i) => { + return { + line_desc: i.line_desc, + // db_price: i.actual_price, + act_price: i.actual_price, + cost: i.actual_cost, + quantity: i.quantity, + joblineid: i.joblineid, + oem_partno: i.jobline && i.jobline.oem_partno, + part_type: i.jobline && i.jobline.part_type + }; + }), + isReturn: true + } + }); + delete search.billid; - history({search: queryString.stringify(search)}); - setOpen(false); - }; - useEffect(() => { - if (open === false) form.resetFields(); - }, [open, form]); + history({ search: queryString.stringify(search) }); + setOpen(false); + }; + useEffect(() => { + if (open === false) form.resetFields(); + }, [open, form]); - return ( - <> - setOpen(false)} - destroyOnClose - title={t("bills.actions.return")} - onOk={() => form.submit()} - > -
- - {(fields, {add, remove, move}) => { - return ( - - - - - - - - - - - - {fields.map((field, index) => ( - - - - - - - - ))} - -
- { - form.setFieldsValue({ - billlines: form - .getFieldsValue() - .billlines.map((b) => ({ - ...b, - selected: e.target.checked, - })), - }); - }} - /> - {t("billlines.fields.line_desc")}{t("billlines.fields.quantity")}{t("billlines.fields.actual_price")}{t("billlines.fields.actual_cost")}
- - - - - - - - - - - - - - - - - - - -
- ); - }} -
-
-
- - - ); + return ( + <> + setOpen(false)} + destroyOnClose + title={t("bills.actions.return")} + onOk={() => form.submit()} + > +
+ + {(fields, { add, remove, move }) => { + return ( + + + + + + + + + + + + {fields.map((field, index) => ( + + + + + + + + ))} + +
+ { + form.setFieldsValue({ + billlines: form.getFieldsValue().billlines.map((b) => ({ + ...b, + selected: e.target.checked + })) + }); + }} + /> + {t("billlines.fields.line_desc")}{t("billlines.fields.quantity")}{t("billlines.fields.actual_price")}{t("billlines.fields.actual_cost")}
+ + + + + + + + + + + + + + + + + + + +
+ ); + }} +
+
+
+ + + ); } diff --git a/client/src/components/bill-detail-edit/bill-detail-edit.container.jsx b/client/src/components/bill-detail-edit/bill-detail-edit.container.jsx index ab7e9bc20..3cebb3e07 100644 --- a/client/src/components/bill-detail-edit/bill-detail-edit.container.jsx +++ b/client/src/components/bill-detail-edit/bill-detail-edit.container.jsx @@ -1,40 +1,38 @@ -import {Drawer, Grid} from "antd"; +import { Drawer, Grid } from "antd"; import queryString from "query-string"; import React from "react"; -import {useLocation, useNavigate} from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; import BillDetailEditComponent from "./bill-detail-edit-component"; export default function BillDetailEditcontainer() { - const search = queryString.parse(useLocation().search); - const history = useNavigate(); + const search = queryString.parse(useLocation().search); + const history = useNavigate(); - const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) - .filter((screen) => !!screen[1]) - .slice(-1)[0]; + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) + .filter((screen) => !!screen[1]) + .slice(-1)[0]; - const bpoints = { - xs: "100%", - sm: "100%", - md: "100%", - lg: "100%", - xl: "90%", - xxl: "90%", - }; - const drawerPercentage = selectedBreakpoint - ? bpoints[selectedBreakpoint[0]] - : "100%"; + const bpoints = { + xs: "100%", + sm: "100%", + md: "100%", + lg: "100%", + xl: "90%", + xxl: "90%" + }; + const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%"; - return ( - { - delete search.billid; - history({search: queryString.stringify(search)}); - }} - destroyOnClose - open={search.billid} - > - - - ); + return ( + { + delete search.billid; + history({ search: queryString.stringify(search) }); + }} + destroyOnClose + open={search.billid} + > + + + ); } diff --git a/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx b/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx index c93a4a0be..18d6db174 100644 --- a/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx +++ b/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx @@ -1,471 +1,426 @@ -import {useApolloClient, useMutation} from "@apollo/client"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; -import {Button, Checkbox, Form, Modal, notification, Space} from "antd"; +import { useApolloClient, useMutation } from "@apollo/client"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { Button, Checkbox, Form, Modal, notification, Space } from "antd"; import _ from "lodash"; -import React, {useEffect, useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {INSERT_NEW_BILL} from "../../graphql/bills.queries"; -import {UPDATE_INVENTORY_LINES} from "../../graphql/inventory.queries"; -import {UPDATE_JOB_LINE} from "../../graphql/jobs-lines.queries"; -import {QUERY_JOB_LBR_ADJUSTMENTS, UPDATE_JOB,} from "../../graphql/jobs.queries"; -import {MUTATION_MARK_RETURN_RECEIVED} from "../../graphql/parts-orders.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectBillEnterModal} from "../../redux/modals/modals.selectors"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import React, { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_NEW_BILL } from "../../graphql/bills.queries"; +import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries"; +import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries"; +import { QUERY_JOB_LBR_ADJUSTMENTS, UPDATE_JOB } from "../../graphql/jobs.queries"; +import { MUTATION_MARK_RETURN_RECEIVED } from "../../graphql/parts-orders.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectBillEnterModal } from "../../redux/modals/modals.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; import confirmDialog from "../../utils/asyncConfirm"; import useLocalStorage from "../../utils/useLocalStorage"; import BillFormContainer from "../bill-form/bill-form.container"; -import {CalculateBillTotal} from "../bill-form/bill-form.totals.utility"; -import {handleUpload as handleLocalUpload} from "../documents-local-upload/documents-local-upload.utility"; -import {handleUpload} from "../documents-upload/documents-upload.utility"; +import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility"; +import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility"; +import { handleUpload } from "../documents-upload/documents-upload.utility"; const mapStateToProps = createStructuredSelector({ - billEnterModal: selectBillEnterModal, - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + billEnterModal: selectBillEnterModal, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")), - insertAuditTrail: ({jobid, billid, operation, type}) => - dispatch(insertAuditTrail({jobid, billid, operation, type })), + toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")), + insertAuditTrail: ({ jobid, billid, operation, type }) => + dispatch(insertAuditTrail({ jobid, billid, operation, type })) }); const Templates = TemplateList("job_special"); -function BillEnterModalContainer({ - billEnterModal, - toggleModalVisible, - bodyshop, - currentUser, - insertAuditTrail, - }) { - const [form] = Form.useForm(); - const {t} = useTranslation(); - const [enterAgain, setEnterAgain] = useState(false); - const [insertBill] = useMutation(INSERT_NEW_BILL); - const [updateJobLines] = useMutation(UPDATE_JOB_LINE); - const [updatePartsOrderLines] = useMutation(MUTATION_MARK_RETURN_RECEIVED); - const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES); - const [loading, setLoading] = useState(false); - const client = useApolloClient(); - const [generateLabel, setGenerateLabel] = useLocalStorage( - "enter_bill_generate_label", - false - ); +function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop, currentUser, insertAuditTrail }) { + const [form] = Form.useForm(); + const { t } = useTranslation(); + const [enterAgain, setEnterAgain] = useState(false); + const [insertBill] = useMutation(INSERT_NEW_BILL); + const [updateJobLines] = useMutation(UPDATE_JOB_LINE); + const [updatePartsOrderLines] = useMutation(MUTATION_MARK_RETURN_RECEIVED); + const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES); + const [loading, setLoading] = useState(false); + const client = useApolloClient(); + const [generateLabel, setGenerateLabel] = useLocalStorage("enter_bill_generate_label", false); - const {treatments: {Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); + + const formValues = useMemo(() => { + return { + ...billEnterModal.context.bill, + //Added as a part of IO-2436 for capturing parts price changes. + billlines: billEnterModal.context?.bill?.billlines?.map((line) => ({ + ...line, + original_actual_price: line.actual_price + })), + jobid: (billEnterModal.context.job && billEnterModal.context.job.id) || null, + federal_tax_rate: (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.federal_tax_rate) || 0, + state_tax_rate: (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.state_tax_rate) || 0, + local_tax_rate: (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.local_tax_rate) || 0 + }; + }, [billEnterModal, bodyshop]); + + const handleFinish = async (values) => { + let totals = CalculateBillTotal(values); + if (totals.discrepancy.getAmount() !== 0) { + if (!(await confirmDialog(t("bills.labels.savewithdiscrepancy")))) { + return; + } + } + + setLoading(true); + const { upload, location, outstanding_returns, inventory, federal_tax_exempt, ...remainingValues } = values; + + let adjustmentsToInsert = {}; + let payrollAdjustmentsToInsert = []; + + const r1 = await insertBill({ + variables: { + bill: [ + { + ...remainingValues, + billlines: { + data: + remainingValues.billlines && + remainingValues.billlines.map((i) => { + const { + deductedfromlbr, + lbr_adjustment, + location: lineLocation, + part_type, + create_ppc, + original_actual_price, + ...restI + } = i; + + if (Enhanced_Payroll.treatment === "on") { + if ( + deductedfromlbr && + true //payroll is on + ) { + payrollAdjustmentsToInsert.push({ + id: i.joblineid, + convertedtolbr: true, + convertedtolbr_data: { + mod_lb_hrs: lbr_adjustment.mod_lb_hrs * -1, + mod_lbr_ty: lbr_adjustment.mod_lbr_ty + } + }); + } + } else { + if (deductedfromlbr) { + adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] = + (adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) - + restI.actual_price / lbr_adjustment.rate; + } + } + + return { + ...restI, + deductedfromlbr: deductedfromlbr, + lbr_adjustment, + joblineid: i.joblineid === "noline" ? null : i.joblineid, + applicable_taxes: { + federal: (i.applicable_taxes && i.applicable_taxes.federal) || false, + state: (i.applicable_taxes && i.applicable_taxes.state) || false, + local: (i.applicable_taxes && i.applicable_taxes.local) || false + } + }; + }) + } + } + ] + }, + refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"], + awaitRefetchQueries: true }); - const formValues = useMemo(() => { - return { - ...billEnterModal.context.bill, - //Added as a part of IO-2436 for capturing parts price changes. - billlines: billEnterModal.context?.bill?.billlines?.map((line) => ({ - ...line, - original_actual_price: line.actual_price, - })), - jobid: - (billEnterModal.context.job && billEnterModal.context.job.id) || null, - federal_tax_rate: - (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.federal_tax_rate) || - 0, - state_tax_rate: - (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.state_tax_rate) || - 0, - local_tax_rate: - (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.local_tax_rate) || - 0, - }; - }, [billEnterModal, bodyshop]); - - const handleFinish = async (values) => { - let totals = CalculateBillTotal(values); - if (totals.discrepancy.getAmount() !== 0) { - if (!(await confirmDialog(t("bills.labels.savewithdiscrepancy")))) { - return; + await Promise.all( + payrollAdjustmentsToInsert.map((li) => { + return updateJobLines({ + variables: { + lineId: li.id, + line: { + convertedtolbr: li.convertedtolbr, + convertedtolbr_data: li.convertedtolbr_data } - } - - setLoading(true); - const { - upload, - location, - outstanding_returns, - inventory, - federal_tax_exempt, - ...remainingValues - } = values; - - let adjustmentsToInsert = {}; - let payrollAdjustmentsToInsert = []; - - const r1 = await insertBill({ - variables: { - bill: [ - { - ...remainingValues, - billlines: { - data: - remainingValues.billlines && - remainingValues.billlines.map((i) => { - const { - deductedfromlbr, - lbr_adjustment, - location: lineLocation, - part_type, - create_ppc, - original_actual_price, - ...restI - } = i; - - if (Enhanced_Payroll.treatment === "on") { - if ( - deductedfromlbr && - true //payroll is on - ) { - payrollAdjustmentsToInsert.push({ - id: i.joblineid, - convertedtolbr: true, - convertedtolbr_data: { - mod_lb_hrs: lbr_adjustment.mod_lb_hrs * -1, - mod_lbr_ty: lbr_adjustment.mod_lbr_ty, - }, - }); - } - } else { - if (deductedfromlbr) { - adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] = - (adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) - - restI.actual_price / lbr_adjustment.rate; - } - } - - return { - ...restI, - deductedfromlbr: deductedfromlbr, - lbr_adjustment, - joblineid: i.joblineid === "noline" ? null : i.joblineid, - applicable_taxes: { - federal: - (i.applicable_taxes && i.applicable_taxes.federal) || - false, - state: - (i.applicable_taxes && i.applicable_taxes.state) || - false, - local: - (i.applicable_taxes && i.applicable_taxes.local) || - false, - }, - }; - }), - }, - }, - ], - }, - refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"], - awaitRefetchQueries: true + } }); + }) + ); - await Promise.all( - payrollAdjustmentsToInsert.map((li) => { - return updateJobLines({ - variables: { - lineId: li.id, - line: { - convertedtolbr: li.convertedtolbr, - convertedtolbr_data: li.convertedtolbr_data, - }, - }, - }); - }) - ); - - const adjKeys = Object.keys(adjustmentsToInsert); - if (adjKeys.length > 0) { - //Query the adjustments, merge, and update them. - const existingAdjustments = await client.query({ - query: QUERY_JOB_LBR_ADJUSTMENTS, - variables: { - id: values.jobid, - }, - }); - - const newAdjustments = _.cloneDeep( - existingAdjustments.data.jobs_by_pk.lbr_adjustments - ); - - adjKeys.forEach((key) => { - newAdjustments[key] = - (newAdjustments[key] || 0) + adjustmentsToInsert[key]; - - insertAuditTrail({ - jobid: values.jobid, - operation: AuditTrailMapping.jobmodifylbradj({ - mod_lbr_ty: key, - hours: adjustmentsToInsert[key].toFixed(1), - }), - type: "jobmodifylbradj",}); - }); - - const jobUpdate = client.mutate({ - mutation: UPDATE_JOB, - variables: { - jobId: values.jobid, - job: {lbr_adjustments: newAdjustments}, - }, - }); - if (!!jobUpdate.errors) { - notification["error"]({ - message: t("jobs.errors.saving", { - message: JSON.stringify(jobUpdate.errors), - }), - }); - return; - } + const adjKeys = Object.keys(adjustmentsToInsert); + if (adjKeys.length > 0) { + //Query the adjustments, merge, and update them. + const existingAdjustments = await client.query({ + query: QUERY_JOB_LBR_ADJUSTMENTS, + variables: { + id: values.jobid } + }); - const markPolReceived = - outstanding_returns && - outstanding_returns.filter((o) => o.cm_received === true); + const newAdjustments = _.cloneDeep(existingAdjustments.data.jobs_by_pk.lbr_adjustments); - if (markPolReceived && markPolReceived.length > 0) { - const r2 = await updatePartsOrderLines({ - variables: {partsLineIds: markPolReceived.map((p) => p.id)}, - refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID" ], - }); - if (!!r2.errors) { - setLoading(false); - setEnterAgain(false); - notification["error"]({ - message: t("parts_orders.errors.updating", { - message: JSON.stringify(r2.errors), - }), - }); - } - } - - if (!!r1.errors) { - setLoading(false); - setEnterAgain(false); - notification["error"]({ - message: t("bills.errors.creating", { - message: JSON.stringify(r1.errors), - }), - }); - } - - const billId = r1.data.insert_bills.returning[0].id; - const markInventoryConsumed = - inventory && inventory.filter((i) => i.consumefrominventory); - - if (markInventoryConsumed && markInventoryConsumed.length > 0) { - const r2 = await updateInventoryLines({ - variables: { - InventoryIds: markInventoryConsumed.map((p) => p.id), - consumedbybillid: billId, - }, - }); - if (!!r2.errors) { - setLoading(false); - setEnterAgain(false); - notification["error"]({ - message: t("inventory.errors.updating", { - message: JSON.stringify(r2.errors), - }), - }); - } - } - //If it's not a credit memo, update the statuses. - - if (!values.is_credit_memo) { - await Promise.all( - remainingValues.billlines - .filter((il) => il.joblineid !== "noline") - .map((li) => { - return updateJobLines({ - variables: { - lineId: li.joblineid, - line: { - location: li.location || location, - status: - bodyshop.md_order_statuses.default_received || "Received*", - //Added parts price changes. - ...(li.create_ppc && - li.original_actual_price !== li.actual_price - ? { - act_price_before_ppc: li.original_actual_price, - act_price: li.actual_price, - } - : {}), - }, - }, - }); - }) - ); - } - - ///////////////////////// - if (upload && upload.length > 0) { - //insert Each of the documents? - - if (bodyshop.uselocalmediaserver) { - upload.forEach((u) => { - handleLocalUpload({ - ev: {file: u.originFileObj}, - context: { - jobid: values.jobid, - invoice_number: remainingValues.invoice_number, - vendorid: remainingValues.vendorid, - }, - }); - }); - } else { - upload.forEach((u) => { - handleUpload( - {file: u.originFileObj}, - { - bodyshop: bodyshop, - uploaded_by: currentUser.email, - jobId: values.jobid, - billId: billId, - tagsArray: null, - callback: null, - } - ); - }); - } - } - /////////////////////////// - setLoading(false); - notification["success"]({ - message: t("bills.successes.created"), - }); - - if (generateLabel) { - GenerateDocument( - { - name: Templates.parts_invoice_label_single.key, - variables: { - id: billId, - }, - }, - {}, - "p" - ); - } - - if (billEnterModal.actions.refetch) billEnterModal.actions.refetch(); + adjKeys.forEach((key) => { + newAdjustments[key] = (newAdjustments[key] || 0) + adjustmentsToInsert[key]; insertAuditTrail({ - jobid: values.jobid, - billid: billId, - operation: AuditTrailMapping.billposted( - r1.data.insert_bills.returning[0].invoice_number - ), - type: "billposted", + jobid: values.jobid, + operation: AuditTrailMapping.jobmodifylbradj({ + mod_lbr_ty: key, + hours: adjustmentsToInsert[key].toFixed(1) + }), + type: "jobmodifylbradj" + }); + }); + + const jobUpdate = client.mutate({ + mutation: UPDATE_JOB, + variables: { + jobId: values.jobid, + job: { lbr_adjustments: newAdjustments } + } + }); + if (!!jobUpdate.errors) { + notification["error"]({ + message: t("jobs.errors.saving", { + message: JSON.stringify(jobUpdate.errors) + }) + }); + return; + } + } + + const markPolReceived = outstanding_returns && outstanding_returns.filter((o) => o.cm_received === true); + + if (markPolReceived && markPolReceived.length > 0) { + const r2 = await updatePartsOrderLines({ + variables: { partsLineIds: markPolReceived.map((p) => p.id) }, + refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"] + }); + if (!!r2.errors) { + setLoading(false); + setEnterAgain(false); + notification["error"]({ + message: t("parts_orders.errors.updating", { + message: JSON.stringify(r2.errors) + }) + }); + } + } + + if (!!r1.errors) { + setLoading(false); + setEnterAgain(false); + notification["error"]({ + message: t("bills.errors.creating", { + message: JSON.stringify(r1.errors) + }) + }); + } + + const billId = r1.data.insert_bills.returning[0].id; + const markInventoryConsumed = inventory && inventory.filter((i) => i.consumefrominventory); + + if (markInventoryConsumed && markInventoryConsumed.length > 0) { + const r2 = await updateInventoryLines({ + variables: { + InventoryIds: markInventoryConsumed.map((p) => p.id), + consumedbybillid: billId + } + }); + if (!!r2.errors) { + setLoading(false); + setEnterAgain(false); + notification["error"]({ + message: t("inventory.errors.updating", { + message: JSON.stringify(r2.errors) + }) + }); + } + } + //If it's not a credit memo, update the statuses. + + if (!values.is_credit_memo) { + await Promise.all( + remainingValues.billlines + .filter((il) => il.joblineid !== "noline") + .map((li) => { + return updateJobLines({ + variables: { + lineId: li.joblineid, + line: { + location: li.location || location, + status: bodyshop.md_order_statuses.default_received || "Received*", + //Added parts price changes. + ...(li.create_ppc && li.original_actual_price !== li.actual_price + ? { + act_price_before_ppc: li.original_actual_price, + act_price: li.actual_price + } + : {}) + } + } + }); + }) + ); + } + + ///////////////////////// + if (upload && upload.length > 0) { + //insert Each of the documents? + + if (bodyshop.uselocalmediaserver) { + upload.forEach((u) => { + handleLocalUpload({ + ev: { file: u.originFileObj }, + context: { + jobid: values.jobid, + invoice_number: remainingValues.invoice_number, + vendorid: remainingValues.vendorid + } + }); + }); + } else { + upload.forEach((u) => { + handleUpload( + { file: u.originFileObj }, + { + bodyshop: bodyshop, + uploaded_by: currentUser.email, + jobId: values.jobid, + billId: billId, + tagsArray: null, + callback: null + } + ); + }); + } + } + /////////////////////////// + setLoading(false); + notification["success"]({ + message: t("bills.successes.created") }); - if (enterAgain) { - form.resetFields(); - form.setFieldsValue({ - ...formValues, - vendorid:values.vendorid, - billlines: [], - }); - // form.resetFields(); - } else { - toggleModalVisible(); - } - setEnterAgain(false); - }; + if (generateLabel) { + GenerateDocument( + { + name: Templates.parts_invoice_label_single.key, + variables: { + id: billId + } + }, + {}, + "p" + ); + } - const handleCancel = () => { - const r = window.confirm(t("general.labels.cancel")); - if (r === true) { - toggleModalVisible(); - } - }; + if (billEnterModal.actions.refetch) billEnterModal.actions.refetch(); - useEffect(() => { - if (enterAgain) form.submit(); - }, [enterAgain, form]); + insertAuditTrail({ + jobid: values.jobid, + billid: billId, + operation: AuditTrailMapping.billposted(r1.data.insert_bills.returning[0].invoice_number), + type: "billposted" + }); - useEffect(() => { - if (billEnterModal.open) { - form.setFieldsValue(formValues); - } else { - form.resetFields(); - } - }, [billEnterModal.open, form, formValues]); + if (enterAgain) { + form.resetFields(); + form.setFieldsValue({ + ...formValues, + vendorid: values.vendorid, + billlines: [] + }); + // form.resetFields(); + } else { + toggleModalVisible(); + } + setEnterAgain(false); + }; - return ( - form.submit()} - onCancel={handleCancel} - afterClose={() => { - form.resetFields(); - setLoading(false); - }} - footer={ - - setGenerateLabel(e.target.checked)} - > - {t("bills.labels.generatepartslabel")} - - - - {billEnterModal.context && billEnterModal.context.id ? null : ( - - )} - - } - destroyOnClose - > -
{ - setEnterAgain(false); - }} + const handleCancel = () => { + const r = window.confirm(t("general.labels.cancel")); + if (r === true) { + toggleModalVisible(); + } + }; + + useEffect(() => { + if (enterAgain) form.submit(); + }, [enterAgain, form]); + + useEffect(() => { + if (billEnterModal.open) { + form.setFieldsValue(formValues); + } else { + form.resetFields(); + } + }, [billEnterModal.open, form, formValues]); + + return ( + form.submit()} + onCancel={handleCancel} + afterClose={() => { + form.resetFields(); + setLoading(false); + }} + footer={ + + setGenerateLabel(e.target.checked)}> + {t("bills.labels.generatepartslabel")} + + + + {billEnterModal.context && billEnterModal.context.id ? null : ( + + )} + + } + destroyOnClose + > +
{ + setEnterAgain(false); + }} + > + + +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(BillEnterModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(BillEnterModalContainer); diff --git a/client/src/components/bill-form-lines-extended/bill-form-lines-extended.component.jsx b/client/src/components/bill-form-lines-extended/bill-form-lines-extended.component.jsx index ef54f443c..2cb93f328 100644 --- a/client/src/components/bill-form-lines-extended/bill-form-lines-extended.component.jsx +++ b/client/src/components/bill-form-lines-extended/bill-form-lines-extended.component.jsx @@ -1,136 +1,114 @@ -import {Form, Input, Table} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; +import { Form, Input, Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {alphaSort} from "../../utils/sorters"; +import { alphaSort } from "../../utils/sorters"; import BillFormItemsExtendedFormItem from "./bill-form-lines.extended.formitem.component"; -export default function BillFormLinesExtended({ - lineData, - discount, - form, - responsibilityCenters, - disabled, - }) { - const [search, setSearch] = useState(""); - const {t} = useTranslation(); - const columns = [ +export default function BillFormLinesExtended({ lineData, discount, form, responsibilityCenters, disabled }) { + const [search, setSearch] = useState(""); + const { t } = useTranslation(); + const columns = [ + { + title: t("joblines.fields.line_desc"), + dataIndex: "line_desc", + key: "line_desc", + width: "10%", + sorter: (a, b) => alphaSort(a.line_desc, b.line_desc) + }, + { + title: t("joblines.fields.oem_partno"), + dataIndex: "oem_partno", + key: "oem_partno", + width: "10%", + sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno) + }, + { + title: t("joblines.fields.part_type"), + dataIndex: "part_type", + key: "part_type", + width: "10%", + filters: [ { - title: t("joblines.fields.line_desc"), - dataIndex: "line_desc", - key: "line_desc", - width: "10%", - sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), + text: t("jobs.labels.partsfilter"), + value: ["PAN", "PAP", "PAL", "PAA", "PAS", "PASL"] }, { - title: t("joblines.fields.oem_partno"), - dataIndex: "oem_partno", - key: "oem_partno", - width: "10%", - sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno), + text: t("joblines.fields.part_types.PAN"), + value: ["PAN", "PAP"] }, { - title: t("joblines.fields.part_type"), - dataIndex: "part_type", - key: "part_type", - width: "10%", - filters: [ - { - text: t("jobs.labels.partsfilter"), - value: ["PAN", "PAP", "PAL", "PAA", "PAS", "PASL"], - }, - { - text: t("joblines.fields.part_types.PAN"), - value: ["PAN", "PAP"], - }, - { - text: t("joblines.fields.part_types.PAL"), - value: ["PAL"], - }, - { - text: t("joblines.fields.part_types.PAA"), - value: ["PAA"], - }, - { - text: t("joblines.fields.part_types.PAS"), - value: ["PAS", "PASL"], - }, - ], - onFilter: (value, record) => value.includes(record.part_type), - render: (text, record) => - record.part_type - ? t(`joblines.fields.part_types.${record.part_type}`) - : null, + text: t("joblines.fields.part_types.PAL"), + value: ["PAL"] }, + { + text: t("joblines.fields.part_types.PAA"), + value: ["PAA"] + }, + { + text: t("joblines.fields.part_types.PAS"), + value: ["PAS", "PASL"] + } + ], + onFilter: (value, record) => value.includes(record.part_type), + render: (text, record) => (record.part_type ? t(`joblines.fields.part_types.${record.part_type}`) : null) + }, - { - title: t("joblines.fields.act_price"), - dataIndex: "act_price", - key: "act_price", - width: "10%", - sorter: (a, b) => a.act_price - b.act_price, - shouldCellUpdate: false, - render: (text, record) => ( - <> - - {record.db_ref === "900510" || record.db_ref === "900511" - ? record.prt_dsmk_m - : record.act_price} - - {record.part_qty ? `(x ${record.part_qty})` : null} - {record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? ( - {`(${record.prt_dsmk_p}%)`} - ) : ( - <> - )} - - ), - }, - { - title: t("billlines.fields.posting"), - dataIndex: "posting", - key: "posting", + { + title: t("joblines.fields.act_price"), + dataIndex: "act_price", + key: "act_price", + width: "10%", + sorter: (a, b) => a.act_price - b.act_price, + shouldCellUpdate: false, + render: (text, record) => ( + <> + + {record.db_ref === "900510" || record.db_ref === "900511" ? record.prt_dsmk_m : record.act_price} + + {record.part_qty ? `(x ${record.part_qty})` : null} + {record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? ( + {`(${record.prt_dsmk_p}%)`} + ) : ( + <> + )} + + ) + }, + { + title: t("billlines.fields.posting"), + dataIndex: "posting", + key: "posting", - render: (text, record, index) => ( - - - - ), - }, - ]; - - const data = - search === "" - ? lineData - : lineData.filter( - (l) => - (l.line_desc && - l.line_desc.toLowerCase().includes(search.toLowerCase())) || - (l.oem_partno && - l.oem_partno.toLowerCase().includes(search.toLowerCase())) || - (l.act_price && - l.act_price.toString().startsWith(search.toString())) - ); - - return ( - - - setSearch(e.target.value)} allowClear/> - + render: (text, record, index) => ( + + - ); + ) + } + ]; + + const data = + search === "" + ? lineData + : lineData.filter( + (l) => + (l.line_desc && l.line_desc.toLowerCase().includes(search.toLowerCase())) || + (l.oem_partno && l.oem_partno.toLowerCase().includes(search.toLowerCase())) || + (l.act_price && l.act_price.toString().startsWith(search.toString())) + ); + + return ( + + + setSearch(e.target.value)} allowClear /> +
+ + ); } diff --git a/client/src/components/bill-form-lines-extended/bill-form-lines.extended.formitem.component.jsx b/client/src/components/bill-form-lines-extended/bill-form-lines.extended.formitem.component.jsx index 2761a20e1..50dd3ab10 100644 --- a/client/src/components/bill-form-lines-extended/bill-form-lines.extended.formitem.component.jsx +++ b/client/src/components/bill-form-lines-extended/bill-form-lines.extended.formitem.component.jsx @@ -1,284 +1,216 @@ import React from "react"; -import {MinusCircleFilled, PlusCircleFilled, WarningOutlined,} from "@ant-design/icons"; -import {Button, Form, Input, InputNumber, Select, Space, Switch} from "antd"; -import {useTranslation} from "react-i18next"; +import { MinusCircleFilled, PlusCircleFilled, WarningOutlined } from "@ant-design/icons"; +import { Button, Form, Input, InputNumber, Select, Space, Switch } from "antd"; +import { useTranslation } from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CiecaSelect from "../../utils/Ciecaselect"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(BillFormItemsExtendedFormItem); +export default connect(mapStateToProps, mapDispatchToProps)(BillFormItemsExtendedFormItem); export function BillFormItemsExtendedFormItem({ - value, - bodyshop, - form, - record, - index, - disabled, - responsibilityCenters, - discount, - }) { - // const { billlineskeys } = form.getFieldsValue("billlineskeys"); - - const {t} = useTranslation(); - if (!value) - return ( - - ); + value, + bodyshop, + form, + record, + index, + disabled, + responsibilityCenters, + discount +}) { + // const { billlineskeys } = form.getFieldsValue("billlineskeys"); + const { t } = useTranslation(); + if (!value) return ( - - - - - - - - - { - const {billlineskeys} = form.getFieldsValue("billlineskeys"); - form.setFieldsValue({ - billlineskeys: { - ...billlineskeys, - [record.id]: { - ...billlineskeys[billlineskeys], - actual_cost: !!billlineskeys[billlineskeys].actual_cost - ? billlineskeys[billlineskeys].actual_cost - : Math.round( - (parseFloat(e.target.value) * (1 - discount) + - Number.EPSILON) * - 100 - ) / 100, - }, - }, - }); - }} - /> - - - - - - {() => { - const line = value; - if (!!!line) return null; - const lineDiscount = ( - 1 - - Math.round((line.actual_cost / line.actual_price) * 100) / 100 - ).toPrecision(2); + - + form.setFieldsValue({ + ...values, + billlineskeys: { + ...(values.billlineskeys || {}), + [record.id]: { + joblineid: record.id, + line_desc: record.line_desc, + quantity: record.part_qty || 1, + actual_price: record.act_price, + cost_center: record.part_type + ? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid + ? record.part_type + : responsibilityCenters.defaults && (responsibilityCenters.defaults.costs[record.part_type] || null) + : null + } + } + }); + }} + > + + ); + + return ( + + + + + + + + + { + const { billlineskeys } = form.getFieldsValue("billlineskeys"); + form.setFieldsValue({ + billlineskeys: { + ...billlineskeys, + [record.id]: { + ...billlineskeys[billlineskeys], + actual_cost: !!billlineskeys[billlineskeys].actual_cost + ? billlineskeys[billlineskeys].actual_cost + : Math.round((parseFloat(e.target.value) * (1 - discount) + Number.EPSILON) * 100) / 100 + } + } + }); + }} + /> + + + + + + {() => { + const line = value; + if (!!!line) return null; + const lineDiscount = (1 - Math.round((line.actual_cost / line.actual_price) * 100) / 100).toPrecision(2); + + if (lineDiscount - discount === 0) return
; + return ; + }} + + + + + + + + + + + + {() => { + if (form.getFieldsValue("billlineskeys").billlineskeys[record.id].deductedfromlbr) + return ( +
+ + + + + + +
+ ); + return <>; + }} +
+ + + + + + + + + + + + + + ); } diff --git a/client/src/components/bill-form/bill-form.component.jsx b/client/src/components/bill-form/bill-form.component.jsx index 2cdcddf72..508be6af5 100644 --- a/client/src/components/bill-form/bill-form.component.jsx +++ b/client/src/components/bill-form/bill-form.component.jsx @@ -1,30 +1,30 @@ -import Icon, { UploadOutlined } from '@ant-design/icons'; -import { useApolloClient } from '@apollo/client'; -import { useSplitTreatments } from '@splitsoftware/splitio-react'; -import { Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload } from 'antd'; -import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { MdOpenInNew } from 'react-icons/md'; -import { connect } from 'react-redux'; -import { Link } from 'react-router-dom'; -import { createStructuredSelector } from 'reselect'; -import { CHECK_BILL_INVOICE_NUMBER } from '../../graphql/bills.queries'; -import { selectBodyshop } from '../../redux/user/user.selectors'; -import dayjs from '../../utils/day'; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; -import AlertComponent from '../alert/alert.component'; -import BillFormLinesExtended from '../bill-form-lines-extended/bill-form-lines-extended.component'; -import FormDatePicker from '../form-date-picker/form-date-picker.component'; -import FormFieldsChanged from '../form-fields-changed-alert/form-fields-changed-alert.component'; -import CurrencyInput from '../form-items-formatted/currency-form-item.component'; -import JobSearchSelect from '../job-search-select/job-search-select.component'; -import LayoutFormRow from '../layout-form-row/layout-form-row.component'; -import VendorSearchSelect from '../vendor-search-select/vendor-search-select.component'; -import BillFormLines from './bill-form.lines.component'; -import { CalculateBillTotal } from './bill-form.totals.utility'; +import Icon, { UploadOutlined } from "@ant-design/icons"; +import { useApolloClient } from "@apollo/client"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { MdOpenInNew } from "react-icons/md"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import dayjs from "../../utils/day"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; +import AlertComponent from "../alert/alert.component"; +import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component"; +import FormDatePicker from "../form-date-picker/form-date-picker.component"; +import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; +import CurrencyInput from "../form-items-formatted/currency-form-item.component"; +import JobSearchSelect from "../job-search-select/job-search-select.component"; +import LayoutFormRow from "../layout-form-row/layout-form-row.component"; +import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component"; +import BillFormLines from "./bill-form.lines.component"; +import { CalculateBillTotal } from "./bill-form.totals.utility"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({}); @@ -41,18 +41,18 @@ export function BillFormComponent({ job, loadOutstandingReturns, loadInventory, - preferredMake, + preferredMake }) { const { t } = useTranslation(); const client = useApolloClient(); const [discount, setDiscount] = useState(0); const { - treatments: { Extended_Bill_Posting, ClosingPeriod }, + treatments: { Extended_Bill_Posting, ClosingPeriod } } = useSplitTreatments({ attributes: {}, - names: ['Extended_Bill_Posting', 'ClosingPeriod'], - splitKey: bodyshop.imexshopid, + names: ["Extended_Bill_Posting", "ClosingPeriod"], + splitKey: bodyshop.imexshopid }); const handleVendorSelect = (props, opt) => { @@ -62,16 +62,16 @@ export function BillFormComponent({ !billEdit && loadOutstandingReturns({ variables: { - jobId: form.getFieldValue('jobid'), - vendorId: opt.value, - }, + jobId: form.getFieldValue("jobid"), + vendorId: opt.value + } }); }; const handleFederalTaxExemptSwitchToggle = (checked) => { // Early gate if (!checked) return; - const values = form.getFieldsValue('billlines'); + const values = form.getFieldsValue("billlines"); // Gate bill lines if (!values?.billlines?.length) return; @@ -83,26 +83,26 @@ export function BillFormComponent({ }; useEffect(() => { - if (job) form.validateFields(['is_credit_memo']); + if (job) form.validateFields(["is_credit_memo"]); }, [job, form]); useEffect(() => { - const vendorId = form.getFieldValue('vendorid'); + const vendorId = form.getFieldValue("vendorid"); if (vendorId && vendorAutoCompleteOptions) { const matchingVendors = vendorAutoCompleteOptions.filter((v) => v.id === vendorId); if (matchingVendors.length === 1) { setDiscount(matchingVendors[0].discount); } } - const jobId = form.getFieldValue('jobid'); + const jobId = form.getFieldValue("jobid"); if (jobId) { loadLines({ variables: { id: jobId } }); - if (form.getFieldValue('is_credit_memo') && vendorId && !billEdit) { + if (form.getFieldValue("is_credit_memo") && vendorId && !billEdit) { loadOutstandingReturns({ variables: { jobId: jobId, - vendorId: vendorId, - }, + vendorId: vendorId + } }); } } @@ -118,24 +118,24 @@ export function BillFormComponent({ setDiscount, vendorAutoCompleteOptions, loadLines, - bodyshop.inhousevendorid, + bodyshop.inhousevendorid ]); return (
- + { - if ( - form.getFieldValue('jobid') !== null && - form.getFieldValue('jobid') !== undefined - ) { - loadLines({ variables: { id: form.getFieldValue('jobid') } }); - if ( - form.getFieldValue('vendorid') !== null && - form.getFieldValue('vendorid') !== undefined - ) { + if (form.getFieldValue("jobid") !== null && form.getFieldValue("jobid") !== undefined) { + loadLines({ variables: { id: form.getFieldValue("jobid") } }); + if (form.getFieldValue("vendorid") !== null && form.getFieldValue("vendorid") !== undefined) { loadOutstandingReturns({ variables: { - jobId: form.getFieldValue('jobid'), - vendorId: form.getFieldValue('vendorid'), - }, + jobId: form.getFieldValue("jobid"), + vendorId: form.getFieldValue("vendorid") + } }); } } @@ -164,22 +158,22 @@ export function BillFormComponent({ /> ({ validator(rule, value) { - if (value && !getFieldValue(['isinhouse']) && value === bodyshop.inhousevendorid) { - return Promise.reject(t('bills.validation.manualinhouse')); + if (value && !getFieldValue(["isinhouse"]) && value === bodyshop.inhousevendorid) { + return Promise.reject(t("bills.validation.manualinhouse")); } return Promise.resolve(); - }, - }), + } + }) ]} > - {t('bills.labels.iouexists')} - + {t("bills.labels.iouexists")} + {iou.ro_number} @@ -216,89 +206,85 @@ export function BillFormComponent({ ))} ({ async validator(rule, value) { - const vendorid = getFieldValue('vendorid'); + const vendorid = getFieldValue("vendorid"); if (vendorid && value) { const response = await client.query({ query: CHECK_BILL_INVOICE_NUMBER, variables: { invoice_number: value, - vendorid: vendorid, - }, + vendorid: vendorid + } }); if (response.data.bills_aggregate.aggregate.count === 0) { return Promise.resolve(); } else if ( response.data.bills_aggregate.nodes.length === 1 && - response.data.bills_aggregate.nodes[0].id === form.getFieldValue('id') + response.data.bills_aggregate.nodes[0].id === form.getFieldValue("id") ) { return Promise.resolve(); } - return Promise.reject(t('bills.validation.unique_invoice_number')); + return Promise.reject(t("bills.validation.unique_invoice_number")); } else { return Promise.resolve(); } - }, - }), + } + }) ]} > ({ validator(rule, value) { - if (ClosingPeriod.treatment === 'on' && bodyshop.accountingconfig.ClosingPeriod) { + if (ClosingPeriod.treatment === "on" && bodyshop.accountingconfig.ClosingPeriod) { if ( dayjs(value) - .startOf('day') - .isSameOrAfter( - dayjs(bodyshop.accountingconfig.ClosingPeriod[0]).startOf('day') - ) && + .startOf("day") + .isSameOrAfter(dayjs(bodyshop.accountingconfig.ClosingPeriod[0]).startOf("day")) && dayjs(value) - .startOf('day') - .isSameOrBefore( - dayjs(bodyshop.accountingconfig.ClosingPeriod[1]).endOf('day') - ) + .startOf("day") + .isSameOrBefore(dayjs(bodyshop.accountingconfig.ClosingPeriod[1]).endOf("day")) ) { return Promise.resolve(); } else { - return Promise.reject(t('bills.validation.closingperiod')); + return Promise.reject(t("bills.validation.closingperiod")); } } else { return Promise.resolve(); } - }, - }), + } + }) ]} > ({ validator(rule, value) { - if (value === true && getFieldValue('jobid') && getFieldValue('vendorid')) { + if (value === true && getFieldValue("jobid") && getFieldValue("vendorid")) { //Removed as this would cause an additional reload when validating the form on submit and clear the values. // loadOutstandingReturns({ // variables: { @@ -316,31 +302,31 @@ export function BillFormComponent({ job.status === bodyshop.md_ro_statuses.default_void) && (value === false || !value) ) { - return Promise.reject(t('bills.labels.onlycmforinvoiced')); + return Promise.reject(t("bills.labels.onlycmforinvoiced")); } return Promise.resolve(); - }, - }), + } + }) ]} > {!billEdit && ( - - {bodyshop.md_parts_locations.map((loc, idx) => ( {loc} @@ -353,40 +339,36 @@ export function BillFormComponent({ {InstanceRenderManager({ imex: ( - + - ), + ) })} - + {InstanceRenderManager({ imex: ( <> - + {bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? ( - + ) : null} - ), + ) })} {() => { const values = form.getFieldsValue([ - 'billlines', - 'total', - 'federal_tax_rate', - 'state_tax_rate', - 'local_tax_rate', + "billlines", + "total", + "federal_tax_rate", + "state_tax_rate", + "local_tax_rate" ]); let totals; if (!!values.total && !!values.billlines && values.billlines.length > 0) @@ -394,56 +376,48 @@ export function BillFormComponent({ if (!!totals) return (
- - + + {InstanceRenderManager({ imex: ( - ), + ) })} - + {InstanceRenderManager({ imex: ( - ), + ) })} - {form.getFieldValue('is_credit_memo') ? ( - + {form.getFieldValue("is_credit_memo") ? ( + ) : null}
); @@ -451,9 +425,9 @@ export function BillFormComponent({ }}
- {t('bills.labels.bill_lines')} + {t("bills.labels.bill_lines")} - {Extended_Bill_Posting.treatment === 'on' ? ( + {Extended_Bill_Posting.treatment === "on" ? ( )} - - {t('documents.labels.upload')} + + {t("documents.labels.upload")} { if (Array.isArray(e)) { diff --git a/client/src/components/bill-form/bill-form.container.jsx b/client/src/components/bill-form/bill-form.container.jsx index 67b3c9ab3..e24418b12 100644 --- a/client/src/components/bill-form/bill-form.container.jsx +++ b/client/src/components/bill-form/bill-form.container.jsx @@ -1,83 +1,67 @@ -import {useLazyQuery, useQuery} from "@apollo/client"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useLazyQuery, useQuery } from "@apollo/client"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {QUERY_OUTSTANDING_INVENTORY} from "../../graphql/inventory.queries"; -import {GET_JOB_LINES_TO_ENTER_BILL} from "../../graphql/jobs-lines.queries"; -import {QUERY_UNRECEIVED_LINES} from "../../graphql/parts-orders.queries"; -import {SEARCH_VENDOR_AUTOCOMPLETE} from "../../graphql/vendors.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries"; +import { GET_JOB_LINES_TO_ENTER_BILL } from "../../graphql/jobs-lines.queries"; +import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries"; +import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component"; import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component"; import BillFormComponent from "./bill-form.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -export function BillFormContainer({ - bodyshop, - form, - billEdit, - disabled, - disableInvNumber, - }) { - const {treatments: {Simple_Inventory}} = useSplitTreatments({ - attributes: {}, - names: ["Simple_Inventory"], - splitKey: bodyshop && bodyshop.imexshopid, - }); +export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber }) { + const { + treatments: { Simple_Inventory } + } = useSplitTreatments({ + attributes: {}, + names: ["Simple_Inventory"], + splitKey: bodyshop && bodyshop.imexshopid + }); - const {data: VendorAutoCompleteData} = useQuery( - SEARCH_VENDOR_AUTOCOMPLETE, - {fetchPolicy: "network-only", nextFetchPolicy: "network-only"} - ); + const { data: VendorAutoCompleteData } = useQuery(SEARCH_VENDOR_AUTOCOMPLETE, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const [loadLines, {data: lineData}] = useLazyQuery( - GET_JOB_LINES_TO_ENTER_BILL - ); + const [loadLines, { data: lineData }] = useLazyQuery(GET_JOB_LINES_TO_ENTER_BILL); - const [loadOutstandingReturns, {loading: returnLoading, data: returnData}] = - useLazyQuery(QUERY_UNRECEIVED_LINES); - const [loadInventory, {loading: inventoryLoading, data: inventoryData}] = - useLazyQuery(QUERY_OUTSTANDING_INVENTORY); + const [loadOutstandingReturns, { loading: returnLoading, data: returnData }] = useLazyQuery(QUERY_UNRECEIVED_LINES); + const [loadInventory, { loading: inventoryLoading, data: inventoryData }] = useLazyQuery(QUERY_OUTSTANDING_INVENTORY); - return ( - <> - - {!billEdit && ( - - )} - {Simple_Inventory.treatment === "on" && ( - - )} - - ); + return ( + <> + + {!billEdit && } + {Simple_Inventory.treatment === "on" && ( + + )} + + ); } export default connect(mapStateToProps, null)(BillFormContainer); diff --git a/client/src/components/bill-form/bill-form.lines.component.jsx b/client/src/components/bill-form/bill-form.lines.component.jsx index d800ccf00..555be39e3 100644 --- a/client/src/components/bill-form/bill-form.lines.component.jsx +++ b/client/src/components/bill-form/bill-form.lines.component.jsx @@ -1,17 +1,6 @@ import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons"; import { useSplitTreatments } from "@splitsoftware/splitio-react"; -import { - Button, - Checkbox, - Form, - Input, - InputNumber, - Select, - Space, - Switch, - Table, - Tooltip, -} from "antd"; +import { Button, Checkbox, Form, Input, InputNumber, Select, Space, Switch, Table, Tooltip } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -24,893 +13,641 @@ import CurrencyInput from "../form-items-formatted/currency-form-item.component" import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export function BillEnterModalLinesComponent({ - bodyshop, - disabled, - lineData, - discount, - form, - responsibilityCenters, - billEdit, - billid, + bodyshop, + disabled, + lineData, + discount, + form, + responsibilityCenters, + billEdit, + billid }) { - const { t } = useTranslation(); - const { setFieldsValue, getFieldsValue, getFieldValue } = form; + const { t } = useTranslation(); + const { setFieldsValue, getFieldsValue, getFieldValue } = form; - const { - treatments: { Simple_Inventory, Enhanced_Payroll }, - } = useSplitTreatments({ - attributes: {}, - names: ["Simple_Inventory", "Enhanced_Payroll"], - splitKey: bodyshop && bodyshop.imexshopid, + const { + treatments: { Simple_Inventory, Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Simple_Inventory", "Enhanced_Payroll"], + splitKey: bodyshop && bodyshop.imexshopid + }); + + const columns = (remove) => { + return [ + { + title: t("billlines.fields.jobline"), + dataIndex: "joblineid", + editable: true, + width: "20rem", + formItemProps: (field) => { + return { + key: `${field.index}joblinename`, + name: [field.name, "joblineid"], + label: t("billlines.fields.jobline"), + rules: [ + { + required: true + //message: t("general.validation.required"), + } + ] + }; + }, + wrapper: (props) => ( + prev.is_credit_memo !== cur.is_credit_memo}> + {() => { + return props.children; + }} + + ), + formInput: (record, index) => ( + { + setFieldsValue({ + billlines: getFieldsValue(["billlines"]).billlines.map((item, idx) => { + if (idx === index) { + return { + ...item, + line_desc: opt.line_desc, + quantity: opt.part_qty || 1, + actual_price: opt.cost, + original_actual_price: opt.cost, + cost_center: opt.part_type + ? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid + ? opt.part_type !== "PAE" + ? opt.part_type + : null + : responsibilityCenters.defaults && + (responsibilityCenters.defaults.costs[opt.part_type] || null) + : null + }; + } + return item; + }) + }); + }} + /> + ) + }, + { + title: t("billlines.fields.line_desc"), + dataIndex: "line_desc", + editable: true, + + formItemProps: (field) => { + return { + key: `${field.index}line_desc`, + name: [field.name, "line_desc"], + label: t("billlines.fields.line_desc"), + rules: [ + { + required: true + //message: t("general.validation.required"), + } + ] + }; + }, + formInput: (record, index) => + }, + { + title: t("billlines.fields.quantity"), + dataIndex: "quantity", + editable: true, + width: "4rem", + formItemProps: (field) => { + return { + key: `${field.index}quantity`, + name: [field.name, "quantity"], + label: t("billlines.fields.quantity"), + rules: [ + { + required: true + //message: t("general.validation.required"), + }, + ({ getFieldValue }) => ({ + validator(rule, value) { + if (value && getFieldValue("billlines")[field.fieldKey]?.inventories?.length > value) { + return Promise.reject( + t("bills.validation.inventoryquantity", { + number: getFieldValue("billlines")[field.fieldKey]?.inventories?.length + }) + ); + } + return Promise.resolve(); + } + }) + ] + }; + }, + formInput: (record, index) => + }, + { + title: t("billlines.fields.actual_price"), + dataIndex: "actual_price", + width: "8rem", + editable: true, + formItemProps: (field) => { + return { + key: `${field.index}actual_price`, + name: [field.name, "actual_price"], + label: t("billlines.fields.actual_price"), + rules: [ + { + required: true + //message: t("general.validation.required"), + } + ] + }; + }, + formInput: (record, index) => ( + { + setFieldsValue({ + billlines: getFieldsValue("billlines").billlines.map((item, idx) => { + if (idx === index) { + return { + ...item, + actual_cost: !!item.actual_cost + ? item.actual_cost + : Math.round((parseFloat(e.target.value) * (1 - discount) + Number.EPSILON) * 100) / 100 + }; + } + return item; + }) + }); + }} + /> + ), + additional: (record, index) => + InstanceRenderManager({ + rome: ( + + {() => { + const billLine = getFieldValue(["billlines", record.name]); + const jobLine = lineData.find((line) => line.id === billLine?.joblineid); + + if (!billEdit && billLine && jobLine && billLine?.actual_price !== jobLine?.act_price) { + return ( + + + + + {t("joblines.fields.create_ppc")} + + ); + } else { + return null; + } + }} + + ) + //Do not need to set for promanager as it will default to Rome. + }) + }, + { + title: t("billlines.fields.actual_cost"), + dataIndex: "actual_cost", + editable: true, + width: "8rem", + + formItemProps: (field) => { + return { + key: `${field.index}actual_cost`, + name: [field.name, "actual_cost"], + label: t("billlines.fields.actual_cost"), + rules: [ + { + required: true + //message: t("general.validation.required"), + } + ] + }; + }, + formInput: (record, index) => ( + + {() => { + const line = getFieldsValue(["billlines"]).billlines[index]; + if (!!!line) return null; + let lineDiscount = 1 - line.actual_cost / line.actual_price; + if (isNaN(lineDiscount)) lineDiscount = 0; + return ( + + 0.005 + ? lineDiscount > discount + ? "orange" + : "red" + : "green" + }} + /> + + ); + }} + + } + /> + ) + // additional: (record, index) => ( + // + // {() => { + // const line = getFieldsValue(["billlines"]).billlines[index]; + // if (!!!line) return null; + // const lineDiscount = ( + // 1 - + // Math.round((line.actual_cost / line.actual_price) * 100) / 100 + // ).toPrecision(2); + + // return ( + // + // + // + // ); + // }} + // + // ), + }, + { + title: t("billlines.fields.cost_center"), + dataIndex: "cost_center", + editable: true, + + formItemProps: (field) => { + return { + key: `${field.index}cost_center`, + name: [field.name, "cost_center"], + label: t("billlines.fields.cost_center"), + valuePropName: "value", + rules: [ + { + required: true + //message: t("general.validation.required"), + } + ] + }; + }, + formInput: (record, index) => ( + + ) + }, + ...(billEdit + ? [] + : [ + { + title: t("billlines.fields.location"), + dataIndex: "location", + editable: true, + label: t("billlines.fields.location"), + formItemProps: (field) => { + return { + key: `${field.index}location`, + name: [field.name, "location"] + }; + }, + formInput: (record, index) => ( + + ) + } + ]), + { + title: t("billlines.labels.deductedfromlbr"), + dataIndex: "deductedfromlbr", + editable: true, + formItemProps: (field) => { + return { + valuePropName: "checked", + key: `${field.index}deductedfromlbr`, + name: [field.name, "deductedfromlbr"] + }; + }, + formInput: (record, index) => , + additional: (record, index) => ( + + {() => { + const price = getFieldValue(["billlines", record.name, "actual_price"]); + + const adjustmentRate = getFieldValue(["billlines", record.name, "lbr_adjustment", "rate"]); + + const billline = getFieldValue(["billlines", record.name]); + + const jobline = lineData.find((line) => line.id === billline?.joblineid); + + const employeeTeamName = bodyshop.employee_teams.find((team) => team.id === jobline?.assigned_team); + + if (getFieldValue(["billlines", record.name, "deductedfromlbr"])) + return ( +
+ {Enhanced_Payroll.treatment === "on" ? ( + + {t("joblines.fields.assigned_team", { + name: employeeTeamName?.name + })} + {`${jobline.mod_lb_hrs} units/${t(`joblines.fields.lbr_types.${jobline.mod_lbr_ty}`)}`} + + ) : null} + + + + + {Enhanced_Payroll.treatment === "on" ? ( + + + + ) : ( + + + + )} + + {price && adjustmentRate && `${(price / adjustmentRate).toFixed(1)} hrs`} +
+ ); + return <>; + }} +
+ ) + }, + + ...InstanceRenderManager({ + rome: [], + promanager: [], + imex: [ + { + title: t("billlines.fields.federal_tax_applicable"), + dataIndex: "applicable_taxes.federal", + editable: true, + + formItemProps: (field) => { + return { + key: `${field.index}fedtax`, + valuePropName: "checked", + initialValue: InstanceRenderManager({ + imex: true, + rome: false, + promanager: false + }), + name: [field.name, "applicable_taxes", "federal"] + }; + }, + formInput: (record, index) => + } + ] + }), + + { + title: t("billlines.fields.state_tax_applicable"), + dataIndex: "applicable_taxes.state", + editable: true, + + formItemProps: (field) => { + return { + key: `${field.index}statetax`, + valuePropName: "checked", + name: [field.name, "applicable_taxes", "state"] + }; + }, + formInput: (record, index) => + }, + + ...InstanceRenderManager({ + rome: [], + promanager: [], + imex: [ + { + title: t("billlines.fields.local_tax_applicable"), + dataIndex: "applicable_taxes.local", + editable: true, + + formItemProps: (field) => { + return { + key: `${field.index}localtax`, + valuePropName: "checked", + name: [field.name, "applicable_taxes", "local"] + }; + }, + formInput: (record, index) => + } + ] + }), + { + title: t("general.labels.actions"), + + dataIndex: "actions", + render: (text, record) => ( + + {() => ( + + + {Simple_Inventory.treatment === "on" && ( + + )} + + )} + + ) + } + ]; + }; + + const mergedColumns = (remove) => + columns(remove).map((col) => { + if (!col.editable) return col; + return { + ...col, + onCell: (record) => ({ + record, + formItemProps: col.formItemProps, + formInput: col.formInput, + additional: col.additional, + dataIndex: col.dataIndex, + title: col.title + }) + }; }); - const columns = (remove) => { - return [ - { - title: t("billlines.fields.jobline"), - dataIndex: "joblineid", - editable: true, - width: "20rem", - formItemProps: (field) => { - return { - key: `${field.index}joblinename`, - name: [field.name, "joblineid"], - label: t("billlines.fields.jobline"), - rules: [ - { - required: true, - //message: t("general.validation.required"), - }, - ], - }; - }, - wrapper: (props) => ( - - prev.is_credit_memo !== cur.is_credit_memo - } - > - {() => { - return props.children; - }} - - ), - formInput: (record, index) => ( - { - setFieldsValue({ - billlines: getFieldsValue([ - "billlines", - ]).billlines.map((item, idx) => { - if (idx === index) { - return { - ...item, - line_desc: opt.line_desc, - quantity: opt.part_qty || 1, - actual_price: opt.cost, - original_actual_price: opt.cost, - cost_center: opt.part_type - ? bodyshop.pbs_serialnumber || - bodyshop.cdk_dealerid - ? opt.part_type !== "PAE" - ? opt.part_type - : null - : responsibilityCenters.defaults && - (responsibilityCenters - .defaults.costs[ - opt.part_type - ] || - null) - : null, - }; - } - return item; - }), - }); - }} - /> - ), - }, - { - title: t("billlines.fields.line_desc"), - dataIndex: "line_desc", - editable: true, - - formItemProps: (field) => { - return { - key: `${field.index}line_desc`, - name: [field.name, "line_desc"], - label: t("billlines.fields.line_desc"), - rules: [ - { - required: true, - //message: t("general.validation.required"), - }, - ], - }; - }, - formInput: (record, index) => , - }, - { - title: t("billlines.fields.quantity"), - dataIndex: "quantity", - editable: true, - width: "4rem", - formItemProps: (field) => { - return { - key: `${field.index}quantity`, - name: [field.name, "quantity"], - label: t("billlines.fields.quantity"), - rules: [ - { - required: true, - //message: t("general.validation.required"), - }, - ({ getFieldValue }) => ({ - validator(rule, value) { - if ( - value && - getFieldValue("billlines")[ - field.fieldKey - ]?.inventories?.length > value - ) { - return Promise.reject( - t( - "bills.validation.inventoryquantity", - { - number: getFieldValue( - "billlines" - )[field.fieldKey] - ?.inventories?.length, - } - ) - ); - } - return Promise.resolve(); - }, - }), - ], - }; - }, - formInput: (record, index) => ( - - ), - }, - { - title: t("billlines.fields.actual_price"), - dataIndex: "actual_price", - width: "8rem", - editable: true, - formItemProps: (field) => { - return { - key: `${field.index}actual_price`, - name: [field.name, "actual_price"], - label: t("billlines.fields.actual_price"), - rules: [ - { - required: true, - //message: t("general.validation.required"), - }, - ], - }; - }, - formInput: (record, index) => ( - { - setFieldsValue({ - billlines: getFieldsValue( - "billlines" - ).billlines.map((item, idx) => { - if (idx === index) { - return { - ...item, - actual_cost: !!item.actual_cost - ? item.actual_cost - : Math.round( - (parseFloat( - e.target.value - ) * - (1 - discount) + - Number.EPSILON) * - 100 - ) / 100, - }; - } - return item; - }), - }); - }} - /> - ), - additional: (record, index) => - InstanceRenderManager({ - rome: ( - - {() => { - const billLine = getFieldValue([ - "billlines", - record.name, - ]); - const jobLine = lineData.find( - (line) => - line.id === billLine?.joblineid - ); - - if ( - !billEdit && - billLine && - jobLine && - billLine?.actual_price !== - jobLine?.act_price - ) { - return ( - - - - - {t( - "joblines.fields.create_ppc" - )} - - ); - } else { - return null; - } - }} - - ), - //Do not need to set for promanager as it will default to Rome. - }), - }, - { - title: t("billlines.fields.actual_cost"), - dataIndex: "actual_cost", - editable: true, - width: "8rem", - - formItemProps: (field) => { - return { - key: `${field.index}actual_cost`, - name: [field.name, "actual_cost"], - label: t("billlines.fields.actual_cost"), - rules: [ - { - required: true, - //message: t("general.validation.required"), - }, - ], - }; - }, - formInput: (record, index) => ( - - {() => { - const line = getFieldsValue(["billlines"]) - .billlines[index]; - if (!!!line) return null; - let lineDiscount = - 1 - - line.actual_cost / line.actual_price; - if (isNaN(lineDiscount)) lineDiscount = 0; - return ( - - 0.005 - ? lineDiscount > - discount - ? "orange" - : "red" - : "green", - }} - /> - - ); - }} -
- } - /> - ), - // additional: (record, index) => ( - // - // {() => { - // const line = getFieldsValue(["billlines"]).billlines[index]; - // if (!!!line) return null; - // const lineDiscount = ( - // 1 - - // Math.round((line.actual_cost / line.actual_price) * 100) / 100 - // ).toPrecision(2); - - // return ( - // - // - // - // ); - // }} - // - // ), - }, - { - title: t("billlines.fields.cost_center"), - dataIndex: "cost_center", - editable: true, - - formItemProps: (field) => { - return { - key: `${field.index}cost_center`, - name: [field.name, "cost_center"], - label: t("billlines.fields.cost_center"), - valuePropName: "value", - rules: [ - { - required: true, - //message: t("general.validation.required"), - }, - ], - }; - }, - formInput: (record, index) => ( - - ), - }, - ...(billEdit - ? [] - : [ - { - title: t("billlines.fields.location"), - dataIndex: "location", - editable: true, - label: t("billlines.fields.location"), - formItemProps: (field) => { - return { - key: `${field.index}location`, - name: [field.name, "location"], - }; - }, - formInput: (record, index) => ( - - ), - }, - ]), - { - title: t("billlines.labels.deductedfromlbr"), - dataIndex: "deductedfromlbr", - editable: true, - formItemProps: (field) => { - return { - valuePropName: "checked", - key: `${field.index}deductedfromlbr`, - name: [field.name, "deductedfromlbr"], - }; - }, - formInput: (record, index) => , - additional: (record, index) => ( - - {() => { - const price = getFieldValue([ - "billlines", - record.name, - "actual_price", - ]); - - const adjustmentRate = getFieldValue([ - "billlines", - record.name, - "lbr_adjustment", - "rate", - ]); - - const billline = getFieldValue([ - "billlines", - record.name, - ]); - - const jobline = lineData.find( - (line) => line.id === billline?.joblineid - ); - - const employeeTeamName = - bodyshop.employee_teams.find( - (team) => team.id === jobline?.assigned_team - ); - - if ( - getFieldValue([ - "billlines", - record.name, - "deductedfromlbr", - ]) - ) - return ( -
- {Enhanced_Payroll.treatment === "on" ? ( - - {t( - "joblines.fields.assigned_team", - { - name: employeeTeamName?.name, - } - )} - {`${ - jobline.mod_lb_hrs - } units/${t( - `joblines.fields.lbr_types.${jobline.mod_lbr_ty}` - )}`} - - ) : null} - - - - - {Enhanced_Payroll.treatment === "on" ? ( - - - - ) : ( - - - - )} - - - {price && - adjustmentRate && - `${( - price / adjustmentRate - ).toFixed(1)} hrs`} - -
- ); - return <>; - }} -
- ), - }, - - ...InstanceRenderManager({ - rome:[], - promanager:[], - imex: [ - { - title: t("billlines.fields.federal_tax_applicable"), - dataIndex: "applicable_taxes.federal", - editable: true, - - formItemProps: (field) => { - return { - key: `${field.index}fedtax`, - valuePropName: 'checked', - initialValue: InstanceRenderManager({ - imex: true, - rome: false, - promanager: false, - }), - name: [field.name, 'applicable_taxes', 'federal'], - }; - }, - formInput: (record, index) => ( - - ), - }, - ], - }), - - { - title: t("billlines.fields.state_tax_applicable"), - dataIndex: "applicable_taxes.state", - editable: true, - - formItemProps: (field) => { - return { - key: `${field.index}statetax`, - valuePropName: "checked", - name: [field.name, "applicable_taxes", "state"], - }; - }, - formInput: (record, index) => , - }, - - ...InstanceRenderManager({ - rome:[], - promanager:[], - imex: [ - { - title: t("billlines.fields.local_tax_applicable"), - dataIndex: "applicable_taxes.local", - editable: true, - - formItemProps: (field) => { - return { - key: `${field.index}localtax`, - valuePropName: "checked", - name: [field.name, "applicable_taxes", "local"], - }; - }, - formInput: (record, index) => ( - - ), - }, - ], - }), - { - title: t("general.labels.actions"), - - dataIndex: "actions", - render: (text, record) => ( - - {() => ( - - - {Simple_Inventory.treatment === "on" && ( - - )} - - )} - - ), - }, - ]; - }; - - const mergedColumns = (remove) => - columns(remove).map((col) => { - if (!col.editable) return col; - return { - ...col, - onCell: (record) => ({ - record, - formItemProps: col.formItemProps, - formInput: col.formInput, - additional: col.additional, - dataIndex: col.dataIndex, - title: col.title, - }), - }; - }); - - return ( - { - if (!billlines || billlines.length < 1) { - return Promise.reject( - new Error(t("billlines.validation.atleastone")) - ); - } - }, - }, - ]} - > - {(fields, { add, remove, move }) => { - return ( - <> -
- - - - - ); - }} - - ); + return ( + { + if (!billlines || billlines.length < 1) { + return Promise.reject(new Error(t("billlines.validation.atleastone"))); + } + } + } + ]} + > + {(fields, { add, remove, move }) => { + return ( + <> +
+ + + + + ); + }} + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(BillEnterModalLinesComponent); +export default connect(mapStateToProps, mapDispatchToProps)(BillEnterModalLinesComponent); const EditableCell = ({ - dataIndex, - title, - inputType, - record, - index, - children, - formInput, - formItemProps, - additional, - wrapper, - ...restProps + dataIndex, + title, + inputType, + record, + index, + children, + formInput, + formItemProps, + additional, + wrapper, + ...restProps }) => { - if (additional) - return ( - - ); - if (wrapper) - return ( - - - - ); + if (additional) return ( - + ); + if (wrapper) + return ( + + + + ); + return ( + + ); }; diff --git a/client/src/components/bill-form/bill-form.totals.utility.js b/client/src/components/bill-form/bill-form.totals.utility.js index eb5c67517..cec5c9152 100644 --- a/client/src/components/bill-form/bill-form.totals.utility.js +++ b/client/src/components/bill-form/bill-form.totals.utility.js @@ -1,47 +1,42 @@ import Dinero from "dinero.js"; export const CalculateBillTotal = (invoice) => { - const {total, billlines, federal_tax_rate, local_tax_rate, state_tax_rate} = - invoice; + const { total, billlines, federal_tax_rate, local_tax_rate, state_tax_rate } = invoice; - //TODO Determine why this recalculates so many times. - let subtotal = Dinero({amount: 0}); - let federalTax = Dinero({amount: 0}); - let stateTax = Dinero({amount: 0}); - let localTax = Dinero({amount: 0}); + //TODO Determine why this recalculates so many times. + let subtotal = Dinero({ amount: 0 }); + let federalTax = Dinero({ amount: 0 }); + let stateTax = Dinero({ amount: 0 }); + let localTax = Dinero({ amount: 0 }); - if (!!!billlines) return null; + if (!!!billlines) return null; - billlines.forEach((i) => { - if (!!i) { - const itemTotal = Dinero({ - amount: Math.round((i.actual_cost || 0) * 100), - }).multiply(i.quantity || 1); + billlines.forEach((i) => { + if (!!i) { + const itemTotal = Dinero({ + amount: Math.round((i.actual_cost || 0) * 100) + }).multiply(i.quantity || 1); - subtotal = subtotal.add(itemTotal); - if (i.applicable_taxes?.federal) { - federalTax = federalTax.add( - itemTotal.percentage(federal_tax_rate || 0) - ); - } - if (i.applicable_taxes?.state) - stateTax = stateTax.add(itemTotal.percentage(state_tax_rate || 0)); - if (i.applicable_taxes?.local) - localTax = localTax.add(itemTotal.percentage(local_tax_rate || 0)); - } - }); + subtotal = subtotal.add(itemTotal); + if (i.applicable_taxes?.federal) { + federalTax = federalTax.add(itemTotal.percentage(federal_tax_rate || 0)); + } + if (i.applicable_taxes?.state) stateTax = stateTax.add(itemTotal.percentage(state_tax_rate || 0)); + if (i.applicable_taxes?.local) localTax = localTax.add(itemTotal.percentage(local_tax_rate || 0)); + } + }); - const invoiceTotal = Dinero({amount: Math.round((total || 0) * 100)}); - const enteredTotal = subtotal.add(federalTax).add(stateTax).add(localTax); - const discrepancy = enteredTotal.subtract(invoiceTotal); + const invoiceTotal = Dinero({ amount: Math.round((total || 0) * 100) }); + const enteredTotal = subtotal.add(federalTax).add(stateTax).add(localTax); + const discrepancy = enteredTotal.subtract(invoiceTotal); - return { - subtotal, - federalTax, - stateTax, - localTax, - enteredTotal, - invoiceTotal, - discrepancy, - }; + return { + subtotal, + federalTax, + stateTax, + localTax, + enteredTotal, + invoiceTotal, + discrepancy + }; }; diff --git a/client/src/components/bill-inventory-table/bill-inventory-table.component.jsx b/client/src/components/bill-inventory-table/bill-inventory-table.component.jsx index a9cab74b9..7cbdd27b9 100644 --- a/client/src/components/bill-inventory-table/bill-inventory-table.component.jsx +++ b/client/src/components/bill-inventory-table/bill-inventory-table.component.jsx @@ -1,173 +1,153 @@ -import {Checkbox, Form, Skeleton, Typography} from "antd"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; +import { Checkbox, Form, Skeleton, Typography } from "antd"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component"; import "./bill-inventory-table.styles.scss"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {selectBillEnterModal} from "../../redux/modals/modals.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { selectBillEnterModal } from "../../redux/modals/modals.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - billEnterModal: selectBillEnterModal, + bodyshop: selectBodyshop, + billEnterModal: selectBillEnterModal }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(BillInventoryTable); -export function BillInventoryTable({ - billEnterModal, - bodyshop, - form, - billEdit, - inventoryLoading, - inventoryData, - }) { - const {t} = useTranslation(); +export function BillInventoryTable({ billEnterModal, bodyshop, form, billEdit, inventoryLoading, inventoryData }) { + const { t } = useTranslation(); - useEffect(() => { - if (inventoryData && inventoryData.inventory) { - form.setFieldsValue({ - inventory: billEnterModal.context.consumeinventoryid - ? inventoryData.inventory.map((i) => { - if (i.id === billEnterModal.context.consumeinventoryid) - i.consumefrominventory = true; - return i; - }) - : inventoryData.inventory, - }); + useEffect(() => { + if (inventoryData && inventoryData.inventory) { + form.setFieldsValue({ + inventory: billEnterModal.context.consumeinventoryid + ? inventoryData.inventory.map((i) => { + if (i.id === billEnterModal.context.consumeinventoryid) i.consumefrominventory = true; + return i; + }) + : inventoryData.inventory + }); + } + }, [inventoryData, form, billEnterModal.context.consumeinventoryid]); + + return ( + prev.vendorid !== cur.vendorid} noStyle> + {() => { + const is_inhouse = form.getFieldValue("vendorid") === bodyshop.inhousevendorid; + + if (!is_inhouse || billEdit) { + return null; } - }, [inventoryData, form, billEnterModal.context.consumeinventoryid]); - return ( - prev.vendorid !== cur.vendorid} - noStyle - > - {() => { - const is_inhouse = - form.getFieldValue("vendorid") === bodyshop.inhousevendorid; + if (inventoryLoading) return ; - if (!is_inhouse || billEdit) { - return null; - } + return ( + + {(fields, { add, remove, move }) => { + return ( + <> + {t("inventory.labels.inventory")} +
-
- - {(formInput && formInput(record, record.name)) || - children} - - {additional && additional(record, record.name)} -
-
- - {(formInput && formInput(record, record.name)) || - children} - - - - {(formInput && formInput(record, record.name)) || children} - - +
+ + {(formInput && formInput(record, record.name)) || children} + + {additional && additional(record, record.name)} +
+
+ + {(formInput && formInput(record, record.name)) || children} + + + + {(formInput && formInput(record, record.name)) || children} + +
+ + + + + + + + + + + + + {fields.map((field, index) => ( + + - if (inventoryLoading) return ; + + + + + - return ( - - {(fields, {add, remove, move}) => { - return ( - <> - - {t("inventory.labels.inventory")} - -
{t("billlines.fields.line_desc")}{t("vendors.fields.name")}{t("billlines.fields.quantity")}{t("billlines.fields.actual_price")}{t("billlines.fields.actual_cost")}{t("inventory.fields.comment")}{t("inventory.actions.consumefrominventory")}
+ + + + + + + + + + + + + + + + + + + + + + + +
- - - - - - - - - - - - - {fields.map((field, index) => ( - - - - - - - - - - - - ))} - -
{t("billlines.fields.line_desc")}{t("vendors.fields.name")}{t("billlines.fields.quantity")}{t("billlines.fields.actual_price")}{t("billlines.fields.actual_cost")}{t("inventory.fields.comment")}{t("inventory.actions.consumefrominventory")}
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - ); - }} - - ); + + + + + + + ))} + + + + ); }} -
- ); + + ); + }} + + ); } diff --git a/client/src/components/bill-inventory-table/bill-inventory-table.styles.scss b/client/src/components/bill-inventory-table/bill-inventory-table.styles.scss index 9a47de1dc..173714f41 100644 --- a/client/src/components/bill-inventory-table/bill-inventory-table.styles.scss +++ b/client/src/components/bill-inventory-table/bill-inventory-table.styles.scss @@ -16,4 +16,4 @@ tr:hover { background-color: #f5f5f5; } -} \ No newline at end of file +} diff --git a/client/src/components/bill-line-search-select/bill-line-search-select.component.jsx b/client/src/components/bill-line-search-select/bill-line-search-select.component.jsx index 75616849a..187cf29bf 100644 --- a/client/src/components/bill-line-search-select/bill-line-search-select.component.jsx +++ b/client/src/components/bill-line-search-select/bill-line-search-select.component.jsx @@ -1,98 +1,73 @@ -import {Select} from "antd"; -import React, {forwardRef} from "react"; -import {useTranslation} from "react-i18next"; -import InstanceRenderMgr from '../../utils/instanceRenderMgr'; +import { Select } from "antd"; +import React, { forwardRef } from "react"; +import { useTranslation } from "react-i18next"; +import InstanceRenderMgr from "../../utils/instanceRenderMgr"; //To be used as a form element only. -const {Option} = Select; -const BillLineSearchSelect = ( - {options, disabled, allowRemoved, ...restProps}, - ref -) => { - const {t} = useTranslation(); +const { Option } = Select; +const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps }, ref) => { + const { t } = useTranslation(); - return ( - { + return ( + (option.line_desc && option.line_desc.toLowerCase().includes(inputValue.toLowerCase())) || + (option.oem_partno && option.oem_partno.toLowerCase().includes(inputValue.toLowerCase())) || + (option.alt_partno && option.alt_partno.toLowerCase().includes(inputValue.toLowerCase())) || + (option.act_price && option.act_price.toString().startsWith(inputValue.toString())) + ); + }} + notFoundContent={"Removed."} + {...restProps} + > + + {t("billlines.labels.other")} + + {options + ? options.map((item) => ( + - )) - : null} - - ); + + )) + : null} + + ); }; export default forwardRef(BillLineSearchSelect); diff --git a/client/src/components/bill-mark-exported-button/bill-mark-exported-button.component.jsx b/client/src/components/bill-mark-exported-button/bill-mark-exported-button.component.jsx index a014bf775..5a5720135 100644 --- a/client/src/components/bill-mark-exported-button/bill-mark-exported-button.component.jsx +++ b/client/src/components/bill-mark-exported-button/bill-mark-exported-button.component.jsx @@ -1,97 +1,89 @@ -import {gql, useMutation} from "@apollo/client"; -import {Button, notification} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; +import { gql, useMutation } from "@apollo/client"; +import { Button, notification } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectAuthLevel, selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; -import {HasRbacAccess} from "../rbac-wrapper/rbac-wrapper.component"; -import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component"; +import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - authLevel: selectAuthLevel, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + authLevel: selectAuthLevel, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(BillMarkExportedButton); +export default connect(mapStateToProps, mapDispatchToProps)(BillMarkExportedButton); -export function BillMarkExportedButton({ - currentUser, - bodyshop, - authLevel, - bill, - }) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); +export function BillMarkExportedButton({ currentUser, bodyshop, authLevel, bill }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); - const [updateBill] = useMutation(gql` - mutation UPDATE_BILL($billId: uuid!) { - update_bills(where: { id: { _eq: $billId } }, _set: { exported: true }) { - returning { - id - exported - exported_at - } - } + const [updateBill] = useMutation(gql` + mutation UPDATE_BILL($billId: uuid!) { + update_bills(where: { id: { _eq: $billId } }, _set: { exported: true }) { + returning { + id + exported + exported_at } - `); + } + } + `); - const handleUpdate = async () => { - setLoading(true); - const result = await updateBill({ - variables: {billId: bill.id}, - }); - - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - billid: bill.id, - successful: true, - message: JSON.stringify([t("general.labels.markedexported")]), - useremail: currentUser.email, - }, - ], - }, - }); - - if (!result.errors) { - notification["success"]({ - message: t("bills.successes.markexported"), - }); - } else { - notification["error"]({ - message: t("bills.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - //Get the owner details, populate it all back into the job. - }; - - const hasAccess = HasRbacAccess({ - bodyshop, - authLevel, - action: "bills:reexport", + const handleUpdate = async () => { + setLoading(true); + const result = await updateBill({ + variables: { billId: bill.id } }); - if (hasAccess) - return ( - - ); + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + billid: bill.id, + successful: true, + message: JSON.stringify([t("general.labels.markedexported")]), + useremail: currentUser.email + } + ] + } + }); - return <>; + if (!result.errors) { + notification["success"]({ + message: t("bills.successes.markexported") + }); + } else { + notification["error"]({ + message: t("bills.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + //Get the owner details, populate it all back into the job. + }; + + const hasAccess = HasRbacAccess({ + bodyshop, + authLevel, + action: "bills:reexport" + }); + + if (hasAccess) + return ( + + ); + + return <>; } diff --git a/client/src/components/bill-print-button/bill-print-button.component.jsx b/client/src/components/bill-print-button/bill-print-button.component.jsx index dd3bac20e..f63b9a13f 100644 --- a/client/src/components/bill-print-button/bill-print-button.component.jsx +++ b/client/src/components/bill-print-button/bill-print-button.component.jsx @@ -1,38 +1,38 @@ -import {Button, Space} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { Button, Space } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; -export default function BillPrintButton({billid}) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const Templates = TemplateList("job_special"); +export default function BillPrintButton({ billid }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const Templates = TemplateList("job_special"); - const submitHandler = async () => { - setLoading(true); - try { - await GenerateDocument( - { - name: Templates.parts_invoice_label_single.key, - variables: { - id: billid, - }, - }, - {}, - "p" - ); - } catch (e) { - console.warn("Warning: Error generating a document."); - } - setLoading(false); - }; + const submitHandler = async () => { + setLoading(true); + try { + await GenerateDocument( + { + name: Templates.parts_invoice_label_single.key, + variables: { + id: billid + } + }, + {}, + "p" + ); + } catch (e) { + console.warn("Warning: Error generating a document."); + } + setLoading(false); + }; - return ( - - - - ); + return ( + + + + ); } diff --git a/client/src/components/bill-reexport-button/bill-reexport-button.component.jsx b/client/src/components/bill-reexport-button/bill-reexport-button.component.jsx index 3f5ab32b8..81da224d7 100644 --- a/client/src/components/bill-reexport-button/bill-reexport-button.component.jsx +++ b/client/src/components/bill-reexport-button/bill-reexport-button.component.jsx @@ -1,79 +1,72 @@ -import {gql, useMutation} from "@apollo/client"; -import {Button, notification} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; +import { gql, useMutation } from "@apollo/client"; +import { Button, notification } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectAuthLevel, selectBodyshop,} from "../../redux/user/user.selectors"; -import {HasRbacAccess} from "../rbac-wrapper/rbac-wrapper.component"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors"; +import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - authLevel: selectAuthLevel, + bodyshop: selectBodyshop, + authLevel: selectAuthLevel }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(BillMarkForReexportButton); +export default connect(mapStateToProps, mapDispatchToProps)(BillMarkForReexportButton); -export function BillMarkForReexportButton({bodyshop, authLevel, bill}) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); +export function BillMarkForReexportButton({ bodyshop, authLevel, bill }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); - const [updateBill] = useMutation(gql` - mutation UPDATE_BILL($billId: uuid!) { - update_bills(where: { id: { _eq: $billId } }, _set: { exported: false }) { - returning { - id - exported - exported_at - } - } + const [updateBill] = useMutation(gql` + mutation UPDATE_BILL($billId: uuid!) { + update_bills(where: { id: { _eq: $billId } }, _set: { exported: false }) { + returning { + id + exported + exported_at } - `); + } + } + `); - const handleUpdate = async () => { - setLoading(true); - const result = await updateBill({ - variables: {billId: bill.id}, - }); - - if (!result.errors) { - notification["success"]({ - message: t("bills.successes.reexport"), - }); - } else { - notification["error"]({ - message: t("bills.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - //Get the owner details, populate it all back into the job. - }; - - const hasAccess = HasRbacAccess({ - bodyshop, - authLevel, - action: "bills:reexport", + const handleUpdate = async () => { + setLoading(true); + const result = await updateBill({ + variables: { billId: bill.id } }); - if (hasAccess) - return ( - - ); + if (!result.errors) { + notification["success"]({ + message: t("bills.successes.reexport") + }); + } else { + notification["error"]({ + message: t("bills.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + //Get the owner details, populate it all back into the job. + }; - return <>; + const hasAccess = HasRbacAccess({ + bodyshop, + authLevel, + action: "bills:reexport" + }); + + if (hasAccess) + return ( + + ); + + return <>; } diff --git a/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx b/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx index 5b35bd196..63d322b79 100644 --- a/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx +++ b/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx @@ -1,151 +1,136 @@ -import {FileAddFilled} from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {Button, notification, Tooltip} from "antd"; -import {t} from "i18next"; +import { FileAddFilled } from "@ant-design/icons"; +import { useMutation } from "@apollo/client"; +import { Button, notification, Tooltip } from "antd"; +import { t } from "i18next"; import dayjs from "./../../utils/day"; -import React, {useState} from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {INSERT_INVENTORY_AND_CREDIT} from "../../graphql/inventory.queries"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; -import {CalculateBillTotal} from "../bill-form/bill-form.totals.utility"; +import React, { useState } from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_INVENTORY_AND_CREDIT } from "../../graphql/inventory.queries"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility"; import queryString from "query-string"; -import {useLocation} from "react-router-dom"; +import { useLocation } from "react-router-dom"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(BilllineAddInventory); +export default connect(mapStateToProps, mapDispatchToProps)(BilllineAddInventory); -export function BilllineAddInventory({ - currentUser, - bodyshop, - billline, - disabled, - jobid, - }) { - const [loading, setLoading] = useState(false); - const {billid} = queryString.parse(useLocation().search); - const [insertInventoryLine] = useMutation(INSERT_INVENTORY_AND_CREDIT); +export function BilllineAddInventory({ currentUser, bodyshop, billline, disabled, jobid }) { + const [loading, setLoading] = useState(false); + const { billid } = queryString.parse(useLocation().search); + const [insertInventoryLine] = useMutation(INSERT_INVENTORY_AND_CREDIT); - const addToInventory = async () => { - setLoading(true); + const addToInventory = async () => { + setLoading(true); - //Check to make sure there are no existing items already in the inventory. + //Check to make sure there are no existing items already in the inventory. - const cm = { - vendorid: bodyshop.inhousevendorid, - invoice_number: "ih", - jobid: jobid, - isinhouse: true, - is_credit_memo: true, - date: dayjs().format("YYYY-MM-DD"), - federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate, - state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate, - local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate, - total: 0, - billlines: [ - { - actual_price: billline.actual_price, - actual_cost: billline.actual_cost, - quantity: billline.quantity, - line_desc: billline.line_desc, - cost_center: billline.cost_center, - deductedfromlbr: billline.deductedfromlbr, - applicable_taxes: { - local: billline.applicable_taxes.local, - state: billline.applicable_taxes.state, - federal: billline.applicable_taxes.federal, - }, - }, - ], - }; - - cm.total = CalculateBillTotal(cm).enteredTotal.getAmount() / 100; - - const insertResult = await insertInventoryLine({ - variables: { - joblineId: - billline.joblineid === "noline" ? billline.id : billline.joblineid, //This will return null as there will be no jobline that has the id of the bill line. - //Unfortunately, we can't send null as the GQL syntax validation fails. - joblineStatus: bodyshop.md_order_statuses.default_returned, - inv: { - shopid: bodyshop.id, - billlineid: billline.id, - actual_price: billline.actual_price, - actual_cost: billline.actual_cost, - quantity: billline.quantity, - line_desc: billline.line_desc, - }, - cm: {...cm, billlines: {data: cm.billlines}}, //Fix structure for apollo insert. - pol: { - returnfrombill: billid, - vendorid: bodyshop.inhousevendorid, - deliver_by: dayjs().format("YYYY-MM-DD"), - parts_order_lines: { - data: [ - { - line_desc: billline.line_desc, - - act_price: billline.actual_price, - cost: billline.actual_cost, - quantity: billline.quantity, - job_line_id: - billline.joblineid === "noline" ? null : billline.joblineid, - part_type: billline.jobline && billline.jobline.part_type, - cm_received: true, - }, - ], - }, - order_date: "2022-06-01", - orderedby: currentUser.email, - jobid: jobid, - user_email: currentUser.email, - return: true, - status: "Ordered", - }, - }, - refetchQueries: ["QUERY_BILL_BY_PK"], - }); - - if (!insertResult.errors) { - notification.open({ - type: "success", - message: t("inventory.successes.inserted"), - }); - } else { - notification.open({ - type: "error", - message: t("inventory.errors.inserting", { - error: JSON.stringify(insertResult.errors), - }), - }); + const cm = { + vendorid: bodyshop.inhousevendorid, + invoice_number: "ih", + jobid: jobid, + isinhouse: true, + is_credit_memo: true, + date: dayjs().format("YYYY-MM-DD"), + federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate, + state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate, + local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate, + total: 0, + billlines: [ + { + actual_price: billline.actual_price, + actual_cost: billline.actual_cost, + quantity: billline.quantity, + line_desc: billline.line_desc, + cost_center: billline.cost_center, + deductedfromlbr: billline.deductedfromlbr, + applicable_taxes: { + local: billline.applicable_taxes.local, + state: billline.applicable_taxes.state, + federal: billline.applicable_taxes.federal + } } - - setLoading(false); + ] }; - return ( - - - - ); + cm.total = CalculateBillTotal(cm).enteredTotal.getAmount() / 100; + + const insertResult = await insertInventoryLine({ + variables: { + joblineId: billline.joblineid === "noline" ? billline.id : billline.joblineid, //This will return null as there will be no jobline that has the id of the bill line. + //Unfortunately, we can't send null as the GQL syntax validation fails. + joblineStatus: bodyshop.md_order_statuses.default_returned, + inv: { + shopid: bodyshop.id, + billlineid: billline.id, + actual_price: billline.actual_price, + actual_cost: billline.actual_cost, + quantity: billline.quantity, + line_desc: billline.line_desc + }, + cm: { ...cm, billlines: { data: cm.billlines } }, //Fix structure for apollo insert. + pol: { + returnfrombill: billid, + vendorid: bodyshop.inhousevendorid, + deliver_by: dayjs().format("YYYY-MM-DD"), + parts_order_lines: { + data: [ + { + line_desc: billline.line_desc, + + act_price: billline.actual_price, + cost: billline.actual_cost, + quantity: billline.quantity, + job_line_id: billline.joblineid === "noline" ? null : billline.joblineid, + part_type: billline.jobline && billline.jobline.part_type, + cm_received: true + } + ] + }, + order_date: "2022-06-01", + orderedby: currentUser.email, + jobid: jobid, + user_email: currentUser.email, + return: true, + status: "Ordered" + } + }, + refetchQueries: ["QUERY_BILL_BY_PK"] + }); + + if (!insertResult.errors) { + notification.open({ + type: "success", + message: t("inventory.successes.inserted") + }); + } else { + notification.open({ + type: "error", + message: t("inventory.errors.inserting", { + error: JSON.stringify(insertResult.errors) + }) + }); + } + + setLoading(false); + }; + + return ( + + + + ); } diff --git a/client/src/components/bills-list-table/bills-list-table.component.jsx b/client/src/components/bills-list-table/bills-list-table.component.jsx index a03dea379..5db868406 100644 --- a/client/src/components/bills-list-table/bills-list-table.component.jsx +++ b/client/src/components/bills-list-table/bills-list-table.component.jsx @@ -1,236 +1,209 @@ -import {EditFilled, SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Checkbox, Input, Space, Table} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { EditFilled, SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Checkbox, Input, Space, Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {DateFormatter} from "../../utils/DateFormatter"; -import {alphaSort, dateSort} from "../../utils/sorters"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { DateFormatter } from "../../utils/DateFormatter"; +import { alphaSort, dateSort } from "../../utils/sorters"; +import { TemplateList } from "../../utils/TemplateConstants"; import BillDeleteButton from "../bill-delete-button/bill-delete-button.component"; import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; const mapStateToProps = createStructuredSelector({ - jobRO: selectJobReadOnly, - bodyshop: selectBodyshop, + jobRO: selectJobReadOnly, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setPartsOrderContext: (context) => - dispatch(setModalContext({context: context, modal: "partsOrder"})), - setBillEnterContext: (context) => - dispatch(setModalContext({context: context, modal: "billEnter"})), - setReconciliationContext: (context) => - dispatch(setModalContext({context: context, modal: "reconciliation"})), + setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })), + setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })), + setReconciliationContext: (context) => dispatch(setModalContext({ context: context, modal: "reconciliation" })) }); export function BillsListTableComponent({ - bodyshop, - jobRO, - job, - billsQuery, - handleOnRowClick, - setPartsOrderContext, - setBillEnterContext, - setReconciliationContext, - }) { - const {t} = useTranslation(); + bodyshop, + jobRO, + job, + billsQuery, + handleOnRowClick, + setPartsOrderContext, + setBillEnterContext, + setReconciliationContext +}) { + const { t } = useTranslation(); - const [state, setState] = useState({ - sortedInfo: {}, - }); - // const search = queryString.parse(useLocation().search); - // const selectedBill = search.billid; - const [searchText, setSearchText] = useState(""); + const [state, setState] = useState({ + sortedInfo: {} + }); + // const search = queryString.parse(useLocation().search); + // const selectedBill = search.billid; + const [searchText, setSearchText] = useState(""); - const Templates = TemplateList("bill"); - const bills = billsQuery.data ? billsQuery.data.bills : []; - const { refetch } = billsQuery; - const recordActions = (record, showView = false) => ( + const Templates = TemplateList("bill"); + const bills = billsQuery.data ? billsQuery.data.bills : []; + const { refetch } = billsQuery; + const recordActions = (record, showView = false) => ( + + {showView && ( + + )} + + + + {record.isinhouse && ( + + )} + + ); + const columns = [ + { + title: t("bills.fields.vendorname"), + dataIndex: "vendorname", + key: "vendorname", + sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name), + sortOrder: state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order, + render: (text, record) => {record.vendor.name} + }, + { + title: t("bills.fields.invoice_number"), + dataIndex: "invoice_number", + key: "invoice_number", + sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), + sortOrder: state.sortedInfo.columnKey === "invoice_number" && state.sortedInfo.order + }, + { + title: t("bills.fields.date"), + dataIndex: "date", + key: "date", + sorter: (a, b) => dateSort(a.date, b.date), + sortOrder: state.sortedInfo.columnKey === "date" && state.sortedInfo.order, + render: (text, record) => {record.date} + }, + { + title: t("bills.fields.total"), + dataIndex: "total", + key: "total", + sorter: (a, b) => a.total - b.total, + sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order, + render: (text, record) => {record.total} + }, + { + title: t("bills.fields.is_credit_memo"), + dataIndex: "is_credit_memo", + key: "is_credit_memo", + sorter: (a, b) => a.is_credit_memo - b.is_credit_memo, + sortOrder: state.sortedInfo.columnKey === "is_credit_memo" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: t("bills.fields.exported"), + dataIndex: "exported", + key: "exported", + sorter: (a, b) => a.exported - b.exported, + sortOrder: state.sortedInfo.columnKey === "exported" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => recordActions(record, true) + } + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + const filteredBills = bills + ? searchText === "" + ? bills + : bills.filter( + (b) => + (b.invoice_number || "").toLowerCase().includes(searchText.toLowerCase()) || + (b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) || + (b.total || "").toString().toLowerCase().includes(searchText.toLowerCase()) + ) + : []; + + return ( + - {showView && ( - - )} - - - - {record.isinhouse && ( - - )} - - ); - const columns = [ - { - title: t("bills.fields.vendorname"), - dataIndex: "vendorname", - key: "vendorname", - sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name), - sortOrder: - state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order, - render: (text, record) => {record.vendor.name}, - }, - { - title: t("bills.fields.invoice_number"), - dataIndex: "invoice_number", - key: "invoice_number", - sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), - sortOrder: - state.sortedInfo.columnKey === "invoice_number" && - state.sortedInfo.order, - }, - { - title: t("bills.fields.date"), - dataIndex: "date", - key: "date", - sorter: (a, b) => dateSort(a.date, b.date), - sortOrder: - state.sortedInfo.columnKey === "date" && state.sortedInfo.order, - render: (text, record) => {record.date}, - }, - { - title: t("bills.fields.total"), - dataIndex: "total", - key: "total", - sorter: (a, b) => a.total - b.total, - sortOrder: - state.sortedInfo.columnKey === "total" && state.sortedInfo.order, - render: (text, record) => ( - {record.total} - ), - }, - { - title: t("bills.fields.is_credit_memo"), - dataIndex: "is_credit_memo", - key: "is_credit_memo", - sorter: (a, b) => a.is_credit_memo - b.is_credit_memo, - sortOrder: - state.sortedInfo.columnKey === "is_credit_memo" && - state.sortedInfo.order, - render: (text, record) => , - }, - { - title: t("bills.fields.exported"), - dataIndex: "exported", - key: "exported", - sorter: (a, b) => a.exported - b.exported, - sortOrder: - state.sortedInfo.columnKey === "exported" && state.sortedInfo.order, - render: (text, record) => , - }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", - render: (text, record) => recordActions(record, true), - }, - ]; - - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - - const filteredBills = bills - ? searchText === "" - ? bills - : bills.filter( - (b) => - (b.invoice_number || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (b.vendor.name || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (b.total || "") - .toString() - .toLowerCase() - .includes(searchText.toLowerCase()) - ) - : []; - - return ( - - - {job && job.converted ? ( - <> - - - - ) : null} - - { - e.preventDefault(); - setSearchText(e.target.value); - }} - /> - - } - > - refetch()}> + + + {job && job.converted ? ( + <> + + + + ) : null} + + { + e.preventDefault(); + setSearchText(e.target.value); + }} + /> + + } + > +
+ + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(BillsListTableComponent); +export default connect(mapStateToProps, mapDispatchToProps)(BillsListTableComponent); diff --git a/client/src/components/bills-vendors-list/bills-vendors-list.component.jsx b/client/src/components/bills-vendors-list/bills-vendors-list.component.jsx index e6cf58cb9..433f18960 100644 --- a/client/src/components/bills-vendors-list/bills-vendors-list.component.jsx +++ b/client/src/components/bills-vendors-list/bills-vendors-list.component.jsx @@ -1,121 +1,112 @@ -import React, {useState} from "react"; -import {QUERY_ALL_VENDORS} from "../../graphql/vendors.queries"; -import {useQuery} from "@apollo/client"; +import React, { useState } from "react"; +import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries"; +import { useQuery } from "@apollo/client"; import queryString from "query-string"; -import {useLocation, useNavigate} from "react-router-dom"; -import {Input, Table} from "antd"; -import {useTranslation} from "react-i18next"; -import {alphaSort} from "../../utils/sorters"; +import { useLocation, useNavigate } from "react-router-dom"; +import { Input, Table } from "antd"; +import { useTranslation } from "react-i18next"; +import { alphaSort } from "../../utils/sorters"; import AlertComponent from "../alert/alert.component"; export default function BillsVendorsList() { - const search = queryString.parse(useLocation().search); - const history = useNavigate(); + const search = queryString.parse(useLocation().search); + const history = useNavigate(); - const {loading, error, data} = useQuery(QUERY_ALL_VENDORS, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const { loading, error, data } = useQuery(QUERY_ALL_VENDORS, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const {t} = useTranslation(); + const { t } = useTranslation(); - const [state, setState] = useState({ - sortedInfo: {}, - search: "", - }); + const [state, setState] = useState({ + sortedInfo: {}, + search: "" + }); - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - const columns = [ - { - title: t("vendors.fields.name"), - dataIndex: "name", - key: "name", - sorter: (a, b) => alphaSort(a.name, b.name), - sortOrder: - state.sortedInfo.columnKey === "name" && state.sortedInfo.order, + const columns = [ + { + title: t("vendors.fields.name"), + dataIndex: "name", + key: "name", + sorter: (a, b) => alphaSort(a.name, b.name), + sortOrder: state.sortedInfo.columnKey === "name" && state.sortedInfo.order + }, + { + title: t("vendors.fields.cost_center"), + dataIndex: "cost_center", + key: "cost_center", + sorter: (a, b) => alphaSort(a.cost_center, b.cost_center), + sortOrder: state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order + }, + { + title: t("vendors.fields.city"), + dataIndex: "city", + key: "city" + } + ]; + + const handleOnRowClick = (record) => { + if (record) { + delete search.billid; + if (record.id) { + search.vendorid = record.id; + history.push({ search: queryString.stringify(search) }); + } + } else { + delete search.vendorid; + history.push({ search: queryString.stringify(search) }); + } + }; + + const handleSearch = (e) => { + setState({ ...state, search: e.target.value }); + }; + + if (error) return ; + + const dataSource = state.search + ? data.vendors.filter( + (v) => + (v.name || "").toLowerCase().includes(state.search.toLowerCase()) || + (v.cost_center || "").toLowerCase().includes(state.search.toLowerCase()) || + (v.city || "").toLowerCase().includes(state.search.toLowerCase()) + ) + : (data && data.vendors) || []; + + return ( +
{ + return ( +
+ +
+ ); + }} + dataSource={dataSource} + pagination={{ position: "top" }} + columns={columns} + rowKey="id" + onChange={handleTableChange} + rowSelection={{ + onSelect: (record) => { + handleOnRowClick(record); }, - { - title: t("vendors.fields.cost_center"), - dataIndex: "cost_center", - key: "cost_center", - sorter: (a, b) => alphaSort(a.cost_center, b.cost_center), - sortOrder: - state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order, - }, - { - title: t("vendors.fields.city"), - dataIndex: "city", - key: "city", - }, - ]; - - const handleOnRowClick = (record) => { - if (record) { - delete search.billid; - if (record.id) { - search.vendorid = record.id; - history.push({search: queryString.stringify(search)}); - } - } else { - delete search.vendorid; - history.push({search: queryString.stringify(search)}); - } - }; - - const handleSearch = (e) => { - setState({...state, search: e.target.value}); - }; - - if (error) return ; - - const dataSource = state.search - ? data.vendors.filter( - (v) => - (v.name || "").toLowerCase().includes(state.search.toLowerCase()) || - (v.cost_center || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (v.city || "").toLowerCase().includes(state.search.toLowerCase()) - ) - : (data && data.vendors) || []; - - return ( -
{ - return ( -
- -
- ); - }} - dataSource={dataSource} - pagination={{position: "top"}} - columns={columns} - rowKey="id" - onChange={handleTableChange} - rowSelection={{ - onSelect: (record) => { - handleOnRowClick(record); - }, - selectedRowKeys: [search.vendorid], - type: "radio", - }} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - handleOnRowClick(record); - }, // click row - }; - }} - /> - ); + selectedRowKeys: [search.vendorid], + type: "radio" + }} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + handleOnRowClick(record); + } // click row + }; + }} + /> + ); } diff --git a/client/src/components/breadcrumbs/breadcrumbs.component.jsx b/client/src/components/breadcrumbs/breadcrumbs.component.jsx index 360eb480d..93a9d0192 100644 --- a/client/src/components/breadcrumbs/breadcrumbs.component.jsx +++ b/client/src/components/breadcrumbs/breadcrumbs.component.jsx @@ -1,64 +1,63 @@ -import {HomeFilled} from "@ant-design/icons"; -import {Breadcrumb, Col, Row} from "antd"; +import { HomeFilled } from "@ant-design/icons"; +import { Breadcrumb, Col, Row } from "antd"; import React from "react"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {selectBreadcrumbs} from "../../redux/application/application.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { selectBreadcrumbs } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import GlobalSearch from "../global-search/global-search.component"; import GlobalSearchOs from "../global-search/global-search-os.component"; import "./breadcrumbs.styles.scss"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - breadcrumbs: selectBreadcrumbs, - bodyshop: selectBodyshop, + breadcrumbs: selectBreadcrumbs, + bodyshop: selectBodyshop }); -export function BreadCrumbs({breadcrumbs, bodyshop}) { - - const {treatments: {OpenSearch}} = useSplitTreatments({ - attributes: {}, - names: ["OpenSearch"], - splitKey: bodyshop && bodyshop.imexshopid, - }); - // TODO - Client Update - Technically key is not doing anything here - return ( - - - - {" "} - {(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) || - ""} - - ), - }, - ...breadcrumbs.map((item) => - item.link - ? { - key: item.label, - title: {item.label}, - } - : { - key: item.label, - title: item.label, - } - ), - ]} - /> - - - {OpenSearch.treatment === "on" ? : } - - - ); +export function BreadCrumbs({ breadcrumbs, bodyshop }) { + const { + treatments: { OpenSearch } + } = useSplitTreatments({ + attributes: {}, + names: ["OpenSearch"], + splitKey: bodyshop && bodyshop.imexshopid + }); + // TODO - Client Update - Technically key is not doing anything here + return ( + + + + {(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) || ""} + + ) + }, + ...breadcrumbs.map((item) => + item.link + ? { + key: item.label, + title: {item.label} + } + : { + key: item.label, + title: item.label + } + ) + ]} + /> + + + {OpenSearch.treatment === "on" ? : } + + + ); } export default connect(mapStateToProps, null)(BreadCrumbs); diff --git a/client/src/components/ca-bc-etf-table-modal/ca-bc-etf-table-modal.container.jsx b/client/src/components/ca-bc-etf-table-modal/ca-bc-etf-table-modal.container.jsx index 04a49ca36..04c49e2db 100644 --- a/client/src/components/ca-bc-etf-table-modal/ca-bc-etf-table-modal.container.jsx +++ b/client/src/components/ca-bc-etf-table-modal/ca-bc-etf-table-modal.container.jsx @@ -1,99 +1,87 @@ -import {Button, Form, Modal} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectCaBcEtfTableConvert} from "../../redux/modals/modals.selectors"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { Button, Form, Modal } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectCaBcEtfTableConvert } from "../../redux/modals/modals.selectors"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; import CaBcEtfTableModalComponent from "./ca-bc-etf-table.modal.component"; const mapStateToProps = createStructuredSelector({ - caBcEtfTableModal: selectCaBcEtfTableConvert, + caBcEtfTableModal: selectCaBcEtfTableConvert }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => - dispatch(toggleModalVisible("ca_bc_eftTableConvert")), + toggleModalVisible: () => dispatch(toggleModalVisible("ca_bc_eftTableConvert")) }); -export function ContractsFindModalContainer({ - caBcEtfTableModal, - toggleModalVisible, - }) { - const {t} = useTranslation(); +export function ContractsFindModalContainer({ caBcEtfTableModal, toggleModalVisible }) { + const { t } = useTranslation(); - const {open} = caBcEtfTableModal; - const [loading, setLoading] = useState(false); - const [form] = Form.useForm(); - const EtfTemplate = TemplateList("special").ca_bc_etf_table; + const { open } = caBcEtfTableModal; + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + const EtfTemplate = TemplateList("special").ca_bc_etf_table; - const handleFinish = async (values) => { - logImEXEvent("ca_bc_etf_table_parse"); - setLoading(true); - const claimNumbers = []; - values.table.split("\n").forEach((row, idx, arr) => { - const {1: claim, 2: shortclaim, 4: amount} = row.split("\t"); - if (!claim || !shortclaim) return; - const trimmedShortClaim = shortclaim.trim(); - // const trimmedClaim = claim.trim(); - if (amount.slice(-1) === "-") { - } + const handleFinish = async (values) => { + logImEXEvent("ca_bc_etf_table_parse"); + setLoading(true); + const claimNumbers = []; + values.table.split("\n").forEach((row, idx, arr) => { + const { 1: claim, 2: shortclaim, 4: amount } = row.split("\t"); + if (!claim || !shortclaim) return; + const trimmedShortClaim = shortclaim.trim(); + // const trimmedClaim = claim.trim(); + if (amount.slice(-1) === "-") { + } - claimNumbers.push({ - claim: trimmedShortClaim, - amount: amount.slice(-1) === "-" ? parseFloat(amount) * -1 : amount, - }); - }); + claimNumbers.push({ + claim: trimmedShortClaim, + amount: amount.slice(-1) === "-" ? parseFloat(amount) * -1 : amount + }); + }); - await GenerateDocument( - { - name: EtfTemplate.key, - variables: { - claimNumbers: `%(${claimNumbers.map((c) => c.claim).join("|")})%`, - claimdata: claimNumbers, - }, - }, - {}, - values.sendby === "email" ? "e" : "p" - ); - setLoading(false); - }; - - useEffect(() => { - if (open) { - form.resetFields(); + await GenerateDocument( + { + name: EtfTemplate.key, + variables: { + claimNumbers: `%(${claimNumbers.map((c) => c.claim).join("|")})%`, + claimdata: claimNumbers } - }, [open, form]); - - return ( - toggleModalVisible()} - onOk={() => toggleModalVisible()} - destroyOnClose - forceRender - > -
- - - -
+ }, + {}, + values.sendby === "email" ? "e" : "p" ); + setLoading(false); + }; + + useEffect(() => { + if (open) { + form.resetFields(); + } + }, [open, form]); + + return ( + toggleModalVisible()} + onOk={() => toggleModalVisible()} + destroyOnClose + forceRender + > +
+ + + +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ContractsFindModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(ContractsFindModalContainer); diff --git a/client/src/components/ca-bc-etf-table-modal/ca-bc-etf-table.modal.component.jsx b/client/src/components/ca-bc-etf-table-modal/ca-bc-etf-table.modal.component.jsx index 67659eaa6..e9224ae6b 100644 --- a/client/src/components/ca-bc-etf-table-modal/ca-bc-etf-table.modal.component.jsx +++ b/client/src/components/ca-bc-etf-table-modal/ca-bc-etf-table.modal.component.jsx @@ -1,42 +1,38 @@ -import {Form, Input, Radio} from "antd"; +import { Form, Input, Radio } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); export default connect(mapStateToProps, null)(PartsReceiveModalComponent); -export function PartsReceiveModalComponent({bodyshop, form}) { - const {t} = useTranslation(); +export function PartsReceiveModalComponent({ bodyshop, form }) { + const { t } = useTranslation(); - return ( -
- - - - - - {t("general.labels.email")} - {t("general.labels.print")} - - -
- ); + return ( +
+ + + + + + {t("general.labels.email")} + {t("general.labels.print")} + + +
+ ); } diff --git a/client/src/components/ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component.jsx b/client/src/components/ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component.jsx index 1cb2f3385..cbdd26770 100644 --- a/client/src/components/ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component.jsx +++ b/client/src/components/ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component.jsx @@ -1,50 +1,45 @@ -import React, {useState} from "react"; -import {Button, Form, InputNumber, Popover} from "antd"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {useTranslation} from "react-i18next"; -import {CalculatorFilled} from "@ant-design/icons"; +import React, { useState } from "react"; +import { Button, Form, InputNumber, Popover } from "antd"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { useTranslation } from "react-i18next"; +import { CalculatorFilled } from "@ant-design/icons"; -export default function CABCpvrtCalculator({disabled, form}) { - const [visibility, setVisibility] = useState(false); +export default function CABCpvrtCalculator({ disabled, form }) { + const [visibility, setVisibility] = useState(false); - const {t} = useTranslation(); + const { t } = useTranslation(); - const handleFinish = async (values) => { - logImEXEvent("job_ca_bc_pvrt_calculate"); - form.setFieldsValue({ - ca_bc_pvrt: ((values.rate || 0) * (values.days || 0)).toFixed(2), - }); - form.setFields([{name: "ca_bc_pvrt", touched: true}]); - setVisibility(false); - }; + const handleFinish = async (values) => { + logImEXEvent("job_ca_bc_pvrt_calculate"); + form.setFieldsValue({ + ca_bc_pvrt: ((values.rate || 0) * (values.days || 0)).toFixed(2) + }); + form.setFields([{ name: "ca_bc_pvrt", touched: true }]); + setVisibility(false); + }; - const popContent = ( -
-
- - - - - - - - - -
- ); + const popContent = ( +
+
+ + + + + + + + + +
+ ); - return ( - - - - ); + return ( + + + + ); } diff --git a/client/src/components/card-payment-modal/card-payment-modal.component..jsx b/client/src/components/card-payment-modal/card-payment-modal.component..jsx index 581fe7334..bec81dd2e 100644 --- a/client/src/components/card-payment-modal/card-payment-modal.component..jsx +++ b/client/src/components/card-payment-modal/card-payment-modal.component..jsx @@ -1,357 +1,328 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {useLazyQuery, useMutation} from "@apollo/client"; -import {Button, Card, Col, Form, Input, notification, Row, Space, Spin, Statistic,} from "antd"; +import { DeleteFilled } from "@ant-design/icons"; +import { useLazyQuery, useMutation } from "@apollo/client"; +import { Button, Card, Col, Form, Input, notification, Row, Space, Spin, Statistic } from "antd"; import axios from "axios"; import dayjs from "../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {INSERT_PAYMENT_RESPONSE, QUERY_RO_AND_OWNER_BY_JOB_PKS,} from "../../graphql/payment_response.queries"; -import {INSERT_NEW_PAYMENT} from "../../graphql/payments.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectCardPayment} from "../../redux/modals/modals.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_PAYMENT_RESPONSE, QUERY_RO_AND_OWNER_BY_JOB_PKS } from "../../graphql/payment_response.queries"; +import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectCardPayment } from "../../redux/modals/modals.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component"; import JobSearchSelectComponent from "../job-search-select/job-search-select.component"; const mapStateToProps = createStructuredSelector({ - cardPaymentModal: selectCardPayment, - bodyshop: selectBodyshop, + cardPaymentModal: selectCardPayment, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type})), - toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })), + toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")) }); -const CardPaymentModalComponent = ({ - bodyshop, - cardPaymentModal, - toggleModalVisible, - insertAuditTrail, - }) => { - const {context} = cardPaymentModal; +const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisible, insertAuditTrail }) => { + const { context } = cardPaymentModal; - const [form] = Form.useForm(); + const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - const [insertPayment] = useMutation(INSERT_NEW_PAYMENT); - const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE); - const {t} = useTranslation(); + const [loading, setLoading] = useState(false); + const [insertPayment] = useMutation(INSERT_NEW_PAYMENT); + const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE); + const { t } = useTranslation(); - const [, {data, refetch, queryLoading}] = useLazyQuery( - QUERY_RO_AND_OWNER_BY_JOB_PKS, - { - variables: {jobids: [context.jobid]}, - skip: true, + const [, { data, refetch, queryLoading }] = useLazyQuery(QUERY_RO_AND_OWNER_BY_JOB_PKS, { + variables: { jobids: [context.jobid] }, + skip: true + }); + + console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data); + //Initialize the intellipay window. + const SetIntellipayCallbackFunctions = () => { + console.log("*** Set IntelliPay callback functions."); + window.intellipay.runOnClose(() => { + //window.intellipay.initialize(); + }); + + window.intellipay.runOnApproval(async function (response) { + console.warn("*** Running On Approval Script ***"); + form.setFieldValue("paymentResponse", response); + form.submit(); + }); + + window.intellipay.runOnNonApproval(async function (response) { + // Mutate unsuccessful payment + + const { payments } = form.getFieldsValue(); + + await insertPaymentResponse({ + variables: { + paymentResponse: payments.map((payment) => ({ + amount: payment.amount, + bodyshopid: bodyshop.id, + jobid: payment.jobid, + declinereason: response.declinereason, + ext_paymentid: response.paymentid.toString(), + successful: false, + response + })) } - ); + }); - console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data); - //Initialize the intellipay window. - const SetIntellipayCallbackFunctions = () => { - console.log("*** Set IntelliPay callback functions."); - window.intellipay.runOnClose(() => { - //window.intellipay.initialize(); - }); + payments.forEach((payment) => + insertAuditTrail({ + jobid: payment.jobid, + operation: AuditTrailMapping.failedpayment(), + type: "failedpayment" + }) + ); + }); + }; - window.intellipay.runOnApproval(async function (response) { - console.warn("*** Running On Approval Script ***"); - form.setFieldValue("paymentResponse", response); - form.submit(); - }); + const handleFinish = async (values) => { + try { + await insertPayment({ + variables: { + paymentInput: values.payments.map((payment) => ({ + amount: payment.amount, + transactionid: (values.paymentResponse.paymentid || "").toString(), + payer: t("payments.labels.customer"), + type: values.paymentResponse.cardbrand, + jobid: payment.jobid, + date: dayjs(Date.now()), + payment_responses: { + data: [ + { + amount: payment.amount, + bodyshopid: bodyshop.id, - window.intellipay.runOnNonApproval(async function (response) { - // Mutate unsuccessful payment - - const {payments} = form.getFieldsValue(); - - await insertPaymentResponse({ - variables: { - paymentResponse: payments.map((payment) => ({ - amount: payment.amount, - bodyshopid: bodyshop.id, - jobid: payment.jobid, - declinereason: response.declinereason, - ext_paymentid: response.paymentid.toString(), - successful: false, - response, - })), - }, - }); - - payments.forEach((payment) => - insertAuditTrail({ - jobid: payment.jobid, - operation: AuditTrailMapping.failedpayment(), - type: "failedpayment",}) - ); - }); - }; - - const handleFinish = async (values) => { - try { - await insertPayment({ - variables: { - paymentInput: values.payments.map((payment) => ({ - amount: payment.amount, - transactionid: (values.paymentResponse.paymentid || "").toString(), - payer: t("payments.labels.customer"), - type: values.paymentResponse.cardbrand, - jobid: payment.jobid, - date: dayjs(Date.now()), - payment_responses: { - data: [ - { - amount: payment.amount, - bodyshopid: bodyshop.id, - - jobid: payment.jobid, - declinereason: values.paymentResponse.declinereason, - ext_paymentid: values.paymentResponse.paymentid.toString(), - successful: true, - response: values.paymentResponse, - }, - ], - }, - })), - }, - refetchQueries: ["GET_JOB_BY_PK"], - }); - toggleModalVisible(); - } catch (error) { - console.error(error); - notification.open({ - type: "error", - message: t("payments.errors.inserting", {error: error.message}), - }); - } finally { - setLoading(false); - } - }; - - const handleIntelliPayCharge = async () => { - setLoading(true); - - //Validate - try { - await form.validateFields(); - } catch (error) { - setLoading(false); - return; - } - - try { - const response = await axios.post("/intellipay/lightbox_credentials", { - bodyshop, - refresh: !!window.intellipay, - }); - - if (window.intellipay) { - // eslint-disable-next-line no-eval - eval(response.data); - SetIntellipayCallbackFunctions(); - window.intellipay.autoOpen(); - } else { - var rg = document.createRange(); - let node = rg.createContextualFragment(response.data); - document.documentElement.appendChild(node); - SetIntellipayCallbackFunctions(); - window.intellipay.isAutoOpen = true; - window.intellipay.initialize(); + jobid: payment.jobid, + declinereason: values.paymentResponse.declinereason, + ext_paymentid: values.paymentResponse.paymentid.toString(), + successful: true, + response: values.paymentResponse + } + ] } - } catch (error) { - notification.open({ - type: "error", - message: t("job_payments.notifications.error.openingip"), - }); - setLoading(false); - } - }; + })) + }, + refetchQueries: ["GET_JOB_BY_PK"] + }); + toggleModalVisible(); + } catch (error) { + console.error(error); + notification.open({ + type: "error", + message: t("payments.errors.inserting", { error: error.message }) + }); + } finally { + setLoading(false); + } + }; - return ( - - -
- - {(fields, {add, remove, move}) => { - return ( -
- {fields.map((field, index) => ( - - -
- - - - - - - - - - - { - remove(field.name); - }} - /> - - - - ))} - - - - - ); - }} - + const handleIntelliPayCharge = async () => { + setLoading(true); - - prevValues.payments?.map((p) => p?.jobid).join() !== - curValues.payments?.map((p) => p?.jobid).join() - } + //Validate + try { + await form.validateFields(); + } catch (error) { + setLoading(false); + return; + } + + try { + const response = await axios.post("/intellipay/lightbox_credentials", { + bodyshop, + refresh: !!window.intellipay + }); + + if (window.intellipay) { + // eslint-disable-next-line no-eval + eval(response.data); + SetIntellipayCallbackFunctions(); + window.intellipay.autoOpen(); + } else { + var rg = document.createRange(); + let node = rg.createContextualFragment(response.data); + document.documentElement.appendChild(node); + SetIntellipayCallbackFunctions(); + window.intellipay.isAutoOpen = true; + window.intellipay.initialize(); + } + } catch (error) { + notification.open({ + type: "error", + message: t("job_payments.notifications.error.openingip") + }); + setLoading(false); + } + }; + + return ( + + + + + {(fields, { add, remove, move }) => { + return ( +
+ {fields.map((field, index) => ( + + +
+ + + + + + + + + + + { + remove(field.name); + }} + /> + + + + ))} + + + + + ); + }} + - return ( - - - - - - ); - }} - + + prevValues.payments?.map((p) => p?.jobid).join() !== curValues.payments?.map((p) => p?.jobid).join() + } + > + {() => { + console.log("Updating the owner info section."); + //If all of the job ids have been fileld in, then query and update the IP field. + const { payments } = form.getFieldsValue(); + if (payments?.length > 0 && payments?.filter((p) => p?.jobid).length === payments?.length) { + console.log("**Calling refetch."); + refetch({ jobids: payments.map((p) => p.jobid) }); + } + console.log( + "Acc info", + data, + payments && data && data.jobs.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null + ); + return ( + <> + 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null + } + /> + 0 ? data.jobs.filter((j) => j.ownr_ea)[0]?.ownr_ea : null + } + /> + + ); + }} + + + prevValues.payments?.map((p) => p?.amount).join() !== curValues.payments?.map((p) => p?.amount).join() + } + > + {() => { + const { payments } = form.getFieldsValue(); + const totalAmountToCharge = payments?.reduce((acc, val) => { + return acc + (val?.amount || 0); + }, 0); - {/* Lightbox payment response when it is completed */} - - - - - ); + return ( + + + + + + ); + }} + + + {/* Lightbox payment response when it is completed */} + + + + + ); }; -export default connect( - mapStateToProps, - mapDispatchToProps -)(CardPaymentModalComponent); +export default connect(mapStateToProps, mapDispatchToProps)(CardPaymentModalComponent); diff --git a/client/src/components/card-payment-modal/card-payment-modal.container..jsx b/client/src/components/card-payment-modal/card-payment-modal.container..jsx index 4984ddce5..2f56615bd 100644 --- a/client/src/components/card-payment-modal/card-payment-modal.container..jsx +++ b/client/src/components/card-payment-modal/card-payment-modal.container..jsx @@ -1,57 +1,50 @@ -import {Button, Modal} from "antd"; +import { Button, Modal } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectCardPayment} from "../../redux/modals/modals.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectCardPayment } from "../../redux/modals/modals.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CardPaymentModalComponent from "./card-payment-modal.component."; const mapStateToProps = createStructuredSelector({ - cardPaymentModal: selectCardPayment, - bodyshop: selectBodyshop, + cardPaymentModal: selectCardPayment, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")), + toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")) }); -function CardPaymentModalContainer({ - cardPaymentModal, - toggleModalVisible, - bodyshop, - }) { - const {open} = cardPaymentModal; - const {t} = useTranslation(); +function CardPaymentModalContainer({ cardPaymentModal, toggleModalVisible, bodyshop }) { + const { open } = cardPaymentModal; + const { t } = useTranslation(); - const handleCancel = () => { - toggleModalVisible(); - }; + const handleCancel = () => { + toggleModalVisible(); + }; - const handleOK = () => { - toggleModalVisible(); - }; + const handleOK = () => { + toggleModalVisible(); + }; - return ( - - {t("job_payments.buttons.goback")} - , - ]} - width="80%" - destroyOnClose - > - - - ); + return ( + + {t("job_payments.buttons.goback")} + + ]} + width="80%" + destroyOnClose + > + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(CardPaymentModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(CardPaymentModalContainer); diff --git a/client/src/components/chat-affix/chat-affix.container.jsx b/client/src/components/chat-affix/chat-affix.container.jsx index d82115e72..0541cd167 100644 --- a/client/src/components/chat-affix/chat-affix.container.jsx +++ b/client/src/components/chat-affix/chat-affix.container.jsx @@ -1,100 +1,97 @@ -import {useApolloClient} from "@apollo/client"; -import {getToken, onMessage} from "@firebase/messaging"; -import {Button, notification, Space} from "antd"; +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 {useTranslation} from "react-i18next"; -import {messaging, requestForToken} from "../../firebase/firebase.utils"; +import React, { 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"; -export function ChatAffixContainer({bodyshop, chatVisible}) { - const {t} = useTranslation(); - const client = useApolloClient(); - - useEffect(() => { - if (!bodyshop || !bodyshop.messagingservicesid) return; +export function ChatAffixContainer({ bodyshop, chatVisible }) { + const { t } = useTranslation(); + const client = useApolloClient(); - 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: ( - - - - - ), - }); - } - } + useEffect(() => { + if (!bodyshop || !bodyshop.messagingservicesid) return; - SubscribeToTopic(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [bodyshop]); + 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: ( + + + + + ) + }); + } + } - useEffect(() => { - function handleMessage(payload) { - FcmHandler({ - client, - payload: (payload && payload.data && payload.data.data) || payload.data, - }); - } + SubscribeToTopic(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [bodyshop]); - 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]); + useEffect(() => { + function handleMessage(payload) { + FcmHandler({ + client, + payload: (payload && payload.data && payload.data.data) || payload.data + }); + } - if (!bodyshop || !bodyshop.messagingservicesid) return <>; + 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]); - return ( -
- {bodyshop && bodyshop.messagingservicesid ? : null} -
- ); + if (!bodyshop || !bodyshop.messagingservicesid) return <>; + + return ( +
+ {bodyshop && bodyshop.messagingservicesid ? : null} +
+ ); } export default ChatAffixContainer; 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 d7233d2cf..755e8f514 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,29 +1,27 @@ -import {useMutation} from "@apollo/client"; -import {Button} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {TOGGLE_CONVERSATION_ARCHIVE} from "../../graphql/conversations.queries"; +import { useMutation } from "@apollo/client"; +import { Button } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries"; -export default function ChatArchiveButton({conversation}) { - const [loading, setLoading] = useState(false); - const {t} = useTranslation(); - const [updateConversation] = useMutation(TOGGLE_CONVERSATION_ARCHIVE); - const handleToggleArchive = async () => { - setLoading(true); +export default function ChatArchiveButton({ conversation }) { + const [loading, setLoading] = useState(false); + const { t } = useTranslation(); + const [updateConversation] = useMutation(TOGGLE_CONVERSATION_ARCHIVE); + const handleToggleArchive = async () => { + setLoading(true); - await updateConversation({ - variables: {id: conversation.id, archived: !conversation.archived}, - refetchQueries: ["CONVERSATION_LIST_QUERY"], - }); + await updateConversation({ + variables: { id: conversation.id, archived: !conversation.archived }, + refetchQueries: ["CONVERSATION_LIST_QUERY"] + }); - setLoading(false); - }; + setLoading(false); + }; - return ( - - ); + return ( + + ); } 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 d498949cf..a49c3b4b4 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,122 +1,101 @@ -import {Badge, Card, List, Space, Tag} from "antd"; +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 {createStructuredSelector} from "reselect"; -import {setSelectedConversation} from "../../redux/messaging/messaging.actions"; -import {selectSelectedConversation} from "../../redux/messaging/messaging.selectors"; -import {TimeAgoFormatter} from "../../utils/DateFormatter"; +import { connect } from "react-redux"; +import { AutoSizer, CellMeasurer, CellMeasurerCache, List as VirtualizedList } from "react-virtualized"; +import { createStructuredSelector } from "reselect"; +import { setSelectedConversation } from "../../redux/messaging/messaging.actions"; +import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; +import { TimeAgoFormatter } from "../../utils/DateFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter"; -import {OwnerNameDisplayFunction} from "../owner-name-display/owner-name-display.component"; +import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import _ from "lodash"; import "./chat-conversation-list.styles.scss"; const mapStateToProps = createStructuredSelector({ - selectedConversation: selectSelectedConversation, + selectedConversation: selectSelectedConversation }); const mapDispatchToProps = (dispatch) => ({ - setSelectedConversation: (conversationId) => - dispatch(setSelectedConversation(conversationId)), + setSelectedConversation: (conversationId) => dispatch(setSelectedConversation(conversationId)) }); function ChatConversationListComponent({ - conversationList, - selectedConversation, - setSelectedConversation, - loadMoreConversations, - }) { - const cache = new CellMeasurerCache({ - fixedWidth: true, - defaultHeight: 60, - }); + conversationList, + selectedConversation, + setSelectedConversation, + loadMoreConversations +}) { + const cache = new CellMeasurerCache({ + fixedWidth: true, + defaultHeight: 60 + }); - const rowRenderer = ({index, key, style, parent}) => { - const item = conversationList[index]; - const cardContentRight = - {item.updated_at}; - const cardContentLeft = item.job_conversations.length > 0 - ? item.job_conversations.map((j, idx) => ( - {j.job.ro_number} - )) - : null; + const rowRenderer = ({ index, key, style, parent }) => { + const item = conversationList[index]; + const cardContentRight = {item.updated_at}; + const cardContentLeft = + item.job_conversations.length > 0 + ? item.job_conversations.map((j, idx) => {j.job.ro_number}) + : null; - const names = <>{_.uniq(item.job_conversations.map((j, idx) => - OwnerNameDisplayFunction(j.job) - ))} + const names = <>{_.uniq(item.job_conversations.map((j, idx) => OwnerNameDisplayFunction(j.job)))}; - const cardTitle = <> - {item.label && {item.label}} - {item.job_conversations.length > 0 ? ( - - {names} - - ) : ( - - {item.phone_num} - - )} - - const cardExtra = + const cardTitle = ( + <> + {item.label && {item.label}} + {item.job_conversations.length > 0 ? ( + {names} + ) : ( + + {item.phone_num} + + )} + + ); + const cardExtra = ; - const getCardStyle = () => - item.id === selectedConversation - ? {backgroundColor: 'rgba(128, 128, 128, 0.2)'} - : {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}
-
-
-
- ); - }; + const getCardStyle = () => + item.id === selectedConversation + ? { backgroundColor: "rgba(128, 128, 128, 0.2)" } + : { backgroundColor: index % 2 === 0 ? "#f0f2f5" : "#ffffff" }; return ( -
- - {({height, width}) => ( - { - if (scrollTop + clientHeight === scrollHeight) { - loadMoreConversations(); - } - }} - /> - )} - -
+ + setSelectedConversation(item.id)} + style={style} + className={`chat-list-item + ${item.id === selectedConversation ? "chat-list-selected-conversation" : null}`} + > + +
{cardContentLeft}
+
{cardContentRight}
+
+
+
); + }; + + return ( +
+ + {({ height, width }) => ( + { + if (scrollTop + clientHeight === scrollHeight) { + loadMoreConversations(); + } + }} + /> + )} + +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ChatConversationListComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationListComponent); 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 bdeb99ae2..d851e6c92 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,56 +1,56 @@ -import {useMutation} from "@apollo/client"; -import {Tag} from "antd"; +import { useMutation } from "@apollo/client"; +import { Tag } from "antd"; import React from "react"; -import {Link} from "react-router-dom"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {REMOVE_CONVERSATION_TAG} from "../../graphql/job-conversations.queries"; +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"; -export default function ChatConversationTitleTags({jobConversations}) { - const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG); +export default function ChatConversationTitleTags({ jobConversations }) { + const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG); - const handleRemoveTag = (jobId) => { - const convId = jobConversations[0].conversationid; - if (!!convId) { - removeJobConversation({ - variables: { - conversationId: convId, - jobId: jobId, - }, - update(cache) { - cache.modify({ - id: cache.identify({id: convId, __typename: "conversations"}), - fields: { - job_conversations(ex) { - return ex.filter((e) => e.jobid !== jobId); - }, - }, - }); - }, - }); - logImEXEvent("messaging_remove_job_tag", { - conversationId: convId, - jobId: jobId, - }); + const handleRemoveTag = (jobId) => { + const convId = jobConversations[0].conversationid; + if (!!convId) { + removeJobConversation({ + variables: { + conversationId: convId, + jobId: jobId + }, + update(cache) { + cache.modify({ + id: cache.identify({ id: convId, __typename: "conversations" }), + fields: { + job_conversations(ex) { + return ex.filter((e) => e.jobid !== jobId); + } + } + }); } - }; + }); + logImEXEvent("messaging_remove_job_tag", { + conversationId: convId, + jobId: jobId + }); + } + }; - return ( -
- {jobConversations.map((item) => ( - handleRemoveTag(item.job.id)} - > - - {`${item.job.ro_number || "?"} | `} - - - - ))} -
- ); + return ( +
+ {jobConversations.map((item) => ( + handleRemoveTag(item.job.id)} + > + + {`${item.job.ro_number || "?"} | `} + + + + ))} +
+ ); } 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 bdc1a35a3..41cf0b441 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 @@ -1,4 +1,4 @@ -import {Space} from "antd"; +import { Space } from "antd"; import React from "react"; import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component"; @@ -7,21 +7,15 @@ 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}) { - return ( - - - {conversation && conversation.phone_num} - - - - - - - - ); +export default function ChatConversationTitle({ conversation }) { + 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 7e11bef10..d4a9695c0 100644 --- a/client/src/components/chat-conversation/chat-conversation.component.jsx +++ b/client/src/components/chat-conversation/chat-conversation.component.jsx @@ -6,26 +6,21 @@ 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, - }) { - const [loading, error] = subState; +export default function ChatConversationComponent({ subState, conversation, messages, handleMarkConversationAsRead }) { + const [loading, error] = subState; - if (loading) return ; - if (error) return ; + if (loading) return ; + if (error) return ; - return ( -
- - - -
- ); + return ( +
+ + + +
+ ); } diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index d989fe514..2b91d2320 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -1,87 +1,81 @@ -import {useMutation, useQuery, useSubscription} from "@apollo/client"; -import React, {useState} from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {CONVERSATION_SUBSCRIPTION_BY_PK, 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 { useMutation, useQuery, useSubscription } from "@apollo/client"; +import React, { useState } from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { CONVERSATION_SUBSCRIPTION_BY_PK, 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 { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - selectedConversation: selectSelectedConversation, - bodyshop: selectBodyshop, + selectedConversation: selectSelectedConversation, + bodyshop: selectBodyshop }); export default connect(mapStateToProps, null)(ChatConversationContainer); -export function ChatConversationContainer({bodyshop, selectedConversation}) { - const { - loading: convoLoading, - error: convoError, - data: convoData, - } = useQuery(GET_CONVERSATION_DETAILS, { - variables: {conversationId: selectedConversation}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); +export function ChatConversationContainer({ bodyshop, selectedConversation }) { + const { + loading: convoLoading, + error: convoError, + data: convoData + } = useQuery(GET_CONVERSATION_DETAILS, { + variables: { conversationId: selectedConversation }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const {loading, error, data} = useSubscription( - CONVERSATION_SUBSCRIPTION_BY_PK, - { - variables: {conversationId: selectedConversation}, + 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 [markingAsReadInProgress, setMarkingAsReadInProgress] = useState(false); + const unreadCount = + data && + data.messages && + data.messages.reduce((acc, val) => { + return !val.read && !val.isoutbound ? acc + 1 : acc; + }, 0); - 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 handleMarkConversationAsRead = async () => { + if (unreadCount > 0 && !!selectedConversation && !markingAsReadInProgress) { + setMarkingAsReadInProgress(true); + await markConversationRead({}); + await axios.post("/sms/markConversationRead", { + conversationid: selectedConversation, + imexshopid: bodyshop.imexshopid + }); + setMarkingAsReadInProgress(false); + } + }; - const unreadCount = - data && - data.messages && - data.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 axios.post("/sms/markConversationRead", { - conversationid: selectedConversation, - imexshopid: bodyshop.imexshopid, - }); - setMarkingAsReadInProgress(false); - } - }; - - return ( - - ); + return ( + + ); } diff --git a/client/src/components/chat-label/chat-label.component.jsx b/client/src/components/chat-label/chat-label.component.jsx index 95cb1af4f..7157d02c8 100644 --- a/client/src/components/chat-label/chat-label.component.jsx +++ b/client/src/components/chat-label/chat-label.component.jsx @@ -1,68 +1,59 @@ -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 {useTranslation} from "react-i18next"; -import {UPDATE_CONVERSATION_LABEL} from "../../graphql/conversations.queries"; +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 { useTranslation } from "react-i18next"; +import { UPDATE_CONVERSATION_LABEL } from "../../graphql/conversations.queries"; -export default function ChatLabel({conversation}) { - const [loading, setLoading] = useState(false); - const [editing, setEditing] = useState(false); - const [value, setValue] = useState(conversation.label); +export default function ChatLabel({ conversation }) { + const [loading, setLoading] = useState(false); + const [editing, setEditing] = useState(false); + const [value, setValue] = useState(conversation.label); - const {t} = useTranslation(); - const [updateLabel] = useMutation(UPDATE_CONVERSATION_LABEL); + const { t } = useTranslation(); + const [updateLabel] = useMutation(UPDATE_CONVERSATION_LABEL); - const handleSave = async () => { - setLoading(true); - try { - const response = await updateLabel({ - variables: {id: conversation.id, label: value}, - }); - if (response.errors) { - notification["error"]({ - message: t("messages.errors.updatinglabel", { - error: JSON.stringify(response.errors), - }), - }); - } else { - setEditing(false); - } - } catch (error) { - notification["error"]({ - message: t("messages.errors.updatinglabel", { - error: JSON.stringify(error), - }), - }); - } finally { - setLoading(false); - } - }; - if (editing) { - return ( -
- setValue(e.target.value)} - onBlur={handleSave} - allowClear - /> - {loading && } -
- ); - } else { - return conversation.label && conversation.label.trim() !== "" ? ( - setEditing(true)}> - {conversation.label} - - ) : ( - - setEditing(true)} - /> - - ); + const handleSave = async () => { + setLoading(true); + try { + const response = await updateLabel({ + variables: { id: conversation.id, label: value } + }); + if (response.errors) { + notification["error"]({ + message: t("messages.errors.updatinglabel", { + error: JSON.stringify(response.errors) + }) + }); + } else { + setEditing(false); + } + } catch (error) { + notification["error"]({ + message: t("messages.errors.updatinglabel", { + error: JSON.stringify(error) + }) + }); + } finally { + setLoading(false); } + }; + if (editing) { + return ( +
+ setValue(e.target.value)} onBlur={handleSave} allowClear /> + {loading && } +
+ ); + } else { + return conversation.label && conversation.label.trim() !== "" ? ( + setEditing(true)}> + {conversation.label} + + ) : ( + + setEditing(true)} /> + + ); + } } diff --git a/client/src/components/chat-media-selector/chat-media-selector.component.jsx b/client/src/components/chat-media-selector/chat-media-selector.component.jsx index c2f32883f..0526fde43 100644 --- a/client/src/components/chat-media-selector/chat-media-selector.component.jsx +++ b/client/src/components/chat-media-selector/chat-media-selector.component.jsx @@ -1,100 +1,82 @@ -import {PictureFilled} from "@ant-design/icons"; -import {useQuery} from "@apollo/client"; -import {Badge, Popover} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {GET_DOCUMENTS_BY_JOB} from "../../graphql/documents.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { PictureFilled } from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import { Badge, Popover } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component"; -import JobDocumentsLocalGalleryExternal - from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component"; +import JobDocumentsLocalGalleryExternal from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(ChatMediaSelector); -export function ChatMediaSelector({ - bodyshop, - selectedMedia, - setSelectedMedia, - conversation, - }) { - const {t} = useTranslation(); - const [open, setOpen] = useState(false); +export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, conversation }) { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); - const {loading, error, data} = useQuery(GET_DOCUMENTS_BY_JOB, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - variables: { - jobId: - conversation.job_conversations[0] && - conversation.job_conversations[0].jobid, - }, + const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + jobId: conversation.job_conversations[0] && conversation.job_conversations[0].jobid + }, - skip: - !open || - !conversation.job_conversations || - conversation.job_conversations.length === 0, - }); + skip: !open || !conversation.job_conversations || conversation.job_conversations.length === 0 + }); - const handleVisibleChange = (change) => { - setOpen(change); - }; + const handleVisibleChange = (change) => { + setOpen(change); + }; - useEffect(() => { - setSelectedMedia([]); - }, [setSelectedMedia, conversation]); + useEffect(() => { + setSelectedMedia([]); + }, [setSelectedMedia, conversation]); - const content = ( -
- {loading && } - {error && } - {selectedMedia.filter((s) => s.isSelected).length >= 10 ? ( -
{t("messaging.labels.maxtenimages")}
- ) : null} - {!bodyshop.uselocalmediaserver && data && ( - - )} - {bodyshop.uselocalmediaserver && open && ( - - )} -
- ); + const content = ( +
+ {loading && } + {error && } + {selectedMedia.filter((s) => s.isSelected).length >= 10 ? ( +
{t("messaging.labels.maxtenimages")}
+ ) : null} + {!bodyshop.uselocalmediaserver && data && ( + + )} + {bodyshop.uselocalmediaserver && open && ( + + )} +
+ ); - return ( - {t("messaging.errors.noattachedjobs")} - ) : ( - content - ) - } - title={t("messaging.labels.selectmedia")} - trigger="click" - open={open} - onOpenChange={handleVisibleChange} - > - s.isSelected).length}> - - - - ); + return ( + {t("messaging.errors.noattachedjobs")} : content + } + title={t("messaging.labels.selectmedia")} + trigger="click" + open={open} + onOpenChange={handleVisibleChange} + > + s.isSelected).length}> + + + + ); } 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 e32580900..55aa81dc0 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,113 +1,106 @@ import Icon from "@ant-design/icons"; -import {Tooltip} from "antd"; +import { Tooltip } from "antd"; import i18n from "i18next"; import dayjs from "../../utils/day"; -import React, {useEffect, useRef} from "react"; -import {MdDone, MdDoneAll} from "react-icons/md"; -import {AutoSizer, CellMeasurer, CellMeasurerCache, List,} from "react-virtualized"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; +import React, { useEffect, useRef } from "react"; +import { MdDone, MdDoneAll } from "react-icons/md"; +import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from "react-virtualized"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; import "./chat-message-list.styles.scss"; -export default function ChatMessageListComponent({messages}) { - const virtualizedListRef = useRef(null); +export default function ChatMessageListComponent({ messages }) { + const virtualizedListRef = useRef(null); - const _cache = new CellMeasurerCache({ - fixedWidth: true, - // minHeight: 50, - defaultHeight: 100, - }); + const _cache = new CellMeasurerCache({ + fixedWidth: true, + // minHeight: 50, + defaultHeight: 100 + }); - 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. - }; + 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}) => { - return ( - - {({measure, registerChild}) => ( -
-
- {MessageRender(messages[index])} - {StatusRender(messages[index].status)} -
- {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" - ), - })} -
- )} -
- )} -
- ); - }; + useEffect(scrollToBottom, [messages]); + const _rowRenderer = ({ index, key, parent, style }) => { return ( -
- - {({height, width}) => ( - - )} - -
+ + {({ measure, registerChild }) => ( +
+
+ {MessageRender(messages[index])} + {StatusRender(messages[index].status)} +
+ {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") + })} +
+ )} +
+ )} +
); + }; + + return ( +
+ + {({ height, width }) => ( + + )} + +
+ ); } const MessageRender = (message) => { - return ( - -
- {message.image_path && - message.image_path.map((i, idx) => ( -
- - Received - -
- ))} -
{message.text}
+ return ( + +
+ {message.image_path && + message.image_path.map((i, idx) => ( + - - ); + ))} +
{message.text}
+
+
+ ); }; const StatusRender = (status) => { - switch (status) { - case "sent": - return ; - case "delivered": - return ; - default: - return null; - } + switch (status) { + case "sent": + return ; + case "delivered": + return ; + default: + return null; + } }; 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 61480c310..b0b6054e4 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,55 +1,49 @@ -import {PlusCircleFilled} from "@ant-design/icons"; -import {Button, Form, Popover} from "antd"; +import { PlusCircleFilled } from "@ant-design/icons"; +import { Button, Form, Popover } from "antd"; import React 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 { 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"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser + //currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), + openChatByPhone: (phone) => dispatch(openChatByPhone(phone)) }); -export function ChatNewConversation({openChatByPhone}) { - const {t} = useTranslation(); - const [form] = Form.useForm(); - const handleFinish = (values) => { - openChatByPhone({phone_num: values.phoneNumber}); - form.resetFields(); - }; +export function ChatNewConversation({ openChatByPhone }) { + const { t } = useTranslation(); + const [form] = Form.useForm(); + const handleFinish = (values) => { + openChatByPhone({ phone_num: values.phoneNumber }); + form.resetFields(); + }; - const popContent = ( -
-
- - PhoneItemFormatterValidation(getFieldValue, "phoneNumber"), - ]} - > - - - - -
- ); + const popContent = ( +
+
+ PhoneItemFormatterValidation(getFieldValue, "phoneNumber")]} + > + + + + +
+ ); - return ( - - - - ); + return ( + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ChatNewConversation); +export default connect(mapStateToProps, mapDispatchToProps)(ChatNewConversation); 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 539c5ec36..d2f0de528 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,54 +1,47 @@ -import {notification} from "antd"; +import { notification } from "antd"; import parsePhoneNumber from "libphonenumber-js"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {openChatByPhone} from "../../redux/messaging/messaging.actions"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { openChatByPhone } from "../../redux/messaging/messaging.actions"; import PhoneNumberFormatter from "../../utils/PhoneFormatter"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {searchingForConversation} from "../../redux/messaging/messaging.selectors"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { searchingForConversation } from "../../redux/messaging/messaging.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - searchingForConversation: searchingForConversation, + bodyshop: selectBodyshop, + searchingForConversation: searchingForConversation }); const mapDispatchToProps = (dispatch) => ({ - openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), + openChatByPhone: (phone) => dispatch(openChatByPhone(phone)) }); -export function ChatOpenButton({ - bodyshop, - searchingForConversation, - phone, - jobid, - openChatByPhone, - }) { - const {t} = useTranslation(); - if (!phone) return <>; +export function ChatOpenButton({ bodyshop, searchingForConversation, phone, jobid, openChatByPhone }) { + const { t } = useTranslation(); + if (!phone) return <>; - if (!bodyshop.messagingservicesid) - return {phone}; + if (!bodyshop.messagingservicesid) return {phone}; - return ( - { - e.stopPropagation(); - 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}); - } else { - notification["error"]({message: t("messaging.error.invalidphone")}); - } - }} - > - {phone} - - ); + return ( + { + e.stopPropagation(); + 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 }); + } else { + notification["error"]({ message: t("messaging.error.invalidphone") }); + } + }} + > + {phone} + + ); } export default connect(mapStateToProps, mapDispatchToProps)(ChatOpenButton); diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index 052193b1e..c608f8b11 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -1,13 +1,13 @@ -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 {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 {toggleChatVisible} from "../../redux/messaging/messaging.actions"; -import {selectChatVisible, selectSelectedConversation,} from "../../redux/messaging/messaging.selectors"; +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 { 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 { toggleChatVisible } from "../../redux/messaging/messaging.actions"; +import { selectChatVisible, selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; import ChatConversationListComponent from "../chat-conversation-list/chat-conversation-list.component"; import ChatConversationContainer from "../chat-conversation/chat-conversation.container"; import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component"; @@ -15,119 +15,102 @@ import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import "./chat-popup.styles.scss"; const mapStateToProps = createStructuredSelector({ - selectedConversation: selectSelectedConversation, - chatVisible: selectChatVisible, + selectedConversation: selectSelectedConversation, + chatVisible: selectChatVisible }); const mapDispatchToProps = (dispatch) => ({ - toggleChatVisible: () => dispatch(toggleChatVisible()), + toggleChatVisible: () => dispatch(toggleChatVisible()) }); -export function ChatPopupComponent({ - chatVisible, - selectedConversation, - toggleChatVisible, - }) { - const {t} = useTranslation(); - const [pollInterval, setpollInterval] = useState(0); +export function ChatPopupComponent({ chatVisible, selectedConversation, toggleChatVisible }) { + const { t } = useTranslation(); + const [pollInterval, setpollInterval] = useState(0); - const {data: unreadData} = useQuery(UNREAD_CONVERSATION_COUNT, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - ...(pollInterval > 0 ? {pollInterval} : {}), - }); + const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + ...(pollInterval > 0 ? { pollInterval } : {}) + }); - const [getConversations, {loading, data, refetch, fetchMore}] = - useLazyQuery(CONVERSATION_LIST_QUERY, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - skip: !chatVisible, - ...(pollInterval > 0 ? {pollInterval} : {}), - }); + const [getConversations, { loading, data, refetch, fetchMore }] = useLazyQuery(CONVERSATION_LIST_QUERY, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + skip: !chatVisible, + ...(pollInterval > 0 ? { pollInterval } : {}) + }); - const fcmToken = sessionStorage.getItem("fcmtoken"); + const fcmToken = sessionStorage.getItem("fcmtoken"); - useEffect(() => { - if (fcmToken) { - setpollInterval(0); - } else { - setpollInterval(60000); + useEffect(() => { + if (fcmToken) { + setpollInterval(0); + } else { + setpollInterval(60000); + } + }, [fcmToken]); + + useEffect(() => { + if (chatVisible) + getConversations({ + variables: { + offset: 0 } - }, [fcmToken]); + }); + }, [chatVisible, getConversations]); - useEffect(() => { - if (chatVisible) - getConversations({ - variables: { - offset: 0, - }, - }); - }, [chatVisible, getConversations]); + const loadMoreConversations = useCallback(() => { + if (data) + fetchMore({ + variables: { + offset: data.conversations.length + } + }); + }, [data, fetchMore]); - const loadMoreConversations = useCallback(() => { - if (data) - fetchMore({ - variables: { - offset: data.conversations.length, - }, - }); - }, [data, fetchMore]); + const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0; - const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0; + return ( + + + {chatVisible ? ( +
+ + {t("messaging.labels.messaging")} + + + + + refetch()} /> + {pollInterval > 0 && {t("messaging.labels.nopush")}} + + toggleChatVisible()} + style={{ position: "absolute", right: ".5rem", top: ".5rem" }} + /> - return ( - - - {chatVisible ? ( -
- - - {t("messaging.labels.messaging")} - - - - - - refetch()} - /> - {pollInterval > 0 && ( - {t("messaging.labels.nopush")} - )} - - toggleChatVisible()} - style={{position: "absolute", right: ".5rem", top: ".5rem"}} - /> - - -
- {loading ? ( - - ) : ( - - )} - - - {selectedConversation ? : null} - - - + + + {loading ? ( + ) : ( -
toggleChatVisible()} - style={{cursor: "pointer"}} - > - - {t("messaging.labels.messaging")} -
+ )} - - - ); + +
{selectedConversation ? : null} + + + ) : ( +
toggleChatVisible()} style={{ cursor: "pointer" }}> + + {t("messaging.labels.messaging")} +
+ )} + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(ChatPopupComponent); diff --git a/client/src/components/chat-presets/chat-presets.component.jsx b/client/src/components/chat-presets/chat-presets.component.jsx index 5fee9c0fe..63b79c373 100644 --- a/client/src/components/chat-presets/chat-presets.component.jsx +++ b/client/src/components/chat-presets/chat-presets.component.jsx @@ -1,38 +1,34 @@ -import {PlusCircleOutlined} from "@ant-design/icons"; -import {Dropdown} from "antd"; +import { PlusCircleOutlined } from "@ant-design/icons"; +import { Dropdown } from "antd"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {setMessage} from "../../redux/messaging/messaging.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { setMessage } from "../../redux/messaging/messaging.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) - setMessage: (message) => dispatch(setMessage(message)), + //setUserLanguage: language => dispatch(setUserLanguage(language)) + setMessage: (message) => dispatch(setMessage(message)) }); -export function ChatPresetsComponent({bodyshop, setMessage, className}) { +export function ChatPresetsComponent({ bodyshop, setMessage, className }) { + const items = bodyshop.md_messaging_presets.map((i, idx) => ({ + key: idx, + label: i.label, + onClick: () => setMessage(i.text) + })); - const items = bodyshop.md_messaging_presets.map((i, idx) => ({ - key: idx, - label: (i.label), - onClick: () => setMessage(i.text), - })); - - return ( -
- - - -
- ); + return ( +
+ + + +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ChatPresetsComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ChatPresetsComponent); diff --git a/client/src/components/chat-print-button/chat-print-button.component.jsx b/client/src/components/chat-print-button/chat-print-button.component.jsx index 2e61c0002..0b175b769 100644 --- a/client/src/components/chat-print-button/chat-print-button.component.jsx +++ b/client/src/components/chat-print-button/chat-print-button.component.jsx @@ -1,46 +1,46 @@ -import {MailOutlined, PrinterOutlined} from "@ant-design/icons"; -import {Space, Spin} from "antd"; -import React, {useState} from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {setEmailOptions} from "../../redux/email/email.actions"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { MailOutlined, PrinterOutlined } from "@ant-design/icons"; +import { Space, Spin } from "antd"; +import React, { useState } from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { setEmailOptions } from "../../redux/email/email.actions"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; const mapStateToProps = createStructuredSelector({}); const mapDispatchToProps = (dispatch) => ({ - setEmailOptions: (e) => dispatch(setEmailOptions(e)), + setEmailOptions: (e) => dispatch(setEmailOptions(e)) }); -export function ChatPrintButton({conversation}) { - const [loading, setLoading] = useState(false); +export function ChatPrintButton({ conversation }) { + const [loading, setLoading] = useState(false); - const generateDocument = (type) => { - setLoading(true); - GenerateDocument( - { - name: TemplateList("messaging").conversation_list.key, - variables: {id: conversation.id}, - }, - { - subject: TemplateList("messaging").conversation_list.subject, - }, - type, - conversation.id - ).catch(e => { - console.warn('Something went wrong generating a document.'); - }); - setLoading(false); - } + const generateDocument = (type) => { + setLoading(true); + GenerateDocument( + { + name: TemplateList("messaging").conversation_list.key, + variables: { id: conversation.id } + }, + { + subject: TemplateList("messaging").conversation_list.subject + }, + type, + conversation.id + ).catch((e) => { + console.warn("Something went wrong generating a document."); + }); + setLoading(false); + }; - return ( - - generateDocument('p')}/> - generateDocument('e')}/> - {loading && } - - ); + return ( + + generateDocument("p")} /> + generateDocument("e")} /> + {loading && } + + ); } export default connect(mapStateToProps, mapDispatchToProps)(ChatPrintButton); 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 cae683876..918be1b5f 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,111 +1,101 @@ -import {LoadingOutlined, SendOutlined} from "@ant-design/icons"; -import {Input, Spin} from "antd"; -import React, {useEffect, useRef, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {sendMessage, setMessage,} from "../../redux/messaging/messaging.actions"; -import {selectIsSending, selectMessage,} from "../../redux/messaging/messaging.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { LoadingOutlined, SendOutlined } from "@ant-design/icons"; +import { Input, Spin } from "antd"; +import React, { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { sendMessage, setMessage } from "../../redux/messaging/messaging.actions"; +import { selectIsSending, selectMessage } from "../../redux/messaging/messaging.selectors"; +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"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - isSending: selectIsSending, - message: selectMessage, + bodyshop: selectBodyshop, + isSending: selectIsSending, + message: selectMessage }); const mapDispatchToProps = (dispatch) => ({ - sendMessage: (message) => dispatch(sendMessage(message)), - setMessage: (message) => dispatch(setMessage(message)), + sendMessage: (message) => dispatch(sendMessage(message)), + setMessage: (message) => dispatch(setMessage(message)) }); -function ChatSendMessageComponent({ - conversation, - bodyshop, - sendMessage, - isSending, - message, - setMessage, - }) { - const inputArea = useRef(null); - const [selectedMedia, setSelectedMedia] = useState([]); - useEffect(() => { - inputArea.current.focus(); - }, [isSending, setMessage]); +function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSending, message, setMessage }) { + const inputArea = useRef(null); + const [selectedMedia, setSelectedMedia] = useState([]); + useEffect(() => { + inputArea.current.focus(); + }, [isSending, setMessage]); - const {t} = useTranslation(); + const { t } = useTranslation(); - const handleEnter = () => { - const selectedImages = selectedMedia.filter((i) => i.isSelected); - if ((message === "" || !message) && selectedImages.length === 0) return; - logImEXEvent("messaging_send_message"); + const handleEnter = () => { + const selectedImages = selectedMedia.filter((i) => i.isSelected); + if ((message === "" || !message) && selectedImages.length === 0) return; + logImEXEvent("messaging_send_message"); - if (selectedImages.length < 11) { - sendMessage({ - to: conversation.phone_num, - body: message || "", - messagingServiceSid: bodyshop.messagingservicesid, - conversationid: conversation.id, - selectedMedia: selectedImages, - imexshopid: bodyshop.imexshopid, - }); - setSelectedMedia( - selectedMedia.map((i) => { - return {...i, isSelected: false}; - }) - ); - } - }; + if (selectedImages.length < 11) { + sendMessage({ + to: conversation.phone_num, + body: message || "", + messagingServiceSid: bodyshop.messagingservicesid, + conversationid: conversation.id, + selectedMedia: selectedImages, + imexshopid: bodyshop.imexshopid + }); + setSelectedMedia( + selectedMedia.map((i) => { + return { ...i, isSelected: false }; + }) + ); + } + }; - return ( -
- - - + return ( +
+ + + setMessage(e.target.value)} - onPressEnter={(event) => { - event.preventDefault(); - if (!!!event.shiftKey) handleEnter(); - }} + className="imex-flex-row__margin imex-flex-row__grow" + allowClear + autoFocus + ref={inputArea} + autoSize={{ minRows: 1, maxRows: 4 }} + value={message} + disabled={isSending} + placeholder={t("messaging.labels.typeamessage")} + onChange={(e) => setMessage(e.target.value)} + onPressEnter={(event) => { + event.preventDefault(); + if (!!!event.shiftKey) handleEnter(); + }} /> - - - } - /> -
- ); + + + } + /> +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ChatSendMessageComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ChatSendMessageComponent); diff --git a/client/src/components/chat-tag-ro/chat-tag-ro.component.jsx b/client/src/components/chat-tag-ro/chat-tag-ro.component.jsx index 004b28a3c..f40c5c85e 100644 --- a/client/src/components/chat-tag-ro/chat-tag-ro.component.jsx +++ b/client/src/components/chat-tag-ro/chat-tag-ro.component.jsx @@ -1,45 +1,35 @@ -import {CloseCircleOutlined, LoadingOutlined} from "@ant-design/icons"; -import {Empty, Select, Space} from "antd"; +import { CloseCircleOutlined, LoadingOutlined } from "@ant-design/icons"; +import { Empty, Select, Space } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {OwnerNameDisplayFunction} from "../owner-name-display/owner-name-display.component"; +import { useTranslation } from "react-i18next"; +import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; -export default function ChatTagRoComponent({ - roOptions, - loading, - handleSearch, - handleInsertTag, - setOpen, - }) { - const {t} = useTranslation(); +export default function ChatTagRoComponent({ roOptions, loading, handleSearch, handleInsertTag, setOpen }) { + const { t } = useTranslation(); - return ( - -
- -
- {loading ? : null} + return ( + +
+ +
+ {loading ? : null} - {loading ? ( - - ) : ( - setOpen(false)}/> - )} -
- ); + {loading ? : setOpen(false)} />} +
+ ); } 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 f2ea134f0..f9b6fa5ad 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 @@ -1,66 +1,62 @@ -import {PlusOutlined} from "@ant-design/icons"; -import {useLazyQuery, useMutation} from "@apollo/client"; -import {Tag} from "antd"; +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 {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 React, { 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"; -export default function ChatTagRoContainer({conversation}) { - const {t} = useTranslation(); - const [open, setOpen] = useState(false); +export default function ChatTagRoContainer({ conversation }) { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); - const [loadRo, {loading, data}] = useLazyQuery(SEARCH_FOR_JOBS); + const [loadRo, { loading, data }] = useLazyQuery(SEARCH_FOR_JOBS); - const executeSearch = (v) => { - logImEXEvent("messaging_search_job_tag", {searchTerm: v}); - loadRo(v); - }; + const executeSearch = (v) => { + logImEXEvent("messaging_search_job_tag", { searchTerm: v }); + loadRo(v); + }; - const debouncedExecuteSearch = _.debounce(executeSearch, 500); + const debouncedExecuteSearch = _.debounce(executeSearch, 500); - const handleSearch = (value) => { - debouncedExecuteSearch({variables: {search: value}}); - }; + const handleSearch = (value) => { + debouncedExecuteSearch({ variables: { search: value } }); + }; - const [insertTag] = useMutation(INSERT_CONVERSATION_TAG, { - variables: {conversationId: conversation.id}, - }); + const [insertTag] = useMutation(INSERT_CONVERSATION_TAG, { + variables: { conversationId: conversation.id } + }); - const handleInsertTag = (value, option) => { - logImEXEvent("messaging_add_job_tag"); - insertTag({variables: {jobId: option.key}}); - setOpen(false); - }; + const handleInsertTag = (value, option) => { + logImEXEvent("messaging_add_job_tag"); + insertTag({ variables: { jobId: option.key } }); + setOpen(false); + }; - const existingJobTags = - conversation && - conversation.job_conversations && - conversation.job_conversations.map((i) => i.jobid); + const existingJobTags = + conversation && conversation.job_conversations && conversation.job_conversations.map((i) => i.jobid); - const roOptions = data - ? data.search_jobs.filter((job) => !existingJobTags.includes(job.id)) - : []; + const roOptions = data ? data.search_jobs.filter((job) => !existingJobTags.includes(job.id)) : []; - return ( -
- {open ? ( - - ) : ( - setOpen(true)}> - - {t("messaging.actions.link")} - - )} -
- ); + return ( +
+ {open ? ( + + ) : ( + setOpen(true)}> + + {t("messaging.actions.link")} + + )} +
+ ); } diff --git a/client/src/components/config-form-components/checkbox/checkbox.component.jsx b/client/src/components/config-form-components/checkbox/checkbox.component.jsx index f3945268d..7c5a6c4be 100644 --- a/client/src/components/config-form-components/checkbox/checkbox.component.jsx +++ b/client/src/components/config-form-components/checkbox/checkbox.component.jsx @@ -1,22 +1,22 @@ -import {Checkbox, Form} from "antd"; +import { Checkbox, Form } from "antd"; import React from "react"; -export default function JobIntakeFormCheckboxComponent({formItem, readOnly}) { - const {name, label, required} = formItem; +export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) { + const { name, label, required } = formItem; - return ( - - - - ); + return ( + + + + ); } diff --git a/client/src/components/config-form-components/config-form-components.component.jsx b/client/src/components/config-form-components/config-form-components.component.jsx index c2b513052..abcd6a6b5 100644 --- a/client/src/components/config-form-components/config-form-components.component.jsx +++ b/client/src/components/config-form-components/config-form-components.component.jsx @@ -1,18 +1,18 @@ import React from "react"; import FormTypes from "./config-form-types"; -export default function ConfirmFormComponents({componentList, readOnly}) { - return ( -
- {componentList.map((f, idx) => { - const Comp = FormTypes[f.type]; +export default function ConfirmFormComponents({ componentList, readOnly }) { + return ( +
+ {componentList.map((f, idx) => { + const Comp = FormTypes[f.type]; - if (!!Comp) { - return ; - } else { - return
Error
; - } - })} -
- ); + if (!!Comp) { + return ; + } else { + return
Error
; + } + })} +
+ ); } diff --git a/client/src/components/config-form-components/config-form-types.js b/client/src/components/config-form-components/config-form-types.js index fa2121ef8..a59a66d91 100644 --- a/client/src/components/config-form-components/config-form-types.js +++ b/client/src/components/config-form-components/config-form-types.js @@ -5,11 +5,11 @@ import Text from "./text/text.component"; import Textarea from "./textarea/textarea.component"; const e = { - checkbox: CheckboxFormItem, - slider: Slider, - text: Text, - textarea: Textarea, - rate: Rate, + checkbox: CheckboxFormItem, + slider: Slider, + text: Text, + textarea: Textarea, + rate: Rate }; export default e; diff --git a/client/src/components/config-form-components/rate/rate.component.jsx b/client/src/components/config-form-components/rate/rate.component.jsx index 2e144b7be..9f7e0189f 100644 --- a/client/src/components/config-form-components/rate/rate.component.jsx +++ b/client/src/components/config-form-components/rate/rate.component.jsx @@ -1,21 +1,21 @@ -import {Form, Rate} from "antd"; +import { Form, Rate } from "antd"; import React from "react"; -export default function JobIntakeFormCheckboxComponent({formItem, readOnly}) { - const {name, label, required} = formItem; +export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) { + const { name, label, required } = formItem; - return ( - - - - ); + return ( + + + + ); } diff --git a/client/src/components/config-form-components/slider/slider.component.jsx b/client/src/components/config-form-components/slider/slider.component.jsx index 0d8da40f7..1fde2bfa4 100644 --- a/client/src/components/config-form-components/slider/slider.component.jsx +++ b/client/src/components/config-form-components/slider/slider.component.jsx @@ -1,21 +1,21 @@ -import {Form, Slider} from "antd"; +import { Form, Slider } from "antd"; import React from "react"; -export default function JobIntakeFormCheckboxComponent({formItem, readOnly}) { - const {name, label, required, min, max} = formItem; +export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) { + const { name, label, required, min, max } = formItem; - return ( - - - - ); + return ( + + + + ); } diff --git a/client/src/components/config-form-components/text/text.component.jsx b/client/src/components/config-form-components/text/text.component.jsx index 9364e1c5d..73394e0c0 100644 --- a/client/src/components/config-form-components/text/text.component.jsx +++ b/client/src/components/config-form-components/text/text.component.jsx @@ -1,20 +1,20 @@ -import {Form, Input} from "antd"; +import { Form, Input } from "antd"; import React from "react"; -export default function JobIntakeFormCheckboxComponent({formItem, readOnly}) { - const {name, label, required} = formItem; - return ( - - - - ); +export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) { + const { name, label, required } = formItem; + return ( + + + + ); } diff --git a/client/src/components/config-form-components/textarea/textarea.component.jsx b/client/src/components/config-form-components/textarea/textarea.component.jsx index f707e4298..c9bb19781 100644 --- a/client/src/components/config-form-components/textarea/textarea.component.jsx +++ b/client/src/components/config-form-components/textarea/textarea.component.jsx @@ -1,21 +1,21 @@ -import {Form, Input} from "antd"; +import { Form, Input } from "antd"; import React from "react"; -export default function JobIntakeFormCheckboxComponent({formItem, readOnly}) { - const {name, label, required, rows} = formItem; +export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) { + const { name, label, required, rows } = formItem; - return ( - - - - ); + return ( + + + + ); } diff --git a/client/src/components/conflict/conflict.component.jsx b/client/src/components/conflict/conflict.component.jsx index f9087b1b7..659e64080 100644 --- a/client/src/components/conflict/conflict.component.jsx +++ b/client/src/components/conflict/conflict.component.jsx @@ -1,28 +1,36 @@ import React from "react"; -import {Button, Result} from "antd"; -import {useTranslation} from "react-i18next"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { Button, Result } from "antd"; +import { useTranslation } from "react-i18next"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; export default function ConflictComponent() { - const {t} = useTranslation(); - return ( -
- -
{t("general.labels.instanceconflictext",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})})}
- -
- } - /> - - ); + const { t } = useTranslation(); + return ( +
+ +
+ {t("general.labels.instanceconflictext", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + })} +
+ +
+ } + /> + + ); } diff --git a/client/src/components/contract-cars/contract-cars.component.jsx b/client/src/components/contract-cars/contract-cars.component.jsx index 9868b0ad9..830dd5531 100644 --- a/client/src/components/contract-cars/contract-cars.component.jsx +++ b/client/src/components/contract-cars/contract-cars.component.jsx @@ -1,152 +1,127 @@ -import {Card, Input, Table} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {alphaSort} from "../../utils/sorters"; +import { Card, Input, Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { alphaSort } from "../../utils/sorters"; -export default function ContractsCarsComponent({ - loading, - data, - selectedCarId, - handleSelect, - }) { - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - search: "", - }); +export default function ContractsCarsComponent({ loading, data, selectedCarId, handleSelect }) { + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" }, + search: "" + }); - const {t} = useTranslation(); + const { t } = useTranslation(); - const columns = [ - { - title: t("courtesycars.fields.fleetnumber"), - dataIndex: "fleetnumber", - key: "fleetnumber", - sorter: (a, b) => alphaSort(a.fleetnumber, b.fleetnumber), - sortOrder: - state.sortedInfo.columnKey === "fleetnumber" && state.sortedInfo.order, - }, - { - title: t("courtesycars.fields.status"), - dataIndex: "status", - key: "status", - sorter: (a, b) => alphaSort(a.status, b.status), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - render: (text, record) =>
{t(record.status)}
, - }, - { - title: t("courtesycars.fields.readiness"), - dataIndex: "readiness", - key: "readiness", - sorter: (a, b) => alphaSort(a.readiness, b.readiness), - sortOrder: - state.sortedInfo.columnKey === "readiness" && state.sortedInfo.order, - render: (text, record) => t(record.readiness), - }, - { - title: t("courtesycars.fields.year"), - dataIndex: "year", - key: "year", - sorter: (a, b) => alphaSort(a.year, b.year), - sortOrder: - state.sortedInfo.columnKey === "year" && state.sortedInfo.order, - }, - { - title: t("courtesycars.fields.make"), - dataIndex: "make", - key: "make", - sorter: (a, b) => alphaSort(a.make, b.make), - sortOrder: - state.sortedInfo.columnKey === "make" && state.sortedInfo.order, - }, - { - title: t("courtesycars.fields.model"), - dataIndex: "model", - key: "model", - sorter: (a, b) => alphaSort(a.model, b.model), - sortOrder: - state.sortedInfo.columnKey === "model" && state.sortedInfo.order, - }, - { - title: t("courtesycars.fields.color"), - dataIndex: "color", - key: "color", - sorter: (a, b) => alphaSort(a.color, b.color), - sortOrder: - state.sortedInfo.columnKey === "color" && state.sortedInfo.order, - }, - { - title: t("courtesycars.fields.plate"), - dataIndex: "plate", - key: "plate", - sorter: (a, b) => alphaSort(a.plate, b.plate), - sortOrder: - state.sortedInfo.columnKey === "plate" && state.sortedInfo.order, - }, - ]; + const columns = [ + { + title: t("courtesycars.fields.fleetnumber"), + dataIndex: "fleetnumber", + key: "fleetnumber", + sorter: (a, b) => alphaSort(a.fleetnumber, b.fleetnumber), + sortOrder: state.sortedInfo.columnKey === "fleetnumber" && state.sortedInfo.order + }, + { + title: t("courtesycars.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => alphaSort(a.status, b.status), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + render: (text, record) =>
{t(record.status)}
+ }, + { + title: t("courtesycars.fields.readiness"), + dataIndex: "readiness", + key: "readiness", + sorter: (a, b) => alphaSort(a.readiness, b.readiness), + sortOrder: state.sortedInfo.columnKey === "readiness" && state.sortedInfo.order, + render: (text, record) => t(record.readiness) + }, + { + title: t("courtesycars.fields.year"), + dataIndex: "year", + key: "year", + sorter: (a, b) => alphaSort(a.year, b.year), + sortOrder: state.sortedInfo.columnKey === "year" && state.sortedInfo.order + }, + { + title: t("courtesycars.fields.make"), + dataIndex: "make", + key: "make", + sorter: (a, b) => alphaSort(a.make, b.make), + sortOrder: state.sortedInfo.columnKey === "make" && state.sortedInfo.order + }, + { + title: t("courtesycars.fields.model"), + dataIndex: "model", + key: "model", + sorter: (a, b) => alphaSort(a.model, b.model), + sortOrder: state.sortedInfo.columnKey === "model" && state.sortedInfo.order + }, + { + title: t("courtesycars.fields.color"), + dataIndex: "color", + key: "color", + sorter: (a, b) => alphaSort(a.color, b.color), + sortOrder: state.sortedInfo.columnKey === "color" && state.sortedInfo.order + }, + { + title: t("courtesycars.fields.plate"), + dataIndex: "plate", + key: "plate", + sorter: (a, b) => alphaSort(a.plate, b.plate), + sortOrder: state.sortedInfo.columnKey === "plate" && state.sortedInfo.order + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - const filteredData = - state.search === "" - ? data - : data.filter( - (cc) => - (cc.fleetnumber || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (cc.status || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (cc.year || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (cc.make || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (cc.model || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (cc.color || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (cc.plate || "").toLowerCase().includes(state.search.toLowerCase()) - ); + const filteredData = + state.search === "" + ? data + : data.filter( + (cc) => + (cc.fleetnumber || "").toLowerCase().includes(state.search.toLowerCase()) || + (cc.status || "").toLowerCase().includes(state.search.toLowerCase()) || + (cc.year || "").toLowerCase().includes(state.search.toLowerCase()) || + (cc.make || "").toLowerCase().includes(state.search.toLowerCase()) || + (cc.model || "").toLowerCase().includes(state.search.toLowerCase()) || + (cc.color || "").toLowerCase().includes(state.search.toLowerCase()) || + (cc.plate || "").toLowerCase().includes(state.search.toLowerCase()) + ); - return ( - setState({...state, search: e.target.value})} - /> + return ( + setState({ ...state, search: e.target.value })} + /> + } + > +
{ + return { + onClick: (event) => { + handleSelect(record); } - > -
{ - return { - onClick: (event) => { - handleSelect(record); - }, - }; - }} - /> - - ); + }; + }} + /> + + ); } diff --git a/client/src/components/contract-cars/contract-cars.container.jsx b/client/src/components/contract-cars/contract-cars.container.jsx index 9d5f23355..24364313d 100644 --- a/client/src/components/contract-cars/contract-cars.container.jsx +++ b/client/src/components/contract-cars/contract-cars.container.jsx @@ -1,37 +1,37 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import dayjs from "../../utils/day"; import React from "react"; -import {QUERY_AVAILABLE_CC} from "../../graphql/courtesy-car.queries"; +import { QUERY_AVAILABLE_CC } from "../../graphql/courtesy-car.queries"; import AlertComponent from "../alert/alert.component"; import ContractCarsComponent from "./contract-cars.component"; -export default function ContractCarsContainer({selectedCarState, form}) { - const {loading, error, data} = useQuery(QUERY_AVAILABLE_CC, { - variables: {today: dayjs().format("YYYY-MM-DD")}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", +export default function ContractCarsContainer({ selectedCarState, form }) { + const { loading, error, data } = useQuery(QUERY_AVAILABLE_CC, { + variables: { today: dayjs().format("YYYY-MM-DD") }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + + const [selectedCar, setSelectedCar] = selectedCarState; + + const handleSelect = (record) => { + setSelectedCar(record); + + form.setFieldsValue({ + kmstart: record.mileage, + dailyrate: record.dailycost, + fuelout: record.fuel, + damage: record.damage }); + }; - const [selectedCar, setSelectedCar] = selectedCarState; - - const handleSelect = (record) => { - setSelectedCar(record); - - form.setFieldsValue({ - kmstart: record.mileage, - dailyrate: record.dailycost, - fuelout: record.fuel, - damage: record.damage, - }); - }; - - if (error) return ; - return ( - - ); + if (error) return ; + return ( + + ); } diff --git a/client/src/components/contract-convert-to-ro/contract-convert-to-ro.component.jsx b/client/src/components/contract-convert-to-ro/contract-convert-to-ro.component.jsx index b267d0dc6..ee8d01ed6 100644 --- a/client/src/components/contract-convert-to-ro/contract-convert-to-ro.component.jsx +++ b/client/src/components/contract-convert-to-ro/contract-convert-to-ro.component.jsx @@ -1,397 +1,381 @@ -import {useMutation} from "@apollo/client"; -import {Button, Form, InputNumber, notification, Popover, Radio, Select, Space,} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Form, InputNumber, notification, Popover, Radio, Select, Space } from "antd"; import axios from "axios"; import dayjs from "../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {INSERT_NEW_JOB} from "../../graphql/jobs.queries"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { INSERT_NEW_JOB } from "../../graphql/jobs.queries"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function ContractConvertToRo({ - bodyshop, - currentUser, - contract, - disabled, - }) { - const {t} = useTranslation(); - const [open, setOpen] = useState(false); - const [loading, setLoading] = useState(false); - const [insertJob] = useMutation(INSERT_NEW_JOB); - const history = useNavigate(); +export function ContractConvertToRo({ bodyshop, currentUser, contract, disabled }) { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); + const [insertJob] = useMutation(INSERT_NEW_JOB); + const history = useNavigate(); - const handleFinish = async (values) => { - setLoading(true); + const handleFinish = async (values) => { + setLoading(true); - const contractLength = dayjs(contract.actualreturn).diff( - dayjs(contract.start), - "day" - ); - const billingLines = []; - if (contractLength > 0) - billingLines.push({ - manual_line: true, - unq_seq: 1, - line_no: 1, - line_ref: 1, - line_desc: t("contracts.fields.dailyrate"), - db_price: contract.dailyrate, - act_price: contract.dailyrate, - part_qty: contractLength, - part_type: "CCDR", - tax_part: true, - mod_lb_hrs: 0, - db_ref: "io-ccdr", - // mod_lbr_ty: "PAL", - }); - const mileageDiff = - contract.kmend - contract.kmstart - contract.dailyfreekm * contractLength; - if (mileageDiff > 0) { - billingLines.push({ - manual_line: true, - unq_seq: 2, - line_no: 2, - line_ref: 2, - line_desc: "Fuel Surcharge", - db_price: contract.excesskmrate, - act_price: contract.excesskmrate, - part_type: "CCM", - part_qty: mileageDiff, - tax_part: true, - db_ref: "io-ccm", - mod_lb_hrs: 0, - }); + const contractLength = dayjs(contract.actualreturn).diff(dayjs(contract.start), "day"); + const billingLines = []; + if (contractLength > 0) + billingLines.push({ + manual_line: true, + unq_seq: 1, + line_no: 1, + line_ref: 1, + line_desc: t("contracts.fields.dailyrate"), + db_price: contract.dailyrate, + act_price: contract.dailyrate, + part_qty: contractLength, + part_type: "CCDR", + tax_part: true, + mod_lb_hrs: 0, + db_ref: "io-ccdr" + // mod_lbr_ty: "PAL", + }); + const mileageDiff = contract.kmend - contract.kmstart - contract.dailyfreekm * contractLength; + if (mileageDiff > 0) { + billingLines.push({ + manual_line: true, + unq_seq: 2, + line_no: 2, + line_ref: 2, + line_desc: "Fuel Surcharge", + db_price: contract.excesskmrate, + act_price: contract.excesskmrate, + part_type: "CCM", + part_qty: mileageDiff, + tax_part: true, + db_ref: "io-ccm", + mod_lb_hrs: 0 + }); + } + + if (values.refuelqty > 0) { + billingLines.push({ + manual_line: true, + unq_seq: 3, + line_no: 3, + line_ref: 3, + line_desc: t("contracts.fields.refuelcharge"), + db_price: contract.refuelcharge, + act_price: contract.refuelcharge, + part_qty: values.refuelqty, + part_type: "CCF", + tax_part: true, + db_ref: "io-ccf", + mod_lb_hrs: 0 + }); + } + if (values.applyCleanupCharge) { + billingLines.push({ + manual_line: true, + unq_seq: 4, + line_no: 4, + line_ref: 4, + line_desc: t("contracts.fields.cleanupcharge"), + db_price: contract.cleanupcharge, + act_price: contract.cleanupcharge, + part_qty: 1, + part_type: "CCC", + tax_part: true, + db_ref: "io-ccc", + mod_lb_hrs: 0 + }); + } + if (contract.damagewaiver) { + //Add for cleanup fee. + billingLines.push({ + manual_line: true, + unq_seq: 5, + line_no: 5, + line_ref: 5, + line_desc: t("contracts.fields.damagewaiver"), + db_price: contract.damagewaiver, + act_price: contract.damagewaiver, + part_type: "CCD", + part_qty: 1, + tax_part: true, + db_ref: "io-ccd", + mod_lb_hrs: 0 + }); + } + + const newJob = { + // converted: true, + shopid: bodyshop.id, + ownerid: contract.job.ownerid, + vehicleid: contract.job.vehicleid, + federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate / 100, + state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate / 100, + local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate / 100, + ins_co_nm: values.ins_co_nm, + class: values.class, + converted: true, + clm_no: contract.job.clm_no ? `${contract.job.clm_no}-CC` : null, + ownr_fn: contract.job.owner.ownr_fn, + ownr_ln: contract.job.owner.ownr_ln, + ownr_co_nm: contract.job.owner.ownr_co_nm, + ownr_ph1: contract.job.owner.ownr_ph1, + ownr_ea: contract.job.owner.ownr_ea, + v_model_desc: contract.job.vehicle && contract.job.vehicle.v_model_desc, + v_model_yr: contract.job.vehicle && contract.job.vehicle.v_model_yr, + v_make_desc: contract.job.vehicle && contract.job.vehicle.v_make_desc, + v_vin: contract.job.vehicle && contract.job.vehicle.v_vin, + status: bodyshop.md_ro_statuses.default_completed, + notes: { + data: [ + { + text: t("contracts.labels.noteconvertedfrom", { + agreementnumber: contract.agreementnumber + }), + audit: true, + created_by: currentUser.email + } + ] + }, + joblines: { + data: billingLines + }, + parts_tax_rates: { + PAA: { + prt_type: "PAA", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + PAC: { + prt_type: "PAC", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + PAL: { + prt_type: "PAL", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + PAM: { + prt_type: "PAM", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + PAN: { + prt_type: "PAN", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + PAR: { + prt_type: "PAR", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + PAS: { + prt_type: "PAS", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + CCDR: { + prt_type: "CCDR", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + CCF: { + prt_type: "CCF", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + CCM: { + prt_type: "CCM", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + CCC: { + prt_type: "CCC", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + CCD: { + prt_type: "CCD", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 } - - if (values.refuelqty > 0) { - billingLines.push({ - manual_line: true, - unq_seq: 3, - line_no: 3, - line_ref: 3, - line_desc: t("contracts.fields.refuelcharge"), - db_price: contract.refuelcharge, - act_price: contract.refuelcharge, - part_qty: values.refuelqty, - part_type: "CCF", - tax_part: true, - db_ref: "io-ccf", - mod_lb_hrs: 0, - }); - } - if (values.applyCleanupCharge) { - billingLines.push({ - manual_line: true, - unq_seq: 4, - line_no: 4, - line_ref: 4, - line_desc: t("contracts.fields.cleanupcharge"), - db_price: contract.cleanupcharge, - act_price: contract.cleanupcharge, - part_qty: 1, - part_type: "CCC", - tax_part: true, - db_ref: "io-ccc", - mod_lb_hrs: 0, - }); - } - if (contract.damagewaiver) { - //Add for cleanup fee. - billingLines.push({ - manual_line: true, - unq_seq: 5, - line_no: 5, - line_ref: 5, - line_desc: t("contracts.fields.damagewaiver"), - db_price: contract.damagewaiver, - act_price: contract.damagewaiver, - part_type: "CCD", - part_qty: 1, - tax_part: true, - db_ref: "io-ccd", - mod_lb_hrs: 0, - }); - } - - const newJob = { - // converted: true, - shopid: bodyshop.id, - ownerid: contract.job.ownerid, - vehicleid: contract.job.vehicleid, - federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate / 100, - state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate / 100, - local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate / 100, - ins_co_nm: values.ins_co_nm, - class: values.class, - converted: true, - clm_no: contract.job.clm_no ? `${contract.job.clm_no}-CC` : null, - ownr_fn: contract.job.owner.ownr_fn, - ownr_ln: contract.job.owner.ownr_ln, - ownr_co_nm: contract.job.owner.ownr_co_nm, - ownr_ph1: contract.job.owner.ownr_ph1, - ownr_ea: contract.job.owner.ownr_ea, - v_model_desc: contract.job.vehicle && contract.job.vehicle.v_model_desc, - v_model_yr: contract.job.vehicle && contract.job.vehicle.v_model_yr, - v_make_desc: contract.job.vehicle && contract.job.vehicle.v_make_desc, - v_vin: contract.job.vehicle && contract.job.vehicle.v_vin, - status: bodyshop.md_ro_statuses.default_completed, - notes: { - data: [ - { - text: t("contracts.labels.noteconvertedfrom", { - agreementnumber: contract.agreementnumber, - }), - audit: true, - created_by: currentUser.email, - }, - ], - }, - joblines: { - data: billingLines, - }, - parts_tax_rates: { - PAA: { - prt_type: "PAA", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, - }, - PAC: { - prt_type: "PAC", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, - }, - PAL: { - prt_type: "PAL", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, - }, - PAM: { - prt_type: "PAM", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, - }, - PAN: { - prt_type: "PAN", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, - }, - PAR: { - prt_type: "PAR", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, - }, - PAS: { - prt_type: "PAS", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, - }, - CCDR: { - prt_type: "CCDR", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, - }, - CCF: { - prt_type: "CCF", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, - }, - CCM: { - prt_type: "CCM", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, - }, - CCC: { - prt_type: "CCC", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, - }, - CCD: { - prt_type: "CCD", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, - }, - }, - }; - - //Calcualte the new job totals. - - const newTotals = ( - await axios.post("/job/totals", { - job: {...newJob, joblines: billingLines}, - }) - ).data; - - newJob.clm_total = newTotals.totals.total_repairs.amount / 100; - newJob.job_totals = newTotals; - - const result = await insertJob({ - variables: {job: [newJob]}, - // refetchQueries: ["GET_JOB_BY_PK"], - // awaitRefetchQueries: true, - }); - - if (!!result.errors) { - notification["error"]({ - message: t("jobs.errors.inserting", { - message: JSON.stringify(result.errors), - }), - }); - } else { - notification["success"]({ - message: t("jobs.successes.created"), - onClick: () => { - history.push( - `/manage/jobs/${result.data.insert_jobs.returning[0].id}` - ); - }, - }); - } - - setOpen(false); - setLoading(false); + } }; - const popContent = ( -
-
- - - - - - - - - {t("general.labels.yes")} - {t("general.labels.no")} - - - - - - - - - - -
- ); + //Calcualte the new job totals. - return ( -
- - - -
- ); + const newTotals = ( + await axios.post("/job/totals", { + job: { ...newJob, joblines: billingLines } + }) + ).data; + + newJob.clm_total = newTotals.totals.total_repairs.amount / 100; + newJob.job_totals = newTotals; + + const result = await insertJob({ + variables: { job: [newJob] } + // refetchQueries: ["GET_JOB_BY_PK"], + // awaitRefetchQueries: true, + }); + + if (!!result.errors) { + notification["error"]({ + message: t("jobs.errors.inserting", { + message: JSON.stringify(result.errors) + }) + }); + } else { + notification["success"]({ + message: t("jobs.successes.created"), + onClick: () => { + history.push(`/manage/jobs/${result.data.insert_jobs.returning[0].id}`); + } + }); + } + + setOpen(false); + setLoading(false); + }; + + const popContent = ( +
+
+ + + + + + + + + {t("general.labels.yes")} + {t("general.labels.no")} + + + + + + + + + + +
+ ); + + return ( +
+ + + +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ContractConvertToRo); +export default connect(mapStateToProps, mapDispatchToProps)(ContractConvertToRo); diff --git a/client/src/components/contract-courtesy-car-block/contract-courtesy-car-block.component.jsx b/client/src/components/contract-courtesy-car-block/contract-courtesy-car-block.component.jsx index d95e1b52e..75aaa3c9f 100644 --- a/client/src/components/contract-courtesy-car-block/contract-courtesy-car-block.component.jsx +++ b/client/src/components/contract-courtesy-car-block/contract-courtesy-car-block.component.jsx @@ -1,32 +1,26 @@ -import {Card} from "antd"; +import { Card } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {Link} from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; import DataLabel from "../data-label/data-label.component"; -export default function ContractCourtesyCarBlock({courtesyCar}) { - const {t} = useTranslation(); - return ( - - -
- - {(courtesyCar && courtesyCar.fleetnumber) || ""} - - - {(courtesyCar && courtesyCar.plate) || ""} - - - {`${(courtesyCar && courtesyCar.year) || ""} ${ - (courtesyCar && courtesyCar.make) || "" - } ${(courtesyCar && courtesyCar.model) || ""}`} - -
-
- - ); +export default function ContractCourtesyCarBlock({ courtesyCar }) { + const { t } = useTranslation(); + return ( + + +
+ + {(courtesyCar && courtesyCar.fleetnumber) || ""} + + {(courtesyCar && courtesyCar.plate) || ""} + + {`${(courtesyCar && courtesyCar.year) || ""} ${ + (courtesyCar && courtesyCar.make) || "" + } ${(courtesyCar && courtesyCar.model) || ""}`} + +
+
+ + ); } diff --git a/client/src/components/contract-form/contract-form-job-prefill.component.jsx b/client/src/components/contract-form/contract-form-job-prefill.component.jsx index 639ba21b4..76f32023f 100644 --- a/client/src/components/contract-form/contract-form-job-prefill.component.jsx +++ b/client/src/components/contract-form/contract-form-job-prefill.component.jsx @@ -1,45 +1,43 @@ -import {useLazyQuery} from "@apollo/client"; -import {Button, notification} from "antd"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {GET_JOB_FOR_CC_CONTRACT} from "../../graphql/jobs.queries"; +import { useLazyQuery } from "@apollo/client"; +import { Button, notification } from "antd"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { GET_JOB_FOR_CC_CONTRACT } from "../../graphql/jobs.queries"; -export default function ContractCreateJobPrefillComponent({jobId, form}) { - const [call, {loading, error, data}] = useLazyQuery( - GET_JOB_FOR_CC_CONTRACT - ); - const {t} = useTranslation(); +export default function ContractCreateJobPrefillComponent({ jobId, form }) { + const [call, { loading, error, data }] = useLazyQuery(GET_JOB_FOR_CC_CONTRACT); + const { t } = useTranslation(); - const handleClick = () => { - call({variables: {id: jobId}}); - }; + const handleClick = () => { + call({ variables: { id: jobId } }); + }; - useEffect(() => { - if (data) { - form.setFieldsValue({ - driver_dlst: data.jobs_by_pk.ownr_ast, - driver_fn: data.jobs_by_pk.ownr_fn, - driver_ln: data.jobs_by_pk.ownr_ln, - driver_addr1: data.jobs_by_pk.ownr_addr1, - driver_state: data.jobs_by_pk.ownr_st, - driver_city: data.jobs_by_pk.ownr_city, - driver_zip: data.jobs_by_pk.ownr_zip, - driver_ph1: data.jobs_by_pk.ownr_ph1, - }); - } - }, [data, form]); - - if (error) { - notification["error"]({ - message: t("contracts.errors.fetchingjobinfo", { - error: JSON.stringify(error), - }), - }); + useEffect(() => { + if (data) { + form.setFieldsValue({ + driver_dlst: data.jobs_by_pk.ownr_ast, + driver_fn: data.jobs_by_pk.ownr_fn, + driver_ln: data.jobs_by_pk.ownr_ln, + driver_addr1: data.jobs_by_pk.ownr_addr1, + driver_state: data.jobs_by_pk.ownr_st, + driver_city: data.jobs_by_pk.ownr_city, + driver_zip: data.jobs_by_pk.ownr_zip, + driver_ph1: data.jobs_by_pk.ownr_ph1 + }); } + }, [data, form]); - return ( - - ); + if (error) { + notification["error"]({ + message: t("contracts.errors.fetchingjobinfo", { + error: JSON.stringify(error) + }) + }); + } + + return ( + + ); } diff --git a/client/src/components/contract-form/contract-form.component.jsx b/client/src/components/contract-form/contract-form.component.jsx index ae983a6c6..eeac43701 100644 --- a/client/src/components/contract-form/contract-form.component.jsx +++ b/client/src/components/contract-form/contract-form.component.jsx @@ -1,9 +1,9 @@ -import {WarningFilled} from "@ant-design/icons"; -import {Form, Input, InputNumber, Space} from "antd"; +import { WarningFilled } from "@ant-design/icons"; +import { Form, Input, InputNumber, Space } from "antd"; import dayjs from "../../utils/day"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {DateFormatter} from "../../utils/DateFormatter"; +import { useTranslation } from "react-i18next"; +import { DateFormatter } from "../../utils/DateFormatter"; //import ContractLicenseDecodeButton from "../contract-license-decode-button/contract-license-decode-button.component"; import ContractStatusSelector from "../contract-status-select/contract-status-select.component"; import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component"; @@ -11,361 +11,310 @@ import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; -import InputPhone, {PhoneItemFormatterValidation,} from "../form-items-formatted/phone-form-item.component"; +import InputPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import ContractFormJobPrefill from "./contract-form-job-prefill.component"; -export default function ContractFormComponent({ - form, - create = false, - selectedJobState, - selectedCar, - }) { - const {t} = useTranslation(); - return ( -
- - - {create ? null : ( - - - - )} - - - - - - - {create ? null : ( - - - - )} - {create && ( - p.scheduledreturn !== c.scheduledreturn} - > - {() => { - const insuranceOver = - selectedCar && - selectedCar.insuranceexpires && - dayjs(selectedCar.insuranceexpires) - .endOf("day") - .isBefore(dayjs(form.getFieldValue("scheduledreturn"))); - if (insuranceOver) - return ( - +export default function ContractFormComponent({ form, create = false, selectedJobState, selectedCar }) { + const { t } = useTranslation(); + return ( +
+ + + {create ? null : ( + + + + )} + + + + + + + {create ? null : ( + + + + )} + {create && ( + p.scheduledreturn !== c.scheduledreturn}> + {() => { + const insuranceOver = + selectedCar && + selectedCar.insuranceexpires && + dayjs(selectedCar.insuranceexpires) + .endOf("day") + .isBefore(dayjs(form.getFieldValue("scheduledreturn"))); + if (insuranceOver) + return ( + - - {t("contracts.labels.insuranceexpired")} + + {t("contracts.labels.insuranceexpired")} - - ); - return <>; - }} - - )} - - - - - {create && ( - - p.kmstart !== c.kmstart || p.scheduledreturn !== c.scheduledreturn - } - > - {() => { - const mileageOver = - selectedCar && selectedCar.nextservicekm - ? selectedCar.nextservicekm <= form.getFieldValue("kmstart") - : false; - const dueForService = - selectedCar && - selectedCar.nextservicedate && - dayjs(selectedCar.nextservicedate) - .endOf("day") - .isSameOrBefore( - dayjs(form.getFieldValue("scheduledreturn")) - ); - if (mileageOver || dueForService) - return ( - + + ); + return <>; + }} + + )} + + + + + + {create && ( + p.kmstart !== c.kmstart || p.scheduledreturn !== c.scheduledreturn}> + {() => { + const mileageOver = + selectedCar && selectedCar.nextservicekm + ? selectedCar.nextservicekm <= form.getFieldValue("kmstart") + : false; + const dueForService = + selectedCar && + selectedCar.nextservicedate && + dayjs(selectedCar.nextservicedate) + .endOf("day") + .isSameOrBefore(dayjs(form.getFieldValue("scheduledreturn"))); + if (mileageOver || dueForService) + return ( + - - {t("contracts.labels.cardueforservice")} + + {t("contracts.labels.cardueforservice")} - {`${ - selectedCar && selectedCar.nextservicekm - } km`} - - - {selectedCar && selectedCar.nextservicedate} - + {`${selectedCar && selectedCar.nextservicekm} km`} + + {selectedCar && selectedCar.nextservicedate} - - ); + + ); - return <>; - }} - - )} - {create ? null : ( - - - - )} - - - - - - - - - {create ? null : ( - - - - )} - + return <>; + }} + + )} + {create ? null : ( + + + + )} + + + + + + + + + {create ? null : ( + + + + )} + +
+ + {selectedJobState && (
- - {selectedJobState && ( -
- -
- )} - { - // - } -
+
- + )} + { + // + } +
+
+ + + + + p.driver_dlexpiry !== c.driver_dlexpiry || p.scheduledreturn !== c.scheduledreturn} + > + {() => { + const dlExpiresBeforeReturn = dayjs(form.getFieldValue("driver_dlexpiry")).isBefore( + dayjs(form.getFieldValue("scheduledreturn")) + ); + + return ( +
- - - - p.driver_dlexpiry !== c.driver_dlexpiry || - p.scheduledreturn !== c.scheduledreturn + label={t("contracts.fields.driver_dlexpiry")} + name="driver_dlexpiry" + rules={[ + { + required: true + //message: t("general.validation.required"), } + ]} > - {() => { - const dlExpiresBeforeReturn = dayjs( - form.getFieldValue("driver_dlexpiry") - ).isBefore(dayjs(form.getFieldValue("scheduledreturn"))); + + + {dlExpiresBeforeReturn && ( + + + {t("contracts.labels.dlexpirebeforereturn")} + + )} +
+ ); + }} +
- return ( -
- - - - {dlExpiresBeforeReturn && ( - - - {t("contracts.labels.dlexpirebeforereturn")} - - )} -
- ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - PhoneItemFormatterValidation(getFieldValue, "driver_ph1"), - ]} - > - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ); + + + + + + + + + + + + + + + + + + + + + + + + + PhoneItemFormatterValidation(getFieldValue, "driver_ph1") + ]} + > + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); } diff --git a/client/src/components/contract-job-block/contract-job-block.component.jsx b/client/src/components/contract-job-block/contract-job-block.component.jsx index e0d6af75b..526860b6b 100644 --- a/client/src/components/contract-job-block/contract-job-block.component.jsx +++ b/client/src/components/contract-job-block/contract-job-block.component.jsx @@ -1,33 +1,25 @@ -import {Card} from "antd"; +import { Card } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {Link} from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; import DataLabel from "../data-label/data-label.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; -export default function ContractJobBlock({job}) { - const {t} = useTranslation(); - return ( - - -
- - {(job && job.ro_number) || ""} - - - {`${(job && job.v_model_yr) || ""} ${ - (job && job.v_make_desc) || "" - } ${(job && job.v_model_desc) || ""}`} - - - - -
-
- - ); +export default function ContractJobBlock({ job }) { + const { t } = useTranslation(); + return ( + + +
+ {(job && job.ro_number) || ""} + + {`${(job && job.v_model_yr) || ""} ${(job && job.v_make_desc) || ""} ${(job && job.v_model_desc) || ""}`} + + + + +
+
+ + ); } diff --git a/client/src/components/contract-jobs/contract-jobs.component.jsx b/client/src/components/contract-jobs/contract-jobs.component.jsx index 84dfd6d4e..ea71c2538 100644 --- a/client/src/components/contract-jobs/contract-jobs.component.jsx +++ b/client/src/components/contract-jobs/contract-jobs.component.jsx @@ -1,201 +1,155 @@ -import {Card, Input, Table} from "antd"; -import React, {useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {alphaSort} from "../../utils/sorters"; +import { Card, Input, Table } from "antd"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { alphaSort } from "../../utils/sorters"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; -import {pageLimit} from "../../utils/config"; +import { pageLimit } from "../../utils/config"; -export default function ContractsJobsComponent({ - loading, - data, - selectedJob, - handleSelect, - }) { - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - search: "", - }); +export default function ContractsJobsComponent({ loading, data, selectedJob, handleSelect }) { + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" }, + search: "" + }); - const {t} = useTranslation(); + const { t } = useTranslation(); - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - width: "8%", - sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + width: "8%", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - render: (text, record) => ( - - {record.ro_number ? record.ro_number : t("general.labels.na")} - - ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - ellipsis: true, - sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), - width: "25%", - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, - render: (text, record) => , - }, - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", - width: "10%", - ellipsis: true, - sorter: (a, b) => alphaSort(a.status, b.status), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - render: (text, record) => { - return record.status || t("general.labels.na"); - }, - }, + render: (text, record) => {record.ro_number ? record.ro_number : t("general.labels.na")} + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), + width: "25%", + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + width: "10%", + ellipsis: true, + sorter: (a, b) => alphaSort(a.status, b.status), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + render: (text, record) => { + return record.status || t("general.labels.na"); + } + }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - width: "15%", - ellipsis: true, - render: (text, record) => { - return record.vehicleid ? ( - - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - - ) : ( - t("jobs.errors.novehicle") - ); - }, - }, - { - title: t("vehicles.fields.plate_no"), - dataIndex: "plate_no", - key: "plate_no", - width: "8%", - ellipsis: true, - sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), - sortOrder: - state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, - render: (text, record) => { - return record.plate_no ? ( - {record.plate_no} - ) : ( - t("general.labels.unknown") - ); - }, - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - width: "12%", - ellipsis: true, - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - render: (text, record) => { - return record.clm_no ? ( - {record.clm_no} - ) : ( - t("general.labels.unknown") - ); - }, - }, - ]; + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + width: "15%", + ellipsis: true, + render: (text, record) => { + return record.vehicleid ? ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + ) : ( + t("jobs.errors.novehicle") + ); + } + }, + { + title: t("vehicles.fields.plate_no"), + dataIndex: "plate_no", + key: "plate_no", + width: "8%", + ellipsis: true, + sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), + sortOrder: state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, + render: (text, record) => { + return record.plate_no ? {record.plate_no} : t("general.labels.unknown"); + } + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + width: "12%", + ellipsis: true, + sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), + sortOrder: state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, + render: (text, record) => { + return record.clm_no ? {record.clm_no} : t("general.labels.unknown"); + } + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - const filteredData = - state.search === "" - ? data - : data.filter( - (j) => - (j.ro_number || "") - .toString() - .toLowerCase() - .includes(state.search.toLowerCase()) || - (j.ownr_co_nm || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (j.ownr_fn || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (j.ownr_ln || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (j.clm_no || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (j.v_make_desc || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (j.v_model_desc || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (j.plate_no || "") - .toLowerCase() - .includes(state.search.toLowerCase()) - ); + const filteredData = + state.search === "" + ? data + : data.filter( + (j) => + (j.ro_number || "").toString().toLowerCase().includes(state.search.toLowerCase()) || + (j.ownr_co_nm || "").toLowerCase().includes(state.search.toLowerCase()) || + (j.ownr_fn || "").toLowerCase().includes(state.search.toLowerCase()) || + (j.ownr_ln || "").toLowerCase().includes(state.search.toLowerCase()) || + (j.clm_no || "").toLowerCase().includes(state.search.toLowerCase()) || + (j.v_make_desc || "").toLowerCase().includes(state.search.toLowerCase()) || + (j.v_model_desc || "").toLowerCase().includes(state.search.toLowerCase()) || + (j.plate_no || "").toLowerCase().includes(state.search.toLowerCase()) + ); - const defaultCurrent = useMemo(() => { - const page = - Math.floor( - (filteredData.findIndex((v) => v.id === selectedJob) || 0) / 10 - ) + 1; - if (page === 0) return 1; - return page; - }, [filteredData, selectedJob]); + const defaultCurrent = useMemo(() => { + const page = Math.floor((filteredData.findIndex((v) => v.id === selectedJob) || 0) / 10) + 1; + if (page === 0) return 1; + return page; + }, [filteredData, selectedJob]); - if (loading) return ; - return ( - setState({...state, search: e.target.value})} - /> + if (loading) return ; + return ( + setState({ ...state, search: e.target.value })} + /> + } + > +
{ + return { + onClick: (event) => { + handleSelect(record); } - > -
{ - return { - onClick: (event) => { - handleSelect(record); - }, - }; - }} - /> - - ); + }; + }} + /> + + ); } diff --git a/client/src/components/contract-jobs/contract-jobs.container.jsx b/client/src/components/contract-jobs/contract-jobs.container.jsx index 926d89330..1db612a03 100644 --- a/client/src/components/contract-jobs/contract-jobs.container.jsx +++ b/client/src/components/contract-jobs/contract-jobs.container.jsx @@ -1,41 +1,41 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {QUERY_ALL_ACTIVE_JOBS} from "../../graphql/jobs.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import ContractJobsComponent from "./contract-jobs.component"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); -export function ContractJobsContainer({selectedJobState, bodyshop}) { - const {loading, error, data} = useQuery(QUERY_ALL_ACTIVE_JOBS, { - variables: { - statuses: bodyshop.md_ro_statuses.active_statuses || ["Open"], - isConverted: true, - }, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); - const [selectedJob, setSelectedJob] = selectedJobState; +export function ContractJobsContainer({ selectedJobState, bodyshop }) { + const { loading, error, data } = useQuery(QUERY_ALL_ACTIVE_JOBS, { + variables: { + statuses: bodyshop.md_ro_statuses.active_statuses || ["Open"], + isConverted: true + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const [selectedJob, setSelectedJob] = selectedJobState; - const handleSelect = (record) => { - setSelectedJob(record.id); - }; + const handleSelect = (record) => { + setSelectedJob(record.id); + }; - if (error) return ; - return ( - - ); + if (error) return ; + return ( + + ); } export default connect(mapStateToProps, null)(ContractJobsContainer); diff --git a/client/src/components/contract-license-decode-button/contract-license-decode-button.component.jsx b/client/src/components/contract-license-decode-button/contract-license-decode-button.component.jsx index 49da3a5b2..f6bbf7e02 100644 --- a/client/src/components/contract-license-decode-button/contract-license-decode-button.component.jsx +++ b/client/src/components/contract-license-decode-button/contract-license-decode-button.component.jsx @@ -1,123 +1,101 @@ -import {Button, Input, Modal, Typography} from "antd"; +import { Button, Input, Modal, Typography } from "antd"; import dayjs from "../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import aamva from "../../utils/aamva"; import DataLabel from "../data-label/data-label.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { logImEXEvent } from "../../firebase/firebase.utils"; -export default function ContractLicenseDecodeButton({form}) { - const {t} = useTranslation(); - const [modalVisible, setModalVisible] = useState(false); - const [loading, setLoading] = useState(false); - const [decodedBarcode, setDecodedBarcode] = useState(null); +export default function ContractLicenseDecodeButton({ form }) { + const { t } = useTranslation(); + const [modalVisible, setModalVisible] = useState(false); + const [loading, setLoading] = useState(false); + const [decodedBarcode, setDecodedBarcode] = useState(null); - const handleDecode = (e) => { - logImEXEvent("contract_license_decode"); - setLoading(true); - const aamvaParse = aamva.parse(e.currentTarget.value); - setDecodedBarcode(aamvaParse); - setLoading(false); + const handleDecode = (e) => { + logImEXEvent("contract_license_decode"); + setLoading(true); + const aamvaParse = aamva.parse(e.currentTarget.value); + setDecodedBarcode(aamvaParse); + setLoading(false); + }; + + const handleInsertForm = () => { + logImEXEvent("contract_license_decode_fill_form"); + + const values = { + driver_dlnumber: decodedBarcode.dl, + driver_dlexpiry: dayjs(`20${decodedBarcode.expiration_date}${dayjs(decodedBarcode.birthday).format("DD")}`), + driver_dlst: decodedBarcode.state, + driver_fn: decodedBarcode.name.first, + driver_ln: decodedBarcode.name.last, + driver_addr1: decodedBarcode.address, + driver_city: decodedBarcode.city, + driver_state: decodedBarcode.state, + driver_zip: decodedBarcode.postal_code, + driver_dob: dayjs(decodedBarcode.birthday) }; - const handleInsertForm = () => { - logImEXEvent("contract_license_decode_fill_form"); + form.setFieldsValue(values); + setModalVisible(false); + setDecodedBarcode(null); + }; + const handleClick = () => { + setModalVisible(true); + }; + const handleCancel = () => { + setModalVisible(false); + }; - const values = { - driver_dlnumber: decodedBarcode.dl, - driver_dlexpiry: dayjs( - `20${decodedBarcode.expiration_date}${dayjs( - decodedBarcode.birthday - ).format("DD")}` - ), - driver_dlst: decodedBarcode.state, - driver_fn: decodedBarcode.name.first, - driver_ln: decodedBarcode.name.last, - driver_addr1: decodedBarcode.address, - driver_city: decodedBarcode.city, - driver_state: decodedBarcode.state, - driver_zip: decodedBarcode.postal_code, - driver_dob: dayjs(decodedBarcode.birthday), - }; - - form.setFieldsValue(values); - setModalVisible(false); - setDecodedBarcode(null); - }; - const handleClick = () => { - setModalVisible(true); - }; - const handleCancel = () => { - setModalVisible(false); - }; - - return ( + return ( +
+
- +
+ { + if (!loading) setLoading(true); + }} + onPressEnter={handleDecode} + /> +
+ + {decodedBarcode ? ( +
+ {decodedBarcode.state} + {decodedBarcode.dl} + {decodedBarcode.name.first} + {decodedBarcode.name.last} + {decodedBarcode.address} + {decodedBarcode.address} + + {dayjs(`20${decodedBarcode.expiration_date}${dayjs(decodedBarcode.birthday).format("DD")}`).format( + "MM/DD/YYYY" + )} + + + {dayjs(decodedBarcode.birthday).format("MM/DD/YYYY")} +
-
- { - if (!loading) setLoading(true); - }} - onPressEnter={handleDecode} - /> -
- - {decodedBarcode ? ( -
- - {decodedBarcode.state} - - - {decodedBarcode.dl} - - - {decodedBarcode.name.first} - - - {decodedBarcode.name.last} - - - {decodedBarcode.address} - - - {decodedBarcode.address} - - - {dayjs( - `20${decodedBarcode.expiration_date}${dayjs( - decodedBarcode.birthday - ).format("DD")}` - ).format("MM/DD/YYYY")} - - - {dayjs(decodedBarcode.birthday).format("MM/DD/YYYY")} - -
- - {t("contracts.labels.correctdataonform")} - -
-
- ) : ( -
{t("contracts.labels.waitingforscan")}
- )} -
+ {t("contracts.labels.correctdataonform")}
- - +
+ ) : ( +
{t("contracts.labels.waitingforscan")}
+ )} +
- ); +
+ +
+ ); } diff --git a/client/src/components/contract-status-select/contract-status-select.component.jsx b/client/src/components/contract-status-select/contract-status-select.component.jsx index 72a8a2e5c..8bd048195 100644 --- a/client/src/components/contract-status-select/contract-status-select.component.jsx +++ b/client/src/components/contract-status-select/contract-status-select.component.jsx @@ -1,33 +1,31 @@ -import React, {forwardRef, useEffect, useState} from "react"; -import {Select} from "antd"; -import {useTranslation} from "react-i18next"; +import React, { forwardRef, useEffect, useState } from "react"; +import { Select } from "antd"; +import { useTranslation } from "react-i18next"; -const {Option} = Select; +const { Option } = Select; -const ContractStatusComponent = ({value, onChange}, ref) => { - const [option, setOption] = useState(value); - const {t} = useTranslation(); +const ContractStatusComponent = ({ value, onChange }, ref) => { + const [option, setOption] = useState(value); + const { t } = useTranslation(); - useEffect(() => { - if (value !== option && onChange) { - onChange(option); - } - }, [value, option, onChange]); + useEffect(() => { + if (value !== option && onChange) { + onChange(option); + } + }, [value, option, onChange]); - return ( - - ); + return ( + + ); }; export default forwardRef(ContractStatusComponent); diff --git a/client/src/components/contracts-find-modal/contracts-find-modal.component.jsx b/client/src/components/contracts-find-modal/contracts-find-modal.component.jsx index 4bd08369a..52a216e7d 100644 --- a/client/src/components/contracts-find-modal/contracts-find-modal.component.jsx +++ b/client/src/components/contracts-find-modal/contracts-find-modal.component.jsx @@ -1,37 +1,37 @@ -import {Form, Input} from "antd"; +import { Form, Input } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); export default connect(mapStateToProps, null)(PartsReceiveModalComponent); -export function PartsReceiveModalComponent({bodyshop, form}) { - const {t} = useTranslation(); +export function PartsReceiveModalComponent({ bodyshop, form }) { + const { t } = useTranslation(); - return ( -
- - - - - - -
- ); + return ( +
+ + + + + + +
+ ); } diff --git a/client/src/components/contracts-find-modal/contracts-find-modal.container.jsx b/client/src/components/contracts-find-modal/contracts-find-modal.container.jsx index e7398f6a3..eb3ae6fd4 100644 --- a/client/src/components/contracts-find-modal/contracts-find-modal.container.jsx +++ b/client/src/components/contracts-find-modal/contracts-find-modal.container.jsx @@ -1,169 +1,143 @@ -import {useLazyQuery} from "@apollo/client"; -import {Button, Form, Modal, Table} from "antd"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {FIND_CONTRACT} from "../../graphql/cccontracts.queries"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectContractFinder} from "../../redux/modals/modals.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; +import { useLazyQuery } from "@apollo/client"; +import { Button, Form, Modal, Table } from "antd"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { FIND_CONTRACT } from "../../graphql/cccontracts.queries"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectContractFinder } from "../../redux/modals/modals.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; import ContractsFindModalComponent from "./contracts-find-modal.component"; import AlertComponent from "../alert/alert.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - contractFinderModal: selectContractFinder, + bodyshop: selectBodyshop, + contractFinderModal: selectContractFinder }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("contractFinder")), + toggleModalVisible: () => dispatch(toggleModalVisible("contractFinder")) }); export function ContractsFindModalContainer({ - contractFinderModal, - toggleModalVisible, + contractFinderModal, + toggleModalVisible, - bodyshop, - }) { - const {t} = useTranslation(); + bodyshop +}) { + const { t } = useTranslation(); - const {open} = contractFinderModal; + const { open } = contractFinderModal; - const [form] = Form.useForm(); + const [form] = Form.useForm(); - // const [updateJobLines] = useMutation(UPDATE_JOB_LINE); - const [callSearch, {loading, error, data}] = useLazyQuery(FIND_CONTRACT); - const handleFinish = async (values) => { - logImEXEvent("contract_finder_search"); + // const [updateJobLines] = useMutation(UPDATE_JOB_LINE); + const [callSearch, { loading, error, data }] = useLazyQuery(FIND_CONTRACT); + const handleFinish = async (values) => { + logImEXEvent("contract_finder_search"); - //Execute contract find + //Execute contract find - callSearch({ - variables: { - plate: - (values.plate && values.plate !== "" && values.plate) || undefined, - time: values.time, + callSearch({ + variables: { + plate: (values.plate && values.plate !== "" && values.plate) || undefined, + time: values.time + } + }); + }; + + useEffect(() => { + if (open) { + form.resetFields(); + } + }, [open, form]); + + return ( + toggleModalVisible()} + onOk={() => toggleModalVisible()} + destroyOnClose + forceRender + > +
+ + + {error && } +
( + {record.agreementnumber || ""} + ) }, - }); - }; - - useEffect(() => { - if (open) { - form.resetFields(); - } - }, [open, form]); - - return ( - toggleModalVisible()} - onOk={() => toggleModalVisible()} - destroyOnClose - forceRender - > - - - - {error && ( - - )} -
( - - {record.agreementnumber || ""} - - ), - }, - { - title: t("jobs.fields.ro_number"), - dataIndex: "job.ro_number", - key: "job.ro_number", - render: (text, record) => ( - - {record.job.ro_number || ""} - - ), - }, - { - title: t("contracts.fields.driver"), - dataIndex: "driver_ln", - key: "driver_ln", - render: (text, record) => - `${record.driver_fn || ""} ${record.driver_ln || ""}`, - }, - { - title: t("contracts.labels.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - render: (text, record) => ( - {`${ - record.courtesycar.year - } ${record.courtesycar.make} ${record.courtesycar.model} ${ - record.courtesycar.plate - ? `(${record.courtesycar.plate})` - : "" - }`} - ), - }, - { - title: t("contracts.fields.status"), - dataIndex: "status", - render: (text, record) => t(record.status), - }, - { - title: t("contracts.fields.start"), - dataIndex: "start", - key: "start", - render: (text, record) => ( - {record.start} - ), - }, - { - title: t("contracts.fields.scheduledreturn"), - dataIndex: "scheduledreturn", - key: "scheduledreturn", - render: (text, record) => ( - {record.scheduledreturn} - ), - }, - { - title: t("contracts.fields.actualreturn"), - dataIndex: "actualreturn", - key: "actualreturn", - render: (text, record) => ( - {record.actualreturn} - ), - }, - ]} - rowKey="id" - dataSource={data && data.cccontracts} - /> - - - ); + { + title: t("jobs.fields.ro_number"), + dataIndex: "job.ro_number", + key: "job.ro_number", + render: (text, record) => {record.job.ro_number || ""} + }, + { + title: t("contracts.fields.driver"), + dataIndex: "driver_ln", + key: "driver_ln", + render: (text, record) => `${record.driver_fn || ""} ${record.driver_ln || ""}` + }, + { + title: t("contracts.labels.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + render: (text, record) => ( + {`${ + record.courtesycar.year + } ${record.courtesycar.make} ${record.courtesycar.model} ${ + record.courtesycar.plate ? `(${record.courtesycar.plate})` : "" + }`} + ) + }, + { + title: t("contracts.fields.status"), + dataIndex: "status", + render: (text, record) => t(record.status) + }, + { + title: t("contracts.fields.start"), + dataIndex: "start", + key: "start", + render: (text, record) => {record.start} + }, + { + title: t("contracts.fields.scheduledreturn"), + dataIndex: "scheduledreturn", + key: "scheduledreturn", + render: (text, record) => {record.scheduledreturn} + }, + { + title: t("contracts.fields.actualreturn"), + dataIndex: "actualreturn", + key: "actualreturn", + render: (text, record) => {record.actualreturn} + } + ]} + rowKey="id" + dataSource={data && data.cccontracts} + /> + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ContractsFindModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(ContractsFindModalContainer); diff --git a/client/src/components/contracts-list/contracts-list.component.jsx b/client/src/components/contracts-list/contracts-list.component.jsx index 4cd2a8a38..a42afe90a 100644 --- a/client/src/components/contracts-list/contracts-list.component.jsx +++ b/client/src/components/contracts-list/contracts-list.component.jsx @@ -1,224 +1,188 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Input, Space, Table, Typography} from "antd"; +import { SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Input, Space, Table, Typography } from "antd"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; -import {alphaSort} from "../../utils/sorters"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; +import { alphaSort } from "../../utils/sorters"; import ContractsFindModalContainer from "../contracts-find-modal/contracts-find-modal.container"; import dayjs from "../../utils/day"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {pageLimit} from "../../utils/config"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { pageLimit } from "../../utils/config"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) - setContractFinderContext: (context) => - dispatch(setModalContext({context: context, modal: "contractFinder"})), + //setUserLanguage: language => dispatch(setUserLanguage(language)) + setContractFinderContext: (context) => dispatch(setModalContext({ context: context, modal: "contractFinder" })) }); export default connect(mapStateToProps, mapDispatchToProps)(ContractsList); -export function ContractsList({ - bodyshop, - loading, - contracts, - refetch, - total, - setContractFinderContext, - }) { - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); - const history = useNavigate(); - const search = queryString.parse(useLocation().search); - const {page} = search; +export function ContractsList({ bodyshop, loading, contracts, refetch, total, setContractFinderContext }) { + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); + const history = useNavigate(); + const search = queryString.parse(useLocation().search); + const { page } = search; - const {t} = useTranslation(); + const { t } = useTranslation(); - const columns = [ - { - title: t("contracts.fields.agreementnumber"), - dataIndex: "agreementnumber", - key: "agreementnumber", - sorter: (a, b) => a.agreementnumber - b.agreementnumber, - sortOrder: - state.sortedInfo.columnKey === "agreementnumber" && - state.sortedInfo.order, - render: (text, record) => ( - - {record.agreementnumber || ""} - - ), - }, - { - title: t("jobs.fields.ro_number"), - dataIndex: "job.ro_number", - key: "job.ro_number", - render: (text, record) => ( - - {record.job.ro_number || ""} - - ), - }, - { - title: t("contracts.fields.driver"), - dataIndex: "driver_ln", - key: "driver_ln", - sorter: (a, b) => alphaSort(a.driver_ln, b.driver_ln), - sortOrder: - state.sortedInfo.columnKey === "driver_ln" && state.sortedInfo.order, - render: (text, record) => - bodyshop.last_name_first - ? `${record.driver_ln || ""}, ${record.driver_fn || ""}` - : `${record.driver_fn || ""} ${record.driver_ln || ""}`, - }, - { - title: t("contracts.labels.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - //sorter: (a, b) => alphaSort(a.status, b.status), - //sortOrder: - // state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - render: (text, record) => ( - {`${ - record.courtesycar.year - } ${record.courtesycar.make} ${record.courtesycar.model}${ - record.courtesycar.plate ? ` (${record.courtesycar.plate})` : "" - }${ - record.courtesycar.fleetnumber - ? ` (${record.courtesycar.fleetnumber})` - : "" - }`} - ), - }, - { - title: t("contracts.fields.status"), - dataIndex: "status", - key: "status", - sorter: (a, b) => alphaSort(a.status, b.status), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - render: (text, record) => t(record.status), - }, - { - title: t("contracts.fields.start"), - dataIndex: "start", - key: "start", - sorter: (a, b) => alphaSort(a.start, b.start), - sortOrder: - state.sortedInfo.columnKey === "start" && state.sortedInfo.order, - render: (text, record) => ( - {record.start} - ), - }, - { - title: t("contracts.fields.scheduledreturn"), - dataIndex: "scheduledreturn", - key: "scheduledreturn", - sorter: (a, b) => alphaSort(a.scheduledreturn, b.scheduledreturn), - sortOrder: - state.sortedInfo.columnKey === "scheduledreturn" && - state.sortedInfo.order, - render: (text, record) => ( - {record.scheduledreturn} - ), - }, - { - title: t("contracts.fields.actualreturn"), - dataIndex: "actualreturn", - key: "actualreturn", - sorter: (a, b) => alphaSort(a.actualreturn, b.actualreturn), - sortOrder: - state.sortedInfo.columnKey === "actualreturn" && state.sortedInfo.order, - render: (text, record) => ( - {record.actualreturn} - ), - }, - { - title: t("contracts.fields.length"), - dataIndex: "length", - key: "length", + const columns = [ + { + title: t("contracts.fields.agreementnumber"), + dataIndex: "agreementnumber", + key: "agreementnumber", + sorter: (a, b) => a.agreementnumber - b.agreementnumber, + sortOrder: state.sortedInfo.columnKey === "agreementnumber" && state.sortedInfo.order, + render: (text, record) => ( + {record.agreementnumber || ""} + ) + }, + { + title: t("jobs.fields.ro_number"), + dataIndex: "job.ro_number", + key: "job.ro_number", + render: (text, record) => {record.job.ro_number || ""} + }, + { + title: t("contracts.fields.driver"), + dataIndex: "driver_ln", + key: "driver_ln", + sorter: (a, b) => alphaSort(a.driver_ln, b.driver_ln), + sortOrder: state.sortedInfo.columnKey === "driver_ln" && state.sortedInfo.order, + render: (text, record) => + bodyshop.last_name_first + ? `${record.driver_ln || ""}, ${record.driver_fn || ""}` + : `${record.driver_fn || ""} ${record.driver_ln || ""}` + }, + { + title: t("contracts.labels.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + //sorter: (a, b) => alphaSort(a.status, b.status), + //sortOrder: + // state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + render: (text, record) => ( + {`${ + record.courtesycar.year + } ${record.courtesycar.make} ${record.courtesycar.model}${ + record.courtesycar.plate ? ` (${record.courtesycar.plate})` : "" + }${record.courtesycar.fleetnumber ? ` (${record.courtesycar.fleetnumber})` : ""}`} + ) + }, + { + title: t("contracts.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => alphaSort(a.status, b.status), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + render: (text, record) => t(record.status) + }, + { + title: t("contracts.fields.start"), + dataIndex: "start", + key: "start", + sorter: (a, b) => alphaSort(a.start, b.start), + sortOrder: state.sortedInfo.columnKey === "start" && state.sortedInfo.order, + render: (text, record) => {record.start} + }, + { + title: t("contracts.fields.scheduledreturn"), + dataIndex: "scheduledreturn", + key: "scheduledreturn", + sorter: (a, b) => alphaSort(a.scheduledreturn, b.scheduledreturn), + sortOrder: state.sortedInfo.columnKey === "scheduledreturn" && state.sortedInfo.order, + render: (text, record) => {record.scheduledreturn} + }, + { + title: t("contracts.fields.actualreturn"), + dataIndex: "actualreturn", + key: "actualreturn", + sorter: (a, b) => alphaSort(a.actualreturn, b.actualreturn), + sortOrder: state.sortedInfo.columnKey === "actualreturn" && state.sortedInfo.order, + render: (text, record) => {record.actualreturn} + }, + { + title: t("contracts.fields.length"), + dataIndex: "length", + key: "length", - render: (text, record) => - (record.actualreturn && - record.start && - `${dayjs(record.actualreturn) - .diff(dayjs(record.start), "day", true) - .toFixed(1)} days`) || - "", - }, - ]; + render: (text, record) => + (record.actualreturn && + record.start && + `${dayjs(record.actualreturn).diff(dayjs(record.start), "day", true).toFixed(1)} days`) || + "" + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - search.page = pagination.current; - search.sortcolumn = sorter.columnKey; - search.sortorder = sorter.order; - history({search: queryString.stringify(search)}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + search.page = pagination.current; + search.sortcolumn = sorter.columnKey; + search.sortorder = sorter.order; + history({ search: queryString.stringify(search) }); + }; - return ( - - {search.search && ( - <> - - {t("general.labels.searchresults", {search: search.search})} - - - - )} - - - { - search.search = value; - history({search: queryString.stringify(search)}); - }} - /> - - } - > - -
+ {search.search && ( + <> + + {t("general.labels.searchresults", { search: search.search })} + + + + )} + + + { + search.search = value; + history({ search: queryString.stringify(search) }); + }} + /> + + } + > + +
+ + ); } diff --git a/client/src/components/contracts-rates-change-button/contracts-rates-change-button.component.jsx b/client/src/components/contracts-rates-change-button/contracts-rates-change-button.component.jsx index 0b8508255..922636804 100644 --- a/client/src/components/contracts-rates-change-button/contracts-rates-change-button.component.jsx +++ b/client/src/components/contracts-rates-change-button/contracts-rates-change-button.component.jsx @@ -1,42 +1,38 @@ -import {DownOutlined} from "@ant-design/icons"; -import {Dropdown} from "antd"; +import { DownOutlined } from "@ant-design/icons"; +import { Dropdown } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -export function ContractsRatesChangeButton({disabled, form, bodyshop}) { - const {t} = useTranslation(); +export function ContractsRatesChangeButton({ disabled, form, bodyshop }) { + const { t } = useTranslation(); - const handleClick = ({item, key, keyPath}) => { - const {label, ...rate} = item.props.value; - form.setFieldsValue(rate); - }; + const handleClick = ({ item, key, keyPath }) => { + const { label, ...rate } = item.props.value; + form.setFieldsValue(rate); + }; - const menuItems = bodyshop.md_ccc_rates.map((i, idx) => ({ - key: idx, - label: i.label, - value: i, - })); + const menuItems = bodyshop.md_ccc_rates.map((i, idx) => ({ + key: idx, + label: i.label, + value: i + })); - const menu = {items: menuItems, onClick: handleClick}; + const menu = { items: menuItems, onClick: handleClick }; - return ( - - e.preventDefault()} - > - {t("contracts.actions.changerate")} - - - ); + return ( + + e.preventDefault()}> + {t("contracts.actions.changerate")} + + + ); } export default connect(mapStateToProps, null)(ContractsRatesChangeButton); diff --git a/client/src/components/courtesy-car-contract-list/courtesy-car-contract-list.component.jsx b/client/src/components/courtesy-car-contract-list/courtesy-car-contract-list.component.jsx index d4b6f846c..34c6e3bfc 100644 --- a/client/src/components/courtesy-car-contract-list/courtesy-car-contract-list.component.jsx +++ b/client/src/components/courtesy-car-contract-list/courtesy-car-contract-list.component.jsx @@ -1,104 +1,92 @@ -import {Card, Table} from "antd"; +import { Card, Table } from "antd"; import queryString from "query-string"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {DateFormatter} from "../../utils/DateFormatter"; -import {alphaSort} from "../../utils/sorters"; -import {pageLimit} from "../../utils/config"; +import { useTranslation } from "react-i18next"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { DateFormatter } from "../../utils/DateFormatter"; +import { alphaSort } from "../../utils/sorters"; +import { pageLimit } from "../../utils/config"; -export default function CourtesyCarContractListComponent({ - contracts, - totalContracts, - }) { - const search = queryString.parse(useLocation().search); - const {page, sortcolumn, sortorder} = search; - const history = useNavigate(); +export default function CourtesyCarContractListComponent({ contracts, totalContracts }) { + const search = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder } = search; + const history = useNavigate(); - const {t} = useTranslation(); + const { t } = useTranslation(); - const columns = [ - { - title: t("contracts.fields.agreementnumber"), - dataIndex: "agreementnumber", - key: "agreementnumber", - sorter: (a, b) => a.agreementnumber - b.agreementnumber, - sortOrder: sortcolumn === "agreementnumber" && sortorder, - render: (text, record) => ( - - {record.agreementnumber || ""} - - ), - }, - { - title: t("jobs.fields.ro_number"), - dataIndex: "job.ro_number", - key: "job.ro_number", + const columns = [ + { + title: t("contracts.fields.agreementnumber"), + dataIndex: "agreementnumber", + key: "agreementnumber", + sorter: (a, b) => a.agreementnumber - b.agreementnumber, + sortOrder: sortcolumn === "agreementnumber" && sortorder, + render: (text, record) => ( + {record.agreementnumber || ""} + ) + }, + { + title: t("jobs.fields.ro_number"), + dataIndex: "job.ro_number", + key: "job.ro_number", - render: (text, record) => ( - - {record.job.ro_number || ""} - - ), - }, - { - title: t("contracts.fields.driver"), - dataIndex: "driver_ln", - key: "driver_ln", + render: (text, record) => {record.job.ro_number || ""} + }, + { + title: t("contracts.fields.driver"), + dataIndex: "driver_ln", + key: "driver_ln", - render: (text, record) => - `${record.driver_fn || ""} ${record.driver_ln || ""}`, - }, - { - title: t("contracts.fields.status"), - dataIndex: "status", - key: "status", - sorter: (a, b) => alphaSort(a.status, b.status), - sortOrder: sortcolumn === "status" && sortorder, - render: (text, record) => t(record.status), - }, - { - title: t("contracts.fields.start"), - dataIndex: "start", - key: "start", - sorter: (a, b) => alphaSort(a.start, b.start), - sortOrder: sortcolumn === "start" && sortorder, - render: (text, record) => {record.start}, - }, - { - title: t("contracts.fields.scheduledreturn"), - dataIndex: "scheduledreturn", - key: "scheduledreturn", - sorter: (a, b) => a.scheduledreturn - b.scheduledreturn, - sortOrder: sortcolumn === "scheduledreturn" && sortorder, - render: (text, record) => ( - {record.scheduledreturn} - ), - }, - ]; + render: (text, record) => `${record.driver_fn || ""} ${record.driver_ln || ""}` + }, + { + title: t("contracts.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => alphaSort(a.status, b.status), + sortOrder: sortcolumn === "status" && sortorder, + render: (text, record) => t(record.status) + }, + { + title: t("contracts.fields.start"), + dataIndex: "start", + key: "start", + sorter: (a, b) => alphaSort(a.start, b.start), + sortOrder: sortcolumn === "start" && sortorder, + render: (text, record) => {record.start} + }, + { + title: t("contracts.fields.scheduledreturn"), + dataIndex: "scheduledreturn", + key: "scheduledreturn", + sorter: (a, b) => a.scheduledreturn - b.scheduledreturn, + sortOrder: sortcolumn === "scheduledreturn" && sortorder, + render: (text, record) => {record.scheduledreturn} + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - search.page = pagination.current; - search.sortcolumn = sorter.columnKey; - search.sortorder = sorter.order; - history({search: queryString.stringify(search)}); - }; + const handleTableChange = (pagination, filters, sorter) => { + search.page = pagination.current; + search.sortcolumn = sorter.columnKey; + search.sortorder = sorter.order; + history({ search: queryString.stringify(search) }); + }; - return ( - -
- - ); + return ( + +
+ + ); } diff --git a/client/src/components/courtesy-car-form/courtesy-car-form.component.jsx b/client/src/components/courtesy-car-form/courtesy-car-form.component.jsx index c738a87fb..b08e53db6 100644 --- a/client/src/components/courtesy-car-form/courtesy-car-form.component.jsx +++ b/client/src/components/courtesy-car-form/courtesy-car-form.component.jsx @@ -1,12 +1,12 @@ -import {WarningFilled} from "@ant-design/icons"; -import {useApolloClient} from "@apollo/client"; -import {Button, Form, Input, InputNumber, Space} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import { WarningFilled } from "@ant-design/icons"; +import { useApolloClient } from "@apollo/client"; +import { Button, Form, Input, InputNumber, Space } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; import dayjs from "../../utils/day"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {CHECK_CC_FLEET_NUMBER} from "../../graphql/courtesy-car.queries"; -import {DateFormatter} from "../../utils/DateFormatter"; +import { useTranslation } from "react-i18next"; +import { CHECK_CC_FLEET_NUMBER } from "../../graphql/courtesy-car.queries"; +import { DateFormatter } from "../../utils/DateFormatter"; import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component"; import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component"; import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component"; @@ -15,352 +15,306 @@ import FormDatePicker from "../form-date-picker/form-date-picker.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; -export default function CourtesyCarCreateFormComponent({form, saveLoading}) { - const {t} = useTranslation(); - const client = useApolloClient(); +export default function CourtesyCarCreateFormComponent({ form, saveLoading }) { + const { t } = useTranslation(); + const client = useApolloClient(); - return ( -
- form.submit()} - > - {t("general.actions.save")} - + return ( +
+ form.submit()}> + {t("general.actions.save")} + + } + /> + + {/* */} + + + + + + + + + + + + + + + + + + + + + + + + + ({ + async validator(rule, value) { + if (value) { + const response = await client.query({ + query: CHECK_CC_FLEET_NUMBER, + variables: { + name: value + } + }); + + if (response.data.courtesycars_aggregate.aggregate.count === 0) { + return Promise.resolve(); + } else if ( + response.data.courtesycars_aggregate.nodes.length === 1 && + response.data.courtesycars_aggregate.nodes[0].id === form.getFieldValue("id") + ) { + return Promise.resolve(); + } + return Promise.reject(t("courtesycars.labels.uniquefleet")); + } else { + return Promise.resolve(); } - /> + } + }) + ]} + > + + + + + + + + + + + + + + + - {/* */} - - - - - - - - - - - - - - - - - - - - - - - - - ({ - async validator(rule, value) { - if (value) { - const response = await client.query({ - query: CHECK_CC_FLEET_NUMBER, - variables: { - name: value, - }, - }); - - if ( - response.data.courtesycars_aggregate.aggregate.count === 0 - ) { - return Promise.resolve(); - } else if ( - response.data.courtesycars_aggregate.nodes.length === 1 && - response.data.courtesycars_aggregate.nodes[0].id === - form.getFieldValue("id") - ) { - return Promise.resolve(); - } - return Promise.reject(t("courtesycars.labels.uniquefleet")); - } else { - return Promise.resolve(); - } - }, - }), - ]} - > - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - p.mileage !== c.mileage || p.nextservicekm !== c.nextservicekm - } - > - {() => { - const nextservicekm = form.getFieldValue("nextservicekm"); - const mileageOver = nextservicekm - ? nextservicekm <= form.getFieldValue("mileage") - : false; - if (mileageOver) - return ( - + + + + + + + + + + +
+ + + + p.mileage !== c.mileage || p.nextservicekm !== c.nextservicekm}> + {() => { + const nextservicekm = form.getFieldValue("nextservicekm"); + const mileageOver = nextservicekm ? nextservicekm <= form.getFieldValue("mileage") : false; + if (mileageOver) + return ( + - - {t("contracts.labels.cardueforservice")} + + {t("contracts.labels.cardueforservice")} - {`${nextservicekm} km`} - - ); + {`${nextservicekm} km`} + + ); - return <>; - }} - -
-
- - - - p.nextservicedate !== c.nextservicedate} - > - {() => { - const nextservicedate = form.getFieldValue("nextservicedate"); - const dueForService = - nextservicedate && - dayjs(nextservicedate).endOf("day").isSameOrBefore(dayjs()); + return <>; + }} + +
+
+ + + + p.nextservicedate !== c.nextservicedate}> + {() => { + const nextservicedate = form.getFieldValue("nextservicedate"); + const dueForService = nextservicedate && dayjs(nextservicedate).endOf("day").isSameOrBefore(dayjs()); - if (dueForService) - return ( - + if (dueForService) + return ( + - - {t("contracts.labels.cardueforservice")} + + {t("contracts.labels.cardueforservice")} - + {nextservicedate} - - ); + + ); - return <>; - }} - -
- - - - - - -
- - - - - p.registrationexpires !== c.registrationexpires - } - > - {() => { - const expires = form.getFieldValue("registrationexpires"); - - const dateover = - expires && dayjs(expires).endOf("day").isBefore(dayjs()); - - if (dateover) - return ( - - - - {t("contracts.labels.dateinpast")} - - - ); - - return <>; - }} - -
-
- - - - p.insuranceexpires !== c.insuranceexpires} - > - {() => { - const expires = form.getFieldValue("insuranceexpires"); - - const dateover = - expires && dayjs(expires).endOf("day").isBefore(dayjs()); - - if (dateover) - return ( - - - - {t("contracts.labels.dateinpast")} - - - ); - - return <>; - }} - -
- - - -
+ return <>; + }} +
- ); + + + + + + +
+ + + + p.registrationexpires !== c.registrationexpires}> + {() => { + const expires = form.getFieldValue("registrationexpires"); + + const dateover = expires && dayjs(expires).endOf("day").isBefore(dayjs()); + + if (dateover) + return ( + + + + {t("contracts.labels.dateinpast")} + + + ); + + return <>; + }} + +
+
+ + + + p.insuranceexpires !== c.insuranceexpires}> + {() => { + const expires = form.getFieldValue("insuranceexpires"); + + const dateover = expires && dayjs(expires).endOf("day").isBefore(dayjs()); + + if (dateover) + return ( + + + + {t("contracts.labels.dateinpast")} + + + ); + + return <>; + }} + +
+ + + +
+
+ ); } diff --git a/client/src/components/courtesy-car-fuel-select/courtesy-car-fuel-select.component.jsx b/client/src/components/courtesy-car-fuel-select/courtesy-car-fuel-select.component.jsx index a76193568..ffe1c1f97 100644 --- a/client/src/components/courtesy-car-fuel-select/courtesy-car-fuel-select.component.jsx +++ b/client/src/components/courtesy-car-fuel-select/courtesy-car-fuel-select.component.jsx @@ -1,66 +1,66 @@ -import {Slider} from "antd"; -import React, {forwardRef} from "react"; -import {useTranslation} from "react-i18next"; +import { Slider } from "antd"; +import React, { forwardRef } from "react"; +import { useTranslation } from "react-i18next"; const CourtesyCarFuelComponent = (props, ref) => { - const {t} = useTranslation(); + const { t } = useTranslation(); - const marks = { - 0: { - style: { - color: "#f50", - }, - label: {t("courtesycars.labels.fuel.empty")}, - }, - 13: t("courtesycars.labels.fuel.18"), - 25: t("courtesycars.labels.fuel.14"), - 38: t("courtesycars.labels.fuel.38"), - 50: t("courtesycars.labels.fuel.12"), - 63: t("courtesycars.labels.fuel.58"), - 75: t("courtesycars.labels.fuel.34"), - 88: t("courtesycars.labels.fuel.78"), - 100: { - style: { - color: "#008000", - }, - label: {t("courtesycars.labels.fuel.full")}, - }, - }; + const marks = { + 0: { + style: { + color: "#f50" + }, + label: {t("courtesycars.labels.fuel.empty")} + }, + 13: t("courtesycars.labels.fuel.18"), + 25: t("courtesycars.labels.fuel.14"), + 38: t("courtesycars.labels.fuel.38"), + 50: t("courtesycars.labels.fuel.12"), + 63: t("courtesycars.labels.fuel.58"), + 75: t("courtesycars.labels.fuel.34"), + 88: t("courtesycars.labels.fuel.78"), + 100: { + style: { + color: "#008000" + }, + label: {t("courtesycars.labels.fuel.full")} + } + }; - return ( - { - switch (value) { - case 0: - return t("courtesycars.labels.fuel.empty"); - case 13: - return t("courtesycars.labels.fuel.18"); - case 25: - return t("courtesycars.labels.fuel.14"); - case 38: - return t("courtesycars.labels.fuel.38"); - case 50: - return t("courtesycars.labels.fuel.12"); - case 63: - return t("courtesycars.labels.fuel.58"); - case 75: - return t("courtesycars.labels.fuel.34"); - case 88: - return t("courtesycars.labels.fuel.78"); - case 100: - return t("courtesycars.labels.fuel.full"); - default: - return value; - } - }, - }} - /> - ); + return ( + { + switch (value) { + case 0: + return t("courtesycars.labels.fuel.empty"); + case 13: + return t("courtesycars.labels.fuel.18"); + case 25: + return t("courtesycars.labels.fuel.14"); + case 38: + return t("courtesycars.labels.fuel.38"); + case 50: + return t("courtesycars.labels.fuel.12"); + case 63: + return t("courtesycars.labels.fuel.58"); + case 75: + return t("courtesycars.labels.fuel.34"); + case 88: + return t("courtesycars.labels.fuel.78"); + case 100: + return t("courtesycars.labels.fuel.full"); + default: + return value; + } + } + }} + /> + ); }; export default forwardRef(CourtesyCarFuelComponent); diff --git a/client/src/components/courtesy-car-readiness-select/courtesy-car-readiness-select.component.jsx b/client/src/components/courtesy-car-readiness-select/courtesy-car-readiness-select.component.jsx index b33cf4539..553798328 100644 --- a/client/src/components/courtesy-car-readiness-select/courtesy-car-readiness-select.component.jsx +++ b/client/src/components/courtesy-car-readiness-select/courtesy-car-readiness-select.component.jsx @@ -1,36 +1,32 @@ -import {Select} from "antd"; -import React, {forwardRef, useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; +import { Select } from "antd"; +import React, { forwardRef, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; -const {Option} = Select; +const { Option } = Select; -const CourtesyCarReadinessComponent = ({value, onChange}, ref) => { - const [option, setOption] = useState(value); - const {t} = useTranslation(); +const CourtesyCarReadinessComponent = ({ value, onChange }, ref) => { + const [option, setOption] = useState(value); + const { t } = useTranslation(); - useEffect(() => { - if (value !== option && onChange) { - onChange(option); - } - }, [value, option, onChange]); + useEffect(() => { + if (value !== option && onChange) { + onChange(option); + } + }, [value, option, onChange]); - return ( - - ); + return ( + + ); }; export default forwardRef(CourtesyCarReadinessComponent); diff --git a/client/src/components/courtesy-car-return-modal/courtesy-car-return-modal.component.jsx b/client/src/components/courtesy-car-return-modal/courtesy-car-return-modal.component.jsx index f92302d3c..589c97ad4 100644 --- a/client/src/components/courtesy-car-return-modal/courtesy-car-return-modal.component.jsx +++ b/client/src/components/courtesy-car-return-modal/courtesy-car-return-modal.component.jsx @@ -1,50 +1,50 @@ -import {Form, InputNumber} from "antd"; +import { Form, InputNumber } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; export default function CourtesyCarReturnModalComponent() { - const {t} = useTranslation(); + const { t } = useTranslation(); - return ( -
- - - - - - - - - -
- ); + return ( +
+ + + + + + + + + +
+ ); } diff --git a/client/src/components/courtesy-car-return-modal/courtesy-car-return-modal.container.jsx b/client/src/components/courtesy-car-return-modal/courtesy-car-return-modal.container.jsx index 04fe0d126..acbfc6ff0 100644 --- a/client/src/components/courtesy-car-return-modal/courtesy-car-return-modal.container.jsx +++ b/client/src/components/courtesy-car-return-modal/courtesy-car-return-modal.container.jsx @@ -1,88 +1,77 @@ -import {Form, Modal, notification} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectCourtesyCarReturn} from "../../redux/modals/modals.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { Form, Modal, notification } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectCourtesyCarReturn } from "../../redux/modals/modals.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CourtesyCarReturnModalComponent from "./courtesy-car-return-modal.component"; import dayjs from "../../utils/day"; -import {RETURN_CONTRACT} from "../../graphql/cccontracts.queries"; -import {useMutation} from "@apollo/client"; +import { RETURN_CONTRACT } from "../../graphql/cccontracts.queries"; +import { useMutation } from "@apollo/client"; const mapStateToProps = createStructuredSelector({ - courtesyCarReturnModal: selectCourtesyCarReturn, - bodyshop: selectBodyshop, + courtesyCarReturnModal: selectCourtesyCarReturn, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("courtesyCarReturn")), + toggleModalVisible: () => dispatch(toggleModalVisible("courtesyCarReturn")) }); -export function CCReturnModalContainer({ - courtesyCarReturnModal, - toggleModalVisible, - bodyshop, - }) { - const [loading, setLoading] = useState(false); - const {open, context, actions} = courtesyCarReturnModal; - const {t} = useTranslation(); - const [form] = Form.useForm(); - const [updateContract] = useMutation(RETURN_CONTRACT); - const handleFinish = (values) => { - setLoading(true); - updateContract({ - variables: { - contractId: context.contractId, - cccontract: { - kmend: values.kmend, - actualreturn: values.actualreturn, - status: "contracts.status.returned", - fuelin: values.fuelin, - }, - courtesycarid: context.courtesyCarId, - courtesycar: { - status: "courtesycars.status.in", - fuel: values.fuelin, - mileage: values.kmend, - }, - }, - }) - .then((r) => { - if (actions.refetch) actions.refetch(); - toggleModalVisible(); - }) - .catch((error) => { - notification["error"]({ - message: t("contracts.errors.returning", {error: error}), - }); - }); - setLoading(false); - }; +export function CCReturnModalContainer({ courtesyCarReturnModal, toggleModalVisible, bodyshop }) { + const [loading, setLoading] = useState(false); + const { open, context, actions } = courtesyCarReturnModal; + const { t } = useTranslation(); + const [form] = Form.useForm(); + const [updateContract] = useMutation(RETURN_CONTRACT); + const handleFinish = (values) => { + setLoading(true); + updateContract({ + variables: { + contractId: context.contractId, + cccontract: { + kmend: values.kmend, + actualreturn: values.actualreturn, + status: "contracts.status.returned", + fuelin: values.fuelin + }, + courtesycarid: context.courtesyCarId, + courtesycar: { + status: "courtesycars.status.in", + fuel: values.fuelin, + mileage: values.kmend + } + } + }) + .then((r) => { + if (actions.refetch) actions.refetch(); + toggleModalVisible(); + }) + .catch((error) => { + notification["error"]({ + message: t("contracts.errors.returning", { error: error }) + }); + }); + setLoading(false); + }; - return ( - toggleModalVisible()} - width={"90%"} - okText={t("general.actions.save")} - onOk={() => form.submit()} - okButtonProps={{htmlType: "submit", loading: loading}} - > -
- - -
- ); + return ( + toggleModalVisible()} + width={"90%"} + okText={t("general.actions.save")} + onOk={() => form.submit()} + okButtonProps={{ htmlType: "submit", loading: loading }} + > +
+ + +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(CCReturnModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(CCReturnModalContainer); diff --git a/client/src/components/courtesy-car-status-select/courtesy-car-status-select.component.jsx b/client/src/components/courtesy-car-status-select/courtesy-car-status-select.component.jsx index 64c08d643..72c886da5 100644 --- a/client/src/components/courtesy-car-status-select/courtesy-car-status-select.component.jsx +++ b/client/src/components/courtesy-car-status-select/courtesy-car-status-select.component.jsx @@ -1,44 +1,34 @@ -import React, {forwardRef, useEffect, useState} from "react"; -import {Select} from "antd"; -import {useTranslation} from "react-i18next"; +import React, { forwardRef, useEffect, useState } from "react"; +import { Select } from "antd"; +import { useTranslation } from "react-i18next"; -const {Option} = Select; +const { Option } = Select; -const CourtesyCarStatusComponent = ({value, onChange}, ref) => { - const [option, setOption] = useState(value); - const {t} = useTranslation(); +const CourtesyCarStatusComponent = ({ value, onChange }, ref) => { + const [option, setOption] = useState(value); + const { t } = useTranslation(); - useEffect(() => { - if (value !== option && onChange) { - onChange(option); - } - }, [value, option, onChange]); + useEffect(() => { + if (value !== option && onChange) { + onChange(option); + } + }, [value, option, onChange]); - return ( - - ); + return ( + + ); }; export default forwardRef(CourtesyCarStatusComponent); diff --git a/client/src/components/courtesy-cars-list/courtesy-cars-list.component.jsx b/client/src/components/courtesy-cars-list/courtesy-cars-list.component.jsx index 2409f9fdd..bf144de4a 100644 --- a/client/src/components/courtesy-cars-list/courtesy-cars-list.component.jsx +++ b/client/src/components/courtesy-cars-list/courtesy-cars-list.component.jsx @@ -1,13 +1,5 @@ import { SyncOutlined, WarningFilled } from "@ant-design/icons"; -import { - Button, - Card, - Dropdown, - Input, - Space, - Table, - Tooltip, -} from "antd"; +import { Button, Card, Dropdown, Input, Space, Table, Tooltip } from "antd"; import dayjs from "../../utils/day"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -20,297 +12,270 @@ import useLocalStorage from "../../utils/useLocalStorage"; import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; export default function CourtesyCarsList({ loading, courtesycars, refetch }) { - const [state, setState] = useState({ - sortedInfo: {}, - }); - const [searchText, setSearchText] = useState(""); - const [filter, setFilter] = useLocalStorage( - "filter_courtesy_cars_list", - null - ); - const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {} + }); + const [searchText, setSearchText] = useState(""); + const [filter, setFilter] = useLocalStorage("filter_courtesy_cars_list", null); + const { t } = useTranslation(); - const columns = [ + const columns = [ + { + title: t("courtesycars.fields.fleetnumber"), + dataIndex: "fleetnumber", + key: "fleetnumber", + sorter: (a, b) => alphaSort(a.fleetnumber, b.fleetnumber), + sortOrder: state.sortedInfo.columnKey === "fleetnumber" && state.sortedInfo.order + }, + { + title: t("courtesycars.fields.vin"), + dataIndex: "vin", + key: "vin", + sorter: (a, b) => alphaSort(a.vin, b.vin), + sortOrder: state.sortedInfo.columnKey === "vin" && state.sortedInfo.order, + render: (text, record) => {record.vin} + }, + { + title: t("courtesycars.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => alphaSort(a.status, b.status), + filteredValue: filter?.status || null, + filters: [ { - title: t("courtesycars.fields.fleetnumber"), - dataIndex: "fleetnumber", - key: "fleetnumber", - sorter: (a, b) => alphaSort(a.fleetnumber, b.fleetnumber), - sortOrder: - state.sortedInfo.columnKey === "fleetnumber" && state.sortedInfo.order, + text: t("courtesycars.status.in"), + value: "courtesycars.status.in" }, { - title: t("courtesycars.fields.vin"), - dataIndex: "vin", - key: "vin", - sorter: (a, b) => alphaSort(a.vin, b.vin), - sortOrder: state.sortedInfo.columnKey === "vin" && state.sortedInfo.order, - render: (text, record) => ( - {record.vin} - ), - }, - { - title: t("courtesycars.fields.status"), - dataIndex: "status", - key: "status", - sorter: (a, b) => alphaSort(a.status, b.status), - filteredValue: filter?.status || null, - filters: [ - { - text: t("courtesycars.status.in"), - value: "courtesycars.status.in", - }, - { text: t("courtesycars.status.inservice"), - value: "courtesycars.status.inservice", + value: "courtesycars.status.inservice" }, { - text: t("courtesycars.status.out"), - value: "courtesycars.status.out", - }, - { - text: t("courtesycars.status.sold"), - value: "courtesycars.status.sold", - }, - { - text: t("courtesycars.status.leasereturn"), - value: "courtesycars.status.leasereturn", - }, - ], - onFilter: (value, record) => record.status === value, - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - render: (text, record) => { - const { nextservicedate, nextservicekm, mileage, insuranceexpires } = - record; + text: t("courtesycars.status.out"), + value: "courtesycars.status.out" + }, + { + text: t("courtesycars.status.sold"), + value: "courtesycars.status.sold" + }, + { + text: t("courtesycars.status.leasereturn"), + value: "courtesycars.status.leasereturn" + } + ], + onFilter: (value, record) => record.status === value, + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + render: (text, record) => { + const { nextservicedate, nextservicekm, mileage, insuranceexpires } = record; - const mileageOver = nextservicekm ? nextservicekm <= mileage : false; + const mileageOver = nextservicekm ? nextservicekm <= mileage : false; - const dueForService = - nextservicedate && dayjs(nextservicedate).endOf('day').isSameOrBefore(dayjs()); + const dueForService = nextservicedate && dayjs(nextservicedate).endOf("day").isSameOrBefore(dayjs()); - const insuranceOver = - insuranceexpires && - dayjs(insuranceexpires).endOf("day").isBefore(dayjs()); + const insuranceOver = insuranceexpires && dayjs(insuranceexpires).endOf("day").isBefore(dayjs()); - return ( - - {t(record.status)} - {(mileageOver || dueForService || insuranceOver) && ( - - - - )} - - ); - }, + return ( + + {t(record.status)} + {(mileageOver || dueForService || insuranceOver) && ( + + + + )} + + ); + } + }, + { + title: t("courtesycars.fields.readiness"), + dataIndex: "readiness", + key: "readiness", + sorter: (a, b) => alphaSort(a.readiness, b.readiness), + filteredValue: filter?.readiness || null, + filters: [ + { + text: t("courtesycars.readiness.ready"), + value: "courtesycars.readiness.ready" }, { - title: t("courtesycars.fields.readiness"), - dataIndex: "readiness", - key: "readiness", - sorter: (a, b) => alphaSort(a.readiness, b.readiness), - filteredValue: filter?.readiness || null, - filters: [ - { - text: t("courtesycars.readiness.ready"), - value: "courtesycars.readiness.ready", - }, - { - text: t("courtesycars.readiness.notready"), - value: "courtesycars.readiness.notready", - }, - ], - onFilter: (value, record) => value.includes(record.readiness), - sortOrder: - state.sortedInfo.columnKey === "readiness" && state.sortedInfo.order, - render: (text, record) => t(record.readiness), - }, - { - title: t("courtesycars.fields.year"), - dataIndex: "year", - key: "year", - sorter: (a, b) => alphaSort(a.year, b.year), - sortOrder: - state.sortedInfo.columnKey === "year" && state.sortedInfo.order, - }, - { - title: t("courtesycars.fields.make"), - dataIndex: "make", - key: "make", - sorter: (a, b) => alphaSort(a.make, b.make), - sortOrder: - state.sortedInfo.columnKey === "make" && state.sortedInfo.order, - }, - { - title: t("courtesycars.fields.model"), - dataIndex: "model", - key: "model", - sorter: (a, b) => alphaSort(a.model, b.model), - sortOrder: - state.sortedInfo.columnKey === "model" && state.sortedInfo.order, - }, - { - title: t("courtesycars.fields.color"), - dataIndex: "color", - key: "color", - sorter: (a, b) => alphaSort(a.color, b.color), - sortOrder: - state.sortedInfo.columnKey === "color" && state.sortedInfo.order, - }, - { - title: t("courtesycars.fields.plate"), - dataIndex: "plate", - key: "plate", - sorter: (a, b) => alphaSort(a.plate, b.plate), - sortOrder: - state.sortedInfo.columnKey === "plate" && state.sortedInfo.order, - }, - { - title: t("courtesycars.fields.fuel"), - dataIndex: "fuel", - key: "fuel", - sorter: (a, b) => a.fuel - b.fuel, - sortOrder: - state.sortedInfo.columnKey === "fuel" && state.sortedInfo.order, - render: (text, record) => { - switch (record.fuel) { - case 100: - return t("courtesycars.labels.fuel.full"); - case 88: - return t("courtesycars.labels.fuel.78"); - case 75: + text: t("courtesycars.readiness.notready"), + value: "courtesycars.readiness.notready" + } + ], + onFilter: (value, record) => value.includes(record.readiness), + sortOrder: state.sortedInfo.columnKey === "readiness" && state.sortedInfo.order, + render: (text, record) => t(record.readiness) + }, + { + title: t("courtesycars.fields.year"), + dataIndex: "year", + key: "year", + sorter: (a, b) => alphaSort(a.year, b.year), + sortOrder: state.sortedInfo.columnKey === "year" && state.sortedInfo.order + }, + { + title: t("courtesycars.fields.make"), + dataIndex: "make", + key: "make", + sorter: (a, b) => alphaSort(a.make, b.make), + sortOrder: state.sortedInfo.columnKey === "make" && state.sortedInfo.order + }, + { + title: t("courtesycars.fields.model"), + dataIndex: "model", + key: "model", + sorter: (a, b) => alphaSort(a.model, b.model), + sortOrder: state.sortedInfo.columnKey === "model" && state.sortedInfo.order + }, + { + title: t("courtesycars.fields.color"), + dataIndex: "color", + key: "color", + sorter: (a, b) => alphaSort(a.color, b.color), + sortOrder: state.sortedInfo.columnKey === "color" && state.sortedInfo.order + }, + { + title: t("courtesycars.fields.plate"), + dataIndex: "plate", + key: "plate", + sorter: (a, b) => alphaSort(a.plate, b.plate), + sortOrder: state.sortedInfo.columnKey === "plate" && state.sortedInfo.order + }, + { + title: t("courtesycars.fields.fuel"), + dataIndex: "fuel", + key: "fuel", + sorter: (a, b) => a.fuel - b.fuel, + sortOrder: state.sortedInfo.columnKey === "fuel" && state.sortedInfo.order, + render: (text, record) => { + switch (record.fuel) { + case 100: + return t("courtesycars.labels.fuel.full"); + case 88: + return t("courtesycars.labels.fuel.78"); + case 75: return t("courtesycars.labels.fuel.34"); case 63: - return t("courtesycars.labels.fuel.58"); - case 50: - return t("courtesycars.labels.fuel.12"); - case 38: - return t("courtesycars.labels.fuel.38"); - case 25: - return t("courtesycars.labels.fuel.14"); - case 13: - return t("courtesycars.labels.fuel.18"); - case 0: - return t("courtesycars.labels.fuel.empty"); - default: - return record.fuel; - } - }, - }, - { - title: t("courtesycars.labels.outwith"), - dataIndex: "outwith", - key: "outwith", - // sorter: (a, b) => alphaSort(a.model, b.model), - sortOrder: - state.sortedInfo.columnKey === "model" && state.sortedInfo.order, - render: (text, record) => - record.cccontracts.length === 1 ? ( - - {`${ - record.cccontracts[0].job.ro_number - } - ${OwnerNameDisplayFunction(record.cccontracts[0].job)}`} - - ) : null, - }, - { - title: t("contracts.fields.scheduledreturn"), - dataIndex: "scheduledreturn", - key: "scheduledreturn", - render: (text, record) => - record.cccontracts.length === 1 && ( - - {record.cccontracts[0].scheduledreturn} - - ), - }, - ]; - - const handleTableChange = (pagination, filters, sorter) => { - setState({ ...state, sortedInfo: sorter }); - setFilter(filters); - }; - - const tableData = searchText - ? courtesycars.filter( - (c) => - (c.fleetnumber || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (c.vin || "").toLowerCase().includes(searchText.toLowerCase()) || - (c.year || "").toLowerCase().includes(searchText.toLowerCase()) || - (c.make || "").toLowerCase().includes(searchText.toLowerCase()) || - (c.model || "").toLowerCase().includes(searchText.toLowerCase()) || - (c.plate || "").toLowerCase().includes(searchText.toLowerCase()) || - (t(c.status) || "").toLowerCase().includes(searchText.toLowerCase()) + return t("courtesycars.labels.fuel.58"); + case 50: + return t("courtesycars.labels.fuel.12"); + case 38: + return t("courtesycars.labels.fuel.38"); + case 25: + return t("courtesycars.labels.fuel.14"); + case 13: + return t("courtesycars.labels.fuel.18"); + case 0: + return t("courtesycars.labels.fuel.empty"); + default: + return record.fuel; + } + } + }, + { + title: t("courtesycars.labels.outwith"), + dataIndex: "outwith", + key: "outwith", + // sorter: (a, b) => alphaSort(a.model, b.model), + sortOrder: state.sortedInfo.columnKey === "model" && state.sortedInfo.order, + render: (text, record) => + record.cccontracts.length === 1 ? ( + + {`${record.cccontracts[0].job.ro_number} - ${OwnerNameDisplayFunction(record.cccontracts[0].job)}`} + + ) : null + }, + { + title: t("contracts.fields.scheduledreturn"), + dataIndex: "scheduledreturn", + key: "scheduledreturn", + render: (text, record) => + record.cccontracts.length === 1 && ( + {record.cccontracts[0].scheduledreturn} ) - : courtesycars; + } + ]; - const items = [ - { - key: "courtesycar_inventory", - label: t("printcenter.courtesycarcontract.courtesy_car_inventory"), - onClick: () => - GenerateDocument( - { - name: TemplateList("courtesycar").courtesy_car_inventory.key, - variables: { - //id: contract.id - }, - }, - {}, - "p" - ), - }, - ]; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, sortedInfo: sorter }); + setFilter(filters); + }; - const menu = {items}; + const tableData = searchText + ? courtesycars.filter( + (c) => + (c.fleetnumber || "").toLowerCase().includes(searchText.toLowerCase()) || + (c.vin || "").toLowerCase().includes(searchText.toLowerCase()) || + (c.year || "").toLowerCase().includes(searchText.toLowerCase()) || + (c.make || "").toLowerCase().includes(searchText.toLowerCase()) || + (c.model || "").toLowerCase().includes(searchText.toLowerCase()) || + (c.plate || "").toLowerCase().includes(searchText.toLowerCase()) || + (t(c.status) || "").toLowerCase().includes(searchText.toLowerCase()) + ) + : courtesycars; - return ( - - - - - - - - - { - setSearchText(e.target.value); - }} - value={searchText} - enterButton - /> - + const items = [ + { + key: "courtesycar_inventory", + label: t("printcenter.courtesycarcontract.courtesy_car_inventory"), + onClick: () => + GenerateDocument( + { + name: TemplateList("courtesycar").courtesy_car_inventory.key, + variables: { + //id: contract.id } - > -
- - ); + }, + {}, + "p" + ) + } + ]; + + const menu = { items }; + + return ( + + + + + + + + + { + setSearchText(e.target.value); + }} + value={searchText} + enterButton + /> + + } + > +
+ + ); } diff --git a/client/src/components/csi-response-form/csi-response-form.container.jsx b/client/src/components/csi-response-form/csi-response-form.container.jsx index be8d04faf..137a25faf 100644 --- a/client/src/components/csi-response-form/csi-response-form.container.jsx +++ b/client/src/components/csi-response-form/csi-response-form.container.jsx @@ -1,57 +1,55 @@ -import {useQuery} from "@apollo/client"; -import {Card, Form, Result} from "antd"; +import { useQuery } from "@apollo/client"; +import { Card, Form, Result } from "antd"; import queryString from "query-string"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {useLocation} from "react-router-dom"; -import {QUERY_CSI_RESPONSE_BY_PK} from "../../graphql/csi.queries"; -import {DateFormatter} from "../../utils/DateFormatter"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { useLocation } from "react-router-dom"; +import { QUERY_CSI_RESPONSE_BY_PK } from "../../graphql/csi.queries"; +import { DateFormatter } from "../../utils/DateFormatter"; import AlertComponent from "../alert/alert.component"; import ConfigFormComponents from "../config-form-components/config-form-components.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; export default function CsiResponseFormContainer() { - const {t} = useTranslation(); - const [form] = Form.useForm(); - const searchParams = queryString.parse(useLocation().search); - const {responseid} = searchParams; - const {loading, error, data} = useQuery(QUERY_CSI_RESPONSE_BY_PK, { - variables: { - id: responseid, - }, - skip: !!!responseid, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const { t } = useTranslation(); + const [form] = Form.useForm(); + const searchParams = queryString.parse(useLocation().search); + const { responseid } = searchParams; + const { loading, error, data } = useQuery(QUERY_CSI_RESPONSE_BY_PK, { + variables: { + id: responseid + }, + skip: !!!responseid, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - useEffect(() => { - form.resetFields(); - }, [data, form]); - - if (!!!responseid) - return ( - - - - ); - - if (loading) return ; - if (error) return ; + useEffect(() => { + form.resetFields(); + }, [data, form]); + if (!!!responseid) return ( - -
- - {data.csi_by_pk.validuntil ? ( - <> - {t("csi.fields.validuntil")} - {": "} - {data.csi_by_pk.validuntil} - - ) : null} -
+ + + ); + + if (loading) return ; + if (error) return ; + + return ( + +
+ + {data.csi_by_pk.validuntil ? ( + <> + {t("csi.fields.validuntil")} + {": "} + {data.csi_by_pk.validuntil} + + ) : null} + +
+ ); } diff --git a/client/src/components/csi-response-list-paginated/csi-response-list-paginated.component.jsx b/client/src/components/csi-response-list-paginated/csi-response-list-paginated.component.jsx index e78dbbd3a..d7b49df56 100644 --- a/client/src/components/csi-response-list-paginated/csi-response-list-paginated.component.jsx +++ b/client/src/components/csi-response-list-paginated/csi-response-list-paginated.component.jsx @@ -1,131 +1,117 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Table} from "antd"; +import { SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Table } from "antd"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {DateFormatter} from "../../utils/DateFormatter"; -import {pageLimit} from "../../utils/config"; -import {alphaSort, dateSort} from "../../utils/sorters"; -import OwnerNameDisplay, {OwnerNameDisplayFunction,} from "../owner-name-display/owner-name-display.component"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { DateFormatter } from "../../utils/DateFormatter"; +import { pageLimit } from "../../utils/config"; +import { alphaSort, dateSort } from "../../utils/sorters"; +import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; -export default function CsiResponseListPaginated({ - refetch, - loading, - responses, - total, - }) { - const search = queryString.parse(useLocation().search); - const {responseid} = search; - const history = useNavigate(); - const {t} = useTranslation(); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - page: "", - }); +export default function CsiResponseListPaginated({ refetch, loading, responses, total }) { + const search = queryString.parse(useLocation().search); + const { responseid } = search; + const history = useNavigate(); + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" }, + page: "" + }); + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - - sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - render: (text, record) => ( - - {record.job.ro_number || t("general.labels.na")} - - ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner_name", - key: "owner_name", - sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a.job), - OwnerNameDisplayFunction(b.job) - ), - sortOrder: state.sortedInfo.columnKey === "owner_name" && state.sortedInfo.order, - render: (text, record) => { - return record.job.ownerid ? ( - - - - ) : ( - - + sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => ( + {record.job.ro_number || t("general.labels.na")} + ) + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner_name", + key: "owner_name", + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a.job), OwnerNameDisplayFunction(b.job)), + sortOrder: state.sortedInfo.columnKey === "owner_name" && state.sortedInfo.order, + render: (text, record) => { + return record.job.ownerid ? ( + + + + ) : ( + + - ); - }, - }, - { - title: t("csi.fields.completedon"), - dataIndex: "completedon", - key: "completedon", - ellipsis: true, - sorter: (a, b) => dateSort(a.completedon, b.completedon), - sortOrder: state.sortedInfo.columnKey === "completedon" && state.sortedInfo.order, - render: (text, record) => { - return record.completedon ? ( - {record.completedon} - ) : null; - }, - }, - ]; + ); + } + }, + { + title: t("csi.fields.completedon"), + dataIndex: "completedon", + key: "completedon", + ellipsis: true, + sorter: (a, b) => dateSort(a.completedon, b.completedon), + sortOrder: state.sortedInfo.columnKey === "completedon" && state.sortedInfo.order, + render: (text, record) => { + return record.completedon ? {record.completedon} : null; + } + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setState({ - ...state, - filteredInfo: filters, - sortedInfo: sorter, - page: pagination.current, - }); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ + ...state, + filteredInfo: filters, + sortedInfo: sorter, + page: pagination.current + }); + }; - const handleOnRowClick = (record) => { - if (record) { - if (record.id) { - search.responseid = record.id; - history({search: queryString.stringify(search)}); - } - } else { - delete search.responseid; - history({search: queryString.stringify(search)}); - } - }; + const handleOnRowClick = (record) => { + if (record) { + if (record.id) { + search.responseid = record.id; + history({ search: queryString.stringify(search) }); + } + } else { + delete search.responseid; + history({ search: queryString.stringify(search) }); + } + }; - return ( - refetch()}> - - - } - > -
{ - handleOnRowClick(record); - }, - selectedRowKeys: [responseid], - type: "radio", - - }} - /> - - ); + return ( + refetch()}> + + + } + > +
{ + handleOnRowClick(record); + }, + selectedRowKeys: [responseid], + type: "radio" + }} + /> + + ); } diff --git a/client/src/components/dashboard-components/job-lifecycle/job-lifecycle-dashboard.component.jsx b/client/src/components/dashboard-components/job-lifecycle/job-lifecycle-dashboard.component.jsx index 8e03f04b5..e2dee64f5 100644 --- a/client/src/components/dashboard-components/job-lifecycle/job-lifecycle-dashboard.component.jsx +++ b/client/src/components/dashboard-components/job-lifecycle/job-lifecycle-dashboard.component.jsx @@ -1,168 +1,183 @@ -import {Card, Table, Tag} from "antd"; +import { Card, Table, Tag } from "antd"; import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component"; -import {useTranslation} from "react-i18next"; -import React, {useEffect, useState} from "react"; -import dayjs from '../../../utils/day'; +import { useTranslation } from "react-i18next"; +import React, { useEffect, useState } from "react"; +import dayjs from "../../../utils/day"; import DashboardRefreshRequired from "../refresh-required.component"; import axios from "axios"; -const fortyFiveDaysAgo = () => dayjs().subtract(45, 'day').toLocaleString(); +const fortyFiveDaysAgo = () => dayjs().subtract(45, "day").toLocaleString(); -export default function JobLifecycleDashboardComponent({data, bodyshop, ...cardProps}) { - console.log("🚀 ~ JobLifecycleDashboardComponent ~ bodyshop:", bodyshop) - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const [lifecycleData, setLifecycleData] = useState(null); +export default function JobLifecycleDashboardComponent({ data, bodyshop, ...cardProps }) { + console.log("🚀 ~ JobLifecycleDashboardComponent ~ bodyshop:", bodyshop); + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [lifecycleData, setLifecycleData] = useState(null); - useEffect(() => { - async function getLifecycleData() { - if (data && data.job_lifecycle) { - setLoading(true); - const response = await axios.post("/job/lifecycle", { - jobids: data.job_lifecycle.map(x => x.id), - statuses: bodyshop.md_ro_statuses - }); - setLifecycleData(response.data.durations); - setLoading(false); - } - } + useEffect(() => { + async function getLifecycleData() { + if (data && data.job_lifecycle) { + setLoading(true); + const response = await axios.post("/job/lifecycle", { + jobids: data.job_lifecycle.map((x) => x.id), + statuses: bodyshop.md_ro_statuses + }); + setLifecycleData(response.data.durations); + setLoading(false); + } + } - getLifecycleData().catch(e => { - console.error(`Error in getLifecycleData: ${e}`); - }) - }, [data, bodyshop]); + getLifecycleData().catch((e) => { + console.error(`Error in getLifecycleData: ${e}`); + }); + }, [data, bodyshop]); - const columns = [ - { - title: t('job_lifecycle.columns.status'), - dataIndex: 'status', - bgColor: 'red', - key: 'status', - render: (text, record) => { - return {record.status} - } - }, - { - title: t('job_lifecycle.columns.human_readable'), - dataIndex: 'humanReadable', - key: 'humanReadable', - }, - { - title: t('job_lifecycle.columns.status_count'), - key: 'statusCount', - render: (text, record) => { - return lifecycleData.statusCounts[record.status]; - } - }, - { - title: t('job_lifecycle.columns.percentage'), - dataIndex: 'percentage', - key: 'percentage', - render: (text, record) => { - return record.percentage.toFixed(2) + '%'; - } - }, - ]; + const columns = [ + { + title: t("job_lifecycle.columns.status"), + dataIndex: "status", + bgColor: "red", + key: "status", + render: (text, record) => { + return {record.status}; + } + }, + { + title: t("job_lifecycle.columns.human_readable"), + dataIndex: "humanReadable", + key: "humanReadable" + }, + { + title: t("job_lifecycle.columns.status_count"), + key: "statusCount", + render: (text, record) => { + return lifecycleData.statusCounts[record.status]; + } + }, + { + title: t("job_lifecycle.columns.percentage"), + dataIndex: "percentage", + key: "percentage", + render: (text, record) => { + return record.percentage.toFixed(2) + "%"; + } + } + ]; - if (!data) return null; + if (!data) return null; - if (!data.job_lifecycle || !lifecycleData) return ; + if (!data.job_lifecycle || !lifecycleData) return ; - const extra = `${t('job_lifecycle.content.calculated_based_on')} ${lifecycleData.jobs} ${t('job_lifecycle.content.jobs_in_since')} ${fortyFiveDaysAgo()}` + const extra = `${t("job_lifecycle.content.calculated_based_on")} ${lifecycleData.jobs} ${t("job_lifecycle.content.jobs_in_since")} ${fortyFiveDaysAgo()}`; - return ( - - -
-
- {lifecycleData.summations.map((key, index, array) => { - const isFirst = index === 0; - const isLast = index === array.length - 1; - return ( -
+ +
+
+ {lifecycleData.summations.map((key, index, array) => { + const isFirst = index === 0; + const isLast = index === array.length - 1; + return ( +
- - {key.percentage > 15 ? - <> -
{key.roundedPercentage}
-
- {key.status} -
- - : null} -
- ); - })} -
- -
- {lifecycleData.summations.map((key) => ( - -
- {key.status} [{lifecycleData.statusCounts[key.status]}] ({key.roundedPercentage}) -
-
- ))} -
-
- -
b.value - a.value).slice(0, 3)}/> - + backgroundColor: key.color, + width: `${key.percentage}%` + }} + aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`} + title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`} + > + {key.percentage > 15 ? ( + <> +
{key.roundedPercentage}
+
+ {key.status} +
+ + ) : null} - - - ); + ); + })} + + +
+ {lifecycleData.summations.map((key) => ( + +
+ {key.status} [{lifecycleData.statusCounts[key.status]}] ({key.roundedPercentage}) +
+
+ ))} +
+
+ +
b.value - a.value).slice(0, 3)} + /> + + + + + ); } export const JobLifecycleDashboardGQL = ` job_lifecycle: jobs(where: { actual_in: { - _gte: "${dayjs().subtract(45, 'day').toISOString()}" + _gte: "${dayjs().subtract(45, "day").toISOString()}" } }) { id diff --git a/client/src/components/dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component.jsx b/client/src/components/dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component.jsx index afc93350d..62370079b 100644 --- a/client/src/components/dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component.jsx +++ b/client/src/components/dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component.jsx @@ -1,159 +1,124 @@ -import {Card} from "antd"; +import { Card } from "antd"; import _ from "lodash"; import dayjs from "../../../utils/day"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {Bar, CartesianGrid, ComposedChart, Legend, Line, ResponsiveContainer, Tooltip, XAxis, YAxis,} from "recharts"; +import { useTranslation } from "react-i18next"; +import { Bar, CartesianGrid, ComposedChart, Legend, Line, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; import * as Utils from "../../scoreboard-targets-table/scoreboard-targets-table.util"; import DashboardRefreshRequired from "../refresh-required.component"; -export default function DashboardMonthlyEmployeeEfficiency({ - data, - ...cardProps - }) { - const {t} = useTranslation(); - if (!data) return null; - if (!data.monthly_employee_efficiency) - return ; +export default function DashboardMonthlyEmployeeEfficiency({ data, ...cardProps }) { + const { t } = useTranslation(); + if (!data) return null; + if (!data.monthly_employee_efficiency) return ; - const ticketsByDate = _.groupBy(data.monthly_employee_efficiency, (item) => - dayjs(item.date).format("YYYY-MM-DD") - ); + const ticketsByDate = _.groupBy(data.monthly_employee_efficiency, (item) => dayjs(item.date).format("YYYY-MM-DD")); - const listOfDays = Utils.ListOfDaysInCurrentMonth(); + const listOfDays = Utils.ListOfDaysInCurrentMonth(); - const chartData = listOfDays.reduce((acc, val) => { - //Sum up the current day. - let dailyHrs; - if (!!ticketsByDate[val]) { - dailyHrs = ticketsByDate[val].reduce( - (dayAcc, dayVal) => { - return { - actual: dayAcc.actual + dayVal.actualhrs, - productive: dayAcc.productive + dayVal.productivehrs, - }; - }, - {actual: 0, productive: 0} - ); - } else { - dailyHrs = {actual: 0, productive: 0}; - } + const chartData = listOfDays.reduce((acc, val) => { + //Sum up the current day. + let dailyHrs; + if (!!ticketsByDate[val]) { + dailyHrs = ticketsByDate[val].reduce( + (dayAcc, dayVal) => { + return { + actual: dayAcc.actual + dayVal.actualhrs, + productive: dayAcc.productive + dayVal.productivehrs + }; + }, + { actual: 0, productive: 0 } + ); + } else { + dailyHrs = { actual: 0, productive: 0 }; + } - const dailyEfficiency = - ((dailyHrs.productive - dailyHrs.actual) / dailyHrs.actual + 1) * 100; + const dailyEfficiency = ((dailyHrs.productive - dailyHrs.actual) / dailyHrs.actual + 1) * 100; - const theValue = { - date: dayjs(val).format("DD"), - // ...dailyHrs, - actual: dailyHrs.actual.toFixed(1), - productive: dailyHrs.productive.toFixed(1), - dailyEfficiency: isNaN(dailyEfficiency) ? 0 : dailyEfficiency.toFixed(1), - accActual: - acc.length > 0 - ? acc[acc.length - 1].accActual + dailyHrs.actual - : dailyHrs.actual, + const theValue = { + date: dayjs(val).format("DD"), + // ...dailyHrs, + actual: dailyHrs.actual.toFixed(1), + productive: dailyHrs.productive.toFixed(1), + dailyEfficiency: isNaN(dailyEfficiency) ? 0 : dailyEfficiency.toFixed(1), + accActual: acc.length > 0 ? acc[acc.length - 1].accActual + dailyHrs.actual : dailyHrs.actual, - accProductive: - acc.length > 0 - ? acc[acc.length - 1].accProductive + dailyHrs.productive - : dailyHrs.productive, - accEfficiency: 0, - }; + accProductive: acc.length > 0 ? acc[acc.length - 1].accProductive + dailyHrs.productive : dailyHrs.productive, + accEfficiency: 0 + }; - theValue.accEfficiency = - ((theValue.accProductive - theValue.accActual) / - (theValue.accActual || 0) + - 1) * - 100; + theValue.accEfficiency = ((theValue.accProductive - theValue.accActual) / (theValue.accActual || 0) + 1) * 100; - if (isNaN(theValue.accEfficiency)) { - theValue.accEfficiency = 0; - } else { - theValue.accEfficiency = theValue.accEfficiency.toFixed(1); - } + if (isNaN(theValue.accEfficiency)) { + theValue.accEfficiency = 0; + } else { + theValue.accEfficiency = theValue.accEfficiency.toFixed(1); + } - return [...acc, theValue]; - }, []); + return [...acc, theValue]; + }, []); - return ( - -
- - - - - - - - - - - - - - -
-
- ); + return ( + +
+ + + + + + + + + + + + + + +
+
+ ); } export const DashboardMonthlyEmployeeEfficiencyGql = ` monthly_employee_efficiency: timetickets(where: {_and: [{date: {_gte: "${dayjs() .startOf("month") - .format("YYYY-MM-DD")}"}},{date: {_lte: "${dayjs() - .endOf("month") - .format("YYYY-MM-DD")}"}} ]}) { + .format("YYYY-MM-DD")}"}},{date: {_lte: "${dayjs().endOf("month").format("YYYY-MM-DD")}"}} ]}) { actualhrs productivehrs employeeid diff --git a/client/src/components/dashboard-components/monthly-job-costing/monthly-job-costing.component.jsx b/client/src/components/dashboard-components/monthly-job-costing/monthly-job-costing.component.jsx index deeabbcc3..3924ccb37 100644 --- a/client/src/components/dashboard-components/monthly-job-costing/monthly-job-costing.component.jsx +++ b/client/src/components/dashboard-components/monthly-job-costing/monthly-job-costing.component.jsx @@ -1,164 +1,138 @@ -import {Card, Input, Space, Table, Typography} from "antd"; +import { Card, Input, Space, Table, Typography } from "antd"; import axios from "axios"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {alphaSort} from "../../../utils/sorters"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { alphaSort } from "../../../utils/sorters"; import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component"; import Dinero from "dinero.js"; import DashboardRefreshRequired from "../refresh-required.component"; -import {pageLimit} from "../../../utils/config"; +import { pageLimit } from "../../../utils/config"; -export default function DashboardMonthlyJobCosting({data, ...cardProps}) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const [costingData, setcostingData] = useState(null); - const [searchText, setSearchText] = useState(""); - const [state, setState] = useState({ - sortedInfo: {}, - }); +export default function DashboardMonthlyJobCosting({ data, ...cardProps }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [costingData, setcostingData] = useState(null); + const [searchText, setSearchText] = useState(""); + const [state, setState] = useState({ + sortedInfo: {} + }); - useEffect(() => { - async function getCostingData() { - if (data && data.monthly_sales) { - setLoading(true); - const response = await axios.post("/job/costingmulti", { - jobids: data.monthly_sales.map((x) => x.id), - }); - setcostingData(response.data); - setLoading(false); - } - } + useEffect(() => { + async function getCostingData() { + if (data && data.monthly_sales) { + setLoading(true); + const response = await axios.post("/job/costingmulti", { + jobids: data.monthly_sales.map((x) => x.id) + }); + setcostingData(response.data); + setLoading(false); + } + } - getCostingData(); - }, [data]); + getCostingData(); + }, [data]); - if (!data) return null; - if (!data.monthly_sales) return ; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - const columns = [ - { - title: t("bodyshop.fields.responsibilitycenter"), - dataIndex: "cost_center", - key: "cost_center", - sorter: (a, b) => alphaSort(a.cost_center, b.cost_center), - sortOrder: - state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order, - }, - { - title: t("jobs.labels.sales"), - dataIndex: "sales", - key: "sales", - sorter: (a, b) => - parseFloat(a.sales.substring(1)) - parseFloat(b.sales.substring(1)), - sortOrder: - state.sortedInfo.columnKey === "sales" && state.sortedInfo.order, - }, + if (!data) return null; + if (!data.monthly_sales) return ; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + const columns = [ + { + title: t("bodyshop.fields.responsibilitycenter"), + dataIndex: "cost_center", + key: "cost_center", + sorter: (a, b) => alphaSort(a.cost_center, b.cost_center), + sortOrder: state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order + }, + { + title: t("jobs.labels.sales"), + dataIndex: "sales", + key: "sales", + sorter: (a, b) => parseFloat(a.sales.substring(1)) - parseFloat(b.sales.substring(1)), + sortOrder: state.sortedInfo.columnKey === "sales" && state.sortedInfo.order + }, - { - title: t("jobs.labels.costs"), - dataIndex: "costs", - key: "costs", - sorter: (a, b) => - parseFloat(a.costs.substring(1)) - parseFloat(b.costs.substring(1)), - sortOrder: - state.sortedInfo.columnKey === "costs" && state.sortedInfo.order, - }, + { + title: t("jobs.labels.costs"), + dataIndex: "costs", + key: "costs", + sorter: (a, b) => parseFloat(a.costs.substring(1)) - parseFloat(b.costs.substring(1)), + sortOrder: state.sortedInfo.columnKey === "costs" && state.sortedInfo.order + }, - { - title: t("jobs.labels.gpdollars"), - dataIndex: "gpdollars", - key: "gpdollars", - sorter: (a, b) => - parseFloat(a.gpdollars.substring(1)) - - parseFloat(b.gpdollars.substring(1)), + { + title: t("jobs.labels.gpdollars"), + dataIndex: "gpdollars", + key: "gpdollars", + sorter: (a, b) => parseFloat(a.gpdollars.substring(1)) - parseFloat(b.gpdollars.substring(1)), - sortOrder: - state.sortedInfo.columnKey === "gpdollars" && state.sortedInfo.order, - }, - { - title: t("jobs.labels.gppercent"), - dataIndex: "gppercent", - key: "gppercent", - sorter: (a, b) => - parseFloat(a.gppercent.slice(0, -1) || 0) - - parseFloat(b.gppercent.slice(0, -1) || 0), - sortOrder: - state.sortedInfo.columnKey === "gppercent" && state.sortedInfo.order, - }, - ]; - const filteredData = - searchText === "" - ? (costingData && costingData.allCostCenterData) || [] - : costingData.allCostCenterData.filter((d) => - (d.cost_center || "") - .toString() - .toLowerCase() - .includes(searchText.toLowerCase()) - ); + sortOrder: state.sortedInfo.columnKey === "gpdollars" && state.sortedInfo.order + }, + { + title: t("jobs.labels.gppercent"), + dataIndex: "gppercent", + key: "gppercent", + sorter: (a, b) => parseFloat(a.gppercent.slice(0, -1) || 0) - parseFloat(b.gppercent.slice(0, -1) || 0), + sortOrder: state.sortedInfo.columnKey === "gppercent" && state.sortedInfo.order + } + ]; + const filteredData = + searchText === "" + ? (costingData && costingData.allCostCenterData) || [] + : costingData.allCostCenterData.filter((d) => + (d.cost_center || "").toString().toLowerCase().includes(searchText.toLowerCase()) + ); - return ( - - { - e.preventDefault(); - setSearchText(e.target.value); - }} - /> - - } - {...cardProps} - > - -
-
( - - - - {t("general.labels.totals")} - - - - {Dinero( - costingData && - costingData.allSummaryData && - costingData.allSummaryData.totalSales - ).toFormat()} - - - {Dinero( - costingData && - costingData.allSummaryData && - costingData.allSummaryData.totalCost - ).toFormat()} - - - {Dinero( - costingData && - costingData.allSummaryData && - costingData.allSummaryData.gpdollars - ).toFormat()} - - - - )} - /> - - - - ); + return ( + + { + e.preventDefault(); + setSearchText(e.target.value); + }} + /> + + } + {...cardProps} + > + +
+
( + + + {t("general.labels.totals")} + + + {Dinero( + costingData && costingData.allSummaryData && costingData.allSummaryData.totalSales + ).toFormat()} + + + {Dinero(costingData && costingData.allSummaryData && costingData.allSummaryData.totalCost).toFormat()} + + + {Dinero(costingData && costingData.allSummaryData && costingData.allSummaryData.gpdollars).toFormat()} + + + + )} + /> + + + + ); } diff --git a/client/src/components/dashboard-components/monthly-labor-sales/monthly-labor-sales.component.jsx b/client/src/components/dashboard-components/monthly-labor-sales/monthly-labor-sales.component.jsx index cd4f13e71..ca719b891 100644 --- a/client/src/components/dashboard-components/monthly-labor-sales/monthly-labor-sales.component.jsx +++ b/client/src/components/dashboard-components/monthly-labor-sales/monthly-labor-sales.component.jsx @@ -1,67 +1,64 @@ -import {Card} from "antd"; +import { Card } from "antd"; import Dinero from "dinero.js"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {Cell, Pie, PieChart, ResponsiveContainer, Sector} from "recharts"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Cell, Pie, PieChart, ResponsiveContainer, Sector } from "recharts"; import DashboardRefreshRequired from "../refresh-required.component"; -export default function DashboardMonthlyLaborSales({data, ...cardProps}) { - const {t} = useTranslation(); - const [activeIndex, setActiveIndex] = useState(0); - if (!data) return null; - if (!data.monthly_sales) return ; +export default function DashboardMonthlyLaborSales({ data, ...cardProps }) { + const { t } = useTranslation(); + const [activeIndex, setActiveIndex] = useState(0); + if (!data) return null; + if (!data.monthly_sales) return ; - const laborData = {}; + const laborData = {}; - data.monthly_sales.forEach((job) => { - job.joblines.forEach((jobline) => { - if (!jobline.mod_lbr_ty) return; - if (!laborData[jobline.mod_lbr_ty]) - laborData[jobline.mod_lbr_ty] = Dinero(); - laborData[jobline.mod_lbr_ty] = laborData[jobline.mod_lbr_ty].add( - Dinero({ - amount: Math.round( - (job[`rate_${jobline.mod_lbr_ty.toLowerCase()}`] || 0) * 100 - ), - }).multiply(jobline.mod_lb_hrs || 0) - ); - }); + data.monthly_sales.forEach((job) => { + job.joblines.forEach((jobline) => { + if (!jobline.mod_lbr_ty) return; + if (!laborData[jobline.mod_lbr_ty]) laborData[jobline.mod_lbr_ty] = Dinero(); + laborData[jobline.mod_lbr_ty] = laborData[jobline.mod_lbr_ty].add( + Dinero({ + amount: Math.round((job[`rate_${jobline.mod_lbr_ty.toLowerCase()}`] || 0) * 100) + }).multiply(jobline.mod_lb_hrs || 0) + ); }); + }); - const chartData = Object.keys(laborData).map((key) => { - return { - name: t(`joblines.fields.lbr_types.${key.toUpperCase()}`), - value: laborData[key].getAmount() / 100, - color: pieColor(key.toUpperCase()), - }; - }); + const chartData = Object.keys(laborData).map((key) => { + return { + name: t(`joblines.fields.lbr_types.${key.toUpperCase()}`), + value: laborData[key].getAmount() / 100, + color: pieColor(key.toUpperCase()) + }; + }); - return ( - -
- - - setActiveIndex(index)} - > - {chartData.map((entry, index) => ( - - ))} - - - -
-
- ); + return ( + +
+ + + setActiveIndex(index)} + > + {chartData.map((entry, index) => ( + + ))} + + + +
+
+ ); } export const DashboardMonthlyRevenueGraphGql = ` @@ -69,75 +66,75 @@ export const DashboardMonthlyRevenueGraphGql = ` `; const pieColor = (type) => { - if (type === "LAA") return "lightgreen"; - else if (type === "LAB") return "dodgerblue"; - else if (type === "LAD") return "aliceblue"; - else if (type === "LAE") return "seafoam"; - else if (type === "LAG") return "chartreuse"; - else if (type === "LAF") return "magenta"; - else if (type === "LAM") return "gold"; - else if (type === "LAR") return "crimson"; - else if (type === "LAU") return "slategray"; - else if (type === "LA1") return "slategray"; - else if (type === "LA2") return "slategray"; - else if (type === "LA3") return "slategray"; - else if (type === "LA4") return "slategray"; - return "slategray"; + if (type === "LAA") return "lightgreen"; + else if (type === "LAB") return "dodgerblue"; + else if (type === "LAD") return "aliceblue"; + else if (type === "LAE") return "seafoam"; + else if (type === "LAG") return "chartreuse"; + else if (type === "LAF") return "magenta"; + else if (type === "LAM") return "gold"; + else if (type === "LAR") return "crimson"; + else if (type === "LAU") return "slategray"; + else if (type === "LA1") return "slategray"; + else if (type === "LA2") return "slategray"; + else if (type === "LA3") return "slategray"; + else if (type === "LA4") return "slategray"; + return "slategray"; }; const renderActiveShape = (props) => { - //const RADIAN = Math.PI / 180; - const { - cx, - cy, - //midAngle, - innerRadius, - outerRadius, - startAngle, - endAngle, - fill, - payload, - // percent, - value, - } = props; - // const sin = Math.sin(-RADIAN * midAngle); - // const cos = Math.cos(-RADIAN * midAngle); - // // const sx = cx + (outerRadius + 10) * cos; - // const sy = cy + (outerRadius + 10) * sin; - // const mx = cx + (outerRadius + 30) * cos; - // const my = cy + (outerRadius + 30) * sin; - // //const ex = mx + (cos >= 0 ? 1 : -1) * 22; - // const ey = my; - //const textAnchor = cos >= 0 ? "start" : "end"; + //const RADIAN = Math.PI / 180; + const { + cx, + cy, + //midAngle, + innerRadius, + outerRadius, + startAngle, + endAngle, + fill, + payload, + // percent, + value + } = props; + // const sin = Math.sin(-RADIAN * midAngle); + // const cos = Math.cos(-RADIAN * midAngle); + // // const sx = cx + (outerRadius + 10) * cos; + // const sy = cy + (outerRadius + 10) * sin; + // const mx = cx + (outerRadius + 30) * cos; + // const my = cy + (outerRadius + 30) * sin; + // //const ex = mx + (cos >= 0 ? 1 : -1) * 22; + // const ey = my; + //const textAnchor = cos >= 0 ? "start" : "end"; - return ( - - - {payload.name} - - - {Dinero({amount: Math.round(value * 100)}).toFormat()} - - - - - ); + return ( + + + {payload.name} + + + {Dinero({ amount: Math.round(value * 100) }).toFormat()} + + + + + ); }; // ; +export default function DashboardMonthlyPartsSales({ data, ...cardProps }) { + const { t } = useTranslation(); + const [activeIndex, setActiveIndex] = useState(0); + if (!data) return null; + if (!data.monthly_sales) return ; - const partData = {}; + const partData = {}; - data.monthly_sales.forEach((job) => { - job.joblines.forEach((jobline) => { - if (!jobline.part_type) return; - if (!partData[jobline.part_type]) partData[jobline.part_type] = Dinero(); - partData[jobline.part_type] = partData[jobline.part_type].add( - Dinero({amount: Math.round((jobline.act_price || 0) * 100)}).multiply( - jobline.part_qty || 0 - ) - ); - }); + data.monthly_sales.forEach((job) => { + job.joblines.forEach((jobline) => { + if (!jobline.part_type) return; + if (!partData[jobline.part_type]) partData[jobline.part_type] = Dinero(); + partData[jobline.part_type] = partData[jobline.part_type].add( + Dinero({ amount: Math.round((jobline.act_price || 0) * 100) }).multiply(jobline.part_qty || 0) + ); }); + }); - const chartData = Object.keys(partData).map((key) => { - return { - name: t(`joblines.fields.part_types.${key.toUpperCase()}`), - value: partData[key].getAmount() / 100, - color: pieColor(key.toUpperCase()), - }; - }); + const chartData = Object.keys(partData).map((key) => { + return { + name: t(`joblines.fields.part_types.${key.toUpperCase()}`), + value: partData[key].getAmount() / 100, + color: pieColor(key.toUpperCase()) + }; + }); - return ( - -
- - - setActiveIndex(index)} - > - {chartData.map((entry, index) => ( - - ))} - - - -
-
- ); + return ( + +
+ + + setActiveIndex(index)} + > + {chartData.map((entry, index) => ( + + ))} + + + +
+
+ ); } export const DashboardMonthlyRevenueGraphGql = ` `; const pieColor = (type) => { - if (type === "PAA") return "darkgreen"; - else if (type === "PAC") return "green"; - else if (type === "PAE") return "gold"; - else if (type === "PAG") return "seafoam"; - else if (type === "PAL") return "chartreuse"; - else if (type === "PAM") return "magenta"; - else if (type === "PAN") return "crimson"; - else if (type === "PAO") return "gold"; - else if (type === "PAP") return "crimson"; - else if (type === "PAR") return "indigo"; - else if (type === "PAS") return "dodgerblue"; - else if (type === "PASL") return "dodgerblue"; - return "slategray"; + if (type === "PAA") return "darkgreen"; + else if (type === "PAC") return "green"; + else if (type === "PAE") return "gold"; + else if (type === "PAG") return "seafoam"; + else if (type === "PAL") return "chartreuse"; + else if (type === "PAM") return "magenta"; + else if (type === "PAN") return "crimson"; + else if (type === "PAO") return "gold"; + else if (type === "PAP") return "crimson"; + else if (type === "PAR") return "indigo"; + else if (type === "PAS") return "dodgerblue"; + else if (type === "PASL") return "dodgerblue"; + return "slategray"; }; const renderActiveShape = (props) => { - // const RADIAN = Math.PI / 180; - const { - cx, - cy, - // midAngle, - innerRadius, - outerRadius, - startAngle, - endAngle, - fill, - payload, - // percent, - value, - } = props; - // const sin = Math.sin(-RADIAN * midAngle); - // const cos = Math.cos(-RADIAN * midAngle); - // const sx = cx + (outerRadius + 10) * cos; - //const sy = cy + (outerRadius + 10) * sin; - // const mx = cx + (outerRadius + 30) * cos; - //const my = cy + (outerRadius + 30) * sin; - // const ex = mx + (cos >= 0 ? 1 : -1) * 22; - // const ey = my; - // const textAnchor = cos >= 0 ? "start" : "end"; + // const RADIAN = Math.PI / 180; + const { + cx, + cy, + // midAngle, + innerRadius, + outerRadius, + startAngle, + endAngle, + fill, + payload, + // percent, + value + } = props; + // const sin = Math.sin(-RADIAN * midAngle); + // const cos = Math.cos(-RADIAN * midAngle); + // const sx = cx + (outerRadius + 10) * cos; + //const sy = cy + (outerRadius + 10) * sin; + // const mx = cx + (outerRadius + 30) * cos; + //const my = cy + (outerRadius + 30) * sin; + // const ex = mx + (cos >= 0 ? 1 : -1) * 22; + // const ey = my; + // const textAnchor = cos >= 0 ? "start" : "end"; - return ( - - - {payload.name} - - - {Dinero({amount: Math.round(value * 100)}).toFormat()} - - - - - ); + return ( + + + {payload.name} + + + {Dinero({ amount: Math.round(value * 100) }).toFormat()} + + + + + ); }; diff --git a/client/src/components/dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component.jsx b/client/src/components/dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component.jsx index d5e9c9627..4f9f5ac4c 100644 --- a/client/src/components/dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component.jsx +++ b/client/src/components/dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component.jsx @@ -1,83 +1,66 @@ -import {Card} from "antd"; +import { Card } from "antd"; import dayjs from "../../../utils/day"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import _ from "lodash"; -import {Area, Bar, CartesianGrid, ComposedChart, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis,} from "recharts"; +import { Area, Bar, CartesianGrid, ComposedChart, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; import Dinero from "dinero.js"; import * as Utils from "../../scoreboard-targets-table/scoreboard-targets-table.util"; import DashboardRefreshRequired from "../refresh-required.component"; -export default function DashboardMonthlyRevenueGraph({data, ...cardProps}) { - const {t} = useTranslation(); - if (!data) return null; - if (!data.monthly_sales) return ; +export default function DashboardMonthlyRevenueGraph({ data, ...cardProps }) { + const { t } = useTranslation(); + if (!data) return null; + if (!data.monthly_sales) return ; - const jobsByDate = _.groupBy(data.monthly_sales, (item) => - dayjs(item.date_invoiced).format("YYYY-MM-DD") - ); + const jobsByDate = _.groupBy(data.monthly_sales, (item) => dayjs(item.date_invoiced).format("YYYY-MM-DD")); - const listOfDays = Utils.ListOfDaysInCurrentMonth(); + const listOfDays = Utils.ListOfDaysInCurrentMonth(); - const chartData = listOfDays.reduce((acc, val) => { - //Sum up the current day. - let dailySales; - if (!!jobsByDate[val]) { - dailySales = jobsByDate[val].reduce((dayAcc, dayVal) => { - return dayAcc.add( - Dinero((dayVal.job_totals && dayVal.job_totals.totals.subtotal) || 0) - ); - }, Dinero()); - } else { - dailySales = Dinero(); - } + const chartData = listOfDays.reduce((acc, val) => { + //Sum up the current day. + let dailySales; + if (!!jobsByDate[val]) { + dailySales = jobsByDate[val].reduce((dayAcc, dayVal) => { + return dayAcc.add(Dinero((dayVal.job_totals && dayVal.job_totals.totals.subtotal) || 0)); + }, Dinero()); + } else { + dailySales = Dinero(); + } - const theValue = { - date: dayjs(val).format("DD"), - dailySales: dailySales.getAmount() / 100, - accSales: - acc.length > 0 - ? acc[acc.length - 1].accSales + dailySales.getAmount() / 100 - : dailySales.getAmount() / 100, - }; + const theValue = { + date: dayjs(val).format("DD"), + dailySales: dailySales.getAmount() / 100, + accSales: + acc.length > 0 ? acc[acc.length - 1].accSales + dailySales.getAmount() / 100 : dailySales.getAmount() / 100 + }; - return [...acc, theValue]; - }, []); + return [...acc, theValue]; + }, []); - return ( - -
- - - - - - value && value.toFixed(2)} - /> - - - - - -
-
- ); + return ( + +
+ + + + + + value && value.toFixed(2)} /> + + + + + +
+
+ ); } export const DashboardMonthlyRevenueGraphGql = ` diff --git a/client/src/components/dashboard-components/pojected-monthly-sales/projected-monthly-sales.component.jsx b/client/src/components/dashboard-components/pojected-monthly-sales/projected-monthly-sales.component.jsx index d315fda6e..2845bc858 100644 --- a/client/src/components/dashboard-components/pojected-monthly-sales/projected-monthly-sales.component.jsx +++ b/client/src/components/dashboard-components/pojected-monthly-sales/projected-monthly-sales.component.jsx @@ -1,34 +1,26 @@ -import {Card, Statistic} from "antd"; +import { Card, Statistic } from "antd"; import Dinero from "dinero.js"; import dayjs from "../../../utils/day"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import DashboardRefreshRequired from "../refresh-required.component"; -export default function DashboardProjectedMonthlySales({data, ...cardProps}) { - const {t} = useTranslation(); - if (!data) return null; - if (!data.projected_monthly_sales) - return ; +export default function DashboardProjectedMonthlySales({ data, ...cardProps }) { + const { t } = useTranslation(); + if (!data) return null; + if (!data.projected_monthly_sales) return ; - const dollars = - data.projected_monthly_sales && - data.projected_monthly_sales.reduce( - (acc, val) => - acc.add( - Dinero( - val.job_totals && - val.job_totals.totals && - val.job_totals.totals.subtotal - ) - ), - Dinero() - ); - return ( - - - + const dollars = + data.projected_monthly_sales && + data.projected_monthly_sales.reduce( + (acc, val) => acc.add(Dinero(val.job_totals && val.job_totals.totals && val.job_totals.totals.subtotal)), + Dinero() ); + return ( + + + + ); } export const DashboardProjectedMonthlySalesGql = ` @@ -38,12 +30,9 @@ export const DashboardProjectedMonthlySalesGql = ` {_and: [ {date_invoiced:{_is_null: false }}, {date_invoiced: {_gte: "${dayjs() - .startOf("month") - .startOf("day") - .toISOString()}"}}, {date_invoiced: {_lte: "${dayjs() - .endOf("month") - .endOf("day") - .toISOString()}"}}]}, + .startOf("month") + .startOf("day") + .toISOString()}"}}, {date_invoiced: {_lte: "${dayjs().endOf("month").endOf("day").toISOString()}"}}]}, { _and:[ @@ -51,10 +40,7 @@ _and:[ {actual_completion: {_gte: "${dayjs() .startOf("month") .startOf("day") - .toISOString()}"}}, {actual_completion: {_lte: "${dayjs() - .endOf("month") - .endOf("day") - .toISOString()}"}} + .toISOString()}"}}, {actual_completion: {_lte: "${dayjs().endOf("month").endOf("day").toISOString()}"}} ] }, @@ -63,12 +49,9 @@ _and:[ {date_invoiced: {_is_null: true}}, {actual_completion: {_is_null: true}} {scheduled_completion: {_gte: "${dayjs() - .startOf("month") - .startOf("day") - .toISOString()}"}}, {scheduled_completion: {_lte: "${dayjs() - .endOf("month") - .endOf("day") - .toISOString()}"}} + .startOf("month") + .startOf("day") + .toISOString()}"}}, {scheduled_completion: {_lte: "${dayjs().endOf("month").endOf("day").toISOString()}"}} ]} diff --git a/client/src/components/dashboard-components/refresh-required.component.jsx b/client/src/components/dashboard-components/refresh-required.component.jsx index f4614e025..bae6f0e8e 100644 --- a/client/src/components/dashboard-components/refresh-required.component.jsx +++ b/client/src/components/dashboard-components/refresh-required.component.jsx @@ -1,25 +1,25 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {Card} from "antd"; +import { SyncOutlined } from "@ant-design/icons"; +import { Card } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; export default function DashboardRefreshRequired(props) { - const {t} = useTranslation(); + const { t } = useTranslation(); - return ( - -
- -
{t("dashboard.errors.refreshrequired")}
-
-
- ); + return ( + +
+ +
{t("dashboard.errors.refreshrequired")}
+
+
+ ); } diff --git a/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx b/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx index 5d24bb606..d3caeaca2 100644 --- a/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx +++ b/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx @@ -1,8 +1,4 @@ -import { - BranchesOutlined, - ExclamationCircleFilled, - PauseCircleOutlined, -} from "@ant-design/icons"; +import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined } from "@ant-design/icons"; import { Card, Space, Switch, Table, Tooltip, Typography } from "antd"; import dayjs from "../../../utils/day"; import React, { useState } from "react"; @@ -13,26 +9,20 @@ import { onlyUnique } from "../../../utils/arrayHelper"; import { alphaSort, dateSort } from "../../../utils/sorters"; import useLocalStorage from "../../../utils/useLocalStorage"; import ChatOpenButton from "../../chat-open-button/chat-open-button.component"; -import OwnerNameDisplay, { - OwnerNameDisplayFunction, -} from "../../owner-name-display/owner-name-display.component"; +import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../../owner-name-display/owner-name-display.component"; import DashboardRefreshRequired from "../refresh-required.component"; export default function DashboardScheduledInToday({ data, ...cardProps }) { const { t } = useTranslation(); const [state, setState] = useState({ sortedInfo: {}, - filteredInfo: {}, + filteredInfo: {} }); - const [isTvModeScheduledIn, setIsTvModeScheduledIn] = useLocalStorage( - "isTvModeScheduledIn", - false - ); + const [isTvModeScheduledIn, setIsTvModeScheduledIn] = useLocalStorage("isTvModeScheduledIn", false); if (!data) return null; - if (!data.scheduled_in_today) - return ; + if (!data.scheduled_in_today) return ; const appt = []; // Flatten Data data.scheduled_in_today.forEach((item) => { @@ -68,13 +58,13 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { vehicleid: item.job.vehicleid, note: item.note, start: item.start, - title: item.title, + title: item.title }; appt.push(i); } }); appt.sort(function (a, b) { - return dayjs(a.start) - dayjs(b.start); + return dayjs(a.start) - dayjs(b.start); }); const tvFontSize = 16; @@ -87,35 +77,28 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { key: "start", ellipsis: true, sorter: (a, b) => dateSort(a.start, b.start), - sortOrder: - state.sortedInfo.columnKey === "start" && state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "start" && state.sortedInfo.order, render: (text, record) => ( {record.start} - ), + ) }, { title: t("jobs.fields.ro_number"), dataIndex: "ro_number", key: "ro_number", sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, render: (text, record) => ( - e.stopPropagation()} - > + e.stopPropagation()}> {record.ro_number || t("general.labels.na")} {record.production_vars && record.production_vars.alert ? ( ) : null} - {record.suspended && ( - - )} + {record.suspended && } {record.iouparent && ( @@ -124,23 +107,18 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { - ), + ) }, { title: t("jobs.fields.owner"), dataIndex: "owner", key: "owner", ellipsis: true, - sorter: (a, b) => - alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, render: (text, record) => { return record.ownerid ? ( - e.stopPropagation()} - > + e.stopPropagation()}> @@ -150,7 +128,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { ); - }, + } }, { title: t("jobs.fields.vehicle"), @@ -159,23 +137,15 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { ellipsis: true, sorter: (a, b) => alphaSort( - `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${ - a.v_model_desc || "" - }`, + `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`, `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` ), - sortOrder: - state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, render: (text, record) => { return record.vehicleid ? ( - e.stopPropagation()} - > + e.stopPropagation()}> - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} ) : ( @@ -183,7 +153,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { record.v_model_yr || "" } ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} ); - }, + } }, { title: t("appointments.fields.alt_transport"), @@ -191,9 +161,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { key: "alt_transport", ellipsis: true, sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), - sortOrder: - state.sortedInfo.columnKey === "alt_transport" && - state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order, filters: (appt && appt @@ -202,47 +170,38 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { .map((s) => { return { text: s || "No Alt. Transport", - value: [s], + value: [s] }; }) .sort((a, b) => alphaSort(a.text, b.text))) || [], onFilter: (value, record) => value.includes(record.alt_transport), render: (text, record) => ( - - {record.alt_transport} - - ), + {record.alt_transport} + ) }, { title: t("jobs.fields.lab"), dataIndex: "joblines_body", key: "joblines_body", sorter: (a, b) => a.joblines_body - b.joblines_body, - sortOrder: - state.sortedInfo.columnKey === "joblines_body" && - state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "joblines_body" && state.sortedInfo.order, align: "right", render: (text, record) => ( - - {record.joblines_body.toFixed(1)} - - ), + {record.joblines_body.toFixed(1)} + ) }, { title: t("jobs.fields.lar"), dataIndex: "joblines_ref", key: "joblines_ref", sorter: (a, b) => a.joblines_ref - b.joblines_ref, - sortOrder: - state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order, align: "right", render: (text, record) => ( - - {record.joblines_ref.toFixed(1)} - - ), - }, + {record.joblines_ref.toFixed(1)} + ) + } ]; const columns = [ @@ -252,30 +211,23 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { key: "start", ellipsis: true, sorter: (a, b) => dateSort(a.start, b.start), - sortOrder: - state.sortedInfo.columnKey === "start" && state.sortedInfo.order, - render: (text, record) => {record.start}, + sortOrder: state.sortedInfo.columnKey === "start" && state.sortedInfo.order, + render: (text, record) => {record.start} }, { title: t("jobs.fields.ro_number"), dataIndex: "ro_number", key: "ro_number", sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, render: (text, record) => ( - e.stopPropagation()} - > + e.stopPropagation()}> {record.ro_number || t("general.labels.na")} {record.production_vars && record.production_vars.alert ? ( ) : null} - {record.suspended && ( - - )} + {record.suspended && } {record.iouparent && ( @@ -283,23 +235,18 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { )} - ), + ) }, { title: t("jobs.fields.owner"), dataIndex: "owner", key: "owner", ellipsis: true, - sorter: (a, b) => - alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, render: (text, record) => { return record.ownerid ? ( - e.stopPropagation()} - > + e.stopPropagation()}> ) : ( @@ -307,7 +254,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { ); - }, + } }, { title: t("dashboard.labels.phone"), @@ -320,7 +267,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { - ), + ) }, { title: t("jobs.fields.ownr_ea"), @@ -328,9 +275,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { key: "ownr_ea", ellipsis: true, responsive: ["md"], - render: (text, record) => ( - {record.ownr_ea} - ), + render: (text, record) => {record.ownr_ea} }, { title: t("jobs.fields.vehicle"), @@ -339,29 +284,19 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { ellipsis: true, sorter: (a, b) => alphaSort( - `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${ - a.v_model_desc || "" - }`, + `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`, `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` ), - sortOrder: - state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, render: (text, record) => { return record.vehicleid ? ( - e.stopPropagation()} - > - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} + e.stopPropagation()}> + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} ) : ( - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} ); - }, + } }, { title: t("jobs.fields.ins_co_nm"), @@ -370,8 +305,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { ellipsis: true, responsive: ["md"], sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), - sortOrder: - state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order, filters: (appt && appt @@ -380,12 +314,12 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { .map((s) => { return { text: s || "No Ins. Co.*", - value: [s], + value: [s] }; }) .sort((a, b) => alphaSort(a.text, b.text))) || [], - onFilter: (value, record) => value.includes(record.ins_co_nm), + onFilter: (value, record) => value.includes(record.ins_co_nm) }, { title: t("appointments.fields.alt_transport"), @@ -393,9 +327,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { key: "alt_transport", ellipsis: true, sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), - sortOrder: - state.sortedInfo.columnKey === "alt_transport" && - state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order, filters: (appt && appt @@ -404,13 +336,13 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { .map((s) => { return { text: s || "No Alt. Transport", - value: [s], + value: [s] }; }) .sort((a, b) => alphaSort(a.text, b.text))) || [], - onFilter: (value, record) => value.includes(record.alt_transport), - }, + onFilter: (value, record) => value.includes(record.alt_transport) + } ]; const handleTableChange = (pagination, filters, sorter) => { @@ -419,15 +351,12 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { return ( {t("general.labels.tvmode")} - setIsTvModeScheduledIn(!isTvModeScheduledIn)} - defaultChecked={isTvModeScheduledIn} - /> + setIsTvModeScheduledIn(!isTvModeScheduledIn)} defaultChecked={isTvModeScheduledIn} /> } {...cardProps} @@ -449,11 +378,9 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { } export const DashboardScheduledInTodayGql = ` - scheduled_in_today: appointments(where: {start: {_gte: "${dayjs() - .startOf("day") - .toISOString()}", _lte: "${dayjs() - .endOf("day") - .toISOString()}"}, canceled: {_eq: false}, block: {_neq: true}}) { + scheduled_in_today: appointments(where: {start: {_gte: "${dayjs().startOf("day").toISOString()}", _lte: "${dayjs() + .endOf("day") + .toISOString()}"}, canceled: {_eq: false}, block: {_neq: true}}) { canceled id job { diff --git a/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx b/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx index 1639c8fe9..55a783e8e 100644 --- a/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx +++ b/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx @@ -1,452 +1,382 @@ -import {BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined,} from "@ant-design/icons"; -import {Card, Space, Switch, Table, Tooltip, Typography} from "antd"; +import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined } from "@ant-design/icons"; +import { Card, Space, Switch, Table, Tooltip, Typography } from "antd"; import dayjs from "../../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {Link} from "react-router-dom"; -import {TimeFormatter} from "../../../utils/DateFormatter"; -import {onlyUnique} from "../../../utils/arrayHelper"; -import {alphaSort, dateSort} from "../../../utils/sorters"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; +import { TimeFormatter } from "../../../utils/DateFormatter"; +import { onlyUnique } from "../../../utils/arrayHelper"; +import { alphaSort, dateSort } from "../../../utils/sorters"; import useLocalStorage from "../../../utils/useLocalStorage"; import ChatOpenButton from "../../chat-open-button/chat-open-button.component"; -import OwnerNameDisplay, {OwnerNameDisplayFunction,} from "../../owner-name-display/owner-name-display.component"; +import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../../owner-name-display/owner-name-display.component"; import DashboardRefreshRequired from "../refresh-required.component"; -export default function DashboardScheduledOutToday({data, ...cardProps}) { - const {t} = useTranslation(); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {},}); - const [isTvModeScheduledOut, setIsTvModeScheduledOut] = useLocalStorage( - "isTvModeScheduledOut", - false - ); - if (!data) return null; - if (!data.scheduled_out_today) - return ; +export default function DashboardScheduledOutToday({ data, ...cardProps }) { + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: {} + }); + const [isTvModeScheduledOut, setIsTvModeScheduledOut] = useLocalStorage("isTvModeScheduledOut", false); + if (!data) return null; + if (!data.scheduled_out_today) return ; - const scheduledOutToday = data.scheduled_out_today.map((item) => { - const joblines_body = item.joblines - ? item.joblines - .filter((l) => l.mod_lbr_ty !== "LAR") - .reduce((acc, val) => acc + val.mod_lb_hrs, 0) - : 0; - const joblines_ref = item.joblines - ? item.joblines - .filter((l) => l.mod_lbr_ty === "LAR") - .reduce((acc, val) => acc + val.mod_lb_hrs, 0) - : 0; - return { - ...item, - joblines_body, - joblines_ref, - }; - }); + const scheduledOutToday = data.scheduled_out_today.map((item) => { + const joblines_body = item.joblines + ? item.joblines.filter((l) => l.mod_lbr_ty !== "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0) + : 0; + const joblines_ref = item.joblines + ? item.joblines.filter((l) => l.mod_lbr_ty === "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0) + : 0; + return { + ...item, + joblines_body, + joblines_ref + }; + }); - console.log('Scheduled Out Today') - console.dir(scheduledOutToday); + console.log("Scheduled Out Today"); + console.dir(scheduledOutToday); - const tvFontSize = 18; - const tvFontWeight = "bold"; + const tvFontSize = 18; + const tvFontWeight = "bold"; - const tvColumns = [ - { - title: t("jobs.fields.scheduled_completion"), - dataIndex: "scheduled_completion", - key: "scheduled_completion", - ellipsis: true, - sorter: (a, b) => - dateSort(a.scheduled_completion, b.scheduled_completion), - sortOrder: - state.sortedInfo.columnKey === "scheduled_completion" && - state.sortedInfo.order, - render: (text, record) => ( - + const tvColumns = [ + { + title: t("jobs.fields.scheduled_completion"), + dataIndex: "scheduled_completion", + key: "scheduled_completion", + ellipsis: true, + sorter: (a, b) => dateSort(a.scheduled_completion, b.scheduled_completion), + sortOrder: state.sortedInfo.columnKey === "scheduled_completion" && state.sortedInfo.order, + render: (text, record) => ( + {record.scheduled_completion} - ), - }, - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - render: (text, record) => ( - e.stopPropagation()} - > - - + ) + }, + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => ( + e.stopPropagation()}> + + {record.ro_number || t("general.labels.na")} - {record.production_vars && record.production_vars.alert ? ( - - ) : null} - {record.suspended && ( - - )} - {record.iouparent && ( - - - - )} + {record.production_vars && record.production_vars.alert ? ( + + ) : null} + {record.suspended && } + {record.iouparent && ( + + + + )} - - - ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - ellipsis: true, - sorter: (a, b) => - alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, - render: (text, record) => { - console.log('Render record out today'); - console.dir(record); - return record.ownerid ? ( - e.stopPropagation()} - > - - + + + ) + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + render: (text, record) => { + console.log("Render record out today"); + console.dir(record); + return record.ownerid ? ( + e.stopPropagation()}> + + - - ) : ( - - + + ) : ( + + - ); - }, - }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - ellipsis: true, - sorter: (a, b) => - alphaSort( - `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${ - a.v_model_desc || "" - }`, - `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` - ), - sortOrder: - state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, - render: (text, record) => { - return record.vehicleid ? ( - e.stopPropagation()} - > - - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} + ); + } + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + sorter: (a, b) => + alphaSort( + `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`, + `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` + ), + sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, + render: (text, record) => { + return record.vehicleid ? ( + e.stopPropagation()}> + + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} - - ) : ( - {`${ - record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - ); - }, - }, - { - title: t("appointments.fields.alt_transport"), - dataIndex: "alt_transport", - key: "alt_transport", - ellipsis: true, - sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), - sortOrder: - state.sortedInfo.columnKey === "alt_transport" && - state.sortedInfo.order, - filters: - (scheduledOutToday && - scheduledOutToday - .map((j) => j.alt_transport) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Alt. Transport*", - value: [s], - }; - }) - .sort((a, b) => alphaSort(a.text, b.text))) || - [],onFilter: (value, record) => value.includes(record.alt_transport), - render: (text, record) => ( - - {record.alt_transport} - - ), - }, - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", - ellipsis: true, - sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - filters: - (scheduledOutToday && - scheduledOutToday - .map((j) => j.status) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Status*", - value: [s], - }; - }) - .sort((a, b) => alphaSort(a.text, b.text))) || - [], - onFilter: (value, record) => value.includes(record.status),render: (text, record) => ( - - {record.status} - - ), - }, - { - title: t("jobs.fields.lab"), - dataIndex: "joblines_body", - key: "joblines_body", - sorter: (a, b) => a.joblines_body - b.joblines_body, - sortOrder: - state.sortedInfo.columnKey === "joblines_body" && - state.sortedInfo.order, - align: "right", - render: (text, record) => ( - - {record.joblines_body.toFixed(1)} - - ), - }, - { - title: t("jobs.fields.lar"), - dataIndex: "joblines_ref", - key: "joblines_ref", - sorter: (a, b) => a.joblines_ref - b.joblines_ref, - sortOrder: - state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order, - align: "right", - render: (text, record) => ( - - {record.joblines_ref.toFixed(1)} - - ), - }, - ]; + + ) : ( + {`${ + record.v_model_yr || "" + } ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + ); + } + }, + { + title: t("appointments.fields.alt_transport"), + dataIndex: "alt_transport", + key: "alt_transport", + ellipsis: true, + sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), + sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order, + filters: + (scheduledOutToday && + scheduledOutToday + .map((j) => j.alt_transport) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Alt. Transport*", + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.alt_transport), + render: (text, record) => ( + {record.alt_transport} + ) + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + ellipsis: true, + sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + filters: + (scheduledOutToday && + scheduledOutToday + .map((j) => j.status) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Status*", + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.status), + render: (text, record) => {record.status} + }, + { + title: t("jobs.fields.lab"), + dataIndex: "joblines_body", + key: "joblines_body", + sorter: (a, b) => a.joblines_body - b.joblines_body, + sortOrder: state.sortedInfo.columnKey === "joblines_body" && state.sortedInfo.order, + align: "right", + render: (text, record) => ( + {record.joblines_body.toFixed(1)} + ) + }, + { + title: t("jobs.fields.lar"), + dataIndex: "joblines_ref", + key: "joblines_ref", + sorter: (a, b) => a.joblines_ref - b.joblines_ref, + sortOrder: state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order, + align: "right", + render: (text, record) => ( + {record.joblines_ref.toFixed(1)} + ) + } + ]; - const columns = [ - { - title: t("jobs.fields.scheduled_completion"), - dataIndex: "scheduled_completion", - key: "scheduled_completion", - ellipsis: true, - sorter: (a, b) => - dateSort(a.scheduled_completion, b.scheduled_completion), - sortOrder: - state.sortedInfo.columnKey === "scheduled_completion" && - state.sortedInfo.order, - render: (text, record) => ( - {record.scheduled_completion} - ), - }, - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, render: (text, record) => ( - e.stopPropagation()} - > - - {record.ro_number || t("general.labels.na")} - {record.production_vars && record.production_vars.alert ? ( - - ) : null} - {record.suspended && ( - - )} - {record.iouparent && ( - - - - )} - - - ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - ellipsis: true, - sorter: (a, b) => - alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, - render: (text, record) => { - return record.ownerid ? ( - e.stopPropagation()} - > - - - ) : ( - - + const columns = [ + { + title: t("jobs.fields.scheduled_completion"), + dataIndex: "scheduled_completion", + key: "scheduled_completion", + ellipsis: true, + sorter: (a, b) => dateSort(a.scheduled_completion, b.scheduled_completion), + sortOrder: state.sortedInfo.columnKey === "scheduled_completion" && state.sortedInfo.order, + render: (text, record) => {record.scheduled_completion} + }, + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => ( + e.stopPropagation()}> + + {record.ro_number || t("general.labels.na")} + {record.production_vars && record.production_vars.alert ? ( + + ) : null} + {record.suspended && } + {record.iouparent && ( + + + + )} + + + ) + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + render: (text, record) => { + return record.ownerid ? ( + e.stopPropagation()}> + + + ) : ( + + - ); - }, - }, - { - title: t("dashboard.labels.phone"), - dataIndex: "ownr_ph", - key: "ownr_ph", - ellipsis: true, - responsive: ["md"], - render: (text, record) => ( - + ); + } + }, + { + title: t("dashboard.labels.phone"), + dataIndex: "ownr_ph", + key: "ownr_ph", + ellipsis: true, + responsive: ["md"], + render: (text, record) => ( + + - - ), - }, - { - title: t("jobs.fields.ownr_ea"), - dataIndex: "ownr_ea", - key: "ownr_ea", - ellipsis: true, - responsive: ["md"], - render: (text, record) => ( - {record.ownr_ea} - ), - }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - ellipsis: true, - sorter: (a, b) => - alphaSort( - `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${ - a.v_model_desc || "" - }`, - `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` - ), - sortOrder: - state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, render: (text, record) => { - return record.vehicleid ? ( - e.stopPropagation()} - > - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - - ) : ( - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - ); - }, - }, - { - title: t("jobs.fields.ins_co_nm"), - dataIndex: "ins_co_nm", - key: "ins_co_nm", - ellipsis: true, - responsive: ["md"], - sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), - sortOrder: - state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order, - filters: - (scheduledOutToday && - scheduledOutToday - .map((j) => j.ins_co_nm) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Ins. Co.*", - value: [s], - }; - }) - .sort((a, b) => alphaSort(a.text, b.text))) || - [], - onFilter: (value, record) => value.includes(record.ins_co_nm), - }, - { - title: t("appointments.fields.alt_transport"), - dataIndex: "alt_transport", - key: "alt_transport", - ellipsis: true, - sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), - sortOrder: - state.sortedInfo.columnKey === "alt_transport" && - state.sortedInfo.order, - filters: - (scheduledOutToday && - scheduledOutToday - .map((j) => j.alt_transport) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Alt. Transport*", - value: [s], - }; - }) - .sort((a, b) => alphaSort(a.text, b.text))) || - [], - onFilter: (value, record) => value.includes(record.alt_transport),}, - ]; + + + ) + }, + { + title: t("jobs.fields.ownr_ea"), + dataIndex: "ownr_ea", + key: "ownr_ea", + ellipsis: true, + responsive: ["md"], + render: (text, record) => {record.ownr_ea} + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + sorter: (a, b) => + alphaSort( + `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`, + `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` + ), + sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, + render: (text, record) => { + return record.vehicleid ? ( + e.stopPropagation()}> + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + + ) : ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + ); + } + }, + { + title: t("jobs.fields.ins_co_nm"), + dataIndex: "ins_co_nm", + key: "ins_co_nm", + ellipsis: true, + responsive: ["md"], + sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), + sortOrder: state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order, + filters: + (scheduledOutToday && + scheduledOutToday + .map((j) => j.ins_co_nm) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Ins. Co.*", + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.ins_co_nm) + }, + { + title: t("appointments.fields.alt_transport"), + dataIndex: "alt_transport", + key: "alt_transport", + ellipsis: true, + sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), + sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order, + filters: + (scheduledOutToday && + scheduledOutToday + .map((j) => j.alt_transport) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Alt. Transport*", + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.alt_transport) + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - return ( - - {t("general.labels.tvmode")} - setIsTvModeScheduledOut(!isTvModeScheduledOut)} - defaultChecked={isTvModeScheduledOut} - /> - - }{...cardProps} - > -
-
- - - ); + return ( + + {t("general.labels.tvmode")} + setIsTvModeScheduledOut(!isTvModeScheduledOut)} + defaultChecked={isTvModeScheduledOut} + /> + + } + {...cardProps} + > +
+
+ + + ); } export const DashboardScheduledOutTodayGql = ` diff --git a/client/src/components/dashboard-components/total-production-dollars/total-production-dollars.component.jsx b/client/src/components/dashboard-components/total-production-dollars/total-production-dollars.component.jsx index 5e563a475..4f3bf8599 100644 --- a/client/src/components/dashboard-components/total-production-dollars/total-production-dollars.component.jsx +++ b/client/src/components/dashboard-components/total-production-dollars/total-production-dollars.component.jsx @@ -1,27 +1,23 @@ -import {Card, Statistic} from "antd"; +import { Card, Statistic } from "antd"; import Dinero from "dinero.js"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import DashboardRefreshRequired from "../refresh-required.component"; -export default function DashboardTotalProductionDollars({ - data, - ...cardProps - }) { - const {t} = useTranslation(); - if (!data) return null; - if (!data.production_jobs) return ; - const dollars = - data.production_jobs && - data.production_jobs.reduce( - (acc, val) => - acc.add(Dinero(val.job_totals && val.job_totals.totals.subtotal)), - Dinero() - ); - - return ( - - - +export default function DashboardTotalProductionDollars({ data, ...cardProps }) { + const { t } = useTranslation(); + if (!data) return null; + if (!data.production_jobs) return ; + const dollars = + data.production_jobs && + data.production_jobs.reduce( + (acc, val) => acc.add(Dinero(val.job_totals && val.job_totals.totals.subtotal)), + Dinero() ); + + return ( + + + + ); } diff --git a/client/src/components/dashboard-components/total-production-hours/total-production-hours.component.jsx b/client/src/components/dashboard-components/total-production-hours/total-production-hours.component.jsx index 3d43bb1fb..ffd7068db 100644 --- a/client/src/components/dashboard-components/total-production-hours/total-production-hours.component.jsx +++ b/client/src/components/dashboard-components/total-production-hours/total-production-hours.component.jsx @@ -1,63 +1,47 @@ -import {Card, Space, Statistic} from "antd"; +import { Card, Space, Statistic } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../../redux/user/user.selectors"; import DashboardRefreshRequired from "../refresh-required.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({}); -export default connect( - mapStateToProps, - mapDispatchToProps -)(DashboardTotalProductionHours); +export default connect(mapStateToProps, mapDispatchToProps)(DashboardTotalProductionHours); -export function DashboardTotalProductionHours({ - bodyshop, - data, - ...cardProps - }) { - const {t} = useTranslation(); - if (!data) return null; - if (!data.production_jobs) return ; - const hours = - data.production_jobs && - data.production_jobs.reduce( - (acc, val) => { - return { - body: acc.body + val.labhrs.aggregate.sum.mod_lb_hrs, - ref: acc.ref + val.larhrs.aggregate.sum.mod_lb_hrs, - total: - acc.total + - val.labhrs.aggregate.sum.mod_lb_hrs + - val.larhrs.aggregate.sum.mod_lb_hrs, - }; - }, - {body: 0, ref: 0, total: 0} - ); - const aboveTargetHours = hours.total >= bodyshop.prodtargethrs; - return ( - - - - - - - +export function DashboardTotalProductionHours({ bodyshop, data, ...cardProps }) { + const { t } = useTranslation(); + if (!data) return null; + if (!data.production_jobs) return ; + const hours = + data.production_jobs && + data.production_jobs.reduce( + (acc, val) => { + return { + body: acc.body + val.labhrs.aggregate.sum.mod_lb_hrs, + ref: acc.ref + val.larhrs.aggregate.sum.mod_lb_hrs, + total: acc.total + val.labhrs.aggregate.sum.mod_lb_hrs + val.larhrs.aggregate.sum.mod_lb_hrs + }; + }, + { body: 0, ref: 0, total: 0 } ); + const aboveTargetHours = hours.total >= bodyshop.prodtargethrs; + return ( + + + + + + + + ); } export const DashboardTotalProductionHoursGql = ``; diff --git a/client/src/components/dashboard-grid/dashboard-grid.component.jsx b/client/src/components/dashboard-grid/dashboard-grid.component.jsx index 78466ad0a..614dbb90f 100644 --- a/client/src/components/dashboard-grid/dashboard-grid.component.jsx +++ b/client/src/components/dashboard-grid/dashboard-grid.component.jsx @@ -1,325 +1,316 @@ -import Icon, {SyncOutlined} from "@ant-design/icons"; -import {gql, useMutation, useQuery} from "@apollo/client"; -import {Button, Dropdown, notification, Space} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import Icon, { SyncOutlined } from "@ant-design/icons"; +import { gql, useMutation, useQuery } from "@apollo/client"; +import { Button, Dropdown, notification, Space } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; import i18next from "i18next"; import _ from "lodash"; import dayjs from "../../utils/day"; -import React, {useState} from "react"; -import {Responsive, WidthProvider} from "react-grid-layout"; -import {useTranslation} from "react-i18next"; -import {MdClose} from "react-icons/md"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {UPDATE_DASHBOARD_LAYOUT} from "../../graphql/user.queries"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { Responsive, WidthProvider } from "react-grid-layout"; +import { useTranslation } from "react-i18next"; +import { MdClose } from "react-icons/md"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { UPDATE_DASHBOARD_LAYOUT } from "../../graphql/user.queries"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import DashboardMonthlyEmployeeEfficiency, { - DashboardMonthlyEmployeeEfficiencyGql, + DashboardMonthlyEmployeeEfficiencyGql } from "../dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component"; import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component"; import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component"; import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component"; import DashboardMonthlyRevenueGraph, { - DashboardMonthlyRevenueGraphGql, + DashboardMonthlyRevenueGraphGql } from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component"; import DashboardProjectedMonthlySales, { - DashboardProjectedMonthlySalesGql, + DashboardProjectedMonthlySalesGql } from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component"; -import DashboardTotalProductionDollars - from "../dashboard-components/total-production-dollars/total-production-dollars.component"; +import DashboardTotalProductionDollars from "../dashboard-components/total-production-dollars/total-production-dollars.component"; import DashboardTotalProductionHours, { - DashboardTotalProductionHoursGql, + DashboardTotalProductionHoursGql } from "../dashboard-components/total-production-hours/total-production-hours.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; //Combination of the following: // /node_modules/react-grid-layout/css/styles.css // /node_modules/react-resizable/css/styles.css import DashboardScheduledInToday, { - DashboardScheduledInTodayGql, + DashboardScheduledInTodayGql } from "../dashboard-components/scheduled-in-today/scheduled-in-today.component"; import DashboardScheduledOutToday, { - DashboardScheduledOutTodayGql, + DashboardScheduledOutTodayGql } from "../dashboard-components/scheduled-out-today/scheduled-out-today.component"; import JobLifecycleDashboardComponent, { - JobLifecycleDashboardGQL + JobLifecycleDashboardGQL } from "../dashboard-components/job-lifecycle/job-lifecycle-dashboard.component"; import "./dashboard-grid.styles.scss"; -import {GenerateDashboardData} from "./dashboard-grid.utils"; +import { GenerateDashboardData } from "./dashboard-grid.utils"; const ResponsiveReactGridLayout = WidthProvider(Responsive); const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - bodyshop: selectBodyshop, + currentUser: selectCurrentUser, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function DashboardGridComponent({currentUser, bodyshop}) { - const {t} = useTranslation(); - const [state, setState] = useState({ - ...(bodyshop.associations[0].user.dashboardlayout - ? bodyshop.associations[0].user.dashboardlayout - : {items: [], layout: {}, layouts: []}), +export function DashboardGridComponent({ currentUser, bodyshop }) { + const { t } = useTranslation(); + const [state, setState] = useState({ + ...(bodyshop.associations[0].user.dashboardlayout + ? bodyshop.associations[0].user.dashboardlayout + : { items: [], layout: {}, layouts: [] }) + }); + + const { loading, error, data, refetch } = useQuery(createDashboardQuery(state), { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + + const [updateLayout] = useMutation(UPDATE_DASHBOARD_LAYOUT); + + const handleLayoutChange = async (layout, layouts) => { + logImEXEvent("dashboard_change_layout"); + + setState({ ...state, layout, layouts }); + + const result = await updateLayout({ + variables: { + email: currentUser.email, + layout: { ...state, layout, layouts } + } }); + if (!!result.errors) { + notification["error"]({ + message: t("dashboard.errors.updatinglayout", { + message: JSON.stringify(result.errors) + }) + }); + } + }; + const handleRemoveComponent = (key) => { + logImEXEvent("dashboard_remove_component", { name: key }); + const idxToRemove = state.items.findIndex((i) => i.i === key); - const {loading, error, data, refetch} = useQuery( - createDashboardQuery(state), - {fetchPolicy: "network-only", nextFetchPolicy: "network-only"} - ); + const items = _.cloneDeep(state.items); - const [updateLayout] = useMutation(UPDATE_DASHBOARD_LAYOUT); + items.splice(idxToRemove, 1); + setState({ ...state, items }); + }; - const handleLayoutChange = async (layout, layouts) => { - logImEXEvent("dashboard_change_layout"); - - setState({...state, layout, layouts}); - - const result = await updateLayout({ - variables: { - email: currentUser.email, - layout: {...state, layout, layouts}, - }, - }); - if (!!result.errors) { - notification["error"]({ - message: t("dashboard.errors.updatinglayout", { - message: JSON.stringify(result.errors), - }), - }); + const handleAddComponent = (e) => { + logImEXEvent("dashboard_add_component", { name: e }); + setState({ + ...state, + items: [ + ...state.items, + { + i: e.key, + x: (state.items.length * 2) % (state.cols || 12), + y: 99, // puts it at the bottom + w: componentList[e.key].w || 2, + h: componentList[e.key].h || 2 } - }; - const handleRemoveComponent = (key) => { - logImEXEvent("dashboard_remove_component", {name: key}); - const idxToRemove = state.items.findIndex((i) => i.i === key); + ] + }); + }; - const items = _.cloneDeep(state.items); + const dashboarddata = React.useMemo(() => GenerateDashboardData(data), [data]); + const existingLayoutKeys = state.items.map((i) => i.i); - items.splice(idxToRemove, 1); - setState({...state, items}); - }; + const menuItems = Object.keys(componentList).map((key) => ({ + key: key, + label: componentList[key].label, + value: key, + disabled: existingLayoutKeys.includes(key) + })); - const handleAddComponent = (e) => { - logImEXEvent("dashboard_add_component", {name: e}); - setState({ - ...state, - items: [ - ...state.items, - { - i: e.key, - x: (state.items.length * 2) % (state.cols || 12), - y: 99, // puts it at the bottom - w: componentList[e.key].w || 2, - h: componentList[e.key].h || 2, - }, - ], - }); - }; + const menu = { items: menuItems, onClick: handleAddComponent }; - const dashboarddata = React.useMemo( - () => GenerateDashboardData(data), - [data] - ); - const existingLayoutKeys = state.items.map((i) => i.i); + if (error) return ; - const menuItems = Object.keys(componentList).map((key) => ({ - key: key, - label: componentList[key].label, - value: key, - disabled: existingLayoutKeys.includes(key), - })); + return ( +
+ + + + + + + } + /> - const menu = {items: menuItems, onClick: handleAddComponent}; - - if (error) return ; - - return ( -
- - - - - - - } - /> - - + {state.items.map((item, index) => { + const TheComponent = componentList[item.i].component; + return ( +
- {state.items.map((item, index) => { - const TheComponent = componentList[item.i].component; - return ( -
- - handleRemoveComponent(item.i)} - /> - - -
- ); - })} - -
- ); + + handleRemoveComponent(item.i)} + /> + + +
+ ); + })} + +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(DashboardGridComponent); +export default connect(mapStateToProps, mapDispatchToProps)(DashboardGridComponent); const componentList = { - ProductionDollars: { - label: i18next.t("dashboard.titles.productiondollars"), - component: DashboardTotalProductionDollars, - gqlFragment: null, - w: 1, - h: 1, - minW: 2, - minH: 1, - }, - ProductionHours: { - label: i18next.t("dashboard.titles.productionhours"), - component: DashboardTotalProductionHours, - gqlFragment: DashboardTotalProductionHoursGql, - w: 3, - h: 1, - minW: 3, - minH: 1, - }, - ProjectedMonthlySales: { - label: i18next.t("dashboard.titles.projectedmonthlysales"), - component: DashboardProjectedMonthlySales, - gqlFragment: DashboardProjectedMonthlySalesGql, - w: 2, - h: 1, - minW: 2, - minH: 1, - }, - MonthlyRevenueGraph: { - label: i18next.t("dashboard.titles.monthlyrevenuegraph"), - component: DashboardMonthlyRevenueGraph, - gqlFragment: DashboardMonthlyRevenueGraphGql, - w: 4, - h: 2, - minW: 4, - minH: 2, - }, - MonthlyJobCosting: { - label: i18next.t("dashboard.titles.monthlyjobcosting"), - component: DashboardMonthlyJobCosting, - gqlFragment: null, - minW: 6, - minH: 3, - w: 6, - h: 3, - }, - MonthlyPartsSales: { - label: i18next.t("dashboard.titles.monthlypartssales"), - component: DashboardMonthlyPartsSales, - gqlFragment: null, - minW: 2, - minH: 2, - w: 2, - h: 2, - }, - MonthlyLaborSales: { - label: i18next.t("dashboard.titles.monthlylaborsales"), - component: DashboardMonthlyLaborSales, - gqlFragment: null, - minW: 2, - minH: 2, - w: 2, - h: 2, - }, - // Typo in Efficency should be Efficiency, but changing it would reset users dashboard settings - MonthlyEmployeeEfficency: { - label: i18next.t("dashboard.titles.monthlyemployeeefficiency"), - component: DashboardMonthlyEmployeeEfficiency, - gqlFragment: DashboardMonthlyEmployeeEfficiencyGql, - minW: 2, - minH: 2, - w: 2, - h: 2, - }, - ScheduleInToday: { - label: i18next.t("dashboard.titles.scheduledintoday"), - component: DashboardScheduledInToday, - gqlFragment: DashboardScheduledInTodayGql, - minW: 6, - minH: 2, - w: 10, - h: 3, - }, - ScheduleOutToday: { - label: i18next.t("dashboard.titles.scheduledouttoday"), - component: DashboardScheduledOutToday, - gqlFragment: DashboardScheduledOutTodayGql, - minW: 6, - minH: 2, - w: 10, - h: 3, - }, - JobLifecycle: { - label: i18next.t("dashboard.titles.joblifecycle"), - component: JobLifecycleDashboardComponent, - gqlFragment: JobLifecycleDashboardGQL, - minW: 6, - minH: 3, - w: 6, - h: 3, - }, + ProductionDollars: { + label: i18next.t("dashboard.titles.productiondollars"), + component: DashboardTotalProductionDollars, + gqlFragment: null, + w: 1, + h: 1, + minW: 2, + minH: 1 + }, + ProductionHours: { + label: i18next.t("dashboard.titles.productionhours"), + component: DashboardTotalProductionHours, + gqlFragment: DashboardTotalProductionHoursGql, + w: 3, + h: 1, + minW: 3, + minH: 1 + }, + ProjectedMonthlySales: { + label: i18next.t("dashboard.titles.projectedmonthlysales"), + component: DashboardProjectedMonthlySales, + gqlFragment: DashboardProjectedMonthlySalesGql, + w: 2, + h: 1, + minW: 2, + minH: 1 + }, + MonthlyRevenueGraph: { + label: i18next.t("dashboard.titles.monthlyrevenuegraph"), + component: DashboardMonthlyRevenueGraph, + gqlFragment: DashboardMonthlyRevenueGraphGql, + w: 4, + h: 2, + minW: 4, + minH: 2 + }, + MonthlyJobCosting: { + label: i18next.t("dashboard.titles.monthlyjobcosting"), + component: DashboardMonthlyJobCosting, + gqlFragment: null, + minW: 6, + minH: 3, + w: 6, + h: 3 + }, + MonthlyPartsSales: { + label: i18next.t("dashboard.titles.monthlypartssales"), + component: DashboardMonthlyPartsSales, + gqlFragment: null, + minW: 2, + minH: 2, + w: 2, + h: 2 + }, + MonthlyLaborSales: { + label: i18next.t("dashboard.titles.monthlylaborsales"), + component: DashboardMonthlyLaborSales, + gqlFragment: null, + minW: 2, + minH: 2, + w: 2, + h: 2 + }, + // Typo in Efficency should be Efficiency, but changing it would reset users dashboard settings + MonthlyEmployeeEfficency: { + label: i18next.t("dashboard.titles.monthlyemployeeefficiency"), + component: DashboardMonthlyEmployeeEfficiency, + gqlFragment: DashboardMonthlyEmployeeEfficiencyGql, + minW: 2, + minH: 2, + w: 2, + h: 2 + }, + ScheduleInToday: { + label: i18next.t("dashboard.titles.scheduledintoday"), + component: DashboardScheduledInToday, + gqlFragment: DashboardScheduledInTodayGql, + minW: 6, + minH: 2, + w: 10, + h: 3 + }, + ScheduleOutToday: { + label: i18next.t("dashboard.titles.scheduledouttoday"), + component: DashboardScheduledOutToday, + gqlFragment: DashboardScheduledOutTodayGql, + minW: 6, + minH: 2, + w: 10, + h: 3 + }, + JobLifecycle: { + label: i18next.t("dashboard.titles.joblifecycle"), + component: JobLifecycleDashboardComponent, + gqlFragment: JobLifecycleDashboardGQL, + minW: 6, + minH: 3, + w: 6, + h: 3 + } }; const createDashboardQuery = (state) => { - const componentBasedAdditions = - state && - Array.isArray(state.layout) && - state.layout - .map((item, index) => componentList[item.i].gqlFragment || "") - .join(""); - return gql` + const componentBasedAdditions = + state && + Array.isArray(state.layout) && + state.layout.map((item, index) => componentList[item.i].gqlFragment || "").join(""); + return gql` query QUERY_DASHBOARD_DETAILS { ${componentBasedAdditions || ""} monthly_sales: jobs(where: {_and: [ { voided: {_eq: false}}, {date_invoiced: {_gte: "${dayjs() - .startOf("month") - .startOf("day") - .toISOString()}"}}, {date_invoiced: {_lte: "${dayjs() - .endOf("month") - .endOf("day") - .toISOString()}"}}]}) { + .startOf("month") + .startOf("day") + .toISOString()}"}}, {date_invoiced: {_lte: "${dayjs() + .endOf("month") + .endOf("day") + .toISOString()}"}}]}) { id ro_number date_invoiced diff --git a/client/src/components/dashboard-grid/dashboard-grid.utils.js b/client/src/components/dashboard-grid/dashboard-grid.utils.js index a5fde93ba..d7215d1b4 100644 --- a/client/src/components/dashboard-grid/dashboard-grid.utils.js +++ b/client/src/components/dashboard-grid/dashboard-grid.utils.js @@ -1,3 +1,3 @@ export function GenerateDashboardData(data) { - return data; + return data; } diff --git a/client/src/components/data-label/data-label.component.jsx b/client/src/components/data-label/data-label.component.jsx index d95dbe697..f3a0880de 100644 --- a/client/src/components/data-label/data-label.component.jsx +++ b/client/src/components/data-label/data-label.component.jsx @@ -1,46 +1,42 @@ -import {Typography} from "antd"; +import { Typography } from "antd"; import React from "react"; export default function DataLabel({ - label, - hideIfNull, - children, - vertical, - open = true, - valueStyle = {}, - valueClassName, - onValueClick, - ...props - }) { - if (!open || (hideIfNull && !!!children)) return null; + label, + hideIfNull, + children, + vertical, + open = true, + valueStyle = {}, + valueClassName, + onValueClick, + ...props +}) { + if (!open || (hideIfNull && !!!children)) return null; - return ( -
-
- {`${label}:`} -
-
- {typeof children === "string" ? ( - {children} - ) : ( - children - )} -
-
- ); + return ( +
+
+ {`${label}:`} +
+
+ {typeof children === "string" ? {children} : children} +
+
+ ); } diff --git a/client/src/components/dms-allocations-summary-ap/dms-allocations-summary-ap.component.jsx b/client/src/components/dms-allocations-summary-ap/dms-allocations-summary-ap.component.jsx index 369dc18f4..b4fc66577 100644 --- a/client/src/components/dms-allocations-summary-ap/dms-allocations-summary-ap.component.jsx +++ b/client/src/components/dms-allocations-summary-ap/dms-allocations-summary-ap.component.jsx @@ -1,153 +1,142 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Form, Input, Table} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {pageLimit} from "../../utils/config"; +import { SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Form, Input, Table } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { pageLimit } from "../../utils/config"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(DmsAllocationsSummaryAp); +export default connect(mapStateToProps, mapDispatchToProps)(DmsAllocationsSummaryAp); -export function DmsAllocationsSummaryAp({socket, bodyshop, billids, title}) { - const {t} = useTranslation(); - const [allocationsSummary, setAllocationsSummary] = useState([]); +export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) { + const { t } = useTranslation(); + const [allocationsSummary, setAllocationsSummary] = useState([]); - useEffect(() => { - socket.on("ap-export-success", (billid) => { - setAllocationsSummary((allocationsSummary) => - allocationsSummary.map((a) => { - if (a.billid !== billid) return a; - return {...a, status: "Successful"}; - }) - ); - }); - socket.on("ap-export-failure", ({billid, error}) => { - allocationsSummary.map((a) => { - if (a.billid !== billid) return a; - return {...a, status: error}; - }); - }); + useEffect(() => { + socket.on("ap-export-success", (billid) => { + setAllocationsSummary((allocationsSummary) => + allocationsSummary.map((a) => { + if (a.billid !== billid) return a; + return { ...a, status: "Successful" }; + }) + ); + }); + socket.on("ap-export-failure", ({ billid, error }) => { + allocationsSummary.map((a) => { + if (a.billid !== billid) return a; + return { ...a, status: error }; + }); + }); - if (socket.disconnected) socket.connect(); - return () => { - socket.removeListener("ap-export-success"); - socket.removeListener("ap-export-failure"); - //socket.disconnect(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (socket.connected) { - socket.emit("pbs-calculate-allocations-ap", billids, (ack) => { - setAllocationsSummary(ack); - - socket.allocationsSummary = ack; - }); - } - }, [socket, socket.connected, billids]); - console.log(allocationsSummary); - const columns = [ - { - title: t("general.labels.status"), - dataIndex: "status", - key: "status", - }, - { - title: t("bills.fields.invoice_number"), - dataIndex: ["Posting", "Reference"], - key: "reference", - }, - - { - title: t("jobs.fields.dms.lines"), - dataIndex: "Lines", - key: "Lines", - render: (text, record) => ( -
- - - - - - {record.Posting.Lines.map((l, idx) => ( - - - - - - ))} -
{t("bills.fields.invoice_number")}{t("bodyshop.fields.dms.dms_acctnumber")}{t("jobs.fields.dms.amount")}
{l.InvoiceNumber}{l.Account}{l.Amount}
- ), - }, - ]; - - const handleFinish = async (values) => { - socket.emit(`pbs-export-ap`, { - billids, - txEnvelope: values, - }); + if (socket.disconnected) socket.connect(); + return () => { + socket.removeListener("ap-export-success"); + socket.removeListener("ap-export-failure"); + //socket.disconnect(); }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - return ( - { - socket.emit("pbs-calculate-allocations-ap", billids, (ack) => - setAllocationsSummary(ack) - ); - }} - > - - - } + useEffect(() => { + if (socket.connected) { + socket.emit("pbs-calculate-allocations-ap", billids, (ack) => { + setAllocationsSummary(ack); + + socket.allocationsSummary = ack; + }); + } + }, [socket, socket.connected, billids]); + console.log(allocationsSummary); + const columns = [ + { + title: t("general.labels.status"), + dataIndex: "status", + key: "status" + }, + { + title: t("bills.fields.invoice_number"), + dataIndex: ["Posting", "Reference"], + key: "reference" + }, + + { + title: t("jobs.fields.dms.lines"), + dataIndex: "Lines", + key: "Lines", + render: (text, record) => ( + + + + + + + {record.Posting.Lines.map((l, idx) => ( + + + + + + ))} +
{t("bills.fields.invoice_number")}{t("bodyshop.fields.dms.dms_acctnumber")}{t("jobs.fields.dms.amount")}
{l.InvoiceNumber}{l.Account}{l.Amount}
+ ) + } + ]; + + const handleFinish = async (values) => { + socket.emit(`pbs-export-ap`, { + billids, + txEnvelope: values + }); + }; + + return ( + { + socket.emit("pbs-calculate-allocations-ap", billids, (ack) => setAllocationsSummary(ack)); + }} > - `${record.InvoiceNumber}${record.Account}`} - dataSource={allocationsSummary} - locale={{emptyText: t("dms.labels.refreshallocations")}} - /> - - - - - - - - ); + + + } + > +
`${record.InvoiceNumber}${record.Account}`} + dataSource={allocationsSummary} + locale={{ emptyText: t("dms.labels.refreshallocations") }} + /> + + + + + + + + ); } diff --git a/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx b/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx index 68c65aacf..3551cc6e3 100644 --- a/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx +++ b/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx @@ -1,142 +1,130 @@ -import {Alert, Button, Card, Table, Typography} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { Alert, Button, Card, Table, Typography } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import Dinero from "dinero.js"; -import {SyncOutlined} from "@ant-design/icons"; -import {pageLimit} from "../../utils/config"; +import { SyncOutlined } from "@ant-design/icons"; +import { pageLimit } from "../../utils/config"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(DmsAllocationsSummary); +export default connect(mapStateToProps, mapDispatchToProps)(DmsAllocationsSummary); -export function DmsAllocationsSummary({socket, bodyshop, jobId, title}) { - const {t} = useTranslation(); - const [allocationsSummary, setAllocationsSummary] = useState([]); +export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) { + const { t } = useTranslation(); + const [allocationsSummary, setAllocationsSummary] = useState([]); - useEffect(() => { - if (socket.connected) { - socket.emit("cdk-calculate-allocations", jobId, (ack) => { - setAllocationsSummary(ack); - socket.allocationsSummary = ack; - }); - } - }, [socket, socket.connected, jobId]); + useEffect(() => { + if (socket.connected) { + socket.emit("cdk-calculate-allocations", jobId, (ack) => { + setAllocationsSummary(ack); + socket.allocationsSummary = ack; + }); + } + }, [socket, socket.connected, jobId]); - const columns = [ - { - title: t("jobs.fields.dms.center"), - dataIndex: "center", - key: "center", - }, - { - title: t("jobs.fields.dms.sale"), - dataIndex: "sale", - key: "sale", - render: (text, record) => Dinero(record.sale).toFormat(), - }, - { - title: t("jobs.fields.dms.cost"), - dataIndex: "cost", - key: "cost", - render: (text, record) => Dinero(record.cost).toFormat(), - }, - { - title: t("jobs.fields.dms.sale_dms_acctnumber"), - dataIndex: "sale_dms_acctnumber", - key: "sale_dms_acctnumber", - render: (text, record) => - record.profitCenter && record.profitCenter.dms_acctnumber, - }, - { - title: t("jobs.fields.dms.cost_dms_acctnumber"), - dataIndex: "cost_dms_acctnumber", - key: "cost_dms_acctnumber", - render: (text, record) => - record.costCenter && record.costCenter.dms_acctnumber, - }, - { - title: t("jobs.fields.dms.dms_wip_acctnumber"), - dataIndex: "dms_wip_acctnumber", - key: "dms_wip_acctnumber", - render: (text, record) => - record.costCenter && record.costCenter.dms_wip_acctnumber, - }, - ]; + const columns = [ + { + title: t("jobs.fields.dms.center"), + dataIndex: "center", + key: "center" + }, + { + title: t("jobs.fields.dms.sale"), + dataIndex: "sale", + key: "sale", + render: (text, record) => Dinero(record.sale).toFormat() + }, + { + title: t("jobs.fields.dms.cost"), + dataIndex: "cost", + key: "cost", + render: (text, record) => Dinero(record.cost).toFormat() + }, + { + title: t("jobs.fields.dms.sale_dms_acctnumber"), + dataIndex: "sale_dms_acctnumber", + key: "sale_dms_acctnumber", + render: (text, record) => record.profitCenter && record.profitCenter.dms_acctnumber + }, + { + title: t("jobs.fields.dms.cost_dms_acctnumber"), + dataIndex: "cost_dms_acctnumber", + key: "cost_dms_acctnumber", + render: (text, record) => record.costCenter && record.costCenter.dms_acctnumber + }, + { + title: t("jobs.fields.dms.dms_wip_acctnumber"), + dataIndex: "dms_wip_acctnumber", + key: "dms_wip_acctnumber", + render: (text, record) => record.costCenter && record.costCenter.dms_wip_acctnumber + } + ]; - return ( - { - socket.emit("cdk-calculate-allocations", jobId, (ack) => - setAllocationsSummary(ack) - ); - }} - > - - - } + return ( + { + socket.emit("cdk-calculate-allocations", jobId, (ack) => setAllocationsSummary(ack)); + }} > - {bodyshop.pbs_configuration?.disablebillwip && ( - - )} -
{ - const totals = - allocationsSummary && - allocationsSummary.reduce( - (acc, val) => { - return { - totalSale: acc.totalSale.add(Dinero(val.sale)), - totalCost: acc.totalCost.add(Dinero(val.cost)), - }; - }, - { - totalSale: Dinero(), - totalCost: Dinero(), - } - ); + + + } + > + {bodyshop.pbs_configuration?.disablebillwip && ( + + )} +
{ + const totals = + allocationsSummary && + allocationsSummary.reduce( + (acc, val) => { + return { + totalSale: acc.totalSale.add(Dinero(val.sale)), + totalCost: acc.totalCost.add(Dinero(val.cost)) + }; + }, + { + totalSale: Dinero(), + totalCost: Dinero() + } + ); - return ( - - - - {t("general.labels.totals")} - - - - {totals && totals.totalSale.toFormat()} - - - { - // totals.totalCost.toFormat() - } - - - - - ); - }} - /> - - ); + return ( + + + {t("general.labels.totals")} + + {totals && totals.totalSale.toFormat()} + + { + // totals.totalCost.toFormat() + } + + + + + ); + }} + /> + + ); } diff --git a/client/src/components/dms-cdk-makes/dms-cdk-makes.component.jsx b/client/src/components/dms-cdk-makes/dms-cdk-makes.component.jsx index b0df8abb5..27a63c850 100644 --- a/client/src/components/dms-cdk-makes/dms-cdk-makes.component.jsx +++ b/client/src/components/dms-cdk-makes/dms-cdk-makes.component.jsx @@ -1,105 +1,104 @@ -import {useLazyQuery} from "@apollo/client"; -import {Button, Input, Modal, Table} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {SEARCH_DMS_VEHICLES} from "../../graphql/dms.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useLazyQuery } from "@apollo/client"; +import { Button, Input, Modal, Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { SEARCH_DMS_VEHICLES } from "../../graphql/dms.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(DmsCdkVehicles); -export function DmsCdkVehicles({bodyshop, form, socket, job}) { - const [open, setOpen] = useState(false); - const [selectedModel, setSelectedModel] = useState(null); - const {t} = useTranslation(); +export function DmsCdkVehicles({ bodyshop, form, socket, job }) { + const [open, setOpen] = useState(false); + const [selectedModel, setSelectedModel] = useState(null); + const { t } = useTranslation(); - const [callSearch, {loading, error, data}] = - useLazyQuery(SEARCH_DMS_VEHICLES); - const columns = [ - { - title: t("vehicles.fields.v_make_desc"), - dataIndex: "make", - key: "make", - }, - { - title: t("vehicles.fields.v_model_desc"), - dataIndex: "model", - key: "model", - }, - { - title: t("jobs.fields.dms.dms_make"), - dataIndex: "makecode", - key: "makecode", - }, - { - title: t("jobs.fields.dms.dms_model"), - dataIndex: "modelcode", - key: "modelcode", - }, - ]; + const [callSearch, { loading, error, data }] = useLazyQuery(SEARCH_DMS_VEHICLES); + const columns = [ + { + title: t("vehicles.fields.v_make_desc"), + dataIndex: "make", + key: "make" + }, + { + title: t("vehicles.fields.v_model_desc"), + dataIndex: "model", + key: "model" + }, + { + title: t("jobs.fields.dms.dms_make"), + dataIndex: "makecode", + key: "makecode" + }, + { + title: t("jobs.fields.dms.dms_model"), + dataIndex: "modelcode", + key: "modelcode" + } + ]; - return ( - <> - setOpen(false)} - onOk={() => { - form.setFieldsValue({ - dms_make: selectedModel.makecode, - dms_model: selectedModel.modelcode, - }); - setOpen(false); - }} - > - {error && } -
( - callSearch({variables: {search: val}})} - placeholder={t("general.labels.search")} - /> - )} - columns={columns} - loading={loading} - rowKey="id" - dataSource={data ? data.search_dms_vehicles : []} - onRow={(record) => { - return { - onClick: () => setSelectedModel(record), - }; - }} - rowSelection={{ - onSelect: (record) => { - setSelectedModel(record); - }, + return ( + <> + setOpen(false)} + onOk={() => { + form.setFieldsValue({ + dms_make: selectedModel.makecode, + dms_model: selectedModel.modelcode + }); + setOpen(false); + }} + > + {error && } +
( + callSearch({ variables: { search: val } })} + placeholder={t("general.labels.search")} + /> + )} + columns={columns} + loading={loading} + rowKey="id" + dataSource={data ? data.search_dms_vehicles : []} + onRow={(record) => { + return { + onClick: () => setSelectedModel(record) + }; + }} + rowSelection={{ + onSelect: (record) => { + setSelectedModel(record); + }, - type: "radio", - selectedRowKeys: [selectedModel && selectedModel.id], - }} - /> - - - - ); + type: "radio", + selectedRowKeys: [selectedModel && selectedModel.id] + }} + /> + + + + ); } diff --git a/client/src/components/dms-cdk-makes/dms-cdk-makes.refetch.component.jsx b/client/src/components/dms-cdk-makes/dms-cdk-makes.refetch.component.jsx index d11aab598..ad505f9c3 100644 --- a/client/src/components/dms-cdk-makes/dms-cdk-makes.refetch.component.jsx +++ b/client/src/components/dms-cdk-makes/dms-cdk-makes.refetch.component.jsx @@ -1,37 +1,37 @@ -import {Button} from "antd"; +import { Button } from "antd"; import axios from "axios"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - bodyshop: selectBodyshop, + currentUser: selectCurrentUser, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(DmsCdkMakesRefetch); -export function DmsCdkMakesRefetch({currentUser, bodyshop, form, socket}) { - const [loading, setLoading] = useState(false); - const {t} = useTranslation(); +export function DmsCdkMakesRefetch({ currentUser, bodyshop, form, socket }) { + const [loading, setLoading] = useState(false); + const { t } = useTranslation(); - if (!currentUser.email.includes("@imex.")) return null; + if (!currentUser.email.includes("@imex.")) return null; - const handleRefetch = async () => { - setLoading(true); - await axios.post("/cdk/getvehicles", { - cdk_dealerid: bodyshop.cdk_dealerid, - bodyshopid: bodyshop.id, - }); + const handleRefetch = async () => { + setLoading(true); + await axios.post("/cdk/getvehicles", { + cdk_dealerid: bodyshop.cdk_dealerid, + bodyshopid: bodyshop.id + }); - setLoading(false); - }; - return ( - - ); + setLoading(false); + }; + return ( + + ); } diff --git a/client/src/components/dms-customer-selector/dms-customer-selector.component.jsx b/client/src/components/dms-customer-selector/dms-customer-selector.component.jsx index 88df58895..ec82d36d3 100644 --- a/client/src/components/dms-customer-selector/dms-customer-selector.component.jsx +++ b/client/src/components/dms-customer-selector/dms-customer-selector.component.jsx @@ -1,161 +1,139 @@ -import {Button, Checkbox, Col, Table} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {socket} from "../../pages/dms/dms.container"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {alphaSort} from "../../utils/sorters"; +import { Button, Checkbox, Col, Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { socket } from "../../pages/dms/dms.container"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { alphaSort } from "../../utils/sorters"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(DmsCustomerSelector); +export default connect(mapStateToProps, mapDispatchToProps)(DmsCustomerSelector); -export function DmsCustomerSelector({bodyshop}) { - const {t} = useTranslation(); - const [customerList, setcustomerList] = useState([]); - const [open, setOpen] = useState(false); - const [selectedCustomer, setSelectedCustomer] = useState(null); - const [dmsType, setDmsType] = useState("cdk"); +export function DmsCustomerSelector({ bodyshop }) { + const { t } = useTranslation(); + const [customerList, setcustomerList] = useState([]); + const [open, setOpen] = useState(false); + const [selectedCustomer, setSelectedCustomer] = useState(null); + const [dmsType, setDmsType] = useState("cdk"); - socket.on("cdk-select-customer", (customerList, callback) => { - setOpen(true); - setDmsType("cdk"); - setcustomerList(customerList); - }); - socket.on("pbs-select-customer", (customerList, callback) => { - setOpen(true); - setDmsType("pbs"); - setcustomerList(customerList); - }); + socket.on("cdk-select-customer", (customerList, callback) => { + setOpen(true); + setDmsType("cdk"); + setcustomerList(customerList); + }); + socket.on("pbs-select-customer", (customerList, callback) => { + setOpen(true); + setDmsType("pbs"); + setcustomerList(customerList); + }); - const onUseSelected = () => { - setOpen(false); - socket.emit(`${dmsType}-selected-customer`, selectedCustomer); - setSelectedCustomer(null); - }; + const onUseSelected = () => { + setOpen(false); + socket.emit(`${dmsType}-selected-customer`, selectedCustomer); + setSelectedCustomer(null); + }; - const onUseGeneric = () => { - setOpen(false); - socket.emit( - `${dmsType}-selected-customer`, - bodyshop.cdk_configuration.generic_customer_number - ); - setSelectedCustomer(null); - }; + const onUseGeneric = () => { + setOpen(false); + socket.emit(`${dmsType}-selected-customer`, bodyshop.cdk_configuration.generic_customer_number); + setSelectedCustomer(null); + }; - const onCreateNew = () => { - setOpen(false); - socket.emit(`${dmsType}-selected-customer`, null); - setSelectedCustomer(null); - }; + const onCreateNew = () => { + setOpen(false); + socket.emit(`${dmsType}-selected-customer`, null); + setSelectedCustomer(null); + }; - const cdkColumns = [ - { - title: t("jobs.fields.dms.id"), - dataIndex: ["id", "value"], - key: "id", - }, - { - title: t("jobs.fields.dms.vinowner"), - dataIndex: "vinOwner", - key: "vinOwner", - render: (text, record) => , - }, - { - title: t("jobs.fields.dms.name1"), - dataIndex: ["name1", "fullName"], - key: "name1", - sorter: (a, b) => - alphaSort(a.name1 && a.name1.fullName, b.name1 && b.name1.fullName), - }, + const cdkColumns = [ + { + title: t("jobs.fields.dms.id"), + dataIndex: ["id", "value"], + key: "id" + }, + { + title: t("jobs.fields.dms.vinowner"), + dataIndex: "vinOwner", + key: "vinOwner", + render: (text, record) => + }, + { + title: t("jobs.fields.dms.name1"), + dataIndex: ["name1", "fullName"], + key: "name1", + sorter: (a, b) => alphaSort(a.name1 && a.name1.fullName, b.name1 && b.name1.fullName) + }, - { - title: t("jobs.fields.dms.address"), - //dataIndex: ["name2", "fullName"], - key: "address", - render: (record, value) => - `${ - record.address && - record.address.addressLine && - record.address.addressLine[0] - }, ${record.address && record.address.city} ${ - record.address && record.address.stateOrProvince - } ${record.address && record.address.postalCode}`, - }, - ]; + { + title: t("jobs.fields.dms.address"), + //dataIndex: ["name2", "fullName"], + key: "address", + render: (record, value) => + `${ + record.address && record.address.addressLine && record.address.addressLine[0] + }, ${record.address && record.address.city} ${ + record.address && record.address.stateOrProvince + } ${record.address && record.address.postalCode}` + } + ]; - const pbsColumns = [ - { - title: t("jobs.fields.dms.id"), - dataIndex: "ContactId", - key: "ContactId", - }, - { - title: t("jobs.fields.dms.name1"), - key: "name1", - sorter: (a, b) => alphaSort(a.LastName, b.LastName), - render: (text, record) => - `${record.FirstName || ""} ${record.LastName || ""}`, - }, + const pbsColumns = [ + { + title: t("jobs.fields.dms.id"), + dataIndex: "ContactId", + key: "ContactId" + }, + { + title: t("jobs.fields.dms.name1"), + key: "name1", + sorter: (a, b) => alphaSort(a.LastName, b.LastName), + render: (text, record) => `${record.FirstName || ""} ${record.LastName || ""}` + }, - { - title: t("jobs.fields.dms.address"), - key: "address", - render: (record, value) => - `${record.Address}, ${record.City} ${record.State} ${record.ZipCode}`, - }, - ]; + { + title: t("jobs.fields.dms.address"), + key: "address", + render: (record, value) => `${record.Address}, ${record.City} ${record.State} ${record.ZipCode}` + } + ]; - if (!open) return null; - return ( - -
( -
- - - -
- )} - pagination={{position: "top"}} - columns={dmsType === "cdk" ? cdkColumns : pbsColumns} - rowKey={(record) => - dmsType === "cdk" ? record.id.value : record.ContactId - } - dataSource={customerList} - //onChange={handleTableChange} - rowSelection={{ - onSelect: (record) => { - setSelectedCustomer( - dmsType === "cdk" ? record.id.value : record.ContactId - ); - }, - type: "radio", - selectedRowKeys: [selectedCustomer], - }} - /> - - ); + if (!open) return null; + return ( + +
( +
+ + + +
+ )} + pagination={{ position: "top" }} + columns={dmsType === "cdk" ? cdkColumns : pbsColumns} + rowKey={(record) => (dmsType === "cdk" ? record.id.value : record.ContactId)} + dataSource={customerList} + //onChange={handleTableChange} + rowSelection={{ + onSelect: (record) => { + setSelectedCustomer(dmsType === "cdk" ? record.id.value : record.ContactId); + }, + type: "radio", + selectedRowKeys: [selectedCustomer] + }} + /> + + ); } diff --git a/client/src/components/dms-log-events/dms-log-events.component.jsx b/client/src/components/dms-log-events/dms-log-events.component.jsx index b372df6e2..037664900 100644 --- a/client/src/components/dms-log-events/dms-log-events.component.jsx +++ b/client/src/components/dms-log-events/dms-log-events.component.jsx @@ -1,56 +1,56 @@ -import {Divider, Space, Tag, Timeline} from "antd"; +import { Divider, Space, Tag, Timeline } from "antd"; import dayjs from "../../utils/day"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); export default connect(mapStateToProps, mapDispatchToProps)(DmsLogEvents); -export function DmsLogEvents({socket, logs, bodyshop}) { - return ( - ({ - key: idx, - color: LogLevelHierarchy(log.level), - children: ( - - {log.level} - {dayjs(log.timestamp).format("MM/DD/YYYY HH:mm:ss")} - - {log.message} - - ), - }))} - /> - ); +export function DmsLogEvents({ socket, logs, bodyshop }) { + return ( + ({ + key: idx, + color: LogLevelHierarchy(log.level), + children: ( + + {log.level} + {dayjs(log.timestamp).format("MM/DD/YYYY HH:mm:ss")} + + {log.message} + + ) + }))} + /> + ); } function LogLevelHierarchy(level) { - switch (level) { - case "TRACE": - return "pink"; - case "DEBUG": - return "orange"; - case "INFO": - return "blue"; - case "WARNING": - return "yellow"; - case "ERROR": - return "red"; - default: - return 0; - } + switch (level) { + case "TRACE": + return "pink"; + case "DEBUG": + return "orange"; + case "INFO": + return "blue"; + case "WARNING": + return "yellow"; + case "ERROR": + return "red"; + default: + return 0; + } } diff --git a/client/src/components/dms-post-form/dms-post-form.component.jsx b/client/src/components/dms-post-form/dms-post-form.component.jsx index 654a7ec79..89738cc95 100644 --- a/client/src/components/dms-post-form/dms-post-form.component.jsx +++ b/client/src/components/dms-post-form/dms-post-form.component.jsx @@ -1,26 +1,26 @@ -import {DeleteFilled, DownOutlined} from "@ant-design/icons"; +import { DeleteFilled, DownOutlined } from "@ant-design/icons"; import { - Button, - Card, - Divider, - Dropdown, - Form, - Input, - InputNumber, - Select, - Space, - Statistic, - Switch, - Typography, + Button, + Card, + Divider, + Dropdown, + Form, + Input, + InputNumber, + Select, + Space, + Statistic, + Switch, + Typography } from "antd"; import Dinero from "dinero.js"; import dayjs from "../../utils/day"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {determineDmsType} from "../../pages/dms/dms.container"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { determineDmsType } from "../../pages/dms/dms.container"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import i18n from "../../translations/i18n"; import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component"; import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component"; @@ -29,425 +29,367 @@ import CurrencyInput from "../form-items-formatted/currency-form-item.component" import LayoutFormRow from "../layout-form-row/layout-form-row.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(DmsPostForm); -export function DmsPostForm({bodyshop, socket, job, logsRef}) { - const [form] = Form.useForm(); - const {t} = useTranslation(); +export function DmsPostForm({ bodyshop, socket, job, logsRef }) { + const [form] = Form.useForm(); + const { t } = useTranslation(); - const handlePayerSelect = (value, index) => { - form.setFieldsValue({ - payers: form.getFieldValue("payers").map((payer, mapIndex) => { - if (index !== mapIndex) return payer; - const cdkPayer = - bodyshop.cdk_configuration.payers && - bodyshop.cdk_configuration.payers.find((i) => i.name === value); + const handlePayerSelect = (value, index) => { + form.setFieldsValue({ + payers: form.getFieldValue("payers").map((payer, mapIndex) => { + if (index !== mapIndex) return payer; + const cdkPayer = + bodyshop.cdk_configuration.payers && bodyshop.cdk_configuration.payers.find((i) => i.name === value); - if (!cdkPayer) return payer; + if (!cdkPayer) return payer; - return { - ...cdkPayer, - dms_acctnumber: cdkPayer.dms_acctnumber, - controlnumber: job && job[cdkPayer.control_type], - }; - }), + return { + ...cdkPayer, + dms_acctnumber: cdkPayer.dms_acctnumber, + controlnumber: job && job[cdkPayer.control_type] + }; + }) + }); + }; + + const handleFinish = (values) => { + socket.emit(`${determineDmsType(bodyshop)}-export-job`, { + jobid: job.id, + txEnvelope: values + }); + console.log(logsRef); + if (logsRef) { + console.log("executing", logsRef); + logsRef.curent && + logsRef.current.scrollIntoView({ + behavior: "smooth" }); - }; + } + }; - const handleFinish = (values) => { - socket.emit(`${determineDmsType(bodyshop)}-export-job`, { - jobid: job.id, - txEnvelope: values, - }); - console.log(logsRef); - if (logsRef) { - console.log("executing", logsRef); - logsRef.curent && - logsRef.current.scrollIntoView({ - behavior: "smooth", - }); - } - }; + return ( + +
+ + + + + + + + + + + - return ( - - - - + + + + + + + + + + + + + + + + + + + + + + + )} + + + + + + + + + + + + {(fields, { add, remove }) => { + return ( +
+ {fields.map((field, index) => ( + + + + + + + + + + + + + + + + {t("jobs.fields.dms.payer.controlnumber")}{" "} + ({ + key: idx, + label: key.name, + onClick: () => { + form.setFieldsValue({ + payers: form.getFieldValue("payers").map((row, mapIndex) => { + if (index !== mapIndex) return row; + return { + ...row, + controlnumber: key.controlnumber + }; + }) + }); + } + })) + }} + > + e.preventDefault()}> + + + +
} + key={`${index}controlnumber`} + name={[field.name, "controlnumber"]} rules={[ - { - required: true, - //message: t("general.validation.required"), - }, + { + required: true + } ]} - > - -
- - - - - - -
+ > + + - {bodyshop.cdk_dealerid && ( -
- - - - - - - - - - - - - - - - - - - - - -
- )} - - - + + {() => { + const payers = form.getFieldValue("payers"); - - - - - - - - {(fields, {add, remove}) => { - return ( -
- {fields.map((field, index) => ( - - - - - + const row = payers && payers[index]; - - - + const cdkPayer = + bodyshop.cdk_configuration.payers && + bodyshop.cdk_configuration.payers.find((i) => i && row && i.name === row.name); + if (i18n.exists(`jobs.fields.${cdkPayer?.control_type}`)) + return
{cdkPayer && t(`jobs.fields.${cdkPayer?.control_type}`)}
; + else if (i18n.exists(`jobs.fields.dms.control_type.${cdkPayer?.control_type}`)) { + return
{cdkPayer && t(`jobs.fields.dms.control_type.${cdkPayer?.control_type}`)}
; + } else { + return null; + } + }} +
- - - - - - {t("jobs.fields.dms.payer.controlnumber")}{" "} - ({ - key: idx, - label: key.name, - onClick: () => { - form.setFieldsValue({ - payers: form.getFieldValue("payers").map((row, mapIndex) => { - if (index !== mapIndex) return row; - return { - ...row, - controlnumber: key.controlnumber, - }; - }), - }); - }, - })) - }}> - e.preventDefault()}> - - - -
- } - key={`${index}controlnumber`} - name={[field.name, "controlnumber"]} - rules={[ - { - required: true, - }, - ]} - > - -
- - - {() => { - const payers = form.getFieldValue("payers"); - - const row = payers && payers[index]; - - const cdkPayer = - bodyshop.cdk_configuration.payers && - bodyshop.cdk_configuration.payers.find( - (i) => i && row && i.name === row.name - ); - if ( - i18n.exists(`jobs.fields.${cdkPayer?.control_type}`) - ) - return ( -
- {cdkPayer && - t(`jobs.fields.${cdkPayer?.control_type}`)} -
- ); - else if ( - i18n.exists( - `jobs.fields.dms.control_type.${cdkPayer?.control_type}` - ) - ) { - return ( -
- {cdkPayer && - t( - `jobs.fields.dms.control_type.${cdkPayer?.control_type}` - )} -
- ); - } else { - return null; - } - }} -
- - { - remove(field.name); - }} - /> - - - ))} - - - - - ); - }} - - - {() => { - //Perform Calculation to determine discrepancy. - let totalAllocated = Dinero(); - - const payers = form.getFieldValue("payers"); - payers && - payers.forEach((payer) => { - totalAllocated = totalAllocated.add( - Dinero({amount: Math.round((payer?.amount || 0) * 100)}) - ); - }); - - const totals = - socket.allocationsSummary && - socket.allocationsSummary.reduce( - (acc, val) => { - return { - totalSale: acc.totalSale.add(Dinero(val.sale)), - totalCost: acc.totalCost.add(Dinero(val.cost)), - }; - }, - { - totalSale: Dinero(), - totalCost: Dinero(), - } - ); - const discrep = totals - ? totals.totalSale.subtract(totalAllocated) - : Dinero(); - return ( - - - - - - = - - - - ); + { + remove(field.name); + }} + /> + + + ))} + + - -
- ); + + ); + }} + + + {() => { + //Perform Calculation to determine discrepancy. + let totalAllocated = Dinero(); + + const payers = form.getFieldValue("payers"); + payers && + payers.forEach((payer) => { + totalAllocated = totalAllocated.add(Dinero({ amount: Math.round((payer?.amount || 0) * 100) })); + }); + + const totals = + socket.allocationsSummary && + socket.allocationsSummary.reduce( + (acc, val) => { + return { + totalSale: acc.totalSale.add(Dinero(val.sale)), + totalCost: acc.totalCost.add(Dinero(val.cost)) + }; + }, + { + totalSale: Dinero(), + totalCost: Dinero() + } + ); + const discrep = totals ? totals.totalSale.subtract(totalAllocated) : Dinero(); + return ( + + + - + + = + + + + ); + }} + + +
+ ); } diff --git a/client/src/components/document-editor/document-editor.component.jsx b/client/src/components/document-editor/document-editor.component.jsx index d1d1df491..70c7293f3 100644 --- a/client/src/components/document-editor/document-editor.component.jsx +++ b/client/src/components/document-editor/document-editor.component.jsx @@ -1,110 +1,101 @@ //import "tui-image-editor/dist/tui-image-editor.css"; -import {Result} from "antd"; +import { Result } from "antd"; import * as markerjs2 from "markerjs2"; -import React, {useCallback, useEffect, useRef, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; -import {handleUpload} from "../documents-upload/documents-upload.utility"; -import {GenerateSrcUrl} from "../jobs-documents-gallery/job-documents.utility"; +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import { handleUpload } from "../documents-upload/documents-upload.utility"; +import { GenerateSrcUrl } from "../jobs-documents-gallery/job-documents.utility"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - bodyshop: selectBodyshop, + currentUser: selectCurrentUser, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function DocumentEditorComponent({currentUser, bodyshop, document}) { - const imgRef = useRef(null); - const [loading, setLoading] = useState(false); - const [uploaded, setuploaded] = useState(false); - const markerArea = useRef(null); - const {t} = useTranslation(); +export function DocumentEditorComponent({ currentUser, bodyshop, document }) { + const imgRef = useRef(null); + const [loading, setLoading] = useState(false); + const [uploaded, setuploaded] = useState(false); + const markerArea = useRef(null); + const { t } = useTranslation(); - const triggerUpload = useCallback( - async (dataUrl) => { - setLoading(true); - handleUpload( - { - filename: `${document.key.split("/").pop()}-${Date.now()}.jpg`, - file: await b64toBlob(dataUrl), - onSuccess: () => { - setLoading(false); - setuploaded(true); - }, - onError: () => setLoading(false), - }, - { - bodyshop: bodyshop, - uploaded_by: currentUser.email, - jobId: document.jobid, - //billId: billId, - tagsArray: ["edited"], - //callback: callbackAfterUpload, - } - ); + const triggerUpload = useCallback( + async (dataUrl) => { + setLoading(true); + handleUpload( + { + filename: `${document.key.split("/").pop()}-${Date.now()}.jpg`, + file: await b64toBlob(dataUrl), + onSuccess: () => { + setLoading(false); + setuploaded(true); + }, + onError: () => setLoading(false) }, - [bodyshop, currentUser, document] - ); - - useEffect(() => { - if (imgRef.current !== null) { - // create a marker.js MarkerArea - markerArea.current = new markerjs2.MarkerArea(imgRef.current); - - // attach an event handler to assign annotated image back to our image element - markerArea.current.addEventListener("close", (closeEvent) => { - }); - - markerArea.current.addEventListener("render", (event) => { - const dataUrl = event.dataUrl; - imgRef.current.src = dataUrl; - markerArea.current.close(); - triggerUpload(dataUrl); - }); - // launch marker.js - - markerArea.current.renderAtNaturalSize = true; - markerArea.current.renderImageType = "image/jpeg"; - markerArea.current.renderImageQuality = 1; - //markerArea.current.settings.displayMode = "inline"; - markerArea.current.show(); + { + bodyshop: bodyshop, + uploaded_by: currentUser.email, + jobId: document.jobid, + //billId: billId, + tagsArray: ["edited"] + //callback: callbackAfterUpload, } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [triggerUpload]); + ); + }, + [bodyshop, currentUser, document] + ); - async function b64toBlob(url) { - const res = await fetch(url); - return await res.blob(); + useEffect(() => { + if (imgRef.current !== null) { + // create a marker.js MarkerArea + markerArea.current = new markerjs2.MarkerArea(imgRef.current); + + // attach an event handler to assign annotated image back to our image element + markerArea.current.addEventListener("close", (closeEvent) => {}); + + markerArea.current.addEventListener("render", (event) => { + const dataUrl = event.dataUrl; + imgRef.current.src = dataUrl; + markerArea.current.close(); + triggerUpload(dataUrl); + }); + // launch marker.js + + markerArea.current.renderAtNaturalSize = true; + markerArea.current.renderImageType = "image/jpeg"; + markerArea.current.renderImageQuality = 1; + //markerArea.current.settings.displayMode = "inline"; + markerArea.current.show(); } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [triggerUpload]); - return ( -
- {!loading && !uploaded && ( - sample - )} - {loading && } - {uploaded && ( - - )} -
- ); + async function b64toBlob(url) { + const res = await fetch(url); + return await res.blob(); + } + + return ( +
+ {!loading && !uploaded && ( + sample + )} + {loading && } + {uploaded && } +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(DocumentEditorComponent); +export default connect(mapStateToProps, mapDispatchToProps)(DocumentEditorComponent); diff --git a/client/src/components/document-editor/document-editor.container.jsx b/client/src/components/document-editor/document-editor.container.jsx index fa79bc6c3..b5868e076 100644 --- a/client/src/components/document-editor/document-editor.container.jsx +++ b/client/src/components/document-editor/document-editor.container.jsx @@ -1,62 +1,55 @@ -import {useQuery} from "@apollo/client"; -import {Result} from "antd"; +import { useQuery } from "@apollo/client"; +import { Result } from "antd"; import queryString from "query-string"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation} from "react-router-dom"; -import {QUERY_BODYSHOP} from "../../graphql/bodyshop.queries"; -import {GET_DOCUMENT_BY_PK} from "../../graphql/documents.queries"; -import {setBodyshop} from "../../redux/user/user.actions"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation } from "react-router-dom"; +import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries"; +import { GET_DOCUMENT_BY_PK } from "../../graphql/documents.queries"; +import { setBodyshop } from "../../redux/user/user.actions"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import DocumentEditor from "./document-editor.component"; const mapDispatchToProps = (dispatch) => ({ - setBodyshop: (bs) => dispatch(setBodyshop(bs)), + setBodyshop: (bs) => dispatch(setBodyshop(bs)) }); export default connect(null, mapDispatchToProps)(DocumentEditorContainer); -export function DocumentEditorContainer({setBodyshop}) { - //Get the image details for the image to be saved. - //Get the document id from the search string. - const {documentId} = queryString.parse(useLocation().search); - const {t} = useTranslation(); - const { - loading: loadingShop, - error: errorShop, - data: dataShop, - } = useQuery(QUERY_BODYSHOP, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); +export function DocumentEditorContainer({ setBodyshop }) { + //Get the image details for the image to be saved. + //Get the document id from the search string. + const { documentId } = queryString.parse(useLocation().search); + const { t } = useTranslation(); + const { + loading: loadingShop, + error: errorShop, + data: dataShop + } = useQuery(QUERY_BODYSHOP, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - useEffect(() => { - if (dataShop) setBodyshop(dataShop.bodyshops[0]); - }, [dataShop, setBodyshop]); + useEffect(() => { + if (dataShop) setBodyshop(dataShop.bodyshops[0]); + }, [dataShop, setBodyshop]); - const {loading, error, data} = useQuery(GET_DOCUMENT_BY_PK, { - variables: {documentId}, - skip: !documentId, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const { loading, error, data } = useQuery(GET_DOCUMENT_BY_PK, { + variables: { documentId }, + skip: !documentId, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - if (loading || loadingShop) return ; - if (error || errorShop) - return ( - - ); + if (loading || loadingShop) return ; + if (error || errorShop) return ; - if (!data || !data.documents_by_pk) - return ; - return ( -
- -
- ); + if (!data || !data.documents_by_pk) return ; + return ( +
+ +
+ ); } diff --git a/client/src/components/documents-local-upload/documents-local-upload.component.jsx b/client/src/components/documents-local-upload/documents-local-upload.component.jsx index 087c6f255..d305b1677 100644 --- a/client/src/components/documents-local-upload/documents-local-upload.component.jsx +++ b/client/src/components/documents-local-upload/documents-local-upload.component.jsx @@ -1,71 +1,69 @@ -import {UploadOutlined} from "@ant-design/icons"; -import {Upload} from "antd"; -import React, {useState} from "react"; +import { UploadOutlined } from "@ant-design/icons"; +import { Upload } from "antd"; +import React, { useState } from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; -import {handleUpload} from "./documents-local-upload.utility"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import { handleUpload } from "./documents-local-upload.utility"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - bodyshop: selectBodyshop, + currentUser: selectCurrentUser, + bodyshop: selectBodyshop }); export function DocumentsLocalUploadComponent({ - children, - currentUser, - bodyshop, - job, - vendorid, - invoice_number, - callbackAfterUpload, - allowAllTypes, - }) { - const [fileList, setFileList] = useState([]); + children, + currentUser, + bodyshop, + job, + vendorid, + invoice_number, + callbackAfterUpload, + allowAllTypes +}) { + const [fileList, setFileList] = useState([]); - const handleDone = (uid) => { - setTimeout(() => { - setFileList((fileList) => fileList.filter((x) => x.uid !== uid)); - }, 2000); - }; + const handleDone = (uid) => { + setTimeout(() => { + setFileList((fileList) => fileList.filter((x) => x.uid !== uid)); + }, 2000); + }; - return ( - { - if (f.event && f.event.percent === 100) handleDone(f.file.uid); + return ( + { + if (f.event && f.event.percent === 100) handleDone(f.file.uid); - setFileList(f.fileList); - }} - customRequest={(ev) => - handleUpload({ - ev, - context: { - jobid: job.id, - vendorid, - invoice_number, - callback: callbackAfterUpload, - }, - }) - } - {...(!allowAllTypes && { - accept: "audio/*, video/*, image/*, .pdf, .doc, .docx, .xls, .xlsx", - })} - > - {children || ( - <> -

- -

-

- Click or drag files to this area to upload. -

- - )} -
- ); + setFileList(f.fileList); + }} + customRequest={(ev) => + handleUpload({ + ev, + context: { + jobid: job.id, + vendorid, + invoice_number, + callback: callbackAfterUpload + } + }) + } + {...(!allowAllTypes && { + accept: "audio/*, video/*, image/*, .pdf, .doc, .docx, .xls, .xlsx" + })} + > + {children || ( + <> +

+ +

+

Click or drag files to this area to upload.

+ + )} +
+ ); } export default connect(mapStateToProps, null)(DocumentsLocalUploadComponent); diff --git a/client/src/components/documents-local-upload/documents-local-upload.utility.js b/client/src/components/documents-local-upload/documents-local-upload.utility.js index eb9a97ed7..833ceedcf 100644 --- a/client/src/components/documents-local-upload/documents-local-upload.utility.js +++ b/client/src/components/documents-local-upload/documents-local-upload.utility.js @@ -1,80 +1,72 @@ import cleanAxios from "../../utils/CleanAxios"; -import {store} from "../../redux/store"; -import {addMediaForJob} from "../../redux/media/media.actions"; +import { store } from "../../redux/store"; +import { addMediaForJob } from "../../redux/media/media.actions"; import normalizeUrl from "normalize-url"; -import {notification} from "antd"; +import { notification } from "antd"; import i18n from "i18next"; -export const handleUpload = async ({ev, context}) => { - const {onError, onSuccess, onProgress, file} = ev; - const {jobid, invoice_number, vendorid, callbackAfterUpload} = context; +export const handleUpload = async ({ ev, context }) => { + const { onError, onSuccess, onProgress, file } = ev; + const { jobid, invoice_number, vendorid, callbackAfterUpload } = context; - const bodyshop = store.getState().user.bodyshop; - var options = { - headers: { - "X-Requested-With": "XMLHttpRequest", - ims_token: bodyshop.localmediatoken, - }, - onUploadProgress: (e) => { - if (!!onProgress) onProgress({percent: (e.loaded / e.total) * 100}); - }, - }; - - const formData = new FormData(); - - formData.append("jobid", jobid); - if (invoice_number) { - formData.append("invoice_number", invoice_number); - formData.append("vendorid", vendorid); + const bodyshop = store.getState().user.bodyshop; + var options = { + headers: { + "X-Requested-With": "XMLHttpRequest", + ims_token: bodyshop.localmediatoken + }, + onUploadProgress: (e) => { + if (!!onProgress) onProgress({ percent: (e.loaded / e.total) * 100 }); } - formData.append("file", file); + }; - const imexMediaServerResponse = await cleanAxios.post( - normalizeUrl( - `${bodyshop.localmediaserverhttp}/${ - invoice_number ? "bills" : "jobs" - }/upload` - ), - formData, - { - ...options, - } + const formData = new FormData(); + + formData.append("jobid", jobid); + if (invoice_number) { + formData.append("invoice_number", invoice_number); + formData.append("vendorid", vendorid); + } + formData.append("file", file); + + const imexMediaServerResponse = await cleanAxios.post( + normalizeUrl(`${bodyshop.localmediaserverhttp}/${invoice_number ? "bills" : "jobs"}/upload`), + formData, + { + ...options + } + ); + + if (imexMediaServerResponse.status !== 200) { + if (!!onError) { + onError(imexMediaServerResponse.statusText); + } + } else { + onSuccess && onSuccess(file); + notification.open({ + type: "success", + key: "docuploadsuccess", + message: i18n.t("documents.successes.insert") + }); + store.dispatch( + addMediaForJob({ + jobid, + media: imexMediaServerResponse.data.map((d) => { + return { + ...d, + selected: false, + src: normalizeUrl(`${bodyshop.localmediaserverhttp}/${d.src}`), + ...(d.optimized && { + optimized: normalizeUrl(`${bodyshop.localmediaserverhttp}/${d.optimized}`) + }), + thumbnail: normalizeUrl(`${bodyshop.localmediaserverhttp}/${d.thumbnail}`) + }; + }) + }) ); + } - if (imexMediaServerResponse.status !== 200) { - if (!!onError) { - onError(imexMediaServerResponse.statusText); - } - } else { - onSuccess && onSuccess(file); - notification.open({ - type: "success", - key: "docuploadsuccess", - message: i18n.t("documents.successes.insert"), - }); - store.dispatch( - addMediaForJob({ - jobid, - media: imexMediaServerResponse.data.map((d) => { - return { - ...d, - selected: false, - src: normalizeUrl(`${bodyshop.localmediaserverhttp}/${d.src}`), - ...(d.optimized && { - optimized: normalizeUrl( - `${bodyshop.localmediaserverhttp}/${d.optimized}` - ), - }), - thumbnail: normalizeUrl( - `${bodyshop.localmediaserverhttp}/${d.thumbnail}` - ), - }; - }), - }) - ); - } - - if (callbackAfterUpload) { - callbackAfterUpload(); - } + if (callbackAfterUpload) { + callbackAfterUpload(); + } }; diff --git a/client/src/components/documents-upload/documents-upload.component.jsx b/client/src/components/documents-upload/documents-upload.component.jsx index 37537f251..6392575ec 100644 --- a/client/src/components/documents-upload/documents-upload.component.jsx +++ b/client/src/components/documents-upload/documents-upload.component.jsx @@ -1,117 +1,111 @@ -import {UploadOutlined} from "@ant-design/icons"; -import {notification, Progress, Result, Space, Upload} from "antd"; -import React, {useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { UploadOutlined } from "@ant-design/icons"; +import { notification, Progress, Result, Space, Upload } from "antd"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import formatBytes from "../../utils/formatbytes"; -import {handleUpload} from "./documents-upload.utility"; +import { handleUpload } from "./documents-upload.utility"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - bodyshop: selectBodyshop, + currentUser: selectCurrentUser, + bodyshop: selectBodyshop }); export function DocumentsUploadComponent({ - children, - currentUser, - bodyshop, - jobId, - tagsArray, - billId, - callbackAfterUpload, - totalSize, - ignoreSizeLimit = false, - }) { - const {t} = useTranslation(); - const [fileList, setFileList] = useState([]); + children, + currentUser, + bodyshop, + jobId, + tagsArray, + billId, + callbackAfterUpload, + totalSize, + ignoreSizeLimit = false +}) { + const { t } = useTranslation(); + const [fileList, setFileList] = useState([]); - const pct = useMemo(() => { - return parseInt( - (totalSize / ((bodyshop && bodyshop.jobsizelimit) || 1)) * 100 - ); - }, [bodyshop, totalSize]); - - if (pct > 100 && !ignoreSizeLimit) - return ( - - ); - - const handleDone = (uid) => { - setTimeout(() => { - setFileList((fileList) => fileList.filter((x) => x.uid !== uid)); - }, 2000); - }; + const pct = useMemo(() => { + return parseInt((totalSize / ((bodyshop && bodyshop.jobsizelimit) || 1)) * 100); + }, [bodyshop, totalSize]); + if (pct > 100 && !ignoreSizeLimit) return ( - { - if (f.event && f.event.percent === 100) handleDone(f.file.uid); - setFileList(f.fileList); - }} - beforeUpload={(file, fileList) => { - if (ignoreSizeLimit) return true; - const newFiles = fileList.reduce((acc, val) => acc + val.size, 0); - const shouldStopUpload = - (totalSize + newFiles) / ((bodyshop && bodyshop.jobsizelimit) || 1) >= - 1; + + ); - //Check to see if old files plus newly uploaded ones will be too much. - if (shouldStopUpload) { - notification.open({ - key: "cannotuploaddocuments", - type: "error", - message: t("documents.labels.upload_limitexceeded_title"), - description: t("documents.labels.upload_limitexceeded"), - }); - return Upload.LIST_IGNORE; - } - return true; - }} - customRequest={(ev) => - handleUpload(ev, { - bodyshop: bodyshop, - uploaded_by: currentUser.email, - jobId: jobId, - billId: billId, - tagsArray: tagsArray, - callback: callbackAfterUpload, - }) - } - accept="audio/*, video/*, image/*, .pdf, .doc, .docx, .xls, .xlsx" - // showUploadList={false} - > - {children || ( - <> -

- -

-

- Click or drag files to this area to upload. -

- {!ignoreSizeLimit && ( - - - + const handleDone = (uid) => { + setTimeout(() => { + setFileList((fileList) => fileList.filter((x) => x.uid !== uid)); + }, 2000); + }; + + return ( + { + if (f.event && f.event.percent === 100) handleDone(f.file.uid); + setFileList(f.fileList); + }} + beforeUpload={(file, fileList) => { + if (ignoreSizeLimit) return true; + const newFiles = fileList.reduce((acc, val) => acc + val.size, 0); + const shouldStopUpload = (totalSize + newFiles) / ((bodyshop && bodyshop.jobsizelimit) || 1) >= 1; + + //Check to see if old files plus newly uploaded ones will be too much. + if (shouldStopUpload) { + notification.open({ + key: "cannotuploaddocuments", + type: "error", + message: t("documents.labels.upload_limitexceeded_title"), + description: t("documents.labels.upload_limitexceeded") + }); + return Upload.LIST_IGNORE; + } + return true; + }} + customRequest={(ev) => + handleUpload(ev, { + bodyshop: bodyshop, + uploaded_by: currentUser.email, + jobId: jobId, + billId: billId, + tagsArray: tagsArray, + callback: callbackAfterUpload + }) + } + accept="audio/*, video/*, image/*, .pdf, .doc, .docx, .xls, .xlsx" + // showUploadList={false} + > + {children || ( + <> +

+ +

+

Click or drag files to this area to upload.

+ {!ignoreSizeLimit && ( + + + {t("documents.labels.usage", { - percent: pct, - used: formatBytes(totalSize), - total: formatBytes(bodyshop && bodyshop.jobsizelimit), + percent: pct, + used: formatBytes(totalSize), + total: formatBytes(bodyshop && bodyshop.jobsizelimit) })} - - )} - - )} -
- ); +
+ )} + + )} +
+ ); } export default connect(mapStateToProps, null)(DocumentsUploadComponent); diff --git a/client/src/components/documents-upload/documents-upload.utility.js b/client/src/components/documents-upload/documents-upload.utility.js index 7d62e6e85..6e2e12c24 100644 --- a/client/src/components/documents-upload/documents-upload.utility.js +++ b/client/src/components/documents-upload/documents-upload.utility.js @@ -1,12 +1,12 @@ -import {notification} from "antd"; +import { notification } from "antd"; import axios from "axios"; import i18n from "i18next"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {INSERT_NEW_DOCUMENT} from "../../graphql/documents.queries"; -import {axiosAuthInterceptorId} from "../../utils/CleanAxios"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries"; +import { axiosAuthInterceptorId } from "../../utils/CleanAxios"; import client from "../../utils/GraphQLClient"; import exifr from "exifr"; -import {store} from "../../redux/store"; +import { store } from "../../redux/store"; //Context: currentUserEmail, bodyshop, jobid, invoiceid @@ -15,86 +15,61 @@ var cleanAxios = axios.create(); cleanAxios.interceptors.request.eject(axiosAuthInterceptorId); export const handleUpload = (ev, context) => { - logImEXEvent("document_upload", {filetype: ev.file.type}); + logImEXEvent("document_upload", { filetype: ev.file.type }); - const {onError, onSuccess, onProgress} = ev; - const {bodyshop, jobId} = context; + const { onError, onSuccess, onProgress } = ev; + const { bodyshop, jobId } = context; - const fileName = ev.file.name || ev.filename; + const fileName = ev.file.name || ev.filename; - let key = `${bodyshop.id}/${jobId}/${replaceAccents(fileName).replace( - /[^A-Z0-9]+/gi, - "_" - )}-${new Date().getTime()}`; - let extension = fileName.split(".").pop(); - uploadToCloudinary( - key, - extension, - ev.file.type, - ev.file, - onError, - onSuccess, - onProgress, - context - ); + let key = `${bodyshop.id}/${jobId}/${replaceAccents(fileName).replace(/[^A-Z0-9]+/gi, "_")}-${new Date().getTime()}`; + let extension = fileName.split(".").pop(); + uploadToCloudinary(key, extension, ev.file.type, ev.file, onError, onSuccess, onProgress, context); }; -export const uploadToCloudinary = async ( - key, - extension, - fileType, - file, - onError, - onSuccess, - onProgress, - context -) => { - const {bodyshop, jobId, billId, uploaded_by, callback, tagsArray} = context; +export const uploadToCloudinary = async (key, extension, fileType, file, onError, onSuccess, onProgress, context) => { + const { bodyshop, jobId, billId, uploaded_by, callback, tagsArray } = context; - //Set variables for getting the signed URL. - let timestamp = Math.floor(Date.now() / 1000); - let public_id = key; - let tags = `${bodyshop.imexshopid},${ - tagsArray ? tagsArray.map((tag) => `${tag},`) : "" - }`; - // let eager = import.meta.env.VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS; + //Set variables for getting the signed URL. + let timestamp = Math.floor(Date.now() / 1000); + let public_id = key; + let tags = `${bodyshop.imexshopid},${tagsArray ? tagsArray.map((tag) => `${tag},`) : ""}`; + // let eager = import.meta.env.VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS; - //Get the signed url. + //Get the signed url. - const upload_preset = fileType.startsWith("video") - ? "incoming_upload_video" - : "incoming_upload"; + const upload_preset = fileType.startsWith("video") ? "incoming_upload_video" : "incoming_upload"; - const signedURLResponse = await axios.post("/media/sign", { - public_id: public_id, - tags: tags, - timestamp: timestamp, - upload_preset: upload_preset, + const signedURLResponse = await axios.post("/media/sign", { + public_id: public_id, + tags: tags, + timestamp: timestamp, + upload_preset: upload_preset + }); + + if (signedURLResponse.status !== 200) { + if (!!onError) onError(signedURLResponse.statusText); + notification["error"]({ + message: i18n.t("documents.errors.getpresignurl", { + message: signedURLResponse.statusText + }) }); + return; + } - if (signedURLResponse.status !== 200) { - if (!!onError) onError(signedURLResponse.statusText); - notification["error"]({ - message: i18n.t("documents.errors.getpresignurl", { - message: signedURLResponse.statusText, - }), - }); - return; + //Build request to end to cloudinary. + var signature = signedURLResponse.data; + var options = { + headers: { "X-Requested-With": "XMLHttpRequest" }, + onUploadProgress: (e) => { + if (!!onProgress) onProgress({ percent: (e.loaded / e.total) * 100 }); } + }; - //Build request to end to cloudinary. - var signature = signedURLResponse.data; - var options = { - headers: {"X-Requested-With": "XMLHttpRequest"}, - onUploadProgress: (e) => { - if (!!onProgress) onProgress({percent: (e.loaded / e.total) * 100}); - }, - }; + const formData = new FormData(); + formData.append("file", file); - const formData = new FormData(); - formData.append("file", file); - - formData.append("upload_preset", upload_preset); + formData.append("upload_preset", upload_preset); formData.append("api_key", import.meta.env.VITE_APP_CLOUDINARY_API_KEY); formData.append("public_id", public_id); @@ -103,131 +78,128 @@ export const uploadToCloudinary = async ( formData.append("signature", signature); const cloudinaryUploadResponse = await cleanAxios.post( - `${import.meta.env.VITE_APP_CLOUDINARY_ENDPOINT_API}/${DetermineFileType( - fileType - )}/upload`, + `${import.meta.env.VITE_APP_CLOUDINARY_ENDPOINT_API}/${DetermineFileType(fileType)}/upload`, formData, { - ...options, + ...options } ); - if (cloudinaryUploadResponse.status !== 200) { - if (!!onError) { - onError(cloudinaryUploadResponse.statusText); - } - - try { - axios.post("/newlog", { - message: "client-cloudinary-upload-error", - type: "error", - user: store.getState().user.email, - object: cloudinaryUploadResponse, - }); - } catch (error) { - } - - notification["error"]({ - message: i18n.t("documents.errors.insert", { - message: cloudinaryUploadResponse.statusText, - }), - }); - return; + if (cloudinaryUploadResponse.status !== 200) { + if (!!onError) { + onError(cloudinaryUploadResponse.statusText); } - //Insert the document with the matching key. - let takenat; - if (fileType.includes("image")) { - try { - const exif = await exifr.parse(file); + try { + axios.post("/newlog", { + message: "client-cloudinary-upload-error", + type: "error", + user: store.getState().user.email, + object: cloudinaryUploadResponse + }); + } catch (error) {} - takenat = exif && exif.DateTimeOriginal; - } catch (error) { - console.log("Unable to parse image file for EXIF Data"); - } - } - const documentInsert = await client.mutate({ - mutation: INSERT_NEW_DOCUMENT, - variables: { - docInput: [ - { - ...(jobId ? {jobid: jobId} : {}), - ...(billId ? {billid: billId} : {}), - uploaded_by: uploaded_by, - key: key, - type: fileType, - extension: cloudinaryUploadResponse.data.format || extension, - bodyshopid: bodyshop.id, - size: cloudinaryUploadResponse.data.bytes || file.size, - takenat, - }, - ], - }, + notification["error"]({ + message: i18n.t("documents.errors.insert", { + message: cloudinaryUploadResponse.statusText + }) }); - if (!documentInsert.errors) { - if (!!onSuccess) - onSuccess({ - uid: documentInsert.data.insert_documents.returning[0].id, - name: documentInsert.data.insert_documents.returning[0].name, - status: "done", - key: documentInsert.data.insert_documents.returning[0].key, - }); - notification.open({ - type: "success", - key: "docuploadsuccess", - message: i18n.t("documents.successes.insert"), - }); - if (callback) { - callback(); - } - } else { - if (!!onError) onError(JSON.stringify(documentInsert.errors)); - notification["error"]({ - message: i18n.t("documents.errors.insert", { - message: JSON.stringify(documentInsert.errors), - }), - }); - return; + return; + } + + //Insert the document with the matching key. + let takenat; + if (fileType.includes("image")) { + try { + const exif = await exifr.parse(file); + + takenat = exif && exif.DateTimeOriginal; + } catch (error) { + console.log("Unable to parse image file for EXIF Data"); } + } + const documentInsert = await client.mutate({ + mutation: INSERT_NEW_DOCUMENT, + variables: { + docInput: [ + { + ...(jobId ? { jobid: jobId } : {}), + ...(billId ? { billid: billId } : {}), + uploaded_by: uploaded_by, + key: key, + type: fileType, + extension: cloudinaryUploadResponse.data.format || extension, + bodyshopid: bodyshop.id, + size: cloudinaryUploadResponse.data.bytes || file.size, + takenat + } + ] + } + }); + if (!documentInsert.errors) { + if (!!onSuccess) + onSuccess({ + uid: documentInsert.data.insert_documents.returning[0].id, + name: documentInsert.data.insert_documents.returning[0].name, + status: "done", + key: documentInsert.data.insert_documents.returning[0].key + }); + notification.open({ + type: "success", + key: "docuploadsuccess", + message: i18n.t("documents.successes.insert") + }); + if (callback) { + callback(); + } + } else { + if (!!onError) onError(JSON.stringify(documentInsert.errors)); + notification["error"]({ + message: i18n.t("documents.errors.insert", { + message: JSON.stringify(documentInsert.errors) + }) + }); + return; + } }; //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"; - else if (filetype.startsWith("video")) return "video"; - else if (filetype.startsWith("application/pdf")) return "image"; - else if (filetype.startsWith("application")) return "raw"; + 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"; + return "auto"; } function replaceAccents(str) { - // Verifies if the String has accents and replace them - if (str.search(/[\xC0-\xFF]/g) > -1) { - str = str - .replace(/[\xC0-\xC5]/g, "A") - .replace(/[\xC6]/g, "AE") - .replace(/[\xC7]/g, "C") - .replace(/[\xC8-\xCB]/g, "E") - .replace(/[\xCC-\xCF]/g, "I") - .replace(/[\xD0]/g, "D") - .replace(/[\xD1]/g, "N") - .replace(/[\xD2-\xD6\xD8]/g, "O") - .replace(/[\xD9-\xDC]/g, "U") - .replace(/[\xDD]/g, "Y") - .replace(/[\xDE]/g, "P") - .replace(/[\xE0-\xE5]/g, "a") - .replace(/[\xE6]/g, "ae") - .replace(/[\xE7]/g, "c") - .replace(/[\xE8-\xEB]/g, "e") - .replace(/[\xEC-\xEF]/g, "i") - .replace(/[\xF1]/g, "n") - .replace(/[\xF2-\xF6\xF8]/g, "o") - .replace(/[\xF9-\xFC]/g, "u") - .replace(/[\xFE]/g, "p") - .replace(/[\xFD\xFF]/g, "y"); - } + // Verifies if the String has accents and replace them + if (str.search(/[\xC0-\xFF]/g) > -1) { + str = str + .replace(/[\xC0-\xC5]/g, "A") + .replace(/[\xC6]/g, "AE") + .replace(/[\xC7]/g, "C") + .replace(/[\xC8-\xCB]/g, "E") + .replace(/[\xCC-\xCF]/g, "I") + .replace(/[\xD0]/g, "D") + .replace(/[\xD1]/g, "N") + .replace(/[\xD2-\xD6\xD8]/g, "O") + .replace(/[\xD9-\xDC]/g, "U") + .replace(/[\xDD]/g, "Y") + .replace(/[\xDE]/g, "P") + .replace(/[\xE0-\xE5]/g, "a") + .replace(/[\xE6]/g, "ae") + .replace(/[\xE7]/g, "c") + .replace(/[\xE8-\xEB]/g, "e") + .replace(/[\xEC-\xEF]/g, "i") + .replace(/[\xF1]/g, "n") + .replace(/[\xF2-\xF6\xF8]/g, "o") + .replace(/[\xF9-\xFC]/g, "u") + .replace(/[\xFE]/g, "p") + .replace(/[\xFD\xFF]/g, "y"); + } - return str; + return str; } diff --git a/client/src/components/email-documents/email-documents.component.jsx b/client/src/components/email-documents/email-documents.component.jsx index 5ca6eae75..d3c6b20da 100644 --- a/client/src/components/email-documents/email-documents.component.jsx +++ b/client/src/components/email-documents/email-documents.component.jsx @@ -1,74 +1,63 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {GET_DOCUMENTS_BY_JOB} from "../../graphql/documents.queries"; -import {selectEmailConfig} from "../../redux/email/email.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries"; +import { selectEmailConfig } from "../../redux/email/email.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component"; -import JobsDocumentsLocalGalleryExternalComponent - from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component"; +import JobsDocumentsLocalGalleryExternalComponent from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, - emailConfig: selectEmailConfig, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop, + emailConfig: selectEmailConfig }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(EmailDocumentsComponent); +export default connect(mapStateToProps, mapDispatchToProps)(EmailDocumentsComponent); -export function EmailDocumentsComponent({ - emailConfig, - form, - selectedMediaState, - bodyshop, - }) { - const {t} = useTranslation(); +export function EmailDocumentsComponent({ emailConfig, form, selectedMediaState, bodyshop }) { + const { t } = useTranslation(); - const [selectedMedia, setSelectedMedia] = selectedMediaState; - const {loading, error, data} = useQuery(GET_DOCUMENTS_BY_JOB, { - variables: { - jobId: emailConfig.jobid, - }, - skip: !emailConfig.jobid, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const [selectedMedia, setSelectedMedia] = selectedMediaState; + const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, { + variables: { + jobId: emailConfig.jobid + }, + skip: !emailConfig.jobid, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - return ( -
- {loading && } - {error && } - {selectedMedia.filter((s) => s.isSelected).length >= 10 ? ( -
{t("messaging.labels.maxtenimages")}
- ) : null} - {selectedMedia && - selectedMedia - .filter((s) => s.isSelected) - .reduce((acc, val) => (acc = acc + val.size), 0) >= - 10485760 - new Blob([form.getFieldValue("html")]).size ? ( -
{t("general.errors.sizelimit")}
- ) : null} - {!bodyshop.uselocalmediaserver && data && ( - - )} - {bodyshop.uselocalmediaserver && ( - - )} -
- ); + return ( +
+ {loading && } + {error && } + {selectedMedia.filter((s) => s.isSelected).length >= 10 ? ( +
{t("messaging.labels.maxtenimages")}
+ ) : null} + {selectedMedia && + selectedMedia.filter((s) => s.isSelected).reduce((acc, val) => (acc = acc + val.size), 0) >= + 10485760 - new Blob([form.getFieldValue("html")]).size ? ( +
{t("general.errors.sizelimit")}
+ ) : null} + {!bodyshop.uselocalmediaserver && data && ( + + )} + {bodyshop.uselocalmediaserver && ( + + )} +
+ ); } diff --git a/client/src/components/email-overlay/email-overlay.component.jsx b/client/src/components/email-overlay/email-overlay.component.jsx index d648eddb0..fe14137c1 100644 --- a/client/src/components/email-overlay/email-overlay.component.jsx +++ b/client/src/components/email-overlay/email-overlay.component.jsx @@ -1,262 +1,217 @@ -import {UploadOutlined, UserAddOutlined} from "@ant-design/icons"; -import {Button, Divider, Dropdown, Form, Input, Select, Space, Tabs, Upload,} from "antd"; +import { UploadOutlined, UserAddOutlined } from "@ant-design/icons"; +import { Button, Divider, Dropdown, Form, Input, Select, Space, Tabs, Upload } from "antd"; import _ from "lodash"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectEmailConfig} from "../../redux/email/email.selectors"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; -import {CreateExplorerLinkForJob} from "../../utils/localmedia"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectEmailConfig } from "../../redux/email/email.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import { CreateExplorerLinkForJob } from "../../utils/localmedia"; import EmailDocumentsComponent from "../email-documents/email-documents.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, - emailConfig: selectEmailConfig, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser, + emailConfig: selectEmailConfig }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(EmailOverlayComponent); +export default connect(mapStateToProps, mapDispatchToProps)(EmailOverlayComponent); -export function EmailOverlayComponent({ - emailConfig, - form, - selectedMediaState, - bodyshop, - currentUser, - }) { - const {t} = useTranslation(); - const handleClick = ({item, key, keyPath}) => { - const email = item.props.value; - form.setFieldsValue({ - to: _.uniq([ - ...form.getFieldValue("to"), - ...(typeof email === "string" ? [email] : email), - ]), - }); - }; - const handle_CC_Click = ({item, key, keyPath}) => { - const email = item.props.value; - form.setFieldsValue({ - cc: _.uniq([ - ...(form.getFieldValue("cc") || ""), - ...(typeof email === "string" ? [email] : email), - ]), - }); - }; +export function EmailOverlayComponent({ emailConfig, form, selectedMediaState, bodyshop, currentUser }) { + const { t } = useTranslation(); + const handleClick = ({ item, key, keyPath }) => { + const email = item.props.value; + form.setFieldsValue({ + to: _.uniq([...form.getFieldValue("to"), ...(typeof email === "string" ? [email] : email)]) + }); + }; + const handle_CC_Click = ({ item, key, keyPath }) => { + const email = item.props.value; + form.setFieldsValue({ + cc: _.uniq([...(form.getFieldValue("cc") || ""), ...(typeof email === "string" ? [email] : email)]) + }); + }; - const emailsToMenu = { - items: [ - ...bodyshop.employees - .filter((e) => e.user_email) - .map((e, idx) => ({ - key: idx, - label: `${e.first_name} ${e.last_name}`, - value: e.user_email, - })), - ...bodyshop.md_to_emails.map((e, idx) => ({ - key: idx + "group", - label: e.label, - value: e.emails, - })), - ], - onClick: handleClick, - }; - const menuCC = { - items: [ - ...bodyshop.employees - .filter((e) => e.user_email) - .map((e, idx) => ({ - key: idx, - label: `${e.first_name} ${e.last_name}`, - value: e.user_email, - })), - ...bodyshop.md_to_emails.map((e, idx) => ({ - key: idx + "group", - label: e.label, - value: e.emails, - })), - ], - onClick: handle_CC_Click, - }; + const emailsToMenu = { + items: [ + ...bodyshop.employees + .filter((e) => e.user_email) + .map((e, idx) => ({ + key: idx, + label: `${e.first_name} ${e.last_name}`, + value: e.user_email + })), + ...bodyshop.md_to_emails.map((e, idx) => ({ + key: idx + "group", + label: e.label, + value: e.emails + })) + ], + onClick: handleClick + }; + const menuCC = { + items: [ + ...bodyshop.employees + .filter((e) => e.user_email) + .map((e, idx) => ({ + key: idx, + label: `${e.first_name} ${e.last_name}`, + value: e.user_email + })), + ...bodyshop.md_to_emails.map((e, idx) => ({ + key: idx + "group", + label: e.label, + value: e.emails + })) + ], + onClick: handle_CC_Click + }; - return ( -
- - - - - {t("emails.fields.to")} - - e.preventDefault()} - > - - - - - } - name="to" - rules={[ - { - required: true, - //message: t("general.validation.required"), - }, - ]} - > - - - - - + return ( +
+ + + + + {t("emails.fields.to")} + + e.preventDefault()}> + + + + + } + name="to" + rules={[ + { + required: true + //message: t("general.validation.required"), + } + ]} + > + + + + + - {t("emails.labels.preview")} - {bodyshop.attach_pdf_to_email && ( - {t("emails.labels.pdfcopywillbeattached")} - )} + {t("emails.labels.preview")} + {bodyshop.attach_pdf_to_email && {t("emails.labels.pdfcopywillbeattached")}} - - {() => { - return ( -
+ {() => { + return ( +
- ); - }} - - - - ), - }, - { - key: "attachments", - label: t("emails.labels.attachments"), - children: ( - <> - {bodyshop.uselocalmediaserver && emailConfig.jobid && ( - - - - )} - { - if (Array.isArray(e)) { - return e; - } - return e && e.fileList; - }} - rules={[ - ({getFieldValue}) => ({ - validator(rule, value) { - const totalSize = value.reduce( - (acc, val) => (acc = acc + val.size), - 0 - ); - - const limit = - 10485760 - new Blob([form.getFieldValue("html")]).size; - - if (totalSize > limit) { - return Promise.reject(t("general.errors.sizelimit")); - } - return Promise.resolve(); - }, - }), - ]} - > - - <> -

- -

-

- Click or drag files to this area to upload. -

- -
-
- - ), - }, - ]} + backgroundColor: "lightgray", + borderLeft: "6px solid #2196F3" + }} + dangerouslySetInnerHTML={{ __html: form.getFieldValue("html") }} /> -
- ); + ); + }} + + + + }, + { + key: "attachments", + label: t("emails.labels.attachments"), + children: ( + <> + {bodyshop.uselocalmediaserver && emailConfig.jobid && ( + + + + )} + { + if (Array.isArray(e)) { + return e; + } + return e && e.fileList; + }} + rules={[ + ({ getFieldValue }) => ({ + validator(rule, value) { + const totalSize = value.reduce((acc, val) => (acc = acc + val.size), 0); + + const limit = 10485760 - new Blob([form.getFieldValue("html")]).size; + + if (totalSize > limit) { + return Promise.reject(t("general.errors.sizelimit")); + } + return Promise.resolve(); + } + }) + ]} + > + + <> +

+ +

+

Click or drag files to this area to upload.

+ +
+
+ + ) + } + ]} + /> +
+ ); } diff --git a/client/src/components/email-overlay/email-overlay.container.jsx b/client/src/components/email-overlay/email-overlay.container.jsx index da51ea1c4..73d1d52c4 100644 --- a/client/src/components/email-overlay/email-overlay.container.jsx +++ b/client/src/components/email-overlay/email-overlay.container.jsx @@ -1,260 +1,224 @@ -import {Button, Divider, Form, Modal, notification, Space} from "antd"; +import { Button, Divider, Form, Modal, notification, Space } from "antd"; import axios from "axios"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {toggleEmailOverlayVisible} from "../../redux/email/email.actions"; -import {selectEmailConfig, selectEmailVisible,} from "../../redux/email/email.selectors.js"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { toggleEmailOverlayVisible } from "../../redux/email/email.actions"; +import { selectEmailConfig, selectEmailVisible } from "../../redux/email/email.selectors.js"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import RenderTemplate from "../../utils/RenderTemplate"; -import {EmailSettings} from "../../utils/TemplateConstants"; +import { EmailSettings } from "../../utils/TemplateConstants"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import EmailOverlayComponent from "./email-overlay.component"; const mapStateToProps = createStructuredSelector({ - modalVisible: selectEmailVisible, - emailConfig: selectEmailConfig, - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + modalVisible: selectEmailVisible, + emailConfig: selectEmailConfig, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - toggleEmailOverlayVisible: () => dispatch(toggleEmailOverlayVisible()), + toggleEmailOverlayVisible: () => dispatch(toggleEmailOverlayVisible()) }); -export function EmailOverlayContainer({ - emailConfig, - modalVisible, - toggleEmailOverlayVisible, - bodyshop, - currentUser, - }) { - const {t} = useTranslation(); - const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - const [sending, setSending] = useState(false); - const [rawHtml, setRawHtml] = useState(""); - const [pdfCopytoAttach, setPdfCopytoAttach] = useState({ - filename: null, - pdf: null, - }); - const [selectedMedia, setSelectedMedia] = useState([]); +export function EmailOverlayContainer({ emailConfig, modalVisible, toggleEmailOverlayVisible, bodyshop, currentUser }) { + const { t } = useTranslation(); + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [sending, setSending] = useState(false); + const [rawHtml, setRawHtml] = useState(""); + const [pdfCopytoAttach, setPdfCopytoAttach] = useState({ + filename: null, + pdf: null + }); + const [selectedMedia, setSelectedMedia] = useState([]); - const defaultEmailFrom = { - from: { - name: currentUser.displayName - ? `${currentUser.displayName} @ ${bodyshop.shopname}` - : bodyshop.shopname, - address: EmailSettings.fromAddress, + const defaultEmailFrom = { + from: { + name: currentUser.displayName ? `${currentUser.displayName} @ ${bodyshop.shopname}` : bodyshop.shopname, + address: EmailSettings.fromAddress + } + }; + + const handleFinish = async (allValues) => { + logImEXEvent("email_send_from_modal"); + + //const attachments = []; + + // if (values.fileList) + // await asyncForEach(values.fileList, async (f) => { + // const t = { + // ContentType: f.type, + // Filename: f.name, + // Base64Content: (await toBase64(f.originFileObj)).split(",")[1], + // }; + // attachments.push(t); + // }); + + const { from, ...values } = allValues; + setSending(true); + try { + await axios.post("/sendemail", { + bodyshopid: bodyshop.id, + jobid: emailConfig.jobid, + + ...defaultEmailFrom, + ReplyTo: { + Email: from, + Name: currentUser.displayName }, - }; + ...values, + html: rawHtml, + attachments: [ + ...(values.fileList + ? await Promise.all( + values.fileList.map(async (f) => { + return { + filename: f.name, + path: await toBase64(f.originFileObj) + }; + }) + ) + : []), + ...(pdfCopytoAttach.pdf + ? [ + { + path: pdfCopytoAttach.pdf, + filename: pdfCopytoAttach.filename && `${pdfCopytoAttach.filename}.pdf` + } + ] + : []) + ], + media: selectedMedia.filter((m) => m.isSelected).map((m) => m.fullsize) + //attachments, + }); + notification["success"]({ message: t("emails.successes.sent") }); + toggleEmailOverlayVisible(); + } catch (error) { + notification["error"]({ + message: t("emails.errors.notsent", { message: error.message }) + }); + } + setSending(false); + }; - const handleFinish = async (allValues) => { - logImEXEvent("email_send_from_modal"); + const render = async () => { + logImEXEvent("email_render_template", { template: emailConfig.template }); + setLoading(true); + let { html, pdf, filename } = await RenderTemplate(emailConfig.template, bodyshop, true); - //const attachments = []; + const response = await axios.post("/render/inlinecss", { + html: html, + url: `${window.location.protocol}://${window.location.host}/` + }); + setRawHtml(response.data); - // if (values.fileList) - // await asyncForEach(values.fileList, async (f) => { - // const t = { - // ContentType: f.type, - // Filename: f.name, - // Base64Content: (await toBase64(f.originFileObj)).split(",")[1], - // }; - // attachments.push(t); - // }); + if (pdf) { + setPdfCopytoAttach({ pdf, filename }); + } - const {from, ...values} = allValues; - setSending(true); - try { - await axios.post("/sendemail", { - bodyshopid: bodyshop.id, - jobid: emailConfig.jobid, + form.setFieldsValue({ + from: currentUser.validemail ? currentUser.email : bodyshop.email, + ...emailConfig.messageOptions, + cc: emailConfig.messageOptions.cc && emailConfig.messageOptions.cc.filter((x) => x), + to: emailConfig.messageOptions.to && emailConfig.messageOptions.to.filter((x) => x), + html: response.data, + fileList: [] + }); - ...defaultEmailFrom, - ReplyTo: { - Email: from, - Name: currentUser.displayName, - }, - ...values, - html: rawHtml, - attachments: [ - ...(values.fileList - ? await Promise.all( - values.fileList.map(async (f) => { - return { - filename: f.name, - path: await toBase64(f.originFileObj), - }; - }) - ) - : []), - ...(pdfCopytoAttach.pdf - ? [ - { - path: pdfCopytoAttach.pdf, - filename: - pdfCopytoAttach.filename && - `${pdfCopytoAttach.filename}.pdf`, - }, - ] - : []), - ], - media: selectedMedia.filter((m) => m.isSelected).map((m) => m.fullsize), - //attachments, - }); - notification["success"]({message: t("emails.successes.sent")}); - toggleEmailOverlayVisible(); - } catch (error) { - notification["error"]({ - message: t("emails.errors.notsent", {message: error.message}), - }); - } - setSending(false); - }; + if (bodyshop.md_email_cc[emailConfig.template.name] && bodyshop.md_email_cc[emailConfig.template.name].length > 0) { + form.setFieldsValue({ + cc: [...(form.getFieldValue("cc") || []), ...bodyshop.md_email_cc[emailConfig.template.name]] + }); + } + setLoading(false); + }; - const render = async () => { - logImEXEvent("email_render_template", {template: emailConfig.template}); - setLoading(true); - let {html, pdf, filename} = await RenderTemplate( - emailConfig.template, - bodyshop, - true - ); - - const response = await axios.post("/render/inlinecss", { - html: html, - url: `${window.location.protocol}://${window.location.host}/`, - }); - setRawHtml(response.data); - - if (pdf) { - setPdfCopytoAttach({pdf, filename}); - } - - form.setFieldsValue({ - from: currentUser.validemail ? currentUser.email : bodyshop.email, - ...emailConfig.messageOptions, - cc: - emailConfig.messageOptions.cc && - emailConfig.messageOptions.cc.filter((x) => x), - to: - emailConfig.messageOptions.to && - emailConfig.messageOptions.to.filter((x) => x), - html: response.data, - fileList: [], - }); - - if ( - bodyshop.md_email_cc[emailConfig.template.name] && - bodyshop.md_email_cc[emailConfig.template.name].length > 0 - ) { - form.setFieldsValue({ - cc: [ - ...(form.getFieldValue("cc") || []), - ...bodyshop.md_email_cc[emailConfig.template.name], - ], - }); - } - setLoading(false); - }; - - useEffect(() => { - if (modalVisible) render(); - }, [modalVisible]); // eslint-disable-line react-hooks/exhaustive-deps - return ( - form.submit()} - title={t("emails.labels.emailpreview")} - onCancel={() => { - toggleEmailOverlayVisible(); - }} - //closeIcon={() => null} - okText={t("general.actions.send")} - okButtonProps={{ - loading: sending, - disabled: - selectedMedia && - (selectedMedia - .filter((s) => s.isSelected) - .reduce((acc, val) => (acc = acc + val.size), 0) >= - 10485760 - new Blob([form.getFieldValue("html")]).size || - selectedMedia.filter((s) => s.isSelected).length > 10), - }} + useEffect(() => { + if (modalVisible) render(); + }, [modalVisible]); // eslint-disable-line react-hooks/exhaustive-deps + return ( + form.submit()} + title={t("emails.labels.emailpreview")} + onCancel={() => { + toggleEmailOverlayVisible(); + }} + //closeIcon={() => null} + okText={t("general.actions.send")} + okButtonProps={{ + loading: sending, + disabled: + selectedMedia && + (selectedMedia.filter((s) => s.isSelected).reduce((acc, val) => (acc = acc + val.size), 0) >= + 10485760 - new Blob([form.getFieldValue("html")]).size || + selectedMedia.filter((s) => s.isSelected).length > 10) + }} + > +
+
+ + + + +
+
+ {loading && (
-
- - - - -
- - {loading && ( -
- - {t("emails.labels.preview")} - -
- )} - - {!loading && ( - - )} - + + {t("emails.labels.preview")} +
- - ); + )} + + {!loading && } + +
+
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(EmailOverlayContainer); +export default connect(mapStateToProps, mapDispatchToProps)(EmailOverlayContainer); const toBase64 = (file) => - new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => resolve(reader.result); - reader.onerror = (error) => reject(error); - }); + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve(reader.result); + reader.onerror = (error) => reject(error); + }); // const asyncForEach = async (array, callback) => { // for (let index = 0; index < array.length; index++) { diff --git a/client/src/components/email-test/email-test-component.jsx b/client/src/components/email-test/email-test-component.jsx index 122315afb..787ba3a76 100644 --- a/client/src/components/email-test/email-test-component.jsx +++ b/client/src/components/email-test/email-test-component.jsx @@ -1,115 +1,103 @@ -import {Button, Form, Input, Select, Switch} from "antd"; +import { Button, Form, Input, Select, Switch } from "antd"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {setEmailOptions} from "../../redux/email/email.actions"; -import {selectCurrentUser} from "../../redux/user/user.selectors"; -import {GenerateDocument} from "../../utils/RenderTemplate"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { setEmailOptions } from "../../redux/email/email.actions"; +import { selectCurrentUser } from "../../redux/user/user.selectors"; +import { GenerateDocument } from "../../utils/RenderTemplate"; import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import dayjs from "../../utils/day"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - setEmailOptions: (e) => dispatch(setEmailOptions(e)), + setEmailOptions: (e) => dispatch(setEmailOptions(e)) }); -export function EmailTestComponent({currentUser, setEmailOptions}) { - const [form] = Form.useForm(); +export function EmailTestComponent({ currentUser, setEmailOptions }) { + const [form] = Form.useForm(); - const handleFinish = (values) => { - GenerateDocument( - { - name: values.key, - variables: { - ...(values.start - ? { - start: dayjs(values.start).startOf("day").format("YYYY-MM-DD"), - } - : {}), - ...(values.end - ? {end: dayjs(values.end).endOf("day").format("YYYY-MM-DD")} - : {}), - ...(values.start - ? {starttz: dayjs(values.start).startOf("day")} - : {}), - ...(values.end ? {endtz: dayjs(values.end).endOf("day")} : {}), + const handleFinish = (values) => { + GenerateDocument( + { + name: values.key, + variables: { + ...(values.start + ? { + start: dayjs(values.start).startOf("day").format("YYYY-MM-DD") + } + : {}), + ...(values.end ? { end: dayjs(values.end).endOf("day").format("YYYY-MM-DD") } : {}), + ...(values.start ? { starttz: dayjs(values.start).startOf("day") } : {}), + ...(values.end ? { endtz: dayjs(values.end).endOf("day") } : {}), - ...(values.id ? {id: values.id} : {}), - }, - }, - { - to: values.to, - }, - values.email ? "e" : "p" - ); - }; - - return ( -
-
- - - - - - - - - - - - - - - - - - - - - -
+ ...(values.id ? { id: values.id } : {}) + } + }, + { + to: values.to + }, + values.email ? "e" : "p" ); + }; + + return ( +
+
+ + + + + + + + + + + + + + + + + + + + + +
+ ); } export default connect(mapStateToProps, mapDispatchToProps)(EmailTestComponent); diff --git a/client/src/components/employee-search-select/employee-search-select.component.jsx b/client/src/components/employee-search-select/employee-search-select.component.jsx index 1388ef981..150e4b426 100644 --- a/client/src/components/employee-search-select/employee-search-select.component.jsx +++ b/client/src/components/employee-search-select/employee-search-select.component.jsx @@ -1,43 +1,37 @@ -import {Select, Space, Tag} from "antd"; +import { Select, Space, Tag } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; -const {Option} = Select; +const { Option } = Select; //To be used as a form element only. -const EmployeeSearchSelect = ({options, ...props}) => { - const {t} = useTranslation(); +const EmployeeSearchSelect = ({ options, ...props }) => { + const { t } = useTranslation(); - return ( - + {options + ? options.map((o) => ( + - )) - : null} - - ); + + {o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")} + + + + )) + : null} + + ); }; export default EmployeeSearchSelect; diff --git a/client/src/components/employee-team-search-select/employee-team-search-select.component.jsx b/client/src/components/employee-team-search-select/employee-team-search-select.component.jsx index eef1a88cc..b59589da1 100644 --- a/client/src/components/employee-team-search-select/employee-team-search-select.component.jsx +++ b/client/src/components/employee-team-search-select/employee-team-search-select.component.jsx @@ -1,33 +1,33 @@ -import {useQuery} from "@apollo/client"; -import {Select} from "antd"; -import React, {forwardRef} from "react"; -import {QUERY_TEAMS} from "../../graphql/employee_teams.queries"; +import { useQuery } from "@apollo/client"; +import { Select } from "antd"; +import React, { forwardRef } from "react"; +import { QUERY_TEAMS } from "../../graphql/employee_teams.queries"; import AlertComponent from "../alert/alert.component"; //To be used as a form element only. -const EmployeeTeamSearchSelect = ({...props}, ref) => { - const {loading, error, data} = useQuery(QUERY_TEAMS); +const EmployeeTeamSearchSelect = ({ ...props }, ref) => { + const { loading, error, data } = useQuery(QUERY_TEAMS); - if (error) return ; - return ( - ({ + value: JSON.stringify(e), + label: e.name + })) + : [] + } + {...props} + /> + ); }; export default forwardRef(EmployeeTeamSearchSelect); diff --git a/client/src/components/error-boundary/error-boundary.component.jsx b/client/src/components/error-boundary/error-boundary.component.jsx index d12926cf6..d4683aab3 100644 --- a/client/src/components/error-boundary/error-boundary.component.jsx +++ b/client/src/components/error-boundary/error-boundary.component.jsx @@ -1,143 +1,149 @@ -import {Button, Col, Collapse, Result, Row, Space} from "antd"; +import { Button, Col, Collapse, Result, Row, Space } from "antd"; import React from "react"; -import {withTranslation} from "react-i18next"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { withTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import * as Sentry from "@sentry/react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - bodyshop: selectBodyshop, + currentUser: selectCurrentUser, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); class ErrorBoundary extends React.Component { - constructor() { - super(); - this.state = { - hasErrored: false, - error: null, - info: null, - }; - } + constructor() { + super(); + this.state = { + hasErrored: false, + error: null, + info: null + }; + } - static getDerivedStateFromError(error) { - console.log("ErrorBoundary -> getDerivedStateFromError -> error", error); + static getDerivedStateFromError(error) { + console.log("ErrorBoundary -> getDerivedStateFromError -> error", error); - return {hasErrored: true, error: error}; - } + return { hasErrored: true, error: error }; + } - componentDidCatch(error, info) { - console.log("Exception Caught by Error Boundary.", error, info); - this.setState({...this.state, error, info}); - } + componentDidCatch(error, info) { + console.log("Exception Caught by Error Boundary.", error, info); + this.setState({ ...this.state, error, info }); + } - handleErrorSubmit = () => { - InstanceRenderManager({executeFunction:true,args:[], imex: () => { - window.$crisp.push([ - "do", - "message:send", - [ - "text", - `I hit the following error: \n\n + handleErrorSubmit = () => { + InstanceRenderManager({ + executeFunction: true, + args: [], + imex: () => { + window.$crisp.push([ + "do", + "message:send", + [ + "text", + `I hit the following error: \n\n ${this.state.error.message}\n\n ${this.state.error.stack}\n\n URL:${window.location} as ${this.props.currentUser.email} for ${ - this.props.bodyshop && this.props.bodyshop.name - } - `, - ], - ]); + this.props.bodyshop && this.props.bodyshop.name + } + ` + ] + ]); - window.$crisp.push(["do", "chat:open"]); - } }) - // const errorDescription = `**Please add relevant details about what you were doing before you encountered this issue** + window.$crisp.push(["do", "chat:open"]); + } + }); + // const errorDescription = `**Please add relevant details about what you were doing before you encountered this issue** - // ---- - // System Generated Log: - // ${this.state.error.message} - // ${this.state.error.stack} - // `; + // ---- + // System Generated Log: + // ${this.state.error.message} + // ${this.state.error.stack} + // `; - // const URL = `https://bodyshop.atlassian.net/servicedesk/customer/portal/3/group/8/create/26?summary=123&description=${encodeURI( - // errorDescription - // )}&customfield_10049=${window.location}&email=${ - // this.props.currentUser.email - // }`; - // console.log(`URL`, URL); - // window.open(URL, "_blank"); - }; + // const URL = `https://bodyshop.atlassian.net/servicedesk/customer/portal/3/group/8/create/26?summary=123&description=${encodeURI( + // errorDescription + // )}&customfield_10049=${window.location}&email=${ + // this.props.currentUser.email + // }`; + // console.log(`URL`, URL); + // window.open(URL, "_blank"); + }; - render() { - const {t} = this.props; - const {error, info} = this.state; - if (this.state.hasErrored === true) { - logImEXEvent("error_boundary_rendered", {error, info}); + render() { + const { t } = this.props; + const { error, info } = this.state; + if (this.state.hasErrored === true) { + logImEXEvent("error_boundary_rendered", { error, info }); - window.$crisp.push([ - "set", - "session:event", - [ - [ - [ - "error_boundary", - { - error: this.state.error.message, - stack: this.state.error.stack, - }, - "red", - ], - ], - ], - ]); + window.$crisp.push([ + "set", + "session:event", + [ + [ + [ + "error_boundary", + { + error: this.state.error.message, + stack: this.state.error.stack + }, + "red" + ] + ] + ] + ]); - return ( -
- - - - - } - /> - -
- - -
- {this.state.error.message} -
-
{this.state.error.stack}
-
-
- - - - ); - } else { - return this.props.children; - } + return ( +
+ + + + + } + /> + +
+ + +
+ {this.state.error.message} +
+
{this.state.error.stack}
+
+
+ + + + ); + } else { + return this.props.children; } + } } -export default Sentry.withErrorBoundary( - connect(mapStateToProps, mapDispatchToProps)(withTranslation()(ErrorBoundary)) -); +export default Sentry.withErrorBoundary(connect(mapStateToProps, mapDispatchToProps)(withTranslation()(ErrorBoundary))); diff --git a/client/src/components/eula/eula.component.jsx b/client/src/components/eula/eula.component.jsx index ec91f0b9a..6c6fb73b7 100644 --- a/client/src/components/eula/eula.component.jsx +++ b/client/src/components/eula/eula.component.jsx @@ -1,252 +1,253 @@ -import React, {useCallback, useEffect, useRef, useState} from "react"; -import {Button, Card, Checkbox, Col, Form, Input, Modal, notification, Row} from "antd"; +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { Button, Card, Checkbox, Col, Form, Input, Modal, notification, Row } from "antd"; import Markdown from "react-markdown"; -import {createStructuredSelector} from "reselect"; -import {selectCurrentEula, selectCurrentUser} from "../../redux/user/user.selectors"; -import {connect} from "react-redux"; -import {FormDatePicker} from "../form-date-picker/form-date-picker.component"; -import {INSERT_EULA_ACCEPTANCE} from "../../graphql/user.queries"; -import {useMutation} from "@apollo/client"; -import {acceptEula} from "../../redux/user/user.actions"; -import {useTranslation} from "react-i18next"; -import day from '../../utils/day'; +import { createStructuredSelector } from "reselect"; +import { selectCurrentEula, selectCurrentUser } from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { FormDatePicker } from "../form-date-picker/form-date-picker.component"; +import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries"; +import { useMutation } from "@apollo/client"; +import { acceptEula } from "../../redux/user/user.actions"; +import { useTranslation } from "react-i18next"; +import day from "../../utils/day"; -import './eula.styles.scss'; +import "./eula.styles.scss"; -const Eula = ({currentEula, currentUser, acceptEula}) => { - const [formReady, setFormReady] = useState(false); - const [hasEverScrolledToBottom, setHasEverScrolledToBottom] = useState(false); - const [insertEulaAcceptance] = useMutation(INSERT_EULA_ACCEPTANCE); - const [form] = Form.useForm(); - const markdownCardRef = useRef(null); - const {t} = useTranslation(); - const [api, contextHolder] = notification.useNotification(); +const Eula = ({ currentEula, currentUser, acceptEula }) => { + const [formReady, setFormReady] = useState(false); + const [hasEverScrolledToBottom, setHasEverScrolledToBottom] = useState(false); + const [insertEulaAcceptance] = useMutation(INSERT_EULA_ACCEPTANCE); + const [form] = Form.useForm(); + const markdownCardRef = useRef(null); + const { t } = useTranslation(); + const [api, contextHolder] = notification.useNotification(); - const handleScroll = useCallback((e) => { - const bottom = e.target.scrollHeight - 100 <= e.target.scrollTop + e.target.clientHeight; - if (bottom && !hasEverScrolledToBottom) { - setHasEverScrolledToBottom(true); - } else if (e.target.scrollHeight <= e.target.clientHeight && !hasEverScrolledToBottom) { - setHasEverScrolledToBottom(true); + const handleScroll = useCallback( + (e) => { + const bottom = e.target.scrollHeight - 100 <= e.target.scrollTop + e.target.clientHeight; + if (bottom && !hasEverScrolledToBottom) { + setHasEverScrolledToBottom(true); + } else if (e.target.scrollHeight <= e.target.clientHeight && !hasEverScrolledToBottom) { + setHasEverScrolledToBottom(true); + } + }, + [hasEverScrolledToBottom, setHasEverScrolledToBottom] + ); + + useEffect(() => { + handleScroll({ target: markdownCardRef.current }); + }, [handleScroll]); + + const handleChange = useCallback(() => { + form + .validateFields({ validateOnly: true }) + .then(() => setFormReady(hasEverScrolledToBottom)) + .catch(() => setFormReady(false)); + }, [form, hasEverScrolledToBottom]); + + useEffect(() => { + handleChange(); + }, [handleChange, hasEverScrolledToBottom, form]); + + const onFinish = async ({ acceptTerms, ...formValues }) => { + const eulaId = currentEula.id; + const useremail = currentUser.email; + + try { + const { accepted_terms, ...otherFormValues } = formValues; + + // Trim the values of the fields before submitting + const trimmedFormValues = Object.entries(otherFormValues).reduce((acc, [key, value]) => { + acc[key] = typeof value === "string" ? value.trim() : value; + return acc; + }, {}); + + await insertEulaAcceptance({ + variables: { + eulaAcceptance: { + eulaid: eulaId, + useremail, + ...otherFormValues, + ...trimmedFormValues, + date_accepted: new Date() + } } - }, [hasEverScrolledToBottom, setHasEverScrolledToBottom]); + }); + acceptEula(); + } catch (err) { + api.error({ + message: t("eula.errors.acceptance.message"), + description: t("eula.errors.acceptance.description"), + placement: "bottomRight", + duration: 5000 + }); + console.log(`${t("eula.errors.acceptance.message")}`); + console.dir({ + message: err.message, + stack: err.stack + }); + } + }; - useEffect(() => { - handleScroll({target: markdownCardRef.current}); - }, [handleScroll]); + return ( + <> + {contextHolder} + ( + + + value.trim() !== "" ? Promise.resolve() : Promise.reject(new Error(t("eula.messages.first_name"))) + } + ]} + > + + + + + + value.trim() !== "" ? Promise.resolve() : Promise.reject(new Error(t("eula.messages.last_name"))) + } + ]} + > + + + + + + + + value.trim() !== "" ? Promise.resolve() : Promise.reject(new Error(t("eula.messages.business_name"))) + } + ]} + > + + + + + + + + + + + + + + + + + { + if (day(value).isSame(day(), "day")) { + return Promise.resolve(); + } + return Promise.reject(new Error(t("eula.messages.date_accepted"))); } - }); - acceptEula(); - } catch (err) { - api.error({ - message: t('eula.errors.acceptance.message'), - description: t('eula.errors.acceptance.description'), - placement: 'bottomRight', - duration: 5000, - - }); - console.log(`${t('eula.errors.acceptance.message')}`); - console.dir({ - message: err.message, - stack: err.stack, - }); - } - }; - - return ( - <> - {contextHolder} - ( - - - value.trim() !== '' ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.first_name'))), - },]} - > - - - - - - value.trim() !== '' ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.last_name'))), - }]} - > - - - - - - - - value.trim() !== '' ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.business_name'))), - }]} - > - - - - - - - - - - - - - - - - - { - if (day(value).isSame(day(), 'day')) { - return Promise.resolve(); - } - return Promise.reject(new Error(t('eula.messages.date_accepted'))); - } - }, - ]} - > - - - - - - - - value ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.accepted_terms'))), - }, - ]} - > - {t('eula.labels.accepted_terms')} - - - - - + } + ]} + > + + + + + + + + value ? Promise.resolve() : Promise.reject(new Error(t("eula.messages.accepted_terms"))) + } + ]} + > + {t("eula.labels.accepted_terms")} + + + + + ); const mapStateToProps = createStructuredSelector({ - currentEula: selectCurrentEula, - currentUser: selectCurrentUser, + currentEula: selectCurrentEula, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - acceptEula: () => dispatch(acceptEula()), + acceptEula: () => dispatch(acceptEula()) }); -export default connect(mapStateToProps, mapDispatchToProps)(Eula); \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(Eula); diff --git a/client/src/components/eula/eula.styles.scss b/client/src/components/eula/eula.styles.scss index 6e12e7032..1773ffa7a 100644 --- a/client/src/components/eula/eula.styles.scss +++ b/client/src/components/eula/eula.styles.scss @@ -18,4 +18,4 @@ .eula-accept-button { width: 100%; -} \ No newline at end of file +} diff --git a/client/src/components/export-logs-count-display/export-logs-count-display.component.jsx b/client/src/components/export-logs-count-display/export-logs-count-display.component.jsx index 2fe64c8a2..df8a03a94 100644 --- a/client/src/components/export-logs-count-display/export-logs-count-display.component.jsx +++ b/client/src/components/export-logs-count-display/export-logs-count-display.component.jsx @@ -1,25 +1,25 @@ import React from "react"; -import {WarningOutlined} from "@ant-design/icons"; -import {Space, Tooltip} from "antd"; -import {useTranslation} from "react-i18next"; +import { WarningOutlined } from "@ant-design/icons"; +import { Space, Tooltip } from "antd"; +import { useTranslation } from "react-i18next"; const style = { - fontWeight: "bold", - color: "green", + fontWeight: "bold", + color: "green" }; -export default function ExportLogsCountDisplay({logs}) { - const success = logs.filter((e) => e.successful).length; - const attempts = logs.length; - const {t} = useTranslation(); - return ( - 0 ? style : {}}> - {`${success}/${attempts}`} - {success > 0 && ( - - - - )} - - ); +export default function ExportLogsCountDisplay({ logs }) { + const success = logs.filter((e) => e.successful).length; + const attempts = logs.length; + const { t } = useTranslation(); + return ( + 0 ? style : {}}> + {`${success}/${attempts}`} + {success > 0 && ( + + + + )} + + ); } diff --git a/client/src/components/feature-wrapper/feature-wrapper.component.jsx b/client/src/components/feature-wrapper/feature-wrapper.component.jsx index f249bfa37..0b03a7d7b 100644 --- a/client/src/components/feature-wrapper/feature-wrapper.component.jsx +++ b/client/src/components/feature-wrapper/feature-wrapper.component.jsx @@ -1,14 +1,14 @@ -import dayjs from '../../utils/day'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { connect } from 'react-redux'; -import { createStructuredSelector } from 'reselect'; -import { selectBodyshop } from '../../redux/user/user.selectors'; -import AlertComponent from '../alert/alert.component'; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import dayjs from "../../utils/day"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import AlertComponent from "../alert/alert.component"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); function FeatureWrapper({ bodyshop, featureName, noauth, children, ...restProps }) { @@ -19,12 +19,12 @@ function FeatureWrapper({ bodyshop, featureName, noauth, children, ...restProps return ( noauth || ( diff --git a/client/src/components/form-date-picker/form-date-picker.component.jsx b/client/src/components/form-date-picker/form-date-picker.component.jsx index 4e1c50f7f..500e22d45 100644 --- a/client/src/components/form-date-picker/form-date-picker.component.jsx +++ b/client/src/components/form-date-picker/form-date-picker.component.jsx @@ -1,117 +1,123 @@ -import {DatePicker} from "antd"; +import { DatePicker } from "antd"; import dayjs from "../../utils/day"; -import React, {useRef} from "react"; +import React, { useRef } from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(FormDatePicker); const dateFormat = "MM/DD/YYYY"; export function FormDatePicker({ - bodyshop, - value, - onChange, - onBlur, - onlyFuture, - onlyToday, - isDateOnly = true, - ...restProps - }) { - const ref = useRef(); + bodyshop, + value, + onChange, + onBlur, + onlyFuture, + onlyToday, + isDateOnly = true, + ...restProps +}) { + const ref = useRef(); - const handleChange = (newDate) => { - if (value !== newDate && onChange) { - onChange(isDateOnly ? newDate && newDate.format("YYYY-MM-DD") : newDate); + const handleChange = (newDate) => { + if (value !== newDate && onChange) { + onChange(isDateOnly ? newDate && newDate.format("YYYY-MM-DD") : newDate); + } + }; + + const handleKeyDown = (e) => { + if (e.key.toLowerCase() === "t") { + if (onChange) { + onChange(isDateOnly ? dayjs().format("YYYY-MM-DD") : dayjs()); + } + } else if (e.key.toLowerCase() === "enter") { + if (ref.current && ref.current.blur) ref.current.blur(); + } + }; + + const handleBlur = (e) => { + const v = e.target.value; + if (!v) return; + + const formats = [ + "MMDDYY", + "MMDDYYYY", + "MM/DD/YY", + "MM/DD/YYYY", + "M/DD/YY", + "M/DD/YYYY", + "MM/D/YY", + "MM/D/YYYY", + "M/D/YY", + "M/D/YYYY", + "D/MM/YY", + "D/MM/YYYY", + "DD/M/YY", + "DD/M/YYYY", + "D/M/YY", + "D/M/YYYY" + ]; + + let _a; + + // Iterate through formats to find the correct one + for (let format of formats) { + _a = dayjs(v, format); + if (v === _a.format(format)) { + break; + } + } + + if (_a.isValid() && value && value.isValid && value.isValid()) { + _a.set({ + hours: value.hours(), + minutes: value.minutes(), + seconds: value.seconds(), + milliseconds: value.milliseconds() + }); + } + + if (_a.isValid() && onChange) { + if (onlyFuture) { + if (dayjs().subtract(1, "day").isBefore(_a)) { + onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a); + } else { + onChange(isDateOnly ? dayjs().format("YYYY-MM-DD") : dayjs()); } - }; + } else { + onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a); + } + } + }; - const handleKeyDown = (e) => { - if (e.key.toLowerCase() === "t") { - if (onChange) { - onChange(isDateOnly ? dayjs().format("YYYY-MM-DD") : dayjs()); - } - } else if (e.key.toLowerCase() === "enter") { - if (ref.current && ref.current.blur) ref.current.blur(); - } - }; - - const handleBlur = (e) => { - const v = e.target.value; - if (!v) return; - - const formats = [ - "MMDDYY", "MMDDYYYY", "MM/DD/YY", "MM/DD/YYYY", - "M/DD/YY", "M/DD/YYYY", - "MM/D/YY", "MM/D/YYYY", "M/D/YY", "M/D/YYYY", - "D/MM/YY", "D/MM/YYYY", - "DD/M/YY", "DD/M/YYYY", "D/M/YY", "D/M/YYYY" - ]; - - let _a; - - // Iterate through formats to find the correct one - for (let format of formats) { - _a = dayjs(v, format); - if (v === _a.format(format)) { - break; - } - } - - if ( - _a.isValid() - && value - && value.isValid - && value.isValid() - ) { - _a.set({ - hours: value.hours(), - minutes: value.minutes(), - seconds: value.seconds(), - milliseconds: value.milliseconds(), - }); - } - - if (_a.isValid() && onChange) { - if (onlyFuture) { - if (dayjs().subtract(1, "day").isBefore(_a)) { - onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a); - } else { - onChange(isDateOnly ? dayjs().format("YYYY-MM-DD") : dayjs()); - } - } else { - onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a); - } - } - }; - - return ( -
- { - if (onlyToday) { - return !dayjs().isSame(d, 'day'); - } else if (onlyFuture) { - return dayjs().subtract(1, "day").isAfter(d); - } - }} - {...restProps} - /> -
- ); -} \ No newline at end of file + return ( +
+ { + if (onlyToday) { + return !dayjs().isSame(d, "day"); + } else if (onlyFuture) { + return dayjs().subtract(1, "day").isAfter(d); + } + }} + {...restProps} + /> +
+ ); +} diff --git a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx index e5a498ca8..8c11ece9d 100644 --- a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx +++ b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx @@ -1,49 +1,46 @@ -import React, {forwardRef} from "react"; +import React, { forwardRef } from "react"; //import DatePicker from "react-datepicker"; //import "react-datepicker/src/stylesheets/datepicker.scss"; -import {TimePicker} from "antd"; +import { TimePicker } from "antd"; import dayjs from "../../utils/day"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; //To be used as a form element only. -const DateTimePicker = ( - {value, onChange, onBlur, id, onlyFuture, ...restProps}, - ref -) => { - // const handleChange = (newDate) => { - // if (value !== newDate && onChange) { - // onChange(newDate); - // } - // }; +const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, ...restProps }, ref) => { + // const handleChange = (newDate) => { + // if (value !== newDate && onChange) { + // onChange(newDate); + // } + // }; - return ( -
- dayjs().subtract(1, "day").isAfter(d), - })} - value={value} - onBlur={onBlur} - onChange={onChange} - onlyFuture={onlyFuture} - isDateOnly={false} - /> + return ( +
+ dayjs().subtract(1, "day").isAfter(d) + })} + value={value} + onBlur={onBlur} + onChange={onChange} + onlyFuture={onlyFuture} + isDateOnly={false} + /> - dayjs().isAfter(d), - })} - onChange={onChange} - disableSeconds={true} - minuteStep={15} - onBlur={onBlur} - format="hh:mm a" - {...restProps} - /> -
- ); + dayjs().isAfter(d) + })} + onChange={onChange} + disableSeconds={true} + minuteStep={15} + onBlur={onBlur} + format="hh:mm a" + {...restProps} + /> +
+ ); }; export default forwardRef(DateTimePicker); diff --git a/client/src/components/form-fields-changed-alert/form-fields-changed-alert.component.jsx b/client/src/components/form-fields-changed-alert/form-fields-changed-alert.component.jsx index 1d5576720..502d1bbf9 100644 --- a/client/src/components/form-fields-changed-alert/form-fields-changed-alert.component.jsx +++ b/client/src/components/form-fields-changed-alert/form-fields-changed-alert.component.jsx @@ -1,70 +1,56 @@ import React from "react"; -import {Form, Space} from "antd"; -import {useTranslation} from "react-i18next"; +import { Form, Space } from "antd"; +import { useTranslation } from "react-i18next"; import AlertComponent from "../alert/alert.component"; import "./form-fields-changed.styles.scss"; import Prompt from "../../utils/prompt"; -export default function FormsFieldChanged({form, skipPrompt}) { - const {t} = useTranslation(); +export default function FormsFieldChanged({ form, skipPrompt }) { + const { t } = useTranslation(); - const handleReset = () => { - form.resetFields(); - }; - //if (!form.isFieldsTouched()) return <>; - return ( - - {() => { - const errors = form.getFieldsError().filter((e) => e.errors.length > 0); - if (form.isFieldsTouched()) - return ( - - - - {t("general.messages.unsavedchanges")} - + const handleReset = () => { + form.resetFields(); + }; + //if (!form.isFieldsTouched()) return <>; + return ( + + {() => { + const errors = form.getFieldsError().filter((e) => e.errors.length > 0); + if (form.isFieldsTouched()) + return ( + + + + {t("general.messages.unsavedchanges")} + {t("general.actions.reset")} - - } - /> - {errors.length > 0 && ( - -
    - {errors.map((e, idx) => - e.errors.map((e2, idx2) => ( -
  • {e2}
  • - )) - )} -
- - } - /> - )} -
- ); - return
; - }} -
- ); + + } + /> + {errors.length > 0 && ( + +
    {errors.map((e, idx) => e.errors.map((e2, idx2) =>
  • {e2}
  • ))}
+ + } + /> + )} +
+ ); + return
; + }} +
+ ); } diff --git a/client/src/components/form-input-number-calculator/form-input-number-calculator.component.jsx b/client/src/components/form-input-number-calculator/form-input-number-calculator.component.jsx index 9f73becaf..5040fd909 100644 --- a/client/src/components/form-input-number-calculator/form-input-number-calculator.component.jsx +++ b/client/src/components/form-input-number-calculator/form-input-number-calculator.component.jsx @@ -1,111 +1,108 @@ -import {InputNumber, Popover} from "antd"; -import React, {forwardRef, useEffect, useRef, useState} from "react"; +import { InputNumber, Popover } from "antd"; +import React, { forwardRef, useEffect, useRef, useState } from "react"; -const FormInputNUmberCalculator = ( - {value: formValue, onChange: formOnChange, ...restProps}, - refProp -) => { - const [value, setValue] = useState(formValue); - const [total, setTotal] = useState(0); - const [history, setHistory] = useState([]); +const FormInputNUmberCalculator = ({ value: formValue, onChange: formOnChange, ...restProps }, refProp) => { + const [value, setValue] = useState(formValue); + const [total, setTotal] = useState(0); + const [history, setHistory] = useState([]); - const ref = useRef(null); + const ref = useRef(null); - const handleKeyDown = (e) => { - const {key} = e; - let action; - switch (key) { - case "/": - case "*": - case "+": - case "-": - action = key; - break; - case "Enter": - action = "="; - break; - default: - setValue(e.currentTarget.value); - return; + const handleKeyDown = (e) => { + const { key } = e; + let action; + switch (key) { + case "/": + case "*": + case "+": + case "-": + action = key; + break; + case "Enter": + action = "="; + break; + default: + setValue(e.currentTarget.value); + return; + } + const val = parseFloat(value); + setValue(null); + ref.current.blur(); + ref.current.focus(); + if (!isNaN(val)) { + setHistory([...history, val, action]); + } + }; + + useEffect(() => { + if (value !== formValue && formOnChange) formOnChange(value); + }, [formOnChange, formValue, value]); + + useEffect(() => { + if (history.length > 2) { + const subTotal = history.reduce((acc, val, idx) => { + if (idx === 0) { + return val; } - const val = parseFloat(value); - setValue(null); - ref.current.blur(); - ref.current.focus(); - if (!isNaN(val)) { - setHistory([...history, val, action]); - } - }; + switch (val) { + case "/": + case "*": + case "+": + case "-": + return acc; - useEffect(() => { - if (value !== formValue && formOnChange) formOnChange(value); - }, [formOnChange, formValue, value]); - - useEffect(() => { - if (history.length > 2) { - const subTotal = history.reduce((acc, val, idx) => { - if (idx === 0) { - return val; - } - switch (val) { - case "/": - case "*": - case "+": - case "-": - return acc; - - default: - //Weve got math on our hands. Find the last operator, and apply it accordingly. - switch (history[idx - 1]) { - case "/": - return acc / val; - case "*": - return acc * val; - case "+": - return acc + val; - case "-": - return acc - val; - default: - return acc; - } - } - }, 0); - setTotal(subTotal); - if (history[history.length - 1] === "=") { - setValue(subTotal); - ref.current.blur(); - ref.current.focus(); - setHistory([]); + default: + //Weve got math on our hands. Find the last operator, and apply it accordingly. + switch (history[idx - 1]) { + case "/": + return acc / val; + case "*": + return acc * val; + case "+": + return acc + val; + case "-": + return acc - val; + default: + return acc; } } - }, [history]); + }, 0); + setTotal(subTotal); + if (history[history.length - 1] === "=") { + setValue(subTotal); + ref.current.blur(); + ref.current.focus(); + setHistory([]); + } + } + }, [history]); - const popContent = ( -
- History - {history.map((h, idx) => ( -
- {h} -
- ))} -
{total}
+ const popContent = ( +
+ History + {history.map((h, idx) => ( +
+ {h}
- ); + ))} +
{total}
+
+ ); - return ( -
- 0}> - setHistory([])} - {...restProps} - /> - -
- ); + return ( +
+ 0}> + setHistory([])} + {...restProps} + /> + +
+ ); }; export default forwardRef(FormInputNUmberCalculator); diff --git a/client/src/components/form-items-formatted/colorpicker-form-item.component.jsx b/client/src/components/form-items-formatted/colorpicker-form-item.component.jsx index cd7c1bc16..10734e291 100644 --- a/client/src/components/form-items-formatted/colorpicker-form-item.component.jsx +++ b/client/src/components/form-items-formatted/colorpicker-form-item.component.jsx @@ -1,20 +1,20 @@ import React from "react"; -import {SliderPicker} from "react-color"; +import { SliderPicker } from "react-color"; //To be used as a form element only. -const ColorPickerFormItem = ({value, onChange, style, ...restProps}) => { - const handleChangeComplete = (color) => { - if (onChange) onChange(color); - }; +const ColorPickerFormItem = ({ value, onChange, style, ...restProps }) => { + const handleChangeComplete = (color) => { + if (onChange) onChange(color); + }; - return ( - - ); + return ( + + ); }; export default ColorPickerFormItem; diff --git a/client/src/components/form-items-formatted/currency-form-item.component.jsx b/client/src/components/form-items-formatted/currency-form-item.component.jsx index 3e45c2ff1..8d142372d 100644 --- a/client/src/components/form-items-formatted/currency-form-item.component.jsx +++ b/client/src/components/form-items-formatted/currency-form-item.component.jsx @@ -1,5 +1,5 @@ -import {InputNumber} from "antd"; -import React, {forwardRef} from "react"; +import { InputNumber } from "antd"; +import React, { forwardRef } from "react"; // const locale = "en-us"; // const currencyFormatter = (value) => { @@ -42,16 +42,16 @@ import React, {forwardRef} from "react"; // }; function FormItemCurrency(props, ref) { - return ( - `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",")} - // parser={(value) => value.replace(/\$\s?|(,*)/g, "")} - precision={2} - /> - ); + return ( + `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",")} + // parser={(value) => value.replace(/\$\s?|(,*)/g, "")} + precision={2} + /> + ); } export default forwardRef(FormItemCurrency); diff --git a/client/src/components/form-items-formatted/email-form-item.component.jsx b/client/src/components/form-items-formatted/email-form-item.component.jsx index 5d5db588e..c2a018d26 100644 --- a/client/src/components/form-items-formatted/email-form-item.component.jsx +++ b/client/src/components/form-items-formatted/email-form-item.component.jsx @@ -1,23 +1,23 @@ -import {MailFilled} from "@ant-design/icons"; -import {Input} from "antd"; -import React, {forwardRef} from "react"; +import { MailFilled } from "@ant-design/icons"; +import { Input } from "antd"; +import React, { forwardRef } from "react"; function FormItemEmail(props, ref) { - return ( - - - - ) : ( - - ) - } - /> - ); + return ( + + + + ) : ( + + ) + } + /> + ); } export default forwardRef(FormItemEmail); diff --git a/client/src/components/form-items-formatted/labor-type-form-item.component.jsx b/client/src/components/form-items-formatted/labor-type-form-item.component.jsx index 6066a1133..f1f4d8265 100644 --- a/client/src/components/form-items-formatted/labor-type-form-item.component.jsx +++ b/client/src/components/form-items-formatted/labor-type-form-item.component.jsx @@ -1,11 +1,11 @@ -import React, {forwardRef} from "react"; -import {useTranslation} from "react-i18next"; +import React, { forwardRef } from "react"; +import { useTranslation } from "react-i18next"; -const LaborTypeFormItem = ({value, onChange}, ref) => { - const {t} = useTranslation(); +const LaborTypeFormItem = ({ value, onChange }, ref) => { + const { t } = useTranslation(); - if (!value) return null; + if (!value) return null; - return
{t(`joblines.fields.lbr_types.${value}`)}
; + return
{t(`joblines.fields.lbr_types.${value}`)}
; }; export default forwardRef(LaborTypeFormItem); diff --git a/client/src/components/form-items-formatted/part-type-form-item.component.jsx b/client/src/components/form-items-formatted/part-type-form-item.component.jsx index 8aea0d9aa..3226483ca 100644 --- a/client/src/components/form-items-formatted/part-type-form-item.component.jsx +++ b/client/src/components/form-items-formatted/part-type-form-item.component.jsx @@ -1,11 +1,11 @@ -import React, {forwardRef} from "react"; -import {useTranslation} from "react-i18next"; +import React, { forwardRef } from "react"; +import { useTranslation } from "react-i18next"; -const PartTypeFormItem = ({value, onChange}, ref) => { - const {t} = useTranslation(); +const PartTypeFormItem = ({ value, onChange }, ref) => { + const { t } = useTranslation(); - if (!value) return null; + if (!value) return null; - return
{t(`joblines.fields.part_types.${value}`)}
; + return
{t(`joblines.fields.part_types.${value}`)}
; }; export default forwardRef(PartTypeFormItem); diff --git a/client/src/components/form-items-formatted/phone-form-item.component.jsx b/client/src/components/form-items-formatted/phone-form-item.component.jsx index b8f4d38b1..86c0179b6 100644 --- a/client/src/components/form-items-formatted/phone-form-item.component.jsx +++ b/client/src/components/form-items-formatted/phone-form-item.component.jsx @@ -1,31 +1,31 @@ -import {Input} from "antd"; +import { Input } from "antd"; import i18n from "i18next"; import parsePhoneNumber from "libphonenumber-js"; -import React, {forwardRef} from "react"; +import React, { forwardRef } from "react"; import "./phone-form-item.styles.scss"; function FormItemPhone(props, ref) { - return ( - - ); + return ( + + ); } export default forwardRef(FormItemPhone); export const PhoneItemFormatterValidation = (getFieldValue, name) => ({ - async validator(rule, value) { - if (!value) { - return Promise.resolve(); - } else { - const p = parsePhoneNumber(value, "CA"); - if (p && p.isValid()) { - return Promise.resolve(); - } else { - return Promise.reject(i18n.t("general.validation.invalidphone")); - } - } - }, + async validator(rule, value) { + if (!value) { + return Promise.resolve(); + } else { + const p = parsePhoneNumber(value, "CA"); + if (p && p.isValid()) { + return Promise.resolve(); + } else { + return Promise.reject(i18n.t("general.validation.invalidphone")); + } + } + } }); diff --git a/client/src/components/form-items-formatted/read-only-form-item.component.jsx b/client/src/components/form-items-formatted/read-only-form-item.component.jsx index e606fb3aa..09c6ef6e9 100644 --- a/client/src/components/form-items-formatted/read-only-form-item.component.jsx +++ b/client/src/components/form-items-formatted/read-only-form-item.component.jsx @@ -1,39 +1,31 @@ import Dinero from "dinero.js"; -import React, {forwardRef} from "react"; +import React, { forwardRef } from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -const ReadOnlyFormItem = ( - {bodyshop, value, type = "text", onChange}, - ref -) => { - if (!value) return null; - switch (type) { - case "employee": - const emp = bodyshop.employees.find((e) => e.id === value); - return `${emp?.first_name} ${emp?.last_name}`; +const ReadOnlyFormItem = ({ bodyshop, value, type = "text", onChange }, ref) => { + if (!value) return null; + switch (type) { + case "employee": + const emp = bodyshop.employees.find((e) => e.id === value); + return `${emp?.first_name} ${emp?.last_name}`; - case "text": - return
{value}
; - case "currency": - return ( -
{Dinero({amount: Math.round(value * 100)}).toFormat()}
- ); - default: - return
{value}
; - } + case "text": + return
{value}
; + case "currency": + return
{Dinero({ amount: Math.round(value * 100) }).toFormat()}
; + default: + return
{value}
; + } }; -export default connect( - mapStateToProps, - mapDispatchToProps -)(forwardRef(ReadOnlyFormItem)); +export default connect(mapStateToProps, mapDispatchToProps)(forwardRef(ReadOnlyFormItem)); diff --git a/client/src/components/form-list-move-arrows/form-list-move-arrows.component.jsx b/client/src/components/form-list-move-arrows/form-list-move-arrows.component.jsx index 44a5980c3..cd3c4fe5a 100644 --- a/client/src/components/form-list-move-arrows/form-list-move-arrows.component.jsx +++ b/client/src/components/form-list-move-arrows/form-list-move-arrows.component.jsx @@ -1,23 +1,23 @@ -import {DownOutlined, UpOutlined} from "@ant-design/icons"; -import {Space} from "antd"; +import { DownOutlined, UpOutlined } from "@ant-design/icons"; +import { Space } from "antd"; import React from "react"; -export default function FormListMoveArrows({move, index, total}) { - const upDisabled = index === 0; - const downDisabled = index === total - 1; +export default function FormListMoveArrows({ move, index, total }) { + const upDisabled = index === 0; + const downDisabled = index === total - 1; - const handleUp = () => { - move(index, index - 1); - }; + const handleUp = () => { + move(index, index - 1); + }; - const handleDown = () => { - move(index, index + 1); - }; + const handleDown = () => { + move(index, index + 1); + }; - return ( - - - - - ); + return ( + + + + + ); } diff --git a/client/src/components/global-loading-bar/global-loading-bar.component.jsx b/client/src/components/global-loading-bar/global-loading-bar.component.jsx index d59535512..833cae896 100644 --- a/client/src/components/global-loading-bar/global-loading-bar.component.jsx +++ b/client/src/components/global-loading-bar/global-loading-bar.component.jsx @@ -1,54 +1,54 @@ //import { useNProgress } from "@tanem/react-nprogress"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectLoading} from "../../redux/application/application.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectLoading } from "../../redux/application/application.selectors"; const mapStateToProps = createStructuredSelector({ - loading: selectLoading, + loading: selectLoading }); export default connect(mapStateToProps, null)(GlobalLoadingHeader); -function GlobalLoadingHeader({loading}) { - return ; - // const { animationDuration, isFinished, progress } = useNProgress({ - // isAnimating: loading, - // }); - // return ( - //
- //
- //
- //
- //
- // ); +function GlobalLoadingHeader({ loading }) { + return ; + // const { animationDuration, isFinished, progress } = useNProgress({ + // isAnimating: loading, + // }); + // return ( + //
+ //
+ //
+ //
+ //
+ // ); } diff --git a/client/src/components/global-search/global-search-os.component.jsx b/client/src/components/global-search/global-search-os.component.jsx index 7174b4f94..62e44da5f 100644 --- a/client/src/components/global-search/global-search-os.component.jsx +++ b/client/src/components/global-search/global-search-os.component.jsx @@ -1,215 +1,199 @@ -import {AutoComplete, Divider, Input, Space} from "antd"; +import { AutoComplete, Divider, Input, Space } from "antd"; import axios from "axios"; import _ from "lodash"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {Link, useNavigate} from "react-router-dom"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link, useNavigate } from "react-router-dom"; import PhoneNumberFormatter from "../../utils/PhoneFormatter"; -import OwnerNameDisplay, {OwnerNameDisplayFunction,} from "../owner-name-display/owner-name-display.component"; +import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; export default function GlobalSearchOs() { - const {t} = useTranslation(); - const history = useNavigate(); - const [loading, setLoading] = useState(false); - const [data, setData] = useState(false); + const { t } = useTranslation(); + const history = useNavigate(); + const [loading, setLoading] = useState(false); + const [data, setData] = useState(false); - const executeSearch = async (v) => { - if (v && v && v !== "" && v.length >= 3) { - try { - setLoading(true); - const searchData = await axios.post("/search", { - search: v, - }); + const executeSearch = async (v) => { + if (v && v && v !== "" && v.length >= 3) { + try { + setLoading(true); + const searchData = await axios.post("/search", { + search: v + }); - const resultsByType = { - payments: [], - jobs: [], - bills: [], - owners: [], - vehicles: [], - }; + const resultsByType = { + payments: [], + jobs: [], + bills: [], + owners: [], + vehicles: [] + }; - searchData.data.hits.hits.forEach((hit) => { - resultsByType[hit._index].push(hit._source); - }); - setData([ - { - label: renderTitle(t("menus.header.search.jobs")), - options: resultsByType.jobs.map((job) => { - return { - key: job.id, - value: job.ro_number || "N/A", - label: ( - - }> - {job.ro_number || t("general.labels.na")} - {`${job.status || ""}`} - - - - {`${job.v_model_yr || ""} ${ - job.v_make_desc || "" - } ${job.v_model_desc || ""}`} - {`${job.clm_no || ""}`} - {`${job.plate_no || ""}`} - - - ), - }; - }), - }, - { - label: renderTitle(t("menus.header.search.owners")), - options: resultsByType.owners.map((owner) => { - return { - key: owner.id, - value: OwnerNameDisplayFunction(owner), - label: ( - - } - wrap - > + searchData.data.hits.hits.forEach((hit) => { + resultsByType[hit._index].push(hit._source); + }); + setData([ + { + label: renderTitle(t("menus.header.search.jobs")), + options: resultsByType.jobs.map((job) => { + return { + key: job.id, + value: job.ro_number || "N/A", + label: ( + + }> + {job.ro_number || t("general.labels.na")} + {`${job.status || ""}`} - + - - {owner.ownr_ph1} - - - {owner.ownr_ph2} - - - - ), - }; - }), - }, - { - label: renderTitle(t("menus.header.search.vehicles")), - options: resultsByType.vehicles.map((vehicle) => { - return { - key: vehicle.id, - value: `${vehicle.v_model_yr || ""} ${ - vehicle.v_make_desc || "" - } ${vehicle.v_model_desc || ""}`, - label: ( - - }> + {`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`} + {`${job.clm_no || ""}`} + {`${job.plate_no || ""}`} + + + ) + }; + }) + }, + { + label: renderTitle(t("menus.header.search.owners")), + options: resultsByType.owners.map((owner) => { + return { + key: owner.id, + value: OwnerNameDisplayFunction(owner), + label: ( + + } wrap> - {`${vehicle.v_model_yr || ""} ${ - vehicle.v_make_desc || "" - } ${vehicle.v_model_desc || ""}`} + - {vehicle.plate_no || ""} - - - {vehicle.v_vin || ""} - + {owner.ownr_ph1} + {owner.ownr_ph2} + + + ) + }; + }) + }, + { + label: renderTitle(t("menus.header.search.vehicles")), + options: resultsByType.vehicles.map((vehicle) => { + return { + key: vehicle.id, + value: `${vehicle.v_model_yr || ""} ${vehicle.v_make_desc || ""} ${vehicle.v_model_desc || ""}`, + label: ( + + }> + + {`${vehicle.v_model_yr || ""} ${vehicle.v_make_desc || ""} ${vehicle.v_model_desc || ""}`} - - - ), - }; - }), - }, - { - label: renderTitle(t("menus.header.search.payments")), - options: resultsByType.payments.map((payment) => { - return { - key: payment.id, - value: `${payment.job?.ro_number} ${payment.amount}`, - label: ( - - }> - {payment.paymentnum} - {payment.job?.ro_number} - {payment.memo || ""} - {payment.amount || ""} - {payment.transactionid || ""} - - - ), - }; - }), - }, - { - label: renderTitle(t("menus.header.search.bills")), - options: resultsByType.bills.map((bill) => { - return { - key: bill.id, - value: `${bill.invoice_number} - ${bill.vendor.name}`, - label: ( - - }> - {bill.invoice_number} - {bill.vendor.name} - {bill.date} - - - ), - }; - }), - }, - // { - // label: renderTitle(t("menus.header.search.phonebook")), - // options: resultsByType.search_phonebook.map((pb) => { - // return { - // key: pb.id, - // value: `${pb.firstname || ""} ${pb.lastname || ""} ${ - // pb.company || "" - // }`, - // label: ( - // - // }> - // {`${pb.firstname || ""} ${pb.lastname || ""} ${ - // pb.company || "" - // }`} - // {pb.phone1} - // {pb.email} - // - // - // ), - // }; - // }), - // }, - ]); - } catch (error) { - console.log("Error while fetching search results", error); - } finally { - setLoading(false); - } - } - }; - const debouncedExecuteSearch = _.debounce(executeSearch, 750); + {vehicle.plate_no || ""} + + {vehicle.v_vin || ""} + + + + ) + }; + }) + }, + { + label: renderTitle(t("menus.header.search.payments")), + options: resultsByType.payments.map((payment) => { + return { + key: payment.id, + value: `${payment.job?.ro_number} ${payment.amount}`, + label: ( + + }> + {payment.paymentnum} + {payment.job?.ro_number} + {payment.memo || ""} + {payment.amount || ""} + {payment.transactionid || ""} + + + ) + }; + }) + }, + { + label: renderTitle(t("menus.header.search.bills")), + options: resultsByType.bills.map((bill) => { + return { + key: bill.id, + value: `${bill.invoice_number} - ${bill.vendor.name}`, + label: ( + + }> + {bill.invoice_number} + {bill.vendor.name} + {bill.date} + + + ) + }; + }) + } + // { + // label: renderTitle(t("menus.header.search.phonebook")), + // options: resultsByType.search_phonebook.map((pb) => { + // return { + // key: pb.id, + // value: `${pb.firstname || ""} ${pb.lastname || ""} ${ + // pb.company || "" + // }`, + // label: ( + // + // }> + // {`${pb.firstname || ""} ${pb.lastname || ""} ${ + // pb.company || "" + // }`} + // {pb.phone1} + // {pb.email} + // + // + // ), + // }; + // }), + // }, + ]); + } catch (error) { + console.log("Error while fetching search results", error); + } finally { + setLoading(false); + } + } + }; + const debouncedExecuteSearch = _.debounce(executeSearch, 750); - const handleSearch = (value) => { - debouncedExecuteSearch(value); - }; + const handleSearch = (value) => { + debouncedExecuteSearch(value); + }; - const renderTitle = (title) => { - return {title}; - }; + const renderTitle = (title) => { + return {title}; + }; - return ( - { - history(opt.label.props.to); - }} - onClear={() => setData([])} - > - - - ); + return ( + { + history(opt.label.props.to); + }} + onClear={() => setData([])} + > + + + ); } diff --git a/client/src/components/global-search/global-search.component.jsx b/client/src/components/global-search/global-search.component.jsx index 036463522..9bb0da845 100644 --- a/client/src/components/global-search/global-search.component.jsx +++ b/client/src/components/global-search/global-search.component.jsx @@ -1,200 +1,177 @@ -import {useLazyQuery} from "@apollo/client"; -import {AutoComplete, Divider, Input, Space} from "antd"; +import { useLazyQuery } from "@apollo/client"; +import { AutoComplete, Divider, Input, Space } from "antd"; import _ from "lodash"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {Link, useNavigate} from "react-router-dom"; -import {GLOBAL_SEARCH_QUERY} from "../../graphql/search.queries"; +import { useTranslation } from "react-i18next"; +import { Link, useNavigate } from "react-router-dom"; +import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries"; import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import AlertComponent from "../alert/alert.component"; -import OwnerNameDisplay, {OwnerNameDisplayFunction,} from "../owner-name-display/owner-name-display.component"; +import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; export default function GlobalSearch() { - const {t} = useTranslation(); - const history = useNavigate(); - const [callSearch, {loading, error, data}] = - useLazyQuery(GLOBAL_SEARCH_QUERY); + const { t } = useTranslation(); + const history = useNavigate(); + const [callSearch, { loading, error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY); - const executeSearch = (v) => { - if ( - v && - v.variables.search && - v.variables.search !== "" && - v.variables.search.length >= 3 - ) - callSearch(v); - }; - const debouncedExecuteSearch = _.debounce(executeSearch, 750); + const executeSearch = (v) => { + if (v && v.variables.search && v.variables.search !== "" && v.variables.search.length >= 3) callSearch(v); + }; + const debouncedExecuteSearch = _.debounce(executeSearch, 750); - const handleSearch = (value) => { - console.log("Handle Search"); - debouncedExecuteSearch({variables: {search: value}}); - }; + const handleSearch = (value) => { + console.log("Handle Search"); + debouncedExecuteSearch({ variables: { search: value } }); + }; - const renderTitle = (title) => { - return {title}; - }; + const renderTitle = (title) => { + return {title}; + }; - const options = data - ? [ - { - label: renderTitle(t("menus.header.search.jobs")), - options: data.search_jobs.map((job) => { - return { - key: job.id, - value: job.ro_number || "N/A", - label: ( - - }> - {job.ro_number || t("general.labels.na")} - {`${job.status || ""}`} - - - - {`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${ - job.v_model_desc || "" - }`} - {`${job.clm_no || ""}`} - - - ), - }; - }), - }, - { - label: renderTitle(t("menus.header.search.owners")), - options: data.search_owners.map((owner) => { - return { - key: owner.id, - value: OwnerNameDisplayFunction(owner), - label: ( - - } wrap> + const options = data + ? [ + { + label: renderTitle(t("menus.header.search.jobs")), + options: data.search_jobs.map((job) => { + return { + key: job.id, + value: job.ro_number || "N/A", + label: ( + + }> + {job.ro_number || t("general.labels.na")} + {`${job.status || ""}`} - + - - {owner.ownr_ph1} - - - {owner.ownr_ph2} - - - - ), - }; - }), - }, - { - label: renderTitle(t("menus.header.search.vehicles")), - options: data.search_vehicles.map((vehicle) => { - return { - key: vehicle.id, - value: `${vehicle.v_model_yr || ""} ${ - vehicle.v_make_desc || "" - } ${vehicle.v_model_desc || ""}`, - label: ( - - }> + {`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`} + {`${job.clm_no || ""}`} + + + ) + }; + }) + }, + { + label: renderTitle(t("menus.header.search.owners")), + options: data.search_owners.map((owner) => { + return { + key: owner.id, + value: OwnerNameDisplayFunction(owner), + label: ( + + } wrap> - {`${vehicle.v_model_yr || ""} ${ - vehicle.v_make_desc || "" - } ${vehicle.v_model_desc || ""}`} + - {vehicle.plate_no || ""} - - - {vehicle.v_vin || ""} - + {owner.ownr_ph1} + {owner.ownr_ph2} + + + ) + }; + }) + }, + { + label: renderTitle(t("menus.header.search.vehicles")), + options: data.search_vehicles.map((vehicle) => { + return { + key: vehicle.id, + value: `${vehicle.v_model_yr || ""} ${vehicle.v_make_desc || ""} ${vehicle.v_model_desc || ""}`, + label: ( + + }> + + {`${vehicle.v_model_yr || ""} ${vehicle.v_make_desc || ""} ${vehicle.v_model_desc || ""}`} - - - ), - }; - }), - }, - { - label: renderTitle(t("menus.header.search.payments")), - options: data.search_payments.map((payment) => { - return { - key: payment.id, - value: `${payment.job.ro_number} ${payment.payer} ${payment.amount}`, - label: ( - - }> - {payment.paymentnum} - {payment.job.ro_number} - {payment.memo || ""} - {payment.amount || ""} - {payment.transactionid || ""} - - - ), - }; - }), - }, - { - label: renderTitle(t("menus.header.search.bills")), - options: data.search_bills.map((bill) => { - return { - key: bill.id, - value: `${bill.invoice_number} - ${bill.vendor.name}`, - label: ( - - }> - {bill.invoice_number} - {bill.vendor.name} - {bill.date} - - - ), - }; - }), - }, - { - label: renderTitle(t("menus.header.search.phonebook")), - options: data.search_phonebook.map((pb) => { - return { - key: pb.id, - value: `${pb.firstname || ""} ${pb.lastname || ""} ${ - pb.company || "" - }`, - label: ( - - }> - {`${pb.firstname || ""} ${pb.lastname || ""} ${ - pb.company || "" - }`} - {pb.phone1} - {pb.email} - - - ), - }; - }), - }, - ] - : []; + {vehicle.plate_no || ""} + + {vehicle.v_vin || ""} + + + + ) + }; + }) + }, + { + label: renderTitle(t("menus.header.search.payments")), + options: data.search_payments.map((payment) => { + return { + key: payment.id, + value: `${payment.job.ro_number} ${payment.payer} ${payment.amount}`, + label: ( + + }> + {payment.paymentnum} + {payment.job.ro_number} + {payment.memo || ""} + {payment.amount || ""} + {payment.transactionid || ""} + + + ) + }; + }) + }, + { + label: renderTitle(t("menus.header.search.bills")), + options: data.search_bills.map((bill) => { + return { + key: bill.id, + value: `${bill.invoice_number} - ${bill.vendor.name}`, + label: ( + + }> + {bill.invoice_number} + {bill.vendor.name} + {bill.date} + + + ) + }; + }) + }, + { + label: renderTitle(t("menus.header.search.phonebook")), + options: data.search_phonebook.map((pb) => { + return { + key: pb.id, + value: `${pb.firstname || ""} ${pb.lastname || ""} ${pb.company || ""}`, + label: ( + + }> + {`${pb.firstname || ""} ${pb.lastname || ""} ${pb.company || ""}`} + {pb.phone1} + {pb.email} + + + ) + }; + }) + } + ] + : []; - if (error) return ; + if (error) return ; - return ( - { - history(opt.label.props.to); - }} - > - - - ); + return ( + { + history(opt.label.props.to); + }} + > + + + ); } diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 59acdd8c4..d0683154e 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -23,50 +23,43 @@ import Icon, { TeamOutlined, ToolFilled, UnorderedListOutlined, - UserOutlined, -} from '@ant-design/icons'; -import { useSplitTreatments } from '@splitsoftware/splitio-react'; -import { Layout, Menu, Switch, Tooltip } from 'antd'; -import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { BsKanban } from 'react-icons/bs'; -import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar } from 'react-icons/fa'; -import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from 'react-icons/gi'; -import { IoBusinessOutline } from 'react-icons/io5'; -import { RiSurveyLine } from 'react-icons/ri'; -import { connect } from 'react-redux'; -import { Link } from 'react-router-dom'; -import { createStructuredSelector } from 'reselect'; -import { - selectRecentItems, - selectSelectedHeader, -} from '../../redux/application/application.selectors'; -import { setModalContext } from '../../redux/modals/modals.actions'; -import { signOutStart } from '../../redux/user/user.actions'; -import { selectBodyshop, selectCurrentUser } from '../../redux/user/user.selectors'; -import { FiLogOut } from 'react-icons/fi'; -import { checkBeta, handleBeta, setBeta } from '../../utils/betaHandler'; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; -import { HasFeatureAccess } from '../feature-wrapper/feature-wrapper.component'; + UserOutlined +} from "@ant-design/icons"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { Layout, Menu, Switch, Tooltip } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { BsKanban } from "react-icons/bs"; +import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar } from "react-icons/fa"; +import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi"; +import { IoBusinessOutline } from "react-icons/io5"; +import { RiSurveyLine } from "react-icons/ri"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { signOutStart } from "../../redux/user/user.actions"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import { FiLogOut } from "react-icons/fi"; +import { checkBeta, handleBeta, setBeta } from "../../utils/betaHandler"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; +import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, recentItems: selectRecentItems, selectedHeader: selectSelectedHeader, - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBillEnterContext: (context) => - dispatch(setModalContext({ context: context, modal: 'billEnter' })), - setTimeTicketContext: (context) => - dispatch(setModalContext({ context: context, modal: 'timeTicket' })), - setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: 'payment' })), - setReportCenterContext: (context) => - dispatch(setModalContext({ context: context, modal: 'reportCenter' })), + setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })), + setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })), + setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })), + setReportCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "reportCenter" })), signOutStart: () => dispatch(signOutStart()), - setCardPaymentContext: (context) => - dispatch(setModalContext({ context: context, modal: 'cardPayment' })), + setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" })) }); function Header({ @@ -80,14 +73,14 @@ function Header({ setPaymentContext, setReportCenterContext, recentItems, - setCardPaymentContext, + setCardPaymentContext }) { const { - treatments: { ImEXPay, DmsAp, Simple_Inventory }, + treatments: { ImEXPay, DmsAp, Simple_Inventory } } = useSplitTreatments({ attributes: {}, - names: ['ImEXPay', 'DmsAp', 'Simple_Inventory'], - splitKey: bodyshop && bodyshop.imexshopid, + names: ["ImEXPay", "DmsAp", "Simple_Inventory"], + splitKey: bodyshop && bodyshop.imexshopid }); const [betaSwitch, setBetaSwitch] = useState(false); const { t } = useTranslation(); @@ -109,38 +102,38 @@ function Header({ InstanceRenderManager({ imex: true, rome: true, - promanager: HasFeatureAccess({ featureName: 'bills', bodyshop }), + promanager: HasFeatureAccess({ featureName: "bills", bodyshop }) }) ) { accountingChildren.push( { - key: 'bills', + key: "bills", icon: , - label: {t('menus.header.bills')}, + label: {t("menus.header.bills")} }, { - key: 'enterbills', + key: "enterbills", icon: , - label: t('menus.header.enterbills'), + label: t("menus.header.enterbills"), onClick: () => { setBillEnterContext({ actions: {}, - context: {}, + context: {} }); - }, + } } ); } - if (Simple_Inventory.treatment === 'on') { + if (Simple_Inventory.treatment === "on") { accountingChildren.push( { - type: 'divider', + type: "divider" }, { - key: 'inventory', + key: "inventory", icon: , - label: {t('menus.header.inventory')}, + label: {t("menus.header.inventory")} } ); } @@ -148,43 +141,43 @@ function Header({ InstanceRenderManager({ imex: true, rome: true, - promanager: HasFeatureAccess({ featureName: 'payments', bodyshop }), + promanager: HasFeatureAccess({ featureName: "payments", bodyshop }) }) ) { accountingChildren.push( { - type: 'divider', + type: "divider" }, { - key: 'allpayments', + key: "allpayments", icon: , - label: {t('menus.header.allpayments')}, + label: {t("menus.header.allpayments")} }, { - key: 'enterpayments', + key: "enterpayments", icon: , - label: t('menus.header.enterpayment'), + label: t("menus.header.enterpayment"), onClick: () => { setPaymentContext({ actions: {}, - context: null, + context: null }); - }, + } } ); } - if (ImEXPay.treatment === 'on') { + if (ImEXPay.treatment === "on") { accountingChildren.push({ - key: 'entercardpayments', + key: "entercardpayments", icon: , - label: t('menus.header.entercardpayment'), + label: t("menus.header.entercardpayment"), onClick: () => { setCardPaymentContext({ actions: {}, - context: {}, + context: {} }); - }, + } }); } @@ -192,82 +185,77 @@ function Header({ InstanceRenderManager({ imex: true, rome: true, - promanager: HasFeatureAccess({ featureName: 'timetickets', bodyshop }), + promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop }) }) ) { accountingChildren.push( { - type: 'divider', + type: "divider" }, { - key: 'timetickets', + key: "timetickets", icon: , - label: {t('menus.header.timetickets')}, + label: {t("menus.header.timetickets")} } ); if (bodyshop?.md_tasks_presets?.use_approvals) { accountingChildren.push({ - key: 'ttapprovals', + key: "ttapprovals", icon: , - label: {t('menus.header.ttapprovals')}, + label: {t("menus.header.ttapprovals")} }); } accountingChildren.push( { - key: 'entertimetickets', + key: "entertimetickets", icon: , - label: t('menus.header.entertimeticket'), + label: t("menus.header.entertimeticket"), onClick: () => { setTimeTicketContext({ actions: {}, context: { created_by: currentUser.displayName - ? currentUser.email.concat(' | ', currentUser.displayName) - : currentUser.email, - }, + ? currentUser.email.concat(" | ", currentUser.displayName) + : currentUser.email + } }); - }, + } }, { - type: 'divider', + type: "divider" } ); } const accountingExportChildren = [ { - key: 'receivables', - label: ( - {t('menus.header.accounting-receivables')} - ), - }, + key: "receivables", + label: {t("menus.header.accounting-receivables")} + } ]; - if ( - !((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) || - DmsAp.treatment === 'on' - ) { + if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) || DmsAp.treatment === "on") { accountingExportChildren.push({ - key: 'payables', - label: {t('menus.header.accounting-payables')}, + key: "payables", + label: {t("menus.header.accounting-payables")} }); } if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber))) { accountingExportChildren.push({ - key: 'payments', - label: {t('menus.header.accounting-payments')}, + key: "payments", + label: {t("menus.header.accounting-payments")} }); } accountingExportChildren.push( { - type: 'divider', + type: "divider" }, { - key: 'exportlogs', - label: {t('menus.header.export-logs')}, + key: "exportlogs", + label: {t("menus.header.export-logs")} } ); @@ -275,266 +263,256 @@ function Header({ InstanceRenderManager({ imex: true, rome: true, - promanager: HasFeatureAccess({ featureName: 'export', bodyshop }), + promanager: HasFeatureAccess({ featureName: "export", bodyshop }) }) ) { accountingChildren.push({ - key: 'accountingexport', + key: "accountingexport", icon: , - label: t('menus.header.export'), - children: accountingExportChildren, + label: t("menus.header.export"), + children: accountingExportChildren }); } const menuItems = [ { - key: 'home', + key: "home", icon: , - label: {t('menus.header.home')}, + label: {t("menus.header.home")} }, { - key: 'schedule', + key: "schedule", icon: , - label: {t('menus.header.schedule')}, + label: {t("menus.header.schedule")} }, { - key: 'jobssubmenu', - id: 'header-jobs', + key: "jobssubmenu", + id: "header-jobs", icon: , - label: t('menus.header.jobs'), + label: t("menus.header.jobs"), children: [ { - key: 'activejobs', + key: "activejobs", icon: , - label: {t('menus.header.activejobs')}, + label: {t("menus.header.activejobs")} }, { - key: 'readyjobs', + key: "readyjobs", icon: , - label: {t('menus.header.readyjobs')}, + label: {t("menus.header.readyjobs")} }, { - key: 'parts-queue', + key: "parts-queue", icon: , - label: {t('menus.header.parts-queue')}, + label: {t("menus.header.parts-queue")} }, { - key: 'availablejobs', - id: 'header-jobs-available', + key: "availablejobs", + id: "header-jobs-available", icon: , - label: {t('menus.header.availablejobs')}, + label: {t("menus.header.availablejobs")} }, { - key: 'newjob', + key: "newjob", icon: , - label: {t('menus.header.newjob')}, + label: {t("menus.header.newjob")} }, { - type: 'divider', + type: "divider" }, { - key: 'alljobs', + key: "alljobs", icon: , - label: {t('menus.header.alljobs')}, + label: {t("menus.header.alljobs")} }, { - type: 'divider', + type: "divider" }, { - key: 'productionlist', + key: "productionlist", icon: , - label: {t('menus.header.productionlist')}, + label: {t("menus.header.productionlist")} }, ...(InstanceRenderManager({ imex: true, rome: true, - promanager: HasFeatureAccess({ featureName: 'visualboard', bodyshop }), + promanager: HasFeatureAccess({ featureName: "visualboard", bodyshop }) }) ? [ { - key: 'productionboard', + key: "productionboard", icon: , - label: ( - {t('menus.header.productionboard')} - ), - }, + label: {t("menus.header.productionboard")} + } ] : []), ...(InstanceRenderManager({ imex: true, rome: true, - promanager: HasFeatureAccess({ featureName: 'scoreboard', bodyshop }), + promanager: HasFeatureAccess({ featureName: "scoreboard", bodyshop }) }) ? [ { - type: 'divider', + type: "divider" }, { - key: 'scoreboard', + key: "scoreboard", icon: , - label: {t('menus.header.scoreboard')}, - }, + label: {t("menus.header.scoreboard")} + } ] - : []), - ], + : []) + ] }, { - key: 'customers', + key: "customers", icon: , - label: t('menus.header.customers'), + label: t("menus.header.customers"), children: [ { - key: 'owners', + key: "owners", icon: , - label: {t('menus.header.owners')}, + label: {t("menus.header.owners")} }, { - key: 'vehicles', + key: "vehicles", icon: , - label: {t('menus.header.vehicles')}, - }, - ], + label: {t("menus.header.vehicles")} + } + ] }, ...(InstanceRenderManager({ imex: true, rome: true, - promanager: false, // HasFeatureAccess({ featureName: 'courtesycars', bodyshop }), + promanager: false // HasFeatureAccess({ featureName: 'courtesycars', bodyshop }), }) ? [ { - key: 'ccs', + key: "ccs", icon: , - label: t('menus.header.courtesycars'), + label: t("menus.header.courtesycars"), children: [ { - key: 'courtesycarsall', + key: "courtesycarsall", icon: , - label: {t('menus.header.courtesycars-all')}, + label: {t("menus.header.courtesycars-all")} }, { - key: 'contracts', + key: "contracts", icon: , - label: ( - - {t('menus.header.courtesycars-contracts')} - - ), + label: {t("menus.header.courtesycars-contracts")} }, { - key: 'newcontract', + key: "newcontract", icon: , - label: ( - - {t('menus.header.courtesycars-newcontract')} - - ), - }, - ], - }, + label: {t("menus.header.courtesycars-newcontract")} + } + ] + } ] : []), ...(accountingChildren.length > 0 ? [ { - key: 'accounting', + key: "accounting", icon: , - label: t('menus.header.accounting'), - children: accountingChildren, - }, + label: t("menus.header.accounting"), + children: accountingChildren + } ] : []), { - key: 'phonebook', + key: "phonebook", icon: , - label: {t('menus.header.phonebook')}, + label: {t("menus.header.phonebook")} }, ...(InstanceRenderManager({ imex: true, rome: true, - promanager: HasFeatureAccess({ featureName: 'media', bodyshop }), + promanager: HasFeatureAccess({ featureName: "media", bodyshop }) }) ? [ { - key: 'temporarydocs', + key: "temporarydocs", icon: , - label: {t('menus.header.temporarydocs')}, - }, + label: {t("menus.header.temporarydocs")} + } ] : []), { - key: 'shopsubmenu', + key: "shopsubmenu", icon: , - label: t('menus.header.shop'), + label: t("menus.header.shop"), children: [ { - key: 'shop', + key: "shop", icon: , - label: {t('menus.header.shop_config')}, + label: {t("menus.header.shop_config")} }, { - key: 'dashboard', + key: "dashboard", icon: , - label: {t('menus.header.dashboard')}, + label: {t("menus.header.dashboard")} }, { - key: 'reportcenter', + key: "reportcenter", icon: , - label: t('menus.header.reportcenter'), + label: t("menus.header.reportcenter"), onClick: () => { setReportCenterContext({ actions: {}, - context: {}, + context: {} }); - }, + } }, { - key: 'shop-vendors', + key: "shop-vendors", icon: , - label: {t('menus.header.shop_vendors')}, + label: {t("menus.header.shop_vendors")} }, ...(InstanceRenderManager({ imex: true, rome: true, - promanager: HasFeatureAccess({ featureName: 'csi', bodyshop }), + promanager: HasFeatureAccess({ featureName: "csi", bodyshop }) }) ? [ { - key: 'shop-csi', + key: "shop-csi", icon: , - label: {t('menus.header.shop_csi')}, - }, + label: {t("menus.header.shop_csi")} + } ] - : []), - ], + : []) + ] }, { - key: 'user', - label: currentUser.displayName || currentUser.email || t('general.labels.unknown'), + key: "user", + label: currentUser.displayName || currentUser.email || t("general.labels.unknown"), children: [ { - key: 'signout', + key: "signout", icon: , danger: true, - label: t('user.actions.signout'), - onClick: () => signOutStart(), + label: t("user.actions.signout"), + onClick: () => signOutStart() }, { - key: 'help', + key: "help", icon: , - label: t('menus.header.help'), + label: t("menus.header.help"), onClick: () => { window.open( InstanceRenderManager({ - imex: 'https://help.imex.online/', - rome: 'https://rometech.com//', - promanager: 'https://web-est.com', + imex: "https://help.imex.online/", + rome: "https://rometech.com//", + promanager: "https://web-est.com" }), - '_blank' + "_blank" ); - }, + } }, // { // key: 'rescue', @@ -548,21 +526,21 @@ function Header({ ...(InstanceRenderManager({ imex: true, rome: true, - promanager: HasFeatureAccess({ featureName: 'timetickets', bodyshop }), + promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop }) }) ? [ { - key: 'shiftclock', + key: "shiftclock", icon: , - label: {t('menus.header.shiftclock')}, - }, + label: {t("menus.header.shiftclock")} + } ] : []), { - key: 'profile', + key: "profile", icon: , - label: {t('menus.currentuser.profile')}, - }, + label: {t("menus.currentuser.profile")} + } // { // key: 'langselecter', // label: t("menus.currentuser.languageselector"), @@ -590,46 +568,47 @@ function Header({ // }, // ] // }, - ], + ] }, { - key: 'recent', + key: "recent", icon: , children: recentItems.map((i, idx) => ({ key: idx, - label: {i.label}, - })), - }, + label: {i.label} + })) + } ]; InstanceRenderManager({ - executeFunction:true, + executeFunction: true, args: [], - imex: - () => { menuItems.push({ - key: 'beta-switch', - style: { marginLeft: 'auto' }, - label: ( - - - Try the new app - - - ), - });} -}) + imex: () => { + menuItems.push({ + key: "beta-switch", + style: { marginLeft: "auto" }, + label: ( + + + Try the new app + + + ) + }); + } + }); return ( ({ - setUserLanguage: (language) => dispatch(setUserLanguage(language)), + setUserLanguage: (language) => dispatch(setUserLanguage(language)) }); -export function HeaderContainer({setUserLanguage}) { - const handleMenuClick = (e) => { - if (e.item.props.actiontype === "lang-select") { - i18next.changeLanguage(e.key, (err, t) => { - if (err) { - logImEXEvent("language_change_error", {error: err}); +export function HeaderContainer({ setUserLanguage }) { + const handleMenuClick = (e) => { + if (e.item.props.actiontype === "lang-select") { + i18next.changeLanguage(e.key, (err, t) => { + if (err) { + logImEXEvent("language_change_error", { error: err }); - return console.log("Error encountered when changing languages.", err); - } - logImEXEvent("language_change", {language: e.key}); - - setUserLanguage(e.key); - }); + return console.log("Error encountered when changing languages.", err); } - }; + logImEXEvent("language_change", { language: e.key }); - return ; + setUserLanguage(e.key); + }); + } + }; + + return ; } export default connect(null, mapDispatchToProps)(HeaderContainer); diff --git a/client/src/components/help-rescue/help-rescue.component.jsx b/client/src/components/help-rescue/help-rescue.component.jsx index b9a8ac9bc..07f910de3 100644 --- a/client/src/components/help-rescue/help-rescue.component.jsx +++ b/client/src/components/help-rescue/help-rescue.component.jsx @@ -1,53 +1,53 @@ -import {Button, Input, Space} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; +import { Button, Input, Space } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; export default function HelpRescue() { - const {t} = useTranslation(); - const [code, setCode] = useState(""); + const { t } = useTranslation(); + const [code, setCode] = useState(""); - const handleClick = async () => { - var bodyFormData = new FormData(); - bodyFormData.append("Code", code); - bodyFormData.append("hostederrorhandling", 1); - await fetch("https://secure.logmeinrescue.com/Customer/Code.aspx", { - mode: "no-cors", - method: "POST", - body: bodyFormData, - }); - }; + const handleClick = async () => { + var bodyFormData = new FormData(); + bodyFormData.append("Code", code); + bodyFormData.append("hostederrorhandling", 1); + await fetch("https://secure.logmeinrescue.com/Customer/Code.aspx", { + mode: "no-cors", + method: "POST", + body: bodyFormData + }); + }; - return ( -
- -
{t("help.labels.rescuedesc")}
- setCode(e.target.value)} - value={code} - placeholder={t("help.labels.codeplacholder")} - /> - + return ( +
+ +
{t("help.labels.rescuedesc")}
+ setCode(e.target.value)} + value={code} + placeholder={t("help.labels.codeplacholder")} + /> + -
{ - alert(); - }} - > - Enter your 6-digit code: - -
- - - - - -
-
- ); +
{ + alert(); + }} + > + Enter your 6-digit code: + +
+ + + + + +
+
+ ); } diff --git a/client/src/components/indefinite-loading/indefinite-loading.component.jsx b/client/src/components/indefinite-loading/indefinite-loading.component.jsx index d38edeaa9..37e87344b 100644 --- a/client/src/components/indefinite-loading/indefinite-loading.component.jsx +++ b/client/src/components/indefinite-loading/indefinite-loading.component.jsx @@ -1,43 +1,45 @@ -import {useNProgress} from "@tanem/react-nprogress"; +import { useNProgress } from "@tanem/react-nprogress"; import React from "react"; -export default function IndefiniteLoading({loading}) { - const {animationDuration, isFinished, progress} = useNProgress({ - isAnimating: loading, - }); +export default function IndefiniteLoading({ loading }) { + const { animationDuration, isFinished, progress } = useNProgress({ + isAnimating: loading + }); - return ( + return ( +
+
-
-
-
-
- ); + style={{ + boxShadow: "0 0 10px #29d, 0 0 5px #29d", + display: "block", + height: "100%", + opacity: 1, + position: "absolute", + right: 0, + transform: "rotate(3deg) translate(0px, -4px)", + width: 100 + }} + /> +
+
+ ); } diff --git a/client/src/components/inventory-bill-ro/inventory-bill-ro.component.jsx b/client/src/components/inventory-bill-ro/inventory-bill-ro.component.jsx index fca8815d1..9972478f5 100644 --- a/client/src/components/inventory-bill-ro/inventory-bill-ro.component.jsx +++ b/client/src/components/inventory-bill-ro/inventory-bill-ro.component.jsx @@ -1,67 +1,62 @@ -import {Button} from "antd"; +import { Button } from "antd"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import dayjs from "../../utils/day"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBillEnterContext: (context) => - dispatch(setModalContext({context: context, modal: "billEnter"})), + setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })) }); export default connect(mapStateToProps, mapDispatchToProps)(InventoryBillRo); -export function InventoryBillRo({ - bodyshop, - setBillEnterContext, - inventoryline, - }) { - const {t} = useTranslation(); - return ( - - ); +export function InventoryBillRo({ bodyshop, setBillEnterContext, inventoryline }) { + const { t } = useTranslation(); + return ( + + ); } diff --git a/client/src/components/inventory-line-delete/inventory-line-delete.component.jsx b/client/src/components/inventory-line-delete/inventory-line-delete.component.jsx index ec7e8c20f..5175bac47 100644 --- a/client/src/components/inventory-line-delete/inventory-line-delete.component.jsx +++ b/client/src/components/inventory-line-delete/inventory-line-delete.component.jsx @@ -1,67 +1,60 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {Button, notification, Popconfirm} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {DELETE_INVENTORY_LINE} from "../../graphql/inventory.queries"; +import { DeleteFilled } from "@ant-design/icons"; +import { useMutation } from "@apollo/client"; +import { Button, notification, Popconfirm } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { DELETE_INVENTORY_LINE } from "../../graphql/inventory.queries"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; -export default function InventoryLineDelete({ - inventoryline, - disabled, - refetch, - }) { - const [loading, setLoading] = useState(false); - const {t} = useTranslation(); - const [deleteInventoryLine] = useMutation(DELETE_INVENTORY_LINE); +export default function InventoryLineDelete({ inventoryline, disabled, refetch }) { + const [loading, setLoading] = useState(false); + const { t } = useTranslation(); + const [deleteInventoryLine] = useMutation(DELETE_INVENTORY_LINE); - const handleDelete = async () => { - setLoading(true); - const result = await deleteInventoryLine({ - variables: {lineId: inventoryline.id}, - // update(cache, { errors }) { - // cache.modify({ - // fields: { - // inventory(existingInventory, { readField }) { - // console.log(existingInventory); - // return existingInventory.filter( - // (invRef) => inventoryline.id !== readField("id", invRef) - // ); - // }, - // }, - // }); - // }, - }); + const handleDelete = async () => { + setLoading(true); + const result = await deleteInventoryLine({ + variables: { lineId: inventoryline.id } + // update(cache, { errors }) { + // cache.modify({ + // fields: { + // inventory(existingInventory, { readField }) { + // console.log(existingInventory); + // return existingInventory.filter( + // (invRef) => inventoryline.id !== readField("id", invRef) + // ); + // }, + // }, + // }); + // }, + }); - if (!!!result.errors) { - notification["success"]({message: t("inventory.successes.deleted")}); - } else { - //Check if it's an fkey violation. + if (!!!result.errors) { + notification["success"]({ message: t("inventory.successes.deleted") }); + } else { + //Check if it's an fkey violation. - notification["error"]({ - message: t("bills.errors.deleting", { - error: JSON.stringify(result.errors), - }), - }); - } - if (refetch) refetch(); - setLoading(false); - }; + notification["error"]({ + message: t("bills.errors.deleting", { + error: JSON.stringify(result.errors) + }) + }); + } + if (refetch) refetch(); + setLoading(false); + }; - return ( - }> - - - - - ); + return ( + }> + + + + + ); } diff --git a/client/src/components/inventory-list/inventory-list.component.jsx b/client/src/components/inventory-list/inventory-list.component.jsx index 96a5e64de..de8487add 100644 --- a/client/src/components/inventory-list/inventory-list.component.jsx +++ b/client/src/components/inventory-list/inventory-list.component.jsx @@ -1,224 +1,206 @@ -import {EditFilled, FileAddFilled, SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Input, Space, Table, Typography} from "antd"; +import { EditFilled, FileAddFilled, SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Input, Space, Table, Typography } from "antd"; import queryString from "query-string"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; import InventoryBillRo from "../inventory-bill-ro/inventory-bill-ro.component"; import InventoryLineDelete from "../inventory-line-delete/inventory-line-delete.component"; -import {pageLimit} from "../../utils/config"; +import { pageLimit } from "../../utils/config"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setInventoryUpsertContext: (context) => - dispatch(setModalContext({context: context, modal: "inventoryUpsert"})), + setInventoryUpsertContext: (context) => dispatch(setModalContext({ context: context, modal: "inventoryUpsert" })) }); -export function JobsList({bodyshop, refetch, loading, jobs, total, setInventoryUpsertContext,}) { - const search = queryString.parse(useLocation().search); - const {page, sortcolumn, sortorder} = search; - const history = useNavigate(); +export function JobsList({ bodyshop, refetch, loading, jobs, total, setInventoryUpsertContext }) { + const search = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder } = search; + const history = useNavigate(); - const {t} = useTranslation(); - const columns = [ - { - title: t("billlines.fields.line_desc"), - dataIndex: "line_desc", - key: "line_desc", + const { t } = useTranslation(); + const columns = [ + { + title: t("billlines.fields.line_desc"), + dataIndex: "line_desc", + key: "line_desc", - sorter: true, //(a, b) => alphaSort(a.line_desc, b.line_desc), - sortOrder: sortcolumn === "line_desc" && sortorder, - render: (text, record) => - record.billline?.bill?.job ? ( -
-
{text}
- {`(${record.billline?.bill?.job?.v_model_yr} ${record.billline?.bill?.job?.v_make_desc} ${record.billline?.bill?.job?.v_model_desc})`} -
- ) : ( - text - ), - }, - { - title: t("inventory.labels.frombillinvoicenumber"), - dataIndex: "vendorname", - key: "vendorname", - ellipsis: true, - //sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), + sorter: true, //(a, b) => alphaSort(a.line_desc, b.line_desc), + sortOrder: sortcolumn === "line_desc" && sortorder, + render: (text, record) => + record.billline?.bill?.job ? ( +
+
{text}
+ {`(${record.billline?.bill?.job?.v_model_yr} ${record.billline?.bill?.job?.v_make_desc} ${record.billline?.bill?.job?.v_model_desc})`} +
+ ) : ( + text + ) + }, + { + title: t("inventory.labels.frombillinvoicenumber"), + dataIndex: "vendorname", + key: "vendorname", + ellipsis: true, + //sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), - //sortOrder: sortcolumn === "ownr_ln" && sortorder, - render: (text, record) => - ( - (record.billline?.bill?.invoice_number || "") + - " " + - (record.manualinvoicenumber || "") - ).trim(), - }, - { - title: t("inventory.labels.fromvendor"), - dataIndex: "vendorname", - key: "vendorname", - ellipsis: true, - //sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), + //sortOrder: sortcolumn === "ownr_ln" && sortorder, + render: (text, record) => + ((record.billline?.bill?.invoice_number || "") + " " + (record.manualinvoicenumber || "")).trim() + }, + { + title: t("inventory.labels.fromvendor"), + dataIndex: "vendorname", + key: "vendorname", + ellipsis: true, + //sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), - //sortOrder: sortcolumn === "ownr_ln" && sortorder, - render: (text, record) => - ( - (record.billline?.bill?.vendor?.name || "") + - " " + - (record.manualvendor || "") - ).trim(), - }, - { - title: t("billlines.fields.actual_price"), - dataIndex: "actual_price", - key: "actual_price", + //sortOrder: sortcolumn === "ownr_ln" && sortorder, + render: (text, record) => ((record.billline?.bill?.vendor?.name || "") + " " + (record.manualvendor || "")).trim() + }, + { + title: t("billlines.fields.actual_price"), + dataIndex: "actual_price", + key: "actual_price", - render: (text, record) => ( - {record.actual_price} - ), - }, - { - title: t("billlines.fields.actual_cost"), - dataIndex: "actual_cost", - key: "actual_cost", + render: (text, record) => {record.actual_price} + }, + { + title: t("billlines.fields.actual_cost"), + dataIndex: "actual_cost", + key: "actual_cost", - render: (text, record) => ( - {record.actual_cost} - ), - }, - { - title: t("inventory.fields.comment"), - dataIndex: "comment", - key: "comment", - }, - { - title: t("inventory.labels.consumedbyjob"), - dataIndex: "consumedbyjob", - key: "consumedbyjob", + render: (text, record) => {record.actual_cost} + }, + { + title: t("inventory.fields.comment"), + dataIndex: "comment", + key: "comment" + }, + { + title: t("inventory.labels.consumedbyjob"), + dataIndex: "consumedbyjob", + key: "consumedbyjob", - ellipsis: true, - render: (text, record) => - record.bill?.job?.ro_number ? ( - - {record.bill?.job?.ro_number} - - ) : ( - - ), - }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", + ellipsis: true, + render: (text, record) => + record.bill?.job?.ro_number ? ( + {record.bill?.job?.ro_number} + ) : ( + + ) + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", - ellipsis: true, - render: (text, record) => ( - - - - - ), - }, - ]; + ellipsis: true, + render: (text, record) => ( + + + + + ) + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - search.page = pagination.current; - search.sortcolumn = sorter.column && sorter.column.key; - search.sortorder = sorter.order; - history({search: queryString.stringify(search)}); - }; + const handleTableChange = (pagination, filters, sorter) => { + search.page = pagination.current; + search.sortcolumn = sorter.column && sorter.column.key; + search.sortorder = sorter.order; + history({ search: queryString.stringify(search) }); + }; - return ( - - {search.search && ( - <> - - {t("general.labels.searchresults", {search: search.search})} - - - - )} - - - - - { - search.search = value; - history({search: queryString.stringify(search)}); - }} - enterButton - /> - - } - > -
+ {search.search && ( + <> + + {t("general.labels.searchresults", { search: search.search })} + + + + )} + + + + + { + search.search = value; + history({ search: queryString.stringify(search) }); + }} + enterButton + /> + + } + > +
+ + ); } export default connect(mapStateToProps, mapDispatchToProps)(JobsList); diff --git a/client/src/components/inventory-list/inventory-list.container.jsx b/client/src/components/inventory-list/inventory-list.container.jsx index 3990cdfce..6d154cd20 100644 --- a/client/src/components/inventory-list/inventory-list.container.jsx +++ b/client/src/components/inventory-list/inventory-list.container.jsx @@ -1,62 +1,55 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import queryString from "query-string"; import React from "react"; -import {connect} from "react-redux"; -import {useLocation} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {QUERY_INVENTORY_PAGINATED} from "../../graphql/inventory.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; +import { connect } from "react-redux"; +import { useLocation } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { QUERY_INVENTORY_PAGINATED } from "../../graphql/inventory.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import AlertComponent from "../alert/alert.component"; import InventoryListPaginated from "./inventory-list.component"; -import {pageLimit} from "../../utils/config"; +import { pageLimit } from "../../utils/config"; const mapStateToProps = createStructuredSelector({ - //bodyshop: selectBodyshop, + //bodyshop: selectBodyshop, }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function InventoryList({setBreadcrumbs, setSelectedHeader}) { - const searchParams = queryString.parse(useLocation().search); - const {page, sortcolumn, sortorder, search, showall} = searchParams; +export function InventoryList({ setBreadcrumbs, setSelectedHeader }) { + const searchParams = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder, search, showall } = searchParams; - const {loading, error, data, refetch} = useQuery( - QUERY_INVENTORY_PAGINATED, + const { loading, error, data, refetch } = useQuery(QUERY_INVENTORY_PAGINATED, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + search: search || "", + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, + consumedIsNull: showall === "true" ? null : true, + order: [ { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - variables: { - search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, - consumedIsNull: showall === "true" ? null : true, - order: [ - { - [sortcolumn || "created_at"]: - sortorder && sortorder !== "false" - ? sortorder === "descend" - ? "desc" - : "asc" - : "desc", - }, - ], - }, + [sortcolumn || "created_at"]: + sortorder && sortorder !== "false" ? (sortorder === "descend" ? "desc" : "asc") : "desc" } - ); + ] + } + }); - if (error) return ; - return ( - - ); + if (error) return ; + return ( + + ); } export default connect(mapStateToProps, mapDispatchToProps)(InventoryList); diff --git a/client/src/components/inventory-upsert-modal/inventory-upsert-modal.component.jsx b/client/src/components/inventory-upsert-modal/inventory-upsert-modal.component.jsx index dcf7fb4a7..e718baef6 100644 --- a/client/src/components/inventory-upsert-modal/inventory-upsert-modal.component.jsx +++ b/client/src/components/inventory-upsert-modal/inventory-upsert-modal.component.jsx @@ -1,69 +1,48 @@ -import {Form, Input, Space} from "antd"; +import { Form, Input, Space } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectInventoryUpsert} from "../../redux/modals/modals.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectInventoryUpsert } from "../../redux/modals/modals.selectors"; import FormItemCurrency from "../form-items-formatted/currency-form-item.component"; const mapStateToProps = createStructuredSelector({ - inventoryUpsertModal: selectInventoryUpsert, + inventoryUpsertModal: selectInventoryUpsert }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(NoteUpsertModalComponent); +export default connect(mapStateToProps, mapDispatchToProps)(NoteUpsertModalComponent); -export function NoteUpsertModalComponent({form, inventoryUpsertModal}) { - const {t} = useTranslation(); - const {existingInventory} = inventoryUpsertModal.context; +export function NoteUpsertModalComponent({ form, inventoryUpsertModal }) { + const { t } = useTranslation(); + const { existingInventory } = inventoryUpsertModal.context; - return ( - - - - - - - + return ( + + + + + + + - {!existingInventory && ( - <> - - - - - - - - - - - - - - )} - - ); + {!existingInventory && ( + <> + + + + + + + + + + + + + + )} + + ); } diff --git a/client/src/components/inventory-upsert-modal/inventory-upsert-modal.container.jsx b/client/src/components/inventory-upsert-modal/inventory-upsert-modal.container.jsx index 819aca650..a7fa56646 100644 --- a/client/src/components/inventory-upsert-modal/inventory-upsert-modal.container.jsx +++ b/client/src/components/inventory-upsert-modal/inventory-upsert-modal.container.jsx @@ -1,120 +1,108 @@ -import {useMutation} from "@apollo/client"; -import {Form, Modal, notification} from "antd"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {INSERT_INVENTORY_LINE, UPDATE_INVENTORY_LINE,} from "../../graphql/inventory.queries"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectInventoryUpsert} from "../../redux/modals/modals.selectors"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { useMutation } from "@apollo/client"; +import { Form, Modal, notification } from "antd"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { INSERT_INVENTORY_LINE, UPDATE_INVENTORY_LINE } from "../../graphql/inventory.queries"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectInventoryUpsert } from "../../redux/modals/modals.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import InventoryUpsertModal from "./inventory-upsert-modal.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, - inventoryUpsertModal: selectInventoryUpsert, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser, + inventoryUpsertModal: selectInventoryUpsert }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("inventoryUpsert")), + toggleModalVisible: () => dispatch(toggleModalVisible("inventoryUpsert")) }); -export function InventoryUpsertModalContainer({ - currentUser, - bodyshop, - inventoryUpsertModal, - toggleModalVisible, - }) { - const {t} = useTranslation(); - const [insertInventory] = useMutation(INSERT_INVENTORY_LINE); - const [updateInventoryLine] = useMutation(UPDATE_INVENTORY_LINE); +export function InventoryUpsertModalContainer({ currentUser, bodyshop, inventoryUpsertModal, toggleModalVisible }) { + const { t } = useTranslation(); + const [insertInventory] = useMutation(INSERT_INVENTORY_LINE); + const [updateInventoryLine] = useMutation(UPDATE_INVENTORY_LINE); - const {open, context, actions} = inventoryUpsertModal; - const {existingInventory} = context; - const {refetch} = actions; + const { open, context, actions } = inventoryUpsertModal; + const { existingInventory } = context; + const { refetch } = actions; - const [form] = Form.useForm(); + const [form] = Form.useForm(); - useEffect(() => { - //Required to prevent infinite looping. - if (existingInventory && open) { - form.setFieldsValue(existingInventory); - } else if (!existingInventory && open) { - form.resetFields(); + useEffect(() => { + //Required to prevent infinite looping. + if (existingInventory && open) { + form.setFieldsValue(existingInventory); + } else if (!existingInventory && open) { + form.resetFields(); + } + }, [existingInventory, form, open]); + + const handleFinish = async (formValues) => { + const values = formValues; + + if (existingInventory) { + logImEXEvent("inventory_update"); + + updateInventoryLine({ + variables: { + inventoryId: existingInventory.id, + inventoryItem: values } - }, [existingInventory, form, open]); + }).then((r) => { + notification["success"]({ + message: t("inventory.successes.updated") + }); + }); + // if (refetch) refetch(); + toggleModalVisible(); + } else { + logImEXEvent("inventory_insert"); - const handleFinish = async (formValues) => { - const values = formValues; - - if (existingInventory) { - logImEXEvent("inventory_update"); - - updateInventoryLine({ - variables: { - inventoryId: existingInventory.id, - inventoryItem: values, - }, - }).then((r) => { - notification["success"]({ - message: t("inventory.successes.updated"), - }); - }); - // if (refetch) refetch(); - toggleModalVisible(); - } else { - logImEXEvent("inventory_insert"); - - await insertInventory({ - variables: { - inventoryItem: {shopid: bodyshop.id, ...values}, - }, - update(cache, {data}) { - cache.modify({ - fields: { - inventory(existingInv) { - return [...existingInv, data.insert_inventory_one]; - }, - }, - }); - }, - }); - - if (refetch) refetch(); - form.resetFields(); - toggleModalVisible(); - notification["success"]({ - message: t("inventory.successes.inserted"), - }); - } - }; - - return ( - { - form.submit(); - }} - onCancel={() => { - toggleModalVisible(); - }} - destroyOnClose - > -
- - -
- ); + }); + } + }); + + if (refetch) refetch(); + form.resetFields(); + toggleModalVisible(); + notification["success"]({ + message: t("inventory.successes.inserted") + }); + } + }; + + return ( + { + form.submit(); + }} + onCancel={() => { + toggleModalVisible(); + }} + destroyOnClose + > +
+ + +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(InventoryUpsertModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(InventoryUpsertModalContainer); diff --git a/client/src/components/job-3rd-party-modal/job-3rd-party-modal.component.jsx b/client/src/components/job-3rd-party-modal/job-3rd-party-modal.component.jsx index 2ae6bcdac..b0f5cac79 100644 --- a/client/src/components/job-3rd-party-modal/job-3rd-party-modal.component.jsx +++ b/client/src/components/job-3rd-party-modal/job-3rd-party-modal.component.jsx @@ -1,227 +1,176 @@ -import {useQuery} from "@apollo/client"; -import {Button, Form, Input, InputNumber, Modal, Radio, Select} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR} from "../../graphql/vendors.queries"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { useQuery } from "@apollo/client"; +import { Button, Form, Input, InputNumber, Modal, Radio, Select } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR } from "../../graphql/vendors.queries"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - technician: selectTechnician, + bodyshop: selectBodyshop, + technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(Jobd3RdPartyModal); -export function Jobd3RdPartyModal({bodyshop, jobId, job, technician}) { - const [isModalVisible, setIsModalVisible] = useState(false); - const {t} = useTranslation(); - const [form] = Form.useForm(); - const {data: VendorAutoCompleteData} = useQuery( - SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR, - { - skip: !isModalVisible, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - } +export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) { + const [isModalVisible, setIsModalVisible] = useState(false); + const { t } = useTranslation(); + const [form] = Form.useForm(); + const { data: VendorAutoCompleteData } = useQuery(SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR, { + skip: !isModalVisible, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + + const showModal = () => { + form.setFieldsValue({ + ded_amt: job.ded_amt, + depreciation: job.depreciation_taxes, + custgst: job.ca_customer_gst + }); + setIsModalVisible(true); + }; + + const handleOk = () => { + form.submit(); + setIsModalVisible(false); + }; + + const handleCancel = () => { + form.resetFields(); + setIsModalVisible(false); + }; + const handleFinish = (values) => { + const { sendtype, ...restVals } = values; + + GenerateDocument( + { + name: TemplateList("job_special").special_thirdpartypayer.key, + variables: { id: jobId }, + context: restVals + }, + { subject: TemplateList("job_special").special_thirdpartypayer.subject }, + sendtype ); + }; - const showModal = () => { - form.setFieldsValue({ - ded_amt: job.ded_amt, - depreciation: job.depreciation_taxes, - custgst: job.ca_customer_gst, - }); - setIsModalVisible(true); - }; + const handleInsSelect = (value, option) => { + form.setFieldsValue({ + addr1: option.obj.name, + addr2: option.obj.street1, + addr3: option.obj.street2, + city: option.obj.city, + state: option.obj.state, + zip: option.obj.zip, + vendorid: null + }); + }; - const handleOk = () => { - form.submit(); - setIsModalVisible(false); - }; + const handleVendorSelect = (vendorid, opt) => { + const vendor = VendorAutoCompleteData.vendors.filter((v) => v.id === vendorid)[0]; + if (vendor) { + form.setFieldsValue({ + addr1: vendor.name, + addr2: vendor.street1, + addr3: vendor.street2, + city: vendor.city, + state: vendor.state, + zip: vendor.zip, + ins_co_id: null + }); + } + }; - const handleCancel = () => { - form.resetFields(); - setIsModalVisible(false); - }; - const handleFinish = (values) => { - const {sendtype, ...restVals} = values; - - GenerateDocument( - { - name: TemplateList("job_special").special_thirdpartypayer.key, - variables: {id: jobId}, - context: restVals, - }, - {subject: TemplateList("job_special").special_thirdpartypayer.subject}, - sendtype - ); - }; - - const handleInsSelect = (value, option) => { - form.setFieldsValue({ - addr1: option.obj.name, - addr2: option.obj.street1, - addr3: option.obj.street2, - city: option.obj.city, - state: option.obj.state, - zip: option.obj.zip, - vendorid: null, - }); - }; - - const handleVendorSelect = (vendorid, opt) => { - const vendor = VendorAutoCompleteData.vendors.filter( - (v) => v.id === vendorid - )[0]; - if (vendor) { - form.setFieldsValue({ - addr1: vendor.name, - addr2: vendor.street1, - addr3: vendor.street2, - city: vendor.city, - state: vendor.state, - zip: vendor.zip, - ins_co_id: null, - }); - } - }; - - return ( - <> - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {!technician ? ( - {t("parts_orders.labels.email")} - ) : null} - {t("parts_orders.labels.print")} - - - -
- - ); + return ( + <> + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {!technician ? {t("parts_orders.labels.email")} : null} + {t("parts_orders.labels.print")} + + + +
+ + ); } diff --git a/client/src/components/job-at-change/job-at-change.component.jsx b/client/src/components/job-at-change/job-at-change.component.jsx index 26f581d10..07a652a15 100644 --- a/client/src/components/job-at-change/job-at-change.component.jsx +++ b/client/src/components/job-at-change/job-at-change.component.jsx @@ -1,65 +1,62 @@ import React from "react"; -import {useMutation} from "@apollo/client"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {Dropdown, notification} from "antd"; -import {DownOutlined} from "@ant-design/icons"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useMutation } from "@apollo/client"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { Dropdown, notification } from "antd"; +import { DownOutlined } from "@ant-design/icons"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function JobAltTransportChange({bodyshop, job}) { - const [updateJob] = useMutation(UPDATE_JOB); - const {t} = useTranslation(); +export function JobAltTransportChange({ bodyshop, job }) { + const [updateJob] = useMutation(UPDATE_JOB); + const { t } = useTranslation(); - const onClick = async ({key}) => { - const result = await updateJob({ - variables: { - jobId: job.id, - job: {alt_transport: key === "null" ? null : key}, - }, - }); + const onClick = async ({ key }) => { + const result = await updateJob({ + variables: { + jobId: job.id, + job: { alt_transport: key === "null" ? null : key } + } + }); - if (!!!result.errors) { - // notification["success"]({ message: t("appointments.successes.saved") }); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - }; + if (!!!result.errors) { + // notification["success"]({ message: t("appointments.successes.saved") }); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + }; - const menu = { - items: [ - ...(bodyshop.appt_alt_transport || []).map((alt) => ({ - key: alt, - label: alt, - })), - {key: "null", label: t("general.actions.clear")}, - ], - onClick: onClick, - defaultSelectedKeys: [job && job.alt_transport] - }; + const menu = { + items: [ + ...(bodyshop.appt_alt_transport || []).map((alt) => ({ + key: alt, + label: alt + })), + { key: "null", label: t("general.actions.clear") } + ], + onClick: onClick, + defaultSelectedKeys: [job && job.alt_transport] + }; - return ( - - e.preventDefault()}> - - - - ); + return ( + + e.preventDefault()}> + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobAltTransportChange); +export default connect(mapStateToProps, mapDispatchToProps)(JobAltTransportChange); diff --git a/client/src/components/job-at-change/schedule-event.color.component.jsx b/client/src/components/job-at-change/schedule-event.color.component.jsx index 5de77c758..e66a8b6d9 100644 --- a/client/src/components/job-at-change/schedule-event.color.component.jsx +++ b/client/src/components/job-at-change/schedule-event.color.component.jsx @@ -1,72 +1,70 @@ import React from "react"; -import {useMutation} from "@apollo/client"; -import {UPDATE_APPOINTMENT} from "../../graphql/appointments.queries"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {Dropdown, notification} from "antd"; -import {DownOutlined} from "@ant-design/icons"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useMutation } from "@apollo/client"; +import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { Dropdown, notification } from "antd"; +import { DownOutlined } from "@ant-design/icons"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function ScheduleEventColor({bodyshop, event}) { - const [updateAppointment] = useMutation(UPDATE_APPOINTMENT); - const {t} = useTranslation(); +export function ScheduleEventColor({ bodyshop, event }) { + const [updateAppointment] = useMutation(UPDATE_APPOINTMENT); + const { t } = useTranslation(); - const onClick = async ({key}) => { - const result = await updateAppointment({ - variables: { - appid: event.id, - app: {color: key === "null" ? null : key}, - }, - }); + const onClick = async ({ key }) => { + const result = await updateAppointment({ + variables: { + appid: event.id, + app: { color: key === "null" ? null : key } + } + }); - if (!!!result.errors) { - notification["success"]({message: t("appointments.successes.saved")}); - } else { - notification["error"]({ - message: t("appointments.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - }; + if (!!!result.errors) { + notification["success"]({ message: t("appointments.successes.saved") }); + } else { + notification["error"]({ + message: t("appointments.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + }; - const selectedColor = - event.color && - bodyshop.appt_colors && - bodyshop.appt_colors.filter((color) => color.color.hex === event.color)[0] - ?.label; + const selectedColor = + event.color && + bodyshop.appt_colors && + bodyshop.appt_colors.filter((color) => color.color.hex === event.color)[0]?.label; + const menu = { + defaultSelectedKeys: [event.color], + onClick: onClick, + items: [ + ...(bodyshop.appt_colors || []).map((color) => ({ + key: color.color.hex, + label: color.label, + style: { color: color.color.hex } + })), + { type: "divider" }, + { key: "null", label: t("general.actions.clear") } + ] + }; - const menu = { - defaultSelectedKeys: [event.color], - onClick: onClick, - items: [ - ...(bodyshop.appt_colors || []).map((color) => ({ - key: color.color.hex, - label: color.label, - style: {color: color.color.hex}, - })), - {type: "divider"}, - {key: "null", label: t("general.actions.clear")}, - ] - }; - - return ( - - e.preventDefault()}> - {selectedColor} - - - - ); + return ( + + e.preventDefault()}> + {selectedColor} + + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(ScheduleEventColor); 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 d09938940..b34e90668 100644 --- a/client/src/components/job-at-change/schedule-event.component.jsx +++ b/client/src/components/job-at-change/schedule-event.component.jsx @@ -1,19 +1,19 @@ -import {AlertFilled} from "@ant-design/icons"; -import {Button, Divider, Dropdown, Form, Input, notification, Popover, Select, Space,} from "antd"; +import { AlertFilled } from "@ant-design/icons"; +import { Button, Divider, Dropdown, Form, Input, notification, Popover, Select, Space } from "antd"; import parsePhoneNumber from "libphonenumber-js"; import dayjs from "../../utils/day"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {openChatByPhone, setMessage,} from "../../redux/messaging/messaging.actions"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import DataLabel from "../data-label/data-label.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; @@ -21,358 +21,330 @@ import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event. import ScheduleAtChange from "./job-at-change.component"; import ScheduleEventColor from "./schedule-event.color.component"; import ScheduleEventNote from "./schedule-event.note.component"; -import {useMutation} from "@apollo/client"; -import {UPDATE_APPOINTMENT} from "../../graphql/appointments.queries"; +import { useMutation } from "@apollo/client"; +import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setScheduleContext: (context) => - dispatch(setModalContext({context: context, modal: "schedule"})), - openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), - setMessage: (text) => dispatch(setMessage(text)), + setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })), + openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), + setMessage: (text) => dispatch(setMessage(text)) }); export function ScheduleEventComponent({ - bodyshop, - setMessage, - openChatByPhone, - event, - refetch, - handleCancel, - setScheduleContext, - }) { - const {t} = useTranslation(); - const [open, setOpen] = useState(false); - const history = useNavigate(); - const searchParams = queryString.parse(useLocation().search); - const [updateAppointment] = useMutation(UPDATE_APPOINTMENT); - const [title, setTitle] = useState(event.title); - const blockContent = ( - - setTitle(e.currentTarget.value)} - onBlur={async () => { - await updateAppointment({ - variables: { - appid: event.id, - app: { - title: title, - }, - }, - optimisticResponse: { - update_appointments: { - __typename: "appointments_mutation_response", - returning: [ - { - ...event, - title: title, - __typename: "appointments", - }, - ], - }, - }, - }); - }} - /> + bodyshop, + setMessage, + openChatByPhone, + event, + refetch, + handleCancel, + setScheduleContext +}) { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + const history = useNavigate(); + const searchParams = queryString.parse(useLocation().search); + const [updateAppointment] = useMutation(UPDATE_APPOINTMENT); + const [title, setTitle] = useState(event.title); + const blockContent = ( + + setTitle(e.currentTarget.value)} + onBlur={async () => { + await updateAppointment({ + variables: { + appid: event.id, + app: { + title: title + } + }, + optimisticResponse: { + update_appointments: { + __typename: "appointments_mutation_response", + returning: [ + { + ...event, + title: title, + __typename: "appointments" + } + ] + } + } + }); + }} + /> - + + + ); + + const popoverContent = ( +
+ {!event.isintake ? ( + + {event.title} + - ); - - const popoverContent = ( -
- {!event.isintake ? ( - - {event.title} - - - ) : ( - - - - - + ) : ( + + + + + {`${(event.job && event.job.v_model_yr) || ""} ${ - (event.job && event.job.v_make_desc) || "" + (event.job && event.job.v_make_desc) || "" } ${(event.job && event.job.v_model_desc) || ""}`} - - - )} - - {event.job ? ( -
- - {(event.job && event.job.ro_number) || ""} - - - - {(event.job && event.job.clm_total) || ""} - - - - {(event.job && event.job.ins_co_nm) || ""} - - - {(event.job && event.job.clm_no) || ""} - - - {(event.job && event.job.ownr_ea) || ""} - - - - - - - - - {(event.job && event.job.alt_transport) || ""} - - - -
- ) : ( -
{event.note || ""}
- )} - - - {event.job ? ( - - - - ) : null} - {event.job ? ( - - ) : null} - {event.job ? ( - { - const Template = TemplateList("job").appointment_reminder; - GenerateDocument( - { - name: Template.key, - variables: {id: event.job.id}, - }, - { - to: event.job && event.job.ownr_ea, - subject: Template.subject, - }, - "e", - event.job && event.job.id - ); - }, - }, - { - key: "sms", - label: t("general.labels.sms"), - disabled: event.arrived || !bodyshop.messagingservicesid, - onClick: () => { - const p = parsePhoneNumber(event.job.ownr_ph1, "CA"); - if (p && p.isValid()) { - openChatByPhone({ - phone_num: p.formatInternational(), - jobid: event.job.id, - }); - setMessage( - t("appointments.labels.reminder", { - shopname: bodyshop.shopname, - date: dayjs(event.start).format("MM/DD/YYYY"), - time: dayjs(event.start).format("HH:mm a"), - }) - ); - setOpen(false); - } else { - notification["error"]({ - message: t("messaging.error.invalidphone"), - }); - } - }, - }, - ] - }} - > - - - ) : null} - {event.arrived ? ( - - ) : ( - { - handleCancel({id: event.id, lost_sale_reason}); - }} - > - - ({ + label: lsr, + value: lsr + }))} + /> + + + + } + > + + + )} + + {event.isintake ? ( + + ) : ( + + )} + {event.isintake ? ( + + + + ) : null} + +
+ ); + + const RegularEvent = event.isintake ? ( + + {event.note && } + {`${event.job.ro_number || t("general.labels.na")}`} + + + + {`${(event.job && event.job.v_model_yr) || ""} ${ + (event.job && event.job.v_make_desc) || "" + } ${(event.job && event.job.v_model_desc) || ""}`} + + {`(${(event.job && event.job.labhrs.aggregate.sum.mod_lb_hrs) || "0"} / ${ + (event.job && event.job.larhrs.aggregate.sum.mod_lb_hrs) || "0" + })`} + + {event.job && event.job.alt_transport &&
{event.job.alt_transport}
} +
+ ) : ( +
+ {`${event.title || ""}`} +
+ ); + + return ( + !event.vacation && setOpen(vis)} + trigger="click" + content={event.block ? blockContent : popoverContent} + style={{ + height: "100%", + width: "100%", + + backgroundColor: event.color && event.color.hex ? event.color.hex : event.color + }} + > + {RegularEvent} + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScheduleEventComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ScheduleEventComponent); diff --git a/client/src/components/job-at-change/schedule-event.container.jsx b/client/src/components/job-at-change/schedule-event.container.jsx index fcc85cecc..f576d4073 100644 --- a/client/src/components/job-at-change/schedule-event.container.jsx +++ b/client/src/components/job-at-change/schedule-event.container.jsx @@ -1,78 +1,73 @@ -import {useMutation} from "@apollo/client"; -import {notification} from "antd"; +import { useMutation } from "@apollo/client"; +import { notification } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {useDispatch} from "react-redux"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {CANCEL_APPOINTMENT_BY_ID} from "../../graphql/appointments.queries"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; +import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.queries"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import ScheduleEventComponent from "./schedule-event.component"; -export default function ScheduleEventContainer({bodyshop, event, refetch}) { - const dispatch = useDispatch(); - const {t} = useTranslation(); - const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID); - const [updateJob] = useMutation(UPDATE_JOB); - const handleCancel = async ({id, lost_sale_reason}) => { - logImEXEvent("schedule_cancel_appt"); +export default function ScheduleEventContainer({ bodyshop, event, refetch }) { + const dispatch = useDispatch(); + const { t } = useTranslation(); + const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID); + const [updateJob] = useMutation(UPDATE_JOB); + const handleCancel = async ({ id, lost_sale_reason }) => { + logImEXEvent("schedule_cancel_appt"); - const cancelAppt = await cancelAppointment({ - variables: {appid: event.id}, - }); - notification["success"]({ - message: t("appointments.successes.canceled"), - }); + const cancelAppt = await cancelAppointment({ + variables: { appid: event.id } + }); + notification["success"]({ + message: t("appointments.successes.canceled") + }); - if (!!cancelAppt.errors) { - notification["error"]({ - message: t("appointments.errors.canceling", { - message: JSON.stringify(cancelAppt.errors), - }), - }); - return; + if (!!cancelAppt.errors) { + notification["error"]({ + message: t("appointments.errors.canceling", { + message: JSON.stringify(cancelAppt.errors) + }) + }); + return; + } + + if (event.job) { + const jobUpdate = await updateJob({ + variables: { + jobId: event.job.id, + job: { + date_scheduled: null, + scheduled_in: null, + scheduled_completion: null, + lost_sale_reason, + date_lost_sale: new Date(), + status: bodyshop.md_ro_statuses.default_imported + } } + }); + if (!jobUpdate.errors) { + dispatch( + insertAuditTrail({ + jobid: event.job.id, + operation: AuditTrailMapping.appointmentcancel(lost_sale_reason), + type: "appointmentcancel" + }) + ); + } + if (!!jobUpdate.errors) { + notification["error"]({ + message: t("jobs.errors.updating", { + message: JSON.stringify(jobUpdate.errors) + }) + }); + return; + } + } + if (refetch) refetch(); + }; - if (event.job) { - const jobUpdate = await updateJob({ - variables: { - jobId: event.job.id, - job: { - date_scheduled: null, - scheduled_in: null, - scheduled_completion: null, - lost_sale_reason, - date_lost_sale: new Date(), - status: bodyshop.md_ro_statuses.default_imported, - }, - }, - }); - if (!jobUpdate.errors) { - dispatch( - insertAuditTrail({ - jobid: event.job.id, - operation: AuditTrailMapping.appointmentcancel(lost_sale_reason), - type: "appointmentcancel",}) - ); - } - if (!!jobUpdate.errors) { - notification["error"]({ - message: t("jobs.errors.updating", { - message: JSON.stringify(jobUpdate.errors), - }), - }); - return; - } - } - if (refetch) refetch(); - }; - - return ( - - ); + return ; } diff --git a/client/src/components/job-at-change/schedule-event.note.component.jsx b/client/src/components/job-at-change/schedule-event.note.component.jsx index 951e7a271..a2eb60302 100644 --- a/client/src/components/job-at-change/schedule-event.note.component.jsx +++ b/client/src/components/job-at-change/schedule-event.note.component.jsx @@ -1,75 +1,70 @@ -import {EditFilled, SaveFilled} from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {Button, Input, notification, Space} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {UPDATE_APPOINTMENT} from "../../graphql/appointments.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { EditFilled, SaveFilled } from "@ant-design/icons"; +import { useMutation } from "@apollo/client"; +import { Button, Input, notification, Space } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import DataLabel from "../data-label/data-label.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function ScheduleEventNote({event}) { - const [editing, setEditing] = useState(false); - const [note, setNote] = useState(event.note || ""); - const [loading, setLoading] = useState(false); - const [updateAppointment] = useMutation(UPDATE_APPOINTMENT); - const {t} = useTranslation(); +export function ScheduleEventNote({ event }) { + const [editing, setEditing] = useState(false); + const [note, setNote] = useState(event.note || ""); + const [loading, setLoading] = useState(false); + const [updateAppointment] = useMutation(UPDATE_APPOINTMENT); + const { t } = useTranslation(); - const toggleEdit = async () => { - if (editing) { - //Await the update - setLoading(true); - const result = await updateAppointment({ - variables: { - appid: event.id, - app: {note}, - }, - }); - - if (!!!result.errors) { - // notification["success"]({ message: t("appointments.successes.saved") }); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - - setEditing(false); - } else { - setEditing(true); + const toggleEdit = async () => { + if (editing) { + //Await the update + setLoading(true); + const result = await updateAppointment({ + variables: { + appid: event.id, + app: { note } } - setLoading(false); - }; + }); - return ( - - - {!editing ? ( - event.note || "" - ) : ( - setNote(e.target.value)} - style={{maxWidth: "8vw"}} - /> - )} - - - - ); + if (!!!result.errors) { + // notification["success"]({ message: t("appointments.successes.saved") }); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + + setEditing(false); + } else { + setEditing(true); + } + setLoading(false); + }; + + return ( + + + {!editing ? ( + event.note || "" + ) : ( + setNote(e.target.value)} style={{ maxWidth: "8vw" }} /> + )} + + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(ScheduleEventNote); diff --git a/client/src/components/job-audit-trail/job-audit-trail.component.jsx b/client/src/components/job-audit-trail/job-audit-trail.component.jsx index d9376504d..421733e48 100644 --- a/client/src/components/job-audit-trail/job-audit-trail.component.jsx +++ b/client/src/components/job-audit-trail/job-audit-trail.component.jsx @@ -1,154 +1,136 @@ -import {useQuery} from "@apollo/client"; -import {Button, Card, Col, Row, Table, Tag} from "antd"; -import {SyncOutlined} from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import { Button, Card, Col, Row, Table, Tag } from "antd"; +import { SyncOutlined } from "@ant-design/icons"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {QUERY_AUDIT_TRAIL} from "../../graphql/audit_trail.queries"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectCurrentUser} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectCurrentUser } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(JobAuditTrail); -export function JobAuditTrail({currentUser, jobId}) { - const {t} = useTranslation(); - const {loading, data, refetch} = useQuery(QUERY_AUDIT_TRAIL, { - variables: {jobid: jobId}, - skip: !jobId, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); +export function JobAuditTrail({ currentUser, jobId }) { + const { t } = useTranslation(); + const { loading, data, refetch } = useQuery(QUERY_AUDIT_TRAIL, { + variables: { jobid: jobId }, + skip: !jobId, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const columns = [ - { - title: t("audit.fields.created"), - dataIndex: "created", - key: "created", + const columns = [ + { + title: t("audit.fields.created"), + dataIndex: "created", + key: "created", + render: (text, record) => {record.created} + }, + { + title: t("audit.fields.useremail"), + dataIndex: "useremail", + key: "useremail" + }, + { + title: t("audit.fields.operation"), + dataIndex: "operation", + key: "operation" + } + ]; + const emailColumns = [ + { + title: t("audit.fields.created"), + dataIndex: " created_at", + key: " created_at", + + render: (text, record) => {record.created_at} + }, + + { + title: t("audit.fields.useremail"), + dataIndex: "useremail", + key: "useremail" + }, + + { + title: t("audit.fields.to"), + dataIndex: "to", + key: "to", + + render: (text, record) => record.to && record.to.map((email, idx) => {email}) + }, + { + title: t("audit.fields.cc"), + dataIndex: "cc", + key: "cc", + + render: (text, record) => record.cc && record.cc.map((email, idx) => {email}) + }, + { + title: t("audit.fields.subject"), + dataIndex: "subject", + key: "subject" + }, + { + title: t("audit.fields.status"), + dataIndex: "status", + key: "status" + }, + ...(currentUser?.email.includes("@imex.") + ? [ + { + title: t("audit.fields.contents"), + dataIndex: "contents", + key: "contents", + width: "10%", render: (text, record) => ( - {record.created} - ), - }, - { - title: t("audit.fields.useremail"), - dataIndex: "useremail", - key: "useremail", - }, - { - title: t("audit.fields.operation"), - dataIndex: "operation", - key: "operation", - }, - ]; - const emailColumns = [ - { - title: t("audit.fields.created"), - dataIndex: " created_at", - key: " created_at", - - render: (text, record) => ( - {record.created_at} - ), - }, - - { - title: t("audit.fields.useremail"), - dataIndex: "useremail", - key: "useremail", - }, - - { - title: t("audit.fields.to"), - dataIndex: "to", - key: "to", - - render: (text, record) => - record.to && - record.to.map((email, idx) => {email}), - }, - { - title: t("audit.fields.cc"), - dataIndex: "cc", - key: "cc", - - render: (text, record) => - record.cc && - record.cc.map((email, idx) => {email}), - }, - { - title: t("audit.fields.subject"), - dataIndex: "subject", - key: "subject", - }, - { - title: t("audit.fields.status"), - dataIndex: "status", - key: "status", - }, - ...(currentUser?.email.includes("@imex.") - ? [ - { - title: t("audit.fields.contents"), - dataIndex: "contents", - key: "contents", - width: "10%", - render: (text, record) => ( - - ), - }, - ] - : []), - ]; - return ( - -
- { - refetch(); - }} - > - - - } - > -
- - - - -
- - - - ); + + ) + } + ] + : []) + ]; + return ( + + + { + refetch(); + }} + > + + + } + > +
+ + + + +
+ + + + ); } diff --git a/client/src/components/job-bills-total/job-bills-total.component.jsx b/client/src/components/job-bills-total/job-bills-total.component.jsx index 581a9a8b8..85f8ed893 100644 --- a/client/src/components/job-bills-total/job-bills-total.component.jsx +++ b/client/src/components/job-bills-total/job-bills-total.component.jsx @@ -1,295 +1,270 @@ -import {Card, Col, Row, Space, Statistic, Tooltip, Typography} from "antd"; +import { Card, Col, Row, Space, Statistic, Tooltip, Typography } from "antd"; import Dinero from "dinero.js"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import AlertComponent from "../alert/alert.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import "./job-bills-total.styles.scss"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; -export default function JobBillsTotalComponent({ - loading, - bills, - partsOrders, - jobTotals, - }) { - const {t} = useTranslation(); +export default function JobBillsTotalComponent({ loading, bills, partsOrders, jobTotals }) { + const { t } = useTranslation(); - if (loading) return ; - if (!!!jobTotals) - return ( - + if (loading) return ; + if (!!!jobTotals) return ; + + const totals = jobTotals; + + let billTotals = Dinero(); + let billCms = Dinero(); + let lbrAdjustments = Dinero(); + let totalReturns = Dinero(); + let totalReturnsMarkedNotReceived = Dinero(); + let totalReturnsMarkedReceived = Dinero(); + + partsOrders.forEach((p) => + p.parts_order_lines.forEach((pol) => { + if (p.return) { + totalReturns = totalReturns.add( + Dinero({ + amount: Math.round((pol.act_price || 0) * 100) + }).multiply(pol.quantity) ); - const totals = jobTotals; + if (pol.cm_received === null) { + return; //TODO:AIO This was previously removed. Check if functionality impacted. + // Skip this calculation for bills posted prior to the CNR change. + } else { + if (pol.cm_received === false) { + totalReturnsMarkedNotReceived = totalReturnsMarkedNotReceived.add( + Dinero({ + amount: Math.round((pol.act_price || 0) * 100) + }).multiply(pol.quantity) + ); + } else { + totalReturnsMarkedReceived = totalReturnsMarkedReceived.add( + Dinero({ + amount: Math.round((pol.act_price || 0) * 100) + }).multiply(pol.quantity) + ); + } + } + } + }) + ); - let billTotals = Dinero(); - let billCms = Dinero(); - let lbrAdjustments = Dinero(); - let totalReturns = Dinero(); - let totalReturnsMarkedNotReceived = Dinero(); - let totalReturnsMarkedReceived = Dinero(); + bills.forEach((i) => + i.billlines.forEach((il) => { + if (!i.is_credit_memo) { + billTotals = billTotals.add( + Dinero({ + amount: Math.round((il.actual_price || 0) * 100) + }).multiply(il.quantity) + ); + } else { + billCms = billCms.add( + Dinero({ + amount: Math.round((il.actual_price || 0) * 100) + }).multiply(il.quantity) + ); + } + if (il.deductedfromlbr) { + lbrAdjustments = lbrAdjustments.add( + Dinero({ + amount: Math.round((il.actual_price || 0) * 100) + }).multiply(il.quantity) + ); + } + }) + ); - partsOrders.forEach((p) => - p.parts_order_lines.forEach((pol) => { - if (p.return) { - totalReturns = totalReturns.add( - Dinero({ - amount: Math.round((pol.act_price || 0) * 100), - }).multiply(pol.quantity) - ); + const totalPartsSublet = Dinero(totals.parts.parts.total) + .add(Dinero(totals.parts.sublets.total)) + .add(Dinero(totals.additional.shipping)) + .add(Dinero(totals.additional.towing)) + .add( + InstanceRenderManager({ + imex: Dinero(), + rome: Dinero(totals.additional.additionalCosts), + promanager: "USE_ROME" + }) + ); // Additional costs were captured for Rome, but not imex. - if (pol.cm_received === null) { - return; //TODO:AIO This was previously removed. Check if functionality impacted. - // Skip this calculation for bills posted prior to the CNR change. - } else { - if (pol.cm_received === false) { - totalReturnsMarkedNotReceived = totalReturnsMarkedNotReceived.add( - Dinero({ - amount: Math.round((pol.act_price || 0) * 100), - }).multiply(pol.quantity) - ); - } else { - totalReturnsMarkedReceived = totalReturnsMarkedReceived.add( - Dinero({ - amount: Math.round((pol.act_price || 0) * 100), - }).multiply(pol.quantity) - ); - } + const discrepancy = totalPartsSublet.subtract(billTotals); + + const discrepWithLbrAdj = discrepancy.add(lbrAdjustments); + + const discrepWithCms = discrepWithLbrAdj.add(totalReturns); + const calculatedCreditsNotReceived = totalReturns.subtract(billCms); //billCms is tracked as a negative number. + + return ( + + + + + + } + > + + + - + + } + > + + + = + + } + > + + + + + + } + > + + + = + + } + > + + + + + + } + > + + + = + + } + > + + + + + + + + + + } + > + + + + } + > + = 0 + ? calculatedCreditsNotReceived.toFormat() + : Dinero().toFormat() } - } - }) - ); - - bills.forEach((i) => - i.billlines.forEach((il) => { - if (!i.is_credit_memo) { - billTotals = billTotals.add( - Dinero({ - amount: Math.round((il.actual_price || 0) * 100), - }).multiply(il.quantity) - ); - } else { - billCms = billCms.add( - Dinero({ - amount: Math.round((il.actual_price || 0) * 100), - }).multiply(il.quantity) - ); - } - if (il.deductedfromlbr) { - lbrAdjustments = lbrAdjustments.add( - Dinero({ - amount: Math.round((il.actual_price || 0) * 100), - }).multiply(il.quantity) - ); - } - }) - ); - - const totalPartsSublet = Dinero(totals.parts.parts.total) - .add(Dinero(totals.parts.sublets.total)) - .add(Dinero(totals.additional.shipping)) - .add(Dinero(totals.additional.towing)) - .add( InstanceRenderManager({imex: Dinero(), rome: Dinero(totals.additional.additionalCosts),promanager: "USE_ROME" })) ; // Additional costs were captured for Rome, but not imex. - - const discrepancy = totalPartsSublet.subtract(billTotals); - - const discrepWithLbrAdj = discrepancy.add(lbrAdjustments); - - const discrepWithCms = discrepWithLbrAdj.add(totalReturns); - const calculatedCreditsNotReceived = totalReturns.subtract(billCms); //billCms is tracked as a negative number. - - return ( - - - - - - } - > - - - - - - } - > - - - = - - } - > - - - + - - } - > - - - = - - } - > - - - + - - } - > - - - = - - } - > - - - - - - - - - - } - > - - - - } - > - = 0 - ? calculatedCreditsNotReceived.toFormat() - : Dinero().toFormat() - } - /> - - - } - > - = 0 - ? totalReturnsMarkedNotReceived.toFormat() - : Dinero().toFormat() - } - /> - - - - - - ); + /> + + + } + > + = 0 + ? totalReturnsMarkedNotReceived.toFormat() + : Dinero().toFormat() + } + /> + + + + + + ); } diff --git a/client/src/components/job-calculate-totals/job-calculate-totals.component.jsx b/client/src/components/job-calculate-totals/job-calculate-totals.component.jsx index 3ea7386de..706cf16a0 100644 --- a/client/src/components/job-calculate-totals/job-calculate-totals.component.jsx +++ b/client/src/components/job-calculate-totals/job-calculate-totals.component.jsx @@ -1,60 +1,60 @@ -import {Button, notification} from "antd"; +import { Button, notification } from "antd"; import Axios from "axios"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; -export default function JobCalculateTotals({job, disabled, refetch}) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); +export default function JobCalculateTotals({ job, disabled, refetch }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); - const handleCalculate = async () => { - try { - setLoading(true); + const handleCalculate = async () => { + try { + setLoading(true); - await Axios.post("/job/totalsssu", { - id: job.id, - }); + await Axios.post("/job/totalsssu", { + id: job.id + }); - if (refetch) refetch(); - // const result = await updateJob({ - // refetchQueries: ["GET_JOB_BY_PK"], - // awaitRefetchQueries: true, - // variables: { - // jobId: job.id, - // job: { - // job_totals: newTotals, - // clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), - // owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat( - // "0.00" - // ), - // }, - // }, - // }); - // if (!!!result.errors) { - // notification["success"]({ message: t("jobs.successes.updated") }); - // } else { - // notification["error"]({ - // message: t("jobs.errors.updating", { - // error: JSON.stringify(result.errors), - // }), - // }); - // } - } catch (error) { - notification["error"]({ - message: t("jobs.errors.updating", { - error: JSON.stringify(error), - }), - }); - } finally { - setLoading(false); - } - }; + if (refetch) refetch(); + // const result = await updateJob({ + // refetchQueries: ["GET_JOB_BY_PK"], + // awaitRefetchQueries: true, + // variables: { + // jobId: job.id, + // job: { + // job_totals: newTotals, + // clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), + // owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat( + // "0.00" + // ), + // }, + // }, + // }); + // if (!!!result.errors) { + // notification["success"]({ message: t("jobs.successes.updated") }); + // } else { + // notification["error"]({ + // message: t("jobs.errors.updating", { + // error: JSON.stringify(result.errors), + // }), + // }); + // } + } catch (error) { + notification["error"]({ + message: t("jobs.errors.updating", { + error: JSON.stringify(error) + }) + }); + } finally { + setLoading(false); + } + }; - return ( -
- -
- ); + return ( +
+ +
+ ); } diff --git a/client/src/components/job-checklist/components/job-checklist-form/job-checklist-form.component.jsx b/client/src/components/job-checklist/components/job-checklist-form/job-checklist-form.component.jsx index 881912223..2f901d818 100644 --- a/client/src/components/job-checklist/components/job-checklist-form/job-checklist-form.component.jsx +++ b/client/src/components/job-checklist/components/job-checklist-form/job-checklist-form.component.jsx @@ -1,331 +1,296 @@ -import {useMutation} from "@apollo/client"; -import {Button, Card, Form, Input, notification, Switch} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Card, Form, Input, notification, Switch } from "antd"; import dayjs from "../../../../utils/day"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation, useNavigate, useParams} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../../../firebase/firebase.utils"; -import {MARK_APPOINTMENT_ARRIVED, MARK_LATEST_APPOINTMENT_ARRIVED,} from "../../../../graphql/appointments.queries"; -import {UPDATE_JOB} from "../../../../graphql/jobs.queries"; -import {UPDATE_OWNER} from "../../../../graphql/owners.queries"; -import {insertAuditTrail} from "../../../../redux/application/application.actions"; -import {selectBodyshop, selectCurrentUser,} from "../../../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation, useNavigate, useParams } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../../../firebase/firebase.utils"; +import { MARK_APPOINTMENT_ARRIVED, MARK_LATEST_APPOINTMENT_ARRIVED } from "../../../../graphql/appointments.queries"; +import { UPDATE_JOB } from "../../../../graphql/jobs.queries"; +import { UPDATE_OWNER } from "../../../../graphql/owners.queries"; +import { insertAuditTrail } from "../../../../redux/application/application.actions"; +import { selectBodyshop, selectCurrentUser } from "../../../../redux/user/user.selectors"; import AuditTrailMapping from "../../../../utils/AuditTrailMappings"; import ConfigFormComponents from "../../../config-form-components/config-form-components.component"; import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export function JobChecklistForm({ - insertAuditTrail, - formItems, - bodyshop, - currentUser, - type, - job, - readOnly = false, - }) { - const {t} = useTranslation(); - const [intakeJob] = useMutation(UPDATE_JOB); - const [loading, setLoading] = useState(false); - const [markAptArrived] = useMutation(MARK_APPOINTMENT_ARRIVED); - const [markLatestAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_ARRIVED); - const [updateOwner] = useMutation(UPDATE_OWNER); +export function JobChecklistForm({ insertAuditTrail, formItems, bodyshop, currentUser, type, job, readOnly = false }) { + const { t } = useTranslation(); + const [intakeJob] = useMutation(UPDATE_JOB); + const [loading, setLoading] = useState(false); + const [markAptArrived] = useMutation(MARK_APPOINTMENT_ARRIVED); + const [markLatestAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_ARRIVED); + const [updateOwner] = useMutation(UPDATE_OWNER); - const {jobId} = useParams(); - const history = useNavigate(); - const search = queryString.parse(useLocation().search); - const [form] = Form.useForm(); + const { jobId } = useParams(); + const history = useNavigate(); + const search = queryString.parse(useLocation().search); + const [form] = Form.useForm(); - const handleFinish = async (values) => { - setLoading(true); - logImEXEvent("job_complete_intake"); + const handleFinish = async (values) => { + setLoading(true); + logImEXEvent("job_complete_intake"); - const result = await intakeJob({ - variables: { - jobId: jobId, - job: { - ...(type === "intake" && {inproduction: values.addToProduction}), - status: - (type === "intake" && bodyshop.md_ro_statuses.default_arrived) || - (type === "deliver" && bodyshop.md_ro_statuses.default_delivered), - ...(type === "intake" && {actual_in: new Date()}), - ...(type === "intake" && { - production_vars: { - ...(job ? job.production_vars : {}), + const result = await intakeJob({ + variables: { + jobId: jobId, + job: { + ...(type === "intake" && { inproduction: values.addToProduction }), + status: + (type === "intake" && bodyshop.md_ro_statuses.default_arrived) || + (type === "deliver" && bodyshop.md_ro_statuses.default_delivered), + ...(type === "intake" && { actual_in: new Date() }), + ...(type === "intake" && { + production_vars: { + ...(job ? job.production_vars : {}), - note: - values.production_vars && - values.production_vars.note && - values.production_vars.note !== "" - ? values && - values.production_vars && - values.production_vars.note - : job && job.production_vars && job.production_vars.note, - }, - }), - ...(type === "intake" && { - scheduled_completion: values.scheduled_completion, - }), - ...(type === "intake" && - bodyshop.intakechecklist && - bodyshop.intakechecklist.next_contact_hours && - bodyshop.intakechecklist.next_contact_hours > 0 && { - date_next_contact: dayjs().add( - bodyshop.intakechecklist.next_contact_hours, - "hour" - ), - }), - ...(type === "deliver" && { - actual_completion: values.actual_completion, - }), + note: + values.production_vars && values.production_vars.note && values.production_vars.note !== "" + ? values && values.production_vars && values.production_vars.note + : job && job.production_vars && job.production_vars.note + } + }), + ...(type === "intake" && { + scheduled_completion: values.scheduled_completion + }), + ...(type === "intake" && + bodyshop.intakechecklist && + bodyshop.intakechecklist.next_contact_hours && + bodyshop.intakechecklist.next_contact_hours > 0 && { + date_next_contact: dayjs().add(bodyshop.intakechecklist.next_contact_hours, "hour") + }), + ...(type === "deliver" && { + actual_completion: values.actual_completion + }), - [(type === "intake" && "intakechecklist") || - (type === "deliver" && "deliverchecklist")]: { - ...values, - form: formItems.map((fi) => { - return { - ...fi, - value: values[fi.name], - }; - }), - completed_by: currentUser.email, - completed_at: new Date(), - }, + [(type === "intake" && "intakechecklist") || (type === "deliver" && "deliverchecklist")]: { + ...values, + form: formItems.map((fi) => { + return { + ...fi, + value: values[fi.name] + }; + }), + completed_by: currentUser.email, + completed_at: new Date() + }, - ...(type === "intake" && - values.scheduled_delivery && { - scheduled_delivery: values.scheduled_delivery, - }), - ...(type === "deliver" && { - scheduled_delivery: values.scheduled_delivery, - actual_delivery: values.actual_delivery, - }), - ...(type === "deliver" && - values.removeFromProduction && { - inproduction: false, - }), - }, - }, + ...(type === "intake" && + values.scheduled_delivery && { + scheduled_delivery: values.scheduled_delivery + }), + ...(type === "deliver" && { + scheduled_delivery: values.scheduled_delivery, + actual_delivery: values.actual_delivery + }), + ...(type === "deliver" && + values.removeFromProduction && { + inproduction: false + }) + } + } + }); + if (!!search.appointmentId) { + const appUpdate = await markAptArrived({ + variables: { appointmentId: search.appointmentId } + }); + + if (!!appUpdate.errors) { + notification["error"]({ + message: t("checklist.errors.complete", { + error: JSON.stringify(result.errors) + }) }); - if (!!search.appointmentId) { - const appUpdate = await markAptArrived({ - variables: {appointmentId: search.appointmentId}, - }); + } + } else if (type === "intake" && !search.appointmentId) { + const appUpdate = await markLatestAptArrived({ + variables: { jobId: jobId } + }); - if (!!appUpdate.errors) { - notification["error"]({ - message: t("checklist.errors.complete", { - error: JSON.stringify(result.errors), - }), - }); - } - } else if (type === "intake" && !search.appointmentId) { - const appUpdate = await markLatestAptArrived({ - variables: {jobId: jobId}, - }); + if (!!appUpdate.errors) { + notification["error"]({ + message: t("checklist.errors.complete", { + error: JSON.stringify(result.errors) + }) + }); + } + } - if (!!appUpdate.errors) { - notification["error"]({ - message: t("checklist.errors.complete", { - error: JSON.stringify(result.errors), - }), - }); - } + if (type === "intake" && job.owner && job.owner.id) { + //Updae Owner Allow to Text + const updateOwnerResult = await updateOwner({ + variables: { + ownerId: job.owner.id, + owner: { allow_text_message: values.allow_text_message } } + }); - if (type === "intake" && job.owner && job.owner.id) { - //Updae Owner Allow to Text - const updateOwnerResult = await updateOwner({ - variables: { - ownerId: job.owner.id, - owner: {allow_text_message: values.allow_text_message}, - }, - }); + if (!!updateOwnerResult.errors) { + notification["error"]({ + message: t("checklist.errors.complete", { + error: JSON.stringify(result.errors) + }) + }); + } + } - if (!!updateOwnerResult.errors) { - notification["error"]({ - message: t("checklist.errors.complete", { - error: JSON.stringify(result.errors), - }), - }); - } - } + setLoading(false); - setLoading(false); + if (!!!result.errors) { + notification["success"]({ message: t("checklist.successes.completed") }); + history(`/manage/jobs/${jobId}`); - if (!!!result.errors) { - notification["success"]({message: t("checklist.successes.completed")}); - history(`/manage/jobs/${jobId}`); + insertAuditTrail({ + jobid: jobId, + operation: AuditTrailMapping.jobchecklist( + type, + (type === "deliver" && values.removeFromProduction && false) || (type === "intake" && values.addToProduction), + (type === "intake" && bodyshop.md_ro_statuses.default_arrived) || + (type === "deliver" && bodyshop.md_ro_statuses.default_delivered) + ), + type: "jobchecklist" + }); + } else { + notification["error"]({ + message: t("checklist.errors.complete", { + error: JSON.stringify(result.errors) + }) + }); + } + }; - insertAuditTrail({ - jobid: jobId, - operation: AuditTrailMapping.jobchecklist( - type, - (type === "deliver" && values.removeFromProduction && false) || - (type === "intake" && values.addToProduction), - (type === "intake" && bodyshop.md_ro_statuses.default_arrived) || - (type === "deliver" && bodyshop.md_ro_statuses.default_delivered) - ), - type: "jobchecklist",}); - } else { - notification["error"]({ - message: t("checklist.errors.complete", { - error: JSON.stringify(result.errors), - }), - }); - } - }; + return ( + form.submit()}> + {t("general.actions.submit")} + + ) + } + > +
fi.value) + .reduce((acc, fi) => { + acc[fi.name] = fi.value; + return acc; + }, {}) + }} + > + - return ( - form.submit()} - > - {t("general.actions.submit")} - - ) - } - > - fi.value) - .reduce((acc, fi) => { - acc[fi.name] = fi.value; - return acc; - }, {}), - }} + {type === "intake" && ( +
+ - - - {type === "intake" && ( -
- - - - - - - - - - - - - - - -
- )} - {type === "deliver" && ( -
- - - - - - - - - -
- )} - - - ); + +
+ + + + + + + + + + + + +
+ )} + {type === "deliver" && ( +
+ + + + + + + + + +
+ )} + +
+ ); } export default connect(mapStateToProps, mapDispatchToProps)(JobChecklistForm); diff --git a/client/src/components/job-checklist/components/job-checklist-template-list/job-checklist-template-list.component.jsx b/client/src/components/job-checklist/components/job-checklist-template-list/job-checklist-template-list.component.jsx index 81f6a3f56..05fa55c77 100644 --- a/client/src/components/job-checklist/components/job-checklist-template-list/job-checklist-template-list.component.jsx +++ b/client/src/components/job-checklist/components/job-checklist-template-list/job-checklist-template-list.component.jsx @@ -1,78 +1,72 @@ -import {PrinterFilled} from "@ant-design/icons"; -import {Button, Card, List} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {useParams} from "react-router-dom"; -import {logImEXEvent} from "../../../../firebase/firebase.utils"; -import {GenerateDocument, GenerateDocuments,} from "../../../../utils/RenderTemplate"; -import {TemplateList} from "../../../../utils/TemplateConstants"; +import { PrinterFilled } from "@ant-design/icons"; +import { Button, Card, List } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useParams } from "react-router-dom"; +import { logImEXEvent } from "../../../../firebase/firebase.utils"; +import { GenerateDocument, GenerateDocuments } from "../../../../utils/RenderTemplate"; +import { TemplateList } from "../../../../utils/TemplateConstants"; const TemplateListGenerated = TemplateList(); -export default function JobIntakeTemplateList({templates}) { - const {jobId} = useParams(); - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); +export default function JobIntakeTemplateList({ templates }) { + const { jobId } = useParams(); + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); - const renderTemplate = async (templateKey) => { - setLoading(true); - logImEXEvent("job_checklist_template_render"); + const renderTemplate = async (templateKey) => { + setLoading(true); + logImEXEvent("job_checklist_template_render"); - await GenerateDocument( - { - name: templateKey, - variables: {id: jobId}, - }, - {}, - "p" - ); - setLoading(false); - }; - - const renderAllTemplates = async () => { - logImEXEvent("checklist_render_all_templates"); - setLoading(true); - - await GenerateDocuments( - templates.map((key) => { - return {name: key, variables: {id: jobId}}; - }) - ); - setLoading(false); - }; - - return ( - - {t("checklist.actions.printall")} - - } - > - ( - renderTemplate(template)} - > - - , - ]} - > - - - )} - /> - + await GenerateDocument( + { + name: templateKey, + variables: { id: jobId } + }, + {}, + "p" ); + setLoading(false); + }; + + const renderAllTemplates = async () => { + logImEXEvent("checklist_render_all_templates"); + setLoading(true); + + await GenerateDocuments( + templates.map((key) => { + return { name: key, variables: { id: jobId } }; + }) + ); + setLoading(false); + }; + + return ( + + {t("checklist.actions.printall")} + + } + > + ( + renderTemplate(template)}> + + + ]} + > + + + )} + /> + + ); } diff --git a/client/src/components/job-checklist/job-checklist-display.component.jsx b/client/src/components/job-checklist/job-checklist-display.component.jsx index 83b087b7e..e664c2bab 100644 --- a/client/src/components/job-checklist/job-checklist-display.component.jsx +++ b/client/src/components/job-checklist/job-checklist-display.component.jsx @@ -1,11 +1,11 @@ import React from "react"; import ConfigFormComponents from "../config-form-components/config-form-components.component"; -export default function JobChecklistDisplay({checklist}) { - if (!checklist) return
; - return ( -
- -
- ); +export default function JobChecklistDisplay({ checklist }) { + if (!checklist) return
; + return ( +
+ +
+ ); } diff --git a/client/src/components/job-checklist/job-checklist.component.jsx b/client/src/components/job-checklist/job-checklist.component.jsx index c2357bf54..fe54277a9 100644 --- a/client/src/components/job-checklist/job-checklist.component.jsx +++ b/client/src/components/job-checklist/job-checklist.component.jsx @@ -1,19 +1,19 @@ import React from "react"; import JobChecklistTemplateList from "./components/job-checklist-template-list/job-checklist-template-list.component"; import JobChecklistForm from "./components/job-checklist-form/job-checklist-form.component"; -import {Col, Row} from "antd"; +import { Col, Row } from "antd"; -export default function JobIntakeComponent({checklistConfig, type, job}) { - const {form, templates} = checklistConfig; +export default function JobIntakeComponent({ checklistConfig, type, job }) { + const { form, templates } = checklistConfig; - return ( - -
- - - - - - - ); + return ( + + + + + + + + + ); } diff --git a/client/src/components/job-costing-modal/job-costing-modal.component.jsx b/client/src/components/job-costing-modal/job-costing-modal.component.jsx index 9d7ace44f..5eded72b9 100644 --- a/client/src/components/job-costing-modal/job-costing-modal.component.jsx +++ b/client/src/components/job-costing-modal/job-costing-modal.component.jsx @@ -1,32 +1,27 @@ -import {Typography} from "antd"; +import { Typography } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import JobCostingPartsTable from "../job-costing-parts-table/job-costing-parts-table.component"; import JobCostingStatistics from "../job-costing-statistics/job-costing-statistics.component"; import JobCostingPie from "./job-costing-modal.pie.component"; -export default function JobCostingModalComponent({ - summaryData, - costCenterData, - }) { - const {t} = useTranslation(); +export default function JobCostingModalComponent({ summaryData, costCenterData }) { + const { t } = useTranslation(); - return ( -
- - -
-
- - {t("jobs.labels.sales")} - - -
-
- {t("jobs.labels.cost")} - -
-
+ return ( +
+ + +
+
+ {t("jobs.labels.sales")} +
- ); +
+ {t("jobs.labels.cost")} + +
+
+
+ ); } diff --git a/client/src/components/job-costing-modal/job-costing-modal.container.jsx b/client/src/components/job-costing-modal/job-costing-modal.container.jsx index a1d8f764d..31fd7f949 100644 --- a/client/src/components/job-costing-modal/job-costing-modal.container.jsx +++ b/client/src/components/job-costing-modal/job-costing-modal.container.jsx @@ -1,72 +1,63 @@ -import {Modal} from "antd"; +import { Modal } from "antd"; import axios from "axios"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectJobCosting} from "../../redux/modals/modals.selectors"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectJobCosting } from "../../redux/modals/modals.selectors"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import JobCostingModalComponent from "./job-costing-modal.component"; const mapStateToProps = createStructuredSelector({ - jobCostingModal: selectJobCosting, + jobCostingModal: selectJobCosting }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("jobCosting")), + toggleModalVisible: () => dispatch(toggleModalVisible("jobCosting")) }); -export function JobCostingModalContainer({ - jobCostingModal, - toggleModalVisible, - }) { - const {t} = useTranslation(); - const [costingData, setCostingData] = useState(null); - const {open, context} = jobCostingModal; - const {jobId} = context; +export function JobCostingModalContainer({ jobCostingModal, toggleModalVisible }) { + const { t } = useTranslation(); + const [costingData, setCostingData] = useState(null); + const { open, context } = jobCostingModal; + const { jobId } = context; - useEffect(() => { - async function getData() { - if (jobId && open) { - const {data} = await axios.post("/job/costing", {jobid: jobId}); + useEffect(() => { + async function getData() { + if (jobId && open) { + const { data } = await axios.post("/job/costing", { jobid: jobId }); - setCostingData(data); - } - } + setCostingData(data); + } + } - getData(); - }, [jobId, open]); + getData(); + }, [jobId, open]); - return ( - { - toggleModalVisible(); - setCostingData(null); - }} - onCancel={() => { - toggleModalVisible(); - setCostingData(null); - }} - cancelButtonProps={{style: {display: "none"}}} - width="90%" - destroyOnClose - > - {!costingData ? ( - - ) : ( - - )} - - ); + return ( + { + toggleModalVisible(); + setCostingData(null); + }} + onCancel={() => { + toggleModalVisible(); + setCostingData(null); + }} + cancelButtonProps={{ style: { display: "none" } }} + width="90%" + destroyOnClose + > + {!costingData ? ( + + ) : ( + + )} + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobCostingModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(JobCostingModalContainer); diff --git a/client/src/components/job-costing-modal/job-costing-modal.pie.component.jsx b/client/src/components/job-costing-modal/job-costing-modal.pie.component.jsx index 9e7f17344..736372740 100644 --- a/client/src/components/job-costing-modal/job-costing-modal.pie.component.jsx +++ b/client/src/components/job-costing-modal/job-costing-modal.pie.component.jsx @@ -1,68 +1,54 @@ -import React, {useCallback, useMemo} from "react"; -import {Cell, Pie, PieChart, ResponsiveContainer} from "recharts"; +import React, { useCallback, useMemo } from "react"; +import { Cell, Pie, PieChart, ResponsiveContainer } from "recharts"; import Dinero from "dinero.js"; -export default function JobCostingPieComponent({ - type = "sales", - costCenterData, - }) { - const Calculatedata = useCallback( - (data) => { - if (data && data.length > 0) { - return data.reduce((acc, i) => { - const value = - type === "sales" - ? Dinero(i.sales_dinero).getAmount() - : Dinero(i.costs_dinero).getAmount(); +export default function JobCostingPieComponent({ type = "sales", costCenterData }) { + const Calculatedata = useCallback( + (data) => { + if (data && data.length > 0) { + return data.reduce((acc, i) => { + const value = type === "sales" ? Dinero(i.sales_dinero).getAmount() : Dinero(i.costs_dinero).getAmount(); - if (value > 0) { - acc.push({ - name: i.cost_center, - color: "#" + Math.floor(Math.random() * 16777215).toString(16), + if (value > 0) { + acc.push({ + name: i.cost_center, + color: "#" + Math.floor(Math.random() * 16777215).toString(16), - label: `${i.cost_center} - ${ - type === "sales" - ? Dinero(i.sales_dinero).toFormat() - : Dinero(i.costs_dinero).toFormat() - }`, - value: - type === "sales" - ? Dinero(i.sales_dinero).getAmount() - : Dinero(i.costs_dinero).getAmount(), - }); - } - return acc; - }, []); - } else { - return []; - } - }, - [type] - ); + label: `${i.cost_center} - ${ + type === "sales" ? Dinero(i.sales_dinero).toFormat() : Dinero(i.costs_dinero).toFormat() + }`, + value: type === "sales" ? Dinero(i.sales_dinero).getAmount() : Dinero(i.costs_dinero).getAmount() + }); + } + return acc; + }, []); + } else { + return []; + } + }, + [type] + ); - const memoizedData = useMemo(() => Calculatedata(costCenterData), [ - costCenterData, - Calculatedata, - ]); + const memoizedData = useMemo(() => Calculatedata(costCenterData), [costCenterData, Calculatedata]); - return ( - - - entry.label} - labelLine - > - {memoizedData.map((entry, index) => ( - - ))} - - - - ); + return ( + + + entry.label} + labelLine + > + {memoizedData.map((entry, index) => ( + + ))} + + + + ); } diff --git a/client/src/components/job-costing-parts-table/job-costing-parts-table.component.jsx b/client/src/components/job-costing-parts-table/job-costing-parts-table.component.jsx index 3194a8206..56e8baf85 100644 --- a/client/src/components/job-costing-parts-table/job-costing-parts-table.component.jsx +++ b/client/src/components/job-costing-parts-table/job-costing-parts-table.component.jsx @@ -1,129 +1,105 @@ -import {Input, Space, Table, Typography} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {alphaSort} from "../../utils/sorters"; +import { Input, Space, Table, Typography } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { alphaSort } from "../../utils/sorters"; import Dinero from "dinero.js"; -import {pageLimit} from "../../utils/config"; +import { pageLimit } from "../../utils/config"; -export default function JobCostingPartsTable({data, summaryData}) { - const [searchText, setSearchText] = useState(""); - const [state, setState] = useState({ - sortedInfo: {}, - }); +export default function JobCostingPartsTable({ data, summaryData }) { + const [searchText, setSearchText] = useState(""); + const [state, setState] = useState({ + sortedInfo: {} + }); - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - const {t} = useTranslation(); + const { t } = useTranslation(); - const columns = [ - { - title: t("bodyshop.fields.responsibilitycenter"), - dataIndex: "cost_center", - key: "cost_center", - sorter: (a, b) => alphaSort(a.cost_center, b.cost_center), - sortOrder: - state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order, - }, - { - title: t("jobs.labels.sales"), - dataIndex: "sales", - key: "sales", - sorter: (a, b) => - parseFloat(a.sales.substring(1)) - parseFloat(b.sales.substring(1)), - sortOrder: - state.sortedInfo.columnKey === "sales" && state.sortedInfo.order, - }, + const columns = [ + { + title: t("bodyshop.fields.responsibilitycenter"), + dataIndex: "cost_center", + key: "cost_center", + sorter: (a, b) => alphaSort(a.cost_center, b.cost_center), + sortOrder: state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order + }, + { + title: t("jobs.labels.sales"), + dataIndex: "sales", + key: "sales", + sorter: (a, b) => parseFloat(a.sales.substring(1)) - parseFloat(b.sales.substring(1)), + sortOrder: state.sortedInfo.columnKey === "sales" && state.sortedInfo.order + }, - { - title: t("jobs.labels.costs"), - dataIndex: "costs", - key: "costs", - sorter: (a, b) => - parseFloat(a.costs.substring(1)) - parseFloat(b.costs.substring(1)), - sortOrder: - state.sortedInfo.columnKey === "costs" && state.sortedInfo.order, - }, + { + title: t("jobs.labels.costs"), + dataIndex: "costs", + key: "costs", + sorter: (a, b) => parseFloat(a.costs.substring(1)) - parseFloat(b.costs.substring(1)), + sortOrder: state.sortedInfo.columnKey === "costs" && state.sortedInfo.order + }, - { - title: t("jobs.labels.gpdollars"), - dataIndex: "gpdollars", - key: "gpdollars", - sorter: (a, b) => - parseFloat(a.gpdollars.substring(1)) - - parseFloat(b.gpdollars.substring(1)), + { + title: t("jobs.labels.gpdollars"), + dataIndex: "gpdollars", + key: "gpdollars", + sorter: (a, b) => parseFloat(a.gpdollars.substring(1)) - parseFloat(b.gpdollars.substring(1)), - sortOrder: - state.sortedInfo.columnKey === "gpdollars" && state.sortedInfo.order, - }, - { - title: t("jobs.labels.gppercent"), - dataIndex: "gppercent", - key: "gppercent", - sorter: (a, b) => - parseFloat(a.gppercent.slice(0, -1) || 0) - - parseFloat(b.gppercent.slice(0, -1) || 0), - sortOrder: - state.sortedInfo.columnKey === "gppercent" && state.sortedInfo.order, - }, - ]; + sortOrder: state.sortedInfo.columnKey === "gpdollars" && state.sortedInfo.order + }, + { + title: t("jobs.labels.gppercent"), + dataIndex: "gppercent", + key: "gppercent", + sorter: (a, b) => parseFloat(a.gppercent.slice(0, -1) || 0) - parseFloat(b.gppercent.slice(0, -1) || 0), + sortOrder: state.sortedInfo.columnKey === "gppercent" && state.sortedInfo.order + } + ]; - const filteredData = - searchText === "" - ? data - : data.filter((d) => - (d.cost_center || "") - .toString() - .toLowerCase() - .includes(searchText.toLowerCase()) - ); + const filteredData = + searchText === "" + ? data + : data.filter((d) => (d.cost_center || "").toString().toLowerCase().includes(searchText.toLowerCase())); - return ( -
-
{ - return ( - - { - e.preventDefault(); - setSearchText(e.target.value); - }} - /> - - ); + return ( +
+
{ + return ( + + { + e.preventDefault(); + setSearchText(e.target.value); }} - scroll={{ - x: "50%", //y: "40rem" - }} - onChange={handleTableChange} - pagination={{position: "top", defaultPageSize: pageLimit}} - columns={columns} - rowKey="id" - dataSource={filteredData} - summary={() => ( - - - - {t("general.labels.totals")} - - - - {Dinero(summaryData.totalSales).toFormat()} - - - {Dinero(summaryData.totalCost).toFormat()} - - - {Dinero(summaryData.gpdollars).toFormat()} - - - - )} - /> - - ); + /> + + ); + }} + scroll={{ + x: "50%" //y: "40rem" + }} + onChange={handleTableChange} + pagination={{ position: "top", defaultPageSize: pageLimit }} + columns={columns} + rowKey="id" + dataSource={filteredData} + summary={() => ( + + + {t("general.labels.totals")} + + {Dinero(summaryData.totalSales).toFormat()} + {Dinero(summaryData.totalCost).toFormat()} + {Dinero(summaryData.gpdollars).toFormat()} + + + )} + /> + + ); } diff --git a/client/src/components/job-costing-statistics/job-costing-statistics.component.jsx b/client/src/components/job-costing-statistics/job-costing-statistics.component.jsx index fc06e40eb..38a832d91 100644 --- a/client/src/components/job-costing-statistics/job-costing-statistics.component.jsx +++ b/client/src/components/job-costing-statistics/job-costing-statistics.component.jsx @@ -1,63 +1,33 @@ -import {Statistic} from "antd"; +import { Statistic } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import Dinero from "dinero.js"; -export default function JobCostingStatistics({summaryData}) { - const {t} = useTranslation(); +export default function JobCostingStatistics({ summaryData }) { + const { t } = useTranslation(); - return ( -
-
- - - - - - - - - - - - -
-
- ); + return ( +
+
+ + + + + + + + + + + + +
+
+ ); } diff --git a/client/src/components/job-create-iou/job-create-iou.component.jsx b/client/src/components/job-create-iou/job-create-iou.component.jsx index 6fee70d89..0aed7b2ea 100644 --- a/client/src/components/job-create-iou/job-create-iou.component.jsx +++ b/client/src/components/job-create-iou/job-create-iou.component.jsx @@ -1,98 +1,95 @@ -import {useApolloClient} from "@apollo/client"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; -import {Button, notification, Popconfirm} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {UPDATE_JOB_LINES_IOU} from "../../graphql/jobs-lines.queries"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; -import {CreateIouForJob} from "../jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; +import { useApolloClient } from "@apollo/client"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { Button, notification, Popconfirm } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { UPDATE_JOB_LINES_IOU } from "../../graphql/jobs-lines.queries"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import { CreateIouForJob } from "../jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, - technician: selectTechnician, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser, + technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(JobCreateIOU); -export function JobCreateIOU({bodyshop, currentUser, job, selectedJobLines, technician}) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const client = useApolloClient(); - const history = useNavigate(); +export function JobCreateIOU({ bodyshop, currentUser, job, selectedJobLines, technician }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const client = useApolloClient(); + const history = useNavigate(); + const { + treatments: { IOU_Tracking } + } = useSplitTreatments({ + attributes: {}, + names: ["IOU_Tracking"], + splitKey: bodyshop.imexshopid + }); - const {treatments: {IOU_Tracking}} = useSplitTreatments({ - attributes: {}, - names: ["IOU_Tracking"], - splitKey: bodyshop.imexshopid, + if (IOU_Tracking.treatment !== "on") return null; + + const handleCreateIou = async () => { + setLoading(true); + //Query all of the job details to recreate. + const iouId = await CreateIouForJob( + client, + job.id, + { + status: bodyshop.md_ro_statuses.default_open, + bodyshopid: bodyshop.id, + useremail: currentUser.email + }, + selectedJobLines + ); + notification.open({ + type: "success", + message: t("jobs.successes.ioucreated"), + onClick: () => history(`/manage/jobs/${iouId}`) + }); + const selectedJobLinesIds = selectedJobLines.map((l) => l.id); + await client.mutate({ + mutation: UPDATE_JOB_LINES_IOU, + variables: { ids: selectedJobLinesIds }, + update(cache) { + cache.modify({ + id: cache.identify(job.id), + fields: { + joblines(existingJobLines, { readField }) { + return existingJobLines.map((a) => { + if (!selectedJobLinesIds.includes(a.id)) return a; + return { ...a, ioucreated: true }; + }); + } + } + }); + } }); - if (IOU_Tracking.treatment !== "on") return null; + setLoading(false); + }; - const handleCreateIou = async () => { - setLoading(true); - //Query all of the job details to recreate. - const iouId = await CreateIouForJob( - client, - job.id, - { - status: bodyshop.md_ro_statuses.default_open, - bodyshopid: bodyshop.id, - useremail: currentUser.email, - }, - selectedJobLines - ); - notification.open({ - type: "success", - message: t("jobs.successes.ioucreated"), - onClick: () => history(`/manage/jobs/${iouId}`), - }); - const selectedJobLinesIds = selectedJobLines.map((l) => l.id); - await client.mutate({ - mutation: UPDATE_JOB_LINES_IOU, - variables: {ids: selectedJobLinesIds}, - update(cache) { - cache.modify({ - id: cache.identify(job.id), - fields: { - joblines(existingJobLines, {readField}) { - return existingJobLines.map((a) => { - if (!selectedJobLinesIds.includes(a.id)) return a; - return {...a, ioucreated: true}; - }); - }, - }, - }); - }, - }); - - setLoading(false); - }; - - return ( - - - - ); + return ( + + + + ); } diff --git a/client/src/components/job-damage-visual/job-damage-visual.component.jsx b/client/src/components/job-damage-visual/job-damage-visual.component.jsx index e6d2e3ce5..7efa6186d 100644 --- a/client/src/components/job-damage-visual/job-damage-visual.component.jsx +++ b/client/src/components/job-damage-visual/job-damage-visual.component.jsx @@ -1,748 +1,461 @@ import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; -const Car = ({dmg1, dmg2}) => { - const {t} = useTranslation(); +const Car = ({ dmg1, dmg2 }) => { + const { t } = useTranslation(); - return ( -
- {t("jobs.labels.cards.damage")} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - // - // x - // - } - - -
- ); + return ( +
+ {t("jobs.labels.cards.damage")} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + // + // x + // + } + + +
+ ); }; export default Car; diff --git a/client/src/components/job-detail-cards/job-detail-cards.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.component.jsx index dc126089f..8374dec90 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.component.jsx @@ -1,15 +1,15 @@ -import {PrinterFilled} from "@ant-design/icons"; -import {useQuery} from "@apollo/client"; -import {Button, Card, Col, Divider, Drawer, Grid, Row, Space} from "antd"; +import { PrinterFilled } from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import { Button, Card, Col, Divider, Drawer, Grid, Row, Space } from "antd"; import queryString from "query-string"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {QUERY_JOB_CARD_DETAILS} from "../../graphql/jobs.queries"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import JobSyncButton from "../job-sync-button/job-sync-button.component"; import JobsDetailHeader from "../jobs-detail-header/jobs-detail-header.component"; @@ -23,151 +23,119 @@ import JobDetailCardsPartsComponent from "./job-detail-cards.parts.component"; import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setPrintCenterContext: (context) => - dispatch(setModalContext({context: context, modal: "printCenter"})), + setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" })) }); const span = { - lg: {span: 24}, - xl: {span: 12}, - xxl: {span: 8}, + lg: { span: 24 }, + xl: { span: 12 }, + xxl: { span: 8 } }; -export function JobDetailCards({bodyshop, setPrintCenterContext}) { - const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) - .filter((screen) => !!screen[1]) - .slice(-1)[0]; +export function JobDetailCards({ bodyshop, setPrintCenterContext }) { + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) + .filter((screen) => !!screen[1]) + .slice(-1)[0]; - const bpoints = { - xs: "100%", - sm: "100%", - md: "100%", - lg: "75%", - xl: "75%", - xxl: "60%", - }; - const drawerPercentage = selectedBreakpoint - ? bpoints[selectedBreakpoint[0]] - : "100%"; + const bpoints = { + xs: "100%", + sm: "100%", + md: "100%", + lg: "75%", + xl: "75%", + xxl: "60%" + }; + const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%"; - const searchParams = queryString.parse(useLocation().search); - const {selected} = searchParams; - const history = useNavigate(); - const {loading, error, data, refetch} = useQuery(QUERY_JOB_CARD_DETAILS, { - variables: {id: selected}, - skip: !selected, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", + const searchParams = queryString.parse(useLocation().search); + const { selected } = searchParams; + const history = useNavigate(); + const { loading, error, data, refetch } = useQuery(QUERY_JOB_CARD_DETAILS, { + variables: { id: selected }, + skip: !selected, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + + const { t } = useTranslation(); + const handleDrawerClose = () => { + delete searchParams.selected; + history({ + search: queryString.stringify({ + ...searchParams + }) }); + }; - const {t} = useTranslation(); - const handleDrawerClose = () => { - delete searchParams.selected; - history({ - search: queryString.stringify({ - ...searchParams, - }), - }); - }; + return ( + + {loading ? : null} + {error ? : null} + {data ? ( + {data.jobs_by_pk.ro_number || t("general.labels.na")} + } + extra={ + + - return ( - { + setPrintCenterContext({ + actions: { refetch: refetch }, + context: { + id: data.jobs_by_pk.id, + job: data.jobs_by_pk, + type: "job" + } + }); + }} + > + + {t("jobs.actions.printCenter")} + + + + + + } > - {loading ? : null} - {error ? : null} - {data ? ( - - {data.jobs_by_pk.ro_number || t("general.labels.na")} - - } - extra={ - - - - - - - - - } - > - - - - - - - - - - - - - - - - {!bodyshop.uselocalmediaserver && ( - - - - )} - - - - - - - - - ) : null} - - ); + + + + + + + + + + + + + + + + {!bodyshop.uselocalmediaserver && ( + + + + )} + + + + + + + + + ) : null} + + ); } export default connect(mapStateToProps, mapDispatchToProps)(JobDetailCards); diff --git a/client/src/components/job-detail-cards/job-detail-cards.damage.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.damage.component.jsx index c1d399881..e899344d4 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.damage.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.damage.component.jsx @@ -1,18 +1,14 @@ import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import CardTemplate from "./job-detail-cards.template.component"; import Car from "../job-damage-visual/job-damage-visual.component"; -export default function JobDetailCardsDamageComponent({loading, data}) { - const {t} = useTranslation(); - const {area_of_damage} = data; - return ( - - {area_of_damage ? ( - - ) : ( - t("jobs.errors.nodamage") - )} - - ); +export default function JobDetailCardsDamageComponent({ loading, data }) { + const { t } = useTranslation(); + const { area_of_damage } = data; + return ( + + {area_of_damage ? : t("jobs.errors.nodamage")} + + ); } diff --git a/client/src/components/job-detail-cards/job-detail-cards.dates.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.dates.component.jsx index ebb1ae042..ea3e7828f 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.dates.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.dates.component.jsx @@ -1,188 +1,189 @@ -import {Timeline} from "antd"; +import { Timeline } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; +import { useTranslation } from "react-i18next"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; import CardTemplate from "./job-detail-cards.template.component"; -export default function JobDetailCardsDatesComponent({loading, data}) { - const {t} = useTranslation(); +export default function JobDetailCardsDatesComponent({ loading, data }) { + const { t } = useTranslation(); - return ( - - {data ? ( - - - {data.date_last_contacted} - - ), - }, - ] - : []), - ...(data.date_open - ? [ - { - key: "date_open", - children: ( - <> - - {data.date_open} - - ), - }, - ] - : []), - ...(data.date_estimated - ? [ - { - key: "date_estimated", - children: ( - <> - - {data.date_estimated} - - ), - }, - ] - : []), - ...(data.date_scheduled - ? [ - { - key: "date_scheduled", - children: ( - <> - - {data.date_scheduled} - - ), - }, - ] - : []), - ...(data.scheduled_in - ? [ - { - key: "scheduled_in", - children: ( - <> - - {data.scheduled_in} - - ), - }, - ] - : []), - ...(data.actual_in - ? [ - { - key: "actual_in", - children: ( - <> - - {data.actual_in} - - ), - }, - ] - : []), - ...(data.date_repairstarted - ? [ - { - key: "date_repairstarted", - children: ( - <> - - {data.date_repairstarted} - - ), - }, - ] - : []), - ...(data.scheduled_completion - ? [ - { - key: "scheduled_completion", - children: ( - <> - - {data.scheduled_completion} - - ), - }, - ] - : []), - ...(data.actual_completion - ? [ - { - key: "actual_completion", - children: ( - <> - - {data.actual_completion} - - ), - }, - ] - : []), - ...(data.scheduled_delivery - ? [ - { - key: "scheduled_delivery", - children: ( - <> - - {data.scheduled_delivery} - - ), - }, - ] - : []), - ...(data.actual_delivery - ? [ - { - key: "actual_delivery", - children: ( - <> - - {data.actual_delivery} - - ), - }, - ] - : []), - ...(data.date_invoiced - ? [ - { - key: "date_invoiced", - children: ( - <> - - {data.date_invoiced} - - ), - }, - ] - : []), - ...(data.date_exported - ? [ - { - key: "date_exported", - children: ( - <> - - {data.date_exported} - - ), - }, - ] - : []), - ]} - />) : null} - - ); + return ( + + {data ? ( + + + {data.date_last_contacted} + + ) + } + ] + : []), + ...(data.date_open + ? [ + { + key: "date_open", + children: ( + <> + + {data.date_open} + + ) + } + ] + : []), + ...(data.date_estimated + ? [ + { + key: "date_estimated", + children: ( + <> + + {data.date_estimated} + + ) + } + ] + : []), + ...(data.date_scheduled + ? [ + { + key: "date_scheduled", + children: ( + <> + + {data.date_scheduled} + + ) + } + ] + : []), + ...(data.scheduled_in + ? [ + { + key: "scheduled_in", + children: ( + <> + + {data.scheduled_in} + + ) + } + ] + : []), + ...(data.actual_in + ? [ + { + key: "actual_in", + children: ( + <> + + {data.actual_in} + + ) + } + ] + : []), + ...(data.date_repairstarted + ? [ + { + key: "date_repairstarted", + children: ( + <> + + {data.date_repairstarted} + + ) + } + ] + : []), + ...(data.scheduled_completion + ? [ + { + key: "scheduled_completion", + children: ( + <> + + {data.scheduled_completion} + + ) + } + ] + : []), + ...(data.actual_completion + ? [ + { + key: "actual_completion", + children: ( + <> + + {data.actual_completion} + + ) + } + ] + : []), + ...(data.scheduled_delivery + ? [ + { + key: "scheduled_delivery", + children: ( + <> + + {data.scheduled_delivery} + + ) + } + ] + : []), + ...(data.actual_delivery + ? [ + { + key: "actual_delivery", + children: ( + <> + + {data.actual_delivery} + + ) + } + ] + : []), + ...(data.date_invoiced + ? [ + { + key: "date_invoiced", + children: ( + <> + + {data.date_invoiced} + + ) + } + ] + : []), + ...(data.date_exported + ? [ + { + key: "date_exported", + children: ( + <> + + {data.date_exported} + + ) + } + ] + : []) + ]} + /> + ) : null} + + ); } diff --git a/client/src/components/job-detail-cards/job-detail-cards.documents.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.documents.component.jsx index 87999910d..88ebfdde7 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.documents.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.documents.component.jsx @@ -1,34 +1,34 @@ -import {Carousel} from "antd"; +import { Carousel } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {GenerateThumbUrl} from "../jobs-documents-gallery/job-documents.utility"; +import { useTranslation } from "react-i18next"; +import { GenerateThumbUrl } from "../jobs-documents-gallery/job-documents.utility"; import CardTemplate from "./job-detail-cards.template.component"; -export default function JobDetailCardsDocumentsComponent({loading, data}) { - const {t} = useTranslation(); - - if (!data) - return ( - - null - - ); +export default function JobDetailCardsDocumentsComponent({ loading, data }) { + const { t } = useTranslation(); + if (!data) return ( - - {data.documents.length > 0 ? ( - - {data.documents.map((item) => ( - {item.name}/ - ))} - - ) : ( -
{t("documents.errors.nodocuments")}
- )} -
+ + null + ); + + return ( + + {data.documents.length > 0 ? ( + + {data.documents.map((item) => ( + {item.name} + ))} + + ) : ( +
{t("documents.errors.nodocuments")}
+ )} +
+ ); } diff --git a/client/src/components/job-detail-cards/job-detail-cards.insurance.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.insurance.component.jsx index fa598fa24..97a8554c8 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.insurance.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.insurance.component.jsx @@ -1,45 +1,41 @@ import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import DataLabel from "../data-label/data-label.component"; import CardTemplate from "./job-detail-cards.template.component"; -export default function JobDetailCardsInsuranceComponent({loading, data}) { - const {t} = useTranslation(); +export default function JobDetailCardsInsuranceComponent({ loading, data }) { + const { t } = useTranslation(); - return ( - - {data ? ( - + return ( + + {data ? ( + {data.ins_co_nm} {data.clm_no} - + {data.ins_ea ? ( - -
{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}
-
- ) : ( +
{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}
+
+ ) : ( +
{`${data.ins_ct_fn || ""} ${data.ins_ct_ln || ""}`}
)}
- + {data.ins_ea ? ( - -
{`${data.est_ct_fn || ""} ${data.est_ct_ln || ""}`}
-
- ) : ( +
{`${data.est_ct_fn || ""} ${data.est_ct_ln || ""}`}
+
+ ) : ( +
{`${data.est_ct_fn || ""} ${data.est_ct_ln || ""}`}
)}
- ) : null} -
- ); + ) : null} +
+ ); } diff --git a/client/src/components/job-detail-cards/job-detail-cards.notes.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.notes.component.jsx index 2f46f68c7..1acd895f5 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.notes.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.notes.component.jsx @@ -1,7 +1,7 @@ -import {List} from "antd"; -import {AuditOutlined, EyeInvisibleFilled, WarningFilled,} from "@ant-design/icons"; +import { List } from "antd"; +import { AuditOutlined, EyeInvisibleFilled, WarningFilled } from "@ant-design/icons"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import CardTemplate from "./job-detail-cards.template.component"; import styled from "styled-components"; @@ -10,33 +10,31 @@ const Container = styled.div` overflow-y: auto; `; -export default function JobDetailCardsNotesComponent({loading, data}) { - const {t} = useTranslation(); +export default function JobDetailCardsNotesComponent({ loading, data }) { + const { t } = useTranslation(); - return ( - - {data ? ( - - ( - - {item.critical ? ( - - ) : null} - {item.private ? : null} - {item.audit ? : null} - {item.text} - - )} - /> - - ) : null} - - ); + return ( + + {data ? ( + + ( + + {item.critical ? : null} + {item.private ? : null} + {item.audit ? : null} + {item.text} + + )} + /> + + ) : null} + + ); } diff --git a/client/src/components/job-detail-cards/job-detail-cards.parts.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.parts.component.jsx index d1d0db6c1..92e875f99 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.parts.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.parts.component.jsx @@ -1,121 +1,102 @@ -import {Table} from "antd"; +import { Table } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component"; import PartsStatusPie from "../parts-status-pie/parts-status-pie.component"; import CardTemplate from "./job-detail-cards.template.component"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {onlyUnique} from "../../utils/arrayHelper"; -import {alphaSort} from "../../utils/sorters"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { onlyUnique } from "../../utils/arrayHelper"; +import { alphaSort } from "../../utils/sorters"; import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component"; import JobLineStatusPopup from "../job-line-status-popup/job-line-status-popup.component"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - jobRO: selectJobReadOnly, + //currentUser: selectCurrentUser + jobRO: selectJobReadOnly }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobDetailCardsPartsComponent); +export default connect(mapStateToProps, mapDispatchToProps)(JobDetailCardsPartsComponent); -export function JobDetailCardsPartsComponent({loading, data, jobRO}) { - const {t} = useTranslation(); - const {joblines_status} = data; +export function JobDetailCardsPartsComponent({ loading, data, jobRO }) { + const { t } = useTranslation(); + const { joblines_status } = data; - const columns = [ - { - title: t("joblines.fields.line_desc"), - dataIndex: "line_desc", - fixed: "left", - key: "line_desc", - sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), - onCell: (record) => ({ - className: record.manual_line && "job-line-manual", - style: { - ...(record.critical ? {boxShadow: " -.5em 0 0 #FFC107"} : {}), - }, - }), - width: "30%", - ellipsis: true, - }, - { - title: t("joblines.fields.part_type"), - dataIndex: "part_type", - key: "part_type", - width: "15%", - sorter: (a, b) => - alphaSort( - t(`joblines.fields.part_types.${a.part_type}`), - t(`joblines.fields.part_types.${b.part_type}`) - ), - render: (text, record) => - record.part_type - ? t(`joblines.fields.part_types.${record.part_type}`) - : null, - }, - { - title: t("joblines.fields.part_qty"), - dataIndex: "part_qty", - key: "part_qty", - width: "10%", - }, - { - title: t("joblines.fields.notes"), - dataIndex: "notes", - key: "notes", - render: (text, record) => ( - - ), - }, - { - title: t("joblines.fields.location"), - dataIndex: "location", - key: "location", - sorter: (a, b) => alphaSort(a.location, b.location), - render: (text, record) => ( - - ), - }, - { - title: t("joblines.fields.status"), - dataIndex: "status", - key: "status", - sorter: (a, b) => alphaSort(a.status, b.status), - filters: - (data && - data.joblines - ?.map((l) => l.status) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Status*", - value: [s], - }; - })) || - [], - onFilter: (value, record) => value.includes(record.status), - render: (text, record) => ( - - ), - }, - ]; - return ( -
- - -
- - - ); + const columns = [ + { + title: t("joblines.fields.line_desc"), + dataIndex: "line_desc", + fixed: "left", + key: "line_desc", + sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), + onCell: (record) => ({ + className: record.manual_line && "job-line-manual", + style: { + ...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {}) + } + }), + width: "30%", + ellipsis: true + }, + { + title: t("joblines.fields.part_type"), + dataIndex: "part_type", + key: "part_type", + width: "15%", + sorter: (a, b) => + alphaSort(t(`joblines.fields.part_types.${a.part_type}`), t(`joblines.fields.part_types.${b.part_type}`)), + render: (text, record) => (record.part_type ? t(`joblines.fields.part_types.${record.part_type}`) : null) + }, + { + title: t("joblines.fields.part_qty"), + dataIndex: "part_qty", + key: "part_qty", + width: "10%" + }, + { + title: t("joblines.fields.notes"), + dataIndex: "notes", + key: "notes", + render: (text, record) => + }, + { + title: t("joblines.fields.location"), + dataIndex: "location", + key: "location", + sorter: (a, b) => alphaSort(a.location, b.location), + render: (text, record) => + }, + { + title: t("joblines.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => alphaSort(a.status, b.status), + filters: + (data && + data.joblines + ?.map((l) => l.status) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Status*", + value: [s] + }; + })) || + [], + onFilter: (value, record) => value.includes(record.status), + render: (text, record) => + } + ]; + return ( +
+ + +
+ + + ); } diff --git a/client/src/components/job-detail-cards/job-detail-cards.template.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.template.component.jsx index c233e5741..eb56b0b47 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.template.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.template.component.jsx @@ -1,47 +1,31 @@ -import {Card} from "antd"; +import { Card } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; const mapStateToProps = createStructuredSelector({ - technician: selectTechnician, + technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobDetailCardTemplate); +export default connect(mapStateToProps, mapDispatchToProps)(JobDetailCardTemplate); -export function JobDetailCardTemplate({ - loading, - title, - extraLink, - technician, - ...otherProps - }) { - const {t} = useTranslation(); +export function JobDetailCardTemplate({ loading, title, extraLink, technician, ...otherProps }) { + const { t } = useTranslation(); - let extra; - if (extraLink && !technician) - extra = { - extra: {t("jobs.labels.cards.more")}, - }; - return ( - - {otherProps.children} - - ); + let extra; + if (extraLink && !technician) + extra = { + extra: {t("jobs.labels.cards.more")} + }; + return ( + + {otherProps.children} + + ); } diff --git a/client/src/components/job-detail-cards/job-detail-cards.totals.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.totals.component.jsx index 3dc014ac4..ddcdb2c68 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.totals.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.totals.component.jsx @@ -1,35 +1,35 @@ -import {Statistic} from "antd"; +import { Statistic } from "antd"; import Dinero from "dinero.js"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import CardTemplate from "./job-detail-cards.template.component"; -export default function JobDetailCardsTotalsComponent({loading, data}) { - const {t} = useTranslation(); +export default function JobDetailCardsTotalsComponent({ loading, data }) { + const { t } = useTranslation(); - return ( - - {data.job_totals ? ( -
- - - -
- ) : ( - t("jobs.errors.nofinancial") - )} -
- ); + return ( + + {data.job_totals ? ( +
+ + + +
+ ) : ( + t("jobs.errors.nofinancial") + )} +
+ ); } diff --git a/client/src/components/job-detail-cards/job-detail-cards.vehicle.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.vehicle.component.jsx index ae85eb127..3a3f2388e 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.vehicle.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.vehicle.component.jsx @@ -1,23 +1,23 @@ import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import CardTemplate from "./job-detail-cards.template.component"; -export default function JobDetailCardsVehicleComponent({loading, data}) { - const {t} = useTranslation(); +export default function JobDetailCardsVehicleComponent({ loading, data }) { + const { t } = useTranslation(); - return ( - - {data ? ( - + return ( + + {data ? ( + {` ${data.vehicle?.v_model_yr || t("general.labels.na")} ${ - data.vehicle?.v_make_desc || t("general.labels.na") + data.vehicle?.v_make_desc || t("general.labels.na") } ${data.vehicle?.v_model_desc || t("general.labels.na")}`} - ) : null} - - ); + ) : null} + + ); } diff --git a/client/src/components/job-detail-lines/job-lines-expander.component.jsx b/client/src/components/job-detail-lines/job-lines-expander.component.jsx index 932f32232..1152dc546 100644 --- a/client/src/components/job-detail-lines/job-lines-expander.component.jsx +++ b/client/src/components/job-detail-lines/job-lines-expander.component.jsx @@ -1,138 +1,133 @@ -import {useQuery} from "@apollo/client"; -import {Col, Divider, Row, Skeleton, Space, Timeline, Typography} from "antd"; +import { useQuery } from "@apollo/client"; +import { Col, Divider, Row, Skeleton, Space, Timeline, Typography } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {Link} from "react-router-dom"; -import {GET_JOB_LINE_ORDERS} from "../../graphql/jobs.queries"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; +import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {DateFormatter} from "../../utils/DateFormatter"; +import { DateFormatter } from "../../utils/DateFormatter"; import AlertComponent from "../alert/alert.component"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander); -export function JobLinesExpander({jobline, jobid, bodyshop}) { - const {t} = useTranslation(); - const {loading, error, data} = useQuery(GET_JOB_LINE_ORDERS, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - variables: { - joblineid: jobline.id, - }, - }); +export function JobLinesExpander({ jobline, jobid, bodyshop }) { + const { t } = useTranslation(); + const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + joblineid: jobline.id + } + }); - if (loading) return ; - if (error) return ; + if (loading) return ; + if (error) return ; - return ( - - - - {t("parts_orders.labels.parts_orders")} - - 0 - ? data.parts_order_lines.map((line) => ({ - key: line.id, - children: ( - } wrap> - - {line.parts_order.order_number} - - {line.parts_order.order_date} - {line.parts_order.vendor.name} - - ), - })) - : [ - { - key: "no-orders", - children: t("parts_orders.labels.notyetordered"), - }, - ] - } - /> - - {t("bills.labels.bills")} - 0 - ? data.billlines.map((line) => ({ - key: line.id, - children: ( - - - - {line.bill.invoice_number} - - - - {`${t("billlines.fields.actual_price")}: `}{line.actual_price} - - - {`${t("billlines.fields.actual_cost")}: `}{line.actual_cost} - - - {line.bill.date} - - {line.bill.vendor.name} - - ), - })) - : [ - { - key: "no-orders", - children: t("parts_orders.labels.notyetordered"), - }, - ] - } - /> - - - - {t("parts_dispatch.labels.parts_dispatch")} - - 0 ? ( - data.parts_dispatch_lines.map((line) => ({ - key: line.id, - children: ( - } wrap> - - {line.parts_dispatch.number} - - { - bodyshop.employees.find( - (e) => e.id === line.parts_dispatch.employeeid - )?.first_name - } - - {t("parts_dispatch_lines.fields.accepted_at")} - {line.accepted_at} - - - ) - })) - ) : ({ - key: 'dispatch-lines', - children: t("parts_orders.labels.notyetordered"), - }) - }/> - - - ); + return ( + + + {t("parts_orders.labels.parts_orders")} + 0 + ? data.parts_order_lines.map((line) => ({ + key: line.id, + children: ( + } wrap> + + {line.parts_order.order_number} + + {line.parts_order.order_date} + {line.parts_order.vendor.name} + + ) + })) + : [ + { + key: "no-orders", + children: t("parts_orders.labels.notyetordered") + } + ] + } + />{" "} + + + {t("bills.labels.bills")} + 0 + ? data.billlines.map((line) => ({ + key: line.id, + children: ( + + + + {line.bill.invoice_number} + + + + + {`${t("billlines.fields.actual_price")}: `} + {line.actual_price} + + + + + {`${t("billlines.fields.actual_cost")}: `} + {line.actual_cost} + + + + {line.bill.date} + + {line.bill.vendor.name} + + ) + })) + : [ + { + key: "no-orders", + children: t("parts_orders.labels.notyetordered") + } + ] + } + /> + + + {t("parts_dispatch.labels.parts_dispatch")} + 0 + ? data.parts_dispatch_lines.map((line) => ({ + key: line.id, + children: ( + } wrap> + {line.parts_dispatch.number} + {bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name} + + {t("parts_dispatch_lines.fields.accepted_at")} + {line.accepted_at} + + + ) + })) + : { + key: "dispatch-lines", + children: t("parts_orders.labels.notyetordered") + } + } + /> + + + ); } 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 e20f186c3..9b394af71 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 @@ -9,96 +9,89 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr"; import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component"; import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component"; -export default function JobLinesPartPriceChange({job, line, refetch}) { - const [loading, setLoading] = useState(false); - const [updatePartPrice] = useMutation(UPDATE_LINE_PPC); +export default function JobLinesPartPriceChange({ job, line, refetch }) { + const [loading, setLoading] = useState(false); + const [updatePartPrice] = useMutation(UPDATE_LINE_PPC); - const handleFinish = async (values) => { - try { - setLoading(true); - const result = await updatePartPrice({ - variables: { - id: line.id, - jobline: { - act_price_before_ppc: line.act_price_before_ppc - ? line.act_price_before_ppc - : line.act_price, - act_price: values.act_price, - }, - }, - }); - await axios.post("/job/totalsssu", { - id: job.id, - }); - if (result.errors) { - notification.open({ - type: "error", - message: t("joblines.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - if (refetch) refetch(); - } else { - notification.open({ - type: "success", - message: t("joblines.successes.saved"), - }); - } - } catch (error) { - notification.open({ - type: "error", - message: t("joblines.errors.saving", {error: JSON.stringify(error)}), - }); - } finally { - setLoading(false); + const handleFinish = async (values) => { + try { + setLoading(true); + const result = await updatePartPrice({ + variables: { + id: line.id, + jobline: { + act_price_before_ppc: line.act_price_before_ppc ? line.act_price_before_ppc : line.act_price, + act_price: values.act_price + } } - }; + }); + await axios.post("/job/totalsssu", { + id: job.id + }); + if (result.errors) { + notification.open({ + type: "error", + message: t("joblines.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + if (refetch) refetch(); + } else { + notification.open({ + type: "success", + message: t("joblines.successes.saved") + }); + } + } catch (error) { + notification.open({ + type: "error", + message: t("joblines.errors.saving", { error: JSON.stringify(error) }) + }); + } finally { + setLoading(false); + } + }; - const popcontent = InstanceRenderManager({ - imex: null, - rome: ( -
- - - - - + const popcontent = InstanceRenderManager({ + imex: null, + rome: ( +
+ + + + + ), - promanager: null - }) ; + promanager: null + }); - return ( - - - - {line.db_ref === "900510" || line.db_ref === "900511" - ? line.prt_dsmk_m - : line.act_price} - - {line.prt_dsmk_p && line.prt_dsmk_p !== 0 ? ( - {`(${line.prt_dsmk_p}%)`} - ) : ( - <> - )} - {line.act_price_before_ppc && line.act_price_before_ppc !== 0 ? ( - - - ( - {line.act_price_before_ppc} - ) + return ( + + + + {line.db_ref === "900510" || line.db_ref === "900511" ? line.prt_dsmk_m : line.act_price} + + {line.prt_dsmk_p && line.prt_dsmk_p !== 0 ? ( + {`(${line.prt_dsmk_p}%)`} + ) : ( + <> + )} + {line.act_price_before_ppc && line.act_price_before_ppc !== 0 ? ( + + + ({line.act_price_before_ppc}) - - ) : ( - <> - )} - - - ); + + ) : ( + <> + )} + + + ); } 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 492fa57df..8dfa60132 100644 --- a/client/src/components/job-detail-lines/job-lines.component.jsx +++ b/client/src/components/job-detail-lines/job-lines.component.jsx @@ -6,56 +6,53 @@ import { MinusCircleTwoTone, PlusCircleTwoTone, SyncOutlined, - WarningFilled, -} from '@ant-design/icons'; -import { PageHeader } from '@ant-design/pro-layout'; -import { useMutation } from '@apollo/client'; -import { Button, Dropdown, Input, Space, Table, Tag } from 'antd'; -import axios from 'axios'; -import React, { useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { connect } from 'react-redux'; -import { createStructuredSelector } from 'reselect'; -import { DELETE_JOB_LINE_BY_PK } from '../../graphql/jobs-lines.queries'; -import { selectJobReadOnly } from '../../redux/application/application.selectors'; -import { setModalContext } from '../../redux/modals/modals.actions'; -import { selectTechnician } from '../../redux/tech/tech.selectors'; -import { onlyUnique } from '../../utils/arrayHelper'; -import { alphaSort } from '../../utils/sorters'; -import JobLineLocationPopup from '../job-line-location-popup/job-line-location-popup.component'; -import JobLineNotePopup from '../job-line-note-popup/job-line-note-popup.component'; -import JobLineStatusPopup from '../job-line-status-popup/job-line-status-popup.component'; -import JobLinesBillRefernece from '../job-lines-bill-reference/job-lines-bill-reference.component'; + WarningFilled +} from "@ant-design/icons"; +import { PageHeader } from "@ant-design/pro-layout"; +import { useMutation } from "@apollo/client"; +import { Button, Dropdown, Input, Space, Table, Tag } from "antd"; +import axios from "axios"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { DELETE_JOB_LINE_BY_PK } from "../../graphql/jobs-lines.queries"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { onlyUnique } from "../../utils/arrayHelper"; +import { alphaSort } from "../../utils/sorters"; +import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component"; +import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component"; +import JobLineStatusPopup from "../job-line-status-popup/job-line-status-popup.component"; +import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-reference.component"; // import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container"; // import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container"; // import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container"; -import { useSplitTreatments } from '@splitsoftware/splitio-react'; -import _ from 'lodash'; -import { selectBodyshop } from '../../redux/user/user.selectors'; -import dayjs from '../../utils/day'; -import { HasFeatureAccess } from '../feature-wrapper/feature-wrapper.component'; -import JobCreateIOU from '../job-create-iou/job-create-iou.component'; -import JobLineBulkAssignComponent from '../job-line-bulk-assign/job-line-bulk-assign.component'; -import JobLineDispatchButton from '../job-line-dispatch-button/job-line-dispatch-button.component'; -import JoblineTeamAssignment from '../job-line-team-assignment/job-line-team-assignmnent.component'; -import JobSendPartPriceChangeComponent from '../job-send-parts-price-change/job-send-parts-price-change.component'; -import PartsOrderModalContainer from '../parts-order-modal/parts-order-modal.container'; -import JobLinesExpander from './job-lines-expander.component'; -import JobLinesPartPriceChange from './job-lines-part-price-change.component'; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import _ from "lodash"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import dayjs from "../../utils/day"; +import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; +import JobCreateIOU from "../job-create-iou/job-create-iou.component"; +import JobLineBulkAssignComponent from "../job-line-bulk-assign/job-line-bulk-assign.component"; +import JobLineDispatchButton from "../job-line-dispatch-button/job-line-dispatch-button.component"; +import JoblineTeamAssignment from "../job-line-team-assignment/job-line-team-assignmnent.component"; +import JobSendPartPriceChangeComponent from "../job-send-parts-price-change/job-send-parts-price-change.component"; +import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container"; +import JobLinesExpander from "./job-lines-expander.component"; +import JobLinesPartPriceChange from "./job-lines-part-price-change.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, jobRO: selectJobReadOnly, - technician: selectTechnician, + technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ - setJobLineEditContext: (context) => - dispatch(setModalContext({ context: context, modal: 'jobLineEdit' })), - setPartsOrderContext: (context) => - dispatch(setModalContext({ context: context, modal: 'partsOrder' })), - setBillEnterContext: (context) => - dispatch(setModalContext({ context: context, modal: 'billEnter' })), + setJobLineEditContext: (context) => dispatch(setModalContext({ context: context, modal: "jobLineEdit" })), + setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })), + setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })) }); export function JobLinesComponent({ @@ -70,153 +67,147 @@ export function JobLinesComponent({ job, setJobLineEditContext, form, - setBillEnterContext, + setBillEnterContext }) { const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK); const { - treatments: { Enhanced_Payroll }, + treatments: { Enhanced_Payroll } } = useSplitTreatments({ attributes: {}, - names: ['Enhanced_Payroll'], - splitKey: bodyshop.imexshopid, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid }); const [selectedLines, setSelectedLines] = useState([]); const [state, setState] = useState({ sortedInfo: {}, - filteredInfo: {}, + filteredInfo: {} }); const { t } = useTranslation(); const jobIsPrivate = bodyshop.md_ins_cos.find((c) => c.name === job.ins_co_nm)?.private; const columns = [ { - title: '#', - dataIndex: 'line_no', - key: 'line_no', + title: "#", + dataIndex: "line_no", + key: "line_no", sorter: (a, b) => a.line_no - b.line_no, - fixed: 'left', - sortOrder: state.sortedInfo.columnKey === 'line_no' && state.sortedInfo.order, + fixed: "left", + sortOrder: state.sortedInfo.columnKey === "line_no" && state.sortedInfo.order }, { - title: t('joblines.fields.line_desc'), - dataIndex: 'line_desc', - fixed: 'left', - key: 'line_desc', + title: t("joblines.fields.line_desc"), + dataIndex: "line_desc", + fixed: "left", + key: "line_desc", sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), onCell: (record) => ({ - className: record.manual_line && 'job-line-manual', + className: record.manual_line && "job-line-manual", style: { - ...(record.critical ? { boxShadow: ' -.5em 0 0 #FFC107' } : {}), - }, + ...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {}) + } }), - sortOrder: state.sortedInfo.columnKey === 'line_desc' && state.sortedInfo.order, - ellipsis: true, + sortOrder: state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order, + ellipsis: true }, { - title: t('joblines.fields.oem_partno'), - dataIndex: 'oem_partno', - key: 'oem_partno', + title: t("joblines.fields.oem_partno"), + dataIndex: "oem_partno", + key: "oem_partno", sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno), - sortOrder: state.sortedInfo.columnKey === 'oem_partno' && state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order, ellipsis: true, onCell: (record) => ({ - className: record.manual_line && 'job-line-manual', + className: record.manual_line && "job-line-manual", style: { - ...(record.parts_dispatch_lines[0]?.accepted_at - ? { boxShadow: ' -.5em 0 0 #FFC107' } - : {}), - }, + ...(record.parts_dispatch_lines[0]?.accepted_at ? { boxShadow: " -.5em 0 0 #FFC107" } : {}) + } }), render: (text, record) => ( - {`${record.oem_partno || ''} ${record.alt_partno ? `(${record.alt_partno})` : ''}`.trim()} + {`${record.oem_partno || ""} ${record.alt_partno ? `(${record.alt_partno})` : ""}`.trim()} - ), + ) }, { - title: t('joblines.fields.op_code_desc'), - dataIndex: 'op_code_desc', - key: 'op_code_desc', + title: t("joblines.fields.op_code_desc"), + dataIndex: "op_code_desc", + key: "op_code_desc", sorter: (a, b) => alphaSort(a.op_code_desc, b.op_code_desc), - sortOrder: state.sortedInfo.columnKey === 'op_code_desc' && state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "op_code_desc" && state.sortedInfo.order, ellipsis: true, - render: (text, record) => - `${record.op_code_desc || ''}${record.alt_partm ? ` ${record.alt_partm}` : ''}`, + render: (text, record) => `${record.op_code_desc || ""}${record.alt_partm ? ` ${record.alt_partm}` : ""}` }, { - title: t('joblines.fields.part_type'), - dataIndex: 'part_type', - key: 'part_type', + title: t("joblines.fields.part_type"), + dataIndex: "part_type", + key: "part_type", filteredValue: state.filteredInfo.part_type || null, sorter: (a, b) => alphaSort(a.part_type, b.part_type), - sortOrder: state.sortedInfo.columnKey === 'part_type' && state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "part_type" && state.sortedInfo.order, filters: [ { - text: t('jobs.labels.partsfilter'), - value: ['PAN', 'PAC', 'PAR', 'PAL', 'PAA', 'PAM', 'PAP', 'PAS', 'PASL', 'PAG'], + text: t("jobs.labels.partsfilter"), + value: ["PAN", "PAC", "PAR", "PAL", "PAA", "PAM", "PAP", "PAS", "PASL", "PAG"] }, { - text: t('joblines.fields.part_types.PAN'), - value: ['PAN'], + text: t("joblines.fields.part_types.PAN"), + value: ["PAN"] }, { - text: t('joblines.fields.part_types.PAP'), - value: ['PAP'], + text: t("joblines.fields.part_types.PAP"), + value: ["PAP"] }, { - text: t('joblines.fields.part_types.PAL'), - value: ['PAL'], + text: t("joblines.fields.part_types.PAL"), + value: ["PAL"] }, { - text: t('joblines.fields.part_types.PAA'), - value: ['PAA'], + text: t("joblines.fields.part_types.PAA"), + value: ["PAA"] }, { - text: t('joblines.fields.part_types.PAG'), - value: ['PAG'], + text: t("joblines.fields.part_types.PAG"), + value: ["PAG"] }, { - text: t('joblines.fields.part_types.PAS'), - value: ['PAS'], + text: t("joblines.fields.part_types.PAS"), + value: ["PAS"] }, { - text: t('joblines.fields.part_types.PASL'), - value: ['PASL'], + text: t("joblines.fields.part_types.PASL"), + value: ["PASL"] }, { - text: t('joblines.fields.part_types.PAC'), - value: ['PAC'], + text: t("joblines.fields.part_types.PAC"), + value: ["PAC"] }, { - text: t('joblines.fields.part_types.PAR'), - value: ['PAR'], + text: t("joblines.fields.part_types.PAR"), + value: ["PAR"] }, { - text: t('joblines.fields.part_types.PAM'), - value: ['PAM'], - }, + text: t("joblines.fields.part_types.PAM"), + value: ["PAM"] + } ], onFilter: (value, record) => value.includes(record.part_type), - render: (text, record) => - record.part_type ? t(`joblines.fields.part_types.${record.part_type}`) : null, + render: (text, record) => (record.part_type ? t(`joblines.fields.part_types.${record.part_type}`) : null) }, { - title: t('joblines.fields.act_price'), - dataIndex: 'act_price', - key: 'act_price', + title: t("joblines.fields.act_price"), + dataIndex: "act_price", + key: "act_price", sorter: (a, b) => a.act_price - b.act_price, - sortOrder: state.sortedInfo.columnKey === 'act_price' && state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order, ellipsis: true, - render: (text, record) => ( - - ), + render: (text, record) => }, { - title: t('joblines.fields.part_qty'), - dataIndex: 'part_qty', - key: 'part_qty', + title: t("joblines.fields.part_qty"), + dataIndex: "part_qty", + key: "part_qty" }, // { @@ -240,73 +231,70 @@ export function JobLinesComponent({ // ), // }, { - title: t('joblines.fields.mod_lbr_ty'), - dataIndex: 'mod_lbr_ty', - key: 'mod_lbr_ty', + title: t("joblines.fields.mod_lbr_ty"), + dataIndex: "mod_lbr_ty", + key: "mod_lbr_ty", sorter: (a, b) => alphaSort(a.mod_lbr_ty, b.mod_lbr_ty), - sortOrder: state.sortedInfo.columnKey === 'mod_lbr_ty' && state.sortedInfo.order, - render: (text, record) => - record.mod_lbr_ty ? t(`joblines.fields.lbr_types.${record.mod_lbr_ty}`) : null, + sortOrder: state.sortedInfo.columnKey === "mod_lbr_ty" && state.sortedInfo.order, + render: (text, record) => (record.mod_lbr_ty ? t(`joblines.fields.lbr_types.${record.mod_lbr_ty}`) : null) }, { - title: t('joblines.fields.mod_lb_hrs'), - dataIndex: 'mod_lb_hrs', - key: 'mod_lb_hrs', + title: t("joblines.fields.mod_lb_hrs"), + dataIndex: "mod_lb_hrs", + key: "mod_lb_hrs", sorter: (a, b) => a.mod_lb_hrs - b.mod_lb_hrs, - sortOrder: state.sortedInfo.columnKey === 'mod_lb_hrs' && state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order }, { - title: t('joblines.fields.line_ind'), - dataIndex: 'line_ind', - key: 'line_ind', + title: t("joblines.fields.line_ind"), + dataIndex: "line_ind", + key: "line_ind", sorter: (a, b) => alphaSort(a.line_ind, b.line_ind), - sortOrder: state.sortedInfo.columnKey === 'line_ind' && state.sortedInfo.order, - responsive: ['md'], + sortOrder: state.sortedInfo.columnKey === "line_ind" && state.sortedInfo.order, + responsive: ["md"] }, - ...(Enhanced_Payroll.treatment === 'on' + ...(Enhanced_Payroll.treatment === "on" ? [ { - title: t('joblines.fields.assigned_team'), - dataIndex: 'assigned_team', - key: 'assigned_team', - render: (text, record) => ( - - ), - }, + title: t("joblines.fields.assigned_team"), + dataIndex: "assigned_team", + key: "assigned_team", + render: (text, record) => + } ] : []), { - title: t('joblines.fields.notes'), - dataIndex: 'notes', - key: 'notes', - render: (text, record) => , + title: t("joblines.fields.notes"), + dataIndex: "notes", + key: "notes", + render: (text, record) => }, { - title: t('joblines.fields.location'), - dataIndex: 'location', - key: 'location', - render: (text, record) => , + title: t("joblines.fields.location"), + dataIndex: "location", + key: "location", + render: (text, record) => }, - ...(HasFeatureAccess({ featureName: 'bills', bodyshop }) + ...(HasFeatureAccess({ featureName: "bills", bodyshop }) ? [ { - title: t('joblines.labels.billref'), - dataIndex: 'billref', - key: 'billref', + title: t("joblines.labels.billref"), + dataIndex: "billref", + key: "billref", render: (text, record) => , - responsive: ['md'], - }, + responsive: ["md"] + } ] : []), { - title: t('joblines.fields.status'), - dataIndex: 'status', - key: 'status', + title: t("joblines.fields.status"), + dataIndex: "status", + key: "status", sorter: (a, b) => alphaSort(a.status, b.status), - sortOrder: state.sortedInfo.columnKey === 'status' && state.sortedInfo.order, + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, filteredValue: state.filteredInfo.status || null, filters: @@ -316,18 +304,18 @@ export function JobLinesComponent({ .filter(onlyUnique) .map((s) => { return { - text: s || 'No Status*', - value: [s], + text: s || "No Status*", + value: [s] }; })) || [], onFilter: (value, record) => value.includes(record.status), - render: (text, record) => , + render: (text, record) => }, { - title: t('general.labels.actions'), - dataIndex: 'actions', - key: 'actions', + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", render: (text, record) => ( {(record.manual_line || jobIsPrivate) && ( @@ -337,7 +325,7 @@ export function JobLinesComponent({ onClick={() => { setJobLineEditContext({ actions: { refetch: refetch, submit: form && form.submit }, - context: { ...record, jobid: job.id }, + context: { ...record, jobid: job.id } }); }} > @@ -352,16 +340,14 @@ export function JobLinesComponent({ cache.modify({ fields: { joblines(existingJobLines, { readField }) { - return existingJobLines.filter( - (jlRef) => record.id !== readField('id', jlRef) - ); - }, - }, + return existingJobLines.filter((jlRef) => record.id !== readField("id", jlRef)); + } + } }); - }, + } }); - await axios.post('/job/totalsssu', { - id: job.id, + await axios.post("/job/totalsssu", { + id: job.id }); refetch && refetch(); }} @@ -371,31 +357,29 @@ export function JobLinesComponent({ )} - ), - }, + ) + } ]; const handleTableChange = (pagination, filters, sorter) => { setState((state) => ({ ...state, filteredInfo: filters, - sortedInfo: sorter, + sortedInfo: sorter })); }; const handleMark = (e) => { - if (e.key === 'clear') { + if (e.key === "clear") { setSelectedLines([]); } else { const markedTypes = [e.key]; - if (e.key === 'PAN') markedTypes.push('PAP'); - if (e.key === 'PAS') markedTypes.push('PASL'); + if (e.key === "PAN") markedTypes.push("PAP"); + if (e.key === "PAS") markedTypes.push("PASL"); setSelectedLines((selectedLines) => _.uniq([ ...selectedLines, - ...jobLines.filter( - (item) => markedTypes.includes(item.part_type) || markedTypes.includes(item.mod_lbr_ty) - ), + ...jobLines.filter((item) => markedTypes.includes(item.part_type) || markedTypes.includes(item.mod_lbr_ty)) ]) ); } @@ -404,35 +388,35 @@ export function JobLinesComponent({ const markMenu = { onClick: handleMark, items: [ - { key: 'PAA', label: t('joblines.fields.part_types.PAA') }, - { key: 'PAN', label: t('joblines.fields.part_types.PAN') }, - { key: 'PAL', label: t('joblines.fields.part_types.PAL') }, - { key: 'PAS', label: t('joblines.fields.part_types.PAS') }, - { type: 'divider' }, - { key: 'LAA', label: t('joblines.fields.lbr_types.LAA') }, - { key: 'LAB', label: t('joblines.fields.lbr_types.LAB') }, - { key: 'LAD', label: t('joblines.fields.lbr_types.LAD') }, - { key: 'LAE', label: t('joblines.fields.lbr_types.LAE') }, - { key: 'LAF', label: t('joblines.fields.lbr_types.LAF') }, - { key: 'LAG', label: t('joblines.fields.lbr_types.LAG') }, - { key: 'LAM', label: t('joblines.fields.lbr_types.LAM') }, - { key: 'LAR', label: t('joblines.fields.lbr_types.LAR') }, - { key: 'LAS', label: t('joblines.fields.lbr_types.LAS') }, - { key: 'LAU', label: t('joblines.fields.lbr_types.LAU') }, - { key: 'LA1', label: t('joblines.fields.lbr_types.LA1') }, - { key: 'LA2', label: t('joblines.fields.lbr_types.LA2') }, - { key: 'LA3', label: t('joblines.fields.lbr_types.LA3') }, - { key: 'LA4', label: t('joblines.fields.lbr_types.LA4') }, - { type: 'divider' }, - { key: 'clear', label: t('general.labels.clear') }, - ], + { key: "PAA", label: t("joblines.fields.part_types.PAA") }, + { key: "PAN", label: t("joblines.fields.part_types.PAN") }, + { key: "PAL", label: t("joblines.fields.part_types.PAL") }, + { key: "PAS", label: t("joblines.fields.part_types.PAS") }, + { type: "divider" }, + { key: "LAA", label: t("joblines.fields.lbr_types.LAA") }, + { key: "LAB", label: t("joblines.fields.lbr_types.LAB") }, + { key: "LAD", label: t("joblines.fields.lbr_types.LAD") }, + { key: "LAE", label: t("joblines.fields.lbr_types.LAE") }, + { key: "LAF", label: t("joblines.fields.lbr_types.LAF") }, + { key: "LAG", label: t("joblines.fields.lbr_types.LAG") }, + { key: "LAM", label: t("joblines.fields.lbr_types.LAM") }, + { key: "LAR", label: t("joblines.fields.lbr_types.LAR") }, + { key: "LAS", label: t("joblines.fields.lbr_types.LAS") }, + { key: "LAU", label: t("joblines.fields.lbr_types.LAU") }, + { key: "LA1", label: t("joblines.fields.lbr_types.LA1") }, + { key: "LA2", label: t("joblines.fields.lbr_types.LA2") }, + { key: "LA3", label: t("joblines.fields.lbr_types.LA3") }, + { key: "LA4", label: t("joblines.fields.lbr_types.LA4") }, + { type: "divider" }, + { key: "clear", label: t("general.labels.clear") } + ] }; return (
- - + + - {bodyshop.region_config.toLowerCase().startsWith('us') && ( - - )} + {bodyshop.region_config.toLowerCase().startsWith("us") && } { e.preventDefault(); setSearchText(e.target.value); @@ -595,7 +546,7 @@ export function JobLinesComponent({ dataSource={jobLines} onChange={handleTableChange} scroll={{ - x: true, + x: true }} expandable={{ expandedRowRender: (record) => , @@ -606,7 +557,7 @@ export function JobLinesComponent({ onExpand(record, e)} /> ) : ( onExpand(record, e)} /> - ), + ) }} onRow={(record, rowIndex) => { return { @@ -615,7 +566,7 @@ export function JobLinesComponent({ notMatchingLines.length !== selectedLines.length ? setSelectedLines(notMatchingLines) : setSelectedLines([...selectedLines, record]); - }, // double click row + } // double click row }; }} rowSelection={{ @@ -625,11 +576,11 @@ export function JobLinesComponent({ }, onSelect: (record, selected, selectedRows, nativeEvent) => { if (selected) { - setSelectedLines((selectedLines) => _.uniqBy([...selectedLines, record], 'id')); + setSelectedLines((selectedLines) => _.uniqBy([...selectedLines, record], "id")); } else { setSelectedLines((selectedLines) => selectedLines.filter((l) => l.id !== record.id)); } - }, + } }} />
diff --git a/client/src/components/job-detail-lines/job-lines.container.jsx b/client/src/components/job-detail-lines/job-lines.container.jsx index 5aa94637d..ee7883273 100644 --- a/client/src/components/job-detail-lines/job-lines.container.jsx +++ b/client/src/components/job-detail-lines/job-lines.container.jsx @@ -1,48 +1,29 @@ -import React, {useMemo, useState} from "react"; +import React, { useMemo, useState } from "react"; import JobLinesComponent from "./job-lines.component"; -function JobLinesContainer({job, joblines, refetch, form, ...rest}) { - const [searchText, setSearchText] = useState(""); +function JobLinesContainer({ job, joblines, refetch, form, ...rest }) { + const [searchText, setSearchText] = useState(""); - const jobLines = useMemo(() => { - return joblines - ? searchText - ? joblines.filter( - (jl) => - (jl.unq_seq || "") - .toString() - .toLowerCase() - .includes(searchText.toLowerCase()) || - (jl.line_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (jl.part_type || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (jl.oem_partno || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (jl.op_code_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (jl.db_price || "") - .toString() - .includes(searchText.toLowerCase()) || - (jl.act_price || "").toString().includes(searchText.toLowerCase()) - ) - : joblines - : []; - }, [joblines, searchText]); + const jobLines = useMemo(() => { + return joblines + ? searchText + ? joblines.filter( + (jl) => + (jl.unq_seq || "").toString().toLowerCase().includes(searchText.toLowerCase()) || + (jl.line_desc || "").toLowerCase().includes(searchText.toLowerCase()) || + (jl.part_type || "").toLowerCase().includes(searchText.toLowerCase()) || + (jl.oem_partno || "").toLowerCase().includes(searchText.toLowerCase()) || + (jl.op_code_desc || "").toLowerCase().includes(searchText.toLowerCase()) || + (jl.db_price || "").toString().includes(searchText.toLowerCase()) || + (jl.act_price || "").toString().includes(searchText.toLowerCase()) + ) + : joblines + : []; + }, [joblines, searchText]); - return ( - - ); + return ( + + ); } export default JobLinesContainer; diff --git a/client/src/components/job-employee-assignments/job-employee-assignments.component.jsx b/client/src/components/job-employee-assignments/job-employee-assignments.component.jsx index 481ffd94b..ea8ddb679 100644 --- a/client/src/components/job-employee-assignments/job-employee-assignments.component.jsx +++ b/client/src/components/job-employee-assignments/job-employee-assignments.component.jsx @@ -1,197 +1,185 @@ -import {DeleteFilled, PlusCircleFilled} from "@ant-design/icons"; -import {Button, Col, Popover, Row, Select, Space, Spin} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { DeleteFilled, PlusCircleFilled } from "@ant-design/icons"; +import { Button, Col, Popover, Row, Select, Space, Spin } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import DataLabel from "../data-label/data-label.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - jobRO: selectJobReadOnly, + bodyshop: selectBodyshop, + jobRO: selectJobReadOnly }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -const iconStyle = {marginLeft: ".3rem"}; +const iconStyle = { marginLeft: ".3rem" }; export function JobEmployeeAssignments({ - bodyshop, - jobRO, - body, - refinish, - prep, - csr, - handleAdd, - handleRemove, - loading, - }) { - const {t} = useTranslation(); - const [assignment, setAssignment] = useState({ - operation: null, - employeeid: null, - }); - const [visibility, setVisibility] = useState(false); + bodyshop, + jobRO, + body, + refinish, + prep, + csr, + handleAdd, + handleRemove, + loading +}) { + const { t } = useTranslation(); + const [assignment, setAssignment] = useState({ + operation: null, + employeeid: null + }); + const [visibility, setVisibility] = useState(false); - const onChange = (value, option) => { - setAssignment({...assignment, employeeid: value, name: option.name}); - }; + const onChange = (value, option) => { + setAssignment({ ...assignment, employeeid: value, name: option.name }); + }; - const popContent = ( - -
- - - - - - - - - - ); + const popContent = ( + + + + + + + + + + + + ); - return ( - - - - {body ? ( -
- {`${body.first_name || ""} ${body.last_name || ""}`} - !jobRO && handleRemove("body")} - /> -
- ) : ( - { - if (!jobRO) { - setAssignment({operation: "body"}); - setVisibility(true); - } - }} - /> - )} -
- - {prep ? ( -
- {`${prep.first_name || ""} ${prep.last_name || ""}`} - !jobRO && handleRemove("prep")} - /> -
- ) : ( - { - if (!jobRO) { - setAssignment({operation: "prep"}); - setVisibility(true); - } - }} - /> - )} -
- - {refinish ? ( -
- {`${refinish.first_name || ""} ${ - refinish.last_name || "" - }`} - !jobRO && handleRemove("refinish")} - /> -
- ) : ( - { - if (!jobRO) { - setAssignment({operation: "refinish"}); - setVisibility(true); - } - }} - /> - )} -
- - {csr ? ( -
- {`${csr.first_name || ""} ${csr.last_name || ""}`} - !jobRO && handleRemove("csr")} - /> -
- ) : ( - { - if (!jobRO) { - setAssignment({operation: "csr"}); - setVisibility(true); - } - }} - /> - )} -
-
-
- ); + return ( + + + + {body ? ( +
+ {`${body.first_name || ""} ${body.last_name || ""}`} + !jobRO && handleRemove("body")} + /> +
+ ) : ( + { + if (!jobRO) { + setAssignment({ operation: "body" }); + setVisibility(true); + } + }} + /> + )} +
+ + {prep ? ( +
+ {`${prep.first_name || ""} ${prep.last_name || ""}`} + !jobRO && handleRemove("prep")} + /> +
+ ) : ( + { + if (!jobRO) { + setAssignment({ operation: "prep" }); + setVisibility(true); + } + }} + /> + )} +
+ + {refinish ? ( +
+ {`${refinish.first_name || ""} ${refinish.last_name || ""}`} + !jobRO && handleRemove("refinish")} + /> +
+ ) : ( + { + if (!jobRO) { + setAssignment({ operation: "refinish" }); + setVisibility(true); + } + }} + /> + )} +
+ + {csr ? ( +
+ {`${csr.first_name || ""} ${csr.last_name || ""}`} + !jobRO && handleRemove("csr")} + /> +
+ ) : ( + { + if (!jobRO) { + setAssignment({ operation: "csr" }); + setVisibility(true); + } + }} + /> + )} +
+
+
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobEmployeeAssignments); +export default connect(mapStateToProps, mapDispatchToProps)(JobEmployeeAssignments); diff --git a/client/src/components/job-employee-assignments/job-employee-assignments.container.jsx b/client/src/components/job-employee-assignments/job-employee-assignments.container.jsx index db8a1e931..f5cc9921e 100644 --- a/client/src/components/job-employee-assignments/job-employee-assignments.container.jsx +++ b/client/src/components/job-employee-assignments/job-employee-assignments.container.jsx @@ -1,116 +1,108 @@ -import {useMutation} from "@apollo/client"; -import {notification} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {UPDATE_JOB_ASSIGNMENTS} from "../../graphql/jobs.queries"; +import { useMutation } from "@apollo/client"; +import { notification } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { UPDATE_JOB_ASSIGNMENTS } from "../../graphql/jobs.queries"; import JobEmployeeAssignmentsComponent from "./job-employee-assignments.component"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {insertAuditTrail} from "../../redux/application/application.actions"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser + //currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobEmployeeAssignmentsContainer); +export default connect(mapStateToProps, mapDispatchToProps)(JobEmployeeAssignmentsContainer); -export function JobEmployeeAssignmentsContainer({ - job, - refetch, - insertAuditTrail, - }) { - const {t} = useTranslation(); - const [updateJob] = useMutation(UPDATE_JOB_ASSIGNMENTS); - const [loading, setLoading] = useState(false); +export function JobEmployeeAssignmentsContainer({ job, refetch, insertAuditTrail }) { + const { t } = useTranslation(); + const [updateJob] = useMutation(UPDATE_JOB_ASSIGNMENTS); + const [loading, setLoading] = useState(false); - const handleAdd = async (assignment) => { - setLoading(true); - const {operation, employeeid, name} = assignment; - logImEXEvent("job_assign_employee", {operation}); + const handleAdd = async (assignment) => { + setLoading(true); + const { operation, employeeid, name } = assignment; + logImEXEvent("job_assign_employee", { operation }); - let empAssignment = determineFieldName(operation); + let empAssignment = determineFieldName(operation); - const result = await updateJob({ - variables: {jobId: job.id, job: {[empAssignment]: employeeid}}, - }); - if (refetch) refetch(); + const result = await updateJob({ + variables: { jobId: job.id, job: { [empAssignment]: employeeid } } + }); + if (refetch) refetch(); - if (!!!result.errors) { + if (!!!result.errors) { insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.jobassignmentchange(operation, name), - type: "jobassignmentchange", + jobid: job.id, + operation: AuditTrailMapping.jobassignmentchange(operation, name), + type: "jobassignmentchange" }); - } else { - notification["error"]({ - message: t("jobs.errors.assigning", { - message: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - }; - const handleRemove = async (operation) => { - setLoading(true); - logImEXEvent("job_unassign_employee", {operation}); + } else { + notification["error"]({ + message: t("jobs.errors.assigning", { + message: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + }; + const handleRemove = async (operation) => { + setLoading(true); + logImEXEvent("job_unassign_employee", { operation }); - let empAssignment = determineFieldName(operation); - const result = await updateJob({ - variables: {jobId: job.id, job: {[empAssignment]: null}}, - }); + let empAssignment = determineFieldName(operation); + const result = await updateJob({ + variables: { jobId: job.id, job: { [empAssignment]: null } } + }); if (!!!result.errors) { insertAuditTrail({ jobid: job.id, operation: AuditTrailMapping.jobassignmentremoved(operation), - type: "jobassignmentremoved", + type: "jobassignmentremoved" }); } else { notification["error"]({ message: t("jobs.errors.assigning", { - message: JSON.stringify(result.errors), - }), + message: JSON.stringify(result.errors) + }) }); } setLoading(false); }; - return ( -
- -
- ); + return ( +
+ +
+ ); } const determineFieldName = (operation) => { - switch (operation) { - case "body": - return "employee_body"; - case "prep": - return "employee_prep"; - case "csr": - return "employee_csr"; - case "refinish": - return "employee_refinish"; + switch (operation) { + case "body": + return "employee_body"; + case "prep": + return "employee_prep"; + case "csr": + return "employee_csr"; + case "refinish": + return "employee_refinish"; - default: - return null; - } + default: + return null; + } }; diff --git a/client/src/components/job-lifecycle/job-lifecycle.component.jsx b/client/src/components/job-lifecycle/job-lifecycle.component.jsx index 26125684e..37eec2572 100644 --- a/client/src/components/job-lifecycle/job-lifecycle.component.jsx +++ b/client/src/components/job-lifecycle/job-lifecycle.component.jsx @@ -1,247 +1,270 @@ -import React, {useCallback, useEffect, useState} from 'react'; -import day from '../../utils/day'; -import axios from 'axios'; -import {Badge, Card, Space, Table, Tag} from 'antd'; -import {gql, useQuery} from "@apollo/client"; -import {DateTimeFormatterFunction} from "../../utils/DateFormatter"; -import {isEmpty} from "lodash"; -import {useTranslation} from "react-i18next"; +import React, { useCallback, useEffect, useState } from "react"; +import day from "../../utils/day"; +import axios from "axios"; +import { Badge, Card, Space, Table, Tag } from "antd"; +import { gql, useQuery } from "@apollo/client"; +import { DateTimeFormatterFunction } from "../../utils/DateFormatter"; +import { isEmpty } from "lodash"; +import { useTranslation } from "react-i18next"; -import './job-lifecycle.styles.scss'; +import "./job-lifecycle.styles.scss"; // show text on bar if text can fit -export function JobLifecycleComponent({job, statuses, ...rest}) { - const [loading, setLoading] = useState(true); - const [lifecycleData, setLifecycleData] = useState(null); - const {t} = useTranslation(); // Used for tracking external state changes. +export function JobLifecycleComponent({ job, statuses, ...rest }) { + const [loading, setLoading] = useState(true); + const [lifecycleData, setLifecycleData] = useState(null); + const { t } = useTranslation(); // Used for tracking external state changes. - const {data} = useQuery(gql` - query get_job_test($id: uuid!){ - jobs_by_pk(id:$id){ - id - status - } + const { data } = useQuery( + gql` + query get_job_test($id: uuid!) { + jobs_by_pk(id: $id) { + id + status } - `, { - variables: { - id: job.id - }, - fetchPolicy: 'cache-only' - }); + } + `, + { + variables: { + id: job.id + }, + fetchPolicy: "cache-only" + } + ); - /** - * Gets the lifecycle data for the job. - * @returns {Promise} - */ - const getLifecycleData = useCallback(async () => { - if (job && job.id && statuses && statuses.statuses) { - try { - setLoading(true); - const response = await axios.post("/job/lifecycle", { - jobids: job.id, - statuses: statuses.statuses - }); - const data = response.data.transition[job.id]; - setLifecycleData(data); - } catch (err) { - console.error(`${t('job_lifecycle.errors.fetch')}: ${err.message}`); - } finally { - setLoading(false); - } + /** + * Gets the lifecycle data for the job. + * @returns {Promise} + */ + const getLifecycleData = useCallback(async () => { + if (job && job.id && statuses && statuses.statuses) { + try { + setLoading(true); + const response = await axios.post("/job/lifecycle", { + jobids: job.id, + statuses: statuses.statuses + }); + const data = response.data.transition[job.id]; + setLifecycleData(data); + } catch (err) { + console.error(`${t("job_lifecycle.errors.fetch")}: ${err.message}`); + } finally { + setLoading(false); + } + } + }, [job, statuses, t]); + + useEffect(() => { + if (!data) return; + setTimeout(() => { + getLifecycleData().catch((err) => console.error(`${t("job_lifecycle.errors.fetch")}: ${err.message}`)); + }, 500); + }, [data, getLifecycleData, t]); + + const columns = [ + { + title: t("job_lifecycle.columns.value"), + dataIndex: "value", + key: "value" + }, + { + title: t("job_lifecycle.columns.start"), + dataIndex: "start", + key: "start", + render: (text) => DateTimeFormatterFunction(text), + sorter: (a, b) => day(a.start).unix() - day(b.start).unix() + }, + { + title: t("job_lifecycle.columns.relative_start"), + dataIndex: "start_readable", + key: "start_readable" + }, + { + title: t("job_lifecycle.columns.end"), + dataIndex: "end", + key: "end", + sorter: (a, b) => { + if (isEmpty(a.end) || isEmpty(b.end)) { + if (isEmpty(a.end) && isEmpty(b.end)) { + return 0; + } + return isEmpty(a.end) ? 1 : -1; } - }, [job, statuses, t]); + return day(a.end).unix() - day(b.end).unix(); + }, + render: (text) => (isEmpty(text) ? t("job_lifecycle.content.not_available") : DateTimeFormatterFunction(text)) + }, + { + title: t("job_lifecycle.columns.relative_end"), + dataIndex: "end_readable", + key: "end_readable" + }, + { + title: t("job_lifecycle.columns.duration"), + dataIndex: "duration_readable", + key: "duration_readable", + sorter: (a, b) => a.duration - b.duration + } + ]; - useEffect(() => { - if (!data) return; - setTimeout(() => { - getLifecycleData().catch(err => console.error(`${t('job_lifecycle.errors.fetch')}: ${err.message}`)); - }, 500); - }, [data, getLifecycleData, t]); + return ( + + {!loading ? ( + lifecycleData && lifecycleData.lifecycle && lifecycleData.durations ? ( + + + + {t("job_lifecycle.content.title_durations")} + + } + style={{ width: "100%" }} + > +
+ {lifecycleData.durations.summations.map((key, index, array) => { + const isFirst = index === 0; + const isLast = index === array.length - 1; + return ( +
DateTimeFormatterFunction(text), - sorter: (a, b) => day(a.start).unix() - day(b.start).unix(), - }, - { - title: t('job_lifecycle.columns.relative_start'), - dataIndex: 'start_readable', - key: 'start_readable', - }, - { - title: t('job_lifecycle.columns.end'), - dataIndex: 'end', - key: 'end', - sorter: (a, b) => { - if (isEmpty(a.end) || isEmpty(b.end)) { - if (isEmpty(a.end) && isEmpty(b.end)) { - return 0; - } - return isEmpty(a.end) ? 1 : -1; - } - return day(a.end).unix() - day(b.end).unix(); - }, - render: (text) => isEmpty(text) ? t('job_lifecycle.content.not_available') : DateTimeFormatterFunction(text) - }, - { - title: t('job_lifecycle.columns.relative_end'), - dataIndex: 'end_readable', - key: 'end_readable', - }, - { - title: t('job_lifecycle.columns.duration'), - dataIndex: 'duration_readable', - key: 'duration_readable', - sorter: (a, b) => a.duration - b.duration, - }, - ]; + borderTop: "1px solid #f0f2f5", + borderBottom: "1px solid #f0f2f5", + borderLeft: isFirst ? "1px solid #f0f2f5" : undefined, + borderRight: isLast ? "1px solid #f0f2f5" : undefined, - return ( - - {!loading ? ( - lifecycleData && lifecycleData.lifecycle && lifecycleData.durations ? ( - - - - {t('job_lifecycle.content.title_durations')} - - - )} - style={{width: '100%'}} - > -
- {lifecycleData.durations.summations.map((key, index, array) => { - const isFirst = index === 0; - const isLast = index === array.length - 1; - return ( -
- - {key.percentage > 15 ? - <> -
{key.roundedPercentage}
-
- {key.status} -
- - : null} -
- ); - })} -
- -
- {lifecycleData.durations.summations.map((key) => ( - -
- {key.status} ({key.roundedPercentage}) -
-
- ))} -
-
- {(lifecycleData?.durations?.humanReadableTotal) || - (lifecycleData.lifecycle[0] && lifecycleData.lifecycle[0].value && lifecycleData?.durations?.totalCurrentStatusDuration?.humanReadable) ? - -
    - {lifecycleData.durations && lifecycleData.durations.humanReadableTotal && -
  • - {t('job_lifecycle.content.previous_status_accumulated_time')}: {lifecycleData.durations.humanReadableTotal} -
  • - } - {lifecycleData.lifecycle[0] && lifecycleData.lifecycle[0].value && lifecycleData?.durations?.totalCurrentStatusDuration?.humanReadable && -
  • - {t('job_lifecycle.content.current_status_accumulated_time')} ({lifecycleData.lifecycle[0].value}): {lifecycleData.durations.totalCurrentStatusDuration.humanReadable} -
  • - } -
-
- : null} -
- - - - {t('job_lifecycle.content.title_transitions')} - - - )}> -
- - - ) : ( - - {t('job_lifecycle.content.data_unavailable')} - - ) - ) : ( - - {t('job_lifecycle.content.loading')} + backgroundColor: key.color, + width: `${key.percentage}%` + }} + aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`} + title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`} + > + {key.percentage > 15 ? ( + <> +
{key.roundedPercentage}
+
+ {key.status} +
+ + ) : null} + + ); + })} + + +
+ {lifecycleData.durations.summations.map((key) => ( + +
+ {key.status} ({key.roundedPercentage}) +
+
+ ))} +
+
+ {lifecycleData?.durations?.humanReadableTotal || + (lifecycleData.lifecycle[0] && + lifecycleData.lifecycle[0].value && + lifecycleData?.durations?.totalCurrentStatusDuration?.humanReadable) ? ( + +
    + {lifecycleData.durations && lifecycleData.durations.humanReadableTotal && ( +
  • + + {t("job_lifecycle.content.previous_status_accumulated_time")}: + {" "} + {lifecycleData.durations.humanReadableTotal} +
  • + )} + {lifecycleData.lifecycle[0] && + lifecycleData.lifecycle[0].value && + lifecycleData?.durations?.totalCurrentStatusDuration?.humanReadable && ( +
  • + + {t("job_lifecycle.content.current_status_accumulated_time")} ( + {lifecycleData.lifecycle[0].value}): + {" "} + {lifecycleData.durations.totalCurrentStatusDuration.humanReadable} +
  • + )} +
- )} + ) : null} +
+ + + + {t("job_lifecycle.content.title_transitions")} + + + } + > +
+ + + ) : ( + + {t("job_lifecycle.content.data_unavailable")} + + ) + ) : ( + + {t("job_lifecycle.content.loading")} - ); + )} + + ); } -export default JobLifecycleComponent; \ No newline at end of file +export default JobLifecycleComponent; diff --git a/client/src/components/job-line-bulk-assign/job-line-bulk-assign.component.jsx b/client/src/components/job-line-bulk-assign/job-line-bulk-assign.component.jsx index 63a9164dd..a64a77b7f 100644 --- a/client/src/components/job-line-bulk-assign/job-line-bulk-assign.component.jsx +++ b/client/src/components/job-line-bulk-assign/job-line-bulk-assign.component.jsx @@ -1,147 +1,128 @@ -import React, {useState} from "react"; +import React, { useState } from "react"; -import {useMutation} from "@apollo/client"; -import {Button, Form, notification, Popover, Select, Space} from "antd"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {UPDATE_LINE_BULK_ASSIGN} from "../../graphql/jobs-lines.queries"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; -import {insertAuditTrail} from "../../redux/application/application.actions"; +import { useMutation } from "@apollo/client"; +import { Button, Form, notification, Popover, Select, Space } from "antd"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { UPDATE_LINE_BULK_ASSIGN } from "../../graphql/jobs-lines.queries"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - jobRO: selectJobReadOnly, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + jobRO: selectJobReadOnly, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation}) => - dispatch(insertAuditTrail({jobid, operation})), + insertAuditTrail: ({ jobid, operation }) => dispatch(insertAuditTrail({ jobid, operation })) }); export default connect(mapStateToProps, mapDispatchToProps)(JoblineBulkAssign); export function JoblineBulkAssign({ - setSelectedLines, - selectedLines, - insertAuditTrail, - bodyshop, - jobRO, - job, - currentUser, - }) { - const [visible, setVisible] = useState(false); - const [loading, setLoading] = useState(false); - const [form] = Form.useForm(); + setSelectedLines, + selectedLines, + insertAuditTrail, + bodyshop, + jobRO, + job, + currentUser +}) { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); - const {t} = useTranslation(); - const [assignLines] = useMutation(UPDATE_LINE_BULK_ASSIGN); + const { t } = useTranslation(); + const [assignLines] = useMutation(UPDATE_LINE_BULK_ASSIGN); - const handleConvert = async (values) => { - try { - setLoading(true); - const result = await assignLines({ - variables: { - jobline: { - assigned_team: values.assigned_team, - }, - ids: selectedLines.map((l) => l.id), - }, - }); - if (result.errors) { - notification.open({ - type: "error", - message: t("parts_dispatch.errors.creating", { - error: JSON.stringify(result.errors), - }), - }); - } else { - //Insert the audit trail here. - - const teamName = bodyshop.employee_teams.find( - (et) => et.id === values.assigned_team - )?.name; - - const hours = selectedLines.reduce( - (acc, val) => (acc += val.mod_lb_hrs), - 0 - ); - - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.assignedlinehours( - teamName, - hours.toFixed(1) - ), - }); - setSelectedLines([]); - setVisible(false); - } - } catch (error) { - notification.open({ - type: "error", - message: t("parts_dispatch.errors.creating", { - error: error, - }), - }); - } finally { - setLoading(false); + const handleConvert = async (values) => { + try { + setLoading(true); + const result = await assignLines({ + variables: { + jobline: { + assigned_team: values.assigned_team + }, + ids: selectedLines.map((l) => l.id) } - }; + }); + if (result.errors) { + notification.open({ + type: "error", + message: t("parts_dispatch.errors.creating", { + error: JSON.stringify(result.errors) + }) + }); + } else { + //Insert the audit trail here. - const popMenu = ( -
-
- - - + const teamName = bodyshop.employee_teams.find((et) => et.id === values.assigned_team)?.name; - - - - - -
- ); + const hours = selectedLines.reduce((acc, val) => (acc += val.mod_lb_hrs), 0); - return ( - - - - ); + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.assignedlinehours(teamName, hours.toFixed(1)) + }); + setSelectedLines([]); + setVisible(false); + } + } catch (error) { + notification.open({ + type: "error", + message: t("parts_dispatch.errors.creating", { + error: error + }) + }); + } finally { + setLoading(false); + } + }; + + const popMenu = ( +
+
+ + + + + + + + + +
+ ); + + return ( + + + + ); } diff --git a/client/src/components/job-line-convert-to-labor/job-line-convert-to-labor.component.jsx b/client/src/components/job-line-convert-to-labor/job-line-convert-to-labor.component.jsx index b27f250bd..a581acd90 100644 --- a/client/src/components/job-line-convert-to-labor/job-line-convert-to-labor.component.jsx +++ b/client/src/components/job-line-convert-to-labor/job-line-convert-to-labor.component.jsx @@ -1,245 +1,195 @@ -import {ClockCircleOutlined} from "@ant-design/icons"; -import {useApolloClient} from "@apollo/client"; -import {Button, Card, Form, notification, Popover, Select, Space, Tooltip,} from "antd"; -import {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {QUERY_JOB_LBR_ADJUSTMENTS, UPDATE_JOB,} from "../../graphql/jobs.queries"; +import { ClockCircleOutlined } from "@ant-design/icons"; +import { useApolloClient } from "@apollo/client"; +import { Button, Card, Form, notification, Popover, Select, Space, Tooltip } from "antd"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { QUERY_JOB_LBR_ADJUSTMENTS, UPDATE_JOB } from "../../graphql/jobs.queries"; import _ from "lodash"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {UPDATE_JOB_LINE} from "../../graphql/jobs-lines.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser + //currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobLineConvertToLabor); +export default connect(mapStateToProps, mapDispatchToProps)(JobLineConvertToLabor); -export function JobLineConvertToLabor({ - children, - jobline, - job, - insertAuditTrail, - ...otherBtnProps - }) { - const {t} = useTranslation(); +export function JobLineConvertToLabor({ children, jobline, job, insertAuditTrail, ...otherBtnProps }) { + const { t } = useTranslation(); - const [loading, setLoading] = useState(false); - const [form] = Form.useForm(); - const [visibility, setVisibility] = useState(false); - const client = useApolloClient(); + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + const [visibility, setVisibility] = useState(false); + const client = useApolloClient(); - const handleFinish = async (values) => { - const {mod_lbr_ty} = values; - logImEXEvent("job_convert_dollar_to_labor"); + const handleFinish = async (values) => { + const { mod_lbr_ty } = values; + logImEXEvent("job_convert_dollar_to_labor"); - setLoading(true); + setLoading(true); - const existingAdjustments = await client.query({ - query: QUERY_JOB_LBR_ADJUSTMENTS, - variables: { - id: job.id, - }, - }); + const existingAdjustments = await client.query({ + query: QUERY_JOB_LBR_ADJUSTMENTS, + variables: { + id: job.id + } + }); - const newAdjustments = _.cloneDeep( - existingAdjustments.data.jobs_by_pk.lbr_adjustments - ); - const adjustment = calculateAdjustment({mod_lbr_ty, job, jobline}); - newAdjustments[mod_lbr_ty] = (newAdjustments[mod_lbr_ty] || 0) + adjustment; + const newAdjustments = _.cloneDeep(existingAdjustments.data.jobs_by_pk.lbr_adjustments); + const adjustment = calculateAdjustment({ mod_lbr_ty, job, jobline }); + newAdjustments[mod_lbr_ty] = (newAdjustments[mod_lbr_ty] || 0) + adjustment; - const jobUpdate = client.mutate({ - mutation: UPDATE_JOB, - variables: { - jobId: job.id, - job: {lbr_adjustments: newAdjustments}, - }, - }); + const jobUpdate = client.mutate({ + mutation: UPDATE_JOB, + variables: { + jobId: job.id, + job: { lbr_adjustments: newAdjustments } + } + }); - const lineUpdate = client.mutate({ - mutation: UPDATE_JOB_LINE, - variables: { - lineId: jobline.id, - line: { - convertedtolbr: true, - convertedtolbr_data: { - mod_lbr_ty: mod_lbr_ty, - mod_lb_hrs: adjustment, - }, - }, - }, - }); - - if (!!jobUpdate.errors) { - notification["error"]({ - message: t("jobs.errors.saving", { - message: JSON.stringify(jobUpdate.errors), - }), - }); - return; + const lineUpdate = client.mutate({ + mutation: UPDATE_JOB_LINE, + variables: { + lineId: jobline.id, + line: { + convertedtolbr: true, + convertedtolbr_data: { + mod_lbr_ty: mod_lbr_ty, + mod_lb_hrs: adjustment + } } - if (!!lineUpdate.errors) { - notification["error"]({ - message: t("joblines.errors.saving", { - message: JSON.stringify(lineUpdate.errors), - }), - }); - return; - } - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.jobmodifylbradj({ - hours: calculateAdjustment({mod_lbr_ty, job, jobline}).toFixed(1), + } + }); + + if (!!jobUpdate.errors) { + notification["error"]({ + message: t("jobs.errors.saving", { + message: JSON.stringify(jobUpdate.errors) + }) + }); + return; + } + if (!!lineUpdate.errors) { + notification["error"]({ + message: t("joblines.errors.saving", { + message: JSON.stringify(lineUpdate.errors) + }) + }); + return; + } + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.jobmodifylbradj({ + hours: calculateAdjustment({ mod_lbr_ty, job, jobline }).toFixed(1), + mod_lbr_ty + }), + type: "jobmodifylbradj" + }); + setLoading(false); + setVisibility(false); + }; + + const overlay = ( + +
+ + + + + + {() => { + const { mod_lbr_ty } = form.getFieldsValue(); + return t("joblines.labels.adjustmenttobeadded", { + adjustment: calculateAdjustment({ mod_lbr_ty, - }), - type: "jobmodifylbradj",}); - setLoading(false); - setVisibility(false); - }; + job, + jobline + }).toFixed(1) + }); + }} + - const overlay = ( - - - - - + + + + + + + ); - - {() => { - const {mod_lbr_ty} = form.getFieldsValue(); - return t("joblines.labels.adjustmenttobeadded", { - adjustment: calculateAdjustment({ - mod_lbr_ty, - job, - jobline, - }).toFixed(1), - }); - }} - + const handleClick = (e) => { + setLoading(true); - - - - - -
- ); + form.setFieldsValue({ + // date: new dayjs(), + // bodyhrs: Math.round(v.bodyhrs * 10) / 10, + // painthrs: Math.round(v.painthrs * 10) / 10, + }); + setVisibility(true); + setLoading(false); + }; - const handleClick = (e) => { - setLoading(true); - - form.setFieldsValue({ - // date: new dayjs(), - // bodyhrs: Math.round(v.bodyhrs * 10) / 10, - // painthrs: Math.round(v.painthrs * 10) / 10, - }); - setVisibility(true); - setLoading(false); - }; - - return ( - <> - {children} - {jobline.act_price !== 0 && ( - - - - - - )} - - ); + return ( + <> + {children} + {jobline.act_price !== 0 && ( + + + + + + )} + + ); } -function calculateAdjustment({mod_lbr_ty, job, jobline}) { - if (!mod_lbr_ty) return 0; - const rate = job[`rate_${mod_lbr_ty.toLowerCase()}`]; +function calculateAdjustment({ mod_lbr_ty, job, jobline }) { + if (!mod_lbr_ty) return 0; + const rate = job[`rate_${mod_lbr_ty.toLowerCase()}`]; - if (rate === 0 || rate === null || rate === undefined) return 0; - const adj = jobline.act_price / job[`rate_${mod_lbr_ty.toLowerCase()}`]; + if (rate === 0 || rate === null || rate === undefined) return 0; + const adj = jobline.act_price / job[`rate_${mod_lbr_ty.toLowerCase()}`]; - return adj; + return adj; } diff --git a/client/src/components/job-line-dispatch-button/job-line-dispatch-button.component.jsx b/client/src/components/job-line-dispatch-button/job-line-dispatch-button.component.jsx index 594102bd2..9331cf4d7 100644 --- a/client/src/components/job-line-dispatch-button/job-line-dispatch-button.component.jsx +++ b/client/src/components/job-line-dispatch-button/job-line-dispatch-button.component.jsx @@ -1,167 +1,145 @@ -import React, {useState} from "react"; +import React, { useState } from "react"; -import {useMutation} from "@apollo/client"; -import {Button, Form, notification, Popover, Select, Space} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Form, notification, Popover, Select, Space } from "antd"; import day from "../../utils/day"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {INSERT_PARTS_DISPATCH} from "../../graphql/parts-dispatch.queries"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_PARTS_DISPATCH } from "../../graphql/parts-dispatch.queries"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - jobRO: selectJobReadOnly, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + jobRO: selectJobReadOnly, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobLineDispatchButton); +export default connect(mapStateToProps, mapDispatchToProps)(JobLineDispatchButton); export function JobLineDispatchButton({ - setSelectedLines, - selectedLines, + setSelectedLines, + selectedLines, - bodyshop, - jobRO, - job, - currentUser, - }) { - const [visible, setVisible] = useState(false); - const [loading, setLoading] = useState(false); - const [form] = Form.useForm(); - const Templates = TemplateList("job_special", { - ro_number: job.ro_number, - }); - const {t} = useTranslation(); - const [dispatchLines] = useMutation(INSERT_PARTS_DISPATCH); + bodyshop, + jobRO, + job, + currentUser +}) { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + const Templates = TemplateList("job_special", { + ro_number: job.ro_number + }); + const { t } = useTranslation(); + const [dispatchLines] = useMutation(INSERT_PARTS_DISPATCH); - const handleConvert = async (values) => { - try { - setLoading(true); - //THIS HAS NOT YET BEEN TESTED. START BY FINISHING THIS FUNCTION. - const result = await dispatchLines({ - variables: { - partsDispatch: { - dispatched_at: day(), - employeeid: values.employeeid, - jobid: job.id, - dispatched_by: currentUser.email, - parts_dispatch_lines: { - data: selectedLines.map((l) => ({ - joblineid: l.id, - quantity: l.part_qty, - })), - }, - }, - //joblineids: selectedLines.map((l) => l.id), - }, - }); - if (result.errors) { - console.log("🚀 ~ handleConvert ~ result.errors:", result.errors) - notification.open({ - type: "error", - message: t("parts_dispatch.errors.creating", { - error: result.errors, - }), - }); - } else { - setSelectedLines([]); - await GenerateDocument( - { - name: Templates.parts_dispatch.key, - variables: { - id: result.data.insert_parts_dispatch_one.id, - }, - }, - {}, - "p" - ); + const handleConvert = async (values) => { + try { + setLoading(true); + //THIS HAS NOT YET BEEN TESTED. START BY FINISHING THIS FUNCTION. + const result = await dispatchLines({ + variables: { + partsDispatch: { + dispatched_at: day(), + employeeid: values.employeeid, + jobid: job.id, + dispatched_by: currentUser.email, + parts_dispatch_lines: { + data: selectedLines.map((l) => ({ + joblineid: l.id, + quantity: l.part_qty + })) } - setVisible(false); - } catch (error) { - console.log("🚀 ~ handleConvert ~ error:", error) - notification.open({ - type: "error", - message: t("parts_dispatch.errors.creating", { - error: error, - }), - }); - } finally { - setLoading(false); + } + //joblineids: selectedLines.map((l) => l.id), } - }; + }); + if (result.errors) { + console.log("🚀 ~ handleConvert ~ result.errors:", result.errors); + notification.open({ + type: "error", + message: t("parts_dispatch.errors.creating", { + error: result.errors + }) + }); + } else { + setSelectedLines([]); + await GenerateDocument( + { + name: Templates.parts_dispatch.key, + variables: { + id: result.data.insert_parts_dispatch_one.id + } + }, + {}, + "p" + ); + } + setVisible(false); + } catch (error) { + console.log("🚀 ~ handleConvert ~ error:", error); + notification.open({ + type: "error", + message: t("parts_dispatch.errors.creating", { + error: error + }) + }); + } finally { + setLoading(false); + } + }; - const popMenu = ( -
-
- - - + const popMenu = ( +
+ + + + - - - - - -
- ); + + + + + +
+ ); - return ( - - - - ); + return ( + + + + ); } diff --git a/client/src/components/job-line-location-popup/job-line-location-popup.component.jsx b/client/src/components/job-line-location-popup/job-line-location-popup.component.jsx index 5860095d3..7171f6d11 100644 --- a/client/src/components/job-line-location-popup/job-line-location-popup.component.jsx +++ b/client/src/components/job-line-location-popup/job-line-location-popup.component.jsx @@ -1,91 +1,85 @@ -import {useMutation} from "@apollo/client"; -import {notification, Select, Space} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {UPDATE_JOB_LINE} from "../../graphql/jobs-lines.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useMutation } from "@apollo/client"; +import { notification, Select, Space } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function JobLineLocationPopup({bodyshop, jobline, disabled}) { - const [editing, setEditing] = useState(false); - const [loading, setLoading] = useState(false); - const [location, setLocation] = useState(jobline.location); - const [updateJob] = useMutation(UPDATE_JOB_LINE); - const {t} = useTranslation(); +export function JobLineLocationPopup({ bodyshop, jobline, disabled }) { + const [editing, setEditing] = useState(false); + const [loading, setLoading] = useState(false); + const [location, setLocation] = useState(jobline.location); + const [updateJob] = useMutation(UPDATE_JOB_LINE); + const { t } = useTranslation(); - useEffect(() => { - if (editing) setLocation(jobline.location); - }, [editing, jobline.location]); + useEffect(() => { + if (editing) setLocation(jobline.location); + }, [editing, jobline.location]); - const handleChange = (e) => { - setLocation(e); - }; + const handleChange = (e) => { + setLocation(e); + }; - const handleSave = async (e) => { - setLoading(true); - const result = await updateJob({ - variables: {lineId: jobline.id, line: {location: location || ""}}, - }); + const handleSave = async (e) => { + setLoading(true); + const result = await updateJob({ + variables: { lineId: jobline.id, line: { location: location || "" } } + }); - if (!!!result.errors) { - notification["success"]({message: t("joblines.successes.saved")}); - } else { - notification["error"]({ - message: t("joblines.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - setEditing(false); - }; + if (!!!result.errors) { + notification["success"]({ message: t("joblines.successes.saved") }); + } else { + notification["error"]({ + message: t("joblines.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + setEditing(false); + }; - if (editing) - return ( -
- - - -
- ); + if (editing) return ( -
!disabled && setEditing(true)} - > - - {jobline.location} - {jobline.parts_dispatch_lines?.length > 0 && "-Disp"} - -
+
+ + + +
); + return ( +
!disabled && setEditing(true)}> + + {jobline.location} + {jobline.parts_dispatch_lines?.length > 0 && "-Disp"} + +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobLineLocationPopup); +export default connect(mapStateToProps, mapDispatchToProps)(JobLineLocationPopup); diff --git a/client/src/components/job-line-note-popup/job-line-note-popup.component.jsx b/client/src/components/job-line-note-popup/job-line-note-popup.component.jsx index aeff30600..11d943354 100644 --- a/client/src/components/job-line-note-popup/job-line-note-popup.component.jsx +++ b/client/src/components/job-line-note-popup/job-line-note-popup.component.jsx @@ -1,78 +1,78 @@ -import {FieldTimeOutlined} from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {Input, notification} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {UPDATE_JOB_LINE} from "../../graphql/jobs-lines.queries"; +import { FieldTimeOutlined } from "@ant-design/icons"; +import { useMutation } from "@apollo/client"; +import { Input, notification } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -export default function JobLineNotePopup({jobline, disabled}) { - const [editing, setEditing] = useState(false); - const [loading, setLoading] = useState(false); - const [note, setNote] = useState(jobline.note); - const [updateJob] = useMutation(UPDATE_JOB_LINE); - const {t} = useTranslation(); +export default function JobLineNotePopup({ jobline, disabled }) { + const [editing, setEditing] = useState(false); + const [loading, setLoading] = useState(false); + const [note, setNote] = useState(jobline.note); + const [updateJob] = useMutation(UPDATE_JOB_LINE); + const { t } = useTranslation(); - useEffect(() => { - if (editing) setNote(jobline.notes); - }, [editing, jobline.notes]); + useEffect(() => { + if (editing) setNote(jobline.notes); + }, [editing, jobline.notes]); - const handleChange = (e) => { - e.stopPropagation(); - setNote(e.currentTarget.value); - }; + const handleChange = (e) => { + e.stopPropagation(); + setNote(e.currentTarget.value); + }; - const handleSave = async (e) => { - e.stopPropagation(); - setLoading(true); - const result = await updateJob({ - variables: {lineId: jobline.id, line: {notes: note || ""}}, - }); + const handleSave = async (e) => { + e.stopPropagation(); + setLoading(true); + const result = await updateJob({ + variables: { lineId: jobline.id, line: { notes: note || "" } } + }); - if (!!!result.errors) { - notification["success"]({message: t("joblines.successes.saved")}); - } else { - notification["error"]({ - message: t("joblines.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - setEditing(false); - }; + if (!!!result.errors) { + notification["success"]({ message: t("joblines.successes.saved") }); + } else { + notification["error"]({ + message: t("joblines.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + setEditing(false); + }; - if (editing) - return ( -
- : null} - value={note} - onChange={handleChange} - onPressEnter={handleSave} - onBlur={handleSave} - /> -
- ); + if (editing) return ( -
-
!disabled && setEditing(true)} - > - {jobline.ioucreated && ( -
- - {t("joblines.labels.ioucreated")} -
- )} - {jobline.notes || null} -
-
+
+ : null} + value={note} + onChange={handleChange} + onPressEnter={handleSave} + onBlur={handleSave} + /> +
); + return ( +
+
!disabled && setEditing(true)} + > + {jobline.ioucreated && ( +
+ + {t("joblines.labels.ioucreated")} +
+ )} + {jobline.notes || null} +
+
+ ); } diff --git a/client/src/components/job-line-status-popup/job-line-status-popup.component.jsx b/client/src/components/job-line-status-popup/job-line-status-popup.component.jsx index f9bf006ae..ecc468010 100644 --- a/client/src/components/job-line-status-popup/job-line-status-popup.component.jsx +++ b/client/src/components/job-line-status-popup/job-line-status-popup.component.jsx @@ -1,85 +1,82 @@ -import {notification, Select} from "antd"; -import React, {useEffect, useState} from "react"; -import {useMutation} from "@apollo/client"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {UPDATE_JOB_LINE} from "../../graphql/jobs-lines.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { notification, Select } from "antd"; +import React, { useEffect, useState } from "react"; +import { useMutation } from "@apollo/client"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function JobLineStatusPopup({bodyshop, jobline, disabled}) { - const [editing, setEditing] = useState(false); - const [loading, setLoading] = useState(false); - const [status, setStatus] = useState(jobline.status); - const [updateJob] = useMutation(UPDATE_JOB_LINE); - const {t} = useTranslation(); +export function JobLineStatusPopup({ bodyshop, jobline, disabled }) { + const [editing, setEditing] = useState(false); + const [loading, setLoading] = useState(false); + const [status, setStatus] = useState(jobline.status); + const [updateJob] = useMutation(UPDATE_JOB_LINE); + const { t } = useTranslation(); - useEffect(() => { - if (editing) setStatus(jobline.status); - }, [editing, jobline.status]); + useEffect(() => { + if (editing) setStatus(jobline.status); + }, [editing, jobline.status]); - const handleChange = (e) => { - setStatus(e); - }; + const handleChange = (e) => { + setStatus(e); + }; - const handleSave = async (e) => { - setLoading(true); - const result = await updateJob({ - variables: {lineId: jobline.id, line: {status: status || ""}}, - }); + const handleSave = async (e) => { + setLoading(true); + const result = await updateJob({ + variables: { lineId: jobline.id, line: { status: status || "" } } + }); - if (!!!result.errors) { - notification["success"]({message: t("joblines.successes.saved")}); - } else { - notification["error"]({ - message: t("joblines.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - setEditing(false); - }; + if (!!!result.errors) { + notification["success"]({ message: t("joblines.successes.saved") }); + } else { + notification["error"]({ + message: t("joblines.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + setEditing(false); + }; - if (editing) - return ( -
- - - -
- ); + if (editing) return ( -
!disabled && setEditing(true)} - > - {jobline.status} -
+
+ + + +
); + return ( +
!disabled && setEditing(true)}> + {jobline.status} +
+ ); } export default connect(mapStateToProps, mapDispatchToProps)(JobLineStatusPopup); diff --git a/client/src/components/job-line-team-assignment/job-line-team-assignmnent.component.jsx b/client/src/components/job-line-team-assignment/job-line-team-assignmnent.component.jsx index 32ca763c6..d80b281fd 100644 --- a/client/src/components/job-line-team-assignment/job-line-team-assignmnent.component.jsx +++ b/client/src/components/job-line-team-assignment/job-line-team-assignmnent.component.jsx @@ -1,116 +1,96 @@ -import {notification, Select} from "antd"; -import React, {useEffect, useState} from "react"; -import {useMutation} from "@apollo/client"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {UPDATE_JOB_LINE} from "../../graphql/jobs-lines.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { notification, Select } from "antd"; +import React, { useEffect, useState } from "react"; +import { useMutation } from "@apollo/client"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -import {insertAuditTrail} from "../../redux/application/application.actions"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation}) => - dispatch(insertAuditTrail({jobid, operation})), + insertAuditTrail: ({ jobid, operation }) => dispatch(insertAuditTrail({ jobid, operation })) }); -export function JoblineTeamAssignment({ - bodyshop, - jobline, - disabled, - jobId, - insertAuditTrail, - }) { - const [editing, setEditing] = useState(false); - const [loading, setLoading] = useState(false); - const [assignedTeam, setAssignedTeam] = useState(jobline.assigned_team); - const [updateJob] = useMutation(UPDATE_JOB_LINE); - const {t} = useTranslation(); +export function JoblineTeamAssignment({ bodyshop, jobline, disabled, jobId, insertAuditTrail }) { + const [editing, setEditing] = useState(false); + const [loading, setLoading] = useState(false); + const [assignedTeam, setAssignedTeam] = useState(jobline.assigned_team); + const [updateJob] = useMutation(UPDATE_JOB_LINE); + const { t } = useTranslation(); - useEffect(() => { - if (editing) setAssignedTeam(jobline.assigned_team); - }, [editing, jobline.assigned_team]); + useEffect(() => { + if (editing) setAssignedTeam(jobline.assigned_team); + }, [editing, jobline.assigned_team]); - const handleChange = (e) => { - setAssignedTeam(e); - }; + const handleChange = (e) => { + setAssignedTeam(e); + }; - const handleSave = async (e) => { - setLoading(true); - const result = await updateJob({ - variables: { - lineId: jobline.id, - line: {assigned_team: assignedTeam}, - }, - }); + const handleSave = async (e) => { + setLoading(true); + const result = await updateJob({ + variables: { + lineId: jobline.id, + line: { assigned_team: assignedTeam } + } + }); - if (!!!result.errors) { - notification["success"]({message: t("joblines.successes.saved")}); - //insert the audit trail here. - const teamName = bodyshop.employee_teams.find( - (et) => et.id === assignedTeam - )?.name; - insertAuditTrail({ - jobid: jobId, - operation: AuditTrailMapping.assignedlinehours( - teamName, - jobline.mod_lb_hrs - ), - }); - } else { - notification["error"]({ - message: t("joblines.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - setEditing(false); - }; - - if (editing) - return ( -
- - - -
- ); - - const team = bodyshop.employee_teams.find( - (tm) => tm.id === jobline.assigned_team - ); + if (!!!result.errors) { + notification["success"]({ message: t("joblines.successes.saved") }); + //insert the audit trail here. + const teamName = bodyshop.employee_teams.find((et) => et.id === assignedTeam)?.name; + insertAuditTrail({ + jobid: jobId, + operation: AuditTrailMapping.assignedlinehours(teamName, jobline.mod_lb_hrs) + }); + } else { + notification["error"]({ + message: t("joblines.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + setEditing(false); + }; + if (editing) return ( -
!disabled && setEditing(true)} - > - {team?.name} -
+
+ + + +
); + + const team = bodyshop.employee_teams.find((tm) => tm.id === jobline.assigned_team); + + return ( +
!disabled && setEditing(true)}> + {team?.name} +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JoblineTeamAssignment); +export default connect(mapStateToProps, mapDispatchToProps)(JoblineTeamAssignment); diff --git a/client/src/components/job-lines-bill-reference/job-lines-bill-reference.component.jsx b/client/src/components/job-lines-bill-reference/job-lines-bill-reference.component.jsx index df4e4fdcc..d31172bee 100644 --- a/client/src/components/job-lines-bill-reference/job-lines-bill-reference.component.jsx +++ b/client/src/components/job-lines-bill-reference/job-lines-bill-reference.component.jsx @@ -1,17 +1,17 @@ import React from "react"; -import {WarningFilled} from "@ant-design/icons"; +import { WarningFilled } from "@ant-design/icons"; -export default function JobLinesBillRefernece({jobline}) { - const billLine = jobline.billlines && jobline.billlines[0]; +export default function JobLinesBillRefernece({ jobline }) { + const billLine = jobline.billlines && jobline.billlines[0]; - if (!billLine) return null; - const subletRequired = billLine.actual_price !== jobline.act_price; - return ( -
- {subletRequired && } - {`${billLine.actual_price.toFixed(2)} x ${billLine.quantity} (${ - billLine.bill.vendor.name - } #${billLine.bill.invoice_number})`} -
- ); + if (!billLine) return null; + const subletRequired = billLine.actual_price !== jobline.act_price; + return ( +
+ {subletRequired && } + {`${billLine.actual_price.toFixed(2)} x ${billLine.quantity} (${ + billLine.bill.vendor.name + } #${billLine.bill.invoice_number})`} +
+ ); } diff --git a/client/src/components/job-lines-preset-button/job-lines-preset-button.component.jsx b/client/src/components/job-lines-preset-button/job-lines-preset-button.component.jsx index 4472e434f..1a724edc6 100644 --- a/client/src/components/job-lines-preset-button/job-lines-preset-button.component.jsx +++ b/client/src/components/job-lines-preset-button/job-lines-preset-button.component.jsx @@ -1,58 +1,47 @@ -import {DownOutlined} from "@ant-design/icons"; -import {Dropdown} from "antd"; +import { DownOutlined } from "@ant-design/icons"; +import { Dropdown } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function JoblinePresetButton({bodyshop, form}) { - const {t} = useTranslation(); +export function JoblinePresetButton({ bodyshop, form }) { + const { t } = useTranslation(); - const handleSelect = (item) => { - form.setFieldsValue(item); - }; + const handleSelect = (item) => { + form.setFieldsValue(item); + }; - - const menu = { - items: bodyshop.md_jobline_presets.map((i, idx) => ({ - key: idx, - label: i.label, - style: {breakInside: "avoid"}, - onClick: () => handleSelect(i), - })), - style: { - columnCount: Math.max( - Math.floor(bodyshop.md_jobline_presets.length / 15), - 1 - ), - }, + const menu = { + items: bodyshop.md_jobline_presets.map((i, idx) => ({ + key: idx, + label: i.label, + style: { breakInside: "avoid" }, + onClick: () => handleSelect(i) + })), + style: { + columnCount: Math.max(Math.floor(bodyshop.md_jobline_presets.length / 15), 1) } + }; - return ( - - ); + return ( + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JoblinePresetButton); +export default connect(mapStateToProps, mapDispatchToProps)(JoblinePresetButton); diff --git a/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.component.jsx b/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.component.jsx index 3f62ade7e..e07c54606 100644 --- a/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.component.jsx +++ b/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.component.jsx @@ -1,303 +1,199 @@ -import {Form, Input, InputNumber, Modal, Select, Switch} from "antd"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; +import { Form, Input, InputNumber, Modal, Select, Switch } from "antd"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import InputCurrency from "../form-items-formatted/currency-form-item.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import JoblinesPreset from "../job-lines-preset-button/job-lines-preset-button.component"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobLinesUpsertModalComponent); +export default connect(mapStateToProps, mapDispatchToProps)(JobLinesUpsertModalComponent); -export function JobLinesUpsertModalComponent({ - bodyshop, - open, - jobLine, - handleCancel, - handleFinish, - loading, - }) { - const {t} = useTranslation(); - const [form] = Form.useForm(); +export function JobLinesUpsertModalComponent({ bodyshop, open, jobLine, handleCancel, handleFinish, loading }) { + const { t } = useTranslation(); + const [form] = Form.useForm(); - useEffect(() => { - form.resetFields(); - }, [open, form]); + useEffect(() => { + form.resetFields(); + }, [open, form]); + const { + treatments: { Allow_Negative_Jobline_Price, Autohouse_Detail_line } + } = useSplitTreatments({ + attributes: {}, + names: ["Allow_Negative_Jobline_Price", "Autohouse_Detail_line"], + splitKey: bodyshop.imexshopid + }); - const {treatments: {Allow_Negative_Jobline_Price, Autohouse_Detail_line}} = useSplitTreatments({ - attributes: {}, - names: ["Allow_Negative_Jobline_Price", "Autohouse_Detail_line"], - splitKey: bodyshop.imexshopid, - }); - - return ( - form.submit()} - okButtonProps={{loading: loading}} - onCancel={handleCancel} - e - > -
form.submit()} + okButtonProps={{ loading: loading }} + onCancel={handleCancel} + e + > + + + + + + + + + + + + + + + + + + ({ + validator(rule, value) { + if (!!getFieldValue("mod_lbr_ty") === (!!value || value === 0)) { + return Promise.resolve(); + } + return Promise.reject(t("joblines.validations.hrsrequirediflbrtyp")); + } + }) + ]} + > + + + {Autohouse_Detail_line.treatment === "on" && ( + ({ + validator(rule, value) { + if (value === false || value === undefined || value === null) return Promise.resolve(); + if (value === true && ["LA1", "LA2", "LA3", "LA4", "LAU"].includes(getFieldValue("mod_lbr_ty"))) { + return Promise.resolve(); + } + return Promise.reject(t("joblines.validations.ahdetailonlyonuserdefinedtypes")); + } + }) + ]} > - - - - - - - - - - - - - - - - - ({ - validator(rule, value) { - if ( - !!getFieldValue("mod_lbr_ty") === (!!value || value === 0) - ) { - return Promise.resolve(); - } - return Promise.reject( - t("joblines.validations.hrsrequirediflbrtyp") - ); - }, - }), - ]} - > - - - {Autohouse_Detail_line.treatment === "on" && ( - ({ - validator(rule, value) { - if ( - value === false || - value === undefined || - value === null - ) - return Promise.resolve(); - if ( - value === true && - ["LA1", "LA2", "LA3", "LA4", "LAU"].includes( - getFieldValue("mod_lbr_ty") - ) - ) { - return Promise.resolve(); - } - return Promise.reject( - t("joblines.validations.ahdetailonlyonuserdefinedtypes") - ); - }, - }), - ]} - > - - - )} - - - - - - - - - ({ - validator(rule, value) { - if (!!getFieldValue("part_type") === !!value) { - return Promise.resolve(); - } - return Promise.reject( - t("joblines.validations.requiredifparttype") - ); - }, - }), - ]} - > - - - {/* + + + )} + + + + + + + + + ({ + validator(rule, value) { + if (!!getFieldValue("part_type") === !!value) { + return Promise.resolve(); + } + return Promise.reject(t("joblines.validations.requiredifparttype")); + } + }) + ]} + > + + + {/* */} - ({ - validator(rule, value) { - if (!value || getFieldValue("part_type") !== "PAE") { - return Promise.resolve(); - } - return Promise.reject( - t("joblines.validations.zeropriceexistingpart") - ); - }, - }), - ({getFieldValue}) => ({ - validator(rule, value) { - if ( - !!getFieldValue("part_type") === (!!value || value === 0) - ) { - return Promise.resolve(); - } - return Promise.reject( - t("joblines.validations.requiredifparttype") - ); - }, - }), - ]} - > - - - - - - - - - - -
- ); + ({ + validator(rule, value) { + if (!value || getFieldValue("part_type") !== "PAE") { + return Promise.resolve(); + } + return Promise.reject(t("joblines.validations.zeropriceexistingpart")); + } + }), + ({ getFieldValue }) => ({ + validator(rule, value) { + if (!!getFieldValue("part_type") === (!!value || value === 0)) { + return Promise.resolve(); + } + return Promise.reject(t("joblines.validations.requiredifparttype")); + } + }) + ]} + > + + + + + + + + + + + + ); } diff --git a/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx b/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx index e307c9e13..39cf2a2af 100644 --- a/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx +++ b/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx @@ -1,146 +1,136 @@ -import {useMutation} from "@apollo/client"; -import {notification} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {INSERT_NEW_JOB_LINE, UPDATE_JOB_LINE,} from "../../graphql/jobs-lines.queries"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectJobLineEditModal} from "../../redux/modals/modals.selectors"; +import { useMutation } from "@apollo/client"; +import { notification } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_NEW_JOB_LINE, UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectJobLineEditModal } from "../../redux/modals/modals.selectors"; import UndefinedToNull from "../../utils/undefinedtonull"; import JobLinesUpdsertModal from "./job-lines-upsert-modal.component"; import Axios from "axios"; import Dinero from "dinero.js"; import CriticalPartsScan from "../../utils/criticalPartsScan"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - jobLineEditModal: selectJobLineEditModal, - bodyshop: selectBodyshop, + jobLineEditModal: selectJobLineEditModal, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit")), + toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit")) }); -function JobLinesUpsertModalContainer({ - jobLineEditModal, - toggleModalVisible, - bodyshop, - }) { +function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bodyshop }) { + const { + treatments: { CriticalPartsScanning } + } = useSplitTreatments({ + attributes: {}, + names: ["CriticalPartsScanning"], + splitKey: bodyshop.imexshopid + }); - const {treatments: {CriticalPartsScanning}} = useSplitTreatments({ - attributes: {}, - names: ['CriticalPartsScanning'], - splitKey: bodyshop.imexshopid, - }); + const { t } = useTranslation(); + const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE); + const [updateJobLine] = useMutation(UPDATE_JOB_LINE); + const [loading, setLoading] = useState(false); - const {t} = useTranslation(); - const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE); - const [updateJobLine] = useMutation(UPDATE_JOB_LINE); - const [loading, setLoading] = useState(false); - - const handleFinish = async (values) => { - setLoading(true); - if (!jobLineEditModal.context.id) { - const r = await insertJobLine({ - variables: { - lineInput: [ - { - jobid: jobLineEditModal.context.jobid, - manual_line: !( - jobLineEditModal.context && jobLineEditModal.context.id - ), - ...UndefinedToNull({ - ...values, - prt_dsmk_m: Dinero({ - amount: Math.round((values.act_price || 0) * 100), - }) - .percentage(Math.abs(values.prt_dsmk_p || 0)) - .multiply(values.prt_dsmk_p >= 0 ? 1 : -1) - .toFormat(0.0), - }), - }, - ], - }, - refetchQueries: ["GET_LINE_TICKET_BY_PK"], - }); - if (!r.errors) { - await Axios.post("/job/totalsssu", { - id: jobLineEditModal.context.jobid, - }); - if (jobLineEditModal.actions.refetch) - jobLineEditModal.actions.refetch(); - //Need to recalcuate totals. - toggleModalVisible(); - notification["success"]({ - message: t("joblines.successes.created"), - }); - } else { - notification["error"]({ - message: t("joblines.errors.creating", { - message: JSON.stringify(r.errors.message), - }), - }); + const handleFinish = async (values) => { + setLoading(true); + if (!jobLineEditModal.context.id) { + const r = await insertJobLine({ + variables: { + lineInput: [ + { + jobid: jobLineEditModal.context.jobid, + manual_line: !(jobLineEditModal.context && jobLineEditModal.context.id), + ...UndefinedToNull({ + ...values, + prt_dsmk_m: Dinero({ + amount: Math.round((values.act_price || 0) * 100) + }) + .percentage(Math.abs(values.prt_dsmk_p || 0)) + .multiply(values.prt_dsmk_p >= 0 ? 1 : -1) + .toFormat(0.0) + }) } - } else { - const r = await updateJobLine({ - variables: { - lineId: jobLineEditModal.context.id, - line: { - ...values, - prt_dsmk_m: Dinero({ - amount: Math.round(values.act_price * 100), - }) - .percentage(Math.abs(values.prt_dsmk_p || 0)) - .multiply(values.prt_dsmk_p >= 0 ? 1 : -1) - .toFormat(0.0), - }, - }, - refetchQueries: ["GET_LINE_TICKET_BY_PK"], - }); - if (!r.errors) { - notification["success"]({ - message: t("joblines.successes.updated"), - }); - } else { - notification["success"]({ - message: t("joblines.errors.updating", { - message: JSON.stringify(r.errors.message), - }), - }); - } - - if (jobLineEditModal.actions.submit) { - jobLineEditModal.actions.submit(); - } else { - if (jobLineEditModal.actions.refetch) - jobLineEditModal.actions.refetch(); - } - toggleModalVisible(); - } - if (CriticalPartsScanning.treatment === "on") { - CriticalPartsScan(jobLineEditModal.context.jobid); - } - setLoading(false); - }; - - const handleCancel = () => { + ] + }, + refetchQueries: ["GET_LINE_TICKET_BY_PK"] + }); + if (!r.errors) { + await Axios.post("/job/totalsssu", { + id: jobLineEditModal.context.jobid + }); + if (jobLineEditModal.actions.refetch) jobLineEditModal.actions.refetch(); + //Need to recalcuate totals. toggleModalVisible(); - }; + notification["success"]({ + message: t("joblines.successes.created") + }); + } else { + notification["error"]({ + message: t("joblines.errors.creating", { + message: JSON.stringify(r.errors.message) + }) + }); + } + } else { + const r = await updateJobLine({ + variables: { + lineId: jobLineEditModal.context.id, + line: { + ...values, + prt_dsmk_m: Dinero({ + amount: Math.round(values.act_price * 100) + }) + .percentage(Math.abs(values.prt_dsmk_p || 0)) + .multiply(values.prt_dsmk_p >= 0 ? 1 : -1) + .toFormat(0.0) + } + }, + refetchQueries: ["GET_LINE_TICKET_BY_PK"] + }); + if (!r.errors) { + notification["success"]({ + message: t("joblines.successes.updated") + }); + } else { + notification["success"]({ + message: t("joblines.errors.updating", { + message: JSON.stringify(r.errors.message) + }) + }); + } - return ( - - ); + if (jobLineEditModal.actions.submit) { + jobLineEditModal.actions.submit(); + } else { + if (jobLineEditModal.actions.refetch) jobLineEditModal.actions.refetch(); + } + toggleModalVisible(); + } + if (CriticalPartsScanning.treatment === "on") { + CriticalPartsScan(jobLineEditModal.context.jobid); + } + setLoading(false); + }; + + const handleCancel = () => { + toggleModalVisible(); + }; + + return ( + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobLinesUpsertModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(JobLinesUpsertModalContainer); diff --git a/client/src/components/job-parts-queue-count/job-parts-queue-count.component.jsx b/client/src/components/job-parts-queue-count/job-parts-queue-count.component.jsx index d9b7744f0..ee09712c1 100644 --- a/client/src/components/job-parts-queue-count/job-parts-queue-count.component.jsx +++ b/client/src/components/job-parts-queue-count/job-parts-queue-count.component.jsx @@ -1,81 +1,73 @@ -import React, {useMemo} from "react"; -import {Col, Row, Tag, Tooltip} from "antd"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useMemo } from "react"; +import { Col, Row, Tag, Tooltip } from "antd"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(JobPartsQueueCount); -export function JobPartsQueueCount({bodyshop, parts, style}) { - const partsStatus = useMemo(() => { - if (!parts) return null; - return parts.reduce( - (acc, val) => { - if (val.part_type === "PAS" || val.part_type === "PASL") return acc; - acc.total = acc.total + val.count; - acc[val.status] = acc[val.status] + val.count; - - return acc; - }, - { - total: 0, - null: 0, - [bodyshop.md_order_statuses.default_bo]: 0, - [bodyshop.md_order_statuses.default_ordered]: 0, - [bodyshop.md_order_statuses.default_received]: 0, - [bodyshop.md_order_statuses.default_returned]: 0, - } - ); - }, [bodyshop, parts]); - +export function JobPartsQueueCount({ bodyshop, parts, style }) { + const partsStatus = useMemo(() => { if (!parts) return null; + return parts.reduce( + (acc, val) => { + if (val.part_type === "PAS" || val.part_type === "PASL") return acc; + acc.total = acc.total + val.count; + acc[val.status] = acc[val.status] + val.count; - return ( - - - - {partsStatus.total} - - - - - {partsStatus["null"]} - - - - - - {partsStatus[bodyshop.md_order_statuses.default_ordered]} - - - - - - - {partsStatus[bodyshop.md_order_statuses.default_received]} - - - - - - - {partsStatus[bodyshop.md_order_statuses.default_returned]} - - - - - - - {partsStatus[bodyshop.md_order_statuses.default_bo]} - - - - + return acc; + }, + { + total: 0, + null: 0, + [bodyshop.md_order_statuses.default_bo]: 0, + [bodyshop.md_order_statuses.default_ordered]: 0, + [bodyshop.md_order_statuses.default_received]: 0, + [bodyshop.md_order_statuses.default_returned]: 0 + } ); + }, [bodyshop, parts]); + + if (!parts) return null; + + return ( + + + + {partsStatus.total} + + + + + {partsStatus["null"]} + + + + + {partsStatus[bodyshop.md_order_statuses.default_ordered]} + + + + + {partsStatus[bodyshop.md_order_statuses.default_received]} + + + + + {partsStatus[bodyshop.md_order_statuses.default_returned]} + + + + + {partsStatus[bodyshop.md_order_statuses.default_bo]} + + + + ); } diff --git a/client/src/components/job-payments/job-payments.component.jsx b/client/src/components/job-payments/job-payments.component.jsx index df67dc1b1..bfb74d8fc 100644 --- a/client/src/components/job-payments/job-payments.component.jsx +++ b/client/src/components/job-payments/job-payments.component.jsx @@ -1,242 +1,230 @@ -import {EditFilled} from "@ant-design/icons"; -import {Button, Card, Space, Table} from "antd"; +import { EditFilled } from "@ant-design/icons"; +import { Button, Card, Space, Table } from "antd"; import Dinero from "dinero.js"; -import React, {useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {openChatByPhone, setMessage,} from "../../redux/messaging/messaging.actions"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {DateFormatter} from "../../utils/DateFormatter"; -import {TemplateList} from "../../utils/TemplateConstants"; -import {alphaSort, dateSort} from "../../utils/sorters"; +import { DateFormatter } from "../../utils/DateFormatter"; +import { TemplateList } from "../../utils/TemplateConstants"; +import { alphaSort, dateSort } from "../../utils/sorters"; import DataLabel from "../data-label/data-label.component"; import PaymentExpandedRowComponent from "../payment-expanded-row/payment-expanded-row.component"; import PaymentsGenerateLink from "../payments-generate-link/payments-generate-link.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - jobRO: selectJobReadOnly, + bodyshop: selectBodyshop, + jobRO: selectJobReadOnly }); const mapDispatchToProps = (dispatch) => ({ - 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)), + 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)) }); export function JobPayments({ - job, - jobRO, - bodyshop, - setMessage, - openChatByPhone, - setPaymentContext, - setCardPaymentContext, - refetch - }) { + job, + jobRO, + bodyshop, + setMessage, + openChatByPhone, + setPaymentContext, + setCardPaymentContext, + refetch +}) { + const { + treatments: { ImEXPay } + } = useSplitTreatments({ + attributes: {}, + names: ["ImEXPay"], + splitKey: bodyshop && bodyshop.imexshopid + }); - const {treatments: {ImEXPay}} = useSplitTreatments({ - attributes: {}, - names: ["ImEXPay"], - splitKey: bodyshop && bodyshop.imexshopid, - }); + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: {} + }); - const {t} = useTranslation(); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {}, - }); + const columns = [ + { + title: t("payments.fields.date"), + dataIndex: "date", + key: "date", + sorter: (a, b) => dateSort(a.date, b.date), - const columns = [ - { - title: t("payments.fields.date"), - dataIndex: "date", - key: "date", - sorter: (a, b) => dateSort(a.date, b.date), - - sortOrder: - state.sortedInfo.columnKey === "date" && state.sortedInfo.order, - render: (text, record) => {record.date}, - }, - { - title: t("payments.fields.payer"), - dataIndex: "payer", - key: "payer", - sorter: (a, b) => alphaSort(a.payer, b.payer), - sortOrder: - state.sortedInfo.columnKey === "payer" && state.sortedInfo.order, - }, - { - title: t("payments.fields.amount"), - dataIndex: "amount", - key: "amount", - sorter: (a, b) => a.amount - b.amount, - sortOrder: - state.sortedInfo.columnKey === "amount" && state.sortedInfo.order, - render: (text, record) => ( - {record.amount} - ), - }, - { - title: t("payments.fields.memo"), - dataIndex: "memo", - key: "memo", - sorter: (a, b) => alphaSort(a.memo, b.memo), - sortOrder: - state.sortedInfo.columnKey === "memo" && state.sortedInfo.order, - }, - { - title: t("payments.fields.type"), - dataIndex: "type", - key: "type", - sorter: (a, b) => alphaSort(a.type, b.type), - sortOrder: - state.sortedInfo.columnKey === "type" && state.sortedInfo.order, - }, - { - title: t("payments.fields.transactionid"), - dataIndex: "transactionid", - key: "transactionid", - sorter: (a, b) => alphaSort(a.transactionid, b.transactionid), - sortOrder: - state.sortedInfo.columnKey === "transactionid" && - state.sortedInfo.order, - }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", - render: (text, record) => ( - - - - - ), - }, - ]; - - const total = useMemo(() => { - return ( - job.payments && - job.payments.reduce((acc, val) => { - acc = acc.add(Dinero({amount: Math.round(val.amount * 100)})); - return acc; - }, Dinero()) - ); - }, [job.payments]); - - const balance = useMemo(() => { - if (job && job.job_totals && job.job_totals.totals.total_repairs) - return Dinero(job.job_totals.totals.total_repairs).subtract(total); - return Dinero({amount: 0}).subtract(total); - }, [job, total]); - - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + sortOrder: state.sortedInfo.columnKey === "date" && state.sortedInfo.order, + render: (text, record) => {record.date} + }, + { + title: t("payments.fields.payer"), + dataIndex: "payer", + key: "payer", + sorter: (a, b) => alphaSort(a.payer, b.payer), + sortOrder: state.sortedInfo.columnKey === "payer" && state.sortedInfo.order + }, + { + title: t("payments.fields.amount"), + dataIndex: "amount", + key: "amount", + sorter: (a, b) => a.amount - b.amount, + sortOrder: state.sortedInfo.columnKey === "amount" && state.sortedInfo.order, + render: (text, record) => {record.amount} + }, + { + title: t("payments.fields.memo"), + dataIndex: "memo", + key: "memo", + sorter: (a, b) => alphaSort(a.memo, b.memo), + sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order + }, + { + title: t("payments.fields.type"), + dataIndex: "type", + key: "type", + sorter: (a, b) => alphaSort(a.type, b.type), + sortOrder: state.sortedInfo.columnKey === "type" && state.sortedInfo.order + }, + { + title: t("payments.fields.transactionid"), + dataIndex: "transactionid", + key: "transactionid", + sorter: (a, b) => alphaSort(a.transactionid, b.transactionid), + sortOrder: state.sortedInfo.columnKey === "transactionid" && state.sortedInfo.order + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => ( + + + + + ) + } + ]; + const total = useMemo(() => { return ( - - {ImEXPay.treatment === "on" && ( - <> - - - - )} - - - - {balance.toFormat()} - - - } - > -
( - - ), - }} - summary={() => ( - <> - - - {t("payments.labels.totalpayments")} - - - - {total.toFormat()} - - - - - - - )} - /> - + job.payments && + job.payments.reduce((acc, val) => { + acc = acc.add(Dinero({ amount: Math.round(val.amount * 100) })); + return acc; + }, Dinero()) ); + }, [job.payments]); + + const balance = useMemo(() => { + if (job && job.job_totals && job.job_totals.totals.total_repairs) + return Dinero(job.job_totals.totals.total_repairs).subtract(total); + return Dinero({ amount: 0 }).subtract(total); + }, [job, total]); + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + return ( + + {ImEXPay.treatment === "on" && ( + <> + + + + )} + + + + {balance.toFormat()} + + + } + > +
+ }} + summary={() => ( + <> + + + {t("payments.labels.totalpayments")} + + + + {total.toFormat()} + + + + + + + )} + /> + + ); } export default connect(mapStateToProps, mapDispatchToProps)(JobPayments); diff --git a/client/src/components/job-profile-data-warning/job-profile-data-warning.component.jsx b/client/src/components/job-profile-data-warning/job-profile-data-warning.component.jsx index 4c82e4adb..ed9c16579 100644 --- a/client/src/components/job-profile-data-warning/job-profile-data-warning.component.jsx +++ b/client/src/components/job-profile-data-warning/job-profile-data-warning.component.jsx @@ -4,19 +4,14 @@ import { useTranslation } from "react-i18next"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; export default function JobProfileDataWarning({ job }) { - const { t } = useTranslation(); + const { t } = useTranslation(); - let missingProfileInfo = - Object.keys(job.cieca_pft).length === 0 || - Object.keys(job.cieca_pfl).length === 0 || - Object.keys(job.materials).length === 0; + let missingProfileInfo = + Object.keys(job.cieca_pft).length === 0 || + Object.keys(job.cieca_pfl).length === 0 || + Object.keys(job.materials).length === 0; - if (missingProfileInfo && InstanceRenderManager({ rome: true })) - return ( - - ); - return null; + if (missingProfileInfo && InstanceRenderManager({ rome: true })) + return ; + return null; } diff --git a/client/src/components/job-reconciliation-bills-table/job-reconciliation-bills-table.component.jsx b/client/src/components/job-reconciliation-bills-table/job-reconciliation-bills-table.component.jsx index 849cea1c3..2815a7ad9 100644 --- a/client/src/components/job-reconciliation-bills-table/job-reconciliation-bills-table.component.jsx +++ b/client/src/components/job-reconciliation-bills-table/job-reconciliation-bills-table.component.jsx @@ -1,118 +1,100 @@ -import {Checkbox, Table, Typography} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; +import { Checkbox, Table, Typography } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {alphaSort} from "../../utils/sorters"; +import { alphaSort } from "../../utils/sorters"; -export default function JobReconciliationBillsTable({ - billLineState, - invoiceLineData, - }) { - const {t} = useTranslation(); +export default function JobReconciliationBillsTable({ billLineState, invoiceLineData }) { + const { t } = useTranslation(); - const [state, setState] = useState({ - sortedInfo: {}, - }); + const [state, setState] = useState({ + sortedInfo: {} + }); - const [selectedLines, setSelectedLines] = billLineState; + const [selectedLines, setSelectedLines] = billLineState; - const columns = [ - { - title: t("billlines.fields.line_desc"), - dataIndex: "line_desc", - key: "line_desc", - ellipsis: true, - width: "10rem", - sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), - sortOrder: - state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order, - }, - { - title: t("billlines.labels.from"), - dataIndex: "from", - key: "from", + const columns = [ + { + title: t("billlines.fields.line_desc"), + dataIndex: "line_desc", + key: "line_desc", + ellipsis: true, + width: "10rem", + sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), + sortOrder: state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order + }, + { + title: t("billlines.labels.from"), + dataIndex: "from", + key: "from", - ellipsis: true, - render: (text, record) => - `${record.bill.vendor && record.bill.vendor.name} / ${ - record.bill.invoice_number - }`, - }, - { - title: t("billlines.fields.actual_price"), - dataIndex: "actual_price", - key: "actual_price", - sorter: (a, b) => a.actual_price - b.actual_price, - width: "7rem", - sortOrder: - state.sortedInfo.columnKey === "actual_price" && state.sortedInfo.order, - render: (text, record) => ( - {record.actual_price} - ), - }, - { - title: t("billlines.fields.actual_cost"), - dataIndex: "actual_cost", - key: "actual_cost", - sorter: (a, b) => a.actual_cost - b.actual_cost, - width: "7rem", - sortOrder: - state.sortedInfo.columnKey === "actual_cost" && state.sortedInfo.order, - render: (text, record) => ( - {record.actual_cost} - ), - }, - { - title: t("joblines.fields.part_qty"), - dataIndex: "quantity", - key: "quantity", - sorter: (a, b) => a.quantity - b.quantity, - width: "4rem", - sortOrder: - state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order, - }, - { - title: t("bills.fields.is_credit_memo_short"), - dataIndex: "is_credit_memo", - key: "is_credit_memo", - sorter: (a, b) => a.bill.is_credit_memo - b.bill.is_credit_memo, - width: "3rem", - sortOrder: - state.sortedInfo.columnKey === "is_credit_memo" && - state.sortedInfo.order, + ellipsis: true, + render: (text, record) => `${record.bill.vendor && record.bill.vendor.name} / ${record.bill.invoice_number}` + }, + { + title: t("billlines.fields.actual_price"), + dataIndex: "actual_price", + key: "actual_price", + sorter: (a, b) => a.actual_price - b.actual_price, + width: "7rem", + sortOrder: state.sortedInfo.columnKey === "actual_price" && state.sortedInfo.order, + render: (text, record) => {record.actual_price} + }, + { + title: t("billlines.fields.actual_cost"), + dataIndex: "actual_cost", + key: "actual_cost", + sorter: (a, b) => a.actual_cost - b.actual_cost, + width: "7rem", + sortOrder: state.sortedInfo.columnKey === "actual_cost" && state.sortedInfo.order, + render: (text, record) => {record.actual_cost} + }, + { + title: t("joblines.fields.part_qty"), + dataIndex: "quantity", + key: "quantity", + sorter: (a, b) => a.quantity - b.quantity, + width: "4rem", + sortOrder: state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order + }, + { + title: t("bills.fields.is_credit_memo_short"), + dataIndex: "is_credit_memo", + key: "is_credit_memo", + sorter: (a, b) => a.bill.is_credit_memo - b.bill.is_credit_memo, + width: "3rem", + sortOrder: state.sortedInfo.columnKey === "is_credit_memo" && state.sortedInfo.order, - render: (text, record) => ( - - ), - }, - ]; + render: (text, record) => + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - const handleOnRowClick = (selectedRecordKeys, selectedRecords) => { - setSelectedLines(selectedRecordKeys); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + const handleOnRowClick = (selectedRecordKeys, selectedRecords) => { + setSelectedLines(selectedRecordKeys); + }; - return ( -
- {t("bills.labels.bills")} -
{ - return {disabled: record.deductedfromlbr}; - }, - }} - /> - - ); + return ( +
+ {t("bills.labels.bills")} +
{ + return { disabled: record.deductedfromlbr }; + } + }} + /> + + ); } diff --git a/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx b/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx index 82ffcde0d..730614fc0 100644 --- a/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx +++ b/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx @@ -1,69 +1,57 @@ -import {Col, Row} from "antd"; -import React, {useState} from "react"; +import { Col, Row } from "antd"; +import React, { useState } from "react"; import JobReconciliationBillsTable from "../job-reconciliation-bills-table/job-reconciliation-bills-table.component"; import JobReconciliationPartsTable from "../job-reconciliation-parts-table/job-reconciliation-parts-table.component"; import JobReconciliationTotals from "../job-reconciliation-totals/job-reconciliation-totals.component"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; -export default function JobReconciliationModalComponent({job, bills}) { - const jobLineState = useState([]); - const billLineState = useState([]); +export default function JobReconciliationModalComponent({ job, bills }) { + const jobLineState = useState([]); + const billLineState = useState([]); - const invoiceLineData = - bills - .map((i) => - i.billlines.map((il) => { - return {...il, bill: i}; - }) - ) - .flat() || []; + const invoiceLineData = + bills + .map((i) => + i.billlines.map((il) => { + return { ...il, bill: i }; + }) + ) + .flat() || []; - const filterFunction = InstanceRenderManager({ - imex: (j) => - (j.part_type !== null && j.part_type !== "PAE") || - (j.line_desc && - j.line_desc.toLowerCase().includes("towing") && - j.lbr_op === "OP13") || - j.db_ref === "936004", //ADD SHIPPING LINE. - rome: (j) => - (j.part_type !== "PAE" && - j.act_price !== 0 && - j.part_qty !== 0) || - j.misc_amt !== 0 || - (j.line_desc && - j.line_desc.toLowerCase().includes("towing") && - j.lbr_op === "OP13") || - j.db_ref === "936004", //ADD SHIPPING LINE. - }); + const filterFunction = InstanceRenderManager({ + imex: (j) => + (j.part_type !== null && j.part_type !== "PAE") || + (j.line_desc && j.line_desc.toLowerCase().includes("towing") && j.lbr_op === "OP13") || + j.db_ref === "936004", //ADD SHIPPING LINE. + rome: (j) => + (j.part_type !== "PAE" && j.act_price !== 0 && j.part_qty !== 0) || + j.misc_amt !== 0 || + (j.line_desc && j.line_desc.toLowerCase().includes("towing") && j.lbr_op === "OP13") || + j.db_ref === "936004" //ADD SHIPPING LINE. + }); - const jobLineData = job.joblines.filter((j) => filterFunction(j)); + const jobLineData = job.joblines.filter((j) => filterFunction(j)); - return ( -
-
- -
- - - - - - - - - - - - ); + return ( +
+
+ +
+ + + + + + + + + + + + ); } diff --git a/client/src/components/job-reconciliation-modal/job-reconciliation.modal.container.jsx b/client/src/components/job-reconciliation-modal/job-reconciliation.modal.container.jsx index 25fcc5e86..9e9cd1a93 100644 --- a/client/src/components/job-reconciliation-modal/job-reconciliation.modal.container.jsx +++ b/client/src/components/job-reconciliation-modal/job-reconciliation.modal.container.jsx @@ -1,68 +1,57 @@ -import {useQuery} from "@apollo/client"; -import {Modal} from "antd"; +import { useQuery } from "@apollo/client"; +import { Modal } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {GET_JOB_RECONCILIATION_BY_PK} from "../../graphql/jobs.queries"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectReconciliation} from "../../redux/modals/modals.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { GET_JOB_RECONCILIATION_BY_PK } from "../../graphql/jobs.queries"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectReconciliation } from "../../redux/modals/modals.selectors"; import JobReconciliationModalComponent from "./job-reconciliation-modal.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import AlertComponent from "../alert/alert.component"; import "./job-reconciliation-modal.styles.scss"; const mapStateToProps = createStructuredSelector({ - reconciliationModal: selectReconciliation, + reconciliationModal: selectReconciliation }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("reconciliation")), + toggleModalVisible: () => dispatch(toggleModalVisible("reconciliation")) }); -function JobReconciliationModalContainer({ - reconciliationModal, - toggleModalVisible, - }) { - const {t} = useTranslation(); - const {context, open} = reconciliationModal; - const {job} = context; +function JobReconciliationModalContainer({ reconciliationModal, toggleModalVisible }) { + const { t } = useTranslation(); + const { context, open } = reconciliationModal; + const { job } = context; - const {loading, error, data} = useQuery(GET_JOB_RECONCILIATION_BY_PK, { - variables: {id: job && job.id}, - skip: !(job && job.id) || !open, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const { loading, error, data } = useQuery(GET_JOB_RECONCILIATION_BY_PK, { + variables: { id: job && job.id }, + skip: !(job && job.id) || !open, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const handleCancel = () => { - toggleModalVisible(); - }; + const handleCancel = () => { + toggleModalVisible(); + }; - return ( - - {loading && } - {error && } - {data && ( - - )} - - ); + return ( + + {loading && } + {error && } + {data && } + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobReconciliationModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(JobReconciliationModalContainer); diff --git a/client/src/components/job-reconciliation-parts-table/job-reconciliation-parts-table.component.jsx b/client/src/components/job-reconciliation-parts-table/job-reconciliation-parts-table.component.jsx index ec394d77e..6ade6a77f 100644 --- a/client/src/components/job-reconciliation-parts-table/job-reconciliation-parts-table.component.jsx +++ b/client/src/components/job-reconciliation-parts-table/job-reconciliation-parts-table.component.jsx @@ -1,134 +1,121 @@ -import {Table, Typography} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; +import { Table, Typography } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {alphaSort} from "../../utils/sorters"; +import { alphaSort } from "../../utils/sorters"; import "./job-reconciliation-parts-table.styles.scss"; -export default function JobReconcilitionPartsTable({ - jobLineState, - jobLineData, - }) { - const {t} = useTranslation(); +export default function JobReconcilitionPartsTable({ jobLineState, jobLineData }) { + const { t } = useTranslation(); - const [state, setState] = useState({ - sortedInfo: {}, - }); + const [state, setState] = useState({ + sortedInfo: {} + }); - const [selectedLines, setSelectedLines] = jobLineState; + const [selectedLines, setSelectedLines] = jobLineState; - const columns = [ - { - title: t("joblines.fields.line_desc"), - dataIndex: "line_desc", - key: "line_desc", - sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), - ellipses: true, - sortOrder: - state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order, - }, - // { - // title: t("joblines.fields.oem_partno"), - // dataIndex: "oem_partno", - // key: "oem_partno", - // sorter: (a, b) => - // alphaSort( - // a.oem_partno ? a.oem_partno : a.op_code_desc, - // b.oem_partno ? b.oem_partno : b.op_code_desc - // ), - // sortOrder: - // state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order, + const columns = [ + { + title: t("joblines.fields.line_desc"), + dataIndex: "line_desc", + key: "line_desc", + sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), + ellipses: true, + sortOrder: state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order + }, + // { + // title: t("joblines.fields.oem_partno"), + // dataIndex: "oem_partno", + // key: "oem_partno", + // sorter: (a, b) => + // alphaSort( + // a.oem_partno ? a.oem_partno : a.op_code_desc, + // b.oem_partno ? b.oem_partno : b.op_code_desc + // ), + // sortOrder: + // state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order, - // render: (text, record) => ( - // - // {record.oem_partno ? record.oem_partno : record.op_code_desc} - // - // ), - // }, - // { - // title: t("joblines.fields.part_type"), - // dataIndex: "part_type", - // key: "part_type", - // sorter: (a, b) => alphaSort(a.part_type, b.part_type), - // sortOrder: - // state.sortedInfo.columnKey === "part_type" && state.sortedInfo.order, - // }, - { - title: t("joblines.fields.act_price"), - dataIndex: "act_price", - key: "act_price", - sorter: (a, b) => a.act_price - b.act_price, - width: "7rem", - sortOrder: - state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order, + // render: (text, record) => ( + // + // {record.oem_partno ? record.oem_partno : record.op_code_desc} + // + // ), + // }, + // { + // title: t("joblines.fields.part_type"), + // dataIndex: "part_type", + // key: "part_type", + // sorter: (a, b) => alphaSort(a.part_type, b.part_type), + // sortOrder: + // state.sortedInfo.columnKey === "part_type" && state.sortedInfo.order, + // }, + { + title: t("joblines.fields.act_price"), + dataIndex: "act_price", + key: "act_price", + sorter: (a, b) => a.act_price - b.act_price, + width: "7rem", + sortOrder: state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order, - render: (text, record) => ( - {record.act_price} - ), - }, - { - title: t("joblines.fields.part_qty"), - dataIndex: "part_qty", - key: "part_qty", - width: "4rem", - }, - { - title: t("joblines.fields.total"), - dataIndex: "total", - width: "7rem", - key: "total", - sorter: (a, b) => a.act_price * a.part_qty - b.act_price * b.part_qty, - sortOrder: - state.sortedInfo.columnKey === "total" && state.sortedInfo.order, + render: (text, record) => {record.act_price} + }, + { + title: t("joblines.fields.part_qty"), + dataIndex: "part_qty", + key: "part_qty", + width: "4rem" + }, + { + title: t("joblines.fields.total"), + dataIndex: "total", + width: "7rem", + key: "total", + sorter: (a, b) => a.act_price * a.part_qty - b.act_price * b.part_qty, + sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order, - render: (text, record) => ( - - {record.act_price * record.part_qty} - - ), - }, + render: (text, record) => {record.act_price * record.part_qty} + }, - { - title: t("joblines.fields.status"), - dataIndex: "status", - key: "status", - sorter: (a, b) => alphaSort(a.status, b.status), - width: "6rem", - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - }, - ]; + { + title: t("joblines.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => alphaSort(a.status, b.status), + width: "6rem", + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - const handleOnRowClick = (selectedRecordKeys, selectedRecords) => { - setSelectedLines(selectedRecordKeys); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + const handleOnRowClick = (selectedRecordKeys, selectedRecords) => { + setSelectedLines(selectedRecordKeys); + }; - return ( -
- {t("jobs.labels.lines")} -
({ - disabled: record.removed, - }), - selectedRowKeys: selectedLines, - }} - rowClassName={(record) => record.removed && "text-strikethrough"} - /> -
- {t("jobs.labels.reconciliation.removedpartsstrikethrough")} -
- - ); + return ( +
+ {t("jobs.labels.lines")} +
({ + disabled: record.removed + }), + selectedRowKeys: selectedLines + }} + rowClassName={(record) => record.removed && "text-strikethrough"} + /> +
+ {t("jobs.labels.reconciliation.removedpartsstrikethrough")} +
+ + ); } diff --git a/client/src/components/job-reconciliation-totals/job-reconciliation-totals.component.jsx b/client/src/components/job-reconciliation-totals/job-reconciliation-totals.component.jsx index 1a6aeabfe..e430f1546 100644 --- a/client/src/components/job-reconciliation-totals/job-reconciliation-totals.component.jsx +++ b/client/src/components/job-reconciliation-totals/job-reconciliation-totals.component.jsx @@ -1,118 +1,81 @@ -import {Button, Space, Statistic} from "antd"; +import { Button, Space, Statistic } from "antd"; import Dinero from "dinero.js"; import _ from "lodash"; -import React, {useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {reconcileByAssocLine, reconcileByPrice,} from "./job-reconciliation-totals.utility"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { reconcileByAssocLine, reconcileByPrice } from "./job-reconciliation-totals.utility"; -export default function JobReconciliationTotals({ - billLines, - jobLines, - jobLineState, - billLineState, - }) { - const [errors, setErrors] = useState([]); - const {t} = useTranslation(); - const selectedBillLines = billLineState[0]; - const selectedJobLines = jobLineState[0]; +export default function JobReconciliationTotals({ billLines, jobLines, jobLineState, billLineState }) { + const [errors, setErrors] = useState([]); + const { t } = useTranslation(); + const selectedBillLines = billLineState[0]; + const selectedJobLines = jobLineState[0]; - const totals = useMemo(() => { - const jlLookup = _.keyBy(selectedJobLines, (i) => i); - const billLookup = _.keyBy(selectedBillLines, (i) => i); + const totals = useMemo(() => { + const jlLookup = _.keyBy(selectedJobLines, (i) => i); + const billLookup = _.keyBy(selectedBillLines, (i) => i); - return { - joblinesTotal: jobLines - .filter((jl) => !!jlLookup[jl.id]) - .reduce((acc, val) => { - return acc.add( - Dinero({amount: Math.round((val.act_price || 0) * 100)}).multiply( - val.part_qty || 1 - ) - ); - }, Dinero()), - billLinesTotal: billLines - .filter((bl) => !!billLookup[bl.id]) - .reduce((acc, val) => { - return acc.add( - Dinero({ - amount: Math.round((val.actual_price || 0) * 100), - }) - .multiply(val.quantity || 1) - .multiply(val.bill.is_credit_memo ? -1 : 1) - ); - }, Dinero()), - }; - }, [billLines, jobLines, selectedBillLines, selectedJobLines]); + return { + joblinesTotal: jobLines + .filter((jl) => !!jlLookup[jl.id]) + .reduce((acc, val) => { + return acc.add(Dinero({ amount: Math.round((val.act_price || 0) * 100) }).multiply(val.part_qty || 1)); + }, Dinero()), + billLinesTotal: billLines + .filter((bl) => !!billLookup[bl.id]) + .reduce((acc, val) => { + return acc.add( + Dinero({ + amount: Math.round((val.actual_price || 0) * 100) + }) + .multiply(val.quantity || 1) + .multiply(val.bill.is_credit_memo ? -1 : 1) + ); + }, Dinero()) + }; + }, [billLines, jobLines, selectedBillLines, selectedJobLines]); - return ( -
-
- - - - - -
-
- - - - - -
- {errors.length > 0 && ( -
- {t("general.labels.errors")} -
    - {errors.map((error, idx) => ( -
  • {error}
  • - ))} -
-
- )} + return ( +
+
+ + + + + +
+
+ + + + + +
+ {errors.length > 0 && ( +
+ {t("general.labels.errors")} +
    + {errors.map((error, idx) => ( +
  • {error}
  • + ))} +
- ); + )} +
+ ); } diff --git a/client/src/components/job-reconciliation-totals/job-reconciliation-totals.utility.js b/client/src/components/job-reconciliation-totals/job-reconciliation-totals.utility.js index 129188996..aa8faada1 100644 --- a/client/src/components/job-reconciliation-totals/job-reconciliation-totals.utility.js +++ b/client/src/components/job-reconciliation-totals/job-reconciliation-totals.utility.js @@ -1,112 +1,89 @@ import i18next from "i18next"; import _ from "lodash"; -export const reconcileByAssocLine = ( - jobLines, - jobLineState, - billLines, - billLineState, - setErrors -) => { - const [selectedBillLines, setSelectedBillLines] = billLineState; - const [selectedJobLines, setSelectedJobLines] = jobLineState; +export const reconcileByAssocLine = (jobLines, jobLineState, billLines, billLineState, setErrors) => { + const [selectedBillLines, setSelectedBillLines] = billLineState; + const [selectedJobLines, setSelectedJobLines] = jobLineState; - const allJoblinesFromBills = billLines - .filter((bl) => bl.joblineid && bl.jobline && !bl.jobline.removed) - .map((bl) => bl.joblineid); + const allJoblinesFromBills = billLines + .filter((bl) => bl.joblineid && bl.jobline && !bl.jobline.removed) + .map((bl) => bl.joblineid); - const duplicatedJobLinesbyInvoiceId = _.filter( - allJoblinesFromBills, - (val, i, iteratee) => _.includes(iteratee, val, i + 1) - ); - - if (duplicatedJobLinesbyInvoiceId.length > 0) - setErrors((errors) => [ - ...errors, - ..._.uniqBy(duplicatedJobLinesbyInvoiceId).map((dupedId) => { - return i18next.t("jobs.labels.reconciliation.multiplebilllines", { - line_desc: jobLines.find((j) => j.id === dupedId)?.line_desc, - }); - }), - ]); - - setSelectedBillLines( - _.union( - selectedBillLines, - billLines.filter((bl) => !!bl.joblineid).map((bl) => bl.id) - ) - ); - - setSelectedJobLines( - _.union(selectedJobLines, _.uniqBy(allJoblinesFromBills)) - ); -}; - -export const reconcileByPrice = ( - jobLines, - jobLineState, - billLines, - billLineState, - setErrors -) => { - const [selectedBillLines, setSelectedBillLines] = billLineState; - const [selectedJobLines, setSelectedJobLines] = jobLineState; - - const allActPricesFromJobs = jobLines.map((jl) => jl.act_price); - const duplicateActPrices = _.filter( - allActPricesFromJobs, - (val, i, iteratee) => _.includes(iteratee, val, i + 1) - ); - - if (duplicateActPrices.length > 0) - setErrors((errors) => [ - ...errors, - ..._.uniqBy(duplicateActPrices).map((dupeAp) => - i18next.t("jobs.labels.reconciliation.multipleactprices", { - act_price: dupeAp, - }) - ), - ]); - - const foundJobLines = []; - var foundBillLines = []; - const actPricesWithMoreThan1MatchingBill = []; - - jobLines.forEach((jl) => { - const matchingBillLineIds = billLines - .filter( - (bl) => - bl.actual_price === jl.act_price && - bl.quantity === jl.part_qty && - !jl.removed - ) - .map((bl) => bl.id); - - if (matchingBillLineIds.length > 1) { - actPricesWithMoreThan1MatchingBill.push(jl.act_price); - } - - foundBillLines = [...foundBillLines, ...matchingBillLineIds]; - if (matchingBillLineIds.length > 0) { - foundJobLines.push(jl.id); - } - }); + const duplicatedJobLinesbyInvoiceId = _.filter(allJoblinesFromBills, (val, i, iteratee) => + _.includes(iteratee, val, i + 1) + ); + if (duplicatedJobLinesbyInvoiceId.length > 0) setErrors((errors) => [ - ...errors, - ..._.uniqBy(duplicateActPrices).map((dupeAp) => - i18next.t("jobs.labels.reconciliation.multipleactprices", { - act_price: dupeAp, - }) - ), - ..._.uniqBy(actPricesWithMoreThan1MatchingBill).map((act_price) => - i18next.t("jobs.labels.reconciliation.multiplebillsforactprice", { - act_price: act_price, - }) - ), + ...errors, + ..._.uniqBy(duplicatedJobLinesbyInvoiceId).map((dupedId) => { + return i18next.t("jobs.labels.reconciliation.multiplebilllines", { + line_desc: jobLines.find((j) => j.id === dupedId)?.line_desc + }); + }) ]); - setSelectedBillLines(_.union(selectedBillLines, _.uniqBy(foundBillLines))); + setSelectedBillLines( + _.union( + selectedBillLines, + billLines.filter((bl) => !!bl.joblineid).map((bl) => bl.id) + ) + ); - setSelectedJobLines(_.union(selectedJobLines, _.uniqBy(foundJobLines))); + setSelectedJobLines(_.union(selectedJobLines, _.uniqBy(allJoblinesFromBills))); +}; + +export const reconcileByPrice = (jobLines, jobLineState, billLines, billLineState, setErrors) => { + const [selectedBillLines, setSelectedBillLines] = billLineState; + const [selectedJobLines, setSelectedJobLines] = jobLineState; + + const allActPricesFromJobs = jobLines.map((jl) => jl.act_price); + const duplicateActPrices = _.filter(allActPricesFromJobs, (val, i, iteratee) => _.includes(iteratee, val, i + 1)); + + if (duplicateActPrices.length > 0) + setErrors((errors) => [ + ...errors, + ..._.uniqBy(duplicateActPrices).map((dupeAp) => + i18next.t("jobs.labels.reconciliation.multipleactprices", { + act_price: dupeAp + }) + ) + ]); + + const foundJobLines = []; + var foundBillLines = []; + const actPricesWithMoreThan1MatchingBill = []; + + jobLines.forEach((jl) => { + const matchingBillLineIds = billLines + .filter((bl) => bl.actual_price === jl.act_price && bl.quantity === jl.part_qty && !jl.removed) + .map((bl) => bl.id); + + if (matchingBillLineIds.length > 1) { + actPricesWithMoreThan1MatchingBill.push(jl.act_price); + } + + foundBillLines = [...foundBillLines, ...matchingBillLineIds]; + if (matchingBillLineIds.length > 0) { + foundJobLines.push(jl.id); + } + }); + + setErrors((errors) => [ + ...errors, + ..._.uniqBy(duplicateActPrices).map((dupeAp) => + i18next.t("jobs.labels.reconciliation.multipleactprices", { + act_price: dupeAp + }) + ), + ..._.uniqBy(actPricesWithMoreThan1MatchingBill).map((act_price) => + i18next.t("jobs.labels.reconciliation.multiplebillsforactprice", { + act_price: act_price + }) + ) + ]); + + setSelectedBillLines(_.union(selectedBillLines, _.uniqBy(foundBillLines))); + + setSelectedJobLines(_.union(selectedJobLines, _.uniqBy(foundJobLines))); }; diff --git a/client/src/components/job-remove-from-parst-queue/job-remove-from-parts-queue.component.jsx b/client/src/components/job-remove-from-parst-queue/job-remove-from-parts-queue.component.jsx index f7dd7b00b..92682aa83 100644 --- a/client/src/components/job-remove-from-parst-queue/job-remove-from-parts-queue.component.jsx +++ b/client/src/components/job-remove-from-parst-queue/job-remove-from-parts-queue.component.jsx @@ -1,37 +1,37 @@ -import {useMutation} from "@apollo/client"; -import {Checkbox, notification, Space, Spin} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; +import { useMutation } from "@apollo/client"; +import { Checkbox, notification, Space, Spin } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; -export default function JobRemoveFromPartsQueue({checked, jobId}) { - const [updateJob] = useMutation(UPDATE_JOB); - const {t} = useTranslation(); +export default function JobRemoveFromPartsQueue({ checked, jobId }) { + const [updateJob] = useMutation(UPDATE_JOB); + const { t } = useTranslation(); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(false); - const handleChange = async (e) => { - setLoading(true); - const result = await updateJob({ - variables: {jobId: jobId, job: {queued_for_parts: e.target.checked}}, - }); + const handleChange = async (e) => { + setLoading(true); + const result = await updateJob({ + variables: { jobId: jobId, job: { queued_for_parts: e.target.checked } } + }); - if (!!!result.errors) { - notification["success"]({message: t("jobs.successes.save")}); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - }; + if (!!!result.errors) { + notification["success"]({ message: t("jobs.successes.save") }); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + }; - return ( - - - {loading && } - - ); + return ( + + + {loading && } + + ); } diff --git a/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx b/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx index 97e4436de..a8c673552 100644 --- a/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx +++ b/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx @@ -1,193 +1,173 @@ -import {useLazyQuery, useMutation} from "@apollo/client"; -import {CheckCircleOutlined} from "@ant-design/icons"; -import {Button, Card, Form, InputNumber, notification, Popover, Space,} from "antd"; +import { useLazyQuery, useMutation } from "@apollo/client"; +import { CheckCircleOutlined } from "@ant-design/icons"; +import { Button, Card, Form, InputNumber, notification, Popover, Space } from "antd"; import dayjs from "../../utils/day"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import { - INSERT_SCOREBOARD_ENTRY, - QUERY_SCOREBOARD_ENTRY, - UPDATE_SCOREBOARD_ENTRY, + INSERT_SCOREBOARD_ENTRY, + QUERY_SCOREBOARD_ENTRY, + UPDATE_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -export default function ScoreboardAddButton({ - job, - disabled, - ...otherBtnProps - }) { - const {t} = useTranslation(); - const [insertScoreboardEntry] = useMutation(INSERT_SCOREBOARD_ENTRY); - const [updateScoreboardEntry] = useMutation(UPDATE_SCOREBOARD_ENTRY); - const [loading, setLoading] = useState(false); - const [form] = Form.useForm(); - const [visibility, setVisibility] = useState(false); - const [callQuery, {loading: entryLoading, data: entryData}] = useLazyQuery( - QUERY_SCOREBOARD_ENTRY - ); +export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps }) { + const { t } = useTranslation(); + const [insertScoreboardEntry] = useMutation(INSERT_SCOREBOARD_ENTRY); + const [updateScoreboardEntry] = useMutation(UPDATE_SCOREBOARD_ENTRY); + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + const [visibility, setVisibility] = useState(false); + const [callQuery, { loading: entryLoading, data: entryData }] = useLazyQuery(QUERY_SCOREBOARD_ENTRY); - useEffect(() => { - if (visibility) { - callQuery({variables: {jobid: job.id}}); + useEffect(() => { + if (visibility) { + callQuery({ variables: { jobid: job.id } }); + } + }, [visibility, job.id, callQuery]); + + useEffect(() => { + if (entryData && entryData.scoreboard && entryData.scoreboard[0]) { + form.setFieldsValue(entryData.scoreboard[0]); + } + }, [entryData, form]); + + const handleFinish = async (values) => { + logImEXEvent("job_close_add_to_scoreboard"); + values.date = dayjs(values.date).format("YYYY-MM-DD"); + + setLoading(true); + let result; + + if (entryData && entryData.scoreboard && entryData.scoreboard[0]) { + result = await updateScoreboardEntry({ + variables: { + sbId: entryData.scoreboard[0].id, + sbInput: values } - }, [visibility, job.id, callQuery]); + }); + } else { + result = await insertScoreboardEntry({ + variables: { sbInput: [{ jobid: job.id, ...values }] } + }); + } - useEffect(() => { - if (entryData && entryData.scoreboard && entryData.scoreboard[0]) { - form.setFieldsValue(entryData.scoreboard[0]); - } - }, [entryData, form]); + if (!!result.errors) { + notification["error"]({ + message: t("scoreboard.errors.adding", { + message: JSON.stringify(result.errors) + }) + }); + } else { + notification["success"]({ + message: t("scoreboard.successes.added") + }); + } + setLoading(false); + setVisibility(false); + }; - const handleFinish = async (values) => { - logImEXEvent("job_close_add_to_scoreboard"); - values.date = dayjs(values.date).format("YYYY-MM-DD"); - - setLoading(true); - let result; - - if (entryData && entryData.scoreboard && entryData.scoreboard[0]) { - result = await updateScoreboardEntry({ - variables: { - sbId: entryData.scoreboard[0].id, - sbInput: values, - }, - }); - } else { - result = await insertScoreboardEntry({ - variables: {sbInput: [{jobid: job.id, ...values}]}, - }); - } - - if (!!result.errors) { - notification["error"]({ - message: t("scoreboard.errors.adding", { - message: JSON.stringify(result.errors), - }), - }); - } else { - notification["success"]({ - message: t("scoreboard.successes.added"), - }); - } - setLoading(false); - setVisibility(false); - }; - - const overlay = ( - -
- {entryLoading ? ( - - ) : ( -
- - - - - - - - - - - - - - - - )} -
- {entryData && entryData.scoreboard && entryData.scoreboard[0] && ( - - - {t("jobs.labels.alreadyaddedtoscoreboard")} - - )} -
- ); - - const handleClick = (e) => { - setLoading(true); - const v = job.joblines.reduce( - (acc, val) => { - if (val.mod_lbr_ty !== "LAR") - acc = {...acc, bodyhrs: acc.bodyhrs + val.mod_lb_hrs}; - if (val.mod_lbr_ty === "LAR") - acc = {...acc, painthrs: acc.painthrs + val.mod_lb_hrs}; - return acc; - }, - { - bodyhrs: 0, - painthrs: 0, - } - ); - - //Add Labor Adjustments - v.painthrs = v.painthrs + (job.lbr_adjustments.LAR || 0); - v.bodyhrs = - v.bodyhrs + - Object.keys(job.lbr_adjustments) - .filter((key) => key !== "LAR") - .reduce((acc, val) => { - return acc + job.lbr_adjustments[val]; - }, 0); - form.setFieldsValue({ - date: dayjs(), - bodyhrs: Math.round(v.bodyhrs * 10) / 10, - painthrs: Math.round(v.painthrs * 10) / 10, - }); - setVisibility(true); - setLoading(false); - }; - - return ( - - - + + + + + + + + + + + + + + + )} +
+ {entryData && entryData.scoreboard && entryData.scoreboard[0] && ( + + + {t("jobs.labels.alreadyaddedtoscoreboard")} + + )} + + ); + + const handleClick = (e) => { + setLoading(true); + const v = job.joblines.reduce( + (acc, val) => { + if (val.mod_lbr_ty !== "LAR") acc = { ...acc, bodyhrs: acc.bodyhrs + val.mod_lb_hrs }; + if (val.mod_lbr_ty === "LAR") acc = { ...acc, painthrs: acc.painthrs + val.mod_lb_hrs }; + return acc; + }, + { + bodyhrs: 0, + painthrs: 0 + } ); + + //Add Labor Adjustments + v.painthrs = v.painthrs + (job.lbr_adjustments.LAR || 0); + v.bodyhrs = + v.bodyhrs + + Object.keys(job.lbr_adjustments) + .filter((key) => key !== "LAR") + .reduce((acc, val) => { + return acc + job.lbr_adjustments[val]; + }, 0); + form.setFieldsValue({ + date: dayjs(), + bodyhrs: Math.round(v.bodyhrs * 10) / 10, + painthrs: Math.round(v.painthrs * 10) / 10 + }); + setVisibility(true); + setLoading(false); + }; + + return ( + + + + ); } diff --git a/client/src/components/job-search-select/job-search-select.component.jsx b/client/src/components/job-search-select/job-search-select.component.jsx index 9c893d14a..117d0f407 100644 --- a/client/src/components/job-search-select/job-search-select.component.jsx +++ b/client/src/components/job-search-select/job-search-select.component.jsx @@ -1,119 +1,107 @@ -import {useLazyQuery} from "@apollo/client"; -import {Select, Space, Spin, Tag} from "antd"; +import { useLazyQuery } from "@apollo/client"; +import { Select, Space, Spin, Tag } from "antd"; import _ from "lodash"; -import React, {forwardRef, useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE, SEARCH_JOBS_FOR_AUTOCOMPLETE,} from "../../graphql/jobs.queries"; +import React, { forwardRef, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE, SEARCH_JOBS_FOR_AUTOCOMPLETE } from "../../graphql/jobs.queries"; import AlertComponent from "../alert/alert.component"; -import {OwnerNameDisplayFunction} from "../owner-name-display/owner-name-display.component"; -import { SearchOutlined } from '@ant-design/icons'; -import { LoadingOutlined } from '@ant-design/icons'; +import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; +import { SearchOutlined } from "@ant-design/icons"; +import { LoadingOutlined } from "@ant-design/icons"; -const {Option} = Select; +const { Option } = Select; const JobSearchSelect = ( - { - disabled, - convertedOnly = false, - notInvoiced = false, - notExported = true, - clm_no = false, - ...restProps - }, - ref + { disabled, convertedOnly = false, notInvoiced = false, notExported = true, clm_no = false, ...restProps }, + ref ) => { - const {t} = useTranslation(); - const [theOptions, setTheOptions] = useState([]); - const [callSearch, {loading, error, data}] = useLazyQuery( - SEARCH_JOBS_FOR_AUTOCOMPLETE, - {} + const { t } = useTranslation(); + const [theOptions, setTheOptions] = useState([]); + const [callSearch, { loading, error, data }] = useLazyQuery(SEARCH_JOBS_FOR_AUTOCOMPLETE, {}); + + const [callIdSearch, { loading: idLoading, error: idError, data: idData }] = useLazyQuery( + SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE + ); + + const executeSearch = (v) => { + if (v && v.variables?.search !== "" && v.variables.search.length >= 2) callSearch(v); + }; + const debouncedExecuteSearch = _.debounce(executeSearch, 500); + + const handleSearch = (value) => { + debouncedExecuteSearch({ + variables: { + search: value, + ...(convertedOnly || notExported + ? { + ...(convertedOnly ? { isConverted: true } : {}), + ...(notExported ? { notExported: true } : {}), + ...(notInvoiced ? { notInvoiced: true } : {}) + } + : {}) + } + }); + }; + + useEffect(() => { + if (restProps.value) { + callIdSearch({ variables: { id: restProps.value } }); // Sometimes results in a no-op. Not sure how to fix. + } + }, [restProps.value, callIdSearch]); + + useEffect(() => { + setTheOptions( + _.uniqBy( + [ + ...(idData && idData.jobs_by_pk ? [idData.jobs_by_pk] : []), + ...(data && data.search_jobs ? data.search_jobs : []) + ], + "id" + ) ); + }, [data, idData]); - const [callIdSearch, {loading: idLoading, error: idError, data: idData}] = - useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE); - - const executeSearch = (v) => { - if (v && v.variables?.search !== "" && v.variables.search.length >= 2) - callSearch(v); - }; - const debouncedExecuteSearch = _.debounce(executeSearch, 500); - - const handleSearch = (value) => { - debouncedExecuteSearch({ - variables: { - search: value, - ...(convertedOnly || notExported - ? { - ...(convertedOnly ? {isConverted: true} : {}), - ...(notExported ? {notExported: true} : {}), - ...(notInvoiced ? {notInvoiced: true} : {}), - } - : {}), - }, - }); - }; - - useEffect(() => { - if (restProps.value) { - callIdSearch({variables: {id: restProps.value}}); // Sometimes results in a no-op. Not sure how to fix. - } - }, [restProps.value, callIdSearch]); - - useEffect(() => { - setTheOptions( - _.uniqBy( - [ - ...(idData && idData.jobs_by_pk ? [idData.jobs_by_pk] : []), - ...(data && data.search_jobs ? data.search_jobs : []), - ], - "id" - ) - ); - }, [data, idData]); - - return ( -
- } + notFoundContent={loading ? : null} + {...restProps} + > + {theOptions + ? theOptions.map((o) => ( + - )) - : null} - - - {error ? : null} - {idError ? ( - - ) : null} -
- ); + + {o.status} + + + + )) + : null} + + + {error ? : null} + {idError ? : null} + + ); }; export default forwardRef(JobSearchSelect); diff --git a/client/src/components/job-send-parts-price-change/job-send-parts-price-change.component.jsx b/client/src/components/job-send-parts-price-change/job-send-parts-price-change.component.jsx index 7f218520c..fb26d9818 100644 --- a/client/src/components/job-send-parts-price-change/job-send-parts-price-change.component.jsx +++ b/client/src/components/job-send-parts-price-change/job-send-parts-price-change.component.jsx @@ -1,31 +1,31 @@ -import {Button, notification} from "antd"; +import { Button, notification } from "antd"; import axios from "axios"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; -export default function JobSendPartPriceChangeComponent({job}) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const handleClick = async () => { - setLoading(true); - try { - const ppcData = await axios.post("/job/ppc", {jobid: job.id}); - await axios.post("http://localhost:1337/ppc/", ppcData.data); - } catch (error) { - notification.open({ - type: "error", - message: t("jobs.errors.partspricechange", { - error: JSON.stringify(error), - }), - }); - } finally { - setLoading(false); - } - }; +export default function JobSendPartPriceChangeComponent({ job }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const handleClick = async () => { + setLoading(true); + try { + const ppcData = await axios.post("/job/ppc", { jobid: job.id }); + await axios.post("http://localhost:1337/ppc/", ppcData.data); + } catch (error) { + notification.open({ + type: "error", + message: t("jobs.errors.partspricechange", { + error: JSON.stringify(error) + }) + }); + } finally { + setLoading(false); + } + }; - return ( - - ); + return ( + + ); } diff --git a/client/src/components/job-sync-button/job-sync-button.component.jsx b/client/src/components/job-sync-button/job-sync-button.component.jsx index 08c294b1f..e69689340 100644 --- a/client/src/components/job-sync-button/job-sync-button.component.jsx +++ b/client/src/components/job-sync-button/job-sync-button.component.jsx @@ -1,23 +1,21 @@ -import {Button} from "antd"; +import { Button } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {SyncOutlined} from "@ant-design/icons"; -import {useNavigate} from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { SyncOutlined } from "@ant-design/icons"; +import { useNavigate } from "react-router-dom"; -export default function JobSyncButton({job}) { - const {t} = useTranslation(); - const history = useNavigate(); - const handleClick = () => { - history( - `/manage/available?availableJobId=${job.available_jobs[0].id}&clm_no=${job.clm_no}` - ); - }; - if (job && job.available_jobs && job.available_jobs.length > 0) - return ( - - ); - else return null; +export default function JobSyncButton({ job }) { + const { t } = useTranslation(); + const history = useNavigate(); + const handleClick = () => { + history(`/manage/available?availableJobId=${job.available_jobs[0].id}&clm_no=${job.clm_no}`); + }; + if (job && job.available_jobs && job.available_jobs.length > 0) + return ( + + ); + else return null; } diff --git a/client/src/components/job-totals-table/job-totals-table.component.jsx b/client/src/components/job-totals-table/job-totals-table.component.jsx index f56322c75..e475ef5fd 100644 --- a/client/src/components/job-totals-table/job-totals-table.component.jsx +++ b/client/src/components/job-totals-table/job-totals-table.component.jsx @@ -1,12 +1,12 @@ -import {Card, Col, Collapse, Result, Row} from "antd"; +import { Card, Col, Collapse, Result, Row } from "antd"; //import { JsonEditor as Editor } from "jsoneditor-react"; //import "jsoneditor-react/es/editor.min.css"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {selectCurrentUser} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectCurrentUser } from "../../redux/user/user.selectors"; import JobCalculateTotals from "../job-calculate-totals/job-calculate-totals.component"; import "./job-totals-table.styles.scss"; import JobTotalsTableLabor from "./job-totals.table.labor.component"; @@ -15,88 +15,84 @@ import JobTotalsTableParts from "./job-totals.table.parts.component"; import JobTotalsTableTotals from "./job-totals.table.totals.component"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - jobRO: selectJobReadOnly, + currentUser: selectCurrentUser, + jobRO: selectJobReadOnly }); const colSpan = { - md: {span: 24}, - lg: {span: 12}, + md: { span: 24 }, + lg: { span: 12 } }; -export function JobsTotalsTableComponent({jobRO, currentUser, job}) { - const {t} = useTranslation(); - - if (!!!job.job_totals) { - return ( - - } - /> - - ); - } +export function JobsTotalsTableComponent({ jobRO, currentUser, job }) { + const { t } = useTranslation(); + if (!!!job.job_totals) { return ( -
- -
- - - + + } /> + + ); + } + + return ( +
+ +
+ + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - {(currentUser.email.includes("@imex.") || - currentUser.email.includes("@rome.")) && ( - - - - - -
+
+ + + + + + + + + + + + {(currentUser.email.includes("@imex.") || currentUser.email.includes("@rome.")) && ( + + + + + +
                           {JSON.stringify(
-                              {
-                                  CIECA: job.cieca_ttl && job.cieca_ttl.data,
-                                  CIECASTL: job.cieca_stl && job.cieca_stl.data,
-                                  ImEXCalc: job.job_totals,
-                              },
-                              null,
-                              2
+                            {
+                              CIECA: job.cieca_ttl && job.cieca_ttl.data,
+                              CIECASTL: job.cieca_stl && job.cieca_stl.data,
+                              ImEXCalc: job.job_totals
+                            },
+                            null,
+                            2
                           )}
                         
-
-
-
-
- - )} - - - - - ); + + + + + + )} + + + + + ); } export default connect(mapStateToProps, null)(JobsTotalsTableComponent); diff --git a/client/src/components/job-totals-table/job-totals.table.labor.component.jsx b/client/src/components/job-totals-table/job-totals.table.labor.component.jsx index 206b13978..2e8bb48e9 100644 --- a/client/src/components/job-totals-table/job-totals.table.labor.component.jsx +++ b/client/src/components/job-totals-table/job-totals.table.labor.component.jsx @@ -1,218 +1,172 @@ -import {Space, Table} from "antd"; +import { Space, Table } from "antd"; import Dinero from "dinero.js"; -import React, {useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {alphaSort} from "../../utils/sorters"; +import { alphaSort } from "../../utils/sorters"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; -export default function JobTotalsTableLabor({job}) { - const {t} = useTranslation(); - const [state, setState] = useState({ - sortedInfo: { - columnKey: "profitcenter_labor", - field: "profitcenter_labor", - order: "ascend", - }, - filteredInfo: {}, - }); +export default function JobTotalsTableLabor({ job }) { + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: { + columnKey: "profitcenter_labor", + field: "profitcenter_labor", + order: "ascend" + }, + filteredInfo: {} + }); - const data = useMemo(() => { - return Object.keys(job.job_totals.rates) - .filter( - (key) => - key !== "mapa" && - key !== "mash" && - key !== "subtotal" && - key !== "rates_subtotal" - ) - .map((key) => { - return { - id: key, - ...job.job_totals.rates[key], - }; - }); - }, [job.job_totals.rates]); + const data = useMemo(() => { + return Object.keys(job.job_totals.rates) + .filter((key) => key !== "mapa" && key !== "mash" && key !== "subtotal" && key !== "rates_subtotal") + .map((key) => { + return { + id: key, + ...job.job_totals.rates[key] + }; + }); + }, [job.job_totals.rates]); - const columns = [ - { - title: t("joblines.fields.profitcenter_labor"), - dataIndex: "profitcenter_labor", - key: "profitcenter_labor", - defaultSortOrder: "ascend", - sorter: (a, b) => - alphaSort( - t(`jobs.fields.rate_${a.id.toLowerCase()}`), - t(`jobs.fields.rate_${b.id.toLowerCase()}`) - ), - sortOrder: - state.sortedInfo.columnKey === "profitcenter_labor" && - state.sortedInfo.order, - render: (text, record) => - t(`jobs.fields.rate_${record.id.toLowerCase()}`), - }, - { - title: t("jobs.labels.rates"), - dataIndex: "rate", - key: "rate", - align: "right", - sorter: (a, b) => a.rate - b.rate, - sortOrder: - state.sortedInfo.columnKey === "rate" && state.sortedInfo.order, - render: (text, record) => ( - {record.rate} - ), - }, - { - title: t("joblines.fields.mod_lb_hrs"), - dataIndex: "mod_lb_hrs", + const columns = [ + { + title: t("joblines.fields.profitcenter_labor"), + dataIndex: "profitcenter_labor", + key: "profitcenter_labor", + defaultSortOrder: "ascend", + sorter: (a, b) => + alphaSort(t(`jobs.fields.rate_${a.id.toLowerCase()}`), t(`jobs.fields.rate_${b.id.toLowerCase()}`)), + sortOrder: state.sortedInfo.columnKey === "profitcenter_labor" && state.sortedInfo.order, + render: (text, record) => t(`jobs.fields.rate_${record.id.toLowerCase()}`) + }, + { + title: t("jobs.labels.rates"), + dataIndex: "rate", + key: "rate", + align: "right", + sorter: (a, b) => a.rate - b.rate, + sortOrder: state.sortedInfo.columnKey === "rate" && state.sortedInfo.order, + render: (text, record) => {record.rate} + }, + { + title: t("joblines.fields.mod_lb_hrs"), + dataIndex: "mod_lb_hrs", - key: "mod_lb_hrs", - sorter: (a, b) => a.mod_lb_hrs - b.mod_lb_hrs, - sortOrder: - state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order, + key: "mod_lb_hrs", + sorter: (a, b) => a.mod_lb_hrs - b.mod_lb_hrs, + sortOrder: state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order, - render: (text, record) => record.hours.toFixed(1), - }, - { - title: t("joblines.fields.total"), - dataIndex: "total", - key: "total", - align: "right", - sorter: (a, b) => a.total.amount - b.total.amount, - sortOrder: - state.sortedInfo.columnKey === "total" && state.sortedInfo.order, + render: (text, record) => record.hours.toFixed(1) + }, + { + title: t("joblines.fields.total"), + dataIndex: "total", + key: "total", + align: "right", + sorter: (a, b) => a.total.amount - b.total.amount, + sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order, - render: (text, record) => Dinero(record.total).toFormat(), - }, - ]; + render: (text, record) => Dinero(record.total).toFormat() + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - return ( -
( - <> - - - - {t("jobs.labels.labor_rates_subtotal")} - - - - - {( - job.job_totals.rates.mapa.hours + - job.job_totals.rates.mash.hours - ).toFixed(1)} - - - - {Dinero( - job.job_totals.rates.rates_subtotal - ).toFormat()} - - - - - - - {t("jobs.labels.mapa")} - {InstanceRenderManager({ - imex: - job.materials && - job.materials.mapa && - job.materials.mapa.cal_maxdlr && - job.materials.mapa.cal_maxdlr > 0 && - t("jobs.labels.threshhold", { - amount: job.materials.mapa - .cal_maxdlr, - }), - rome: - job.materials && - job.materials.MAPA && - job.materials.MAPA.cal_maxdlr !== - undefined && - t("jobs.labels.threshhold", { - amount: job.materials.MAPA - .cal_maxdlr, - }), - })} - - - - - {job.job_totals.rates.mapa.rate} - - - - {job.job_totals.rates.mapa.hours.toFixed(1)} - - - {Dinero(job.job_totals.rates.mapa.total).toFormat()} - - - - - - {t("jobs.labels.mash")} - { - InstanceRenderManager({ - imex:job.materials && - job.materials.mash && - job.materials.mash.cal_maxdlr && - job.materials.mash.cal_maxdlr > 0 && - t("jobs.labels.threshhold", { - amount: job.materials.mash.cal_maxdlr, - }), - rome: job.materials && - job.materials.MASH && - job.materials.MASH.cal_maxdlr !== - undefined && - t("jobs.labels.threshhold", { - amount: job.materials.MASH.cal_maxdlr, - }) - }) - } - - - - - {job.job_totals.rates.mash.rate} - - - - {job.job_totals.rates.mash.hours.toFixed(1)} - - - {Dinero(job.job_totals.rates.mash.total).toFormat()} - - - - - {t("jobs.labels.rates_subtotal")} - - - - - - {Dinero( - job.job_totals.rates.subtotal - ).toFormat()} - - - - - )} - /> - ); + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + return ( +
( + <> + + + {t("jobs.labels.labor_rates_subtotal")} + + + + {(job.job_totals.rates.mapa.hours + job.job_totals.rates.mash.hours).toFixed(1)} + + + {Dinero(job.job_totals.rates.rates_subtotal).toFormat()} + + + + + + {t("jobs.labels.mapa")} + {InstanceRenderManager({ + imex: + job.materials && + job.materials.mapa && + job.materials.mapa.cal_maxdlr && + job.materials.mapa.cal_maxdlr > 0 && + t("jobs.labels.threshhold", { + amount: job.materials.mapa.cal_maxdlr + }), + rome: + job.materials && + job.materials.MAPA && + job.materials.MAPA.cal_maxdlr !== undefined && + t("jobs.labels.threshhold", { + amount: job.materials.MAPA.cal_maxdlr + }) + })} + + + + {job.job_totals.rates.mapa.rate} + + {job.job_totals.rates.mapa.hours.toFixed(1)} + {Dinero(job.job_totals.rates.mapa.total).toFormat()} + + + + + {t("jobs.labels.mash")} + {InstanceRenderManager({ + imex: + job.materials && + job.materials.mash && + job.materials.mash.cal_maxdlr && + job.materials.mash.cal_maxdlr > 0 && + t("jobs.labels.threshhold", { + amount: job.materials.mash.cal_maxdlr + }), + rome: + job.materials && + job.materials.MASH && + job.materials.MASH.cal_maxdlr !== undefined && + t("jobs.labels.threshhold", { + amount: job.materials.MASH.cal_maxdlr + }) + })} + + + + {job.job_totals.rates.mash.rate} + + {job.job_totals.rates.mash.hours.toFixed(1)} + {Dinero(job.job_totals.rates.mash.total).toFormat()} + + + + {t("jobs.labels.rates_subtotal")} + + + + + {Dinero(job.job_totals.rates.subtotal).toFormat()} + + + + )} + /> + ); } diff --git a/client/src/components/job-totals-table/job-totals.table.other.component.jsx b/client/src/components/job-totals-table/job-totals.table.other.component.jsx index 3fed60af3..797451165 100644 --- a/client/src/components/job-totals-table/job-totals.table.other.component.jsx +++ b/client/src/components/job-totals-table/job-totals.table.other.component.jsx @@ -1,106 +1,101 @@ -import {Table} from "antd"; +import { Table } from "antd"; import Dinero from "dinero.js"; -import React, {useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {alphaSort} from "../../utils/sorters"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { alphaSort } from "../../utils/sorters"; -export default function JobTotalsTableOther({job}) { - const {t} = useTranslation(); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {}, - }); +export default function JobTotalsTableOther({ job }) { + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: {} + }); - const data = useMemo(() => { - return [ - ...((job.job_totals.additional.additionalCostItems && - job.job_totals.additional.additionalCostItems.map((i) => { - return { - key: i.key, - total: i.total, - }; - })) || - []), - { - key: t("jobs.fields.adjustment_bottom_line"), - total: job.job_totals.additional.adjustments, - }, - { - key: t("jobs.fields.towing_payable"), - total: job.job_totals.additional.towing, - }, - { - key: t("jobs.fields.storage_payable"), - total: job.job_totals.additional.storage, - }, - // { - // key: t("jobs.fields.ca_bc_pvrt"), - // total: job.job_totals.additional.pvrt, - // }, - ]; - }, [job.job_totals, t]); - - const columns = [ - { - title: t("general.labels.item"), - dataIndex: "key", - key: "key", - sorter: (a, b) => alphaSort(a.key, b.key), - sortOrder: state.sortedInfo.columnKey === "key" && state.sortedInfo.order, - width: "0%", - }, - { - title: t("joblines.fields.total"), - dataIndex: "total", - key: "total", - sorter: (a, b) => a.total.amount - b.total.amount, - sortOrder: - state.sortedInfo.columnKey === "total" && state.sortedInfo.order, - width: "20%", - align: "right", - render: (text, record) => Dinero(record.total).toFormat(), - }, + const data = useMemo(() => { + return [ + ...((job.job_totals.additional.additionalCostItems && + job.job_totals.additional.additionalCostItems.map((i) => { + return { + key: i.key, + total: i.total + }; + })) || + []), + { + key: t("jobs.fields.adjustment_bottom_line"), + total: job.job_totals.additional.adjustments + }, + { + key: t("jobs.fields.towing_payable"), + total: job.job_totals.additional.towing + }, + { + key: t("jobs.fields.storage_payable"), + total: job.job_totals.additional.storage + } + // { + // key: t("jobs.fields.ca_bc_pvrt"), + // total: job.job_totals.additional.pvrt, + // }, ]; + }, [job.job_totals, t]); - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - return ( -
( - <> - - - {t("jobs.labels.additionaltotal")} - + const columns = [ + { + title: t("general.labels.item"), + dataIndex: "key", + key: "key", + sorter: (a, b) => alphaSort(a.key, b.key), + sortOrder: state.sortedInfo.columnKey === "key" && state.sortedInfo.order, + width: "0%" + }, + { + title: t("joblines.fields.total"), + dataIndex: "total", + key: "total", + sorter: (a, b) => a.total.amount - b.total.amount, + sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order, + width: "20%", + align: "right", + render: (text, record) => Dinero(record.total).toFormat() + } + ]; - - - {Dinero(job.job_totals.additional.total).toFormat()} - - - - - - {t("jobs.labels.subletstotal")} - + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + return ( +
( + <> + + + {t("jobs.labels.additionaltotal")} + - - - {Dinero(job.job_totals.parts.sublets.total).toFormat()} - - - - - )} - /> - ); + + {Dinero(job.job_totals.additional.total).toFormat()} + + + + + {t("jobs.labels.subletstotal")} + + + + {Dinero(job.job_totals.parts.sublets.total).toFormat()} + + + + )} + /> + ); } 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 e888213ef..bfe6e5bf2 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 @@ -1,133 +1,113 @@ -import {Table} from "antd"; +import { Table } from "antd"; import Dinero from "dinero.js"; -import React, {useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {alphaSort} from "../../utils/sorters"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { alphaSort } from "../../utils/sorters"; -export default function JobTotalsTableParts({job}) { - const {t} = useTranslation(); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {}, +export default function JobTotalsTableParts({ job }) { + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: {} + }); + + const insuranceAdjustments = useMemo(() => { + if (!job.job_totals) return []; + if (!job.job_totals?.parts?.adjustments) return []; + const adjs = []; + Object.keys(job.job_totals?.parts?.adjustments).forEach((key) => { + if (Dinero(job.job_totals?.parts?.adjustments[key]).getAmount() !== 0) { + adjs.push({ + id: key, + amount: Dinero(job.job_totals.parts.adjustments[key]) + }); + } }); - const insuranceAdjustments = useMemo(() => { - if (!job.job_totals) return []; - if (!job.job_totals?.parts?.adjustments) return []; - const adjs = []; - Object.keys(job.job_totals?.parts?.adjustments).forEach((key) => { - if (Dinero(job.job_totals?.parts?.adjustments[key]).getAmount() !== 0) { - adjs.push({ - id: key, - amount: Dinero(job.job_totals.parts.adjustments[key]), - }); - } - }); + return adjs; + }, [job.job_totals]); - return adjs; - }, [job.job_totals]); + const data = useMemo(() => { + return Object.keys(job.job_totals.parts.parts.list) + .filter((key) => key !== "mapa" && key !== "mash" && key !== "subtotal" && key !== "rates_subtotal") + .map((key) => { + return { + id: key, + ...job.job_totals.parts.parts.list[key] + }; + }); + }, [job.job_totals.parts.parts.list]); - const data = useMemo(() => { - return Object.keys(job.job_totals.parts.parts.list) - .filter( - (key) => - key !== "mapa" && - key !== "mash" && - key !== "subtotal" && - key !== "rates_subtotal" + const columns = [ + { + title: t("joblines.fields.part_type"), + dataIndex: "id", + key: "id", + sorter: (a, b) => alphaSort(t(`jobs.fields.${a.id.toLowerCase()}`), t(`jobs.fields.${b.id.toLowerCase()}`)), + width: "80%", + sortOrder: state.sortedInfo.columnKey === "id" && state.sortedInfo.order, + render: (text, record) => t(`jobs.fields.${record.id.toLowerCase()}`) + }, + { + title: t("joblines.fields.total"), + dataIndex: "total", + key: "total", + sorter: (a, b) => a.total.amount - b.total.amount, + sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order, + width: "20%", + align: "right", + render: (text, record) => Dinero(record.total).toFormat() + } + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + return ( +
( + <> + + {t("jobs.labels.prt_dsmk_total")} + + {Dinero(job.job_totals.parts.parts.prt_dsmk_total).toFormat()} + + + + + + {t("jobs.labels.partstotal")} + + + + {Dinero(job.job_totals.parts.parts.total).toFormat()} + + + { + //TODO:AIO This shoudl only be in the US version. need to verify whether this causes problems for the CA version. + insuranceAdjustments.length > 0 && ( + + {t("jobs.labels.profileadjustments")} + ) - .map((key) => { - return { - id: key, - ...job.job_totals.parts.parts.list[key], - }; - }); - }, [job.job_totals.parts.parts.list]); + } + {insuranceAdjustments.map((adj, idx) => ( + + {t(`jobs.fields.${adj.id.toLowerCase()}`)} - const columns = [ - { - title: t("joblines.fields.part_type"), - dataIndex: "id", - key: "id", - sorter: (a, b) => - alphaSort( - t(`jobs.fields.${a.id.toLowerCase()}`), - t(`jobs.fields.${b.id.toLowerCase()}`) - ), - width: "80%", - sortOrder: state.sortedInfo.columnKey === "id" && state.sortedInfo.order, - render: (text, record) => t(`jobs.fields.${record.id.toLowerCase()}`), - }, - { - title: t("joblines.fields.total"), - dataIndex: "total", - key: "total", - sorter: (a, b) => a.total.amount - b.total.amount, - sortOrder: - state.sortedInfo.columnKey === "total" && state.sortedInfo.order, - width: "20%", - align: "right", - render: (text, record) => Dinero(record.total).toFormat(), - }, - ]; - - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - return ( -
( - <> - - - {t("jobs.labels.prt_dsmk_total")} - - - {Dinero(job.job_totals.parts.parts.prt_dsmk_total).toFormat()} - - - - - - {t("jobs.labels.partstotal")} - - - - - {Dinero(job.job_totals.parts.parts.total).toFormat()} - - - - { - //TODO:AIO This shoudl only be in the US version. need to verify whether this causes problems for the CA version. - insuranceAdjustments.length > 0 && ( - - - {t("jobs.labels.profileadjustments")} - - - )} - {insuranceAdjustments.map((adj, idx) => ( - - - {t(`jobs.fields.${adj.id.toLowerCase()}`)} - - - - {adj.amount.toFormat()} - - - ))} - - )} - /> - ); + {adj.amount.toFormat()} + + ))} + + )} + /> + ); } 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 ac18af0b5..5be1fb010 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 @@ -1,16 +1,16 @@ -import { Table } from 'antd'; -import Dinero from 'dinero.js'; -import React, { useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Table } from "antd"; +import Dinero from "dinero.js"; +import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; -import { connect } from 'react-redux'; -import { createStructuredSelector } from 'reselect'; -import { selectBodyshop } from '../../redux/user/user.selectors'; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) @@ -23,171 +23,171 @@ export function JobTotalsTableTotals({ bodyshop, job }) { const data = useMemo(() => { return [ { - key: t('jobs.labels.subtotal'), + key: t("jobs.labels.subtotal"), total: job.job_totals.totals.subtotal, - bold: true, + bold: true }, ...InstanceRenderManager({ imex: [ { - key: t('jobs.labels.local_tax_amt'), - total: job.job_totals.totals.local_tax, + key: t("jobs.labels.local_tax_amt"), + total: job.job_totals.totals.local_tax }, { - key: t('jobs.labels.state_tax_amt'), - total: job.job_totals.totals.state_tax, + key: t("jobs.labels.state_tax_amt"), + total: job.job_totals.totals.state_tax }, - ...(bodyshop.region_config === 'CA_BC' + ...(bodyshop.region_config === "CA_BC" ? [ { - key: t('jobs.fields.ca_bc_pvrt'), - total: job.job_totals.additional.pvrt, - }, + key: t("jobs.fields.ca_bc_pvrt"), + total: job.job_totals.additional.pvrt + } ] : []), { - key: t('jobs.labels.federal_tax_amt'), - total: job.job_totals.totals.federal_tax, - }, + key: t("jobs.labels.federal_tax_amt"), + total: job.job_totals.totals.federal_tax + } ], - promanager: 'USE_ROME', + promanager: "USE_ROME", rome: job.job_totals.totals.us_sales_tax_breakdown ? [ { - key: `${bodyshop.md_responsibility_centers.taxes.tax_ty1?.tax_type1 || 'T1'} - ${[ + key: `${bodyshop.md_responsibility_centers.taxes.tax_ty1?.tax_type1 || "T1"} - ${[ job.cieca_pft.ty1_rate1, job.cieca_pft.ty1_rate2, job.cieca_pft.ty1_rate3, job.cieca_pft.ty1_rate4, - job.cieca_pft.ty1_rate5, + job.cieca_pft.ty1_rate5 ] .filter((i) => i > 0) - .join(', ')}%`, - total: job.job_totals.totals.us_sales_tax_breakdown.ty1Tax, + .join(", ")}%`, + total: job.job_totals.totals.us_sales_tax_breakdown.ty1Tax }, { - key: `${bodyshop.md_responsibility_centers.taxes.tax_ty2?.tax_type2 || 'T2'} - ${[ + key: `${bodyshop.md_responsibility_centers.taxes.tax_ty2?.tax_type2 || "T2"} - ${[ job.cieca_pft.ty2_rate1, job.cieca_pft.ty2_rate2, job.cieca_pft.ty2_rate3, job.cieca_pft.ty2_rate4, - job.cieca_pft.ty2_rate5, + job.cieca_pft.ty2_rate5 ] .filter((i) => i > 0) - .join(', ')}%`, - total: job.job_totals.totals.us_sales_tax_breakdown.ty2Tax, + .join(", ")}%`, + total: job.job_totals.totals.us_sales_tax_breakdown.ty2Tax }, { - key: `${bodyshop.md_responsibility_centers.taxes.tax_ty3?.tax_type3 || 'T3'} - ${[ + key: `${bodyshop.md_responsibility_centers.taxes.tax_ty3?.tax_type3 || "T3"} - ${[ job.cieca_pft.ty3_rate1, job.cieca_pft.ty3_rate2, job.cieca_pft.ty3_rate3, job.cieca_pft.ty3_rate4, - job.cieca_pft.ty3_rate5, + job.cieca_pft.ty3_rate5 ] .filter((i) => i > 0) - .join(', ')}%`, - total: job.job_totals.totals.us_sales_tax_breakdown.ty3Tax, + .join(", ")}%`, + total: job.job_totals.totals.us_sales_tax_breakdown.ty3Tax }, { - key: `${bodyshop.md_responsibility_centers.taxes.tax_ty4?.tax_type4 || 'T4'} - ${[ + key: `${bodyshop.md_responsibility_centers.taxes.tax_ty4?.tax_type4 || "T4"} - ${[ job.cieca_pft.ty4_rate1, job.cieca_pft.ty4_rate2, job.cieca_pft.ty4_rate3, job.cieca_pft.ty4_rate4, - job.cieca_pft.ty4_rate5, + job.cieca_pft.ty4_rate5 ] .filter((i) => i > 0) - .join(', ')}%`, - total: job.job_totals.totals.us_sales_tax_breakdown.ty4Tax, + .join(", ")}%`, + 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 || "TT"} - ${[ job.cieca_pft.ty5_rate1, job.cieca_pft.ty5_rate2, job.cieca_pft.ty5_rate3, job.cieca_pft.ty5_rate4, - job.cieca_pft.ty5_rate5, + job.cieca_pft.ty5_rate5 ] .filter((i) => i > 0) - .join(', ')}%`, - total: job.job_totals.totals.us_sales_tax_breakdown.ty5Tax, + .join(", ")}%`, + total: job.job_totals.totals.us_sales_tax_breakdown.ty5Tax }, { - key: t('jobs.labels.total_sales_tax'), + key: t("jobs.labels.total_sales_tax"), bold: true, total: Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty1Tax) .add(Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty2Tax)) .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)) - .toJSON(), - }, + .toJSON() + } ].filter((item) => item.total.amount !== 0) : [ { - key: t('jobs.labels.state_tax_amt'), - total: job.job_totals.totals.state_tax, - }, - ], + key: t("jobs.labels.state_tax_amt"), + total: job.job_totals.totals.state_tax + } + ] }), { - key: t('jobs.labels.total_repairs'), + key: t("jobs.labels.total_repairs"), total: job.job_totals.totals.total_repairs, - bold: true, + bold: true }, { - key: t('jobs.fields.ded_amt'), - total: job.job_totals.totals.custPayable.deductible, + key: t("jobs.fields.ded_amt"), + total: job.job_totals.totals.custPayable.deductible }, // { // key: t("jobs.fields.federal_tax_payable"), // total: job.job_totals.totals.custPayable.federal_tax, // }, { - key: t('jobs.fields.other_amount_payable'), - total: job.job_totals.totals.custPayable.other_customer_amount, + key: t("jobs.fields.other_amount_payable"), + total: job.job_totals.totals.custPayable.other_customer_amount }, { - key: t('jobs.fields.depreciation_taxes'), - total: job.job_totals.totals.custPayable.dep_taxes, + key: t("jobs.fields.depreciation_taxes"), + total: job.job_totals.totals.custPayable.dep_taxes }, { - key: t('jobs.labels.total_cust_payable'), + key: t("jobs.labels.total_cust_payable"), total: job.job_totals.totals.custPayable.total, - bold: true, + bold: true }, { - key: t('jobs.labels.net_repairs'), + key: t("jobs.labels.net_repairs"), total: job.job_totals.totals.net_repairs, - bold: true, - }, + bold: true + } ]; }, [job.job_totals, job.cieca_pft, t, bodyshop.md_responsibility_centers]); const columns = [ { //title: t("joblines.fields.part_type"), - dataIndex: 'key', - key: 'key', - width: '80%', + dataIndex: "key", + key: "key", + width: "80%", onCell: (record, rowIndex) => { - return { style: { fontWeight: record.bold && 'bold' } }; - }, + return { style: { fontWeight: record.bold && "bold" } }; + } }, { - title: t('joblines.fields.total'), - dataIndex: 'total', - key: 'total', - align: 'right', + title: t("joblines.fields.total"), + dataIndex: "total", + key: "total", + align: "right", render: (text, record) => Dinero(record.total).toFormat(), - width: '20%', + width: "20%", onCell: (record, rowIndex) => { - return { style: { fontWeight: record.bold && 'bold' } }; - }, - }, + return { style: { fontWeight: record.bold && "bold" } }; + } + } ]; return ( @@ -198,7 +198,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) { pagination={false} dataSource={data} scroll={{ - x: true, + x: true }} /> ); diff --git a/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx b/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx index 325f58e4e..1cc4942e1 100644 --- a/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx +++ b/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx @@ -1,64 +1,64 @@ -import {DownCircleFilled} from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {Button, Dropdown, notification} from "antd"; +import { DownCircleFilled } from "@ant-design/icons"; +import { useMutation } from "@apollo/client"; +import { Button, Dropdown, notification } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {UPDATE_JOB_STATUS} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminStatus); -export function JobsAdminStatus({insertAuditTrail, bodyshop, job}) { - const {t} = useTranslation(); +export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) { + const { t } = useTranslation(); - const [mutationUpdateJobstatus] = useMutation(UPDATE_JOB_STATUS); - const updateJobStatus = (status) => { - mutationUpdateJobstatus({ - variables: {jobId: job.id, status: status}, - }) - .then((r) => { - notification["success"]({message: t("jobs.successes.save")}); - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.admin_jobstatuschange(status), - type: "admin_jobstatuschange",}); - // refetch(); - }) - .catch((error) => { - notification["error"]({message: t("jobs.errors.saving")}); - }); - }; + const [mutationUpdateJobstatus] = useMutation(UPDATE_JOB_STATUS); + const updateJobStatus = (status) => { + mutationUpdateJobstatus({ + variables: { jobId: job.id, status: status } + }) + .then((r) => { + notification["success"]({ message: t("jobs.successes.save") }); + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.admin_jobstatuschange(status), + type: "admin_jobstatuschange" + }); + // refetch(); + }) + .catch((error) => { + notification["error"]({ message: t("jobs.errors.saving") }); + }); + }; - const statusMenu = { - items: bodyshop.md_ro_statuses.statuses.map((item) => ({ - key: item, - label: item, - })), - onClick: (e) => { - updateJobStatus(e.key); - }, + const statusMenu = { + items: bodyshop.md_ro_statuses.statuses.map((item) => ({ + key: item, + label: item + })), + onClick: (e) => { + updateJobStatus(e.key); } + }; - return ( - <> - - - - - ); + + + + + ); } diff --git a/client/src/components/jobs-admin-class/jobs-admin-class.component.jsx b/client/src/components/jobs-admin-class/jobs-admin-class.component.jsx index 4d5b80541..35c326675 100644 --- a/client/src/components/jobs-admin-class/jobs-admin-class.component.jsx +++ b/client/src/components/jobs-admin-class/jobs-admin-class.component.jsx @@ -1,84 +1,75 @@ -import {useMutation} from "@apollo/client"; -import {Button, Form, notification, Popconfirm, Select} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useMutation } from "@apollo/client"; +import { Button, Form, notification, Popconfirm, Select } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminClass); -export function JobsAdminClass({bodyshop, job}) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const [form] = Form.useForm(); - const [updateJob] = useMutation(UPDATE_JOB); +export function JobsAdminClass({ bodyshop, job }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + const [updateJob] = useMutation(UPDATE_JOB); - const handleFinish = async (values) => { - setLoading(true); - const result = await updateJob({ - variables: {jobId: job.id, job: values}, - }); + const handleFinish = async (values) => { + setLoading(true); + const result = await updateJob({ + variables: { jobId: job.id, job: values } + }); - if (!!!result.errors) { - notification["success"]({message: t("jobs.successes.save")}); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - //Get the owner details, populate it all back into the job. - }; + if (!!!result.errors) { + notification["success"]({ message: t("jobs.successes.save") }); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + //Get the owner details, populate it all back into the job. + }; - useEffect(() => { - //form.resetFields(); - }, [form, job]); + useEffect(() => { + //form.resetFields(); + }, [form, job]); - return ( -
-
- - - - + return ( +
+
+ + + + - form.submit()} - > - - -
- ); + form.submit()}> + + +
+ ); } diff --git a/client/src/components/jobs-admin-dates/jobs-admin-dates.component.jsx b/client/src/components/jobs-admin-dates/jobs-admin-dates.component.jsx index 23375fac8..2a340b9dd 100644 --- a/client/src/components/jobs-admin-dates/jobs-admin-dates.component.jsx +++ b/client/src/components/jobs-admin-dates/jobs-admin-dates.component.jsx @@ -1,198 +1,152 @@ -import {useMutation} from "@apollo/client"; -import {Button, Form, notification} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Form, notification } from "antd"; import dayjs from "../../utils/day"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {DateTimeFormat} from "./../../utils/DateFormatter"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { DateTimeFormat } from "./../../utils/DateFormatter"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser + //currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsAdminDatesChange); +export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminDatesChange); -export function JobsAdminDatesChange({insertAuditTrail, job}) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const [form] = Form.useForm(); - const [updateJob] = useMutation(UPDATE_JOB); +export function JobsAdminDatesChange({ insertAuditTrail, job }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + const [updateJob] = useMutation(UPDATE_JOB); - const handleFinish = async (values) => { - setLoading(true); - const result = await updateJob({ - variables: {jobId: job.id, job: values}, - refetchQueries: ["GET_JOB_BY_PK"], - awaitRefetchQueries: true, - }); + const handleFinish = async (values) => { + setLoading(true); + const result = await updateJob({ + variables: { jobId: job.id, job: values }, + refetchQueries: ["GET_JOB_BY_PK"], + awaitRefetchQueries: true + }); - const changedAuditFields = form.getFieldsValue( - true, - (meta) => meta && meta.touched - ); + const changedAuditFields = form.getFieldsValue(true, (meta) => meta && meta.touched); - Object.keys(changedAuditFields).forEach((key) => { - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.admin_jobfieldchange( - key, - changedAuditFields[key] instanceof dayjs - ? DateTimeFormat(changedAuditFields[key]) - : changedAuditFields[key] - ), - type: "admin_jobfieldchange",}); - }); + Object.keys(changedAuditFields).forEach((key) => { + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.admin_jobfieldchange( + key, + changedAuditFields[key] instanceof dayjs ? DateTimeFormat(changedAuditFields[key]) : changedAuditFields[key] + ), + type: "admin_jobfieldchange" + }); + }); - if (!!!result.errors) { - notification["success"]({message: t("jobs.successes.save")}); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } + if (!!!result.errors) { + notification["success"]({ message: t("jobs.successes.save") }); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } - form.resetFields(); + form.resetFields(); - setLoading(false); - //Get the owner details, populate it all back into the job. - }; + setLoading(false); + //Get the owner details, populate it all back into the job. + }; - useEffect(() => { - //form.resetFields(); - }, [form, job]); + useEffect(() => { + //form.resetFields(); + }, [form, job]); - return ( -
-
- - - - - - - - - - - - - - + return ( +
+ + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - - - + + + + + + + + + + + - -
- ); + +
+ ); } diff --git a/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx b/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx index 90cc87b34..319a85bb5 100644 --- a/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx +++ b/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx @@ -1,73 +1,62 @@ -import {useMutation} from "@apollo/client"; -import {Button, Space, notification} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import { - DELETE_DELIVERY_CHECKLIST, - DELETE_INTAKE_CHECKLIST, -} from "../../graphql/jobs.queries"; +import { useMutation } from "@apollo/client"; +import { Button, Space, notification } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { DELETE_DELIVERY_CHECKLIST, DELETE_INTAKE_CHECKLIST } from "../../graphql/jobs.queries"; -export default function JobAdminDeleteIntake({job}) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); +export default function JobAdminDeleteIntake({ job }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); - const [deleteIntake] = useMutation(DELETE_INTAKE_CHECKLIST); - const [deleteDelivery] = useMutation(DELETE_DELIVERY_CHECKLIST); + const [deleteIntake] = useMutation(DELETE_INTAKE_CHECKLIST); + const [deleteDelivery] = useMutation(DELETE_DELIVERY_CHECKLIST); - const handleDelete = async (values) => { - setLoading(true); - const result = await deleteIntake({ - variables: {jobId: job.id}, - }); + const handleDelete = async (values) => { + setLoading(true); + const result = await deleteIntake({ + variables: { jobId: job.id } + }); - if (!!!result.errors) { - notification["success"]({message: t("jobs.successes.save")}); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - }; + if (!!!result.errors) { + notification["success"]({ message: t("jobs.successes.save") }); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + }; - const handleDeleteDelivery = async (values) => { - setLoading(true); - const result = await deleteDelivery({ - variables: {jobId: job.id}, - }); + const handleDeleteDelivery = async (values) => { + setLoading(true); + const result = await deleteDelivery({ + variables: { jobId: job.id } + }); - if (!!!result.errors) { - notification["success"]({message: t("jobs.successes.save")}); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - }; + if (!!!result.errors) { + notification["success"]({ message: t("jobs.successes.save") }); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + }; - return ( - <> - - - - - - ); + return ( + <> + + + + + + ); } diff --git a/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx b/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx index 5e7924fc7..8be01cb02 100644 --- a/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx +++ b/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx @@ -1,165 +1,140 @@ -import {useMutation} from "@apollo/client"; -import {Button, Space, notification} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import dayjs from '../../utils/day'; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries"; -import { - MARK_JOB_AS_EXPORTED, - MARK_JOB_AS_UNINVOICED, - MARK_JOB_FOR_REEXPORT, -} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import { - selectBodyshop, - selectCurrentUser, -} from "../../redux/user/user.selectors"; +import { useMutation } from "@apollo/client"; +import { Button, Space, notification } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import dayjs from "../../utils/day"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { MARK_JOB_AS_EXPORTED, MARK_JOB_AS_UNINVOICED, MARK_JOB_FOR_REEXPORT } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobAdminMarkReexport); +export default connect(mapStateToProps, mapDispatchToProps)(JobAdminMarkReexport); -export function JobAdminMarkReexport({ - insertAuditTrail, - bodyshop, - currentUser, - job, - }) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); +export function JobAdminMarkReexport({ insertAuditTrail, bodyshop, currentUser, job }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); - const [markJobForReexport] = useMutation(MARK_JOB_FOR_REEXPORT); - const [markJobExported] = useMutation(MARK_JOB_AS_EXPORTED); - const [markJobUninvoiced] = useMutation(MARK_JOB_AS_UNINVOICED); + const [markJobForReexport] = useMutation(MARK_JOB_FOR_REEXPORT); + const [markJobExported] = useMutation(MARK_JOB_AS_EXPORTED); + const [markJobUninvoiced] = useMutation(MARK_JOB_AS_UNINVOICED); - const handleMarkForExport = async () => { - setLoading(true); - const result = await markJobForReexport({ - variables: { - jobId: job.id, - default_invoiced: bodyshop.md_ro_statuses.default_invoiced, - }, - }); + const handleMarkForExport = async () => { + setLoading(true); + const result = await markJobForReexport({ + variables: { + jobId: job.id, + default_invoiced: bodyshop.md_ro_statuses.default_invoiced + } + }); - if (!result.errors) { - notification["success"]({message: t("jobs.successes.save")}); - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.admin_jobmarkforreexport(), - type: "admin_jobmarkforreexport",}); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - }; + if (!result.errors) { + notification["success"]({ message: t("jobs.successes.save") }); + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.admin_jobmarkforreexport(), + type: "admin_jobmarkforreexport" + }); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + }; - const handleMarkExported = async () => { - setLoading(true); - const result = await markJobExported({ - variables: { - jobId: job.id, - date_exported: dayjs(), - default_exported: bodyshop.md_ro_statuses.default_exported, - }, - }); + const handleMarkExported = async () => { + setLoading(true); + const result = await markJobExported({ + variables: { + jobId: job.id, + date_exported: dayjs(), + default_exported: bodyshop.md_ro_statuses.default_exported + } + }); - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - jobid: job.id, - successful: true, - message: JSON.stringify([t("general.labels.markedexported")]), - useremail: currentUser.email, - }, - ], - }, - }); + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + jobid: job.id, + successful: true, + message: JSON.stringify([t("general.labels.markedexported")]), + useremail: currentUser.email + } + ] + } + }); - if (!result.errors) { - notification["success"]({message: t("jobs.successes.save")}); - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.admin_jobmarkexported(), - type: "admin_jobmarkexported",}); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - }; + if (!result.errors) { + notification["success"]({ message: t("jobs.successes.save") }); + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.admin_jobmarkexported(), + type: "admin_jobmarkexported" + }); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + }; - const handleUninvoice = async () => { - setLoading(true); - const result = await markJobUninvoiced({ - variables: { - jobId: job.id, - default_delivered: bodyshop.md_ro_statuses.default_delivered, - }, - }); + const handleUninvoice = async () => { + setLoading(true); + const result = await markJobUninvoiced({ + variables: { + jobId: job.id, + default_delivered: bodyshop.md_ro_statuses.default_delivered + } + }); - if (!result.errors) { - notification["success"]({message: t("jobs.successes.save")}); - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.admin_jobuninvoice(), - type: "admin_jobuninvoice",}); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - }; + if (!result.errors) { + notification["success"]({ message: t("jobs.successes.save") }); + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.admin_jobuninvoice(), + type: "admin_jobuninvoice" + }); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + }; - return ( - <> - - - - - - - ); + return ( + <> + + + + + + + ); } diff --git a/client/src/components/jobs-admin-owner-reassociate/jobs-admin-owner-reassociate.component.jsx b/client/src/components/jobs-admin-owner-reassociate/jobs-admin-owner-reassociate.component.jsx index 737753cdb..68e1cbacd 100644 --- a/client/src/components/jobs-admin-owner-reassociate/jobs-admin-owner-reassociate.component.jsx +++ b/client/src/components/jobs-admin-owner-reassociate/jobs-admin-owner-reassociate.component.jsx @@ -1,64 +1,59 @@ -import {useMutation} from "@apollo/client"; -import {Button, Form, notification} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; +import { useMutation } from "@apollo/client"; +import { Button, Form, notification } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; import OwnerSearchSelect from "../owner-search-select/owner-search-select.component"; -export default function JobAdminOwnerReassociate({job}) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const [form] = Form.useForm(); - const [updateJob] = useMutation(UPDATE_JOB); - const handleFinish = async (values) => { - setLoading(true); - const result = await updateJob({ - variables: {jobId: job.id, job: {ownerid: values.ownerid}}, - }); +export default function JobAdminOwnerReassociate({ job }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + const [updateJob] = useMutation(UPDATE_JOB); + const handleFinish = async (values) => { + setLoading(true); + const result = await updateJob({ + variables: { jobId: job.id, job: { ownerid: values.ownerid } } + }); - if (!!!result.errors) { - notification["success"]({message: t("jobs.successes.save")}); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - //Get the owner details, populate it all back into the job. - }; + if (!!!result.errors) { + notification["success"]({ message: t("jobs.successes.save") }); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + //Get the owner details, populate it all back into the job. + }; - useEffect(() => { - //form.resetFields(); - }, [form, job]); + useEffect(() => { + //form.resetFields(); + }, [form, job]); - return ( -
-
{t("jobs.labels.ownerassociation")}
-
- - - - -
{t("jobs.labels.associationwarning")}
- -
- ); + return ( +
+
{t("jobs.labels.ownerassociation")}
+
+ + + + +
{t("jobs.labels.associationwarning")}
+ +
+ ); } diff --git a/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx b/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx index 4290764e8..61d9cbaf6 100644 --- a/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx +++ b/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx @@ -1,65 +1,59 @@ -import {useMutation} from "@apollo/client"; -import { notification, Switch} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {UPDATE_REMOVE_FROM_AR} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; +import { useMutation } from "@apollo/client"; +import { notification, Switch } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { UPDATE_REMOVE_FROM_AR } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; const mapStateToProps = createStructuredSelector({}); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminRemoveAR); -export function JobsAdminRemoveAR({insertAuditTrail, job}) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const [switchValue, setSwitchValue] = useState(job.remove_from_ar); +export function JobsAdminRemoveAR({ insertAuditTrail, job }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [switchValue, setSwitchValue] = useState(job.remove_from_ar); - const [mutationUpdateRemoveFromAR] = useMutation(UPDATE_REMOVE_FROM_AR); + const [mutationUpdateRemoveFromAR] = useMutation(UPDATE_REMOVE_FROM_AR); - const handleChange = async (value) => { - setLoading(true); - const result = await mutationUpdateRemoveFromAR({ - variables: {jobId: job.id, remove_from_ar: value}, - }); + const handleChange = async (value) => { + setLoading(true); + const result = await mutationUpdateRemoveFromAR({ + variables: { jobId: job.id, remove_from_ar: value } + }); - if (!result.errors) { - notification["success"]({message: t("jobs.successes.save")}); - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.admin_job_remove_from_ar(value), - type: "admin_job_remove_from_ar",}); - setSwitchValue(value); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - }; + if (!result.errors) { + notification["success"]({ message: t("jobs.successes.save") }); + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.admin_job_remove_from_ar(value), + type: "admin_job_remove_from_ar" + }); + setSwitchValue(value); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + }; - return ( - <> -
-
- {t("jobs.labels.remove_from_ar")}: -
-
- -
-
- - ); + return ( + <> +
+
{t("jobs.labels.remove_from_ar")}:
+
+ +
+
+ + ); } 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 2ffd82763..ea6e8f8ab 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 @@ -1,69 +1,63 @@ -import {useMutation} from "@apollo/client"; -import {Button, notification} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {UNVOID_JOB} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { useMutation } from "@apollo/client"; +import { Button, notification } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { UNVOID_JOB } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminUnvoid); -export function JobsAdminUnvoid({ - insertAuditTrail, - bodyshop, - job, - currentUser, - }) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const [mutationUnvoidJob] = useMutation(UNVOID_JOB); +export function JobsAdminUnvoid({ insertAuditTrail, bodyshop, job, currentUser }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [mutationUnvoidJob] = useMutation(UNVOID_JOB); - const handleUpdate = async (values) => { - setLoading(true); - const result = await mutationUnvoidJob({ - variables: { - jobId: job.id, - default_imported: bodyshop.md_ro_statuses.default_imported, - currentUserEmail: currentUser.email, - text: t("jobs.labels.unvoidnote"), - }, - }); + const handleUpdate = async (values) => { + setLoading(true); + const result = await mutationUnvoidJob({ + variables: { + jobId: job.id, + default_imported: bodyshop.md_ro_statuses.default_imported, + currentUserEmail: currentUser.email, + text: t("jobs.labels.unvoidnote") + } + }); - if (!result.errors) { - notification["success"]({message: t("jobs.successes.save")}); + if (!result.errors) { + notification["success"]({ message: t("jobs.successes.save") }); - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.admin_jobunvoid(), - type: "admin_jobunvoid", - }); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - //Get the owner details, populate it all back into the job. - }; + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.admin_jobunvoid(), + type: "admin_jobunvoid" + }); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + //Get the owner details, populate it all back into the job. + }; - return ( - <> - - - ); + return ( + <> + + + ); } diff --git a/client/src/components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component.jsx b/client/src/components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component.jsx index 60e9c268e..ce2d317e4 100644 --- a/client/src/components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component.jsx +++ b/client/src/components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component.jsx @@ -1,64 +1,59 @@ -import {useMutation} from "@apollo/client"; -import {Button, Form, notification} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; +import { useMutation } from "@apollo/client"; +import { Button, Form, notification } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; import VehicleSearchSelect from "../vehicle-search-select/vehicle-search-select.component"; -export default function JobAdminOwnerReassociate({job}) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const [form] = Form.useForm(); - const [updateJob] = useMutation(UPDATE_JOB); - const handleFinish = async (values) => { - setLoading(true); - const result = await updateJob({ - variables: {jobId: job.id, job: {vehicleid: values.vehicleid}}, - }); +export default function JobAdminOwnerReassociate({ job }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + const [updateJob] = useMutation(UPDATE_JOB); + const handleFinish = async (values) => { + setLoading(true); + const result = await updateJob({ + variables: { jobId: job.id, job: { vehicleid: values.vehicleid } } + }); - if (!!!result.errors) { - notification["success"]({message: t("jobs.successes.save")}); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - //Get the owner details, populate it all back into the job. - }; + if (!!!result.errors) { + notification["success"]({ message: t("jobs.successes.save") }); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + //Get the owner details, populate it all back into the job. + }; - useEffect(() => { - //form.resetFields(); - }, [form, job]); + useEffect(() => { + //form.resetFields(); + }, [form, job]); - return ( -
-
{t("jobs.labels.vehicleassociation")}
-
- - - - -
{t("jobs.labels.associationwarning")}
- -
- ); + return ( +
+
{t("jobs.labels.vehicleassociation")}
+
+ + + + +
{t("jobs.labels.associationwarning")}
+ +
+ ); } diff --git a/client/src/components/jobs-available-scan/jobs-available-scan.component.jsx b/client/src/components/jobs-available-scan/jobs-available-scan.component.jsx index a0dc4491b..111714fad 100644 --- a/client/src/components/jobs-available-scan/jobs-available-scan.component.jsx +++ b/client/src/components/jobs-available-scan/jobs-available-scan.component.jsx @@ -1,159 +1,150 @@ -import {DownloadOutlined, SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Input, notification, Space, Table} from "antd"; +import { DownloadOutlined, SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Input, notification, Space, Table } from "antd"; import axios from "axios"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectPartnerVersion} from "../../redux/application/application.selectors"; -import {alphaSort} from "../../utils/sorters"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectPartnerVersion } from "../../redux/application/application.selectors"; +import { alphaSort } from "../../utils/sorters"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - partnerVersion: selectPartnerVersion, + //currentUser: selectCurrentUser + partnerVersion: selectPartnerVersion }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(JobsAvailableScan); -export function JobsAvailableScan({partnerVersion, refetch}) { - const [estimatesOnDisk, setEstimatesOnDisk] = useState([]); - const [loading, setLoading] = useState(false); - const [searchText, setSearchText] = useState(""); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, +export function JobsAvailableScan({ partnerVersion, refetch }) { + const [estimatesOnDisk, setEstimatesOnDisk] = useState([]); + const [loading, setLoading] = useState(false); + const [searchText, setSearchText] = useState(""); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); + const { t } = useTranslation(); + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + const handleImport = async (filepath) => { + setLoading(true); + const response = await axios.post("http://localhost:1337/import/", { + filepath }); - const {t} = useTranslation(); + if (response.data.success) { + //Came through + if (refetch) refetch(); + } else { + notification["error"]({ + message: t("jobs.errors.scanimport", { message: response.data.error }) + }); + } + setLoading(false); + }; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const columns = [ + { + title: t("jobs.fields.cieca_id"), + dataIndex: "cieca_id", + key: "cieca_id", + sorter: (a, b) => alphaSort(a, b), + sortOrder: state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + sorter: (a, b) => alphaSort(a.owner, b.owner), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + sorter: (a, b) => alphaSort(a.vehicle, b.vehicle), + sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), + sortOrder: state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order + }, + { + title: t("jobs.fields.ins_co_nm"), + dataIndex: "ins_co_nm", + key: "ins_co_nm", + sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), + sortOrder: state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order + }, + { + title: t("general.labels.actions"), + key: "actions", + render: (text, record) => ( + + ) + } + ]; - const handleImport = async (filepath) => { - setLoading(true); - const response = await axios.post("http://localhost:1337/import/", { - filepath, - }); - if (response.data.success) { - //Came through - if (refetch) refetch(); - } else { - notification["error"]({ - message: t("jobs.errors.scanimport", {message: response.data.error}), - }); - } - setLoading(false); - }; + const scanEstimates = async () => { + setLoading(true); + const estimatesOnDiskResponse = await axios.post("http://localhost:1337/scan/"); + setEstimatesOnDisk(estimatesOnDiskResponse.data); + setLoading(false); + }; - const columns = [ - { - title: t("jobs.fields.cieca_id"), - dataIndex: "cieca_id", - key: "cieca_id", - sorter: (a, b) => alphaSort(a, b), - sortOrder: - state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - ellipsis: true, - sorter: (a, b) => alphaSort(a.owner, b.owner), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - sorter: (a, b) => alphaSort(a.vehicle, b.vehicle), - sortOrder: - state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.ins_co_nm"), - dataIndex: "ins_co_nm", - key: "ins_co_nm", - sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), - sortOrder: - state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order, - }, - { - title: t("general.labels.actions"), - key: "actions", - render: (text, record) => ( - - ), - }, - ]; + const data = estimatesOnDisk + ? searchText + ? estimatesOnDisk.filter( + (j) => + (j.owner || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.vehicle || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) + ) + : estimatesOnDisk + : []; - const scanEstimates = async () => { - setLoading(true); - const estimatesOnDiskResponse = await axios.post( - "http://localhost:1337/scan/" - ); - setEstimatesOnDisk(estimatesOnDiskResponse.data); - setLoading(false); - }; + return ( + + - const data = estimatesOnDisk - ? searchText - ? estimatesOnDisk.filter( - (j) => - (j.owner || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.vehicle || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) - ) - : estimatesOnDisk - : []; - - return ( - - - - { - setSearchText(e.currentTarget.value); - }} - /> - - } - > -
- - ); + { + setSearchText(e.currentTarget.value); + }} + /> + + } + > +
+ + ); } diff --git a/client/src/components/jobs-available-table/jobs-available-supplement.estlines.util.js b/client/src/components/jobs-available-table/jobs-available-supplement.estlines.util.js index 8d7c6d58d..f87901e1c 100644 --- a/client/src/components/jobs-available-table/jobs-available-supplement.estlines.util.js +++ b/client/src/components/jobs-available-table/jobs-available-supplement.estlines.util.js @@ -1,78 +1,76 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; import _ from "lodash"; -import {GET_ALL_JOBLINES_BY_PK} from "../../graphql/jobs-lines.queries"; +import { GET_ALL_JOBLINES_BY_PK } from "../../graphql/jobs-lines.queries"; export const GetSupplementDelta = async (client, jobId, newLines) => { - const { - data: {joblines: existingLinesFromDb}, - } = await client.query({ - query: GET_ALL_JOBLINES_BY_PK, - variables: {id: jobId}, - }); + const { + data: { joblines: existingLinesFromDb } + } = await client.query({ + query: GET_ALL_JOBLINES_BY_PK, + variables: { id: jobId } + }); - const existingLines = _.cloneDeep(existingLinesFromDb); - const linesToInsert = []; - const linesToUpdate = []; + const existingLines = _.cloneDeep(existingLinesFromDb); + const linesToInsert = []; + const linesToUpdate = []; - newLines.forEach((newLine) => { - const matchingIndex = existingLines.findIndex( - (eL) => eL.unq_seq === newLine.unq_seq - ); + newLines.forEach((newLine) => { + const matchingIndex = existingLines.findIndex((eL) => eL.unq_seq === newLine.unq_seq); - //Should do a check to make sure there is only 1 matching unq sequence number. + //Should do a check to make sure there is only 1 matching unq sequence number. - if (matchingIndex >= 0) { - //Found a relevant matching line. Add it to lines to update. - linesToUpdate.push({ - id: existingLines[matchingIndex].id, - newData: {...newLine, removed: false, act_price_before_ppc: null}, - }); + if (matchingIndex >= 0) { + //Found a relevant matching line. Add it to lines to update. + linesToUpdate.push({ + id: existingLines[matchingIndex].id, + newData: { ...newLine, removed: false, act_price_before_ppc: null } + }); - //Splice out item we found for performance. - existingLines.splice(matchingIndex, 1); - } else { - //Didn't find a match. Must be a new line. - linesToInsert.push(newLine); - } - }); - - //Wahtever is left in the existing lines, are lines that should be removed. - const insertQueries = linesToInsert.reduce((acc, value, idx) => { - return acc + generateInsertQuery(value, idx, jobId); - }, ""); - - const updateQueries = linesToUpdate.reduce((acc, value, idx) => { - return acc + generateUpdateQuery(value, idx); - }, ""); - - const removeQueries = existingLines - .filter((l) => !l.manual_line) - .reduce((acc, value, idx) => { - return acc + generateRemoveQuery(value, idx); - }, ""); - //console.log(insertQueries, updateQueries, removeQueries); - - if ((insertQueries + updateQueries + removeQueries).trim() === "") { - return new Promise((resolve, reject) => { - resolve(null); - }); + //Splice out item we found for performance. + existingLines.splice(matchingIndex, 1); + } else { + //Didn't find a match. Must be a new line. + linesToInsert.push(newLine); } + }); + //Wahtever is left in the existing lines, are lines that should be removed. + const insertQueries = linesToInsert.reduce((acc, value, idx) => { + return acc + generateInsertQuery(value, idx, jobId); + }, ""); + + const updateQueries = linesToUpdate.reduce((acc, value, idx) => { + return acc + generateUpdateQuery(value, idx); + }, ""); + + const removeQueries = existingLines + .filter((l) => !l.manual_line) + .reduce((acc, value, idx) => { + return acc + generateRemoveQuery(value, idx); + }, ""); + //console.log(insertQueries, updateQueries, removeQueries); + + if ((insertQueries + updateQueries + removeQueries).trim() === "") { return new Promise((resolve, reject) => { - resolve(gql` + resolve(null); + }); + } + + return new Promise((resolve, reject) => { + resolve(gql` mutation SUPPLEMENT_EST_LINES{ ${insertQueries + updateQueries + removeQueries} } `); - }); + }); }; const generateInsertQuery = (lineToInsert, index, jobId) => { - return ` + return ` insert_joblines${index}: insert_joblines(objects: ${JSON.stringify({ - ...lineToInsert, - jobid: jobId, - }).replace(/"(\w+)"\s*:/g, "$1:")}) { + ...lineToInsert, + jobid: jobId + }).replace(/"(\w+)"\s*:/g, "$1:")}) { returning { id } @@ -80,13 +78,10 @@ const generateInsertQuery = (lineToInsert, index, jobId) => { }; const generateUpdateQuery = (lineToUpdate, index) => { - return ` + return ` update_joblines${index}: update_joblines(where: { id: { _eq: "${ - lineToUpdate.id - }" } }, _set: ${JSON.stringify(lineToUpdate.newData).replace( - /"(\w+)"\s*:/g, - "$1:" - )}) { + lineToUpdate.id + }" } }, _set: ${JSON.stringify(lineToUpdate.newData).replace(/"(\w+)"\s*:/g, "$1:")}) { returning { id } @@ -94,7 +89,7 @@ const generateUpdateQuery = (lineToUpdate, index) => { }; const generateRemoveQuery = (lineToRemove, index) => { - return ` + return ` update_joblines_r${index}: update_joblines(where: {id: {_eq: "${lineToRemove.id}"}}, _set: {removed: true}) { returning{ id diff --git a/client/src/components/jobs-available-table/jobs-available-supplement.headerfields.js b/client/src/components/jobs-available-table/jobs-available-supplement.headerfields.js index 348779424..2ef855682 100644 --- a/client/src/components/jobs-available-table/jobs-available-supplement.headerfields.js +++ b/client/src/components/jobs-available-table/jobs-available-supplement.headerfields.js @@ -1,234 +1,234 @@ const headerFields = [ - //AD1 - "ins_co_id", - "ins_co_nm", - "ins_addr1", - "ins_addr2", - "ins_city", - "ins_st", - "ins_zip", - "ins_ctry", - "ins_ea", - "policy_no", - "ded_amt", - "ded_status", - "asgn_no", - "asgn_date", - "asgn_type", - "clm_no", - "clm_ofc_id", - "clm_ofc_nm", - "clm_addr1", - "clm_addr2", - "clm_city", - "clm_st", - "clm_zip", - "clm_ctry", - "clm_ph1", - "clm_ph1x", - "clm_ph2", - "clm_ph2x", - "clm_fax", - "clm_faxx", - "clm_ct_ln", - "clm_ct_fn", - "clm_title", - "clm_ct_ph", - "clm_ct_phx", - "clm_ea", - "payee_nms", - "pay_type", - "pay_date", - "pay_chknm", - "pay_amt", - "agt_co_id", - "agt_co_nm", - "agt_addr1", - "agt_addr2", - "agt_city", - "agt_st", - "agt_zip", - "agt_ctry", - "agt_ph1", - "agt_ph1x", - "agt_ph2", - "agt_ph2x", - "agt_fax", - "agt_faxx", - "agt_ct_ln", - "agt_ct_fn", - "agt_ct_ph", - "agt_ct_phx", - "agt_ea", - "agt_lic_no", - "loss_date", - "loss_type", - "loss_desc", - "theft_ind", - "cat_no", - "tlos_ind", - "cust_pr", - "insd_ln", - "insd_fn", - "insd_title", - "insd_co_nm", - "insd_addr1", - "insd_addr2", - "insd_city", - "insd_st", - "insd_zip", - "insd_ctry", - "insd_ph1", - "insd_ph1x", - "insd_ph2", - "insd_ph2x", - "insd_fax", - "insd_faxx", - "insd_ea", - "ownr_ln", - "ownr_fn", - "ownr_title", - "ownr_co_nm", - "ownr_addr1", - "ownr_addr2", - "ownr_city", - "ownr_st", - "ownr_zip", - "ownr_ctry", - "ownr_ph1", - "ownr_ph1x", - "ownr_ph2", - "ownr_ph2x", - "ownr_fax", - "ownr_faxx", - "ownr_ea", - "ins_ph1", - "ins_ph1x", - "ins_ph2", - "ins_ph2x", - "ins_fax", - "ins_faxx", - "ins_ct_ln", - "ins_ct_fn", - "ins_title", - "ins_ct_ph", - "ins_ct_phx", - "loss_cat", - //AD2 - "clmt_ln", - "clmt_fn", - "clmt_title", - "clmt_co_nm", - "clmt_addr1", - "clmt_addr2", - "clmt_city", - "clmt_st", - "clmt_zip", - "clmt_ctry", - "clmt_ph1", - "clmt_ph1x", - "clmt_ph2", - "clmt_ph2x", - "clmt_fax", - "clmt_faxx", - "clmt_ea", - "est_co_id", - "est_co_nm", - "est_addr1", - "est_addr2", - "est_city", - "est_st", - "est_zip", - "est_ctry", - "est_ph1", - "est_ph1x", - "est_ph2", - "est_ph2x", - "est_fax", - "est_faxx", - "est_ct_ln", - "est_ct_fn", - "est_ea", - "est_lic_no", - "est_fileno", - "insp_ct_ln", - "insp_ct_fn", - "insp_addr1", - "insp_addr2", - "insp_city", - "insp_st", - "insp_zip", - "insp_ctry", - "insp_ph1", - "insp_ph1x", - "insp_ph2", - "insp_ph2x", - "insp_fax", - "insp_faxx", - "insp_ea", - "insp_code", - "insp_desc", - "insp_date", - "insp_time", - "rf_co_id", - "rf_co_nm", - "rf_addr1", - "rf_addr2", - "rf_city", - "rf_st", - "rf_zip", - "rf_ctry", - "rf_ph1", - "rf_ph1x", - "rf_ph2", - "rf_ph2x", - "rf_fax", - "rf_faxx", - "rf_ct_ln", - "rf_ct_fn", - "rf_ea", - "rf_tax_id", - "rf_lic_no", - "rf_bar_no", - "ro_in_date", - "ro_in_time", - "tar_date", - "tar_time", - "ro_cmpdate", - "ro_cmptime", - "date_out", - "time_out", - "rf_estimtr", - "mktg_type", - "mktg_src", - "loc_nm", - "loc_addr1", - "loc_addr2", - "loc_city", - "loc_st", - "loc_zip", - "loc_ctry", - "loc_ph1", - "loc_ph1x", - "loc_ph2", - "loc_ph2x", - "loc_fax", - "loc_faxx", - "loc_ct_ln", - "loc_ct_fn", - "loc_title", - "loc_ph", - "loc_phx", - "loc_ea", - //VEH - "plate_no", - "plate_st", - "v_vin", - "v_model_yr", - "v_make_desc", - "v_model_desc", - "v_options", - "v_color", + //AD1 + "ins_co_id", + "ins_co_nm", + "ins_addr1", + "ins_addr2", + "ins_city", + "ins_st", + "ins_zip", + "ins_ctry", + "ins_ea", + "policy_no", + "ded_amt", + "ded_status", + "asgn_no", + "asgn_date", + "asgn_type", + "clm_no", + "clm_ofc_id", + "clm_ofc_nm", + "clm_addr1", + "clm_addr2", + "clm_city", + "clm_st", + "clm_zip", + "clm_ctry", + "clm_ph1", + "clm_ph1x", + "clm_ph2", + "clm_ph2x", + "clm_fax", + "clm_faxx", + "clm_ct_ln", + "clm_ct_fn", + "clm_title", + "clm_ct_ph", + "clm_ct_phx", + "clm_ea", + "payee_nms", + "pay_type", + "pay_date", + "pay_chknm", + "pay_amt", + "agt_co_id", + "agt_co_nm", + "agt_addr1", + "agt_addr2", + "agt_city", + "agt_st", + "agt_zip", + "agt_ctry", + "agt_ph1", + "agt_ph1x", + "agt_ph2", + "agt_ph2x", + "agt_fax", + "agt_faxx", + "agt_ct_ln", + "agt_ct_fn", + "agt_ct_ph", + "agt_ct_phx", + "agt_ea", + "agt_lic_no", + "loss_date", + "loss_type", + "loss_desc", + "theft_ind", + "cat_no", + "tlos_ind", + "cust_pr", + "insd_ln", + "insd_fn", + "insd_title", + "insd_co_nm", + "insd_addr1", + "insd_addr2", + "insd_city", + "insd_st", + "insd_zip", + "insd_ctry", + "insd_ph1", + "insd_ph1x", + "insd_ph2", + "insd_ph2x", + "insd_fax", + "insd_faxx", + "insd_ea", + "ownr_ln", + "ownr_fn", + "ownr_title", + "ownr_co_nm", + "ownr_addr1", + "ownr_addr2", + "ownr_city", + "ownr_st", + "ownr_zip", + "ownr_ctry", + "ownr_ph1", + "ownr_ph1x", + "ownr_ph2", + "ownr_ph2x", + "ownr_fax", + "ownr_faxx", + "ownr_ea", + "ins_ph1", + "ins_ph1x", + "ins_ph2", + "ins_ph2x", + "ins_fax", + "ins_faxx", + "ins_ct_ln", + "ins_ct_fn", + "ins_title", + "ins_ct_ph", + "ins_ct_phx", + "loss_cat", + //AD2 + "clmt_ln", + "clmt_fn", + "clmt_title", + "clmt_co_nm", + "clmt_addr1", + "clmt_addr2", + "clmt_city", + "clmt_st", + "clmt_zip", + "clmt_ctry", + "clmt_ph1", + "clmt_ph1x", + "clmt_ph2", + "clmt_ph2x", + "clmt_fax", + "clmt_faxx", + "clmt_ea", + "est_co_id", + "est_co_nm", + "est_addr1", + "est_addr2", + "est_city", + "est_st", + "est_zip", + "est_ctry", + "est_ph1", + "est_ph1x", + "est_ph2", + "est_ph2x", + "est_fax", + "est_faxx", + "est_ct_ln", + "est_ct_fn", + "est_ea", + "est_lic_no", + "est_fileno", + "insp_ct_ln", + "insp_ct_fn", + "insp_addr1", + "insp_addr2", + "insp_city", + "insp_st", + "insp_zip", + "insp_ctry", + "insp_ph1", + "insp_ph1x", + "insp_ph2", + "insp_ph2x", + "insp_fax", + "insp_faxx", + "insp_ea", + "insp_code", + "insp_desc", + "insp_date", + "insp_time", + "rf_co_id", + "rf_co_nm", + "rf_addr1", + "rf_addr2", + "rf_city", + "rf_st", + "rf_zip", + "rf_ctry", + "rf_ph1", + "rf_ph1x", + "rf_ph2", + "rf_ph2x", + "rf_fax", + "rf_faxx", + "rf_ct_ln", + "rf_ct_fn", + "rf_ea", + "rf_tax_id", + "rf_lic_no", + "rf_bar_no", + "ro_in_date", + "ro_in_time", + "tar_date", + "tar_time", + "ro_cmpdate", + "ro_cmptime", + "date_out", + "time_out", + "rf_estimtr", + "mktg_type", + "mktg_src", + "loc_nm", + "loc_addr1", + "loc_addr2", + "loc_city", + "loc_st", + "loc_zip", + "loc_ctry", + "loc_ph1", + "loc_ph1x", + "loc_ph2", + "loc_ph2x", + "loc_fax", + "loc_faxx", + "loc_ct_ln", + "loc_ct_fn", + "loc_title", + "loc_ph", + "loc_phx", + "loc_ea", + //VEH + "plate_no", + "plate_st", + "v_vin", + "v_model_yr", + "v_make_desc", + "v_model_desc", + "v_options", + "v_color" ]; export default headerFields; diff --git a/client/src/components/jobs-available-table/jobs-available-table.component.jsx b/client/src/components/jobs-available-table/jobs-available-table.component.jsx index 475979e7f..3d397597e 100644 --- a/client/src/components/jobs-available-table/jobs-available-table.component.jsx +++ b/client/src/components/jobs-available-table/jobs-available-table.component.jsx @@ -1,254 +1,210 @@ -import {DeleteFilled, DownloadOutlined, PlusCircleFilled, SyncOutlined,} from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {Alert, Button, Card, Input, notification, Space, Table} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {DELETE_ALL_AVAILABLE_JOBS, DELETE_AVAILABLE_JOB,} from "../../graphql/available-jobs.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { DeleteFilled, DownloadOutlined, PlusCircleFilled, SyncOutlined } from "@ant-design/icons"; +import { useMutation } from "@apollo/client"; +import { Alert, Button, Card, Input, notification, Space, Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { DELETE_ALL_AVAILABLE_JOBS, DELETE_AVAILABLE_JOB } from "../../graphql/available-jobs.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {TimeAgoFormatter} from "../../utils/DateFormatter"; -import {alphaSort} from "../../utils/sorters"; +import { TimeAgoFormatter } from "../../utils/DateFormatter"; +import { alphaSort } from "../../utils/sorters"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsAvailableComponent); +export default connect(mapStateToProps, mapDispatchToProps)(JobsAvailableComponent); -export function JobsAvailableComponent({ - bodyshop, - loading, - data, - refetch, - addJobAsNew, - addJobAsSupp, - }) { - const [deleteAllAvailableJobs] = useMutation(DELETE_ALL_AVAILABLE_JOBS); - const [deleteJob] = useMutation(DELETE_AVAILABLE_JOB); - const {t} = useTranslation(); - const [searchText, setSearchText] = useState(""); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); +export function JobsAvailableComponent({ bodyshop, loading, data, refetch, addJobAsNew, addJobAsSupp }) { + const [deleteAllAvailableJobs] = useMutation(DELETE_ALL_AVAILABLE_JOBS); + const [deleteJob] = useMutation(DELETE_AVAILABLE_JOB); + const { t } = useTranslation(); + const [searchText, setSearchText] = useState(""); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - const columns = [ - { - title: t("jobs.fields.cieca_id"), - dataIndex: "cieca_id", - key: "cieca_id", - sorter: (a, b) => alphaSort(a.cieca_id, b.cieca_id), - sortOrder: - state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.ro_number"), - dataIndex: "job_id", - key: "job_id", - //width: "8%", - // onFilter: (value, record) => record.ro_number.includes(value), - // filteredValue: state.filteredInfo.text || null, - sorter: (a, b) => - alphaSort(a.job && a.job.ro_number, b.job && b.job.ro_number), - sortOrder: - state.sortedInfo.columnKey === "job_id" && state.sortedInfo.order, - render: (text, record) => - record.job ? ( - - {(record.job && record.job.ro_number) || t("general.labels.na")} - - ) : ( -
- {(record.job && record.job.ro_number) || t("general.labels.na")} -
- ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "ownr_name", - key: "ownr_name", - ellipsis: true, - sorter: (a, b) => alphaSort(a.ownr_name, b.ownr_name), - sortOrder: - state.sortedInfo.columnKey === "ownr_name" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle_info", - key: "vehicle_info", - sorter: (a, b) => alphaSort(a.vehicle_info, b.vehicle_info), - sortOrder: - state.sortedInfo.columnKey === "vehicle_info" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.ins_co_nm"), - dataIndex: "ins_co_nm", - key: "ins_co_nm", - sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), - sortOrder: - state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.clm_total"), - dataIndex: "clm_amt", - key: "clm_amt", - sorter: (a, b) => a.clm_amt - b.clm_amt, - sortOrder: - state.sortedInfo.columnKey === "clm_amt" && state.sortedInfo.order, - render: (text, record) => ( - {record.clm_amt} - ), - }, - { - title: t("jobs.fields.uploaded_by"), - dataIndex: "uploaded_by", - key: "uploaded_by", - sorter: (a, b) => alphaSort(a.uploaded_by, b.uploaded_by), - sortOrder: - state.sortedInfo.columnKey === "uploaded_by" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.updated_at"), - dataIndex: "updated_at", - key: "updated_at", - sorter: (a, b) => new Date(a.updated_at) - new Date(b.updated_at), - sortOrder: - state.sortedInfo.columnKey === "updated_at" && state.sortedInfo.order, - render: (text, record) => ( - {record.updated_at} - ), - }, - { - title: t("general.labels.actions"), - key: "actions", - render: (text, record) => { - const isClosed = - record.job && - (record.job.status === bodyshop.md_ro_statuses.default_exported || - record.job.status === bodyshop.md_ro_statuses.default_invoiced); - return ( - - - {!isClosed && ( - <> - - - - )} - {isClosed && ( - - )} - - ); - }, - }, - ]; + const columns = [ + { + title: t("jobs.fields.cieca_id"), + dataIndex: "cieca_id", + key: "cieca_id", + sorter: (a, b) => alphaSort(a.cieca_id, b.cieca_id), + sortOrder: state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order + }, + { + title: t("jobs.fields.ro_number"), + dataIndex: "job_id", + key: "job_id", + //width: "8%", + // onFilter: (value, record) => record.ro_number.includes(value), + // filteredValue: state.filteredInfo.text || null, + sorter: (a, b) => alphaSort(a.job && a.job.ro_number, b.job && b.job.ro_number), + sortOrder: state.sortedInfo.columnKey === "job_id" && state.sortedInfo.order, + render: (text, record) => + record.job ? ( + + {(record.job && record.job.ro_number) || t("general.labels.na")} + + ) : ( +
{(record.job && record.job.ro_number) || t("general.labels.na")}
+ ) + }, + { + title: t("jobs.fields.owner"), + dataIndex: "ownr_name", + key: "ownr_name", + ellipsis: true, + sorter: (a, b) => alphaSort(a.ownr_name, b.ownr_name), + sortOrder: state.sortedInfo.columnKey === "ownr_name" && state.sortedInfo.order + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle_info", + key: "vehicle_info", + sorter: (a, b) => alphaSort(a.vehicle_info, b.vehicle_info), + sortOrder: state.sortedInfo.columnKey === "vehicle_info" && state.sortedInfo.order + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), + sortOrder: state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order + }, + { + title: t("jobs.fields.ins_co_nm"), + dataIndex: "ins_co_nm", + key: "ins_co_nm", + sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), + sortOrder: state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order + }, + { + title: t("jobs.fields.clm_total"), + dataIndex: "clm_amt", + key: "clm_amt", + sorter: (a, b) => a.clm_amt - b.clm_amt, + sortOrder: state.sortedInfo.columnKey === "clm_amt" && state.sortedInfo.order, + render: (text, record) => {record.clm_amt} + }, + { + title: t("jobs.fields.uploaded_by"), + dataIndex: "uploaded_by", + key: "uploaded_by", + sorter: (a, b) => alphaSort(a.uploaded_by, b.uploaded_by), + sortOrder: state.sortedInfo.columnKey === "uploaded_by" && state.sortedInfo.order + }, + { + title: t("jobs.fields.updated_at"), + dataIndex: "updated_at", + key: "updated_at", + sorter: (a, b) => new Date(a.updated_at) - new Date(b.updated_at), + sortOrder: state.sortedInfo.columnKey === "updated_at" && state.sortedInfo.order, + render: (text, record) => {record.updated_at} + }, + { + title: t("general.labels.actions"), + key: "actions", + render: (text, record) => { + const isClosed = + record.job && + (record.job.status === bodyshop.md_ro_statuses.default_exported || + record.job.status === bodyshop.md_ro_statuses.default_invoiced); + return ( + + + {!isClosed && ( + <> + + + + )} + {isClosed && } + + ); + } + } + ]; - const availableJobs = data - ? searchText - ? data.available_jobs.filter( - (j) => - (j.ownr_name || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.vehicle_info || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) - ) - : data.available_jobs - : []; + const availableJobs = data + ? searchText + ? data.available_jobs.filter( + (j) => + (j.ownr_name || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.vehicle_info || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) + ) + : data.available_jobs + : []; - return ( - - - + return ( + + + - { - setSearchText(e.currentTarget.value); - }} - /> - - } - > -
- - ); + { + setSearchText(e.currentTarget.value); + }} + /> + + } + > +
+ + ); } 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 a2e37067e..53792a9ed 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,26 +1,26 @@ -import {gql, useApolloClient, useLazyQuery, useMutation, useQuery,} from "@apollo/client"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; -import {Col, Row, notification} from "antd"; +import { gql, useApolloClient, useLazyQuery, useMutation, useQuery } from "@apollo/client"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { Col, Row, notification } from "antd"; import Axios from "axios"; import _ from "lodash"; import dayjs from "../../utils/day"; import queryString from "query-string"; -import React, {useCallback, useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import React, { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import { - DELETE_AVAILABLE_JOB, - QUERY_AVAILABLE_JOBS, - QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK, + DELETE_AVAILABLE_JOB, + QUERY_AVAILABLE_JOBS, + QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK } from "../../graphql/available-jobs.queries"; -import {INSERT_NEW_JOB, UPDATE_JOB} from "../../graphql/jobs.queries"; -import {INSERT_NEW_NOTE} from "../../graphql/notes.queries"; -import {SEARCH_VEHICLE_BY_VIN} from "../../graphql/vehicles.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries"; +import { INSERT_NEW_NOTE } from "../../graphql/notes.queries"; +import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import confirmDialog from "../../utils/asyncConfirm"; import CriticalPartsScan from "../../utils/criticalPartsScan"; @@ -29,300 +29,311 @@ import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.compon import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.container"; -import {GetSupplementDelta} from "./jobs-available-supplement.estlines.util"; +import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util"; import HeaderFields from "./jobs-available-supplement.headerfields"; import JobsAvailableTableComponent from "./jobs-available-table.component"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({ jobid, operation, type }) => - dispatch(insertAuditTrail({ jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,}) { +export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail }) { + const { + treatments: { CriticalPartsScanning } + } = useSplitTreatments({ + attributes: {}, + names: ["CriticalPartsScanning"], + splitKey: bodyshop.imexshopid + }); - const {treatments: {CriticalPartsScanning}} = useSplitTreatments({ - attributes: {}, - names: ["CriticalPartsScanning"], - splitKey: bodyshop.imexshopid, + const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const { clm_no, availableJobId } = queryString.parse(useLocation().search); + const history = useNavigate(); + const { t } = useTranslation(); + + const [ownerModalVisible, setOwnerModalVisible] = useState(false); + const [jobModalVisible, setJobModalVisible] = useState(false); + + const [selectedJob, setSelectedJob] = useState(null); + const [selectedOwner, setSelectedOwner] = useState(null); + const [partsQueueToggle, setPartsQueueToggle] = useState(bodyshop.md_functionality_toggles.parts_queue_toggle); + const [updateSchComp, setSchComp] = useState({ + actual_in: dayjs(), + checked: false, + scheduled_completion: dayjs(), + automatic: false + }); + + const [insertLoading, setInsertLoading] = useState(false); + + const [insertNote] = useMutation(INSERT_NEW_NOTE); + const [deleteJob] = useMutation(DELETE_AVAILABLE_JOB); + const [updateJob] = useMutation(UPDATE_JOB); + + const [insertNewJob] = useMutation(INSERT_NEW_JOB); + const client = useApolloClient(); + + const estDataLazyLoad = useLazyQuery(QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK); + const [loadEstData, estDataRaw] = estDataLazyLoad; + + const importOptionsState = useState({ overrideHeaders: false }); + const importOptions = importOptionsState[0]; + const modalSearchState = useState(""); + + //Import Scenario + const onOwnerFindModalOk = async (lazyData) => { + logImEXEvent("job_import_new"); + + setOwnerModalVisible(false); + + setInsertLoading(true); + const estData = replaceEmpty(lazyData?.available_jobs_by_pk || estDataRaw.data.available_jobs_by_pk); + + if (!(estData && estData.est_data)) { + //We don't have the right data. Error! + setInsertLoading(false); + notification["error"]({ + message: t("jobs.errors.creating", { error: "No job data present." }) + }); + return; + } + // if (process.env.VITE_APP_COUNTRY === "USA") { + //Massage the CCC file set to remove duplicate UNQ_SEQ. + InstanceRenderManager({ + executeFunction: true, + rome: ResolveCCCLineIssues, + args: [estData.est_data, bodyshop], + promanager: ResolveCCCLineIssues }); - const {loading, error, data, refetch} = useQuery(QUERY_AVAILABLE_JOBS, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); - const {clm_no, availableJobId} = queryString.parse(useLocation().search); - const history = useNavigate(); - const {t} = useTranslation(); - - const [ownerModalVisible, setOwnerModalVisible] = useState(false); - const [jobModalVisible, setJobModalVisible] = useState(false); - - const [selectedJob, setSelectedJob] = useState(null); - const [selectedOwner, setSelectedOwner] = useState(null); - const [partsQueueToggle, setPartsQueueToggle] = useState( - bodyshop.md_functionality_toggles.parts_queue_toggle - ); - const [updateSchComp, setSchComp] = useState({ - actual_in: dayjs(), - checked: false, - scheduled_completion: dayjs(), - automatic: false, + // } else { + //IO-539 Check for Parts Rate on PAL for SGI use case. + //TODO:AIO Check that the async function is actually waiting before moving on. + await InstanceRenderManager({ + executeFunction: true, + imex: CheckTaxRates, + rome: CheckTaxRatesUSA, + promanager: CheckTaxRatesUSA, + args: [estData.est_data, bodyshop] }); - const [insertLoading, setInsertLoading] = useState(false); + // } + // const newTotals = ( + // await Axios.post("/job/totals", { + // job: { + // ...estData.est_data, + // joblines: estData.est_data.joblines.data, + // }, + // }) + // ).data; - const [insertNote] = useMutation(INSERT_NEW_NOTE); - const [deleteJob] = useMutation(DELETE_AVAILABLE_JOB); - const [updateJob] = useMutation(UPDATE_JOB); - - const [insertNewJob] = useMutation(INSERT_NEW_JOB); - const client = useApolloClient(); - - const estDataLazyLoad = useLazyQuery(QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK); - const [loadEstData, estDataRaw] = estDataLazyLoad; - - const importOptionsState = useState({overrideHeaders: false}); - const importOptions = importOptionsState[0]; - const modalSearchState = useState(""); - - //Import Scenario - const onOwnerFindModalOk = async (lazyData) => { - logImEXEvent("job_import_new"); - - setOwnerModalVisible(false); - - setInsertLoading(true); - const estData = replaceEmpty( - lazyData?.available_jobs_by_pk || estDataRaw.data.available_jobs_by_pk - ); - - if (!(estData && estData.est_data)) { - //We don't have the right data. Error! - setInsertLoading(false); - notification["error"]({ - message: t("jobs.errors.creating", {error: "No job data present."}), - }); - return; + let existingVehicles; + if (estData.est_data.v_vin) { + //There's vehicle data, need to double-check the VIN. + existingVehicles = await client.query({ + query: SEARCH_VEHICLE_BY_VIN, + variables: { + vin: estData.est_data.v_vin || estData.est_data.vehicle.data.v_vin } - // if (process.env.VITE_APP_COUNTRY === "USA") { - //Massage the CCC file set to remove duplicate UNQ_SEQ. - InstanceRenderManager({executeFunction:true,rome: ResolveCCCLineIssues, args: [estData.est_data, bodyshop], promanager: ResolveCCCLineIssues }) + }); + } - // } else { - //IO-539 Check for Parts Rate on PAL for SGI use case. - //TODO:AIO Check that the async function is actually waiting before moving on. - await InstanceRenderManager({executeFunction: true, - imex: CheckTaxRates, rome: CheckTaxRatesUSA, promanager: CheckTaxRatesUSA, args: [estData.est_data, bodyshop]}) - - // } - // const newTotals = ( - // await Axios.post("/job/totals", { - // job: { - // ...estData.est_data, - // joblines: estData.est_data.joblines.data, - // }, - // }) - // ).data; - - let existingVehicles; - if (estData.est_data.v_vin) { - //There's vehicle data, need to double-check the VIN. - existingVehicles = await client.query({ - query: SEARCH_VEHICLE_BY_VIN, - variables: { - vin: estData.est_data.v_vin || estData.est_data.vehicle.data.v_vin, - }, - }); + const newJob = { + ...estData.est_data, + // clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), + // owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"), + // job_totals: newTotals, + date_open: dayjs(), + status: bodyshop.md_ro_statuses.default_imported, + notes: { + data: { + created_by: currentUser.email, + audit: true, + text: t("jobs.labels.importnote") } + }, + queued_for_parts: partsQueueToggle, + ...(existingVehicles && existingVehicles.data.vehicles.length > 0 + ? { vehicleid: existingVehicles.data.vehicles[0].id, vehicle: null } + : {}) + }; - const newJob = { - ...estData.est_data, - // clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), - // owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"), - // job_totals: newTotals, - date_open: dayjs(), - status: bodyshop.md_ro_statuses.default_imported, - notes: { - data: { - created_by: currentUser.email, - audit: true, - text: t("jobs.labels.importnote"), - }, - }, - queued_for_parts: partsQueueToggle, - ...(existingVehicles && existingVehicles.data.vehicles.length > 0 - ? {vehicleid: existingVehicles.data.vehicles[0].id, vehicle: null} - : {}), - }; + if (selectedOwner) { + newJob.ownerid = selectedOwner; + delete newJob.owner; + } + if (newJob.vehicleid) { + delete newJob.vehicle; + } - if (selectedOwner) { - newJob.ownerid = selectedOwner; - delete newJob.owner; + if (typeof newJob.kmin === "string") { + newJob.kmin = null; + } + + try { + const r = await insertNewJob({ + variables: { + job: newJob } - if (newJob.vehicleid) { - delete newJob.vehicle; + }); + await Axios.post("/job/totalsssu", { + id: r.data.insert_jobs.returning[0].id + }); + + if (CriticalPartsScanning.treatment === "on") { + CriticalPartsScan(r.data.insert_jobs.returning[0].id); + } + notification["success"]({ + message: t("jobs.successes.created"), + onClick: () => { + history(`/manage/jobs/${r.data.insert_jobs.returning[0].id}`); } - - if (typeof newJob.kmin === "string") { - newJob.kmin = null; - } - - try { - const r = await insertNewJob({ - variables: { - job: newJob, - }, - }); - await Axios.post("/job/totalsssu", { - id: r.data.insert_jobs.returning[0].id, - }); - - if (CriticalPartsScanning.treatment === "on") { - CriticalPartsScan(r.data.insert_jobs.returning[0].id); - } - notification["success"]({ - message: t("jobs.successes.created"), - onClick: () => { - history(`/manage/jobs/${r.data.insert_jobs.returning[0].id}`); - }, - }); - //Job has been inserted. Clean up the available jobs record. + }); + //Job has been inserted. Clean up the available jobs record. insertAuditTrail({ jobid: r.data.insert_jobs.returning[0].id, operation: AuditTrailMapping.jobimported(), - type: "jobimported", + type: "jobimported" }); - await deleteJob({ - variables: {id: estData.id}, - }).then((r) => { - refetch(); - setInsertLoading(false); - }); + await deleteJob({ + variables: { id: estData.id } + }).then((r) => { + refetch(); + setInsertLoading(false); + }); - setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); - } catch (r) { - //error while inserting - notification["error"]({ - message: t("jobs.errors.creating", {error: r.message}), - }); - refetch().catch(err => { - console.error(`Something went wrong in jobs available table container - ${err.message || ''}`) - }); - setInsertLoading(false); - setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); + } catch (r) { + //error while inserting + notification["error"]({ + message: t("jobs.errors.creating", { error: r.message }) + }); + refetch().catch((err) => { + console.error(`Something went wrong in jobs available table container - ${err.message || ""}`); + }); + setInsertLoading(false); + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); + } + }; + + //Supplement scenario + const onJobFindModalOk = async () => { + logImEXEvent("job_import_supplement"); + + setJobModalVisible(false); + setInsertLoading(true); + + const estData = estDataRaw.data.available_jobs_by_pk; + + if (!(estData && estData.est_data)) { + //We don't have the right data. Error! + setInsertLoading(false); + notification["error"]({ + message: t("jobs.errors.creating", { error: "No job data present." }) + }); + } else { + //create upsert job + let supp = replaceEmpty({ ...estData.est_data }); + //IO-539 Check for Parts Rate on PAL for SGI use case. + + await InstanceRenderManager({ + executeFunction: true, + imex: CheckTaxRates, + rome: CheckTaxRatesUSA, + promanager: CheckTaxRatesUSA, + args: [(supp, bodyshop)] + }); + await InstanceRenderManager({ + executeFunction: true, + rome: ResolveCCCLineIssues, + promanager: ResolveCCCLineIssues, + args: [(supp, bodyshop)] + }); + + delete supp.owner; + delete supp.vehicle; + delete supp.ins_co_nm; + if (!importOptions.overrideHeaders) { + HeaderFields.forEach((item) => delete supp[item]); + } + + let suppDelta = await GetSupplementDelta(client, selectedJob, supp.joblines.data); + + delete supp.joblines; + if (suppDelta !== null) { + await client.mutate({ + mutation: gql` + ${suppDelta} + ` + }); + } + const updateResult = await updateJob({ + variables: { + jobId: selectedJob, + job: { + ...supp, + // clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), + // owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat( + // "0.00" + // ), + // job_totals: newTotals, + queued_for_parts: partsQueueToggle + } } - }; + }); - //Supplement scenario - const onJobFindModalOk = async () => { - logImEXEvent("job_import_supplement"); + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); - setJobModalVisible(false); - setInsertLoading(true); + if (CriticalPartsScanning.treatment === "on") { + CriticalPartsScan(updateResult.data.update_jobs.returning[0].id); + } + if (updateResult.errors) { + //error while inserting + notification["error"]({ + message: t("jobs.errors.creating", { + error: JSON.stringify(updateResult.errors) + }) + }); + refetch(); + setInsertLoading(false); + return; + } - const estData = estDataRaw.data.available_jobs_by_pk; + const newTotals = await Axios.post("/job/totalsssu", { + id: selectedJob + }); - if (!(estData && estData.est_data)) { - //We don't have the right data. Error! - setInsertLoading(false); - notification["error"]({ - message: t("jobs.errors.creating", {error: "No job data present."}), - }); - } else { - //create upsert job - let supp = replaceEmpty({...estData.est_data}); - //IO-539 Check for Parts Rate on PAL for SGI use case. + if (newTotals.status !== 200) { + notification["error"]({ + message: t("jobs.errors.totalscalc") + }); + setInsertLoading(false); + return; + } + notification["success"]({ + message: t("jobs.successes.supplemented"), + onClick: () => { + history(`/manage/jobs/${updateResult.data.update_jobs.returning[0].id}`); + } + }); + //Job has been inserted. Clean up the available jobs record. - await InstanceRenderManager({executeFunction:true, imex: CheckTaxRates, rome: CheckTaxRatesUSA, promanager: CheckTaxRatesUSA, args: [(supp, bodyshop)]}) - await InstanceRenderManager({executeFunction:true, rome: ResolveCCCLineIssues,promanager: ResolveCCCLineIssues ,args:[(supp, bodyshop)] }) - - delete supp.owner; - delete supp.vehicle; - delete supp.ins_co_nm; - if (!importOptions.overrideHeaders) { - HeaderFields.forEach((item) => delete supp[item]); - } - - let suppDelta = await GetSupplementDelta( - client, - selectedJob, - supp.joblines.data - ); - - delete supp.joblines; - if (suppDelta !== null) { - await client.mutate({ - mutation: gql` - ${suppDelta} - `, - }); - } - const updateResult = await updateJob({ - variables: { - jobId: selectedJob, - job: { - ...supp, - // clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), - // owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat( - // "0.00" - // ), - // job_totals: newTotals, - queued_for_parts: partsQueueToggle, - }, - }, - }); - - setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); - - if (CriticalPartsScanning.treatment === "on") { - CriticalPartsScan(updateResult.data.update_jobs.returning[0].id); - } - if (updateResult.errors) { - //error while inserting - notification["error"]({ - message: t("jobs.errors.creating", { - error: JSON.stringify(updateResult.errors), - }), - }); - refetch(); - setInsertLoading(false); - return; - } - - const newTotals = await Axios.post("/job/totalsssu", { - id: selectedJob, - }); - - if (newTotals.status !== 200) { - notification["error"]({ - message: t("jobs.errors.totalscalc"), - }); - setInsertLoading(false); - return; - } - notification["success"]({ - message: t("jobs.successes.supplemented"), - onClick: () => { - history( - `/manage/jobs/${updateResult.data.update_jobs.returning[0].id}` - ); - }, - }); - //Job has been inserted. Clean up the available jobs record. - - deleteJob({ - variables: {id: estData.id}, - }).then((r) => { - refetch(); - setInsertLoading(false); - }); + deleteJob({ + variables: { id: estData.id } + }).then((r) => { + refetch(); + setInsertLoading(false); + }); await insertNote({ variables: { @@ -331,348 +342,325 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail, jobid: selectedJob, created_by: currentUser.email, audit: true, - text: t("jobs.labels.supplementnote"), - }, - ], - }, + text: t("jobs.labels.supplementnote") + } + ] + } }); insertAuditTrail({ jobid: selectedJob, operation: AuditTrailMapping.jobsupplement(), - type: "jobsupplement", + type: "jobsupplement" }); } }; - const owner = - estDataRaw.data && - estDataRaw.data.available_jobs_by_pk && - estDataRaw.data.available_jobs_by_pk.est_data && - estDataRaw.data.available_jobs_by_pk.est_data.owner && - estDataRaw.data.available_jobs_by_pk.est_data.owner.data && - !estDataRaw.data.available_jobs_by_pk.issupplement - ? estDataRaw.data.available_jobs_by_pk.est_data.owner.data - : null; + const owner = + estDataRaw.data && + estDataRaw.data.available_jobs_by_pk && + estDataRaw.data.available_jobs_by_pk.est_data && + estDataRaw.data.available_jobs_by_pk.est_data.owner && + estDataRaw.data.available_jobs_by_pk.est_data.owner.data && + !estDataRaw.data.available_jobs_by_pk.issupplement + ? estDataRaw.data.available_jobs_by_pk.est_data.owner.data + : null; - const onOwnerModalCancel = () => { - setOwnerModalVisible(false); - setSelectedOwner(null); - setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); - }; + const onOwnerModalCancel = () => { + setOwnerModalVisible(false); + setSelectedOwner(null); + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); + }; - const onJobModalCancel = () => { - setJobModalVisible(false); - modalSearchState[1](""); - setSelectedJob(null); - setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); - }; + const onJobModalCancel = () => { + setJobModalVisible(false); + modalSearchState[1](""); + setSelectedJob(null); + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); + }; - const addJobAsNew = (record) => { - loadEstData({variables: {id: record.id}}); - setOwnerModalVisible(true); - }; + const addJobAsNew = (record) => { + loadEstData({ variables: { id: record.id } }); + setOwnerModalVisible(true); + }; - const addJobAsSupp = useCallback((record) => { - loadEstData({variables: {id: record.id}}); - modalSearchState[1](record.clm_no); - setJobModalVisible(true); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const addJobAsSupp = useCallback((record) => { + loadEstData({ variables: { id: record.id } }); + modalSearchState[1](record.clm_no); + setJobModalVisible(true); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - useEffect(() => { - if (availableJobId && clm_no) - addJobAsSupp({id: availableJobId, clm_no: clm_no}); - }, [addJobAsSupp, availableJobId, clm_no]); + useEffect(() => { + if (availableJobId && clm_no) addJobAsSupp({ id: availableJobId, clm_no: clm_no }); + }, [addJobAsSupp, availableJobId, clm_no]); - if (error) return ; + if (error) return ; - - return ( - - - - { - - - // currentUser.email.includes("@rome.") || - // currentUser.email.includes("@imex.") ? ( - // - // ) : null - } - - - - - - - - - - ); + return ( + + + + { + // currentUser.email.includes("@rome.") || + // currentUser.email.includes("@imex.") ? ( + // + // ) : null + } + + + + + + + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsAvailableContainer); +export default connect(mapStateToProps, mapDispatchToProps)(JobsAvailableContainer); function replaceEmpty(someObj, replaceValue = null) { - const replacer = (key, value) => - value === "" ? replaceValue || null : value; - //^ because you seem to want to replace (strings) "null" or "undefined" too - const temp = JSON.stringify(someObj, replacer); - return JSON.parse(temp); -} - -async function CheckTaxRatesUSA(estData,bodyshop){ - if (!estData.parts_tax_rates?.PAM) { - estData.parts_tax_rates.PAM = estData.parts_tax_rates.PAC; + const replacer = (key, value) => (value === "" ? replaceValue || null : value); + //^ because you seem to want to replace (strings) "null" or "undefined" too + const temp = JSON.stringify(someObj, replacer); + return JSON.parse(temp); } +async function CheckTaxRatesUSA(estData, bodyshop) { + if (!estData.parts_tax_rates?.PAM) { + estData.parts_tax_rates.PAM = estData.parts_tax_rates.PAC; + } } async function CheckTaxRates(estData, bodyshop) { - //LKQ Check - if ( + //LKQ Check + if ( !estData.parts_tax_rates?.PAL || estData.parts_tax_rates?.PAL?.prt_tax_rt === null || estData.parts_tax_rates?.PAL?.prt_tax_rt === 0 - ) { + ) { const res = await confirmDialog( - `ImEX Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.` - ); + `ImEX Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.` + ); if (res) { - if (!estData.parts_tax_rates.PAL) { - estData.parts_tax_rates.PAL = { - prt_discp: 0, - prt_mktyp: true, - prt_mkupp: 0, - prt_type: "PAL", - }; + if (!estData.parts_tax_rates.PAL) { + estData.parts_tax_rates.PAL = { + prt_discp: 0, + prt_mktyp: true, + prt_mkupp: 0, + prt_type: "PAL" + }; + } + estData.parts_tax_rates.PAL.prt_tax_rt = bodyshop.bill_tax_rates.state_tax_rate / 100; + estData.parts_tax_rates.PAL.prt_tax_in = true; } - estData.parts_tax_rates.PAL.prt_tax_rt = - bodyshop.bill_tax_rates.state_tax_rate / 100; - estData.parts_tax_rates.PAL.prt_tax_in = true; - } - } - //PAC Check - if ( + } + //PAC Check + if ( !estData.parts_tax_rates?.PAC || estData.parts_tax_rates?.PAC?.prt_tax_rt === null || estData.parts_tax_rates?.PAC?.prt_tax_rt === 0 - ) { + ) { const res = await confirmDialog( - `ImEX Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.` - ); + `ImEX Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.` + ); if (res) { - if (!estData.parts_tax_rates.PAC) { - estData.parts_tax_rates.PAC = { - prt_discp: 0, - prt_mktyp: true, - prt_mkupp: 0, - prt_type: "PAC", - }; + if (!estData.parts_tax_rates.PAC) { + estData.parts_tax_rates.PAC = { + prt_discp: 0, + prt_mktyp: true, + prt_mkupp: 0, + prt_type: "PAC" + }; + } + estData.parts_tax_rates.PAC.prt_tax_rt = bodyshop.bill_tax_rates.state_tax_rate / 100; + estData.parts_tax_rates.PAC.prt_tax_in = true; } - estData.parts_tax_rates.PAC.prt_tax_rt = - bodyshop.bill_tax_rates.state_tax_rate / 100; - estData.parts_tax_rates.PAC.prt_tax_in = true; - } - } - //PAM Check - if ( + } + //PAM Check + if ( !estData.parts_tax_rates?.PAM || estData.parts_tax_rates?.PAM?.prt_tax_rt === null || estData.parts_tax_rates?.PAM?.prt_tax_rt === 0 - ) { + ) { const res = await confirmDialog( - `ImEX Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.` + `ImEX Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.` ); if (res) { - if (!estData.parts_tax_rates.PAM) { - estData.parts_tax_rates.PAM = { - prt_discp: 0, - prt_mktyp: true, - prt_mkupp: 0, - prt_type: "PAM", - }; - } - estData.parts_tax_rates.PAM.prt_tax_rt = - bodyshop.bill_tax_rates.state_tax_rate / 100; - estData.parts_tax_rates.PAM.prt_tax_in = true; - } + if (!estData.parts_tax_rates.PAM) { + estData.parts_tax_rates.PAM = { + prt_discp: 0, + prt_mktyp: true, + prt_mkupp: 0, + prt_type: "PAM" + }; + } + estData.parts_tax_rates.PAM.prt_tax_rt = bodyshop.bill_tax_rates.state_tax_rate / 100; + estData.parts_tax_rates.PAM.prt_tax_in = true; } + } - if ( + if ( !estData.parts_tax_rates?.PAR || estData.parts_tax_rates?.PAR?.prt_tax_rt === null || estData.parts_tax_rates?.PAR?.prt_tax_rt === 0 - ) { + ) { const res = await confirmDialog( - `ImEX Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.` - ); + `ImEX Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.` + ); if (res) { - if (!estData.parts_tax_rates.PAR) { - estData.parts_tax_rates.PAR = { - prt_discp: 0, - prt_mktyp: true, - prt_mkupp: 0, - prt_type: "PAR", - }; - } - estData.parts_tax_rates.PAR.prt_tax_rt = - bodyshop.bill_tax_rates.state_tax_rate / 100; - estData.parts_tax_rates.PAR.prt_tax_in = true; + if (!estData.parts_tax_rates.PAR) { + estData.parts_tax_rates.PAR = { + prt_discp: 0, + prt_mktyp: true, + prt_mkupp: 0, + prt_type: "PAR" + }; + } + estData.parts_tax_rates.PAR.prt_tax_rt = bodyshop.bill_tax_rates.state_tax_rate / 100; + estData.parts_tax_rates.PAR.prt_tax_in = true; } + } + + //IO-1387 If a sublet line is NOT R&R, use the labor tax. If it is, use the sublet tax rate. + //Currently limited to SK shops only. + //if (bodyshop.region_config === "CA_SK") { + estData.joblines.data.forEach((jl, index) => { + if ((jl.part_type === "PASL" || jl.part_type === "PAS") && jl.lbr_op !== "OP11") { + estData.joblines.data[index].tax_part = jl.lbr_tax; } - //IO-1387 If a sublet line is NOT R&R, use the labor tax. If it is, use the sublet tax rate. - //Currently limited to SK shops only. - //if (bodyshop.region_config === "CA_SK") { - estData.joblines.data.forEach((jl, index) => { - if ( - (jl.part_type === "PASL" || jl.part_type === "PAS") && - jl.lbr_op !== "OP11" - ) { - estData.joblines.data[index].tax_part = jl.lbr_tax; - } - - //Set markup lines and tax lines as taxable. - //900510 is a mark up. 900510 is a discount. - if (jl.db_ref === "900510") { - estData.joblines.data[index].tax_part = true; - } - }); - //} + //Set markup lines and tax lines as taxable. + //900510 is a mark up. 900510 is a discount. + if (jl.db_ref === "900510") { + estData.joblines.data[index].tax_part = true; + } + }); + //} } - function ResolveCCCLineIssues(estData, bodyshop) { - //Find all misc amounts, populate them to the act price. - //TODO Ensure that this doesnt get violated - //This needs to be done before cleansing unq_seq since some misc prices could move over. - estData.joblines.data.forEach((line) => { - if (line.misc_amt && line.misc_amt !== 0) { - line.act_price = line.act_price + line.misc_amt; - line.tax_part = !!line.misc_tax; - } - //WEB EST SPECIFIC CLEAN UP - InstanceRenderManager({executeFunction: true, args:[], promanager: () => { - if (line.mod_lbr_ty === "LAET" || line.mod_lbr_ty === "LAUT") { +function ResolveCCCLineIssues(estData, bodyshop) { + //Find all misc amounts, populate them to the act price. + //TODO Ensure that this doesnt get violated + //This needs to be done before cleansing unq_seq since some misc prices could move over. + estData.joblines.data.forEach((line) => { + if (line.misc_amt && line.misc_amt !== 0) { + line.act_price = line.act_price + line.misc_amt; + line.tax_part = !!line.misc_tax; + } + //WEB EST SPECIFIC CLEAN UP + InstanceRenderManager({ + executeFunction: true, + args: [], + promanager: () => { + if (line.mod_lbr_ty === "LAET" || line.mod_lbr_ty === "LAUT") { // line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`; - line.mod_lbr_ty = "LAR"; - } - }}) + line.mod_lbr_ty = "LAR"; + } + } }); - - //Group by line no - // For everything but the first one, strip out the price number in - - // InstanceRenderManager({executeFunction:true, args:[], promanager: () => { - // const groupedByLineRef = _.groupBy(estData.joblines.data, "line_ref"); - // Object.keys(groupedByLineRef).forEach((lineRef) => { - // let index0ActPrice; - // groupedByLineRef[lineRef].forEach((line, index) => { - // //Let the first one keep it - // if (index === 0){ - // index0ActPrice = line.act_price; - // return;} - // //Web Est seems to have additional costs with UNQ_SEQ 0. Keep them all? - // if (line.unq_seq === 0) return; - // if(index0ActPrice !== line.act_price){ - // line.notes += ` | Price override.`; - // return; - // } - // const indexInEstData = estData.joblines.data.findIndex( - // (l) => l.unq_seq === line.unq_seq - // ); - // estData.joblines.data[ - // indexInEstData - // ].notes += ` | Scrubbed due to the line_ref issue. (prev act price = ${estData.joblines.data[indexInEstData].act_price})`; - // estData.joblines.data[indexInEstData].act_price = 0; - // estData.joblines.data[indexInEstData].db_price = 0; - // }); - // }) - // }}) + }); + //Group by line no + // For everything but the first one, strip out the price number in -InstanceRenderManager({ - executeFunction: true, - args: [], - promanager: null, //Require to prevent auto firing of Rome. - rome: () => { - //Generate the list of duplicated UNQ_SEQ that will feed into the next section to scrub the lines. - const unqSeqHash = _.groupBy(estData.joblines.data, 'unq_seq'); - const duplicatedUnqSeq = Object.keys(unqSeqHash).filter((key) => unqSeqHash[key].length > 1); - - duplicatedUnqSeq.forEach((unq_seq) => { - //Keys are strings, convert to int. - const int_unq_seq = parseInt(unq_seq); - - //When line splitting, the first line is always the non-refinish line. We will keep it as is. - //We will cleanse the second line, which is always the next line. - const nonRefLineIndex = estData.joblines.data.findIndex( - (line) => line.unq_seq === int_unq_seq - ); - estData.joblines.data[nonRefLineIndex + 1] = { - ...estData.joblines.data[nonRefLineIndex + 1], - part_type: null, - act_price: 0, - db_price: 0, - prt_dsmk_p: 0, - prt_dsmk_m: 0, - }; - }); - }, -}); - + // InstanceRenderManager({executeFunction:true, args:[], promanager: () => { + // const groupedByLineRef = _.groupBy(estData.joblines.data, "line_ref"); + // Object.keys(groupedByLineRef).forEach((lineRef) => { + // let index0ActPrice; + // groupedByLineRef[lineRef].forEach((line, index) => { + // //Let the first one keep it + // if (index === 0){ + // index0ActPrice = line.act_price; + // return;} + // //Web Est seems to have additional costs with UNQ_SEQ 0. Keep them all? + // if (line.unq_seq === 0) return; + // if(index0ActPrice !== line.act_price){ + // line.notes += ` | Price override.`; + // return; + // } + // const indexInEstData = estData.joblines.data.findIndex( + // (l) => l.unq_seq === line.unq_seq + // ); + // estData.joblines.data[ + // indexInEstData + // ].notes += ` | Scrubbed due to the line_ref issue. (prev act price = ${estData.joblines.data[indexInEstData].act_price})`; + // estData.joblines.data[indexInEstData].act_price = 0; + // estData.joblines.data[indexInEstData].db_price = 0; + // }); + // }) + // }}) + InstanceRenderManager({ + executeFunction: true, + args: [], + promanager: null, //Require to prevent auto firing of Rome. + rome: () => { + //Generate the list of duplicated UNQ_SEQ that will feed into the next section to scrub the lines. + const unqSeqHash = _.groupBy(estData.joblines.data, "unq_seq"); + const duplicatedUnqSeq = Object.keys(unqSeqHash).filter((key) => unqSeqHash[key].length > 1); + duplicatedUnqSeq.forEach((unq_seq) => { + //Keys are strings, convert to int. + const int_unq_seq = parseInt(unq_seq); + //When line splitting, the first line is always the non-refinish line. We will keep it as is. + //We will cleanse the second line, which is always the next line. + const nonRefLineIndex = estData.joblines.data.findIndex((line) => line.unq_seq === int_unq_seq); + estData.joblines.data[nonRefLineIndex + 1] = { + ...estData.joblines.data[nonRefLineIndex + 1], + part_type: null, + act_price: 0, + db_price: 0, + prt_dsmk_p: 0, + prt_dsmk_m: 0 + }; + }); + } + }); } diff --git a/client/src/components/jobs-change-status/jobs-change-status.component.jsx b/client/src/components/jobs-change-status/jobs-change-status.component.jsx index 796283f94..12545faac 100644 --- a/client/src/components/jobs-change-status/jobs-change-status.component.jsx +++ b/client/src/components/jobs-change-status/jobs-change-status.component.jsx @@ -1,119 +1,101 @@ -import {DownCircleFilled} from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {Button, Dropdown, notification} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {UPDATE_JOB_STATUS} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { DownCircleFilled } from "@ant-design/icons"; +import { useMutation } from "@apollo/client"; +import { Button, Dropdown, notification } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - jobRO: selectJobReadOnly, + bodyshop: selectBodyshop, + jobRO: selectJobReadOnly }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export function JobsChangeStatus({job, bodyshop, jobRO, insertAuditTrail}) { - const {t} = useTranslation(); +export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) { + const { t } = useTranslation(); - const [availableStatuses, setAvailableStatuses] = useState([]); - const [otherStages, setOtherStages] = useState([]); - const [mutationUpdateJobstatus] = useMutation(UPDATE_JOB_STATUS); - const updateJobStatus = (status) => { - mutationUpdateJobstatus({ - variables: {jobId: job.id, status: status}, - }) - .then((r) => { - notification["success"]({message: t("jobs.successes.save")}); - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.jobstatuschange(status), - type: "jobstatuschange",}); - // refetch(); - }) - .catch((error) => { - notification["error"]({message: t("jobs.errors.saving")}); - }); - }; + const [availableStatuses, setAvailableStatuses] = useState([]); + const [otherStages, setOtherStages] = useState([]); + const [mutationUpdateJobstatus] = useMutation(UPDATE_JOB_STATUS); + const updateJobStatus = (status) => { + mutationUpdateJobstatus({ + variables: { jobId: job.id, status: status } + }) + .then((r) => { + notification["success"]({ message: t("jobs.successes.save") }); + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.jobstatuschange(status), + type: "jobstatuschange" + }); + // refetch(); + }) + .catch((error) => { + notification["error"]({ message: t("jobs.errors.saving") }); + }); + }; - useEffect(() => { - //Figure out what scenario were in, populate accodingly - if (job && bodyshop) { - if ( - bodyshop.md_ro_statuses.pre_production_statuses.includes(job.status) - ) { - setAvailableStatuses(bodyshop.md_ro_statuses.pre_production_statuses); - if (bodyshop.md_ro_statuses.production_statuses[0]) - setOtherStages([bodyshop.md_ro_statuses.production_statuses[0]]); - } else if ( - bodyshop.md_ro_statuses.production_statuses.includes(job.status) - ) { - setAvailableStatuses(bodyshop.md_ro_statuses.production_statuses); - setOtherStages([ - bodyshop.md_ro_statuses.default_imported, - bodyshop.md_ro_statuses.default_delivered, - ]); - } else if ( - bodyshop.md_ro_statuses.post_production_statuses.includes(job.status) - ) { - setAvailableStatuses( - bodyshop.md_ro_statuses.post_production_statuses.filter( - (s) => - s !== bodyshop.md_ro_statuses.default_invoiced && - s !== bodyshop.md_ro_statuses.default_exported - ) - ); - if (bodyshop.md_ro_statuses.production_statuses[0]) - setOtherStages([bodyshop.md_ro_statuses.production_statuses[0]]); - } else { - console.log( - "Status didn't match any restrictions. Allowing all status changes." - ); - setAvailableStatuses(bodyshop.md_ro_statuses.statuses); - } - } - }, [job, setAvailableStatuses, bodyshop]); - - const statusMenu = { - items: [ - ...availableStatuses.map((item) => ({ - key: item, - label: item, - })), - ...(job.converted - ? [ - {type: "divider"}, - ...otherStages.map((item) => ({ - key: item, - label: item, - })), - ] - : []), - ], - onClick: (e) => updateJobStatus(e.key) + useEffect(() => { + //Figure out what scenario were in, populate accodingly + if (job && bodyshop) { + if (bodyshop.md_ro_statuses.pre_production_statuses.includes(job.status)) { + setAvailableStatuses(bodyshop.md_ro_statuses.pre_production_statuses); + if (bodyshop.md_ro_statuses.production_statuses[0]) + setOtherStages([bodyshop.md_ro_statuses.production_statuses[0]]); + } else if (bodyshop.md_ro_statuses.production_statuses.includes(job.status)) { + setAvailableStatuses(bodyshop.md_ro_statuses.production_statuses); + setOtherStages([bodyshop.md_ro_statuses.default_imported, bodyshop.md_ro_statuses.default_delivered]); + } else if (bodyshop.md_ro_statuses.post_production_statuses.includes(job.status)) { + setAvailableStatuses( + bodyshop.md_ro_statuses.post_production_statuses.filter( + (s) => s !== bodyshop.md_ro_statuses.default_invoiced && s !== bodyshop.md_ro_statuses.default_exported + ) + ); + if (bodyshop.md_ro_statuses.production_statuses[0]) + setOtherStages([bodyshop.md_ro_statuses.production_statuses[0]]); + } else { + console.log("Status didn't match any restrictions. Allowing all status changes."); + setAvailableStatuses(bodyshop.md_ro_statuses.statuses); + } } + }, [job, setAvailableStatuses, bodyshop]); - return ( - - - - ); + return ( + + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(JobsChangeStatus); diff --git a/client/src/components/jobs-close-auto-allocate/jobs-close-auto-allocate.component.jsx b/client/src/components/jobs-close-auto-allocate/jobs-close-auto-allocate.component.jsx index 6a4a0d67d..f90e75d32 100644 --- a/client/src/components/jobs-close-auto-allocate/jobs-close-auto-allocate.component.jsx +++ b/client/src/components/jobs-close-auto-allocate/jobs-close-auto-allocate.component.jsx @@ -1,97 +1,95 @@ -import {Button, Dropdown} from "antd"; +import { Button, Dropdown } from "antd"; import _ from "lodash"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -export function JobsCloseAutoAllocate({bodyshop, joblines, form, disabled}) { - const {t} = useTranslation(); +export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) { + const { t } = useTranslation(); - const handleAllocate = (defaults) => { - form.setFieldsValue({ - joblines: joblines.map((jl) => { - const ret = _.cloneDeep(jl); - if (jl.part_type) { - ret.profitcenter_part = defaults.profits[jl.part_type.toUpperCase()]; - } else { - } - if (jl.mod_lbr_ty) { - ret.profitcenter_labor = - defaults.profits[jl.mod_lbr_ty.toUpperCase()]; - } else { - ret.profitcenter_labor = null; - } - //Verify that this is also manually updated in server/job-costing - if ( - InstanceRenderManager({ - imex: !jl.part_type && !jl.mod_lbr_ty, - rome: !ret.profitcenter_part, - promanager: 'USE_ROME', - }) - ) { - const lineDesc = jl.line_desc ? jl.line_desc.toLowerCase() : ''; - if (lineDesc.includes('shop materials')) { - ret.profitcenter_part = defaults.profits['MASH']; - } else if (lineDesc.includes('paint/materials')) { - ret.profitcenter_part = defaults.profits['MAPA']; - } else if (lineDesc.includes('ats amount')) { - ret.profitcenter_part = defaults.profits['ATS']; - } else if (jl.act_price > 0) { - ret.profitcenter_part = defaults.profits['PAO']; - } else { - ret.profitcenter_part = null; - } - } - return ret; - }), - }); - }; + const handleAllocate = (defaults) => { + form.setFieldsValue({ + joblines: joblines.map((jl) => { + const ret = _.cloneDeep(jl); + if (jl.part_type) { + ret.profitcenter_part = defaults.profits[jl.part_type.toUpperCase()]; + } else { + } + if (jl.mod_lbr_ty) { + ret.profitcenter_labor = defaults.profits[jl.mod_lbr_ty.toUpperCase()]; + } else { + ret.profitcenter_labor = null; + } + //Verify that this is also manually updated in server/job-costing + if ( + InstanceRenderManager({ + imex: !jl.part_type && !jl.mod_lbr_ty, + rome: !ret.profitcenter_part, + promanager: "USE_ROME" + }) + ) { + const lineDesc = jl.line_desc ? jl.line_desc.toLowerCase() : ""; + if (lineDesc.includes("shop materials")) { + ret.profitcenter_part = defaults.profits["MASH"]; + } else if (lineDesc.includes("paint/materials")) { + ret.profitcenter_part = defaults.profits["MAPA"]; + } else if (lineDesc.includes("ats amount")) { + ret.profitcenter_part = defaults.profits["ATS"]; + } else if (jl.act_price > 0) { + ret.profitcenter_part = defaults.profits["PAO"]; + } else { + ret.profitcenter_part = null; + } + } + return ret; + }) + }); + }; - const handleAutoAllocateClick = () => { - logImEXEvent("jobs_close_allocate_auto"); + const handleAutoAllocateClick = () => { + logImEXEvent("jobs_close_allocate_auto"); - const {defaults} = bodyshop.md_responsibility_centers; - handleAllocate(defaults); - }; + const { defaults } = bodyshop.md_responsibility_centers; + handleAllocate(defaults); + }; - const handleMenuClick = ({item, key, keyPath, domEvent}) => { - logImEXEvent("jobs_close_allocate_auto_dms"); - form.setFieldsValue({dms_allocation: key}); - handleAllocate( - bodyshop.md_responsibility_centers.dms_defaults.find( - (x) => x.name === key - ) - ); - }; + const handleMenuClick = ({ item, key, keyPath, domEvent }) => { + logImEXEvent("jobs_close_allocate_auto_dms"); + form.setFieldsValue({ dms_allocation: key }); + handleAllocate(bodyshop.md_responsibility_centers.dms_defaults.find((x) => x.name === key)); + }; - const menu = bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? { - items: bodyshop.md_responsibility_centers.dms_defaults.map((mapping) => ({ + const menu = + bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber + ? { + items: bodyshop.md_responsibility_centers.dms_defaults.map((mapping) => ({ key: mapping.name, label: mapping.name, - disabled: disabled, - })), - onClick: handleMenuClick, - } : { - items: [] - } + disabled: disabled + })), + onClick: handleMenuClick + } + : { + items: [] + }; - return bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? ( - - - - ) : ( - - ); + return bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? ( + + + + ) : ( + + ); } export default connect(mapStateToProps, null)(JobsCloseAutoAllocate); diff --git a/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx b/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx index d96ddba63..e84b31595 100644 --- a/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx +++ b/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx @@ -1,250 +1,231 @@ -import {useMutation} from "@apollo/client"; -import {Button, notification} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, notification } from "antd"; import axios from "axios"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {auth, logImEXEvent} from "../../firebase/firebase.utils"; -import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { auth, logImEXEvent } from "../../firebase/firebase.utils"; +import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import client from "../../utils/GraphQLClient"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({ jobid, operation, type }) => - dispatch(insertAuditTrail({ jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); function updateJobCache(items) { - client.cache.modify({ - id: "ROOT_QUERY", - fields: { - jobs(existingJobs = []) { - return existingJobs.filter( - (jobRef) => jobRef.__ref.includes(items) === false - ); - }, - }, - }); + client.cache.modify({ + id: "ROOT_QUERY", + fields: { + jobs(existingJobs = []) { + return existingJobs.filter((jobRef) => jobRef.__ref.includes(items) === false); + } + } + }); } export function JobsCloseExportButton({ - bodyshop, - currentUser, - jobId, - disabled, - setSelectedJobs, - refetch, - insertAuditTrail, - }) { - const history = useNavigate(); - const { t } = useTranslation(); - const [updateJob] = useMutation(UPDATE_JOB); - const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); - const [loading, setLoading] = useState(false); + bodyshop, + currentUser, + jobId, + disabled, + setSelectedJobs, + refetch, + insertAuditTrail +}) { + const history = useNavigate(); + const { t } = useTranslation(); + const [updateJob] = useMutation(UPDATE_JOB); + const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); + const [loading, setLoading] = useState(false); - const handleQbxml = async () => { - //Check if it's a CDK setup. - if (bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) { - history(`/manage/dms?jobId=${jobId}`); - return; - } - logImEXEvent("jobs_close_export"); + const handleQbxml = async () => { + //Check if it's a CDK setup. + if (bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) { + history(`/manage/dms?jobId=${jobId}`); + return; + } + logImEXEvent("jobs_close_export"); - setLoading(true); - //Check if it's a QBO Setup. - let PartnerResponse; - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { - PartnerResponse = await axios.post(`/qbo/receivables`, { - jobIds: [jobId], - elgen: true, - }); - } else { - //Default is QBD + setLoading(true); + //Check if it's a QBO Setup. + let PartnerResponse; + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + PartnerResponse = await axios.post(`/qbo/receivables`, { + jobIds: [jobId], + elgen: true + }); + } else { + //Default is QBD - let QbXmlResponse; - try { - QbXmlResponse = await axios.post( - "/accounting/qbxml/receivables", - {jobIds: [jobId]}, - { - headers: { - Authorization: `Bearer ${await auth.currentUser.getIdToken()}`, - }, - } - ); - console.log("handle -> XML", QbXmlResponse); - } catch (error) { - console.log("Error getting QBXML from Server.", error); - notification["error"]({ - message: t("jobs.errors.exporting", { - error: "Unable to retrieve QBXML. " + JSON.stringify(error.message), - }), - }); - setLoading(false); - return; + let QbXmlResponse; + try { + QbXmlResponse = await axios.post( + "/accounting/qbxml/receivables", + { jobIds: [jobId] }, + { + headers: { + Authorization: `Bearer ${await auth.currentUser.getIdToken()}` } - - try { - PartnerResponse = await axios.post( - "http://localhost:1337/qb/", - // "http://609feaeae986.ngrok.io/qb/", - QbXmlResponse.data, - { - headers: { - Authorization: `Bearer ${await auth.currentUser.getIdToken()}`, - }, - } - ); - } catch (error) { - console.log("Error connecting to quickbooks or partner.", error); - notification["error"]({ - message: t("jobs.errors.exporting-partner"), - }); - setLoading(false); - return; - } - } - - console.log("PartnerResponse", PartnerResponse); - - //Check to see if any of them failed. If they didn't don't execute the update. - const failedTransactions = PartnerResponse.data.filter((r) => !r.success); - const successfulTransactions = PartnerResponse.data.filter( - (r) => r.success + } ); - if (failedTransactions.length > 0) { - //Uh oh. At least one was no good. - failedTransactions.forEach((ft) => { - //insert failed export log - notification.open({ - // key: "failedexports", - type: "error", - message: t("jobs.errors.exporting", { - error: ft.errorMessage || "", - }), - }); - }); - - if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { - //QBO Logs are handled server side. - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - jobid: jobId, - successful: false, - message: JSON.stringify( - failedTransactions.map((ft) => ft.errorMessage) - ), - useremail: currentUser.email, - }, - ], - }, - }); - } - } else { - //Insert success export log. - if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { - //QBO Logs are handled server side. - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - jobid: jobId, - successful: true, - useremail: currentUser.email, - }, - ], - }, - }); - - const jobUpdateResponse = await updateJob({ - variables: { - jobId: jobId, - job: { - status: bodyshop.md_ro_statuses.default_exported || "Exported*", - date_exported: new Date(), - }, - }, - }); - - if (!!!jobUpdateResponse.errors) { - notification.open({ - type: "success", - key: "jobsuccessexport", - message: t("jobs.successes.exported"), - }); - insertAuditTrail({ - jobid: jobId, - operation: AuditTrailMapping.jobexported(), - type: "jobexported",}); - updateJobCache( - jobUpdateResponse.data.update_jobs.returning.map((job) => job.id) - ); - } else { - notification["error"]({ - message: t("jobs.errors.exporting", { - error: JSON.stringify(jobUpdateResponse.error), - }), - }); - } - } - if ( - bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo && - successfulTransactions.length > 0 - ) { - notification.open({ - type: "success", - key: "jobsuccessexport", - message: t("jobs.successes.exported"), - }); - insertAuditTrail({ - jobid: jobId, - operation: AuditTrailMapping.jobexported(),type: "jobexported", - }); - updateJobCache([ - ...new Set( - successfulTransactions.map( - (st) => - st[ - bodyshop.accountingconfig && bodyshop.accountingconfig.qbo - ? "jobid" - : "id" - ] - ) - ), - ]); - } - if (setSelectedJobs) { - setSelectedJobs((selectedJobs) => { - return selectedJobs.filter((i) => i !== jobId); - }); - } - } + console.log("handle -> XML", QbXmlResponse); + } catch (error) { + console.log("Error getting QBXML from Server.", error); + notification["error"]({ + message: t("jobs.errors.exporting", { + error: "Unable to retrieve QBXML. " + JSON.stringify(error.message) + }) + }); setLoading(false); - }; + return; + } - return ( - - ); + try { + PartnerResponse = await axios.post( + "http://localhost:1337/qb/", + // "http://609feaeae986.ngrok.io/qb/", + QbXmlResponse.data, + { + headers: { + Authorization: `Bearer ${await auth.currentUser.getIdToken()}` + } + } + ); + } catch (error) { + console.log("Error connecting to quickbooks or partner.", error); + notification["error"]({ + message: t("jobs.errors.exporting-partner") + }); + setLoading(false); + return; + } + } + + console.log("PartnerResponse", PartnerResponse); + + //Check to see if any of them failed. If they didn't don't execute the update. + const failedTransactions = PartnerResponse.data.filter((r) => !r.success); + const successfulTransactions = PartnerResponse.data.filter((r) => r.success); + if (failedTransactions.length > 0) { + //Uh oh. At least one was no good. + failedTransactions.forEach((ft) => { + //insert failed export log + notification.open({ + // key: "failedexports", + type: "error", + message: t("jobs.errors.exporting", { + error: ft.errorMessage || "" + }) + }); + }); + + if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { + //QBO Logs are handled server side. + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + jobid: jobId, + successful: false, + message: JSON.stringify(failedTransactions.map((ft) => ft.errorMessage)), + useremail: currentUser.email + } + ] + } + }); + } + } else { + //Insert success export log. + if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { + //QBO Logs are handled server side. + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + jobid: jobId, + successful: true, + useremail: currentUser.email + } + ] + } + }); + + const jobUpdateResponse = await updateJob({ + variables: { + jobId: jobId, + job: { + status: bodyshop.md_ro_statuses.default_exported || "Exported*", + date_exported: new Date() + } + } + }); + + if (!!!jobUpdateResponse.errors) { + notification.open({ + type: "success", + key: "jobsuccessexport", + message: t("jobs.successes.exported") + }); + insertAuditTrail({ + jobid: jobId, + operation: AuditTrailMapping.jobexported(), + type: "jobexported" + }); + updateJobCache(jobUpdateResponse.data.update_jobs.returning.map((job) => job.id)); + } else { + notification["error"]({ + message: t("jobs.errors.exporting", { + error: JSON.stringify(jobUpdateResponse.error) + }) + }); + } + } + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { + notification.open({ + type: "success", + key: "jobsuccessexport", + message: t("jobs.successes.exported") + }); + insertAuditTrail({ + jobid: jobId, + operation: AuditTrailMapping.jobexported(), + type: "jobexported" + }); + updateJobCache([ + ...new Set( + successfulTransactions.map( + (st) => st[bodyshop.accountingconfig && bodyshop.accountingconfig.qbo ? "jobid" : "id"] + ) + ) + ]); + } + if (setSelectedJobs) { + setSelectedJobs((selectedJobs) => { + return selectedJobs.filter((i) => i !== jobId); + }); + } + } + setLoading(false); + }; + + return ( + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsCloseExportButton); +export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseExportButton); diff --git a/client/src/components/jobs-close-lines/jobs-close-lines.component.jsx b/client/src/components/jobs-close-lines/jobs-close-lines.component.jsx index 98acce7e2..06f454521 100644 --- a/client/src/components/jobs-close-lines/jobs-close-lines.component.jsx +++ b/client/src/components/jobs-close-lines/jobs-close-lines.component.jsx @@ -1,216 +1,202 @@ -import {Form, Select, Space, Tooltip} from "antd"; +import { Form, Select, Space, Tooltip } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import LaborTypeFormItem from "../form-items-formatted/labor-type-form-item.component"; import PartTypeFormItem from "../form-items-formatted/part-type-form-item.component"; import ReadOnlyFormItem from "../form-items-formatted/read-only-form-item.component"; -import {WarningOutlined} from "@ant-design/icons"; +import { WarningOutlined } from "@ant-design/icons"; import "./jobs-close-lines.styles.scss"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - jobRO: selectJobReadOnly, + bodyshop: selectBodyshop, + jobRO: selectJobReadOnly }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function JobsCloseLines({bodyshop, job, jobRO}) { - const {t} = useTranslation(); - return ( -
- - {(fields, {add, remove, move}) => { - return ( -
- - - - - - - - - - - - - - - {fields.map((field, index) => ( - - - - - - - - - - - - ))} - -
{t("joblines.fields.line_desc")}{t("joblines.fields.part_type")}{t("joblines.fields.act_price")}{t("joblines.fields.prt_dsmk_m")}{t("joblines.fields.op_code_desc")}{t("joblines.fields.mod_lbr_ty")}{t("joblines.fields.mod_lb_hrs")}{t("joblines.fields.profitcenter_part")}{t("joblines.fields.profitcenter_labor")}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ); - }} - - - ); +export function JobsCloseLines({ bodyshop, job, jobRO }) { + const { t } = useTranslation(); + return ( +
+ + {(fields, { add, remove, move }) => { + return ( + + + + + + + + + + + + + + + + {fields.map((field, index) => ( + + + + + + + + + + + + ))} + +
{t("joblines.fields.line_desc")}{t("joblines.fields.part_type")}{t("joblines.fields.act_price")}{t("joblines.fields.prt_dsmk_m")}{t("joblines.fields.op_code_desc")}{t("joblines.fields.mod_lbr_ty")}{t("joblines.fields.mod_lb_hrs")}{t("joblines.fields.profitcenter_part")}{t("joblines.fields.profitcenter_labor")}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); + }} +
+
+ ); } export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseLines); -const HasBeenConvertedTolabor = ({value}) => { - const {t} = useTranslation(); +const HasBeenConvertedTolabor = ({ value }) => { + const { t } = useTranslation(); - if (!value) return null; - return ( - - - - ); + if (!value) return null; + return ( + + + + ); }; diff --git a/client/src/components/jobs-convert-button/jobs-convert-button.component.jsx b/client/src/components/jobs-convert-button/jobs-convert-button.component.jsx index 0afed461d..ba7bfb799 100644 --- a/client/src/components/jobs-convert-button/jobs-convert-button.component.jsx +++ b/client/src/components/jobs-convert-button/jobs-convert-button.component.jsx @@ -1,276 +1,241 @@ -import {useMutation} from "@apollo/client"; -import {Button, Form, Input, notification, Popover, Select, Space, Switch,} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Form, Input, notification, Popover, Select, Space, Switch } from "antd"; import axios from "axios"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {CONVERT_JOB_TO_RO} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { CONVERT_JOB_TO_RO } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, - jobRO: selectJobReadOnly, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop, + jobRO: selectJobReadOnly }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export function JobsConvertButton({ - bodyshop, - job, - refetch, - jobRO, - insertAuditTrail, - parentFormIsFieldsTouched, - }) { - const [open, setOpen] = useState(false); - const [loading, setLoading] = useState(false); - const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO); - const {t} = useTranslation(); - const [form] = Form.useForm(); +export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTrail, parentFormIsFieldsTouched }) { + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); + const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO); + const { t } = useTranslation(); + const [form] = Form.useForm(); - const handleConvert = async ({employee_csr, category, ...values}) => { - if (parentFormIsFieldsTouched()) { - alert(t("jobs.labels.savebeforeconversion")); - return; + const handleConvert = async ({ employee_csr, category, ...values }) => { + if (parentFormIsFieldsTouched()) { + alert(t("jobs.labels.savebeforeconversion")); + return; + } + setLoading(true); + const res = await mutationConvertJob({ + variables: { + jobId: job.id, + job: { + converted: true, + ...(bodyshop.enforce_conversion_csr ? { employee_csr } : {}), + ...(bodyshop.enforce_conversion_category ? { category } : {}), + ...values } - setLoading(true); - const res = await mutationConvertJob({ - variables: { - jobId: job.id, - job: { - converted: true, - ...(bodyshop.enforce_conversion_csr ? {employee_csr} : {}), - ...(bodyshop.enforce_conversion_category ? {category} : {}), - ...values, - }, - }, - }); + } + }); - if (values.ca_gst_registrant) { - await axios.post("/job/totalsssu", { - id: job.id, - }); - } + if (values.ca_gst_registrant) { + await axios.post("/job/totalsssu", { + id: job.id + }); + } - if (!res.errors) { - refetch(); - notification["success"]({ - message: t("jobs.successes.converted"), - }); - - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.jobconverted( - res.data.update_jobs.returning[0].ro_number - ), - type: "jobconverted", + if (!res.errors) { + refetch(); + notification["success"]({ + message: t("jobs.successes.converted") }); - setOpen(false); - } - setLoading(false); - }; + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.jobconverted(res.data.update_jobs.returning[0].ro_number), + type: "jobconverted" + }); - const popMenu = ( -
-
+ + + + + {bodyshop.enforce_class && ( + + + + )} + {bodyshop.enforce_referral && ( + <> + - - - - {bodyshop.enforce_class && ( - - - - )} - {bodyshop.enforce_referral && ( - <> - - - - - - - - )} - {bodyshop.enforce_conversion_csr && ( - - - - )} - {bodyshop.enforce_conversion_category && ( - - - - )} - {bodyshop.region_config.toLowerCase().startsWith("ca") && ( - - - - )} - - - - - - - - - - - -
- ); - - if (job.converted) return <>; - - return ( - - + + + + + ); + + if (job.converted) return <>; + + return ( + + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(JobsConvertButton); diff --git a/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx b/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx index be26f3bb5..533889b96 100644 --- a/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx +++ b/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx @@ -1,13 +1,13 @@ -import {Collapse, Form, Input, InputNumber, Select, Switch} from "antd"; +import { Collapse, Form, Input, InputNumber, Select, Switch } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component"; -import FormItemPhone, {PhoneItemFormatterValidation,} from "../form-items-formatted/phone-form-item.component"; +import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component"; import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component"; import JobsDetailRatesParts from "../jobs-detail-rates/jobs-detail-rates.parts.component"; @@ -21,362 +21,291 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function JobsCreateJobsInfo({bodyshop, form, selected}) { - const {t} = useTranslation(); - const {getFieldValue} = form; - return ( -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PhoneItemFormatterValidation(getFieldValue, "ins_ph1"), - ]} - > - - - - - +export function JobsCreateJobsInfo({ bodyshop, form, selected }) { + const { t } = useTranslation(); + const { getFieldValue } = form; + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PhoneItemFormatterValidation(getFieldValue, "ins_ph1")]} + > + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {bodyshop.region_config.toLowerCase().startsWith("ca") && ( - - - - )} - - - - - - - - - - - - - - { - InstanceRenderManager({imex: - - - - - - - - - - - - }) - } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {bodyshop.region_config.toLowerCase().startsWith("ca") && ( + + + + )} + + + + + + + + + + + + + + {InstanceRenderManager({ + imex: ( + + + + + + + + + + + + ) + })} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - // - // - // - // - // - // - } - - - - - - - - - - { - InstanceRenderManager({rome: - <> - - - - - }) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + // + // + // + // + // + // } -
- ); + + + + + + +
+
+
+ + {InstanceRenderManager({ + rome: ( + <> + + + + + + ) + })} +
+ ); } export default connect(mapStateToProps, mapDispatchToProps)(JobsCreateJobsInfo); diff --git a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.component.jsx b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.component.jsx index af0fd57b8..34fa3ff6e 100644 --- a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.component.jsx +++ b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.component.jsx @@ -1,52 +1,49 @@ -import {Checkbox, Col, Row} from "antd"; -import React, {useContext} from "react"; -import {useTranslation} from "react-i18next"; +import { Checkbox, Col, Row } from "antd"; +import React, { useContext } from "react"; +import { useTranslation } from "react-i18next"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import JobsCreateOwnerInfoNewComponent from "./jobs-create-owner-info.new.component"; import JobsCreateOwnerInfoSearchComponent from "./jobs-create-owner-info.search.component"; const colSpan = { - sm: {span: 24}, - lg: {span: 12}, + sm: { span: 24 }, + lg: { span: 12 } }; -export default function JobsCreateOwnerInfoComponent({loading, owners}) { - const {t} = useTranslation(); - const [state, setState] = useContext(JobCreateContext); +export default function JobsCreateOwnerInfoComponent({ loading, owners }) { + const { t } = useTranslation(); + const [state, setState] = useContext(JobCreateContext); - return ( -
- - - { - setState({ - ...state, - owner: { - ...state.owner, - new: !state.owner.new, - selectedid: null, - }, - }); - }} - > - {t("jobs.labels.create.newowner")} - - + return ( +
+ + + { + setState({ + ...state, + owner: { + ...state.owner, + new: !state.owner.new, + selectedid: null + } + }); + }} + > + {t("jobs.labels.create.newowner")} + + - - - + + + - - - - -
- ); + + + +
+
+ ); } diff --git a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.container.jsx b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.container.jsx index 2ff02825d..0b5a44b04 100644 --- a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.container.jsx +++ b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.container.jsx @@ -1,24 +1,19 @@ -import {useQuery} from "@apollo/client"; -import React, {useContext} from "react"; -import {QUERY_SEARCH_OWNER_BY_IDX} from "../../graphql/owners.queries"; +import { useQuery } from "@apollo/client"; +import React, { useContext } from "react"; +import { QUERY_SEARCH_OWNER_BY_IDX } from "../../graphql/owners.queries"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import AlertComponent from "../alert/alert.component"; import JobsCreateOwnerInfoComponent from "./jobs-create-owner-info.component"; export default function JobsCreateOwnerContainer() { - const [state] = useContext(JobCreateContext); - const {loading, error, data} = useQuery(QUERY_SEARCH_OWNER_BY_IDX, { - variables: {search: `%${state.owner.search}%`}, - skip: !state.owner.search, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const [state] = useContext(JobCreateContext); + const { loading, error, data } = useQuery(QUERY_SEARCH_OWNER_BY_IDX, { + variables: { search: `%${state.owner.search}%` }, + skip: !state.owner.search, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - if (error) return ; - return ( - - ); + if (error) return ; + return ; } diff --git a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.new.component.jsx b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.new.component.jsx index 7eb8d794c..d6ee2076f 100644 --- a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.new.component.jsx +++ b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.new.component.jsx @@ -1,178 +1,142 @@ -import {Form, Input, Switch} from "antd"; -import React, {useContext} from "react"; -import {useTranslation} from "react-i18next"; +import { Form, Input, Switch } from "antd"; +import React, { useContext } from "react"; +import { useTranslation } from "react-i18next"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import FormItemEmail from "../form-items-formatted/email-form-item.component"; -import FormItemPhone, {PhoneItemFormatterValidation,} from "../form-items-formatted/phone-form-item.component"; +import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; export default function JobsCreateOwnerInfoNewComponent() { - const [state] = useContext(JobCreateContext); + const [state] = useContext(JobCreateContext); - const {t} = useTranslation(); - return ( -
- - ({ - required: - state.owner.new && - (!getFieldValue(["owner", "data", "ownr_co_nm"]) || - getFieldValue(["owner", "data", "ownr_co_nm"]) === ""), - //message: t("general.validation.required"), - }), - ]} - > - - - ({ - required: - state.owner.new && - (!getFieldValue(["owner", "data", "ownr_co_nm"]) || - getFieldValue(["owner", "data", "ownr_co_nm"]) === ""), - //message: t("general.validation.required"), - }), - ]} - > - - - + const { t } = useTranslation(); + return ( +
+ + ({ + required: + state.owner.new && + (!getFieldValue(["owner", "data", "ownr_co_nm"]) || + getFieldValue(["owner", "data", "ownr_co_nm"]) === "") + //message: t("general.validation.required"), + }) + ]} + > + + + ({ + required: + state.owner.new && + (!getFieldValue(["owner", "data", "ownr_co_nm"]) || + getFieldValue(["owner", "data", "ownr_co_nm"]) === "") + //message: t("general.validation.required"), + }) + ]} + > + + + - - - - - ({ - required: - state.owner.new && - (!getFieldValue(["owner", "data", "ownr_ln"]) || - !getFieldValue(["owner", "data", "ownr_fn"]) || - getFieldValue(["owner", "data", "ownr_ln"]) === "" || - getFieldValue(["owner", "data", "ownr_fn"]) === ""), - //message: t("general.validation.required"), - }), - ]} - > - - - + + + + + ({ + required: + state.owner.new && + (!getFieldValue(["owner", "data", "ownr_ln"]) || + !getFieldValue(["owner", "data", "ownr_fn"]) || + getFieldValue(["owner", "data", "ownr_ln"]) === "" || + getFieldValue(["owner", "data", "ownr_fn"]) === "") + //message: t("general.validation.required"), + }) + ]} + > + + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - PhoneItemFormatterValidation( - getFieldValue, - "owner.data.ownr_ph1" - ), - ]} - > - - - - PhoneItemFormatterValidation( - getFieldValue, - "owner.data.ownr_ph2" - ), - ]} - > - - - - - - - - - - - -
- ); + + + + + PhoneItemFormatterValidation(getFieldValue, "owner.data.ownr_ph1")]} + > + + + PhoneItemFormatterValidation(getFieldValue, "owner.data.ownr_ph2")]} + > + + + + + + + + + + + +
+ ); } diff --git a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.search.component.jsx b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.search.component.jsx index 6a58fb409..442fe3f67 100644 --- a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.search.component.jsx +++ b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.search.component.jsx @@ -1,165 +1,142 @@ -import {Card, Input, Table} from "antd"; -import React, {useContext, useState} from "react"; -import {useTranslation} from "react-i18next"; +import { Card, Input, Table } from "antd"; +import React, { useContext, useState } from "react"; +import { useTranslation } from "react-i18next"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import PhoneFormatter from "../../utils/PhoneFormatter"; -import {alphaSort} from "../../utils/sorters"; +import { alphaSort } from "../../utils/sorters"; -export default function JobsCreateOwnerInfoSearchComponent({ - loading, - owners, - }) { - const [state, setState] = useContext(JobCreateContext); - const [tableState, setTableState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); +export default function JobsCreateOwnerInfoSearchComponent({ loading, owners }) { + const [state, setState] = useContext(JobCreateContext); + const [tableState, setTableState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); - const {t} = useTranslation(); + const { t } = useTranslation(); - const columns = [ - { - title: t("owners.fields.ownr_ln"), - dataIndex: "ownr_ln", - key: "ownr_ln", - sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), - sortOrder: - tableState.sortedInfo.columnKey === "ownr_ln" && - tableState.sortedInfo.order, - }, - { - title: t("owners.fields.ownr_fn"), - dataIndex: "ownr_fn", - key: "ownr_fn", - sorter: (a, b) => alphaSort(a.ownr_fn, b.ownr_fn), - sortOrder: - tableState.sortedInfo.columnKey === "ownr_fn" && - tableState.sortedInfo.order, - }, - { - title: t("owners.fields.ownr_co_nm"), - dataIndex: "ownr_co_nm", - key: "ownr_co_nm", - sorter: (a, b) => alphaSort(a.ownr_co_nm, b.ownr_co_nm), - sortOrder: - tableState.sortedInfo.columnKey === "ownr_co_nm" && - tableState.sortedInfo.order, - }, - { - title: t("owners.fields.ownr_addr1"), - dataIndex: "ownr_addr1", - key: "ownr_addr1", - sorter: (a, b) => alphaSort(a.ownr_addr1, b.ownr_addr1), - sortOrder: - tableState.sortedInfo.columnKey === "ownr_addr1" && - tableState.sortedInfo.order, - }, - { - title: t("owners.fields.ownr_city"), - dataIndex: "ownr_city", - key: "ownr_city", - sorter: (a, b) => alphaSort(a.ownr_city, b.ownr_city), - sortOrder: - tableState.sortedInfo.columnKey === "ownr_city" && - tableState.sortedInfo.order, - }, - { - title: t("owners.fields.ownr_ea"), - dataIndex: "ownr_ea", - key: "ownr_ea", - sorter: (a, b) => alphaSort(a.ownr_ea, b.ownr_ea), - sortOrder: - tableState.sortedInfo.columnKey === "ownr_ea" && - tableState.sortedInfo.order, - }, - { - title: t("owners.fields.ownr_ph1"), - dataIndex: "ownr_ph1", - key: "ownr_ph1", - render: (text, record) => ( - {record.ownr_ph1} - ), - sorter: (a, b) => alphaSort(a.ownr_ph1, b.ownr_ph1), - sortOrder: - tableState.sortedInfo.columnKey === "ownr_ph1" && - tableState.sortedInfo.order, - }, - { - title: t("owners.fields.ownr_ph2"), - dataIndex: "ownr_ph2", - key: "ownr_ph2", - render: (text, record) => ( - {record.ownr_ph2} - ), - sorter: (a, b) => alphaSort(a.ownr_ph2, b.ownr_ph2), - sortOrder: - tableState.sortedInfo.columnKey === "ownr_ph2" && - tableState.sortedInfo.order, - }, - ]; + const columns = [ + { + title: t("owners.fields.ownr_ln"), + dataIndex: "ownr_ln", + key: "ownr_ln", + sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), + sortOrder: tableState.sortedInfo.columnKey === "ownr_ln" && tableState.sortedInfo.order + }, + { + title: t("owners.fields.ownr_fn"), + dataIndex: "ownr_fn", + key: "ownr_fn", + sorter: (a, b) => alphaSort(a.ownr_fn, b.ownr_fn), + sortOrder: tableState.sortedInfo.columnKey === "ownr_fn" && tableState.sortedInfo.order + }, + { + title: t("owners.fields.ownr_co_nm"), + dataIndex: "ownr_co_nm", + key: "ownr_co_nm", + sorter: (a, b) => alphaSort(a.ownr_co_nm, b.ownr_co_nm), + sortOrder: tableState.sortedInfo.columnKey === "ownr_co_nm" && tableState.sortedInfo.order + }, + { + title: t("owners.fields.ownr_addr1"), + dataIndex: "ownr_addr1", + key: "ownr_addr1", + sorter: (a, b) => alphaSort(a.ownr_addr1, b.ownr_addr1), + sortOrder: tableState.sortedInfo.columnKey === "ownr_addr1" && tableState.sortedInfo.order + }, + { + title: t("owners.fields.ownr_city"), + dataIndex: "ownr_city", + key: "ownr_city", + sorter: (a, b) => alphaSort(a.ownr_city, b.ownr_city), + sortOrder: tableState.sortedInfo.columnKey === "ownr_city" && tableState.sortedInfo.order + }, + { + title: t("owners.fields.ownr_ea"), + dataIndex: "ownr_ea", + key: "ownr_ea", + sorter: (a, b) => alphaSort(a.ownr_ea, b.ownr_ea), + sortOrder: tableState.sortedInfo.columnKey === "ownr_ea" && tableState.sortedInfo.order + }, + { + title: t("owners.fields.ownr_ph1"), + dataIndex: "ownr_ph1", + key: "ownr_ph1", + render: (text, record) => {record.ownr_ph1}, + sorter: (a, b) => alphaSort(a.ownr_ph1, b.ownr_ph1), + sortOrder: tableState.sortedInfo.columnKey === "ownr_ph1" && tableState.sortedInfo.order + }, + { + title: t("owners.fields.ownr_ph2"), + dataIndex: "ownr_ph2", + key: "ownr_ph2", + render: (text, record) => {record.ownr_ph2}, + sorter: (a, b) => alphaSort(a.ownr_ph2, b.ownr_ph2), + sortOrder: tableState.sortedInfo.columnKey === "ownr_ph2" && tableState.sortedInfo.order + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setTableState({...tableState, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setTableState({ ...tableState, filteredInfo: filters, sortedInfo: sorter }); + }; - return ( - { - setState({ - ...state, - owner: {...state.owner, search: value}, - }); - }} - enterButton - /> + return ( + { + setState({ + ...state, + owner: { ...state.owner, search: value } + }); + }} + enterButton + /> + } + > + { + setState({ + ...state, + owner: { ...state.owner, new: false, selectedid: props.id } + }); + }, + type: "radio", + selectedRowKeys: [state.owner.selectedid] + }} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + if (record) { + if (record.id) { + setState({ + ...state, + owner: { + ...state.owner, + new: false, + selectedid: record.id + } + }); + + return; + } + } + setState({ + ...state, + owner: { ...state.owner, selectedid: null } + }); } - > -
{ - setState({ - ...state, - owner: {...state.owner, new: false, selectedid: props.id}, - }); - }, - type: "radio", - selectedRowKeys: [state.owner.selectedid], - }} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - if (record) { - if (record.id) { - setState({ - ...state, - owner: { - ...state.owner, - new: false, - selectedid: record.id, - }, - }); - - return; - } - } - setState({ - ...state, - owner: {...state.owner, selectedid: null}, - }); - }, - }; - }} - /> - - ); + }; + }} + /> + + ); } diff --git a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.component.jsx b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.component.jsx index 4cdad32bf..9b8ca7074 100644 --- a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.component.jsx +++ b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.component.jsx @@ -1,71 +1,64 @@ -import {Checkbox, Col, Row} from "antd"; -import React, {useContext} from "react"; +import { Checkbox, Col, Row } from "antd"; +import React, { useContext } from "react"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import JobsCreateVehicleInfoNewComponent from "./jobs-create-vehicle-info.new.component"; import JobsCreateVehicleInfoSearchComponent from "./jobs-create-vehicle-info.search.component"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; const colSpan = { - sm: {span: 24}, - lg: {span: 12}, + sm: { span: 24 }, + lg: { span: 12 } }; -export default function JobsCreateVehicleInfoComponent({ - loading, - vehicles, - form, - }) { - const [state, setState] = useContext(JobCreateContext); - const {t} = useTranslation(); - return ( -
- -
- { - setState({ - ...state, - vehicle: { - ...state.vehicle, - none: false, - new: !state.vehicle.new, - selectedid: null, - }, - }); - }} - > - {t("jobs.labels.create.newvehicle")} - - { - setState({ - ...state, - vehicle: { - ...state.vehicle, - new: false, - none: !state.vehicle.none, - selectedid: null, - }, - }); - }} - > - {t("jobs.labels.create.novehicle")} - - - - - - - - - - - ); +export default function JobsCreateVehicleInfoComponent({ loading, vehicles, form }) { + const [state, setState] = useContext(JobCreateContext); + const { t } = useTranslation(); + return ( +
+ +
+ { + setState({ + ...state, + vehicle: { + ...state.vehicle, + none: false, + new: !state.vehicle.new, + selectedid: null + } + }); + }} + > + {t("jobs.labels.create.newvehicle")} + + { + setState({ + ...state, + vehicle: { + ...state.vehicle, + new: false, + none: !state.vehicle.none, + selectedid: null + } + }); + }} + > + {t("jobs.labels.create.novehicle")} + + + + + + + + + + + ); } diff --git a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.container.jsx b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.container.jsx index a9bfa39c0..471a3dae3 100644 --- a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.container.jsx +++ b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.container.jsx @@ -1,26 +1,20 @@ -import {useQuery} from "@apollo/client"; -import React, {useContext} from "react"; -import {SEARCH_VEHICLES} from "../../graphql/vehicles.queries"; +import { useQuery } from "@apollo/client"; +import React, { useContext } from "react"; +import { SEARCH_VEHICLES } from "../../graphql/vehicles.queries"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import AlertComponent from "../alert/alert.component"; import JobsCreateVehicleInfoComponent from "./jobs-create-vehicle-info.component"; -export default function JobsCreateVehicleInfoContainer({form}) { - const [state] = useContext(JobCreateContext); - const {loading, error, data} = useQuery(SEARCH_VEHICLES, { - variables: {search: `%${state.vehicle.search}%`}, - skip: !state.vehicle.search, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); +export default function JobsCreateVehicleInfoContainer({ form }) { + const [state] = useContext(JobCreateContext); + const { loading, error, data } = useQuery(SEARCH_VEHICLES, { + variables: { search: `%${state.vehicle.search}%` }, + skip: !state.vehicle.search, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - if (error) return ; + if (error) return ; - return ( - - ); + return ; } diff --git a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.new.component.jsx b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.new.component.jsx index dad4385cd..d3026676d 100644 --- a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.new.component.jsx +++ b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.new.component.jsx @@ -1,202 +1,156 @@ -import {Form, Input} from "antd"; -import React, {useContext} from "react"; -import {useTranslation} from "react-i18next"; +import { Form, Input } from "antd"; +import React, { useContext } from "react"; +import { useTranslation } from "react-i18next"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import JobsCreateVehicleInfoPredefined from "./jobs-create-vehicle-info.predefined.component"; -export default function JobsCreateVehicleInfoNewComponent({form}) { - const [state] = useContext(JobCreateContext); +export default function JobsCreateVehicleInfoNewComponent({ form }) { + const [state] = useContext(JobCreateContext); - const {t} = useTranslation(); - return ( -
- - - - - - - - - - - - - + const { t } = useTranslation(); + return ( +
+ + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - { - // - // - // - } - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + { + // + // + // + } + + + + + + + + + + + + + + + + + + + + - - - - - -
- ); + + + + + +
+ ); } diff --git a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.predefined.component.jsx b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.predefined.component.jsx index a8e577419..338e873dd 100644 --- a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.predefined.component.jsx +++ b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.predefined.component.jsx @@ -1,81 +1,80 @@ -import {PlusOutlined, SearchOutlined} from "@ant-design/icons"; -import {Button, Input, Popover, Table} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; +import { PlusOutlined, SearchOutlined } from "@ant-design/icons"; +import { Button, Input, Popover, Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import PredefinedVehicles from "./predefined-vehicles.js"; -export default function JobsCreateVehicleInfoPredefined({disabled, form}) { - const [open, setOpen] = useState(false); - const [search, setSearch] = useState(""); - const {t} = useTranslation(); - const handleOpenChange = (newOpen) => { - setOpen(newOpen); - setSearch(""); - }; - const filteredPredefinedVehicles = - search === "" - ? PredefinedVehicles - : PredefinedVehicles.filter( - (v) => - v.make.toLowerCase().includes(search.toLowerCase()) || - v.model.toLowerCase().includes(search.toLowerCase()) - ); +export default function JobsCreateVehicleInfoPredefined({ disabled, form }) { + const [open, setOpen] = useState(false); + const [search, setSearch] = useState(""); + const { t } = useTranslation(); + const handleOpenChange = (newOpen) => { + setOpen(newOpen); + setSearch(""); + }; + const filteredPredefinedVehicles = + search === "" + ? PredefinedVehicles + : PredefinedVehicles.filter( + (v) => + v.make.toLowerCase().includes(search.toLowerCase()) || v.model.toLowerCase().includes(search.toLowerCase()) + ); - const popContent = () => ( -
-
setSearch(value)}/>} - dataSource={filteredPredefinedVehicles} - columns={[ - { - dataIndex: "make", - key: "make", - title: t("vehicles.fields.v_make_desc"), - }, - { - dataIndex: "model", - key: "model", - title: t("vehicles.fields.v_model_desc"), - }, - { - dataIndex: "select", - key: "select", - title: t("general.labels.actions"), - render: (value, record) => ( - - ), - }, - ]} - /> - - ); - return ( - - - - ); + const popContent = () => ( +
+
setSearch(value)} />} + dataSource={filteredPredefinedVehicles} + columns={[ + { + dataIndex: "make", + key: "make", + title: t("vehicles.fields.v_make_desc") + }, + { + dataIndex: "model", + key: "model", + title: t("vehicles.fields.v_model_desc") + }, + { + dataIndex: "select", + key: "select", + title: t("general.labels.actions"), + render: (value, record) => ( + + ) + } + ]} + /> + + ); + return ( + + + + ); } diff --git a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.search.component.jsx b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.search.component.jsx index 44495f613..124ae31f3 100644 --- a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.search.component.jsx +++ b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.search.component.jsx @@ -1,134 +1,127 @@ -import React, {useContext, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {Card, Input, Space, Table} from "antd"; -import {Link} from "react-router-dom"; -import {alphaSort} from "../../utils/sorters"; +import React, { useContext, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Card, Input, Space, Table } from "antd"; +import { Link } from "react-router-dom"; +import { alphaSort } from "../../utils/sorters"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; -export default function JobsCreateVehicleInfoSearchComponent({ - loading, - vehicles, - }) { - const [state, setState] = useContext(JobCreateContext); - const [tableState, setTableState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); +export default function JobsCreateVehicleInfoSearchComponent({ loading, vehicles }) { + const [state, setState] = useContext(JobCreateContext); + const [tableState, setTableState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); - const {t} = useTranslation(); + const { t } = useTranslation(); - const columns = [ - { - title: t("vehicles.fields.v_vin"), - dataIndex: "v_vin", - key: "v_vin", - sorter: (a, b) => alphaSort(a.v_vin, b.v_vin), - sortOrder: - tableState.sortedInfo.columnKey === "v_vin" && - tableState.sortedInfo.order, - render: (text, record) => ( - - {record.v_vin} - - ), - }, - { - title: t("vehicles.fields.description"), - dataIndex: "description", - key: "description", - render: (text, record) => { - return ( - {`${record.v_model_yr} ${record.v_make_desc} ${record.v_model_desc} ${record.v_color}`} - ); - }, - }, - { - title: t("vehicles.fields.plate_no"), - dataIndex: "plate", - key: "plate", - render: (text, record) => { - return {`${record.plate_st} | ${record.plate_no}`}; - }, - }, - ]; + const columns = [ + { + title: t("vehicles.fields.v_vin"), + dataIndex: "v_vin", + key: "v_vin", + sorter: (a, b) => alphaSort(a.v_vin, b.v_vin), + sortOrder: tableState.sortedInfo.columnKey === "v_vin" && tableState.sortedInfo.order, + render: (text, record) => ( + + {record.v_vin} + + ) + }, + { + title: t("vehicles.fields.description"), + dataIndex: "description", + key: "description", + render: (text, record) => { + return {`${record.v_model_yr} ${record.v_make_desc} ${record.v_model_desc} ${record.v_color}`}; + } + }, + { + title: t("vehicles.fields.plate_no"), + dataIndex: "plate", + key: "plate", + render: (text, record) => { + return {`${record.plate_st} | ${record.plate_no}`}; + } + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setTableState({...tableState, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setTableState({ ...tableState, filteredInfo: filters, sortedInfo: sorter }); + }; - return ( - - { - setState({ - ...state, - vehicle: {...state.vehicle, search: value}, - }); - }} - enterButton - /> - + return ( + + { + setState({ + ...state, + vehicle: { ...state.vehicle, search: value } + }); + }} + enterButton + /> + + } + > +
{ + setState({ + ...state, + vehicle: { + ...state.vehicle, + none: false, + new: false, + selectedid: props.id, + vehicleObj: props + } + }); + }, + type: "radio", + selectedRowKeys: [state.vehicle.selectedid] + }} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + if (record) { + if (record.id) { + setState({ + ...state, + vehicle: { + ...state.vehicle, + none: false, + new: false, + selectedid: record.id, + vehicleObj: record + } + }); + + return; + } + } + setState({ + ...state, + vehicle: { + ...state.vehicle, + selectedid: null, + vehicleObj: null + } + }); } - > -
{ - setState({ - ...state, - vehicle: { - ...state.vehicle, - none: false, - new: false, - selectedid: props.id, - vehicleObj: props, - }, - }); - }, - type: "radio", - selectedRowKeys: [state.vehicle.selectedid], - }} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - if (record) { - if (record.id) { - setState({ - ...state, - vehicle: { - ...state.vehicle, - none: false, - new: false, - selectedid: record.id, - vehicleObj: record, - }, - }); - - return; - } - } - setState({ - ...state, - vehicle: { - ...state.vehicle, - selectedid: null, - vehicleObj: null, - }, - }); - }, - }; - }} - /> - - ); + }; + }} + /> + + ); } diff --git a/client/src/components/jobs-create-vehicle-info/predefined-vehicles.js b/client/src/components/jobs-create-vehicle-info/predefined-vehicles.js index fab4b33a8..56e2e7de9 100644 --- a/client/src/components/jobs-create-vehicle-info/predefined-vehicles.js +++ b/client/src/components/jobs-create-vehicle-info/predefined-vehicles.js @@ -1,3991 +1,3991 @@ const obj = [ - { - make: "ACURA", - model: "1.6 EL", - }, - { - make: "ACURA", - model: "1.7 EL", - }, - { - make: "ACURA", - model: "2.3 CL", - }, - { - make: "ACURA", - model: "2.5TL", - }, - { - make: "ACURA", - model: "3.2TL", - }, - { - make: "ACURA", - model: "3.5RL", - }, - { - make: "ACURA", - model: "CL", - }, - { - make: "ACURA", - model: "CSX", - }, - { - make: "ACURA", - model: "EL", - }, - { - make: "ACURA", - model: "GSR", - }, - { - make: "ACURA", - model: "ILX", - }, - { - make: "ACURA", - model: "INTEGRA", - }, - { - make: "ACURA", - model: "LEGEND", - }, - { - make: "ACURA", - model: "MDX", - }, - { - make: "ACURA", - model: "NSX", - }, - { - make: "ACURA", - model: "RDX", - }, - { - make: "ACURA", - model: "RSX", - }, - { - make: "ACURA", - model: "TL", - }, - { - make: "ACURA", - model: "TL-S", - }, - { - make: "ACURA", - model: "TLX", - }, - { - make: "ACURA", - model: "TSK 4", - }, - { - make: "ACURA", - model: "TSX", - }, - { - make: "ACURA", - model: "VIGOR", - }, - { - make: "AMC", - model: "GENERAL", - }, - { - make: "AMC", - model: "RAMBLER", - }, - { - make: "ATLAS", - model: "CUB", - }, - { - make: "AUDI", - model: "100", - }, - { - make: "AUDI", - model: "A2", - }, - { - make: "AUDI", - model: "A2 QUATTRO", - }, - { - make: "AUDI", - model: "A3", - }, - { - make: "AUDI", - model: "A4", - }, - { - make: "AUDI", - model: "A4 QUATTRO", - }, - { - make: "AUDI", - model: "A5", - }, - { - make: "AUDI", - model: "A6", - }, - { - make: "AUDI", - model: "Q3", - }, - { - make: "AUDI", - model: "Q5", - }, - { - make: "AUDI", - model: "Q7", - }, - { - make: "AUDI", - model: "QUATTRO", - }, - { - make: "AUDI", - model: "RS4", - }, - { - make: "AUDI", - model: "S3", - }, - { - make: "AUDI", - model: "S4", - }, - { - make: "AUDI", - model: "S5", - }, - { - make: "AUDI", - model: "S6", - }, - { - make: "AUDI", - model: "TT", - }, - { - make: "AUSTIN", - model: "HEALY", - }, - { - make: "BMW", - model: "128I", - }, - { - make: "BMW", - model: "135I", - }, - { - make: "BMW", - model: "135IS", - }, - { - make: "BMW", - model: "228I", - }, - { - make: "BMW", - model: "3 SERIES", - }, - { - make: "BMW", - model: "318", - }, - { - make: "BMW", - model: "318 TI", - }, - { - make: "BMW", - model: "318I", - }, - { - make: "BMW", - model: "318IS", - }, - { - make: "BMW", - model: "320CI", - }, - { - make: "BMW", - model: "320I", - }, - { - make: "BMW", - model: "323I", - }, - { - make: "BMW", - model: "325", - }, - { - make: "BMW", - model: "325CI", - }, - { - make: "BMW", - model: "325E", - }, - { - make: "BMW", - model: "325I", - }, - { - make: "BMW", - model: "325IS", - }, - { - make: "BMW", - model: "325XI", - }, - { - make: "BMW", - model: "328CI", - }, - { - make: "BMW", - model: "328D", - }, - { - make: "BMW", - model: "328I", - }, - { - make: "BMW", - model: "328XI", - }, - { - make: "BMW", - model: "330CI", - }, - { - make: "BMW", - model: "330I", - }, - { - make: "BMW", - model: "330XI", - }, - { - make: "BMW", - model: "335I", - }, - { - make: "BMW", - model: "335XI", - }, - { - make: "BMW", - model: "352I", - }, - { - make: "BMW", - model: "428I", - }, - { - make: "BMW", - model: "430I", - }, - { - make: "BMW", - model: "435I", - }, - { - make: "BMW", - model: "440I", - }, - { - make: "BMW", - model: "520I", - }, - { - make: "BMW", - model: "525I", - }, - { - make: "BMW", - model: "528I", - }, - { - make: "BMW", - model: "530", - }, - { - make: "BMW", - model: "530I", - }, - { - make: "BMW", - model: "530IA", - }, - { - make: "BMW", - model: "530XI", - }, - { - make: "BMW", - model: "535I", - }, - { - make: "BMW", - model: "540I", - }, - { - make: "BMW", - model: "640I", - }, - { - make: "BMW", - model: "645CI", - }, - { - make: "BMW", - model: "735", - }, - { - make: "BMW", - model: "740I", - }, - { - make: "BMW", - model: "745LI", - }, - { - make: "BMW", - model: "750I", - }, - { - make: "BMW", - model: "750LI", - }, - { - make: "BMW", - model: "K-1200", - }, - { - make: "BMW", - model: "M3", - }, - { - make: "BMW", - model: "M5", - }, - { - make: "BMW", - model: "X1", - }, - { - make: "BMW", - model: "X3", - }, - { - make: "BMW", - model: "X5", - }, - { - make: "BMW", - model: "X6", - }, - { - make: "BMW", - model: "Z3", - }, - { - make: "BMW", - model: "Z4", - }, - { - make: "BUICK", - model: "ALLURE", - }, - { - make: "BUICK", - model: "CENTURY", - }, - { - make: "BUICK", - model: "ENCLAVE", - }, - { - make: "BUICK", - model: "ENCORE", - }, - { - make: "BUICK", - model: "ENVISION", - }, - { - make: "BUICK", - model: "INVICTA", - }, - { - make: "BUICK", - model: "LACROSSE", - }, - { - make: "BUICK", - model: "LESABRE", - }, - { - make: "BUICK", - model: "LUCERNE", - }, - { - make: "BUICK", - model: "PARK AVENUE", - }, - { - make: "BUICK", - model: "REGAL", - }, - { - make: "BUICK", - model: "RENDEZVOUS", - }, - { - make: "BUICK", - model: "RIVIERA", - }, - { - make: "BUICK", - model: "ROADMASTER", - }, - { - make: "BUICK", - model: "SKYLARK", - }, - { - make: "BUICK", - model: "SOMERSET", - }, - { - make: "BUICK", - model: "SPECIAL", - }, - { - make: "BUICK", - model: "TERRAZA", - }, - { - make: "BUICK", - model: "VERANO", - }, - { - make: "CADILLAC", - model: "ATS", - }, - { - make: "CADILLAC", - model: "CATERA", - }, - { - make: "CADILLAC", - model: "CTS", - }, - { - make: "CADILLAC", - model: "DE VILLE", - }, - { - make: "CADILLAC", - model: "DTS", - }, - { - make: "CADILLAC", - model: "ELDORADO", - }, - { - make: "CADILLAC", - model: "ESCLADE", - }, - { - make: "CADILLAC", - model: "FLEETWOOD", - }, - { - make: "CADILLAC", - model: "SEVILLE", - }, - { - make: "CADILLAC", - model: "SRX", - }, - { - make: "CADILLAC", - model: "STS", - }, - { - make: "CADILLAC", - model: "XLR", - }, - { - make: "CHEVROLET", - model: "1500", - }, - { - make: "CHEVROLET", - model: "2500", - }, - { - make: "CHEVROLET", - model: "2500HD", - }, - { - make: "CHEVROLET", - model: "2WHDR", - }, - { - make: "CHEVROLET", - model: "3500 EXPRESS VAN", - }, - { - make: "CHEVROLET", - model: "3500 HD", - }, - { - make: "CHEVROLET", - model: "ALER", - }, - { - make: "CHEVROLET", - model: "ASTRO", - }, - { - make: "CHEVROLET", - model: "AVALANCHE", - }, - { - make: "CHEVROLET", - model: "AVEO", - }, - { - make: "CHEVROLET", - model: "BEL AIR", - }, - { - make: "CHEVROLET", - model: "BERETTA", - }, - { - make: "CHEVROLET", - model: "BLAZER", - }, - { - make: "CHEVROLET", - model: "BOLT", - }, - { - make: "CHEVROLET", - model: "BOLT EV", - }, - { - make: "CHEVROLET", - model: "C10", - }, - { - make: "CHEVROLET", - model: "C20", - }, - { - make: "CHEVROLET", - model: "C2500", - }, - { - make: "CHEVROLET", - model: "C3500", - }, - { - make: "CHEVROLET", - model: "CAMARO", - }, - { - make: "CHEVROLET", - model: "CAPRICE", - }, - { - make: "CHEVROLET", - model: "CAVALIER", - }, - { - make: "CHEVROLET", - model: "CHEVELLE", - }, - { - make: "CHEVROLET", - model: "CITY EXPRESS", - }, - { - make: "CHEVROLET", - model: "COBALT", - }, - { - make: "CHEVROLET", - model: "COLORADO", - }, - { - make: "CHEVROLET", - model: "CORSICA", - }, - { - make: "CHEVROLET", - model: "CORVETTE", - }, - { - make: "CHEVROLET", - model: "CRUZE", - }, - { - make: "CHEVROLET", - model: "EL CAMINO", - }, - { - make: "CHEVROLET", - model: "EPICA", - }, - { - make: "CHEVROLET", - model: "EQUINOX", - }, - { - make: "CHEVROLET", - model: "EXPRESS", - }, - { - make: "CHEVROLET", - model: "EXPRESS 1500", - }, - { - make: "CHEVROLET", - model: "EXPRESS 2500", - }, - { - make: "CHEVROLET", - model: "EXPRESS 3500", - }, - { - make: "CHEVROLET", - model: "EXPRESS VAN", - }, - { - make: "CHEVROLET", - model: "G1500", - }, - { - make: "CHEVROLET", - model: "G2500", - }, - { - make: "CHEVROLET", - model: "G30", - }, - { - make: "CHEVROLET", - model: "G3500", - }, - { - make: "CHEVROLET", - model: "G4500", - }, - { - make: "CHEVROLET", - model: "GMT-400", - }, - { - make: "CHEVROLET", - model: "HD 2500", - }, - { - make: "CHEVROLET", - model: "HHR", - }, - { - make: "CHEVROLET", - model: "IMPALA", - }, - { - make: "CHEVROLET", - model: "K1500", - }, - { - make: "CHEVROLET", - model: "K2500", - }, - { - make: "CHEVROLET", - model: "K3500", - }, - { - make: "CHEVROLET", - model: "K5500", - }, - { - make: "CHEVROLET", - model: "LUMINA", - }, - { - make: "CHEVROLET", - model: "MALIBU", - }, - { - make: "CHEVROLET", - model: "MALIBU HYBRID", - }, - { - make: "CHEVROLET", - model: "MONTE CARLO", - }, - { - make: "CHEVROLET", - model: "NOMAD", - }, - { - make: "CHEVROLET", - model: "NOVA", - }, - { - make: "CHEVROLET", - model: "OPTRA", - }, - { - make: "CHEVROLET", - model: "ORLANDO", - }, - { - make: "CHEVROLET", - model: "P30", - }, - { - make: "CHEVROLET", - model: "S10", - }, - { - make: "CHEVROLET", - model: "S10 BLAZER", - }, - { - make: "CHEVROLET", - model: "SAVANNA", - }, - { - make: "CHEVROLET", - model: "SILVERADO", - }, - { - make: "CHEVROLET", - model: "SILVERADO 2500 HD", - }, - { - make: "CHEVROLET", - model: "SILVERADO 3500", - }, - { - make: "CHEVROLET", - model: "SILVERADO 3500 HD", - }, - { - make: "CHEVROLET", - model: "SONIC", - }, - { - make: "CHEVROLET", - model: "SPARK", - }, - { - make: "CHEVROLET", - model: "SPORT VAN G20", - }, - { - make: "CHEVROLET", - model: "SPRINT", - }, - { - make: "CHEVROLET", - model: "SUBURBAN", - }, - { - make: "CHEVROLET", - model: "TAHOE", - }, - { - make: "CHEVROLET", - model: "TRACKER", - }, - { - make: "CHEVROLET", - model: "TRAILBLAZER", - }, - { - make: "CHEVROLET", - model: "TRAVERSE", - }, - { - make: "CHEVROLET", - model: "TRAX", - }, - { - make: "CHEVROLET", - model: "TREX", - }, - { - make: "CHEVROLET", - model: "UPLANDER", - }, - { - make: "CHEVROLET", - model: "VENTURE", - }, - { - make: "CHEVROLET", - model: "VOLT", - }, - { - make: "CHEVROLET", - model: "Z24", - }, - { - make: "CHEVROLET", - model: "Z28 IROC", - }, - { - make: "CHRYSLER", - model: "200", - }, - { - make: "CHRYSLER", - model: "300", - }, - { - make: "CHRYSLER", - model: "300C", - }, - { - make: "CHRYSLER", - model: "300M", - }, - { - make: "CHRYSLER", - model: "5TH AVE", - }, - { - make: "CHRYSLER", - model: "ASPEN", - }, - { - make: "CHRYSLER", - model: "CIRRUS", - }, - { - make: "CHRYSLER", - model: "CONCORDE", - }, - { - make: "CHRYSLER", - model: "CROSSFIRE", - }, - { - make: "CHRYSLER", - model: "DAYTONA", - }, - { - make: "CHRYSLER", - model: "E CLASS", - }, - { - make: "CHRYSLER", - model: "INTREPID", - }, - { - make: "CHRYSLER", - model: "LEBARON", - }, - { - make: "CHRYSLER", - model: "LHS", - }, - { - make: "CHRYSLER", - model: "NEON", - }, - { - make: "CHRYSLER", - model: "NEW YORKER", - }, - { - make: "CHRYSLER", - model: "PACIFICA", - }, - { - make: "CHRYSLER", - model: "PT CRUISER", - }, - { - make: "CHRYSLER", - model: "SEBRING", - }, - { - make: "CHRYSLER", - model: "TOWN & COUNTRY", - }, - { - make: "DATSUN", - model: "PICK-UP", - }, - { - make: "DODGE", - model: "1500", - }, - { - make: "DODGE", - model: "250", - }, - { - make: "DODGE", - model: "2500", - }, - { - make: "DODGE", - model: "4500", - }, - { - make: "DODGE", - model: "5500", - }, - { - make: "DODGE", - model: "ARIES", - }, - { - make: "DODGE", - model: "AVENGER", - }, - { - make: "DODGE", - model: "B1500", - }, - { - make: "DODGE", - model: "B200", - }, - { - make: "DODGE", - model: "B250", - }, - { - make: "DODGE", - model: "B350", - }, - { - make: "DODGE", - model: "CALIBER", - }, - { - make: "DODGE", - model: "CAMPERVAN", - }, - { - make: "DODGE", - model: "CARAVAN", - }, - { - make: "DODGE", - model: "CHALLENGER", - }, - { - make: "DODGE", - model: "CHARGER", - }, - { - make: "DODGE", - model: "COLT", - }, - { - make: "DODGE", - model: "CORNET", - }, - { - make: "DODGE", - model: "CV VAN", - }, - { - make: "DODGE", - model: "D100", - }, - { - make: "DODGE", - model: "D200", - }, - { - make: "DODGE", - model: "D250", - }, - { - make: "DODGE", - model: "D300", - }, - { - make: "DODGE", - model: "D350", - }, - { - make: "DODGE", - model: "DAKOTA", - }, - { - make: "DODGE", - model: "DART", - }, - { - make: "DODGE", - model: "DEMON", - }, - { - make: "DODGE", - model: "DURANGO", - }, - { - make: "DODGE", - model: "DYNASTY", - }, - { - make: "DODGE", - model: "GRAND CARAVAN", - }, - { - make: "DODGE", - model: "GRAND CARAVEN SE", - }, - { - make: "DODGE", - model: "JOURNEY", - }, - { - make: "DODGE", - model: "MAGNUM", - }, - { - make: "DODGE", - model: "MOTORHOME", - }, - { - make: "DODGE", - model: "NEON", - }, - { - make: "DODGE", - model: "NITRO", - }, - { - make: "DODGE", - model: "PRO MASTER", - }, - { - make: "DODGE", - model: "PRO MASTER 3500", - }, - { - make: "DODGE", - model: "RAM", - }, - { - make: "DODGE", - model: "RAM 1500", - }, - { - make: "DODGE", - model: "RAM 250", - }, - { - make: "DODGE", - model: "RAM 2500", - }, - { - make: "DODGE", - model: "RAM 3500", - }, - { - make: "DODGE", - model: "RAM 5500", - }, - { - make: "DODGE", - model: "RAM PRO MASTER", - }, - { - make: "DODGE", - model: "RAM SRT10", - }, - { - make: "DODGE", - model: "RAM VAN", - }, - { - make: "DODGE", - model: "RAMPAGE", - }, - { - make: "DODGE", - model: "SHADOW", - }, - { - make: "DODGE", - model: "SPIRIT", - }, - { - make: "DODGE", - model: "SPRINTER 3500", - }, - { - make: "DODGE", - model: "SRT", - }, - { - make: "DODGE", - model: "STEALTH", - }, - { - make: "DODGE", - model: "STRATUS", - }, - { - make: "DODGE", - model: "SWINGER", - }, - { - make: "DODGE", - model: "SX 2.0", - }, - { - make: "DODGE", - model: "W200", - }, - { - make: "DODGE", - model: "W350", - }, - { - make: "EAGLE", - model: "SUMMIT", - }, - { - make: "EAGLE", - model: "TALON", - }, - { - make: "EAGLE", - model: "VISION", - }, - { - make: "FIAT", - model: "500C", - }, - { - make: "FIAT", - model: "500L", - }, - { - make: "FORD", - model: "500", - }, - { - make: "FORD", - model: "AEROSTAR", - }, - { - make: "FORD", - model: "ASPIRE", - }, - { - make: "FORD", - model: "BRONCO", - }, - { - make: "FORD", - model: "CMAX", - }, - { - make: "FORD", - model: "CONTOUR", - }, - { - make: "FORD", - model: "CROWN VIC", - }, - { - make: "FORD", - model: "CUBE VAN", - }, - { - make: "FORD", - model: "DUMP TRUCK", - }, - { - make: "FORD", - model: "E150", - }, - { - make: "FORD", - model: "E250", - }, - { - make: "FORD", - model: "E350", - }, - { - make: "FORD", - model: "E450", - }, - { - make: "FORD", - model: "ECO SPORT", - }, - { - make: "FORD", - model: "ECONOLINE", - }, - { - make: "FORD", - model: "EDGE", - }, - { - make: "FORD", - model: "ESCAPE", - }, - { - make: "FORD", - model: "ESCORT", - }, - { - make: "FORD", - model: "EXCURSION", - }, - { - make: "FORD", - model: "EXPEDITION", - }, - { - make: "FORD", - model: "EXPLORER", - }, - { - make: "FORD", - model: "EXPLORER SPORT", - }, - { - make: "FORD", - model: "EXPLORER SPORT TRAC", - }, - { - make: "FORD", - model: "F-150", - }, - { - make: "FORD", - model: "F-250", - }, - { - make: "FORD", - model: "F-350", - }, - { - make: "FORD", - model: "F-450", - }, - { - make: "FORD", - model: "F-550", - }, - { - make: "FORD", - model: "FAIRLANE", - }, - { - make: "FORD", - model: "FESTIVA", - }, - { - make: "FORD", - model: "FIESTA", - }, - { - make: "FORD", - model: "FLAT DECK", - }, - { - make: "FORD", - model: "FLEX", - }, - { - make: "FORD", - model: "FOCUS", - }, - { - make: "FORD", - model: "FREESTAR", - }, - { - make: "FORD", - model: "FREESTYLE", - }, - { - make: "FORD", - model: "FUSION", - }, - { - make: "FORD", - model: "GALAXIE 500", - }, - { - make: "FORD", - model: "LARIAT", - }, - { - make: "FORD", - model: "LCF", - }, - { - make: "FORD", - model: "LGT", - }, - { - make: "FORD", - model: "MOTORHOME", - }, - { - make: "FORD", - model: "MUSTANG", - }, - { - make: "FORD", - model: "PLEASURE WAY", - }, - { - make: "FORD", - model: "PROBE", - }, - { - make: "FORD", - model: "RANGER", - }, - { - make: "FORD", - model: "RAPTOR", - }, - { - make: "FORD", - model: "SPORT TRAC", - }, - { - make: "FORD", - model: "SUPER DUTY", - }, - { - make: "FORD", - model: "TAURUS", - }, - { - make: "FORD", - model: "T-BIRD", - }, - { - make: "FORD", - model: "T-BUCKET", - }, - { - make: "FORD", - model: "TEMPO", - }, - { - make: "FORD", - model: "THUNDERBIRD", - }, - { - make: "FORD", - model: "TRANSIT", - }, - { - make: "FORD", - model: "TRANSIT 250", - }, - { - make: "FORD", - model: "TRANSIT 350HD", - }, - { - make: "FORD", - model: "TRANSIT CONNECT", - }, - { - make: "FORD", - model: "TRANSIT COONECT", - }, - { - make: "FORD", - model: "WINDSTAR", - }, - { - make: "FREIGHTLINER", - model: "FL70", - }, - { - make: "FREIGHTLINER", - model: "M2", - }, - { - make: "GEO", - model: "METRO", - }, - { - make: "GEO", - model: "STORM", - }, - { - make: "GEO", - model: "TRACKER", - }, - { - make: "GMC", - model: "1500", - }, - { - make: "GMC", - model: "2500 HD", - }, - { - make: "GMC", - model: "3500", - }, - { - make: "GMC", - model: "4000", - }, - { - make: "GMC", - model: "4500HD", - }, - { - make: "GMC", - model: "ACADIA", - }, - { - make: "GMC", - model: "C1500", - }, - { - make: "GMC", - model: "C2500", - }, - { - make: "GMC", - model: "C4500", - }, - { - make: "GMC", - model: "CANYON", - }, - { - make: "GMC", - model: "CARGO", - }, - { - make: "GMC", - model: "DENALI HD", - }, - { - make: "GMC", - model: "ENVOY", - }, - { - make: "GMC", - model: "G2500 VANDURA", - }, - { - make: "GMC", - model: "G35 SAVANNA", - }, - { - make: "GMC", - model: "GEO", - }, - { - make: "GMC", - model: "JIMMY", - }, - { - make: "GMC", - model: "K 2500", - }, - { - make: "GMC", - model: "K1500", - }, - { - make: "GMC", - model: "K3500", - }, - { - make: "GMC", - model: "RALLY", - }, - { - make: "GMC", - model: "S15", - }, - { - make: "GMC", - model: "S1500", - }, - { - make: "GMC", - model: "SAFARI", - }, - { - make: "GMC", - model: "SAVANA", - }, - { - make: "GMC", - model: "SAVANA 3500", - }, - { - make: "GMC", - model: "SIERRA", - }, - { - make: "GMC", - model: "SIERRA 1500", - }, - { - make: "GMC", - model: "SIERRA 2500", - }, - { - make: "GMC", - model: "SIERRA 2500 HD", - }, - { - make: "GMC", - model: "SIERRA 3500", - }, - { - make: "GMC", - model: "SIERRA 3500, DENALI", - }, - { - make: "GMC", - model: "SIERRA K1500", - }, - { - make: "GMC", - model: "SLX", - }, - { - make: "GMC", - model: "SONOMA", - }, - { - make: "GMC", - model: "T7500", - }, - { - make: "GMC", - model: "TERRAIN", - }, - { - make: "GMC", - model: "TOP KICK", - }, - { - make: "GMC", - model: "TOP KICK 6500", - }, - { - make: "GMC", - model: "TOP KICK C4500", - }, - { - make: "GMC", - model: "TRACKER", - }, - { - make: "GMC", - model: "UTILIMASTER CUBE VAN", - }, - { - make: "GMC", - model: "V15", - }, - { - make: "GMC", - model: "VANDURA", - }, - { - make: "GMC", - model: "VIXEN", - }, - { - make: "GMC", - model: "W4500", - }, - { - make: "GMC", - model: "W550", - }, - { - make: "GMC", - model: "W5500", - }, - { - make: "GMC", - model: "YUKON", - }, - { - make: "GOLF", - model: "SPORT WAGON", - }, - { - make: "HINO", - model: "268", - }, - { - make: "HINO", - model: "338", - }, - { - make: "HONDA", - model: "ACCORD", - }, - { - make: "HONDA", - model: "CIVIC", - }, - { - make: "HONDA", - model: "CIVIC HYBRID", - }, - { - make: "HONDA", - model: "CRV", - }, - { - make: "HONDA", - model: "CRX", - }, - { - make: "HONDA", - model: "CR-Z", - }, - { - make: "HONDA", - model: "DEL SOL", - }, - { - make: "HONDA", - model: "ELEMENT", - }, - { - make: "HONDA", - model: "FIT", - }, - { - make: "HONDA", - model: "HR V", - }, - { - make: "HONDA", - model: "INSIGHT", - }, - { - make: "HONDA", - model: "ODYSSEY", - }, - { - make: "HONDA", - model: "PASSPORT", - }, - { - make: "HONDA", - model: "PILOT", - }, - { - make: "HONDA", - model: "PRELUDE", - }, - { - make: "HONDA", - model: "RIDGELINE", - }, - { - make: "HONDA", - model: "S2000", - }, - { - make: "HUMMER", - model: "H2", - }, - { - make: "HUMMER", - model: "H3", - }, - { - make: "HYUNDAI", - model: "ACCENT", - }, - { - make: "HYUNDAI", - model: "AZERA", - }, - { - make: "HYUNDAI", - model: "ELANTRA", - }, - { - make: "HYUNDAI", - model: "ENTOURAGE", - }, - { - make: "HYUNDAI", - model: "GENESIS", - }, - { - make: "HYUNDAI", - model: "IONIQ", - }, - { - make: "HYUNDAI", - model: "KONA", - }, - { - make: "HYUNDAI", - model: "SANTA FE", - }, - { - make: "HYUNDAI", - model: "SANTA FE SPORT", - }, - { - make: "HYUNDAI", - model: "SCOUPE", - }, - { - make: "HYUNDAI", - model: "SONATA", - }, - { - make: "HYUNDAI", - model: "TIBURON", - }, - { - make: "HYUNDAI", - model: "TOURING", - }, - { - make: "HYUNDAI", - model: "TUCSON", - }, - { - make: "HYUNDAI", - model: "VELOSTER", - }, - { - make: "HYUNDAI", - model: "VENUE", - }, - { - make: "HYUNDAI", - model: "VERA CRUZ", - }, - { - make: "HYUNDAI", - model: "XG350", - }, - { - make: "HYUNDAI", - model: "XG7", - }, - { - make: "INFINITI", - model: "DURASTAR", - }, - { - make: "INFINITI", - model: "EX35", - }, - { - make: "INFINITI", - model: "FX35", - }, - { - make: "INFINITI", - model: "FX45", - }, - { - make: "INFINITI", - model: "FX50", - }, - { - make: "INFINITI", - model: "G20", - }, - { - make: "INFINITI", - model: "G35", - }, - { - make: "INFINITI", - model: "G35X", - }, - { - make: "INFINITI", - model: "G37", - }, - { - make: "INFINITI", - model: "G37X", - }, - { - make: "INFINITI", - model: "i30", - }, - { - make: "INFINITI", - model: "I35", - }, - { - make: "INFINITI", - model: "J30", - }, - { - make: "INFINITI", - model: "JX35", - }, - { - make: "INFINITI", - model: "L30", - }, - { - make: "INFINITI", - model: "Q35", - }, - { - make: "INFINITI", - model: "Q45", - }, - { - make: "INFINITI", - model: "Q50", - }, - { - make: "INFINITI", - model: "Q60", - }, - { - make: "INFINITI", - model: "QX4", - }, - { - make: "INFINITI", - model: "QX50", - }, - { - make: "INFINITI", - model: "QX56", - }, - { - make: "INFINITI", - model: "QX60", - }, - { - make: "ISUZU", - model: "BIGHORN", - }, - { - make: "ISUZU", - model: "CAB OVER", - }, - { - make: "ISUZU", - model: "HOMBRE", - }, - { - make: "ISUZU", - model: "NPR", - }, - { - make: "ISUZU", - model: "NPR - HD", - }, - { - make: "ISUZU", - model: "NRR", - }, - { - make: "ISUZU", - model: "NRR - CAB OVER", - }, - { - make: "ISUZU", - model: "RODEO", - }, - { - make: "ISUZU", - model: "TROOPER", - }, - { - make: "JAGUAR", - model: "F-PACE", - }, - { - make: "JAGUAR", - model: "GATOR 850D", - }, - { - make: "JAGUAR", - model: "IPACE", - }, - { - make: "JAGUAR", - model: "S TYPE", - }, - { - make: "JAGUAR", - model: "VANDEN PLAS", - }, - { - make: "JAGUAR", - model: "XJ1", - }, - { - make: "JAGUAR", - model: "XJ6", - }, - { - make: "JAGUAR", - model: "XJ8", - }, - { - make: "JAGUAR", - model: "XJS", - }, - { - make: "JAGUAR", - model: "XK8", - }, - { - make: "JAGUAR", - model: "X-TYPE", - }, - { - make: "JEEP", - model: "CHEROKEE", - }, - { - make: "JEEP", - model: "CJ", - }, - { - make: "JEEP", - model: "CJ7", - }, - { - make: "JEEP", - model: "COMMANCHE", - }, - { - make: "JEEP", - model: "COMMANDER", - }, - { - make: "JEEP", - model: "COMPASS", - }, - { - make: "JEEP", - model: "CP", - }, - { - make: "JEEP", - model: "GRAND CHEROKEE", - }, - { - make: "JEEP", - model: "GRAND WAGONEER", - }, - { - make: "JEEP", - model: "JK", - }, - { - make: "JEEP", - model: "LIBERTY", - }, - { - make: "JEEP", - model: "PATRIOT", - }, - { - make: "JEEP", - model: "PT", - }, - { - make: "JEEP", - model: "RENEGADE", - }, - { - make: "JEEP", - model: "RUBICON", - }, - { - make: "JEEP", - model: "TJ", - }, - { - make: "JEEP", - model: "TJ SPORT", - }, - { - make: "JEEP", - model: "WRANGLER", - }, - { - make: "JEEP", - model: "YJ", - }, - { - make: "KIA", - model: "CADENZA", - }, - { - make: "KIA", - model: "CARNIVAL", - }, - { - make: "KIA", - model: "FORTE", - }, - { - make: "KIA", - model: "FORTE 5", - }, - { - make: "KIA", - model: "MAGENTIS", - }, - { - make: "KIA", - model: "NIRO", - }, - { - make: "KIA", - model: "OPTIMA", - }, - { - make: "KIA", - model: "RIO", - }, - { - make: "KIA", - model: "RIO 5", - }, - { - make: "KIA", - model: "RONDO", - }, - { - make: "KIA", - model: "RX-V", - }, - { - make: "KIA", - model: "SEDONA", - }, - { - make: "KIA", - model: "SELTO", - }, - { - make: "KIA", - model: "SEPHIA", - }, - { - make: "KIA", - model: "SORENTO", - }, - { - make: "KIA", - model: "SOUL", - }, - { - make: "KIA", - model: "SPECTRA", - }, - { - make: "KIA", - model: "SPECTRA 5", - }, - { - make: "KIA", - model: "SPORT", - }, - { - make: "KIA", - model: "SPORTAGE", - }, - { - make: "KIA", - model: "VAN", - }, - { - make: "LAND ROVER", - model: "DISCOVERY SPORT", - }, - { - make: "LAND ROVER", - model: "LR2", - }, - { - make: "LAND ROVER", - model: "RANGE ROVER EVOQUE", - }, - { - make: "LAND ROVER", - model: "RANGE ROVER SPORT", - }, - { - make: "LANDROVER", - model: "40 SE", - }, - { - make: "LANDROVER", - model: "46HSE", - }, - { - make: "LANDROVER", - model: "DEFENDER", - }, - { - make: "LANDROVER", - model: "DISCOVERY", - }, - { - make: "LANDROVER", - model: "DISCOVERY II", - }, - { - make: "LANDROVER", - model: "FREELANDER", - }, - { - make: "LANDROVER", - model: "LR2", - }, - { - make: "LANDROVER", - model: "RANGE ROVER", - }, - { - make: "LEXUS", - model: "ES300", - }, - { - make: "LEXUS", - model: "ES350", - }, - { - make: "LEXUS", - model: "GS250", - }, - { - make: "LEXUS", - model: "GS300", - }, - { - make: "LEXUS", - model: "GS450", - }, - { - make: "LEXUS", - model: "GX470", - }, - { - make: "LEXUS", - model: "HS250", - }, - { - make: "LEXUS", - model: "IS250", - }, - { - make: "LEXUS", - model: "IS300", - }, - { - make: "LEXUS", - model: "IX 330", - }, - { - make: "LEXUS", - model: "LS400", - }, - { - make: "LEXUS", - model: "LS430", - }, - { - make: "LEXUS", - model: "LX450", - }, - { - make: "LEXUS", - model: "LX470", - }, - { - make: "LEXUS", - model: "NX200", - }, - { - make: "LEXUS", - model: "RC F", - }, - { - make: "LEXUS", - model: "RX 300", - }, - { - make: "LEXUS", - model: "RX 330", - }, - { - make: "LEXUS", - model: "RX 350", - }, - { - make: "LEXUS", - model: "RX 400H", - }, - { - make: "LEXUS", - model: "RX300", - }, - { - make: "LEXUS", - model: "RX330", - }, - { - make: "LEXUS", - model: "RX350", - }, - { - make: "LEXUS", - model: "RX400", - }, - { - make: "LEXUS", - model: "RX450H", - }, - { - make: "LEXUS", - model: "SC430", - }, - { - make: "LEXUS", - model: "SOARER", - }, - { - make: "LEXUS", - model: "UX250", - }, - { - make: "LINCOLN", - model: "AVIATOR", - }, - { - make: "LINCOLN", - model: "CONTINENTAL", - }, - { - make: "LINCOLN", - model: "LS", - }, - { - make: "LINCOLN", - model: "MARK", - }, - { - make: "LINCOLN", - model: "MARK 5", - }, - { - make: "LINCOLN", - model: "MARK LT", - }, - { - make: "LINCOLN", - model: "MKC", - }, - { - make: "LINCOLN", - model: "MKS", - }, - { - make: "LINCOLN", - model: "MKX", - }, - { - make: "LINCOLN", - model: "MKZ", - }, - { - make: "LINCOLN", - model: "NAVIGATOR", - }, - { - make: "LINCOLN", - model: "PICKUP", - }, - { - make: "LINCOLN", - model: "TOWN CAR", - }, - { - make: "LINCOLN", - model: "ZEPHER", - }, - { - make: "Make", - model: "Model", - }, - { - make: "MARK", - model: "LT", - }, - { - make: "MASERATI", - model: "GHIBLI", - }, - { - make: "MAZDA", - model: "2", - }, - { - make: "MAZDA", - model: "3", - }, - { - make: "MAZDA", - model: "3 H/B", - }, - { - make: "MAZDA", - model: "3 SPORT", - }, - { - make: "MAZDA", - model: "323", - }, - { - make: "MAZDA", - model: "3GS", - }, - { - make: "MAZDA", - model: "3GT", - }, - { - make: "MAZDA", - model: "5", - }, - { - make: "MAZDA", - model: "6", - }, - { - make: "MAZDA", - model: "626", - }, - { - make: "MAZDA", - model: "626ES", - }, - { - make: "MAZDA", - model: "929", - }, - { - make: "MAZDA", - model: "B2000", - }, - { - make: "MAZDA", - model: "B2200", - }, - { - make: "MAZDA", - model: "B2220", - }, - { - make: "MAZDA", - model: "B2300", - }, - { - make: "MAZDA", - model: "B2500", - }, - { - make: "MAZDA", - model: "B2600", - }, - { - make: "MAZDA", - model: "B2600I", - }, - { - make: "MAZDA", - model: "B3000", - }, - { - make: "MAZDA", - model: "B4000", - }, - { - make: "MAZDA", - model: "B-SERIES", - }, - { - make: "MAZDA", - model: "CX 7", - }, - { - make: "MAZDA", - model: "CX3", - }, - { - make: "MAZDA", - model: "CX-3", - }, - { - make: "MAZDA", - model: "CX5", - }, - { - make: "MAZDA", - model: "CX-5", - }, - { - make: "MAZDA", - model: "CX5 GT", - }, - { - make: "MAZDA", - model: "CX9", - }, - { - make: "MAZDA", - model: "CX-9", - }, - { - make: "MAZDA", - model: "D", - }, - { - make: "MAZDA", - model: "MAZDA 3", - }, - { - make: "MAZDA", - model: "MAZDA 5", - }, - { - make: "MAZDA", - model: "MAZDA 6", - }, - { - make: "MAZDA", - model: "MIATA", - }, - { - make: "MAZDA", - model: "MIATA GX", - }, - { - make: "MAZDA", - model: "MILLENIA", - }, - { - make: "MAZDA", - model: "MPV", - }, - { - make: "MAZDA", - model: "MR 2", - }, - { - make: "MAZDA", - model: "MX-3", - }, - { - make: "MAZDA", - model: "MX-3 PRECIDIA", - }, - { - make: "MAZDA", - model: "MX-5", - }, - { - make: "MAZDA", - model: "MX-6", - }, - { - make: "MAZDA", - model: "PRECIDIA", - }, - { - make: "MAZDA", - model: "PROTEGE", - }, - { - make: "MAZDA", - model: "PROTEGE 5", - }, - { - make: "MAZDA", - model: "RX7", - }, - { - make: "MAZDA", - model: "RX8", - }, - { - make: "MAZDA", - model: "TRIBUTE", - }, - { - make: "MERCEDES-BENZ", - model: "190E", - }, - { - make: "MERCEDES-BENZ", - model: "240D", - }, - { - make: "MERCEDES-BENZ", - model: "245 BCLASS", - }, - { - make: "MERCEDES-BENZ", - model: "300", - }, - { - make: "MERCEDES-BENZ", - model: "300SD", - }, - { - make: "MERCEDES-BENZ", - model: "300SL", - }, - { - make: "MERCEDES-BENZ", - model: "B200", - }, - { - make: "MERCEDES-BENZ", - model: "B250", - }, - { - make: "MERCEDES-BENZ", - model: "C220", - }, - { - make: "MERCEDES-BENZ", - model: "C230", - }, - { - make: "MERCEDES-BENZ", - model: "C230 KOMPRESSOR", - }, - { - make: "MERCEDES-BENZ", - model: "C240", - }, - { - make: "MERCEDES-BENZ", - model: "C300", - }, - { - make: "MERCEDES-BENZ", - model: "C300W", - }, - { - make: "MERCEDES-BENZ", - model: "C320", - }, - { - make: "MERCEDES-BENZ", - model: "C350", - }, - { - make: "MERCEDES-BENZ", - model: "C400", - }, - { - make: "MERCEDES-BENZ", - model: "C43", - }, - { - make: "MERCEDES-BENZ", - model: "C63", - }, - { - make: "MERCEDES-BENZ", - model: "C63 AMG", - }, - { - make: "MERCEDES-BENZ", - model: "CLA25", - }, - { - make: "MERCEDES-BENZ", - model: "CLK", - }, - { - make: "MERCEDES-BENZ", - model: "CLK230", - }, - { - make: "MERCEDES-BENZ", - model: "CLK320", - }, - { - make: "MERCEDES-BENZ", - model: "CLK350", - }, - { - make: "MERCEDES-BENZ", - model: "CLK500", - }, - { - make: "MERCEDES-BENZ", - model: "CLR350", - }, - { - make: "MERCEDES-BENZ", - model: "COMPRESSOR", - }, - { - make: "MERCEDES-BENZ", - model: "E300", - }, - { - make: "MERCEDES-BENZ", - model: "E320", - }, - { - make: "MERCEDES-BENZ", - model: "E400", - }, - { - make: "MERCEDES-BENZ", - model: "E420", - }, - { - make: "MERCEDES-BENZ", - model: "E500W", - }, - { - make: "MERCEDES-BENZ", - model: "E550", - }, - { - make: "MERCEDES-BENZ", - model: "GLA25", - }, - { - make: "MERCEDES-BENZ", - model: "GLC30", - }, - { - make: "MERCEDES-BENZ", - model: "GLE350", - }, - { - make: "MERCEDES-BENZ", - model: "GLK", - }, - { - make: "MERCEDES-BENZ", - model: "GLK25", - }, - { - make: "MERCEDES-BENZ", - model: "GLK250", - }, - { - make: "MERCEDES-BENZ", - model: "GLK35", - }, - { - make: "MERCEDES-BENZ", - model: "GLK35", - }, - { - make: "MERCEDES-BENZ", - model: "M1320", - }, - { - make: "MERCEDES-BENZ", - model: "METRIS", - }, - { - make: "MERCEDES-BENZ", - model: "MI320", - }, - { - make: "MERCEDES-BENZ", - model: "MI350", - }, - { - make: "MERCEDES-BENZ", - model: "MI500", - }, - { - make: "MERCEDES-BENZ", - model: "ML350", - }, - { - make: "MERCEDES-BENZ", - model: "ML400", - }, - { - make: "MERCEDES-BENZ", - model: "ML430", - }, - { - make: "MERCEDES-BENZ", - model: "ML55", - }, - { - make: "MERCEDES-BENZ", - model: "ML63", - }, - { - make: "MERCEDES-BENZ", - model: "R320", - }, - { - make: "MERCEDES-BENZ", - model: "S155", - }, - { - make: "MERCEDES-BENZ", - model: "S450V", - }, - { - make: "MERCEDES-BENZ", - model: "SL500", - }, - { - make: "MERCEDES-BENZ", - model: "SLK230", - }, - { - make: "MERCEDES-BENZ", - model: "SLK350", - }, - { - make: "MERCEDES-BENZ", - model: "SPRINTER", - }, - { - make: "MERCEDES-BENZ", - model: "SPRINTER VAN", - }, - { - make: "MERCURY", - model: "COMET", - }, - { - make: "MERCURY", - model: "COUGAR", - }, - { - make: "MERCURY", - model: "GRAND MARQUIS", - }, - { - make: "MERCURY", - model: "MARQUIS", - }, - { - make: "MERCURY", - model: "MURADER", - }, - { - make: "MERCURY", - model: "MYSTIQUE", - }, - { - make: "MERCURY", - model: "SABLE", - }, - { - make: "MERCURY", - model: "TOPAZ", - }, - { - make: "MERCURY", - model: "VILLAGER", - }, - { - make: "MINI", - model: "COOPER", - }, - { - make: "MINI", - model: "COOPER CLUBMAN", - }, - { - make: "MINI", - model: "COOPER COUNTRYMAN", - }, - { - make: "MINI", - model: "COOPER S", - }, - { - make: "MITSUBISHI", - model: "DELICA", - }, - { - make: "MITSUBISHI", - model: "ECLIPSE", - }, - { - make: "MITSUBISHI", - model: "ECLIPSE CROSS", - }, - { - make: "MITSUBISHI", - model: "ENDVR", - }, - { - make: "MITSUBISHI", - model: "GALANT", - }, - { - make: "MITSUBISHI", - model: "LANCER", - }, - { - make: "MITSUBISHI", - model: "LANCER EVOLUTION", - }, - { - make: "MITSUBISHI", - model: "MIRAGE", - }, - { - make: "MITSUBISHI", - model: "MONTERO", - }, - { - make: "MITSUBISHI", - model: "OUTLANDER", - }, - { - make: "MITSUBISHI", - model: "PJERO", - }, - { - make: "MITSUBISHI", - model: "RVR", - }, - { - make: "MITSUBISHI", - model: "SPYDER", - }, - { - make: "NISSAN", - model: "200", - }, - { - make: "NISSAN", - model: "200SX", - }, - { - make: "NISSAN", - model: "240SX", - }, - { - make: "NISSAN", - model: "240Z", - }, - { - make: "NISSAN", - model: "2500 VAN", - }, - { - make: "NISSAN", - model: "280", - }, - { - make: "NISSAN", - model: "300ZX", - }, - { - make: "NISSAN", - model: "350Z", - }, - { - make: "NISSAN", - model: "370Z", - }, - { - make: "NISSAN", - model: "ALTIMA", - }, - { - make: "NISSAN", - model: "ARMADA", - }, - { - make: "NISSAN", - model: "AXXESS", - }, - { - make: "NISSAN", - model: "CUBE", - }, - { - make: "NISSAN", - model: "D21", - }, - { - make: "NISSAN", - model: "FIGARO", - }, - { - make: "NISSAN", - model: "FRONTIER", - }, - { - make: "NISSAN", - model: "HALFTON DLX", - }, - { - make: "NISSAN", - model: "JUKE", - }, - { - make: "NISSAN", - model: "KICKS", - }, - { - make: "NISSAN", - model: "LEAF", - }, - { - make: "NISSAN", - model: "MAXIMA", - }, - { - make: "NISSAN", - model: "MICRA", - }, - { - make: "NISSAN", - model: "MURANO", - }, - { - make: "NISSAN", - model: "NV200", - }, - { - make: "NISSAN", - model: "NV250", - }, - { - make: "NISSAN", - model: "NV2500", - }, - { - make: "NISSAN", - model: "PATHFINDER", - }, - { - make: "NISSAN", - model: "PULSAR", - }, - { - make: "NISSAN", - model: "QASHQAI", - }, - { - make: "NISSAN", - model: "QSHQI", - }, - { - make: "NISSAN", - model: "QUEST", - }, - { - make: "NISSAN", - model: "ROGUE", - }, - { - make: "NISSAN", - model: "SENTRA", - }, - { - make: "NISSAN", - model: "SILVIA", - }, - { - make: "NISSAN", - model: "SKYLINE", - }, - { - make: "NISSAN", - model: "SKYLINE GTR", - }, - { - make: "NISSAN", - model: "TERRANO", - }, - { - make: "NISSAN", - model: "TITAN", - }, - { - make: "NISSAN", - model: "TRANO - PATHFINDER", - }, - { - make: "NISSAN", - model: "VERS", - }, - { - make: "NISSAN", - model: "VERSA", - }, - { - make: "NISSAN", - model: "X-TERRA", - }, - { - make: "NISSAN", - model: "X-TRAIL", - }, - { - make: "OLDSMOBILE", - model: "ACHIEVA", - }, - { - make: "OLDSMOBILE", - model: "ALERO", - }, - { - make: "OLDSMOBILE", - model: "AURORA", - }, - { - make: "OLDSMOBILE", - model: "BRAVADO", - }, - { - make: "OLDSMOBILE", - model: "CUTLASS", - }, - { - make: "OLDSMOBILE", - model: "DELTA 88", - }, - { - make: "OLDSMOBILE", - model: "DELTA 88 ROYALE BROUGHAM", - }, - { - make: "OLDSMOBILE", - model: "INTRIGUE", - }, - { - make: "OLDSMOBILE", - model: "NINETY EIGHT", - }, - { - make: "OLDSMOBILE", - model: "OMEGA", - }, - { - make: "OLDSMOBILE", - model: "REGENCY", - }, - { - make: "OLDSMOBILE", - model: "SILHOUETTE", - }, - { - make: "OLDSMOBILE", - model: "TORONADO", - }, - { - make: "PLYMOUTH", - model: "ACCLAIM", - }, - { - make: "PLYMOUTH", - model: "BARACUDA", - }, - { - make: "PLYMOUTH", - model: "BREEZE", - }, - { - make: "PLYMOUTH", - model: "CARAVAN", - }, - { - make: "PLYMOUTH", - model: "GR.VOYAGER", - }, - { - make: "PLYMOUTH", - model: "NEON", - }, - { - make: "PLYMOUTH", - model: "RELIANT", - }, - { - make: "PLYMOUTH", - model: "SUNDANCE", - }, - { - make: "PLYMOUTH", - model: "VALANT", - }, - { - make: "PLYMOUTH", - model: "VOYAGER", - }, - { - make: "POLARIS", - model: "570 CREW", - }, - { - make: "POLARIS", - model: "GENERAL 2+2", - }, - { - make: "PONTIAC", - model: "ACADIAN", - }, - { - make: "PONTIAC", - model: "AZTEC", - }, - { - make: "PONTIAC", - model: "BEAUMONT", - }, - { - make: "PONTIAC", - model: "BONNEVILLE", - }, - { - make: "PONTIAC", - model: "FIERO", - }, - { - make: "PONTIAC", - model: "FIREBIRD", - }, - { - make: "PONTIAC", - model: "FIREFLY", - }, - { - make: "PONTIAC", - model: "G3", - }, - { - make: "PONTIAC", - model: "G5", - }, - { - make: "PONTIAC", - model: "G6", - }, - { - make: "PONTIAC", - model: "G8", - }, - { - make: "PONTIAC", - model: "GRAND AM", - }, - { - make: "PONTIAC", - model: "GRAND PRIX", - }, - { - make: "PONTIAC", - model: "GTO", - }, - { - make: "PONTIAC", - model: "MONTANA", - }, - { - make: "PONTIAC", - model: "PARRISSIEN", - }, - { - make: "PONTIAC", - model: "PURSUIT", - }, - { - make: "PONTIAC", - model: "SOLSTICE", - }, - { - make: "PONTIAC", - model: "SUNBIRD", - }, - { - make: "PONTIAC", - model: "SUNFIRE", - }, - { - make: "PONTIAC", - model: "SUNRUNNER", - }, - { - make: "PONTIAC", - model: "TEMPEST", - }, - { - make: "PONTIAC", - model: "TORRENT", - }, - { - make: "PONTIAC", - model: "TRANS-AM", - }, - { - make: "PONTIAC", - model: "TRANSPORT", - }, - { - make: "PONTIAC", - model: "VIBE", - }, - { - make: "PONTIAC", - model: "WAVE", - }, - { - make: "PORSCHE", - model: "911", - }, - { - make: "PORSCHE", - model: "928", - }, - { - make: "PORSCHE", - model: "944", - }, - { - make: "PORSCHE", - model: "944 TURBO", - }, - { - make: "PORSCHE", - model: "BOXSTER", - }, - { - make: "PORSCHE", - model: "CAYENNE", - }, - { - make: "RANGE ROVER", - model: "46SHE", - }, - { - make: "RANGE ROVER", - model: "PROMA", - }, - { - make: "RANGE ROVER", - model: "RANGE", - }, - { - make: "SAAB", - model: "9", - }, - { - make: "SAAB", - model: "900S", - }, - { - make: "SAAB", - model: "9-2X", - }, - { - make: "SAAB", - model: "9-3", - }, - { - make: "SAAB", - model: "93 AREO", - }, - { - make: "SAAB", - model: "9-5", - }, - { - make: "SATURN", - model: "ASTRA", - }, - { - make: "SATURN", - model: "AURA", - }, - { - make: "SATURN", - model: "ION", - }, - { - make: "SATURN", - model: "L100", - }, - { - make: "SATURN", - model: "L200", - }, - { - make: "SATURN", - model: "LS1", - }, - { - make: "SATURN", - model: "LS2", - }, - { - make: "SATURN", - model: "OUTLOOK", - }, - { - make: "SATURN", - model: "RELAY", - }, - { - make: "SATURN", - model: "S SERIES", - }, - { - make: "SATURN", - model: "S11", - }, - { - make: "SATURN", - model: "SC1", - }, - { - make: "SATURN", - model: "SKY", - }, - { - make: "SATURN", - model: "SL1", - }, - { - make: "SATURN", - model: "SL2", - }, - { - make: "SATURN", - model: "SW1", - }, - { - make: "SATURN", - model: "SW2", - }, - { - make: "SATURN", - model: "VUE", - }, - { - make: "SATURN", - model: "WGN", - }, - { - make: "SCION", - model: "FR-S", - }, - { - make: "SCION", - model: "IM", - }, - { - make: "SCION", - model: "SXB", - }, - { - make: "SCION", - model: "TC", - }, - { - make: "SCION", - model: "XB", - }, - { - make: "SCION", - model: "XD", - }, - { - make: "SMART", - model: "FORTWO", - }, - { - make: "SUBARU", - model: "ASENT", - }, - { - make: "SUBARU", - model: "B9 TRIBECA", - }, - { - make: "SUBARU", - model: "BAJA", - }, - { - make: "SUBARU", - model: "BRAT", - }, - { - make: "SUBARU", - model: "BRZ", - }, - { - make: "SUBARU", - model: "CROSSTREK", - }, - { - make: "SUBARU", - model: "DOMINGO", - }, - { - make: "SUBARU", - model: "DOMNGO", - }, - { - make: "SUBARU", - model: "FORESTER", - }, - { - make: "SUBARU", - model: "GL", - }, - { - make: "SUBARU", - model: "IMPREZA", - }, - { - make: "SUBARU", - model: "IMPREZA STI", - }, - { - make: "SUBARU", - model: "JUSTY", - }, - { - make: "SUBARU", - model: "LEGACY", - }, - { - make: "SUBARU", - model: "LOYALE", - }, - { - make: "SUBARU", - model: "OUTBACK", - }, - { - make: "SUBARU", - model: "SVX", - }, - { - make: "SUBARU", - model: "TRIBECA", - }, - { - make: "SUBARU", - model: "WRX", - }, - { - make: "SUBARU", - model: "WRX STI", - }, - { - make: "SUBARU", - model: "XV", - }, - { - make: "SUZUKI", - model: "AERIO", - }, - { - make: "SUZUKI", - model: "CARRY", - }, - { - make: "SUZUKI", - model: "ESTEEM", - }, - { - make: "SUZUKI", - model: "GRAND VITARA", - }, - { - make: "SUZUKI", - model: "MINI-TRUCK", - }, - { - make: "SUZUKI", - model: "SAMURAI", - }, - { - make: "SUZUKI", - model: "SIDEKICK", - }, - { - make: "SUZUKI", - model: "SWIFT", - }, - { - make: "SUZUKI", - model: "SX4", - }, - { - make: "SUZUKI", - model: "VITARA", - }, - { - make: "SUZUKI", - model: "XL7", - }, - { - make: "TESLA", - model: "MODEL 3", - }, - { - make: "TOYOTA", - model: " PRIUS C", - }, - { - make: "TOYOTA", - model: "4 RUNNER", - }, - { - make: "TOYOTA", - model: "4X4", - }, - { - make: "TOYOTA", - model: "ALPHARD", - }, - { - make: "TOYOTA", - model: "ARISTO", - }, - { - make: "TOYOTA", - model: "AVALON", - }, - { - make: "TOYOTA", - model: "CALDI", - }, - { - make: "TOYOTA", - model: "CALDINA", - }, - { - make: "TOYOTA", - model: "CAMRY", - }, - { - make: "TOYOTA", - model: "CANOPY", - }, - { - make: "TOYOTA", - model: "CELICA", - }, - { - make: "TOYOTA", - model: "CELSLOR", - }, - { - make: "TOYOTA", - model: "CHASE", - }, - { - make: "TOYOTA", - model: "CH-R", - }, - { - make: "TOYOTA", - model: "COROLLA", - }, - { - make: "TOYOTA", - model: "COROLLA CE", - }, - { - make: "TOYOTA", - model: "COROLLA.HYBRID", - }, - { - make: "TOYOTA", - model: "CRESSIDA", - }, - { - make: "TOYOTA", - model: "DELICA", - }, - { - make: "TOYOTA", - model: "ECHO", - }, - { - make: "TOYOTA", - model: "FJ", - }, - { - make: "TOYOTA", - model: "HIGHLANDER", - }, - { - make: "TOYOTA", - model: "LAND CRUISER", - }, - { - make: "TOYOTA", - model: "MATRIX", - }, - { - make: "TOYOTA", - model: "MR2", - }, - { - make: "TOYOTA", - model: "PASEO", - }, - { - make: "TOYOTA", - model: "PICK UP", - }, - { - make: "TOYOTA", - model: "PRADA", - }, - { - make: "TOYOTA", - model: "PREVIA", - }, - { - make: "TOYOTA", - model: "PRIUS", - }, - { - make: "TOYOTA", - model: "PRIUS HYBIRD", - }, - { - make: "TOYOTA", - model: "PRIUS V", - }, - { - make: "TOYOTA", - model: "RAV4", - }, - { - make: "TOYOTA", - model: "SCION", - }, - { - make: "TOYOTA", - model: "SEQUOIA", - }, - { - make: "TOYOTA", - model: "SIENNA", - }, - { - make: "TOYOTA", - model: "SOLARA", - }, - { - make: "TOYOTA", - model: "SORER", - }, - { - make: "TOYOTA", - model: "SP & SP", - }, - { - make: "TOYOTA", - model: "SUPRA", - }, - { - make: "TOYOTA", - model: "SURF", - }, - { - make: "TOYOTA", - model: "TACOMA", - }, - { - make: "TOYOTA", - model: "TERCEL", - }, - { - make: "TOYOTA", - model: "TUNDRA", - }, - { - make: "TOYOTA", - model: "VENZA", - }, - { - make: "TOYOTA", - model: "YARIS", - }, - { - make: "TRIUMPH", - model: "TR3", - }, - { - make: "VOLKSWAGEN", - model: "ATLAS", - }, - { - make: "VOLKSWAGEN", - model: "BEETLE", - }, - { - make: "VOLKSWAGEN", - model: "CABRIOLET", - }, - { - make: "VOLKSWAGEN", - model: "CC", - }, - { - make: "VOLKSWAGEN", - model: "CORRADO", - }, - { - make: "VOLKSWAGEN", - model: "DUNEBUGGY", - }, - { - make: "VOLKSWAGEN", - model: "EOS", - }, - { - make: "VOLKSWAGEN", - model: "EUROVAN", - }, - { - make: "VOLKSWAGEN", - model: "GOLF", - }, - { - make: "VOLKSWAGEN", - model: "GOLF CABRIOLET", - }, - { - make: "VOLKSWAGEN", - model: "GOLF CITY", - }, - { - make: "VOLKSWAGEN", - model: "GOLF GTI", - }, - { - make: "VOLKSWAGEN", - model: "GOLF SPORT WAGEN", - }, - { - make: "VOLKSWAGEN", - model: "GTI", - }, - { - make: "VOLKSWAGEN", - model: "JETTA", - }, - { - make: "VOLKSWAGEN", - model: "JETTA TDI", - }, - { - make: "VOLKSWAGEN", - model: "KIT CAR", - }, - { - make: "VOLKSWAGEN", - model: "PASSAT", - }, - { - make: "VOLKSWAGEN", - model: "R32", - }, - { - make: "VOLKSWAGEN", - model: "RABBIT", - }, - { - make: "VOLKSWAGEN", - model: "ROUTAN", - }, - { - make: "VOLKSWAGEN", - model: "TIGUAN", - }, - { - make: "VOLKSWAGEN", - model: "TOUAREG", - }, - { - make: "VOLKSWAGEN", - model: "TRANSPORTER VAN", - }, - { - make: "VOLKSWAGEN", - model: "VANAGON", - }, - { - make: "VOLKSWAGEN", - model: "WESTFALIA", - }, - { - make: "VOLKSWAGEN", - model: "WINNEBAGO", - }, - { - make: "VOLVO", - model: "240", - }, - { - make: "VOLVO", - model: "244", - }, - { - make: "VOLVO", - model: "740", - }, - { - make: "VOLVO", - model: "760", - }, - { - make: "VOLVO", - model: "850", - }, - { - make: "VOLVO", - model: "940", - }, - { - make: "VOLVO", - model: "960", - }, - { - make: "VOLVO", - model: "C30", - }, - { - make: "VOLVO", - model: "C70", - }, - { - make: "VOLVO", - model: "DL", - }, - { - make: "VOLVO", - model: "GLE", - }, - { - make: "VOLVO", - model: "GLT", - }, - { - make: "VOLVO", - model: "GT", - }, - { - make: "VOLVO", - model: "S40", - }, - { - make: "VOLVO", - model: "S60", - }, - { - make: "VOLVO", - model: "S70", - }, - { - make: "VOLVO", - model: "S80", - }, - { - make: "VOLVO", - model: "S90", - }, - { - make: "VOLVO", - model: "V40", - }, - { - make: "VOLVO", - model: "V50", - }, - { - make: "VOLVO", - model: "V70", - }, - { - make: "VOLVO", - model: "V70 - X/C", - }, - { - make: "VOLVO", - model: "XC60", - }, - { - make: "VOLVO", - model: "XC70", - }, - { - make: "VOLVO", - model: "XC90", - }, + { + make: "ACURA", + model: "1.6 EL" + }, + { + make: "ACURA", + model: "1.7 EL" + }, + { + make: "ACURA", + model: "2.3 CL" + }, + { + make: "ACURA", + model: "2.5TL" + }, + { + make: "ACURA", + model: "3.2TL" + }, + { + make: "ACURA", + model: "3.5RL" + }, + { + make: "ACURA", + model: "CL" + }, + { + make: "ACURA", + model: "CSX" + }, + { + make: "ACURA", + model: "EL" + }, + { + make: "ACURA", + model: "GSR" + }, + { + make: "ACURA", + model: "ILX" + }, + { + make: "ACURA", + model: "INTEGRA" + }, + { + make: "ACURA", + model: "LEGEND" + }, + { + make: "ACURA", + model: "MDX" + }, + { + make: "ACURA", + model: "NSX" + }, + { + make: "ACURA", + model: "RDX" + }, + { + make: "ACURA", + model: "RSX" + }, + { + make: "ACURA", + model: "TL" + }, + { + make: "ACURA", + model: "TL-S" + }, + { + make: "ACURA", + model: "TLX" + }, + { + make: "ACURA", + model: "TSK 4" + }, + { + make: "ACURA", + model: "TSX" + }, + { + make: "ACURA", + model: "VIGOR" + }, + { + make: "AMC", + model: "GENERAL" + }, + { + make: "AMC", + model: "RAMBLER" + }, + { + make: "ATLAS", + model: "CUB" + }, + { + make: "AUDI", + model: "100" + }, + { + make: "AUDI", + model: "A2" + }, + { + make: "AUDI", + model: "A2 QUATTRO" + }, + { + make: "AUDI", + model: "A3" + }, + { + make: "AUDI", + model: "A4" + }, + { + make: "AUDI", + model: "A4 QUATTRO" + }, + { + make: "AUDI", + model: "A5" + }, + { + make: "AUDI", + model: "A6" + }, + { + make: "AUDI", + model: "Q3" + }, + { + make: "AUDI", + model: "Q5" + }, + { + make: "AUDI", + model: "Q7" + }, + { + make: "AUDI", + model: "QUATTRO" + }, + { + make: "AUDI", + model: "RS4" + }, + { + make: "AUDI", + model: "S3" + }, + { + make: "AUDI", + model: "S4" + }, + { + make: "AUDI", + model: "S5" + }, + { + make: "AUDI", + model: "S6" + }, + { + make: "AUDI", + model: "TT" + }, + { + make: "AUSTIN", + model: "HEALY" + }, + { + make: "BMW", + model: "128I" + }, + { + make: "BMW", + model: "135I" + }, + { + make: "BMW", + model: "135IS" + }, + { + make: "BMW", + model: "228I" + }, + { + make: "BMW", + model: "3 SERIES" + }, + { + make: "BMW", + model: "318" + }, + { + make: "BMW", + model: "318 TI" + }, + { + make: "BMW", + model: "318I" + }, + { + make: "BMW", + model: "318IS" + }, + { + make: "BMW", + model: "320CI" + }, + { + make: "BMW", + model: "320I" + }, + { + make: "BMW", + model: "323I" + }, + { + make: "BMW", + model: "325" + }, + { + make: "BMW", + model: "325CI" + }, + { + make: "BMW", + model: "325E" + }, + { + make: "BMW", + model: "325I" + }, + { + make: "BMW", + model: "325IS" + }, + { + make: "BMW", + model: "325XI" + }, + { + make: "BMW", + model: "328CI" + }, + { + make: "BMW", + model: "328D" + }, + { + make: "BMW", + model: "328I" + }, + { + make: "BMW", + model: "328XI" + }, + { + make: "BMW", + model: "330CI" + }, + { + make: "BMW", + model: "330I" + }, + { + make: "BMW", + model: "330XI" + }, + { + make: "BMW", + model: "335I" + }, + { + make: "BMW", + model: "335XI" + }, + { + make: "BMW", + model: "352I" + }, + { + make: "BMW", + model: "428I" + }, + { + make: "BMW", + model: "430I" + }, + { + make: "BMW", + model: "435I" + }, + { + make: "BMW", + model: "440I" + }, + { + make: "BMW", + model: "520I" + }, + { + make: "BMW", + model: "525I" + }, + { + make: "BMW", + model: "528I" + }, + { + make: "BMW", + model: "530" + }, + { + make: "BMW", + model: "530I" + }, + { + make: "BMW", + model: "530IA" + }, + { + make: "BMW", + model: "530XI" + }, + { + make: "BMW", + model: "535I" + }, + { + make: "BMW", + model: "540I" + }, + { + make: "BMW", + model: "640I" + }, + { + make: "BMW", + model: "645CI" + }, + { + make: "BMW", + model: "735" + }, + { + make: "BMW", + model: "740I" + }, + { + make: "BMW", + model: "745LI" + }, + { + make: "BMW", + model: "750I" + }, + { + make: "BMW", + model: "750LI" + }, + { + make: "BMW", + model: "K-1200" + }, + { + make: "BMW", + model: "M3" + }, + { + make: "BMW", + model: "M5" + }, + { + make: "BMW", + model: "X1" + }, + { + make: "BMW", + model: "X3" + }, + { + make: "BMW", + model: "X5" + }, + { + make: "BMW", + model: "X6" + }, + { + make: "BMW", + model: "Z3" + }, + { + make: "BMW", + model: "Z4" + }, + { + make: "BUICK", + model: "ALLURE" + }, + { + make: "BUICK", + model: "CENTURY" + }, + { + make: "BUICK", + model: "ENCLAVE" + }, + { + make: "BUICK", + model: "ENCORE" + }, + { + make: "BUICK", + model: "ENVISION" + }, + { + make: "BUICK", + model: "INVICTA" + }, + { + make: "BUICK", + model: "LACROSSE" + }, + { + make: "BUICK", + model: "LESABRE" + }, + { + make: "BUICK", + model: "LUCERNE" + }, + { + make: "BUICK", + model: "PARK AVENUE" + }, + { + make: "BUICK", + model: "REGAL" + }, + { + make: "BUICK", + model: "RENDEZVOUS" + }, + { + make: "BUICK", + model: "RIVIERA" + }, + { + make: "BUICK", + model: "ROADMASTER" + }, + { + make: "BUICK", + model: "SKYLARK" + }, + { + make: "BUICK", + model: "SOMERSET" + }, + { + make: "BUICK", + model: "SPECIAL" + }, + { + make: "BUICK", + model: "TERRAZA" + }, + { + make: "BUICK", + model: "VERANO" + }, + { + make: "CADILLAC", + model: "ATS" + }, + { + make: "CADILLAC", + model: "CATERA" + }, + { + make: "CADILLAC", + model: "CTS" + }, + { + make: "CADILLAC", + model: "DE VILLE" + }, + { + make: "CADILLAC", + model: "DTS" + }, + { + make: "CADILLAC", + model: "ELDORADO" + }, + { + make: "CADILLAC", + model: "ESCLADE" + }, + { + make: "CADILLAC", + model: "FLEETWOOD" + }, + { + make: "CADILLAC", + model: "SEVILLE" + }, + { + make: "CADILLAC", + model: "SRX" + }, + { + make: "CADILLAC", + model: "STS" + }, + { + make: "CADILLAC", + model: "XLR" + }, + { + make: "CHEVROLET", + model: "1500" + }, + { + make: "CHEVROLET", + model: "2500" + }, + { + make: "CHEVROLET", + model: "2500HD" + }, + { + make: "CHEVROLET", + model: "2WHDR" + }, + { + make: "CHEVROLET", + model: "3500 EXPRESS VAN" + }, + { + make: "CHEVROLET", + model: "3500 HD" + }, + { + make: "CHEVROLET", + model: "ALER" + }, + { + make: "CHEVROLET", + model: "ASTRO" + }, + { + make: "CHEVROLET", + model: "AVALANCHE" + }, + { + make: "CHEVROLET", + model: "AVEO" + }, + { + make: "CHEVROLET", + model: "BEL AIR" + }, + { + make: "CHEVROLET", + model: "BERETTA" + }, + { + make: "CHEVROLET", + model: "BLAZER" + }, + { + make: "CHEVROLET", + model: "BOLT" + }, + { + make: "CHEVROLET", + model: "BOLT EV" + }, + { + make: "CHEVROLET", + model: "C10" + }, + { + make: "CHEVROLET", + model: "C20" + }, + { + make: "CHEVROLET", + model: "C2500" + }, + { + make: "CHEVROLET", + model: "C3500" + }, + { + make: "CHEVROLET", + model: "CAMARO" + }, + { + make: "CHEVROLET", + model: "CAPRICE" + }, + { + make: "CHEVROLET", + model: "CAVALIER" + }, + { + make: "CHEVROLET", + model: "CHEVELLE" + }, + { + make: "CHEVROLET", + model: "CITY EXPRESS" + }, + { + make: "CHEVROLET", + model: "COBALT" + }, + { + make: "CHEVROLET", + model: "COLORADO" + }, + { + make: "CHEVROLET", + model: "CORSICA" + }, + { + make: "CHEVROLET", + model: "CORVETTE" + }, + { + make: "CHEVROLET", + model: "CRUZE" + }, + { + make: "CHEVROLET", + model: "EL CAMINO" + }, + { + make: "CHEVROLET", + model: "EPICA" + }, + { + make: "CHEVROLET", + model: "EQUINOX" + }, + { + make: "CHEVROLET", + model: "EXPRESS" + }, + { + make: "CHEVROLET", + model: "EXPRESS 1500" + }, + { + make: "CHEVROLET", + model: "EXPRESS 2500" + }, + { + make: "CHEVROLET", + model: "EXPRESS 3500" + }, + { + make: "CHEVROLET", + model: "EXPRESS VAN" + }, + { + make: "CHEVROLET", + model: "G1500" + }, + { + make: "CHEVROLET", + model: "G2500" + }, + { + make: "CHEVROLET", + model: "G30" + }, + { + make: "CHEVROLET", + model: "G3500" + }, + { + make: "CHEVROLET", + model: "G4500" + }, + { + make: "CHEVROLET", + model: "GMT-400" + }, + { + make: "CHEVROLET", + model: "HD 2500" + }, + { + make: "CHEVROLET", + model: "HHR" + }, + { + make: "CHEVROLET", + model: "IMPALA" + }, + { + make: "CHEVROLET", + model: "K1500" + }, + { + make: "CHEVROLET", + model: "K2500" + }, + { + make: "CHEVROLET", + model: "K3500" + }, + { + make: "CHEVROLET", + model: "K5500" + }, + { + make: "CHEVROLET", + model: "LUMINA" + }, + { + make: "CHEVROLET", + model: "MALIBU" + }, + { + make: "CHEVROLET", + model: "MALIBU HYBRID" + }, + { + make: "CHEVROLET", + model: "MONTE CARLO" + }, + { + make: "CHEVROLET", + model: "NOMAD" + }, + { + make: "CHEVROLET", + model: "NOVA" + }, + { + make: "CHEVROLET", + model: "OPTRA" + }, + { + make: "CHEVROLET", + model: "ORLANDO" + }, + { + make: "CHEVROLET", + model: "P30" + }, + { + make: "CHEVROLET", + model: "S10" + }, + { + make: "CHEVROLET", + model: "S10 BLAZER" + }, + { + make: "CHEVROLET", + model: "SAVANNA" + }, + { + make: "CHEVROLET", + model: "SILVERADO" + }, + { + make: "CHEVROLET", + model: "SILVERADO 2500 HD" + }, + { + make: "CHEVROLET", + model: "SILVERADO 3500" + }, + { + make: "CHEVROLET", + model: "SILVERADO 3500 HD" + }, + { + make: "CHEVROLET", + model: "SONIC" + }, + { + make: "CHEVROLET", + model: "SPARK" + }, + { + make: "CHEVROLET", + model: "SPORT VAN G20" + }, + { + make: "CHEVROLET", + model: "SPRINT" + }, + { + make: "CHEVROLET", + model: "SUBURBAN" + }, + { + make: "CHEVROLET", + model: "TAHOE" + }, + { + make: "CHEVROLET", + model: "TRACKER" + }, + { + make: "CHEVROLET", + model: "TRAILBLAZER" + }, + { + make: "CHEVROLET", + model: "TRAVERSE" + }, + { + make: "CHEVROLET", + model: "TRAX" + }, + { + make: "CHEVROLET", + model: "TREX" + }, + { + make: "CHEVROLET", + model: "UPLANDER" + }, + { + make: "CHEVROLET", + model: "VENTURE" + }, + { + make: "CHEVROLET", + model: "VOLT" + }, + { + make: "CHEVROLET", + model: "Z24" + }, + { + make: "CHEVROLET", + model: "Z28 IROC" + }, + { + make: "CHRYSLER", + model: "200" + }, + { + make: "CHRYSLER", + model: "300" + }, + { + make: "CHRYSLER", + model: "300C" + }, + { + make: "CHRYSLER", + model: "300M" + }, + { + make: "CHRYSLER", + model: "5TH AVE" + }, + { + make: "CHRYSLER", + model: "ASPEN" + }, + { + make: "CHRYSLER", + model: "CIRRUS" + }, + { + make: "CHRYSLER", + model: "CONCORDE" + }, + { + make: "CHRYSLER", + model: "CROSSFIRE" + }, + { + make: "CHRYSLER", + model: "DAYTONA" + }, + { + make: "CHRYSLER", + model: "E CLASS" + }, + { + make: "CHRYSLER", + model: "INTREPID" + }, + { + make: "CHRYSLER", + model: "LEBARON" + }, + { + make: "CHRYSLER", + model: "LHS" + }, + { + make: "CHRYSLER", + model: "NEON" + }, + { + make: "CHRYSLER", + model: "NEW YORKER" + }, + { + make: "CHRYSLER", + model: "PACIFICA" + }, + { + make: "CHRYSLER", + model: "PT CRUISER" + }, + { + make: "CHRYSLER", + model: "SEBRING" + }, + { + make: "CHRYSLER", + model: "TOWN & COUNTRY" + }, + { + make: "DATSUN", + model: "PICK-UP" + }, + { + make: "DODGE", + model: "1500" + }, + { + make: "DODGE", + model: "250" + }, + { + make: "DODGE", + model: "2500" + }, + { + make: "DODGE", + model: "4500" + }, + { + make: "DODGE", + model: "5500" + }, + { + make: "DODGE", + model: "ARIES" + }, + { + make: "DODGE", + model: "AVENGER" + }, + { + make: "DODGE", + model: "B1500" + }, + { + make: "DODGE", + model: "B200" + }, + { + make: "DODGE", + model: "B250" + }, + { + make: "DODGE", + model: "B350" + }, + { + make: "DODGE", + model: "CALIBER" + }, + { + make: "DODGE", + model: "CAMPERVAN" + }, + { + make: "DODGE", + model: "CARAVAN" + }, + { + make: "DODGE", + model: "CHALLENGER" + }, + { + make: "DODGE", + model: "CHARGER" + }, + { + make: "DODGE", + model: "COLT" + }, + { + make: "DODGE", + model: "CORNET" + }, + { + make: "DODGE", + model: "CV VAN" + }, + { + make: "DODGE", + model: "D100" + }, + { + make: "DODGE", + model: "D200" + }, + { + make: "DODGE", + model: "D250" + }, + { + make: "DODGE", + model: "D300" + }, + { + make: "DODGE", + model: "D350" + }, + { + make: "DODGE", + model: "DAKOTA" + }, + { + make: "DODGE", + model: "DART" + }, + { + make: "DODGE", + model: "DEMON" + }, + { + make: "DODGE", + model: "DURANGO" + }, + { + make: "DODGE", + model: "DYNASTY" + }, + { + make: "DODGE", + model: "GRAND CARAVAN" + }, + { + make: "DODGE", + model: "GRAND CARAVEN SE" + }, + { + make: "DODGE", + model: "JOURNEY" + }, + { + make: "DODGE", + model: "MAGNUM" + }, + { + make: "DODGE", + model: "MOTORHOME" + }, + { + make: "DODGE", + model: "NEON" + }, + { + make: "DODGE", + model: "NITRO" + }, + { + make: "DODGE", + model: "PRO MASTER" + }, + { + make: "DODGE", + model: "PRO MASTER 3500" + }, + { + make: "DODGE", + model: "RAM" + }, + { + make: "DODGE", + model: "RAM 1500" + }, + { + make: "DODGE", + model: "RAM 250" + }, + { + make: "DODGE", + model: "RAM 2500" + }, + { + make: "DODGE", + model: "RAM 3500" + }, + { + make: "DODGE", + model: "RAM 5500" + }, + { + make: "DODGE", + model: "RAM PRO MASTER" + }, + { + make: "DODGE", + model: "RAM SRT10" + }, + { + make: "DODGE", + model: "RAM VAN" + }, + { + make: "DODGE", + model: "RAMPAGE" + }, + { + make: "DODGE", + model: "SHADOW" + }, + { + make: "DODGE", + model: "SPIRIT" + }, + { + make: "DODGE", + model: "SPRINTER 3500" + }, + { + make: "DODGE", + model: "SRT" + }, + { + make: "DODGE", + model: "STEALTH" + }, + { + make: "DODGE", + model: "STRATUS" + }, + { + make: "DODGE", + model: "SWINGER" + }, + { + make: "DODGE", + model: "SX 2.0" + }, + { + make: "DODGE", + model: "W200" + }, + { + make: "DODGE", + model: "W350" + }, + { + make: "EAGLE", + model: "SUMMIT" + }, + { + make: "EAGLE", + model: "TALON" + }, + { + make: "EAGLE", + model: "VISION" + }, + { + make: "FIAT", + model: "500C" + }, + { + make: "FIAT", + model: "500L" + }, + { + make: "FORD", + model: "500" + }, + { + make: "FORD", + model: "AEROSTAR" + }, + { + make: "FORD", + model: "ASPIRE" + }, + { + make: "FORD", + model: "BRONCO" + }, + { + make: "FORD", + model: "CMAX" + }, + { + make: "FORD", + model: "CONTOUR" + }, + { + make: "FORD", + model: "CROWN VIC" + }, + { + make: "FORD", + model: "CUBE VAN" + }, + { + make: "FORD", + model: "DUMP TRUCK" + }, + { + make: "FORD", + model: "E150" + }, + { + make: "FORD", + model: "E250" + }, + { + make: "FORD", + model: "E350" + }, + { + make: "FORD", + model: "E450" + }, + { + make: "FORD", + model: "ECO SPORT" + }, + { + make: "FORD", + model: "ECONOLINE" + }, + { + make: "FORD", + model: "EDGE" + }, + { + make: "FORD", + model: "ESCAPE" + }, + { + make: "FORD", + model: "ESCORT" + }, + { + make: "FORD", + model: "EXCURSION" + }, + { + make: "FORD", + model: "EXPEDITION" + }, + { + make: "FORD", + model: "EXPLORER" + }, + { + make: "FORD", + model: "EXPLORER SPORT" + }, + { + make: "FORD", + model: "EXPLORER SPORT TRAC" + }, + { + make: "FORD", + model: "F-150" + }, + { + make: "FORD", + model: "F-250" + }, + { + make: "FORD", + model: "F-350" + }, + { + make: "FORD", + model: "F-450" + }, + { + make: "FORD", + model: "F-550" + }, + { + make: "FORD", + model: "FAIRLANE" + }, + { + make: "FORD", + model: "FESTIVA" + }, + { + make: "FORD", + model: "FIESTA" + }, + { + make: "FORD", + model: "FLAT DECK" + }, + { + make: "FORD", + model: "FLEX" + }, + { + make: "FORD", + model: "FOCUS" + }, + { + make: "FORD", + model: "FREESTAR" + }, + { + make: "FORD", + model: "FREESTYLE" + }, + { + make: "FORD", + model: "FUSION" + }, + { + make: "FORD", + model: "GALAXIE 500" + }, + { + make: "FORD", + model: "LARIAT" + }, + { + make: "FORD", + model: "LCF" + }, + { + make: "FORD", + model: "LGT" + }, + { + make: "FORD", + model: "MOTORHOME" + }, + { + make: "FORD", + model: "MUSTANG" + }, + { + make: "FORD", + model: "PLEASURE WAY" + }, + { + make: "FORD", + model: "PROBE" + }, + { + make: "FORD", + model: "RANGER" + }, + { + make: "FORD", + model: "RAPTOR" + }, + { + make: "FORD", + model: "SPORT TRAC" + }, + { + make: "FORD", + model: "SUPER DUTY" + }, + { + make: "FORD", + model: "TAURUS" + }, + { + make: "FORD", + model: "T-BIRD" + }, + { + make: "FORD", + model: "T-BUCKET" + }, + { + make: "FORD", + model: "TEMPO" + }, + { + make: "FORD", + model: "THUNDERBIRD" + }, + { + make: "FORD", + model: "TRANSIT" + }, + { + make: "FORD", + model: "TRANSIT 250" + }, + { + make: "FORD", + model: "TRANSIT 350HD" + }, + { + make: "FORD", + model: "TRANSIT CONNECT" + }, + { + make: "FORD", + model: "TRANSIT COONECT" + }, + { + make: "FORD", + model: "WINDSTAR" + }, + { + make: "FREIGHTLINER", + model: "FL70" + }, + { + make: "FREIGHTLINER", + model: "M2" + }, + { + make: "GEO", + model: "METRO" + }, + { + make: "GEO", + model: "STORM" + }, + { + make: "GEO", + model: "TRACKER" + }, + { + make: "GMC", + model: "1500" + }, + { + make: "GMC", + model: "2500 HD" + }, + { + make: "GMC", + model: "3500" + }, + { + make: "GMC", + model: "4000" + }, + { + make: "GMC", + model: "4500HD" + }, + { + make: "GMC", + model: "ACADIA" + }, + { + make: "GMC", + model: "C1500" + }, + { + make: "GMC", + model: "C2500" + }, + { + make: "GMC", + model: "C4500" + }, + { + make: "GMC", + model: "CANYON" + }, + { + make: "GMC", + model: "CARGO" + }, + { + make: "GMC", + model: "DENALI HD" + }, + { + make: "GMC", + model: "ENVOY" + }, + { + make: "GMC", + model: "G2500 VANDURA" + }, + { + make: "GMC", + model: "G35 SAVANNA" + }, + { + make: "GMC", + model: "GEO" + }, + { + make: "GMC", + model: "JIMMY" + }, + { + make: "GMC", + model: "K 2500" + }, + { + make: "GMC", + model: "K1500" + }, + { + make: "GMC", + model: "K3500" + }, + { + make: "GMC", + model: "RALLY" + }, + { + make: "GMC", + model: "S15" + }, + { + make: "GMC", + model: "S1500" + }, + { + make: "GMC", + model: "SAFARI" + }, + { + make: "GMC", + model: "SAVANA" + }, + { + make: "GMC", + model: "SAVANA 3500" + }, + { + make: "GMC", + model: "SIERRA" + }, + { + make: "GMC", + model: "SIERRA 1500" + }, + { + make: "GMC", + model: "SIERRA 2500" + }, + { + make: "GMC", + model: "SIERRA 2500 HD" + }, + { + make: "GMC", + model: "SIERRA 3500" + }, + { + make: "GMC", + model: "SIERRA 3500, DENALI" + }, + { + make: "GMC", + model: "SIERRA K1500" + }, + { + make: "GMC", + model: "SLX" + }, + { + make: "GMC", + model: "SONOMA" + }, + { + make: "GMC", + model: "T7500" + }, + { + make: "GMC", + model: "TERRAIN" + }, + { + make: "GMC", + model: "TOP KICK" + }, + { + make: "GMC", + model: "TOP KICK 6500" + }, + { + make: "GMC", + model: "TOP KICK C4500" + }, + { + make: "GMC", + model: "TRACKER" + }, + { + make: "GMC", + model: "UTILIMASTER CUBE VAN" + }, + { + make: "GMC", + model: "V15" + }, + { + make: "GMC", + model: "VANDURA" + }, + { + make: "GMC", + model: "VIXEN" + }, + { + make: "GMC", + model: "W4500" + }, + { + make: "GMC", + model: "W550" + }, + { + make: "GMC", + model: "W5500" + }, + { + make: "GMC", + model: "YUKON" + }, + { + make: "GOLF", + model: "SPORT WAGON" + }, + { + make: "HINO", + model: "268" + }, + { + make: "HINO", + model: "338" + }, + { + make: "HONDA", + model: "ACCORD" + }, + { + make: "HONDA", + model: "CIVIC" + }, + { + make: "HONDA", + model: "CIVIC HYBRID" + }, + { + make: "HONDA", + model: "CRV" + }, + { + make: "HONDA", + model: "CRX" + }, + { + make: "HONDA", + model: "CR-Z" + }, + { + make: "HONDA", + model: "DEL SOL" + }, + { + make: "HONDA", + model: "ELEMENT" + }, + { + make: "HONDA", + model: "FIT" + }, + { + make: "HONDA", + model: "HR V" + }, + { + make: "HONDA", + model: "INSIGHT" + }, + { + make: "HONDA", + model: "ODYSSEY" + }, + { + make: "HONDA", + model: "PASSPORT" + }, + { + make: "HONDA", + model: "PILOT" + }, + { + make: "HONDA", + model: "PRELUDE" + }, + { + make: "HONDA", + model: "RIDGELINE" + }, + { + make: "HONDA", + model: "S2000" + }, + { + make: "HUMMER", + model: "H2" + }, + { + make: "HUMMER", + model: "H3" + }, + { + make: "HYUNDAI", + model: "ACCENT" + }, + { + make: "HYUNDAI", + model: "AZERA" + }, + { + make: "HYUNDAI", + model: "ELANTRA" + }, + { + make: "HYUNDAI", + model: "ENTOURAGE" + }, + { + make: "HYUNDAI", + model: "GENESIS" + }, + { + make: "HYUNDAI", + model: "IONIQ" + }, + { + make: "HYUNDAI", + model: "KONA" + }, + { + make: "HYUNDAI", + model: "SANTA FE" + }, + { + make: "HYUNDAI", + model: "SANTA FE SPORT" + }, + { + make: "HYUNDAI", + model: "SCOUPE" + }, + { + make: "HYUNDAI", + model: "SONATA" + }, + { + make: "HYUNDAI", + model: "TIBURON" + }, + { + make: "HYUNDAI", + model: "TOURING" + }, + { + make: "HYUNDAI", + model: "TUCSON" + }, + { + make: "HYUNDAI", + model: "VELOSTER" + }, + { + make: "HYUNDAI", + model: "VENUE" + }, + { + make: "HYUNDAI", + model: "VERA CRUZ" + }, + { + make: "HYUNDAI", + model: "XG350" + }, + { + make: "HYUNDAI", + model: "XG7" + }, + { + make: "INFINITI", + model: "DURASTAR" + }, + { + make: "INFINITI", + model: "EX35" + }, + { + make: "INFINITI", + model: "FX35" + }, + { + make: "INFINITI", + model: "FX45" + }, + { + make: "INFINITI", + model: "FX50" + }, + { + make: "INFINITI", + model: "G20" + }, + { + make: "INFINITI", + model: "G35" + }, + { + make: "INFINITI", + model: "G35X" + }, + { + make: "INFINITI", + model: "G37" + }, + { + make: "INFINITI", + model: "G37X" + }, + { + make: "INFINITI", + model: "i30" + }, + { + make: "INFINITI", + model: "I35" + }, + { + make: "INFINITI", + model: "J30" + }, + { + make: "INFINITI", + model: "JX35" + }, + { + make: "INFINITI", + model: "L30" + }, + { + make: "INFINITI", + model: "Q35" + }, + { + make: "INFINITI", + model: "Q45" + }, + { + make: "INFINITI", + model: "Q50" + }, + { + make: "INFINITI", + model: "Q60" + }, + { + make: "INFINITI", + model: "QX4" + }, + { + make: "INFINITI", + model: "QX50" + }, + { + make: "INFINITI", + model: "QX56" + }, + { + make: "INFINITI", + model: "QX60" + }, + { + make: "ISUZU", + model: "BIGHORN" + }, + { + make: "ISUZU", + model: "CAB OVER" + }, + { + make: "ISUZU", + model: "HOMBRE" + }, + { + make: "ISUZU", + model: "NPR" + }, + { + make: "ISUZU", + model: "NPR - HD" + }, + { + make: "ISUZU", + model: "NRR" + }, + { + make: "ISUZU", + model: "NRR - CAB OVER" + }, + { + make: "ISUZU", + model: "RODEO" + }, + { + make: "ISUZU", + model: "TROOPER" + }, + { + make: "JAGUAR", + model: "F-PACE" + }, + { + make: "JAGUAR", + model: "GATOR 850D" + }, + { + make: "JAGUAR", + model: "IPACE" + }, + { + make: "JAGUAR", + model: "S TYPE" + }, + { + make: "JAGUAR", + model: "VANDEN PLAS" + }, + { + make: "JAGUAR", + model: "XJ1" + }, + { + make: "JAGUAR", + model: "XJ6" + }, + { + make: "JAGUAR", + model: "XJ8" + }, + { + make: "JAGUAR", + model: "XJS" + }, + { + make: "JAGUAR", + model: "XK8" + }, + { + make: "JAGUAR", + model: "X-TYPE" + }, + { + make: "JEEP", + model: "CHEROKEE" + }, + { + make: "JEEP", + model: "CJ" + }, + { + make: "JEEP", + model: "CJ7" + }, + { + make: "JEEP", + model: "COMMANCHE" + }, + { + make: "JEEP", + model: "COMMANDER" + }, + { + make: "JEEP", + model: "COMPASS" + }, + { + make: "JEEP", + model: "CP" + }, + { + make: "JEEP", + model: "GRAND CHEROKEE" + }, + { + make: "JEEP", + model: "GRAND WAGONEER" + }, + { + make: "JEEP", + model: "JK" + }, + { + make: "JEEP", + model: "LIBERTY" + }, + { + make: "JEEP", + model: "PATRIOT" + }, + { + make: "JEEP", + model: "PT" + }, + { + make: "JEEP", + model: "RENEGADE" + }, + { + make: "JEEP", + model: "RUBICON" + }, + { + make: "JEEP", + model: "TJ" + }, + { + make: "JEEP", + model: "TJ SPORT" + }, + { + make: "JEEP", + model: "WRANGLER" + }, + { + make: "JEEP", + model: "YJ" + }, + { + make: "KIA", + model: "CADENZA" + }, + { + make: "KIA", + model: "CARNIVAL" + }, + { + make: "KIA", + model: "FORTE" + }, + { + make: "KIA", + model: "FORTE 5" + }, + { + make: "KIA", + model: "MAGENTIS" + }, + { + make: "KIA", + model: "NIRO" + }, + { + make: "KIA", + model: "OPTIMA" + }, + { + make: "KIA", + model: "RIO" + }, + { + make: "KIA", + model: "RIO 5" + }, + { + make: "KIA", + model: "RONDO" + }, + { + make: "KIA", + model: "RX-V" + }, + { + make: "KIA", + model: "SEDONA" + }, + { + make: "KIA", + model: "SELTO" + }, + { + make: "KIA", + model: "SEPHIA" + }, + { + make: "KIA", + model: "SORENTO" + }, + { + make: "KIA", + model: "SOUL" + }, + { + make: "KIA", + model: "SPECTRA" + }, + { + make: "KIA", + model: "SPECTRA 5" + }, + { + make: "KIA", + model: "SPORT" + }, + { + make: "KIA", + model: "SPORTAGE" + }, + { + make: "KIA", + model: "VAN" + }, + { + make: "LAND ROVER", + model: "DISCOVERY SPORT" + }, + { + make: "LAND ROVER", + model: "LR2" + }, + { + make: "LAND ROVER", + model: "RANGE ROVER EVOQUE" + }, + { + make: "LAND ROVER", + model: "RANGE ROVER SPORT" + }, + { + make: "LANDROVER", + model: "40 SE" + }, + { + make: "LANDROVER", + model: "46HSE" + }, + { + make: "LANDROVER", + model: "DEFENDER" + }, + { + make: "LANDROVER", + model: "DISCOVERY" + }, + { + make: "LANDROVER", + model: "DISCOVERY II" + }, + { + make: "LANDROVER", + model: "FREELANDER" + }, + { + make: "LANDROVER", + model: "LR2" + }, + { + make: "LANDROVER", + model: "RANGE ROVER" + }, + { + make: "LEXUS", + model: "ES300" + }, + { + make: "LEXUS", + model: "ES350" + }, + { + make: "LEXUS", + model: "GS250" + }, + { + make: "LEXUS", + model: "GS300" + }, + { + make: "LEXUS", + model: "GS450" + }, + { + make: "LEXUS", + model: "GX470" + }, + { + make: "LEXUS", + model: "HS250" + }, + { + make: "LEXUS", + model: "IS250" + }, + { + make: "LEXUS", + model: "IS300" + }, + { + make: "LEXUS", + model: "IX 330" + }, + { + make: "LEXUS", + model: "LS400" + }, + { + make: "LEXUS", + model: "LS430" + }, + { + make: "LEXUS", + model: "LX450" + }, + { + make: "LEXUS", + model: "LX470" + }, + { + make: "LEXUS", + model: "NX200" + }, + { + make: "LEXUS", + model: "RC F" + }, + { + make: "LEXUS", + model: "RX 300" + }, + { + make: "LEXUS", + model: "RX 330" + }, + { + make: "LEXUS", + model: "RX 350" + }, + { + make: "LEXUS", + model: "RX 400H" + }, + { + make: "LEXUS", + model: "RX300" + }, + { + make: "LEXUS", + model: "RX330" + }, + { + make: "LEXUS", + model: "RX350" + }, + { + make: "LEXUS", + model: "RX400" + }, + { + make: "LEXUS", + model: "RX450H" + }, + { + make: "LEXUS", + model: "SC430" + }, + { + make: "LEXUS", + model: "SOARER" + }, + { + make: "LEXUS", + model: "UX250" + }, + { + make: "LINCOLN", + model: "AVIATOR" + }, + { + make: "LINCOLN", + model: "CONTINENTAL" + }, + { + make: "LINCOLN", + model: "LS" + }, + { + make: "LINCOLN", + model: "MARK" + }, + { + make: "LINCOLN", + model: "MARK 5" + }, + { + make: "LINCOLN", + model: "MARK LT" + }, + { + make: "LINCOLN", + model: "MKC" + }, + { + make: "LINCOLN", + model: "MKS" + }, + { + make: "LINCOLN", + model: "MKX" + }, + { + make: "LINCOLN", + model: "MKZ" + }, + { + make: "LINCOLN", + model: "NAVIGATOR" + }, + { + make: "LINCOLN", + model: "PICKUP" + }, + { + make: "LINCOLN", + model: "TOWN CAR" + }, + { + make: "LINCOLN", + model: "ZEPHER" + }, + { + make: "Make", + model: "Model" + }, + { + make: "MARK", + model: "LT" + }, + { + make: "MASERATI", + model: "GHIBLI" + }, + { + make: "MAZDA", + model: "2" + }, + { + make: "MAZDA", + model: "3" + }, + { + make: "MAZDA", + model: "3 H/B" + }, + { + make: "MAZDA", + model: "3 SPORT" + }, + { + make: "MAZDA", + model: "323" + }, + { + make: "MAZDA", + model: "3GS" + }, + { + make: "MAZDA", + model: "3GT" + }, + { + make: "MAZDA", + model: "5" + }, + { + make: "MAZDA", + model: "6" + }, + { + make: "MAZDA", + model: "626" + }, + { + make: "MAZDA", + model: "626ES" + }, + { + make: "MAZDA", + model: "929" + }, + { + make: "MAZDA", + model: "B2000" + }, + { + make: "MAZDA", + model: "B2200" + }, + { + make: "MAZDA", + model: "B2220" + }, + { + make: "MAZDA", + model: "B2300" + }, + { + make: "MAZDA", + model: "B2500" + }, + { + make: "MAZDA", + model: "B2600" + }, + { + make: "MAZDA", + model: "B2600I" + }, + { + make: "MAZDA", + model: "B3000" + }, + { + make: "MAZDA", + model: "B4000" + }, + { + make: "MAZDA", + model: "B-SERIES" + }, + { + make: "MAZDA", + model: "CX 7" + }, + { + make: "MAZDA", + model: "CX3" + }, + { + make: "MAZDA", + model: "CX-3" + }, + { + make: "MAZDA", + model: "CX5" + }, + { + make: "MAZDA", + model: "CX-5" + }, + { + make: "MAZDA", + model: "CX5 GT" + }, + { + make: "MAZDA", + model: "CX9" + }, + { + make: "MAZDA", + model: "CX-9" + }, + { + make: "MAZDA", + model: "D" + }, + { + make: "MAZDA", + model: "MAZDA 3" + }, + { + make: "MAZDA", + model: "MAZDA 5" + }, + { + make: "MAZDA", + model: "MAZDA 6" + }, + { + make: "MAZDA", + model: "MIATA" + }, + { + make: "MAZDA", + model: "MIATA GX" + }, + { + make: "MAZDA", + model: "MILLENIA" + }, + { + make: "MAZDA", + model: "MPV" + }, + { + make: "MAZDA", + model: "MR 2" + }, + { + make: "MAZDA", + model: "MX-3" + }, + { + make: "MAZDA", + model: "MX-3 PRECIDIA" + }, + { + make: "MAZDA", + model: "MX-5" + }, + { + make: "MAZDA", + model: "MX-6" + }, + { + make: "MAZDA", + model: "PRECIDIA" + }, + { + make: "MAZDA", + model: "PROTEGE" + }, + { + make: "MAZDA", + model: "PROTEGE 5" + }, + { + make: "MAZDA", + model: "RX7" + }, + { + make: "MAZDA", + model: "RX8" + }, + { + make: "MAZDA", + model: "TRIBUTE" + }, + { + make: "MERCEDES-BENZ", + model: "190E" + }, + { + make: "MERCEDES-BENZ", + model: "240D" + }, + { + make: "MERCEDES-BENZ", + model: "245 BCLASS" + }, + { + make: "MERCEDES-BENZ", + model: "300" + }, + { + make: "MERCEDES-BENZ", + model: "300SD" + }, + { + make: "MERCEDES-BENZ", + model: "300SL" + }, + { + make: "MERCEDES-BENZ", + model: "B200" + }, + { + make: "MERCEDES-BENZ", + model: "B250" + }, + { + make: "MERCEDES-BENZ", + model: "C220" + }, + { + make: "MERCEDES-BENZ", + model: "C230" + }, + { + make: "MERCEDES-BENZ", + model: "C230 KOMPRESSOR" + }, + { + make: "MERCEDES-BENZ", + model: "C240" + }, + { + make: "MERCEDES-BENZ", + model: "C300" + }, + { + make: "MERCEDES-BENZ", + model: "C300W" + }, + { + make: "MERCEDES-BENZ", + model: "C320" + }, + { + make: "MERCEDES-BENZ", + model: "C350" + }, + { + make: "MERCEDES-BENZ", + model: "C400" + }, + { + make: "MERCEDES-BENZ", + model: "C43" + }, + { + make: "MERCEDES-BENZ", + model: "C63" + }, + { + make: "MERCEDES-BENZ", + model: "C63 AMG" + }, + { + make: "MERCEDES-BENZ", + model: "CLA25" + }, + { + make: "MERCEDES-BENZ", + model: "CLK" + }, + { + make: "MERCEDES-BENZ", + model: "CLK230" + }, + { + make: "MERCEDES-BENZ", + model: "CLK320" + }, + { + make: "MERCEDES-BENZ", + model: "CLK350" + }, + { + make: "MERCEDES-BENZ", + model: "CLK500" + }, + { + make: "MERCEDES-BENZ", + model: "CLR350" + }, + { + make: "MERCEDES-BENZ", + model: "COMPRESSOR" + }, + { + make: "MERCEDES-BENZ", + model: "E300" + }, + { + make: "MERCEDES-BENZ", + model: "E320" + }, + { + make: "MERCEDES-BENZ", + model: "E400" + }, + { + make: "MERCEDES-BENZ", + model: "E420" + }, + { + make: "MERCEDES-BENZ", + model: "E500W" + }, + { + make: "MERCEDES-BENZ", + model: "E550" + }, + { + make: "MERCEDES-BENZ", + model: "GLA25" + }, + { + make: "MERCEDES-BENZ", + model: "GLC30" + }, + { + make: "MERCEDES-BENZ", + model: "GLE350" + }, + { + make: "MERCEDES-BENZ", + model: "GLK" + }, + { + make: "MERCEDES-BENZ", + model: "GLK25" + }, + { + make: "MERCEDES-BENZ", + model: "GLK250" + }, + { + make: "MERCEDES-BENZ", + model: "GLK35" + }, + { + make: "MERCEDES-BENZ", + model: "GLK35" + }, + { + make: "MERCEDES-BENZ", + model: "M1320" + }, + { + make: "MERCEDES-BENZ", + model: "METRIS" + }, + { + make: "MERCEDES-BENZ", + model: "MI320" + }, + { + make: "MERCEDES-BENZ", + model: "MI350" + }, + { + make: "MERCEDES-BENZ", + model: "MI500" + }, + { + make: "MERCEDES-BENZ", + model: "ML350" + }, + { + make: "MERCEDES-BENZ", + model: "ML400" + }, + { + make: "MERCEDES-BENZ", + model: "ML430" + }, + { + make: "MERCEDES-BENZ", + model: "ML55" + }, + { + make: "MERCEDES-BENZ", + model: "ML63" + }, + { + make: "MERCEDES-BENZ", + model: "R320" + }, + { + make: "MERCEDES-BENZ", + model: "S155" + }, + { + make: "MERCEDES-BENZ", + model: "S450V" + }, + { + make: "MERCEDES-BENZ", + model: "SL500" + }, + { + make: "MERCEDES-BENZ", + model: "SLK230" + }, + { + make: "MERCEDES-BENZ", + model: "SLK350" + }, + { + make: "MERCEDES-BENZ", + model: "SPRINTER" + }, + { + make: "MERCEDES-BENZ", + model: "SPRINTER VAN" + }, + { + make: "MERCURY", + model: "COMET" + }, + { + make: "MERCURY", + model: "COUGAR" + }, + { + make: "MERCURY", + model: "GRAND MARQUIS" + }, + { + make: "MERCURY", + model: "MARQUIS" + }, + { + make: "MERCURY", + model: "MURADER" + }, + { + make: "MERCURY", + model: "MYSTIQUE" + }, + { + make: "MERCURY", + model: "SABLE" + }, + { + make: "MERCURY", + model: "TOPAZ" + }, + { + make: "MERCURY", + model: "VILLAGER" + }, + { + make: "MINI", + model: "COOPER" + }, + { + make: "MINI", + model: "COOPER CLUBMAN" + }, + { + make: "MINI", + model: "COOPER COUNTRYMAN" + }, + { + make: "MINI", + model: "COOPER S" + }, + { + make: "MITSUBISHI", + model: "DELICA" + }, + { + make: "MITSUBISHI", + model: "ECLIPSE" + }, + { + make: "MITSUBISHI", + model: "ECLIPSE CROSS" + }, + { + make: "MITSUBISHI", + model: "ENDVR" + }, + { + make: "MITSUBISHI", + model: "GALANT" + }, + { + make: "MITSUBISHI", + model: "LANCER" + }, + { + make: "MITSUBISHI", + model: "LANCER EVOLUTION" + }, + { + make: "MITSUBISHI", + model: "MIRAGE" + }, + { + make: "MITSUBISHI", + model: "MONTERO" + }, + { + make: "MITSUBISHI", + model: "OUTLANDER" + }, + { + make: "MITSUBISHI", + model: "PJERO" + }, + { + make: "MITSUBISHI", + model: "RVR" + }, + { + make: "MITSUBISHI", + model: "SPYDER" + }, + { + make: "NISSAN", + model: "200" + }, + { + make: "NISSAN", + model: "200SX" + }, + { + make: "NISSAN", + model: "240SX" + }, + { + make: "NISSAN", + model: "240Z" + }, + { + make: "NISSAN", + model: "2500 VAN" + }, + { + make: "NISSAN", + model: "280" + }, + { + make: "NISSAN", + model: "300ZX" + }, + { + make: "NISSAN", + model: "350Z" + }, + { + make: "NISSAN", + model: "370Z" + }, + { + make: "NISSAN", + model: "ALTIMA" + }, + { + make: "NISSAN", + model: "ARMADA" + }, + { + make: "NISSAN", + model: "AXXESS" + }, + { + make: "NISSAN", + model: "CUBE" + }, + { + make: "NISSAN", + model: "D21" + }, + { + make: "NISSAN", + model: "FIGARO" + }, + { + make: "NISSAN", + model: "FRONTIER" + }, + { + make: "NISSAN", + model: "HALFTON DLX" + }, + { + make: "NISSAN", + model: "JUKE" + }, + { + make: "NISSAN", + model: "KICKS" + }, + { + make: "NISSAN", + model: "LEAF" + }, + { + make: "NISSAN", + model: "MAXIMA" + }, + { + make: "NISSAN", + model: "MICRA" + }, + { + make: "NISSAN", + model: "MURANO" + }, + { + make: "NISSAN", + model: "NV200" + }, + { + make: "NISSAN", + model: "NV250" + }, + { + make: "NISSAN", + model: "NV2500" + }, + { + make: "NISSAN", + model: "PATHFINDER" + }, + { + make: "NISSAN", + model: "PULSAR" + }, + { + make: "NISSAN", + model: "QASHQAI" + }, + { + make: "NISSAN", + model: "QSHQI" + }, + { + make: "NISSAN", + model: "QUEST" + }, + { + make: "NISSAN", + model: "ROGUE" + }, + { + make: "NISSAN", + model: "SENTRA" + }, + { + make: "NISSAN", + model: "SILVIA" + }, + { + make: "NISSAN", + model: "SKYLINE" + }, + { + make: "NISSAN", + model: "SKYLINE GTR" + }, + { + make: "NISSAN", + model: "TERRANO" + }, + { + make: "NISSAN", + model: "TITAN" + }, + { + make: "NISSAN", + model: "TRANO - PATHFINDER" + }, + { + make: "NISSAN", + model: "VERS" + }, + { + make: "NISSAN", + model: "VERSA" + }, + { + make: "NISSAN", + model: "X-TERRA" + }, + { + make: "NISSAN", + model: "X-TRAIL" + }, + { + make: "OLDSMOBILE", + model: "ACHIEVA" + }, + { + make: "OLDSMOBILE", + model: "ALERO" + }, + { + make: "OLDSMOBILE", + model: "AURORA" + }, + { + make: "OLDSMOBILE", + model: "BRAVADO" + }, + { + make: "OLDSMOBILE", + model: "CUTLASS" + }, + { + make: "OLDSMOBILE", + model: "DELTA 88" + }, + { + make: "OLDSMOBILE", + model: "DELTA 88 ROYALE BROUGHAM" + }, + { + make: "OLDSMOBILE", + model: "INTRIGUE" + }, + { + make: "OLDSMOBILE", + model: "NINETY EIGHT" + }, + { + make: "OLDSMOBILE", + model: "OMEGA" + }, + { + make: "OLDSMOBILE", + model: "REGENCY" + }, + { + make: "OLDSMOBILE", + model: "SILHOUETTE" + }, + { + make: "OLDSMOBILE", + model: "TORONADO" + }, + { + make: "PLYMOUTH", + model: "ACCLAIM" + }, + { + make: "PLYMOUTH", + model: "BARACUDA" + }, + { + make: "PLYMOUTH", + model: "BREEZE" + }, + { + make: "PLYMOUTH", + model: "CARAVAN" + }, + { + make: "PLYMOUTH", + model: "GR.VOYAGER" + }, + { + make: "PLYMOUTH", + model: "NEON" + }, + { + make: "PLYMOUTH", + model: "RELIANT" + }, + { + make: "PLYMOUTH", + model: "SUNDANCE" + }, + { + make: "PLYMOUTH", + model: "VALANT" + }, + { + make: "PLYMOUTH", + model: "VOYAGER" + }, + { + make: "POLARIS", + model: "570 CREW" + }, + { + make: "POLARIS", + model: "GENERAL 2+2" + }, + { + make: "PONTIAC", + model: "ACADIAN" + }, + { + make: "PONTIAC", + model: "AZTEC" + }, + { + make: "PONTIAC", + model: "BEAUMONT" + }, + { + make: "PONTIAC", + model: "BONNEVILLE" + }, + { + make: "PONTIAC", + model: "FIERO" + }, + { + make: "PONTIAC", + model: "FIREBIRD" + }, + { + make: "PONTIAC", + model: "FIREFLY" + }, + { + make: "PONTIAC", + model: "G3" + }, + { + make: "PONTIAC", + model: "G5" + }, + { + make: "PONTIAC", + model: "G6" + }, + { + make: "PONTIAC", + model: "G8" + }, + { + make: "PONTIAC", + model: "GRAND AM" + }, + { + make: "PONTIAC", + model: "GRAND PRIX" + }, + { + make: "PONTIAC", + model: "GTO" + }, + { + make: "PONTIAC", + model: "MONTANA" + }, + { + make: "PONTIAC", + model: "PARRISSIEN" + }, + { + make: "PONTIAC", + model: "PURSUIT" + }, + { + make: "PONTIAC", + model: "SOLSTICE" + }, + { + make: "PONTIAC", + model: "SUNBIRD" + }, + { + make: "PONTIAC", + model: "SUNFIRE" + }, + { + make: "PONTIAC", + model: "SUNRUNNER" + }, + { + make: "PONTIAC", + model: "TEMPEST" + }, + { + make: "PONTIAC", + model: "TORRENT" + }, + { + make: "PONTIAC", + model: "TRANS-AM" + }, + { + make: "PONTIAC", + model: "TRANSPORT" + }, + { + make: "PONTIAC", + model: "VIBE" + }, + { + make: "PONTIAC", + model: "WAVE" + }, + { + make: "PORSCHE", + model: "911" + }, + { + make: "PORSCHE", + model: "928" + }, + { + make: "PORSCHE", + model: "944" + }, + { + make: "PORSCHE", + model: "944 TURBO" + }, + { + make: "PORSCHE", + model: "BOXSTER" + }, + { + make: "PORSCHE", + model: "CAYENNE" + }, + { + make: "RANGE ROVER", + model: "46SHE" + }, + { + make: "RANGE ROVER", + model: "PROMA" + }, + { + make: "RANGE ROVER", + model: "RANGE" + }, + { + make: "SAAB", + model: "9" + }, + { + make: "SAAB", + model: "900S" + }, + { + make: "SAAB", + model: "9-2X" + }, + { + make: "SAAB", + model: "9-3" + }, + { + make: "SAAB", + model: "93 AREO" + }, + { + make: "SAAB", + model: "9-5" + }, + { + make: "SATURN", + model: "ASTRA" + }, + { + make: "SATURN", + model: "AURA" + }, + { + make: "SATURN", + model: "ION" + }, + { + make: "SATURN", + model: "L100" + }, + { + make: "SATURN", + model: "L200" + }, + { + make: "SATURN", + model: "LS1" + }, + { + make: "SATURN", + model: "LS2" + }, + { + make: "SATURN", + model: "OUTLOOK" + }, + { + make: "SATURN", + model: "RELAY" + }, + { + make: "SATURN", + model: "S SERIES" + }, + { + make: "SATURN", + model: "S11" + }, + { + make: "SATURN", + model: "SC1" + }, + { + make: "SATURN", + model: "SKY" + }, + { + make: "SATURN", + model: "SL1" + }, + { + make: "SATURN", + model: "SL2" + }, + { + make: "SATURN", + model: "SW1" + }, + { + make: "SATURN", + model: "SW2" + }, + { + make: "SATURN", + model: "VUE" + }, + { + make: "SATURN", + model: "WGN" + }, + { + make: "SCION", + model: "FR-S" + }, + { + make: "SCION", + model: "IM" + }, + { + make: "SCION", + model: "SXB" + }, + { + make: "SCION", + model: "TC" + }, + { + make: "SCION", + model: "XB" + }, + { + make: "SCION", + model: "XD" + }, + { + make: "SMART", + model: "FORTWO" + }, + { + make: "SUBARU", + model: "ASENT" + }, + { + make: "SUBARU", + model: "B9 TRIBECA" + }, + { + make: "SUBARU", + model: "BAJA" + }, + { + make: "SUBARU", + model: "BRAT" + }, + { + make: "SUBARU", + model: "BRZ" + }, + { + make: "SUBARU", + model: "CROSSTREK" + }, + { + make: "SUBARU", + model: "DOMINGO" + }, + { + make: "SUBARU", + model: "DOMNGO" + }, + { + make: "SUBARU", + model: "FORESTER" + }, + { + make: "SUBARU", + model: "GL" + }, + { + make: "SUBARU", + model: "IMPREZA" + }, + { + make: "SUBARU", + model: "IMPREZA STI" + }, + { + make: "SUBARU", + model: "JUSTY" + }, + { + make: "SUBARU", + model: "LEGACY" + }, + { + make: "SUBARU", + model: "LOYALE" + }, + { + make: "SUBARU", + model: "OUTBACK" + }, + { + make: "SUBARU", + model: "SVX" + }, + { + make: "SUBARU", + model: "TRIBECA" + }, + { + make: "SUBARU", + model: "WRX" + }, + { + make: "SUBARU", + model: "WRX STI" + }, + { + make: "SUBARU", + model: "XV" + }, + { + make: "SUZUKI", + model: "AERIO" + }, + { + make: "SUZUKI", + model: "CARRY" + }, + { + make: "SUZUKI", + model: "ESTEEM" + }, + { + make: "SUZUKI", + model: "GRAND VITARA" + }, + { + make: "SUZUKI", + model: "MINI-TRUCK" + }, + { + make: "SUZUKI", + model: "SAMURAI" + }, + { + make: "SUZUKI", + model: "SIDEKICK" + }, + { + make: "SUZUKI", + model: "SWIFT" + }, + { + make: "SUZUKI", + model: "SX4" + }, + { + make: "SUZUKI", + model: "VITARA" + }, + { + make: "SUZUKI", + model: "XL7" + }, + { + make: "TESLA", + model: "MODEL 3" + }, + { + make: "TOYOTA", + model: " PRIUS C" + }, + { + make: "TOYOTA", + model: "4 RUNNER" + }, + { + make: "TOYOTA", + model: "4X4" + }, + { + make: "TOYOTA", + model: "ALPHARD" + }, + { + make: "TOYOTA", + model: "ARISTO" + }, + { + make: "TOYOTA", + model: "AVALON" + }, + { + make: "TOYOTA", + model: "CALDI" + }, + { + make: "TOYOTA", + model: "CALDINA" + }, + { + make: "TOYOTA", + model: "CAMRY" + }, + { + make: "TOYOTA", + model: "CANOPY" + }, + { + make: "TOYOTA", + model: "CELICA" + }, + { + make: "TOYOTA", + model: "CELSLOR" + }, + { + make: "TOYOTA", + model: "CHASE" + }, + { + make: "TOYOTA", + model: "CH-R" + }, + { + make: "TOYOTA", + model: "COROLLA" + }, + { + make: "TOYOTA", + model: "COROLLA CE" + }, + { + make: "TOYOTA", + model: "COROLLA.HYBRID" + }, + { + make: "TOYOTA", + model: "CRESSIDA" + }, + { + make: "TOYOTA", + model: "DELICA" + }, + { + make: "TOYOTA", + model: "ECHO" + }, + { + make: "TOYOTA", + model: "FJ" + }, + { + make: "TOYOTA", + model: "HIGHLANDER" + }, + { + make: "TOYOTA", + model: "LAND CRUISER" + }, + { + make: "TOYOTA", + model: "MATRIX" + }, + { + make: "TOYOTA", + model: "MR2" + }, + { + make: "TOYOTA", + model: "PASEO" + }, + { + make: "TOYOTA", + model: "PICK UP" + }, + { + make: "TOYOTA", + model: "PRADA" + }, + { + make: "TOYOTA", + model: "PREVIA" + }, + { + make: "TOYOTA", + model: "PRIUS" + }, + { + make: "TOYOTA", + model: "PRIUS HYBIRD" + }, + { + make: "TOYOTA", + model: "PRIUS V" + }, + { + make: "TOYOTA", + model: "RAV4" + }, + { + make: "TOYOTA", + model: "SCION" + }, + { + make: "TOYOTA", + model: "SEQUOIA" + }, + { + make: "TOYOTA", + model: "SIENNA" + }, + { + make: "TOYOTA", + model: "SOLARA" + }, + { + make: "TOYOTA", + model: "SORER" + }, + { + make: "TOYOTA", + model: "SP & SP" + }, + { + make: "TOYOTA", + model: "SUPRA" + }, + { + make: "TOYOTA", + model: "SURF" + }, + { + make: "TOYOTA", + model: "TACOMA" + }, + { + make: "TOYOTA", + model: "TERCEL" + }, + { + make: "TOYOTA", + model: "TUNDRA" + }, + { + make: "TOYOTA", + model: "VENZA" + }, + { + make: "TOYOTA", + model: "YARIS" + }, + { + make: "TRIUMPH", + model: "TR3" + }, + { + make: "VOLKSWAGEN", + model: "ATLAS" + }, + { + make: "VOLKSWAGEN", + model: "BEETLE" + }, + { + make: "VOLKSWAGEN", + model: "CABRIOLET" + }, + { + make: "VOLKSWAGEN", + model: "CC" + }, + { + make: "VOLKSWAGEN", + model: "CORRADO" + }, + { + make: "VOLKSWAGEN", + model: "DUNEBUGGY" + }, + { + make: "VOLKSWAGEN", + model: "EOS" + }, + { + make: "VOLKSWAGEN", + model: "EUROVAN" + }, + { + make: "VOLKSWAGEN", + model: "GOLF" + }, + { + make: "VOLKSWAGEN", + model: "GOLF CABRIOLET" + }, + { + make: "VOLKSWAGEN", + model: "GOLF CITY" + }, + { + make: "VOLKSWAGEN", + model: "GOLF GTI" + }, + { + make: "VOLKSWAGEN", + model: "GOLF SPORT WAGEN" + }, + { + make: "VOLKSWAGEN", + model: "GTI" + }, + { + make: "VOLKSWAGEN", + model: "JETTA" + }, + { + make: "VOLKSWAGEN", + model: "JETTA TDI" + }, + { + make: "VOLKSWAGEN", + model: "KIT CAR" + }, + { + make: "VOLKSWAGEN", + model: "PASSAT" + }, + { + make: "VOLKSWAGEN", + model: "R32" + }, + { + make: "VOLKSWAGEN", + model: "RABBIT" + }, + { + make: "VOLKSWAGEN", + model: "ROUTAN" + }, + { + make: "VOLKSWAGEN", + model: "TIGUAN" + }, + { + make: "VOLKSWAGEN", + model: "TOUAREG" + }, + { + make: "VOLKSWAGEN", + model: "TRANSPORTER VAN" + }, + { + make: "VOLKSWAGEN", + model: "VANAGON" + }, + { + make: "VOLKSWAGEN", + model: "WESTFALIA" + }, + { + make: "VOLKSWAGEN", + model: "WINNEBAGO" + }, + { + make: "VOLVO", + model: "240" + }, + { + make: "VOLVO", + model: "244" + }, + { + make: "VOLVO", + model: "740" + }, + { + make: "VOLVO", + model: "760" + }, + { + make: "VOLVO", + model: "850" + }, + { + make: "VOLVO", + model: "940" + }, + { + make: "VOLVO", + model: "960" + }, + { + make: "VOLVO", + model: "C30" + }, + { + make: "VOLVO", + model: "C70" + }, + { + make: "VOLVO", + model: "DL" + }, + { + make: "VOLVO", + model: "GLE" + }, + { + make: "VOLVO", + model: "GLT" + }, + { + make: "VOLVO", + model: "GT" + }, + { + make: "VOLVO", + model: "S40" + }, + { + make: "VOLVO", + model: "S60" + }, + { + make: "VOLVO", + model: "S70" + }, + { + make: "VOLVO", + model: "S80" + }, + { + make: "VOLVO", + model: "S90" + }, + { + make: "VOLVO", + model: "V40" + }, + { + make: "VOLVO", + model: "V50" + }, + { + make: "VOLVO", + model: "V70" + }, + { + make: "VOLVO", + model: "V70 - X/C" + }, + { + make: "VOLVO", + model: "XC60" + }, + { + make: "VOLVO", + model: "XC70" + }, + { + make: "VOLVO", + model: "XC90" + } ]; export default obj; diff --git a/client/src/components/jobs-detail-change-estimator/jobs-detail-change-estimator.component.jsx b/client/src/components/jobs-detail-change-estimator/jobs-detail-change-estimator.component.jsx index dd5d527d0..94fa1127e 100644 --- a/client/src/components/jobs-detail-change-estimator/jobs-detail-change-estimator.component.jsx +++ b/client/src/components/jobs-detail-change-estimator/jobs-detail-change-estimator.component.jsx @@ -1,40 +1,36 @@ -import {DownOutlined} from "@ant-design/icons"; -import {Dropdown} from "antd"; +import { DownOutlined } from "@ant-design/icons"; +import { Dropdown } from "antd"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -export function JobsDetailChangeEstimator({disabled, form, bodyshop}) { - const handleClick = ({item, key, keyPath}) => { - const est = item.props.value; - form.setFieldsValue(est); - }; +export function JobsDetailChangeEstimator({ disabled, form, bodyshop }) { + const handleClick = ({ item, key, keyPath }) => { + const est = item.props.value; + form.setFieldsValue(est); + }; - const menu = { - items: bodyshop.md_estimators.map((est, idx) => ({ - key: idx, - label: `${est.est_ct_fn} ${est.est_ct_ln}`, - value: est, - })), - onClick: handleClick - } + const menu = { + items: bodyshop.md_estimators.map((est, idx) => ({ + key: idx, + label: `${est.est_ct_fn} ${est.est_ct_ln}`, + value: est + })), + onClick: handleClick + }; - return ( - - e.preventDefault()} - > - - - - ); + return ( + + e.preventDefault()}> + + + + ); } export default connect(mapStateToProps, null)(JobsDetailChangeEstimator); diff --git a/client/src/components/jobs-detail-change-filehandler/jobs-detail-change-filehandler.component.jsx b/client/src/components/jobs-detail-change-filehandler/jobs-detail-change-filehandler.component.jsx index f582b9e41..e69eb5cdb 100644 --- a/client/src/components/jobs-detail-change-filehandler/jobs-detail-change-filehandler.component.jsx +++ b/client/src/components/jobs-detail-change-filehandler/jobs-detail-change-filehandler.component.jsx @@ -1,44 +1,40 @@ -import {DownOutlined} from "@ant-design/icons"; -import {Dropdown} from "antd"; +import { DownOutlined } from "@ant-design/icons"; +import { Dropdown } from "antd"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -export function JobsDetailChangeFilehandler({disabled, form, bodyshop}) { - const handleClick = ({item, key, keyPath}) => { - const est = item.props.value; - form.setFieldsValue(est); - }; +export function JobsDetailChangeFilehandler({ disabled, form, bodyshop }) { + const handleClick = ({ item, key, keyPath }) => { + const est = item.props.value; + form.setFieldsValue(est); + }; - const menu = { - items: bodyshop.md_filehandlers.map((est, idx) => ({ - key: idx, - label: `${est.ins_ct_fn} ${est.ins_ct_ln}`, - value: est, - style: {breakInside: "avoid"}, - })), - style: { - columnCount: Math.floor(bodyshop.md_filehandlers.length / 10) + 1, - }, - onClick: handleClick - } + const menu = { + items: bodyshop.md_filehandlers.map((est, idx) => ({ + key: idx, + label: `${est.ins_ct_fn} ${est.ins_ct_ln}`, + value: est, + style: { breakInside: "avoid" } + })), + style: { + columnCount: Math.floor(bodyshop.md_filehandlers.length / 10) + 1 + }, + onClick: handleClick + }; - return ( - - e.preventDefault()} - > - - - - ); + return ( + + e.preventDefault()}> + + + + ); } export default connect(mapStateToProps, null)(JobsDetailChangeFilehandler); diff --git a/client/src/components/jobs-detail-checklists/jobs-detail-checklists.component.jsx b/client/src/components/jobs-detail-checklists/jobs-detail-checklists.component.jsx index f862a094d..1b07ef005 100644 --- a/client/src/components/jobs-detail-checklists/jobs-detail-checklists.component.jsx +++ b/client/src/components/jobs-detail-checklists/jobs-detail-checklists.component.jsx @@ -1,20 +1,19 @@ import React from "react"; -import {Col, Row} from "antd"; +import { Col, Row } from "antd"; import JobChecklistDisplay from "../job-checklist/job-checklist-display.component"; -const colSpan = {sm: {span: 24}, md: {span: 12}}; -export default function JobsDetailChecklists({job}) { - return ( -
- -
- - - - - - - - - ); +const colSpan = { sm: { span: 24 }, md: { span: 12 } }; +export default function JobsDetailChecklists({ job }) { + return ( +
+ +
+ + + + + + + + ); } diff --git a/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx b/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx index dbe816ae2..80598717e 100644 --- a/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx +++ b/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx @@ -1,160 +1,125 @@ -import {Form, Statistic, Tooltip} from "antd"; -import React, {useMemo} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { Form, Statistic, Tooltip } from "antd"; +import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; import FormRow from "../layout-form-row/layout-form-row.component"; const mapStateToProps = createStructuredSelector({ - jobRO: selectJobReadOnly, - bodyshop: selectBodyshop, + jobRO: selectJobReadOnly, + bodyshop: selectBodyshop }); -export function JobsDetailDatesComponent({jobRO, job, bodyshop}) { - const {t} = useTranslation(); +export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) { + const { t } = useTranslation(); - const jobInPostProduction = useMemo(() => { - return bodyshop.md_ro_statuses.post_production_statuses.includes( - job.status - ); - }, [job.status, bodyshop.md_ro_statuses.post_production_statuses]); + const jobInPostProduction = useMemo(() => { + return bodyshop.md_ro_statuses.post_production_statuses.includes(job.status); + }, [job.status, bodyshop.md_ro_statuses.post_production_statuses]); - const calcRepairDays = job?.joblines?.length ? - job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0) / - (bodyshop.target_touchtime === 0 ? 1 : bodyshop.target_touchtime) : []; + const calcRepairDays = job?.joblines?.length + ? job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0) / + (bodyshop.target_touchtime === 0 ? 1 : bodyshop.target_touchtime) + : []; - return ( -
- - - - - - - - - - - - - - + return ( +
+ + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + - - - + + + - - - + + + - - - - -
- ); + + + + +
+ ); } export default connect(mapStateToProps, null)(JobsDetailDatesComponent); diff --git a/client/src/components/jobs-detail-general/jobs-detail-general.component.jsx b/client/src/components/jobs-detail-general/jobs-detail-general.component.jsx index 99358d805..aa53969f3 100644 --- a/client/src/components/jobs-detail-general/jobs-detail-general.component.jsx +++ b/client/src/components/jobs-detail-general/jobs-detail-general.component.jsx @@ -1,294 +1,255 @@ -import {Col, Form, Input, InputNumber, Row, Select, Space, Switch,} from "antd"; +import { Col, Form, Input, InputNumber, Row, Select, Space, Switch } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component"; -import FormItemPhone, {PhoneItemFormatterValidation,} from "../form-items-formatted/phone-form-item.component"; +import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component"; import Car from "../job-damage-visual/job-damage-visual.component"; import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component"; import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component"; import FormRow from "../layout-form-row/layout-form-row.component"; const mapStateToProps = createStructuredSelector({ - jobRO: selectJobReadOnly, - bodyshop: selectBodyshop, + jobRO: selectJobReadOnly, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -const lossColFields = {sm: {span: 24}, md: {span: 18}, lg: {span: 20}}; -const lossColDamage = {sm: {span: 24}, md: {span: 6}, lg: {span: 4}}; +const lossColFields = { sm: { span: 24 }, md: { span: 18 }, lg: { span: 20 } }; +const lossColDamage = { sm: { span: 24 }, md: { span: 6 }, lg: { span: 4 } }; -export function JobsDetailGeneral({bodyshop, jobRO, job, form}) { - const {getFieldValue} = form; - const {t} = useTranslation(); - return ( -
- - - - - - - - - - - - - - - - - - - +export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) { + const { getFieldValue } = form; + const { t } = useTranslation(); + return ( +
+ + + + + + + + + + + + + + + + + + + - - - - - - - - - - - {t("jobs.fields.ins_ct_ln")} - - - } - name="ins_ct_ln" - > - - - - - - - PhoneItemFormatterValidation(getFieldValue, "ins_ph1"), - ]} - > - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {job.area_of_damage ? ( - - ) : ( - t("jobs.errors.nodamage") - )} - - + + + + + + + + + + + {t("jobs.fields.ins_ct_ln")} + + + } + name="ins_ct_ln" + > + + + + + + PhoneItemFormatterValidation(getFieldValue, "ins_ph1")]} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {job.area_of_damage ? ( + + ) : ( + t("jobs.errors.nodamage") + )} + + - - - - - - {t("jobs.fields.est_ct_fn")} - - - } - name="est_ct_fn" - > - - - - - - - - - - - - - - - - + + + + + + {t("jobs.fields.est_ct_fn")} + + + } + name="est_ct_fn" + > + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - ); + + + + + + + + + + + + + + + + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailGeneral); diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.addtoproduction.util.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.addtoproduction.util.jsx index ea09e49ea..e40c0162a 100644 --- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.addtoproduction.util.jsx +++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.addtoproduction.util.jsx @@ -1,47 +1,41 @@ -import {notification} from "antd"; +import { notification } from "antd"; import i18n from "i18next"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {store} from "../../redux/store"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { store } from "../../redux/store"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; -export default function AddToProduction( - apolloClient, - jobId, - completionCallback, - remove = false -) { - logImEXEvent("job_add_to_production"); +export default function AddToProduction(apolloClient, jobId, completionCallback, remove = false) { + logImEXEvent("job_add_to_production"); - //get a list of all fields on the job - apolloClient - .mutate({ - mutation: UPDATE_JOB, - variables: {jobId: jobId, job: {inproduction: !remove}}, + //get a list of all fields on the job + apolloClient + .mutate({ + mutation: UPDATE_JOB, + variables: { jobId: jobId, job: { inproduction: !remove } } + }) + .then((res) => { + notification["success"]({ + message: i18n.t("jobs.successes.save") + }); + + store.dispatch( + insertAuditTrail({ + jobid: jobId, + operation: AuditTrailMapping.jobinproductionchange(!remove), + type: "jobinproductionchange" }) - .then((res) => { - notification["success"]({ - message: i18n.t("jobs.successes.save"), - }); - - store.dispatch( - insertAuditTrail({ - jobid: jobId, - operation: AuditTrailMapping.jobinproductionchange(!remove), - type: "jobinproductionchange", - }) - ); - if (completionCallback) completionCallback(); + ); + if (completionCallback) completionCallback(); + }) + .catch((error) => { + notification["errors"]({ + message: i18n.t("jobs.errors.saving", { + error: JSON.stringify(error) }) - .catch((error) => { - notification["errors"]({ - message: i18n.t("jobs.errors.saving", { - error: JSON.stringify(error), - }), - }); - }); - - //insert the new job. call the callback with the returned ID when done. + }); + }); + //insert the new job. call the callback with the returned ID when done. } 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 27c81d7b2..0d2804b19 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,490 +1,464 @@ -import {DownCircleFilled} from "@ant-design/icons"; -import {useApolloClient, useMutation} from "@apollo/client"; -import {Button, Card, Dropdown, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space,} from "antd"; -import React, {useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {auth, logImEXEvent} from "../../firebase/firebase.utils"; -import {CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT} from "../../graphql/appointments.queries"; -import {DELETE_JOB, UPDATE_JOB, VOID_JOB} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { DownCircleFilled } from "@ant-design/icons"; +import { useApolloClient, useMutation } from "@apollo/client"; +import { Button, Card, Dropdown, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } from "antd"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { auth, logImEXEvent } from "../../firebase/firebase.utils"; +import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries"; +import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; 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 axios from "axios"; -import {setEmailOptions} from "../../redux/email/email.actions"; -import {openChatByPhone, setMessage} from "../../redux/messaging/messaging.actions"; -import {GET_CURRENT_QUESTIONSET_ID, INSERT_CSI} from "../../graphql/csi.queries"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { setEmailOptions } from "../../redux/email/email.actions"; +import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions"; +import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries"; +import { TemplateList } from "../../utils/TemplateConstants"; import parsePhoneNumber from "libphonenumber-js"; -import {HasFeatureAccess} from "../feature-wrapper/feature-wrapper.component"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; +import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component"; import dayjs from "../../utils/day"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - jobRO: selectJobReadOnly, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + jobRO: selectJobReadOnly, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - setScheduleContext: (context) => - dispatch(setModalContext({context: context, modal: "schedule"})), - setBillEnterContext: (context) => - dispatch(setModalContext({context: context, modal: "billEnter"})), - setPaymentContext: (context) => - dispatch(setModalContext({context: context, modal: "payment"})), - setJobCostingContext: (context) => - dispatch(setModalContext({context: context, modal: "jobCosting"})), - setTimeTicketContext: (context) => - dispatch(setModalContext({context: context, modal: "timeTicket"})), - setCardPaymentContext: (context) => - dispatch(setModalContext({context: context, modal: "cardPayment"})), - insertAuditTrail: ({ jobid, operation, type }) => - dispatch(insertAuditTrail({ jobid, operation, type })), - setTimeTicketTaskContext: (context) => - dispatch(setModalContext({context: context, modal: "timeTicketTask"})), - setEmailOptions: (e) => dispatch(setEmailOptions(e)), - openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), - setMessage: (text) => dispatch(setMessage(text)), + setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })), + setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })), + setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })), + setJobCostingContext: (context) => dispatch(setModalContext({ context: context, modal: "jobCosting" })), + setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })), + setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })), + setTimeTicketTaskContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicketTask" })), + setEmailOptions: (e) => dispatch(setEmailOptions(e)), + openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), + setMessage: (text) => dispatch(setMessage(text)) }); export function JobsDetailHeaderActions({ - job, - bodyshop, - currentUser, - refetch, - setScheduleContext, - setBillEnterContext, - setPaymentContext, - setJobCostingContext, - jobRO, - setTimeTicketContext, - setCardPaymentContext, - insertAuditTrail, - setEmailOptions, - openChatByPhone, - setMessage, - setTimeTicketTaskContext, - }) { - const {t} = useTranslation(); - const client = useApolloClient(); - const history = useNavigate(); - const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - const [isCancelScheduleModalVisible, setIsCancelScheduleModalVisible] = useState(false); - const [insertAppointment] = useMutation(INSERT_MANUAL_APPT); - const [deleteJob] = useMutation(DELETE_JOB); - const [insertCsi] = useMutation(INSERT_CSI); - const [updateJob] = useMutation(UPDATE_JOB); - const [voidJob] = useMutation(VOID_JOB); - const [cancelAllAppointments] = useMutation(CANCEL_APPOINTMENTS_BY_JOB_ID); + job, + bodyshop, + currentUser, + refetch, + setScheduleContext, + setBillEnterContext, + setPaymentContext, + setJobCostingContext, + jobRO, + setTimeTicketContext, + setCardPaymentContext, + insertAuditTrail, + setEmailOptions, + openChatByPhone, + setMessage, + setTimeTicketTaskContext +}) { + const { t } = useTranslation(); + const client = useApolloClient(); + const history = useNavigate(); + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [isCancelScheduleModalVisible, setIsCancelScheduleModalVisible] = useState(false); + const [insertAppointment] = useMutation(INSERT_MANUAL_APPT); + const [deleteJob] = useMutation(DELETE_JOB); + const [insertCsi] = useMutation(INSERT_CSI); + const [updateJob] = useMutation(UPDATE_JOB); + const [voidJob] = useMutation(VOID_JOB); + const [cancelAllAppointments] = useMutation(CANCEL_APPOINTMENTS_BY_JOB_ID); - const {treatments: {ImEXPay}} = useSplitTreatments({ - attributes: {}, - names: ["ImEXPay"], - splitKey: bodyshop && bodyshop.imexshopid, + const { + treatments: { ImEXPay } + } = useSplitTreatments({ + attributes: {}, + names: ["ImEXPay"], + splitKey: bodyshop && bodyshop.imexshopid + }); + + const jobInProduction = useMemo(() => { + return bodyshop.md_ro_statuses.production_statuses.includes(job.status); + }, [job, bodyshop.md_ro_statuses.production_statuses]); + const [visibility, setVisibility] = useState(false); + + const jobInPreProduction = useMemo(() => { + return bodyshop.md_ro_statuses.pre_production_statuses.includes(job.status); + }, [job.status, bodyshop.md_ro_statuses.pre_production_statuses]); + + const jobInPostProduction = useMemo(() => { + return bodyshop.md_ro_statuses.post_production_statuses.includes(job.status); + }, [job.status, bodyshop.md_ro_statuses.post_production_statuses]); + + // Function to show modal + const showCancelScheduleModal = () => { + setIsCancelScheduleModalVisible(true); + }; + + // Function to handle Cancel + const handleCancelScheduleModalCancel = () => { + setIsCancelScheduleModalVisible(false); + }; + + const handleDuplicate = () => + DuplicateJob( + client, + job.id, + { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported }, + (newJobId) => { + history(`/manage/jobs/${newJobId}`); + notification["success"]({ + message: t("jobs.successes.duplicated") + }); + }, + true + ); + + const handleDuplicateConfirm = () => + DuplicateJob(client, job.id, { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported }, (newJobId) => { + history(`/manage/jobs/${newJobId}`); + notification["success"]({ + message: t("jobs.successes.duplicated") + }); }); + const handleFinish = async (values) => { + logImEXEvent("schedule_manual_event"); - const jobInProduction = useMemo(() => { - return bodyshop.md_ro_statuses.production_statuses.includes(job.status); - }, [job, bodyshop.md_ro_statuses.production_statuses]); - const [visibility, setVisibility] = useState(false); + setLoading(true); + try { + insertAppointment({ + variables: { + apt: { ...values, isintake: false, jobid: job.id, bodyshopid: bodyshop.id } + }, + refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"] + }); + notification.open({ + type: "success", + message: t("appointments.successes.created") + }); + } catch (error) { + } finally { + setLoading(false); + setVisibility(false); + } + }; - const jobInPreProduction = useMemo(() => { - return bodyshop.md_ro_statuses.pre_production_statuses.includes(job.status); - }, [job.status, bodyshop.md_ro_statuses.pre_production_statuses]); + const handleDeleteJob = async () => { + //delete the job. + const result = await deleteJob({ variables: { id: job.id } }); - const jobInPostProduction = useMemo(() => { - return bodyshop.md_ro_statuses.post_production_statuses.includes( - job.status - ); - }, [job.status, bodyshop.md_ro_statuses.post_production_statuses]); + if (!!!result.errors) { + notification["success"]({ + message: t("jobs.successes.delete") + }); + //go back to jobs list. + history(`/manage/`); + } else { + notification["error"]({ + message: t("jobs.errors.deleted", { + error: JSON.stringify(result.errors) + }) + }); + } + }; - // Function to show modal - const showCancelScheduleModal = () => { - setIsCancelScheduleModalVisible(true); - }; + const handleCreateCsi = async (e) => { + logImEXEvent("job_create_csi"); + //Is there already a CSI? + if (!job.csiinvites || job.csiinvites.length === 0) { + const questionSetResult = await client.query({ + query: GET_CURRENT_QUESTIONSET_ID + }); - // Function to handle Cancel - const handleCancelScheduleModalCancel = () => { - setIsCancelScheduleModalVisible(false); - }; - - const handleDuplicate = () => - DuplicateJob( - client, - job.id, - {defaultOpenStatus: bodyshop.md_ro_statuses.default_imported}, - (newJobId) => { - history(`/manage/jobs/${newJobId}`); - notification["success"]({ - message: t("jobs.successes.duplicated"), - }); - }, - true - ); - - const handleDuplicateConfirm = () => - DuplicateJob( - client, - job.id, - {defaultOpenStatus: bodyshop.md_ro_statuses.default_imported}, - (newJobId) => { - history(`/manage/jobs/${newJobId}`); - notification["success"]({ - message: t("jobs.successes.duplicated"), - }); - } - ); - - const handleFinish = async (values) => { - logImEXEvent("schedule_manual_event"); - - setLoading(true); - try { - insertAppointment({ - variables: { - apt: {...values, isintake: false, jobid: job.id, bodyshopid: bodyshop.id}, - }, - refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"], - }); - notification.open({ - type: "success", - message: t("appointments.successes.created"), - }); - } catch (error) { - } finally { - setLoading(false); - setVisibility(false); - } - }; - - const handleDeleteJob = async () => { - //delete the job. - const result = await deleteJob({variables: {id: job.id}}); - - if (!!!result.errors) { - notification["success"]({ - message: t("jobs.successes.delete"), - }); - //go back to jobs list. - history(`/manage/`); - } else { - notification["error"]({ - message: t("jobs.errors.deleted", { - error: JSON.stringify(result.errors), - }), - }); - } - }; - - const handleCreateCsi = async (e) => { - logImEXEvent("job_create_csi"); - - //Is there already a CSI? - if (!job.csiinvites || job.csiinvites.length === 0) { - const questionSetResult = await client.query({ - query: GET_CURRENT_QUESTIONSET_ID, - }); - - if (questionSetResult.data.csiquestions.length > 0) { - const result = await insertCsi({ - variables: { - csiInput: { - jobid: job.id, - bodyshopid: bodyshop.id, - questionset: questionSetResult.data.csiquestions[0].id, - relateddata: { - job: { - id: job.id, - ownr_fn: job.ownr_fn, - ro_number: job.ro_number, - v_model_yr: job.v_model_yr, - v_make_desc: job.v_make_desc, - v_model_desc: job.v_model_desc, - }, - bodyshop: { - city: bodyshop.city, - email: bodyshop.email, - state: bodyshop.state, - country: bodyshop.country, - address1: bodyshop.address1, - address2: bodyshop.address2, - shopname: bodyshop.shopname, - zip_post: bodyshop.zip_post, - logo_img_path: bodyshop.logo_img_path, - }, - }, - }, - }, - refetchQueries: ["GET_JOB_BY_PK"], - awaitRefetchQueries: true, - }); - - if (!!!result.errors) { - notification["success"]({message: t("csi.successes.created")}); - } else { - notification["error"]({ - message: t("csi.errors.creating", { - message: JSON.stringify(result.errors), - }), - }); - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.jobvoid(), - type: "jobvoid", - }); - return; - } - if (e.key === "email") - setEmailOptions({ - jobid: job.id, - messageOptions: { - to: [job.ownr_ea], - replyTo: bodyshop.email, - }, - template: { - name: TemplateList("job_special").csi_invitation_action.key, - variables: { - id: result.data.insert_csi.returning[0].id, - }, - }, - }); - - if (e.key === "text") { - const p = parsePhoneNumber(job.ownr_ph1, "CA"); - if (p && p.isValid()) { - openChatByPhone({ - phone_num: p.formatInternational(), - jobid: job.id, - }); - setMessage( - `${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}` - ); - } else { - notification["error"]({ - message: t("messaging.error.invalidphone"), - }); - } - } - if (e.key === "generate") { - //copy it to clipboard. - navigator.clipboard.writeText( - `${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}` - ); - } - } else { - notification["error"]({ - message: t("csi.errors.notconfigured"), - }); - } - } else { - if (e.key === "email") - setEmailOptions({ - jobid: job.id, - messageOptions: { - to: [job.ownr_ea], - replyTo: bodyshop.email, - }, - template: { - name: TemplateList("job_special").csi_invitation_action.key, - variables: { - id: job.csiinvites[0].id, - }, - }, - }); - - if (e.key === "text") { - const p = parsePhoneNumber(job.ownr_ph1, "CA"); - if (p && p.isValid()) { - openChatByPhone({ - phone_num: p.formatInternational(), - jobid: job.id, - }); - setMessage( - `${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}` - ); - } else { - notification["error"]({ - message: t("messaging.error.invalidphone"), - }); - } - } - - if (e.key === "generate") { - //copy it to clipboard. - navigator.clipboard.writeText( - `${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}` - ); - } - } - }; - - const handleVoidJob = async () => { - //delete the job. - const result = await voidJob({ - variables: { - jobId: job.id, + if (questionSetResult.data.csiquestions.length > 0) { + const result = await insertCsi({ + variables: { + csiInput: { + jobid: job.id, + bodyshopid: bodyshop.id, + questionset: questionSetResult.data.csiquestions[0].id, + relateddata: { job: { - status: bodyshop.md_ro_statuses.default_void, - voided: true, - scheduled_in: null, - scheduled_completion: null, - inproduction: false, - date_void: new Date(), + id: job.id, + ownr_fn: job.ownr_fn, + ro_number: job.ro_number, + v_model_yr: job.v_model_yr, + v_make_desc: job.v_make_desc, + v_model_desc: job.v_model_desc }, - note: [ - { - jobid: job.id, - created_by: currentUser.email, - audit: true, - text: t("jobs.labels.voidnote"), - }, - ], - }, + bodyshop: { + city: bodyshop.city, + email: bodyshop.email, + state: bodyshop.state, + country: bodyshop.country, + address1: bodyshop.address1, + address2: bodyshop.address2, + shopname: bodyshop.shopname, + zip_post: bodyshop.zip_post, + logo_img_path: bodyshop.logo_img_path + } + } + } + }, + refetchQueries: ["GET_JOB_BY_PK"], + awaitRefetchQueries: true }); if (!!!result.errors) { - notification["success"]({ - message: t("jobs.successes.voided"), - }); - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.jobvoid(), - type: "jobvoid", - }); - //go back to jobs list. - history(`/manage/`); + notification["success"]({ message: t("csi.successes.created") }); } else { - notification["error"]({ - message: t("jobs.errors.voiding", { - error: JSON.stringify(result.errors), - }), - }); - } - }; - - const handleExportCustData = async (e) => { - logImEXEvent("job_export_cust_data"); - let PartnerResponse; - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { - PartnerResponse = await axios.post(`/qbo/receivables`, { - jobIds: [job.id], - custDataOnly: true, - }); - } else { - //Default is QBD - - let QbXmlResponse; - try { - QbXmlResponse = await axios.post( - "/accounting/qbxml/receivables", - {jobIds: [job.id], custDataOnly: true}, - { - headers: { - Authorization: `Bearer ${await auth.currentUser.getIdToken()}`, - }, - } - ); - console.log("handle -> XML", QbXmlResponse); - } catch (error) { - console.log("Error getting QBXML from Server.", error); - notification["error"]({ - message: t("jobs.errors.exporting", { - error: "Unable to retrieve QBXML. " + JSON.stringify(error.message), - }), - }); - - return; - } - - //let PartnerResponse; - try { - PartnerResponse = await axios.post( - "http://localhost:1337/qb/", - QbXmlResponse.data, - { - headers: { - Authorization: `Bearer ${await auth.currentUser.getIdToken()}`, - }, - } - ); - } catch (error) { - console.log("Error connecting to quickbooks or partner.", error); - notification["error"]({ - message: t("jobs.errors.exporting-partner"), - }); - - return; - } - } - //Check to see if any of them failed. If they didn't execute the update. - const failedTransactions = PartnerResponse.data.filter((r) => !r.success); - if (failedTransactions.length > 0) { - //Uh oh. At least one was no good. - failedTransactions.forEach((ft) => { - //insert failed export log - notification.open({ - // key: "failedexports", - type: "error", - message: t("jobs.errors.exporting", { - error: ft.errorMessage || "", - }), - }); - }); - - //Handle Failures. - } else { - //Insert success export log. - - notification.open({ - type: "success", - key: "jobsuccessexport", - message: t("jobs.successes.exported"), - }); - } - }; - - const handleAlertToggle = (e) => { - logImEXEvent("production_toggle_alert"); - //e.stopPropagation(); - updateJob({ - variables: { - jobId: job.id, - job: { - production_vars: { - ...job.production_vars, - alert: - !!job.production_vars && !!job.production_vars.alert - ? !job.production_vars.alert - : true, - }, - }, - }, - }); - insertAuditTrail({ + notification["error"]({ + message: t("csi.errors.creating", { + message: JSON.stringify(result.errors) + }) + }); + insertAuditTrail({ jobid: job.id, - operation: AuditTrailMapping.alertToggle( - !!job.production_vars && !!job.production_vars.alert - ? !job.production_vars.alert - : true - ), - type: "alertToggle", + operation: AuditTrailMapping.jobvoid(), + type: "jobvoid" + }); + return; + } + if (e.key === "email") + setEmailOptions({ + jobid: job.id, + messageOptions: { + to: [job.ownr_ea], + replyTo: bodyshop.email + }, + template: { + name: TemplateList("job_special").csi_invitation_action.key, + variables: { + id: result.data.insert_csi.returning[0].id + } + } + }); + + if (e.key === "text") { + const p = parsePhoneNumber(job.ownr_ph1, "CA"); + if (p && p.isValid()) { + openChatByPhone({ + phone_num: p.formatInternational(), + jobid: job.id + }); + setMessage( + `${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}` + ); + } else { + notification["error"]({ + message: t("messaging.error.invalidphone") + }); + } + } + if (e.key === "generate") { + //copy it to clipboard. + navigator.clipboard.writeText( + `${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}` + ); + } + } else { + notification["error"]({ + message: t("csi.errors.notconfigured") }); - }; + } + } else { + if (e.key === "email") + setEmailOptions({ + jobid: job.id, + messageOptions: { + to: [job.ownr_ea], + replyTo: bodyshop.email + }, + template: { + name: TemplateList("job_special").csi_invitation_action.key, + variables: { + id: job.csiinvites[0].id + } + } + }); + + if (e.key === "text") { + const p = parsePhoneNumber(job.ownr_ph1, "CA"); + if (p && p.isValid()) { + openChatByPhone({ + phone_num: p.formatInternational(), + jobid: job.id + }); + setMessage(`${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}`); + } else { + notification["error"]({ + message: t("messaging.error.invalidphone") + }); + } + } + + if (e.key === "generate") { + //copy it to clipboard. + navigator.clipboard.writeText( + `${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}` + ); + } + } + }; + + const handleVoidJob = async () => { + //delete the job. + const result = await voidJob({ + variables: { + jobId: job.id, + job: { + status: bodyshop.md_ro_statuses.default_void, + voided: true, + scheduled_in: null, + scheduled_completion: null, + inproduction: false, + date_void: new Date() + }, + note: [ + { + jobid: job.id, + created_by: currentUser.email, + audit: true, + text: t("jobs.labels.voidnote") + } + ] + } + }); + + if (!!!result.errors) { + notification["success"]({ + message: t("jobs.successes.voided") + }); + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.jobvoid(), + type: "jobvoid" + }); + //go back to jobs list. + history(`/manage/`); + } else { + notification["error"]({ + message: t("jobs.errors.voiding", { + error: JSON.stringify(result.errors) + }) + }); + } + }; + + const handleExportCustData = async (e) => { + logImEXEvent("job_export_cust_data"); + let PartnerResponse; + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + PartnerResponse = await axios.post(`/qbo/receivables`, { + jobIds: [job.id], + custDataOnly: true + }); + } else { + //Default is QBD + + let QbXmlResponse; + try { + QbXmlResponse = await axios.post( + "/accounting/qbxml/receivables", + { jobIds: [job.id], custDataOnly: true }, + { + headers: { + Authorization: `Bearer ${await auth.currentUser.getIdToken()}` + } + } + ); + console.log("handle -> XML", QbXmlResponse); + } catch (error) { + console.log("Error getting QBXML from Server.", error); + notification["error"]({ + message: t("jobs.errors.exporting", { + error: "Unable to retrieve QBXML. " + JSON.stringify(error.message) + }) + }); + + return; + } + + //let PartnerResponse; + try { + PartnerResponse = await axios.post("http://localhost:1337/qb/", QbXmlResponse.data, { + headers: { + Authorization: `Bearer ${await auth.currentUser.getIdToken()}` + } + }); + } catch (error) { + console.log("Error connecting to quickbooks or partner.", error); + notification["error"]({ + message: t("jobs.errors.exporting-partner") + }); + + return; + } + } + //Check to see if any of them failed. If they didn't execute the update. + const failedTransactions = PartnerResponse.data.filter((r) => !r.success); + if (failedTransactions.length > 0) { + //Uh oh. At least one was no good. + failedTransactions.forEach((ft) => { + //insert failed export log + notification.open({ + // key: "failedexports", + type: "error", + message: t("jobs.errors.exporting", { + error: ft.errorMessage || "" + }) + }); + }); + + //Handle Failures. + } else { + //Insert success export log. + + notification.open({ + type: "success", + key: "jobsuccessexport", + message: t("jobs.successes.exported") + }); + } + }; + + const handleAlertToggle = (e) => { + logImEXEvent("production_toggle_alert"); + //e.stopPropagation(); + updateJob({ + variables: { + jobId: job.id, + job: { + production_vars: { + ...job.production_vars, + alert: !!job.production_vars && !!job.production_vars.alert ? !job.production_vars.alert : true + } + } + } + }); + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.alertToggle( + !!job.production_vars && !!job.production_vars.alert ? !job.production_vars.alert : true + ), + type: "alertToggle" + }); + }; const handleSuspend = (e) => { logImEXEvent("production_toggle_alert"); @@ -493,614 +467,618 @@ export function JobsDetailHeaderActions({ variables: { jobId: job.id, job: { - suspended: !job.suspended, - }, - }, + suspended: !job.suspended + } + } }); insertAuditTrail({ jobid: job.id, - operation: AuditTrailMapping.jobsuspend( - !!job.suspended ? !job.suspended : true - ), - type: "jobsuspend", + operation: AuditTrailMapping.jobsuspend(!!job.suspended ? !job.suspended : true), + type: "jobsuspend" }); }; - // Function to handle OK - const handleCancelScheduleOK = async () => { - await form.submit(); // Assuming 'form' is the Form instance from useForm() - setIsCancelScheduleModalVisible(false); - }; - - const handleLostSaleFinish = async ({lost_sale_reason}) => { - const jobUpdate = await cancelAllAppointments({ - variables: { - jobid: job.id, - job: { - date_scheduled: null, - scheduled_in: null, - scheduled_completion: null, - lost_sale_reason, - date_lost_sale: new Date(), - status: bodyshop.md_ro_statuses.default_imported, - }, - }, - }); - if (!jobUpdate.errors) { - notification["success"]({ - message: t("appointments.successes.canceled"), - }); - insertAuditTrail({ - jobid: job.id, - operation: - AuditTrailMapping.appointmentcancel(lost_sale_reason), - type: "appointmentcancel", - }); + // Function to handle OK + const handleCancelScheduleOK = async () => { + await form.submit(); // Assuming 'form' is the Form instance from useForm() + setIsCancelScheduleModalVisible(false); + }; + const handleLostSaleFinish = async ({ lost_sale_reason }) => { + const jobUpdate = await cancelAllAppointments({ + variables: { + jobid: job.id, + job: { + date_scheduled: null, + scheduled_in: null, + scheduled_completion: null, + lost_sale_reason, + date_lost_sale: new Date(), + status: bodyshop.md_ro_statuses.default_imported } - }; + } + }); + if (!jobUpdate.errors) { + notification["success"]({ + message: t("appointments.successes.canceled") + }); + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.appointmentcancel(lost_sale_reason), + type: "appointmentcancel" + }); + } + }; - const popOverContent = ( - -
-
- - - - - - - - { - const start = form.getFieldValue("start"); - form.setFieldsValue({end: start.add(30, "minutes")}); - }} - /> - - ({ - async validator(rule, value) { - if (value) { - const {start} = form.getFieldsValue(); - if (dayjs(start).isAfter(dayjs(value))) { - return Promise.reject( - t("employees.labels.endmustbeafterstart") - ); - } else { - return Promise.resolve(); - } - } else { - return Promise.resolve(); - } - }, - }), - ]} - > - - - - - - - - - - - -
-
- ); - - const menuItems = [ - { - key: 'schedule', - disabled: !jobInPreProduction || !job.converted || jobRO, - label: t("jobs.actions.schedule"), - onClick: () => { - logImEXEvent("job_header_schedule"); - setScheduleContext({ - actions: {refetch: refetch}, - context: { - jobId: job.id, - job: job, - alt_transport: job.alt_transport, - }, - }); - }, - }, - { - key: 'cancelallappointments', - onClick: () => { - if (job.status !== bodyshop.md_ro_statuses.default_scheduled) { - return; + const popOverContent = ( + +
+
+ + + + + + + + { + const start = form.getFieldValue("start"); + form.setFieldsValue({ end: start.add(30, "minutes") }); + }} + /> + + ({ + async validator(rule, value) { + if (value) { + const { start } = form.getFieldsValue(); + if (dayjs(start).isAfter(dayjs(value))) { + return Promise.reject(t("employees.labels.endmustbeafterstart")); + } else { + return Promise.resolve(); + } + } else { + return Promise.resolve(); + } } - showCancelScheduleModal() - }, - disabled: job.status !== bodyshop.md_ro_statuses.default_scheduled, - label: t("menus.jobsactions.cancelallappointments") - }, - ...InstanceRenderManager({imex: [ { - key: 'intake', - disabled: !!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO, - label: !!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO ? ( - t("jobs.actions.intake") - ) : ( - - {t("jobs.actions.intake")} - - ) + }) + ]} + > + + + + + + + + + + + +
+
+ ); + + const menuItems = [ + { + key: "schedule", + disabled: !jobInPreProduction || !job.converted || jobRO, + label: t("jobs.actions.schedule"), + onClick: () => { + logImEXEvent("job_header_schedule"); + setScheduleContext({ + actions: { refetch: refetch }, + context: { + jobId: job.id, + job: job, + alt_transport: job.alt_transport + } + }); + } + }, + { + key: "cancelallappointments", + onClick: () => { + if (job.status !== bodyshop.md_ro_statuses.default_scheduled) { + return; + } + showCancelScheduleModal(); }, - { - key: 'deliver', + disabled: job.status !== bodyshop.md_ro_statuses.default_scheduled, + label: t("menus.jobsactions.cancelallappointments") + }, + ...InstanceRenderManager({ + imex: [ + { + key: "intake", + disabled: !!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO, + label: + !!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO ? ( + t("jobs.actions.intake") + ) : ( + {t("jobs.actions.intake")} + ) + }, + { + key: "deliver", disabled: !jobInProduction || jobRO, label: !jobInProduction ? ( - t("jobs.actions.deliver") + t("jobs.actions.deliver") ) : ( - - {t("jobs.actions.deliver")} - + {t("jobs.actions.deliver")} ) - }, - { - key: 'checklist', + }, + { + key: "checklist", disabled: !job.converted, - label: - {t("jobs.actions.viewchecklist")} - - },], rome: "USE_IMEX", promanager:[ -{ - key:'toggleproduction', - disabled: !job.converted || jobRO, - label: -} - - ]}), - ...(InstanceRenderManager({ - imex: true, - rome: "USE_IMEX", - promanager: HasFeatureAccess({ featureName: 'timetickets', bodyshop }), - }) ? [ { + label: {t("jobs.actions.viewchecklist")} + } + ], + rome: "USE_IMEX", + promanager: [ + { + key: "toggleproduction", + disabled: !job.converted || jobRO, + label: + } + ] + }), + ...(InstanceRenderManager({ + imex: true, + rome: "USE_IMEX", + promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop }) + }) + ? [ + { key: "entertimetickets", disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced), label: t("timetickets.actions.enter"), onClick: () => { - logImEXEvent("job_header_enter_time_ticekts"); + logImEXEvent("job_header_enter_time_ticekts"); - setTimeTicketContext({ - actions: {}, - context: { - jobId: job.id, - created_by: currentUser.displayName - ? currentUser.email.concat(" | ", currentUser.displayName) - : currentUser.email, - }, - }); - } - }] : []) - ]; - - if (bodyshop.md_tasks_presets.enable_tasks) { - menuItems.push({ - key: 'claimtimetickettasks', - disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced), - onClick: () => { - setTimeTicketTaskContext({ - actions: {}, - context: {jobid: job.id}, - }); - }, - label: t("timetickets.actions.claimtasks") - }); - } - - menuItems.push( - { - key: 'enterpayments', - disabled: !job.converted, - label: t("menus.header.enterpayment"), - onClick: () => { - logImEXEvent("job_header_enter_payment"); - - setPaymentContext({ - actions: {}, - context: {jobid: job.id}, - }); - } - }); - - if (ImEXPay.treatment === "on") { - menuItems.push({ - key: 'entercardpayments', - disabled: !job.converted, - label: t("menus.header.entercardpayment"), - onClick: () => { - logImEXEvent("job_header_enter_card_payment"); - - setCardPaymentContext({ - actions: {}, - context: {jobid: job.id}, - }); + setTimeTicketContext({ + actions: {}, + context: { + jobId: job.id, + created_by: currentUser.displayName + ? currentUser.email.concat(" | ", currentUser.displayName) + : currentUser.email } - }, - ); - } + }); + } + } + ] + : []) + ]; - if(HasFeatureAccess({featureName: 'courtesycars'})){ - menuItems.push({ - key: 'cccontract', - disabled: jobRO || !job.converted, - label: - {t("menus.jobsactions.newcccontract")} - + if (bodyshop.md_tasks_presets.enable_tasks) { + menuItems.push({ + key: "claimtimetickettasks", + disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced), + onClick: () => { + setTimeTicketTaskContext({ + actions: {}, + context: { jobid: job.id } + }); + }, + label: t("timetickets.actions.claimtasks") + }); + } + + menuItems.push({ + key: "enterpayments", + disabled: !job.converted, + label: t("menus.header.enterpayment"), + onClick: () => { + logImEXEvent("job_header_enter_payment"); + + setPaymentContext({ + actions: {}, + context: { jobid: job.id } }); } + }); - menuItems.push( - job.inproduction ? - { - key: 'addtoproduction', - disabled: !job.converted, - label: t("jobs.actions.removefromproduction"), - onClick: () => AddToProduction(client, job.id, refetch, true) - } : - { - key: 'addtoproduction', - disabled: !job.converted, - label: t("jobs.actions.addtoproduction"), - onClick: () => AddToProduction(client, job.id, refetch) - } - ); + if (ImEXPay.treatment === "on") { + menuItems.push({ + key: "entercardpayments", + disabled: !job.converted, + label: t("menus.header.entercardpayment"), + onClick: () => { + logImEXEvent("job_header_enter_card_payment"); - menuItems.push( + setCardPaymentContext({ + actions: {}, + context: { jobid: job.id } + }); + } + }); + } + + if (HasFeatureAccess({ featureName: "courtesycars" })) { + menuItems.push({ + key: "cccontract", + disabled: jobRO || !job.converted, + label: ( + + {t("menus.jobsactions.newcccontract")} + + ) + }); + } + + menuItems.push( + job.inproduction + ? { + key: "addtoproduction", + disabled: !job.converted, + label: t("jobs.actions.removefromproduction"), + onClick: () => AddToProduction(client, job.id, refetch, true) + } + : { + key: "addtoproduction", + disabled: !job.converted, + label: t("jobs.actions.addtoproduction"), + onClick: () => AddToProduction(client, job.id, refetch) + } + ); + + menuItems.push( + { + key: "togglesuspend", + onClick: handleSuspend, + label: job.suspended ? t("production.actions.unsuspend") : t("production.actions.suspend") + }, + { + key: "toggleAlert", + onClick: handleAlertToggle, + label: + job.production_vars && job.production_vars.alert + ? t("production.labels.alertoff") + : t("production.labels.alerton") + }, + { + key: "dupe", + label: t("menus.jobsactions.duplicate"), + children: [ { - key: 'togglesuspend', - onClick: handleSuspend, - label: job.suspended - ? t("production.actions.unsuspend") - : t("production.actions.suspend") + key: "duplicate", + label: ( + e.stopPropagation()} + onConfirm={handleDuplicate} + getPopupContainer={(trigger) => trigger.parentNode} + > + {t("menus.jobsactions.duplicate")} + + ) }, { - key: 'toggleAlert', - onClick: handleAlertToggle, - label: job.production_vars && job.production_vars.alert - ? t("production.labels.alertoff") - : t("production.labels.alerton") - }, - { - key: 'dupe', - label: t("menus.jobsactions.duplicate"), - children: [ - { - key: 'duplicate', - label: e.stopPropagation()} - onConfirm={handleDuplicate} - getPopupContainer={(trigger) => trigger.parentNode} - > - {t("menus.jobsactions.duplicate")} - - }, - { - key: 'duplicatenolines', - label: e.stopPropagation()} - onConfirm={handleDuplicateConfirm} - getPopupContainer={(trigger) => trigger.parentNode} - > - {t("menus.jobsactions.duplicatenolines")} - - } - ] - }, - ...InstanceRenderManager({ - imex: true, - rome: true, - promanager: HasFeatureAccess({ featureName: 'bills', bodyshop }), - }) ? [ { - key: 'postbills', + key: "duplicatenolines", + label: ( + e.stopPropagation()} + onConfirm={handleDuplicateConfirm} + getPopupContainer={(trigger) => trigger.parentNode} + > + {t("menus.jobsactions.duplicatenolines")} + + ) + } + ] + }, + ...(InstanceRenderManager({ + imex: true, + rome: true, + promanager: HasFeatureAccess({ featureName: "bills", bodyshop }) + }) + ? [ + { + key: "postbills", disabled: !job.converted, label: t("jobs.actions.postbills"), onClick: () => { - logImEXEvent("job_header_enter_bills"); + logImEXEvent("job_header_enter_bills"); - setBillEnterContext({ - actions: {refetch: refetch}, - context: { - job: job, - }, - }); - } - },] : [], - - { - key: 'addtopartsqueue', - disabled: !job.converted || !jobInProduction || jobRO, - label: t("jobs.actions.addtopartsqueue"), - onClick: async () => { - const result = await updateJob({ - variables: { - jobId: job.id, - job: {queued_for_parts: true}, - }, - }); - - if (!!!result.errors) { - notification["success"]({ - message: t("jobs.successes.partsqueue"), - }); - } else { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); + setBillEnterContext({ + actions: { refetch: refetch }, + context: { + job: job } + }); } - }, - { - key: 'closejob', - disabled: !jobInPostProduction, - label: !jobInPostProduction ? ( - t("menus.jobsactions.closejob") - ) : ( - - {t("menus.jobsactions.closejob")} - - ) - }, - { - key: 'admin', - label: - {t("menus.jobsactions.admin")} - - } - ); + } + ] + : []), - if( InstanceRenderManager({ + { + key: "addtopartsqueue", + disabled: !job.converted || !jobInProduction || jobRO, + label: t("jobs.actions.addtopartsqueue"), + onClick: async () => { + const result = await updateJob({ + variables: { + jobId: job.id, + job: { queued_for_parts: true } + } + }); + + if (!!!result.errors) { + notification["success"]({ + message: t("jobs.successes.partsqueue") + }); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + } + }, + { + key: "closejob", + disabled: !jobInPostProduction, + label: !jobInPostProduction ? ( + t("menus.jobsactions.closejob") + ) : ( + + {t("menus.jobsactions.closejob")} + + ) + }, + { + key: "admin", + label: ( + + {t("menus.jobsactions.admin")} + + ) + } + ); + + if ( + InstanceRenderManager({ imex: true, rome: true, - promanager: HasFeatureAccess({ featureName: 'export', bodyshop }), - })){ + promanager: HasFeatureAccess({ featureName: "export", bodyshop }) + }) + ) { + menuItems.push({ + key: "exportcustdata", + disabled: !job.converted, + label: t("jobs.actions.exportcustdata"), + onClick: handleExportCustData + }); + } - menuItems.push( - { - key: 'exportcustdata', - disabled: !job.converted, - label: t("jobs.actions.exportcustdata"), - onClick: handleExportCustData - } + if (HasFeatureAccess({ featureName: "csi", bodyshop })) { + const children = [ + { + key: "email", + disabled: !!!job.ownr_ea, + label: t("general.labels.email"), + onClick: handleCreateCsi + }, + { + key: "text", + disabled: !!!job.ownr_ph1, + label: t("general.labels.text"), + onClick: handleCreateCsi + }, + { + key: "generate", + disabled: job.csiinvites && job.csiinvites.length > 0, + label: t("jobs.actions.generatecsi"), + onClick: handleCreateCsi + } + ]; + + if (job?.csiinvites?.length) { + children.push( + { + type: "divider" + }, + ...job.csiinvites.map((item, idx) => { + return item.completedon + ? { + key: idx, + label: ( + + {item.completedon} + + ) + } + : { + key: idx, + onClick: () => { + navigator.clipboard.writeText(`${window.location.protocol}//${window.location.host}/csi/${item.id}`); + }, + label: t("general.actions.copylink") + }; + }) ); } - - if (HasFeatureAccess({featureName: "csi", bodyshop})) { - const children = [ - { - key: 'email', - disabled: !!!job.ownr_ea, - label: t("general.labels.email"), - onClick: handleCreateCsi - }, - { - key: 'text', - disabled: !!!job.ownr_ph1, - label: t("general.labels.text"), - onClick: handleCreateCsi - }, - { - key: 'generate', - disabled: job.csiinvites && job.csiinvites.length > 0, - label: t("jobs.actions.generatecsi"), - onClick: handleCreateCsi - }, - ]; - - if (job?.csiinvites?.length) { - children.push( - { - type: "divider" - }, - ...job.csiinvites.map((item, idx) => { - return item.completedon ? - { - key: idx, - label: - {item.completedon} - - } : - { - key: idx, - onClick: () => { - navigator.clipboard.writeText( - `${window.location.protocol}//${window.location.host}/csi/${item.id}` - ); - }, - label: t("general.actions.copylink") - } - }), - ) - } - menuItems.push( - { - key: 'sendcsi', - label: t("jobs.actions.sendcsi"), - disabled: !job.converted, - children - } - ); - } - menuItems.push({ - key: 'jobcosting', - disabled: !job.converted, - label: t("jobs.labels.jobcosting"), - onClick: () => { - logImEXEvent("job_header_job_costing"); + key: "sendcsi", + label: t("jobs.actions.sendcsi"), + disabled: !job.converted, + children + }); + } - setJobCostingContext({ - actions: {refetch: refetch}, - context: { - jobId: job.id, - }, - }); - } + menuItems.push({ + key: "jobcosting", + disabled: !job.converted, + label: t("jobs.labels.jobcosting"), + onClick: () => { + logImEXEvent("job_header_job_costing"); + + setJobCostingContext({ + actions: { refetch: refetch }, + context: { + jobId: job.id } - ); - - if (job && !job.converted) { - menuItems.push( - { - key: 'deletejob', - label: e.stopPropagation()} - onConfirm={handleDeleteJob} - > - {t("menus.jobsactions.deletejob")} - - } - ); + }); } + }); - menuItems.push( - { - key: 'manualevent', - onClick: (e) => { - setVisibility(true); - }, - label: t("appointments.labels.manualevent") - } - ) + if (job && !job.converted) { + menuItems.push({ + key: "deletejob", + label: ( + e.stopPropagation()} + onConfirm={handleDeleteJob} + > + {t("menus.jobsactions.deletejob")} + + ) + }); + } - if (!jobRO && job.converted) { - menuItems.push({ - key: 'voidjob', - label: - e.stopPropagation()} - onConfirm={handleVoidJob} - > - {t("menus.jobsactions.void")} - - - }); - } + menuItems.push({ + key: "manualevent", + onClick: (e) => { + setVisibility(true); + }, + label: t("appointments.labels.manualevent") + }); - const menu = { - items: menuItems, - key: 'popovermenu' - } + if (!jobRO && job.converted) { + menuItems.push({ + key: "voidjob", + label: ( + + e.stopPropagation()} + onConfirm={handleVoidJob} + > + {t("menus.jobsactions.void")} + + + ) + }); + } - return ( - <> - - {t("general.actions.cancel")} - , - , - ]} - > -
{ - console.log(s); - handleLostSaleFinish(s); - }} - > - - ({ + label: lsr, + value: lsr + }))} + /> + + +
+ + + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsDetailHeaderActions); +export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailHeaderActions); diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util.js b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util.js index b7c69ab61..7706f91bc 100644 --- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util.js +++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util.js @@ -1,138 +1,124 @@ import Axios from "axios"; import _ from "lodash"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {INSERT_NEW_JOB, QUERY_JOB_FOR_DUPE} from "../../graphql/jobs.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { INSERT_NEW_JOB, QUERY_JOB_FOR_DUPE } from "../../graphql/jobs.queries"; import dayjs from "../../utils/day"; import i18n from "i18next"; -export default async function DuplicateJob( - apolloClient, - jobId, - config, - completionCallback, - keepJobLines = false -) { - logImEXEvent("job_duplicate"); +export default async function DuplicateJob(apolloClient, jobId, config, completionCallback, keepJobLines = false) { + logImEXEvent("job_duplicate"); - const {defaultOpenStatus} = config; - //get a list of all fields on the job - const res = await apolloClient.query({ - query: QUERY_JOB_FOR_DUPE, - variables: {id: jobId}, - }); + const { defaultOpenStatus } = config; + //get a list of all fields on the job + const res = await apolloClient.query({ + query: QUERY_JOB_FOR_DUPE, + variables: { id: jobId } + }); - const {jobs_by_pk} = res.data; - const existingJob = _.cloneDeep(jobs_by_pk); - delete existingJob.__typename; - delete existingJob.id; - delete existingJob.createdat; - delete existingJob.updatedat; - delete existingJob.cieca_stl; - delete existingJob.cieca_ttl; + const { jobs_by_pk } = res.data; + const existingJob = _.cloneDeep(jobs_by_pk); + delete existingJob.__typename; + delete existingJob.id; + delete existingJob.createdat; + delete existingJob.updatedat; + delete existingJob.cieca_stl; + delete existingJob.cieca_ttl; - const newJob = { - ...existingJob, - status: defaultOpenStatus, - }; + const newJob = { + ...existingJob, + status: defaultOpenStatus + }; - const _tempLines = _.cloneDeep(existingJob.joblines); - _tempLines.forEach((line) => { - delete line.id; - delete line.__typename; - line.manual_line = true; - }); - newJob.joblines = keepJobLines ? _tempLines : []; + const _tempLines = _.cloneDeep(existingJob.joblines); + _tempLines.forEach((line) => { + delete line.id; + delete line.__typename; + line.manual_line = true; + }); + newJob.joblines = keepJobLines ? _tempLines : []; - delete newJob.joblines; - newJob.joblines = keepJobLines ? {data: _tempLines} : null; + delete newJob.joblines; + newJob.joblines = keepJobLines ? { data: _tempLines } : null; - const res2 = await apolloClient.mutate({ - mutation: INSERT_NEW_JOB, - variables: {job: [newJob]}, - }); - await Axios.post("/job/totalsssu", { - id: res2.data.insert_jobs.returning[0].id, - }); + const res2 = await apolloClient.mutate({ + mutation: INSERT_NEW_JOB, + variables: { job: [newJob] } + }); + await Axios.post("/job/totalsssu", { + id: res2.data.insert_jobs.returning[0].id + }); - if (completionCallback) - completionCallback(res2.data.insert_jobs.returning[0].id); + if (completionCallback) completionCallback(res2.data.insert_jobs.returning[0].id); - //insert the new job. call the callback with the returned ID when done. + //insert the new job. call the callback with the returned ID when done. -return; + return; } -export async function CreateIouForJob( - apolloClient, - jobId, - config, - jobLinesToKeep -) { - logImEXEvent("job_create_iou"); +export async function CreateIouForJob(apolloClient, jobId, config, jobLinesToKeep) { + logImEXEvent("job_create_iou"); - const {status} = config; - //get a list of all fields on the job - const res = await apolloClient.query({ - query: QUERY_JOB_FOR_DUPE, - variables: {id: jobId}, - }); + const { status } = config; + //get a list of all fields on the job + const res = await apolloClient.query({ + query: QUERY_JOB_FOR_DUPE, + variables: { id: jobId } + }); - const {jobs_by_pk} = res.data; - const existingJob = _.cloneDeep(jobs_by_pk); - delete existingJob.__typename; - delete existingJob.id; - delete existingJob.createdat; - delete existingJob.updatedat; - delete existingJob.cieca_stl; - delete existingJob.cieca_ttl; + const { jobs_by_pk } = res.data; + const existingJob = _.cloneDeep(jobs_by_pk); + delete existingJob.__typename; + delete existingJob.id; + delete existingJob.createdat; + delete existingJob.updatedat; + delete existingJob.cieca_stl; + delete existingJob.cieca_ttl; - const newJob = { - ...existingJob, + const newJob = { + ...existingJob, - converted: true, - status: status, - iouparent: jobId, - date_open: dayjs(), - audit_trails: { - data: [ - { - useremail: config.useremail, - bodyshopid: config.bodyshopid, - operation: i18n.t("audit_trail.messages.jobioucreated"), - }, - ], - }, - }; + converted: true, + status: status, + iouparent: jobId, + date_open: dayjs(), + audit_trails: { + data: [ + { + useremail: config.useremail, + bodyshopid: config.bodyshopid, + operation: i18n.t("audit_trail.messages.jobioucreated") + } + ] + } + }; - const selectedJoblinesIds = jobLinesToKeep.map((l) => l.id); + const selectedJoblinesIds = jobLinesToKeep.map((l) => l.id); - const _tempLines = _.cloneDeep(existingJob.joblines).filter((l) => - selectedJoblinesIds.includes(l.id) - ); - _tempLines.forEach((line) => { - delete line.id; - delete line.__typename; - line.oem_partno = `${line.oem_partno ? `${line.oem_partno} - ` : ``}IOU $${ - (line.act_price && line.act_price.toFixed(2)) || 0 - }/${line.mod_lb_hrs || 0}hrs`; - line.act_price = 0; - line.mod_lb_hrs = 0; - line.manual_line = true; - }); + const _tempLines = _.cloneDeep(existingJob.joblines).filter((l) => selectedJoblinesIds.includes(l.id)); + _tempLines.forEach((line) => { + delete line.id; + delete line.__typename; + line.oem_partno = `${line.oem_partno ? `${line.oem_partno} - ` : ``}IOU $${ + (line.act_price && line.act_price.toFixed(2)) || 0 + }/${line.mod_lb_hrs || 0}hrs`; + line.act_price = 0; + line.mod_lb_hrs = 0; + line.manual_line = true; + }); - delete newJob.joblines; - newJob.joblines = {data: _tempLines}; + delete newJob.joblines; + newJob.joblines = { data: _tempLines }; - const res2 = await apolloClient.mutate({ - mutation: INSERT_NEW_JOB, - variables: {job: [newJob]}, - }); + const res2 = await apolloClient.mutate({ + mutation: INSERT_NEW_JOB, + variables: { job: [newJob] } + }); - Axios.post("/job/totalsssu", { - id: res2.data.insert_jobs.returning[0].id, - }); + Axios.post("/job/totalsssu", { + id: res2.data.insert_jobs.returning[0].id + }); - //insert the new job. call the callback with the returned ID when done. + //insert the new job. call the callback with the returned ID when done. - return res2.data.insert_jobs.returning[0].id; + return res2.data.insert_jobs.returning[0].id; } diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.toggle-production.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.toggle-production.jsx index 9d83b3e0c..a608c6e97 100644 --- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.toggle-production.jsx +++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.toggle-production.jsx @@ -1,33 +1,28 @@ -import { useMutation } from '@apollo/client'; -import { Button, Form, notification, Popover, Space } from 'antd'; -import dayjs from 'dayjs'; -import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { connect } from 'react-redux'; -import { createStructuredSelector } from 'reselect'; -import { JOB_PRODUCTION_TOGGLE } from '../../graphql/jobs.queries'; -import { insertAuditTrail } from '../../redux/application/application.actions'; -import { selectJobReadOnly } from '../../redux/application/application.selectors'; -import { selectBodyshop } from '../../redux/user/user.selectors'; -import AuditTrailMapping from '../../utils/AuditTrailMappings'; -import { DateTimeFormatterFunction } from '../../utils/DateFormatter'; -import FormDateTimePickerComponent from '../form-date-time-picker/form-date-time-picker.component'; +import { useMutation } from "@apollo/client"; +import { Button, Form, notification, Popover, Space } from "antd"; +import dayjs from "dayjs"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { JOB_PRODUCTION_TOGGLE } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import AuditTrailMapping from "../../utils/AuditTrailMappings"; +import { DateTimeFormatterFunction } from "../../utils/DateFormatter"; +import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser, bodyshop: selectBodyshop, - jobRO: selectJobReadOnly, + jobRO: selectJobReadOnly }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({ jobid, operation }) => dispatch(insertAuditTrail({ jobid, operation })), + insertAuditTrail: ({ jobid, operation }) => dispatch(insertAuditTrail({ jobid, operation })) }); -export function JobsDetailHeaderActionsToggleProduction({ - bodyshop, - job, - jobRO, - insertAuditTrail, -}) { +export function JobsDetailHeaderActionsToggleProduction({ bodyshop, job, jobRO, insertAuditTrail }) { const [scenario, setScenario] = useState(false); const [loading, setLoading] = useState(false); const [mutationUpdateJob] = useMutation(JOB_PRODUCTION_TOGGLE); @@ -38,11 +33,11 @@ export function JobsDetailHeaderActionsToggleProduction({ //Figure out what scenario were in, populate accodingly if (job && bodyshop) { if (bodyshop.md_ro_statuses.pre_production_statuses.includes(job.status)) { - setScenario('pre'); + setScenario("pre"); } else if (bodyshop.md_ro_statuses.production_statuses.includes(job.status)) { - setScenario('prod'); + setScenario("prod"); } else { - setScenario('post'); + setScenario("post"); } } }, [job, setScenario, bodyshop]); @@ -55,23 +50,21 @@ export function JobsDetailHeaderActionsToggleProduction({ job: { ...values, status: - scenario === 'pre' - ? bodyshop.md_ro_statuses.default_arrived - : bodyshop.md_ro_statuses.default_delivered, - inproduction: scenario === 'pre' ? true : false, - }, - }, + scenario === "pre" ? bodyshop.md_ro_statuses.default_arrived : bodyshop.md_ro_statuses.default_delivered, + inproduction: scenario === "pre" ? true : false + } + } }); if (!res.errors) { - notification['success']({ - message: t('jobs.successes.converted'), + notification["success"]({ + message: t("jobs.successes.converted") }); insertAuditTrail({ jobid: job.id, operation: - scenario === 'pre' + scenario === "pre" ? AuditTrailMapping.jobintake( res.data.update_jobs.returning[0].status, DateTimeFormatterFunction(values.scheduled_completion) @@ -79,7 +72,7 @@ export function JobsDetailHeaderActionsToggleProduction({ : AuditTrailMapping.jobdelivery( res.data.update_jobs.returning[0].status, DateTimeFormatterFunction(values.actual_completion) - ), + ) }); } setLoading(false); @@ -96,56 +89,56 @@ export function JobsDetailHeaderActionsToggleProduction({ scheduled_completion: job.scheduled_completion, actual_completion: job.actual_completion, scheduled_deliver: job.scheduled_deliver, - actual_delivery: job.actual_delivery, + actual_delivery: job.actual_delivery }} > - {scenario === 'pre' && ( + {scenario === "pre" && ( <> - + )} - {scenario === 'prod' && ( + {scenario === "prod" && ( <> - + @@ -153,15 +146,15 @@ export function JobsDetailHeaderActionsToggleProduction({ @@ -175,13 +168,10 @@ export function JobsDetailHeaderActionsToggleProduction({ getPopupContainer={(trigger) => trigger.parentNode} trigger="click" > - {scenario === 'pre' && t('jobs.actions.intake')} - {scenario === 'prod' && t('jobs.actions.deliver')} + {scenario === "pre" && t("jobs.actions.intake")} + {scenario === "prod" && t("jobs.actions.deliver")} ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsDetailHeaderActionsToggleProduction); +export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailHeaderActionsToggleProduction); 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 6b7479b13..003143a86 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 @@ -1,326 +1,284 @@ -import {BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, WarningFilled,} from "@ant-design/icons"; -import {Card, Col, Divider, Row, Space, Tag, Tooltip} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, WarningFilled } from "@ant-design/icons"; +import { Card, Col, Divider, Row, Space, Tag, Tooltip } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import DataLabel from "../data-label/data-label.component"; import JobAltTransportChange from "../job-at-change/job-at-change.component"; import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container"; import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component"; -import {OwnerNameDisplayFunction} from "../owner-name-display/owner-name-display.component"; +import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component"; -import ProductionListColumnProductionNote - from "../production-list-columns/production-list-columns.productionnote.component"; +import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; import "./jobs-detail-header.styles.scss"; import dayjs from "../../utils/day"; const mapStateToProps = createStructuredSelector({ - jobRO: selectJobReadOnly, - bodyshop: selectBodyshop, + jobRO: selectJobReadOnly, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setPrintCenterContext: (context) => - dispatch(setModalContext({context: context, modal: "printCenter"})), + setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" })) }); const colSpan = { - xs: { - span: 24, - }, - sm: { - span: 24, - }, - md: { - span: 12, - }, - lg: { - span: 6, - }, - xl: { - span: 6, - }, + xs: { + span: 24 + }, + sm: { + span: 24 + }, + md: { + span: 12 + }, + lg: { + span: 6 + }, + xl: { + span: 6 + } }; -export function JobsDetailHeader({job, bodyshop, disabled}) { - const {t} = useTranslation(); - const [notesClamped, setNotesClamped] = useState(true); - const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""} +export function JobsDetailHeader({ job, bodyshop, disabled }) { + const { t } = useTranslation(); + const [notesClamped, setNotesClamped] = useState(true); + const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim(); - const bodyHrs = job.joblines - .filter((j) => j.mod_lbr_ty !== "LAR") - .reduce((acc, val) => acc + val.mod_lb_hrs, 0); - const refinishHrs = job.joblines - .filter((line) => line.mod_lbr_ty === "LAR") - .reduce((acc, val) => acc + val.mod_lb_hrs, 0); + const bodyHrs = job.joblines.filter((j) => j.mod_lbr_ty !== "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0); + const refinishHrs = job.joblines + .filter((line) => line.mod_lbr_ty === "LAR") + .reduce((acc, val) => acc + val.mod_lb_hrs, 0); - const ownerTitle = OwnerNameDisplayFunction(job).trim(); + const ownerTitle = OwnerNameDisplayFunction(job).trim(); - return ( - -
- -
- - - {job.status} - {job.inproduction && ( - - {t("jobs.labels.inproduction")} - - )} - {job.suspended && ( - - )} - {job.iouparent && ( - - - - - - )} - {job.production_vars && job.production_vars.alert ? ( - - ) : null} - {job.status === bodyshop.md_ro_statuses.default_scheduled && - job.scheduled_in ? ( - - - {job.scheduled_in} - - - ) : null} - - - - - - - {job.ins_co_nm} - - {job.clm_no} - - {job.po_number} - - - {job.clm_total} - / - {job.owner_owing} - + return ( + +
+ +
+ + + {job.status} + {job.inproduction && ( + + {t("jobs.labels.inproduction")} + + )} + {job.suspended && } + {job.iouparent && ( + + + + + + )} + {job.production_vars && job.production_vars.alert ? ( + + ) : null} + {job.status === bodyshop.md_ro_statuses.default_scheduled && job.scheduled_in ? ( + + + {job.scheduled_in} + + + ) : null} + + + + + + {job.ins_co_nm} + {job.clm_no} + + {job.po_number} + + + {job.clm_total} + / + {job.owner_owing} + - - {job.alt_transport} - - - {job?.cccontracts?.length > 0 && ( - - {job.cccontracts.map((c, index) => ( - - {`${c.agreementnumber} - ${c.courtesycar.fleetnumber} ${c.courtesycar.year} ${c.courtesycar.make} ${c.courtesycar.model}`}{index !== job.cccontracts.length - 1 ? "," : null} - - - ))} - - )} + + {job.alt_transport} + + + {job?.cccontracts?.length > 0 && ( + + {job.cccontracts.map((c, index) => ( + + + {`${c.agreementnumber} - ${c.courtesycar.fleetnumber} ${c.courtesycar.year} ${c.courtesycar.make} ${c.courtesycar.model}`} + {index !== job.cccontracts.length - 1 ? "," : null} + + + ))} + + )} - - - + + + - - {job.special_coverage_policy && ( - - - - {t("jobs.labels.specialcoveragepolicy")} - - - )} - {job.ca_gst_registrant && ( - - - - {t("jobs.fields.ca_gst_registrant")} - - - )} - -
-
- - - - {ownerTitle.length > 0 - ? ownerTitle - : t("owner.labels.noownerinfo")} - - ) : ( - - {ownerTitle.length > 0 - ? ownerTitle - : t("owner.labels.noownerinfo")} - - ) - } - > -
- - {disabled ? ( - {job.ownr_ph1} - ) : ( - - )} - - - {disabled ? ( - {job.ownr_ph2} - ) : ( - - )} - - - {`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${ - job.ownr_city || "" - } ${job.ownr_st || ""} ${job.ownr_zip || ""}`} - - - {disabled ? ( - <>{job.ownr_ea || ""} - ) : job.ownr_ea ? ( - {job.ownr_ea} - ) : null} - - {job.owner?.tax_number && ( - - {job.owner?.tax_number || ""} - - )} - - {job.owner?.note || ""} - -
-
- - - - {vehicleTitle.length > 0 - ? vehicleTitle - : t("vehicles.labels.novehinfo")}{" "} - - ) : ( - - {vehicleTitle.length > 0 - ? vehicleTitle - : t("vehicles.labels.novehinfo")} - - ) - ) : ( - - ) - } - > -
- - {`${job.plate_no || t("general.labels.na")} (${`${ - job.plate_st || t("general.labels.na") - }`})`} - - - - {`${job.v_vin || t("general.labels.na")}`} - - {bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? ( - job.v_vin?.length !== 17 ? ( - - ) : null - ) : null} - - - {job.regie_number || t("general.labels.na")} - - - - - {job.vehicle && job.vehicle.notes && ( - setNotesClamped(!notesClamped)} - > - {job.vehicle.notes} - - )} - {job.vehicle && job.vehicle.v_paint_codes && ( - - + + {job.special_coverage_policy && ( + + + + {t("jobs.labels.specialcoveragepolicy")} + + + )} + {job.ca_gst_registrant && ( + + + + {t("jobs.fields.ca_gst_registrant")} + + + )} + +
+
+ + + {ownerTitle.length > 0 ? ownerTitle : t("owner.labels.noownerinfo")} + ) : ( + + {ownerTitle.length > 0 ? ownerTitle : t("owner.labels.noownerinfo")} + + ) + } + > +
+ + {disabled ? ( + {job.ownr_ph1} + ) : ( + + )} + + + {disabled ? ( + {job.ownr_ph2} + ) : ( + + )} + + + {`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${ + job.ownr_city || "" + } ${job.ownr_st || ""} ${job.ownr_zip || ""}`} + + + {disabled ? ( + <>{job.ownr_ea || ""} + ) : job.ownr_ea ? ( + {job.ownr_ea} + ) : null} + + {job.owner?.tax_number && ( + + {job.owner?.tax_number || ""} + + )} + + {job.owner?.note || ""} + +
+
+ + + {vehicleTitle.length > 0 ? vehicleTitle : t("vehicles.labels.novehinfo")} + ) : ( + + {vehicleTitle.length > 0 ? vehicleTitle : t("vehicles.labels.novehinfo")} + + ) + ) : ( + + ) + } + > +
+ + {`${job.plate_no || t("general.labels.na")} (${`${job.plate_st || t("general.labels.na")}`})`} + + + {`${job.v_vin || t("general.labels.na")}`} + {bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? ( + job.v_vin?.length !== 17 ? ( + + ) : null + ) : null} + + {job.regie_number || t("general.labels.na")} + + + + {job.vehicle && job.vehicle.notes && ( + setNotesClamped(!notesClamped)} + > + {job.vehicle.notes} + + )} + {job.vehicle && job.vehicle.v_paint_codes && ( + + {Object.keys(job.vehicle.v_paint_codes) - .filter( - (key) => - job.vehicle.v_paint_codes[key] !== "" && - job.vehicle.v_paint_codes[key] !== null && - job.vehicle.v_paint_codes[key] !== undefined - ) - .map((key, idx) => ( - {job.vehicle.v_paint_codes[key]} - ))} + .filter( + (key) => + job.vehicle.v_paint_codes[key] !== "" && + job.vehicle.v_paint_codes[key] !== null && + job.vehicle.v_paint_codes[key] !== undefined + ) + .map((key, idx) => ( + {job.vehicle.v_paint_codes[key]} + ))} - - )} -
-
- - - -
- - - - {bodyHrs.toFixed(1)} / {refinishHrs.toFixed(1)} /{" "} - {(bodyHrs + refinishHrs).toFixed(1)} - -
-
- - - ); + + )} + + + + + +
+ + + + {bodyHrs.toFixed(1)} / {refinishHrs.toFixed(1)} / {(bodyHrs + refinishHrs).toFixed(1)} + +
+
+ + + ); } export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailHeader); diff --git a/client/src/components/jobs-detail-labor/jobs-detail-labor.component.jsx b/client/src/components/jobs-detail-labor/jobs-detail-labor.component.jsx index 10a16449f..0e8ebff57 100644 --- a/client/src/components/jobs-detail-labor/jobs-detail-labor.component.jsx +++ b/client/src/components/jobs-detail-labor/jobs-detail-labor.component.jsx @@ -1,109 +1,110 @@ -import {Col, Row} from "antd"; +import { Col, Row } from "antd"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; import LaborAllocationsTableComponent from "../labor-allocations-table/labor-allocations-table.component"; import TimeTicketList from "../time-ticket-list/time-ticket-list.component"; import PayrollLaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.payroll.component"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - jobRO: selectJobReadOnly, - bodyshop: selectBodyshop, + jobRO: selectJobReadOnly, + bodyshop: selectBodyshop }); export default connect(mapStateToProps, null)(JobsDetailLaborContainer); const ticketSpan = { - xs: { - span: 24, - }, - sm: { - span: 24, - }, - md: { - span: 24, - }, - lg: { - span: 24, - }, - xl: { - span: 16, - }, + xs: { + span: 24 + }, + sm: { + span: 24 + }, + md: { + span: 24 + }, + lg: { + span: 24 + }, + xl: { + span: 16 + } }; const adjSpan = { - xs: { - span: 24, - }, - sm: { - span: 24, - }, - md: { - span: 24, - }, - lg: { - span: 24, - }, - xl: { - span: 8, - }, + xs: { + span: 24 + }, + sm: { + span: 24 + }, + md: { + span: 24 + }, + lg: { + span: 24 + }, + xl: { + span: 8 + } }; export function JobsDetailLaborContainer({ - bodyshop, - jobRO, - job, - jobId, - joblines, - timetickets, - refetch, - loading, - techConsole, - adjustments, - }) { - const {treatments: {Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, - }); + bodyshop, + jobRO, + job, + jobId, + joblines, + timetickets, + refetch, + loading, + techConsole, + adjustments +}) { + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); + return ( + + + + - return ( - - - - - - {Enhanced_Payroll.treatment === "on" ? ( - - - - ) : ( - - - - )} - - ); + {Enhanced_Payroll.treatment === "on" ? ( + + + + ) : ( + + + + )} + + ); } diff --git a/client/src/components/jobs-detail-labor/jobs-detail-labor.container.jsx b/client/src/components/jobs-detail-labor/jobs-detail-labor.container.jsx index 8416ace4c..2991f45da 100644 --- a/client/src/components/jobs-detail-labor/jobs-detail-labor.container.jsx +++ b/client/src/components/jobs-detail-labor/jobs-detail-labor.container.jsx @@ -1,29 +1,29 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import React from "react"; -import {GET_LINE_TICKET_BY_PK} from "../../graphql/jobs-lines.queries"; +import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries"; import AlertComponent from "../alert/alert.component"; import JobsDetailLaborComponent from "./jobs-detail-labor.component"; -export default function JobsDetailLaborContainer({jobId, techConsole, job}) { - const {loading, error, data, refetch} = useQuery(GET_LINE_TICKET_BY_PK, { - variables: {id: jobId}, - skip: !!!jobId, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); +export default function JobsDetailLaborContainer({ jobId, techConsole, job }) { + const { loading, error, data, refetch } = useQuery(GET_LINE_TICKET_BY_PK, { + variables: { id: jobId }, + skip: !!!jobId, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - if (error) return ; + if (error) return ; - return ( - - ); + return ( + + ); } diff --git a/client/src/components/jobs-detail-pli/jobs-detail-pli.component.jsx b/client/src/components/jobs-detail-pli/jobs-detail-pli.component.jsx index 6e3e414af..2aa72db2d 100644 --- a/client/src/components/jobs-detail-pli/jobs-detail-pli.component.jsx +++ b/client/src/components/jobs-detail-pli/jobs-detail-pli.component.jsx @@ -1,4 +1,4 @@ -import {Col, Row} from "antd"; +import { Col, Row } from "antd"; import React from "react"; import AlertComponent from "../alert/alert.component"; import BillDetailEditcontainer from "../bill-detail-edit/bill-detail-edit.container"; @@ -10,54 +10,44 @@ import PartsDispatchTable from "../parts-dispatch-table/parts-dispatch-table.com import FeatureWrapperComponent from "../feature-wrapper/feature-wrapper.component"; export default function JobsDetailPliComponent({ - job, - billsQuery, - handleBillOnRowClick, - handlePartsOrderOnRowClick, - handlePartsDispatchOnRowClick, - }) { - return ( -
- - {billsQuery.error ? ( - - ) : null} - - - null}> -
- - - - - - - null}> - - - - - - - - - - ); + job, + billsQuery, + handleBillOnRowClick, + handlePartsOrderOnRowClick, + handlePartsDispatchOnRowClick +}) { + return ( +
+ + {billsQuery.error ? : null} + + + null}> +
+ + + + + + + null}> + + + + + + + + + + ); } diff --git a/client/src/components/jobs-detail-pli/jobs-detail-pli.container.jsx b/client/src/components/jobs-detail-pli/jobs-detail-pli.container.jsx index f932f9c7d..f968b1da7 100644 --- a/client/src/components/jobs-detail-pli/jobs-detail-pli.container.jsx +++ b/client/src/components/jobs-detail-pli/jobs-detail-pli.container.jsx @@ -1,62 +1,62 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import queryString from "query-string"; import React from "react"; -import {useLocation, useNavigate} from "react-router-dom"; -import {QUERY_BILLS_BY_JOBID} from "../../graphql/bills.queries"; +import { useLocation, useNavigate } from "react-router-dom"; +import { QUERY_BILLS_BY_JOBID } from "../../graphql/bills.queries"; import JobsDetailPliComponent from "./jobs-detail-pli.component"; -export default function JobsDetailPliContainer({job}) { - const billsQuery = useQuery(QUERY_BILLS_BY_JOBID, { - variables: {jobid: job.id}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); +export default function JobsDetailPliContainer({ job }) { + const billsQuery = useQuery(QUERY_BILLS_BY_JOBID, { + variables: { jobid: job.id }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const search = queryString.parse(useLocation().search); - const history = useNavigate(); + const search = queryString.parse(useLocation().search); + const history = useNavigate(); - const handleBillOnRowClick = (record) => { - if (record) { - if (record.id) { - search.billid = record.id; - history({search: queryString.stringify(search)}); - } - } else { - delete search.billid; - history({search: queryString.stringify(search)}); - } - }; + const handleBillOnRowClick = (record) => { + if (record) { + if (record.id) { + search.billid = record.id; + history({ search: queryString.stringify(search) }); + } + } else { + delete search.billid; + history({ search: queryString.stringify(search) }); + } + }; - const handlePartsOrderOnRowClick = (record) => { - if (record) { - if (record.id) { - search.partsorderid = record.id; - history({search: queryString.stringify(search)}); - } - } else { - delete search.partsorderid; - history({search: queryString.stringify(search)}); - } - }; + const handlePartsOrderOnRowClick = (record) => { + if (record) { + if (record.id) { + search.partsorderid = record.id; + history({ search: queryString.stringify(search) }); + } + } else { + delete search.partsorderid; + history({ search: queryString.stringify(search) }); + } + }; - const handlePartsDispatchOnRowClick = (record) => { - if (record) { - if (record.id) { - search.partsdispatchid = record.id; - history.push({search: queryString.stringify(search)}); - } - } else { - delete search.partsdispatchid; - history.push({search: queryString.stringify(search)}); - } - }; - return ( - - ); + const handlePartsDispatchOnRowClick = (record) => { + if (record) { + if (record.id) { + search.partsdispatchid = record.id; + history.push({ search: queryString.stringify(search) }); + } + } else { + delete search.partsdispatchid; + history.push({ search: queryString.stringify(search) }); + } + }; + return ( + + ); } diff --git a/client/src/components/jobs-detail-rates-change-button/jobs-detail-rates-change-button.component.jsx b/client/src/components/jobs-detail-rates-change-button/jobs-detail-rates-change-button.component.jsx index ab0e33e95..065a3a24b 100644 --- a/client/src/components/jobs-detail-rates-change-button/jobs-detail-rates-change-button.component.jsx +++ b/client/src/components/jobs-detail-rates-change-button/jobs-detail-rates-change-button.component.jsx @@ -1,43 +1,39 @@ -import {DownOutlined} from "@ant-design/icons"; -import {Dropdown} from "antd"; +import { DownOutlined } from "@ant-design/icons"; +import { Dropdown } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -export function JobsDetailRatesChangeButton({disabled, form, bodyshop}) { - const {t} = useTranslation(); +export function JobsDetailRatesChangeButton({ disabled, form, bodyshop }) { + const { t } = useTranslation(); - const handleClick = ({item, key, keyPath}) => { - const rate = item.props.value; - form.setFieldsValue({...rate, labor_rate_desc: rate.rate_label}); - }; + const handleClick = ({ item, key, keyPath }) => { + const rate = item.props.value; + form.setFieldsValue({ ...rate, labor_rate_desc: rate.rate_label }); + }; - const menu = { - items: bodyshop.md_labor_rates.map((rate, idx) => ({ - key: idx, - label: rate.rate_label, - value: rate, - })), - onClick: handleClick - } + const menu = { + items: bodyshop.md_labor_rates.map((rate, idx) => ({ + key: idx, + label: rate.rate_label, + value: rate + })), + onClick: handleClick + }; - return ( - - e.preventDefault()} - > - {t("jobs.actions.changelaborrate")} - - - ); + return ( + + e.preventDefault()}> + {t("jobs.actions.changelaborrate")} + + + ); } export default connect(mapStateToProps, null)(JobsDetailRatesChangeButton); diff --git a/client/src/components/jobs-detail-rates/jobs-detail-rates.component.jsx b/client/src/components/jobs-detail-rates/jobs-detail-rates.component.jsx index c1f98a648..f056f603d 100644 --- a/client/src/components/jobs-detail-rates/jobs-detail-rates.component.jsx +++ b/client/src/components/jobs-detail-rates/jobs-detail-rates.component.jsx @@ -1,26 +1,26 @@ -import { Divider, Form, Input, InputNumber, Select, Space, Switch, Tooltip } from 'antd'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { connect } from 'react-redux'; -import { createStructuredSelector } from 'reselect'; -import { selectJobReadOnly } from '../../redux/application/application.selectors'; -import { selectBodyshop } from '../../redux/user/user.selectors'; -import CABCpvrtCalculator from '../ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component'; -import CurrencyInput from '../form-items-formatted/currency-form-item.component'; -import JobsDetailRatesChangeButton from '../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component'; -import JobsMarkPstExempt from '../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component'; -import FormRow from '../layout-form-row/layout-form-row.component'; -import JobsDetailRatesLabor from './jobs-detail-rates.labor.component'; -import JobsDetailRatesMaterials from './jobs-detail-rates.materials.component'; -import JobsDetailRatesOther from './jobs-detail-rates.other.component'; -import JobsDetailRatesParts from './jobs-detail-rates.parts.component'; -import JobsDetailRatesTaxes from './jobs-detail-rates.taxes.component'; -import JobsDetailRatesProfileOVerride from './jobs-detail-rates.profile-override.component'; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { Divider, Form, Input, InputNumber, Select, Space, Switch, Tooltip } from "antd"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import CABCpvrtCalculator from "../ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component"; +import CurrencyInput from "../form-items-formatted/currency-form-item.component"; +import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component"; +import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component"; +import FormRow from "../layout-form-row/layout-form-row.component"; +import JobsDetailRatesLabor from "./jobs-detail-rates.labor.component"; +import JobsDetailRatesMaterials from "./jobs-detail-rates.materials.component"; +import JobsDetailRatesOther from "./jobs-detail-rates.other.component"; +import JobsDetailRatesParts from "./jobs-detail-rates.parts.component"; +import JobsDetailRatesTaxes from "./jobs-detail-rates.taxes.component"; +import JobsDetailRatesProfileOVerride from "./jobs-detail-rates.profile-override.component"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ jobRO: selectJobReadOnly, - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); export function JobsDetailRates({ jobRO, form, job, bodyshop }) { @@ -28,63 +28,52 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) { return (
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + { @@ -202,12 +178,12 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) { // // } - + {InstanceRenderManager({ - imex: , + imex: , rome: ( <> Tax Profile diff --git a/client/src/components/jobs-detail-rates/jobs-detail-rates.labor.component.jsx b/client/src/components/jobs-detail-rates/jobs-detail-rates.labor.component.jsx index cdef71c8e..afbe883ef 100644 --- a/client/src/components/jobs-detail-rates/jobs-detail-rates.labor.component.jsx +++ b/client/src/components/jobs-detail-rates/jobs-detail-rates.labor.component.jsx @@ -1,429 +1,420 @@ -import {Collapse, Form, Switch} from "antd"; +import { Collapse, Form, Switch } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; const mapStateToProps = createStructuredSelector({ - jobRO: selectJobReadOnly, + jobRO: selectJobReadOnly }); -export function JobsDetailRatesLabor({ - jobRO, - expanded, - required = true, - form, - }) { - const {t} = useTranslation(); +export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form }) { + const { t } = useTranslation(); - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } export default connect(mapStateToProps, null)(JobsDetailRatesLabor); diff --git a/client/src/components/jobs-detail-rates/jobs-detail-rates.materials.component.jsx b/client/src/components/jobs-detail-rates/jobs-detail-rates.materials.component.jsx index e3e312fb3..52c6c1e4f 100644 --- a/client/src/components/jobs-detail-rates/jobs-detail-rates.materials.component.jsx +++ b/client/src/components/jobs-detail-rates/jobs-detail-rates.materials.component.jsx @@ -1,147 +1,126 @@ -import {Collapse, Form, Input, InputNumber, Switch} from "antd"; +import { Collapse, Form, Input, InputNumber, Switch } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; const mapStateToProps = createStructuredSelector({ - jobRO: selectJobReadOnly, + jobRO: selectJobReadOnly }); -export function JobsDetailRatesMaterials({ - jobRO, - expanded, - required = true, - form, - }) { - const {t} = useTranslation(); +export function JobsDetailRatesMaterials({ jobRO, expanded, required = true, form }) { + const { t } = useTranslation(); - return ( - - - - - - - - - + return ( + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - ); + + + + + + + + + + + + + + + + + + + + + + ); } export default connect(mapStateToProps, null)(JobsDetailRatesMaterials); diff --git a/client/src/components/jobs-detail-rates/jobs-detail-rates.other.component.jsx b/client/src/components/jobs-detail-rates/jobs-detail-rates.other.component.jsx index 0617a8fea..f9788d08a 100644 --- a/client/src/components/jobs-detail-rates/jobs-detail-rates.other.component.jsx +++ b/client/src/components/jobs-detail-rates/jobs-detail-rates.other.component.jsx @@ -1,106 +1,97 @@ -import {Collapse, Form, Switch} from "antd"; +import { Collapse, Form, Switch } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; const mapStateToProps = createStructuredSelector({ - jobRO: selectJobReadOnly, + jobRO: selectJobReadOnly }); -export function JobsDetailRatesOther({ - jobRO, - expanded, - required = true, - form, - }) { - const {t} = useTranslation(); +export function JobsDetailRatesOther({ jobRO, expanded, required = true, form }) { + const { t } = useTranslation(); - return ( - - - - - - - - - - - - - - - - - - + return ( + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - ); + + + + + + + + + + + + + + + + + + + ); } export default connect(mapStateToProps, null)(JobsDetailRatesOther); diff --git a/client/src/components/jobs-detail-rates/jobs-detail-rates.parts.component.jsx b/client/src/components/jobs-detail-rates/jobs-detail-rates.parts.component.jsx index 241327a3d..a427f3cb0 100644 --- a/client/src/components/jobs-detail-rates/jobs-detail-rates.parts.component.jsx +++ b/client/src/components/jobs-detail-rates/jobs-detail-rates.parts.component.jsx @@ -1,1225 +1,1012 @@ -import {Collapse, Form, InputNumber, Switch} from "antd"; +import { Collapse, Form, InputNumber, Switch } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; const mapStateToProps = createStructuredSelector({ - jobRO: selectJobReadOnly, + jobRO: selectJobReadOnly }); -export function JobsDetailRatesParts({ - jobRO, - expanded, - required = true, - form, - }) { - const {t} = useTranslation(); +export function JobsDetailRatesParts({ jobRO, expanded, required = true, form }) { + const { t } = useTranslation(); - return ( - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); + return ( + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } export default connect(mapStateToProps, null)(JobsDetailRatesParts); diff --git a/client/src/components/jobs-detail-rates/jobs-detail-rates.profile-override.component.jsx b/client/src/components/jobs-detail-rates/jobs-detail-rates.profile-override.component.jsx index a127cd9c7..e24d87b65 100644 --- a/client/src/components/jobs-detail-rates/jobs-detail-rates.profile-override.component.jsx +++ b/client/src/components/jobs-detail-rates/jobs-detail-rates.profile-override.component.jsx @@ -1,43 +1,40 @@ -import {Button, Popconfirm} from "antd"; +import { Button, Popconfirm } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsDetailRatesProfileOVerride); +export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailRatesProfileOVerride); -export function JobsDetailRatesProfileOVerride({bodyshop, form}) { - const {t} = useTranslation(); - return ( - { - form.setFieldsValue({ - cieca_pft: { - ...bodyshop.md_responsibility_centers.taxes.tax_ty1, - ...bodyshop.md_responsibility_centers.taxes.tax_ty2, - ...bodyshop.md_responsibility_centers.taxes.tax_ty3, - ...bodyshop.md_responsibility_centers.taxes.tax_ty4, - ...bodyshop.md_responsibility_centers.taxes.tax_ty5, - }, - materials: bodyshop.md_responsibility_centers.cieca_pfm, - cieca_pfl: bodyshop.md_responsibility_centers.cieca_pfl, - parts_tax_rates: bodyshop.md_responsibility_centers.parts_tax_rates, - }); - }} - title={t("jobs.actions.taxprofileoverride_confirm")} - > - - - ); +export function JobsDetailRatesProfileOVerride({ bodyshop, form }) { + const { t } = useTranslation(); + return ( + { + form.setFieldsValue({ + cieca_pft: { + ...bodyshop.md_responsibility_centers.taxes.tax_ty1, + ...bodyshop.md_responsibility_centers.taxes.tax_ty2, + ...bodyshop.md_responsibility_centers.taxes.tax_ty3, + ...bodyshop.md_responsibility_centers.taxes.tax_ty4, + ...bodyshop.md_responsibility_centers.taxes.tax_ty5 + }, + materials: bodyshop.md_responsibility_centers.cieca_pfm, + cieca_pfl: bodyshop.md_responsibility_centers.cieca_pfl, + parts_tax_rates: bodyshop.md_responsibility_centers.parts_tax_rates + }); + }} + title={t("jobs.actions.taxprofileoverride_confirm")} + > + + + ); } diff --git a/client/src/components/jobs-detail-rates/jobs-detail-rates.taxes.component.jsx b/client/src/components/jobs-detail-rates/jobs-detail-rates.taxes.component.jsx index 165cf6e52..95d68b1f2 100644 --- a/client/src/components/jobs-detail-rates/jobs-detail-rates.taxes.component.jsx +++ b/client/src/components/jobs-detail-rates/jobs-detail-rates.taxes.component.jsx @@ -1,171 +1,152 @@ -import {Collapse, Divider, Form, Input, InputNumber, Space} from "antd"; +import { Collapse, Divider, Form, Input, InputNumber, Space } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - jobRO: selectJobReadOnly, - bodyshop: selectBodyshop, + jobRO: selectJobReadOnly, + bodyshop: selectBodyshop }); -export function JobsDetailRatesTaxes({ - jobRO, - expanded, - bodyshop, - required = true, - form, - }) { - const {t} = useTranslation(); - const formItems = []; - for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { - const section = []; +export function JobsDetailRatesTaxes({ jobRO, expanded, bodyshop, required = true, form }) { + const { t } = useTranslation(); + const formItems = []; + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + const section = []; - section.push( - TaxFormItems({ - typeNum: tyCounter, - rootElements: true, - bodyshop, - jobRO, - key: `root${tyCounter}` - }) - ); - - for (let iterator = 1; iterator <= 5; iterator++) { - section.push( - TaxFormItems({ - typeNum: tyCounter, - typeNumIterator: iterator, - rootElements: false, - jobRO, - key: `nonroot${iterator}` - - }) - ); - } - formItems.push(<> - - {section} - - - ) - - } - return ( - - - {formItems} - - + section.push( + TaxFormItems({ + typeNum: tyCounter, + rootElements: true, + bodyshop, + jobRO, + key: `root${tyCounter}` + }) ); + + for (let iterator = 1; iterator <= 5; iterator++) { + section.push( + TaxFormItems({ + typeNum: tyCounter, + typeNumIterator: iterator, + rootElements: false, + jobRO, + key: `nonroot${iterator}` + }) + ); + } + formItems.push( + <> + + {section} + + + + ); + } + return ( + + + {formItems} + + + ); } export default connect(mapStateToProps, null)(JobsDetailRatesTaxes); -function TaxFormItems({ - typeNum, - typeNumIterator, - rootElements, - bodyshopjobRO, - jobRO, - key - }) { - const {t} = useTranslation(); - - if (rootElements) - return ( - - - - - - ); +function TaxFormItems({ typeNum, typeNumIterator, rootElements, bodyshopjobRO, jobRO, key }) { + const { t } = useTranslation(); + if (rootElements) return ( - <> - - - - - - - - - - - - - + + + ); + + return ( + <> + + + + + + + + + + + + + + ); } diff --git a/client/src/components/jobs-detail-totals/jobs-detail-totals.component.jsx b/client/src/components/jobs-detail-totals/jobs-detail-totals.component.jsx index c38f5e619..d9660584d 100644 --- a/client/src/components/jobs-detail-totals/jobs-detail-totals.component.jsx +++ b/client/src/components/jobs-detail-totals/jobs-detail-totals.component.jsx @@ -1,16 +1,16 @@ -import {Divider} from "antd"; +import { Divider } from "antd"; import React from "react"; import JobPayments from "../job-payments/job-payments.component"; import JobTotalsTable from "../job-totals-table/job-totals-table.component"; -export function JobsDetailTotals({job, refetch}) { - return ( -
- - - -
- ); +export function JobsDetailTotals({ job, refetch }) { + return ( +
+ + + +
+ ); } export default JobsDetailTotals; diff --git a/client/src/components/jobs-documents-gallery/job-documents.utility.js b/client/src/components/jobs-documents-gallery/job-documents.utility.js index 57f946747..a5f6399c0 100644 --- a/client/src/components/jobs-documents-gallery/job-documents.utility.js +++ b/client/src/components/jobs-documents-gallery/job-documents.utility.js @@ -1,22 +1,19 @@ -import {DetermineFileType} from "../documents-upload/documents-upload.utility"; +import { DetermineFileType } from "../documents-upload/documents-upload.utility"; export const GenerateSrcUrl = (value) => { - let extension = value.extension; - if (extension && extension.toLowerCase().includes("heic")) extension = "jpg"; + let extension = value.extension; + if (extension && extension.toLowerCase().includes("heic")) extension = "jpg"; - return `${import.meta.env.VITE_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType( - value.type - )}/upload/${value.key}${extension ? `.${extension}` : ""}`; + return `${import.meta.env.VITE_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType( + value.type + )}/upload/${value.key}${extension ? `.${extension}` : ""}`; }; export const GenerateThumbUrl = (value) => { - let extension = value.extension; - if (extension && extension.toLowerCase().includes("heic")) extension = "jpg"; - else if ( - DetermineFileType(value.type) !== "image" || - (value.type && value.type.includes("application")) - ) - extension = "jpg"; + let extension = value.extension; + if (extension && extension.toLowerCase().includes("heic")) extension = "jpg"; + else if (DetermineFileType(value.type) !== "image" || (value.type && value.type.includes("application"))) + extension = "jpg"; return `${import.meta.env.VITE_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType( value.type diff --git a/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx b/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx index 6ee486a4f..f85db7c8c 100644 --- a/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx @@ -1,145 +1,131 @@ -import {Button, Space} from "antd"; +import { Button, Space } from "antd"; import axios from "axios"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import cleanAxios from "../../utils/CleanAxios"; import formatBytes from "../../utils/formatbytes"; //import yauzl from "yauzl"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsDocumentsDownloadButton); +export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsDownloadButton); -export function JobsDocumentsDownloadButton({bodyshop, galleryImages, identifier}) { +export function JobsDocumentsDownloadButton({ bodyshop, galleryImages, identifier }) { + const { t } = useTranslation(); + const [download, setDownload] = useState(null); - const {t} = useTranslation(); - const [download, setDownload] = useState(null); + const { + treatments: { Direct_Media_Download } + } = useSplitTreatments({ + attributes: {}, + names: ["Direct_Media_Download"], + splitKey: bodyshop.imexshopid + }); - const {treatments: {Direct_Media_Download}} = useSplitTreatments({ - attributes: {}, - names: ["Direct_Media_Download"], - splitKey: bodyshop.imexshopid, + const imagesToDownload = [ + ...galleryImages.images.filter((image) => image.isSelected), + ...galleryImages.other.filter((image) => image.isSelected) + ]; + + function downloadProgress(progressEvent) { + setDownload((currentDownloadState) => { + return { + downloaded: progressEvent.loaded || 0, + speed: (progressEvent.loaded || 0) - ((currentDownloadState && currentDownloadState.downloaded) || 0) + }; + }); + } + + const handleDownload = async () => { + logImEXEvent("jobs_documents_download"); + + const zipUrl = await axios({ + url: "/media/download", + method: "POST", + //responseType: "arraybuffer", // Important + data: { ids: imagesToDownload.map((_) => _.key) } }); - const imagesToDownload = [ - ...galleryImages.images.filter((image) => image.isSelected), - ...galleryImages.other.filter((image) => image.isSelected), - ]; - - function downloadProgress(progressEvent) { - setDownload((currentDownloadState) => { - return { - downloaded: progressEvent.loaded || 0, - speed: - (progressEvent.loaded || 0) - - ((currentDownloadState && currentDownloadState.downloaded) || 0), - }; - }); + const theDownloadedZip = await cleanAxios({ + url: zipUrl.data, + method: "GET", + responseType: "arraybuffer", + onDownloadProgress: downloadProgress + }); + setDownload(null); + if (Direct_Media_Download.treatment === "on") { + try { + // const parentDir = await window.showDirectoryPicker({ + // id: "media", + // startIn: "downloads", + // }); + // const directory = await parentDir.getDirectoryHandle(identifier, { + // create: true, + // }); + // yauzl.fromBuffer( + // Buffer.from(theDownloadedZip.data), + // {}, + // (err, zipFile) => { + // if (err) throw err; + // zipFile.on("entry", (entry) => { + // zipFile.openReadStream(entry, async (readErr, readStream) => { + // if (readErr) { + // zipFile.close(); + // throw readErr; + // } + // if (err) throw err; + // let fileSystemHandle = await directory.getFileHandle( + // entry.fileName, + // { + // create: true, + // } + // ); + // const writable = await fileSystemHandle.createWritable(); + // readStream.on("data", async function (chunk) { + // await writable.write(chunk); + // }); + // readStream.on("end", async function () { + // await writable.close(); + // }); + // }); + // }); + // } + // ); + } catch (e) { + console.log(e); + standardMediaDownload(theDownloadedZip.data); + } + } else { + standardMediaDownload(theDownloadedZip.data); } - const handleDownload = async () => { - logImEXEvent("jobs_documents_download"); + function standardMediaDownload(bufferData) { + const a = document.createElement("a"); + const url = window.URL.createObjectURL(new Blob([bufferData])); + a.href = url; + a.download = `${identifier || "documents"}.zip`; + a.click(); + } + }; - const zipUrl = await axios({ - url: "/media/download", - method: "POST", - //responseType: "arraybuffer", // Important - data: {ids: imagesToDownload.map((_) => _.key)}, - }); - - const theDownloadedZip = await cleanAxios({ - url: zipUrl.data, - method: "GET", - responseType: "arraybuffer", - onDownloadProgress: downloadProgress, - }); - setDownload(null); - if (Direct_Media_Download.treatment === "on") { - try { - // const parentDir = await window.showDirectoryPicker({ - // id: "media", - // startIn: "downloads", - // }); - - // const directory = await parentDir.getDirectoryHandle(identifier, { - // create: true, - // }); - - // yauzl.fromBuffer( - // Buffer.from(theDownloadedZip.data), - // {}, - // (err, zipFile) => { - // if (err) throw err; - // zipFile.on("entry", (entry) => { - // zipFile.openReadStream(entry, async (readErr, readStream) => { - // if (readErr) { - // zipFile.close(); - // throw readErr; - // } - // if (err) throw err; - // let fileSystemHandle = await directory.getFileHandle( - // entry.fileName, - // { - // create: true, - // } - // ); - // const writable = await fileSystemHandle.createWritable(); - // readStream.on("data", async function (chunk) { - // await writable.write(chunk); - // }); - // readStream.on("end", async function () { - // await writable.close(); - // }); - // }); - // }); - // } - // ); - } catch (e) { - console.log(e); - standardMediaDownload(theDownloadedZip.data); - } - } else { - standardMediaDownload(theDownloadedZip.data); - } - - function standardMediaDownload(bufferData) { - const a = document.createElement("a"); - const url = window.URL.createObjectURL(new Blob([bufferData])); - a.href = url; - a.download = `${identifier || "documents"}.zip`; - a.click(); - } - }; - - return ( - <> - - - ); + return ( + <> + + + ); } diff --git a/client/src/components/jobs-documents-gallery/jobs-document-gallery.reassign.component.jsx b/client/src/components/jobs-documents-gallery/jobs-document-gallery.reassign.component.jsx index 376ac8f4d..82011bb05 100644 --- a/client/src/components/jobs-documents-gallery/jobs-document-gallery.reassign.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-document-gallery.reassign.component.jsx @@ -1,172 +1,154 @@ -import {useApolloClient} from "@apollo/client"; -import {Button, Form, notification, Popover, Space} from "antd"; +import { useApolloClient } from "@apollo/client"; +import { Button, Form, notification, Popover, Space } from "antd"; import axios from "axios"; -import React, {useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {GET_DOC_SIZE_BY_JOB} from "../../graphql/documents.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { GET_DOC_SIZE_BY_JOB } from "../../graphql/documents.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import JobSearchSelect from "../job-search-select/job-search-select.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsDocumentsGalleryReassign); +export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsGalleryReassign); -export function JobsDocumentsGalleryReassign({ - bodyshop, - galleryImages, - callback, - }) { - const {t} = useTranslation(); - const [form] = Form.useForm(); +export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages, callback }) { + const { t } = useTranslation(); + const [form] = Form.useForm(); - const selectedImages = useMemo(() => { - return [ - ...galleryImages.images.filter((image) => image.isSelected), - ...galleryImages.other.filter((image) => image.isSelected), - ]; - }, [galleryImages]); - const client = useApolloClient(); - const [open, setOpen] = useState(false); - const [loading, setLoading] = useState(false); + const selectedImages = useMemo(() => { + return [ + ...galleryImages.images.filter((image) => image.isSelected), + ...galleryImages.other.filter((image) => image.isSelected) + ]; + }, [galleryImages]); + const client = useApolloClient(); + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); - // const updateImage = async (i, jobid) => { - // //Move the cloudinary image + // const updateImage = async (i, jobid) => { + // //Move the cloudinary image - // //Update it in the database. - // const result = await updateDocument({ - // variables: { - // id: i.id, - // document: { - // key: i.public_id, - // jobid: jobid, - // }, - // }, - // }); + // //Update it in the database. + // const result = await updateDocument({ + // variables: { + // id: i.id, + // document: { + // key: i.public_id, + // jobid: jobid, + // }, + // }, + // }); - // if (!!result.errors) { - // notification["error"]({ - // message: t("documents.errors.updating", { - // message: JSON.stringify(result.errors), - // }), - // }); - // } else { - // notification["success"]({ - // message: t("documents.successes.updated"), - // }); - // } - // }; + // if (!!result.errors) { + // notification["error"]({ + // message: t("documents.errors.updating", { + // message: JSON.stringify(result.errors), + // }), + // }); + // } else { + // notification["success"]({ + // message: t("documents.successes.updated"), + // }); + // } + // }; - const handleFinish = async ({jobid}) => { - setLoading(true); + const handleFinish = async ({ jobid }) => { + setLoading(true); - //Check to see if the space remaining on the new job is sufficient. If it isn't cancel this. - const newJobData = await client.query({ - query: GET_DOC_SIZE_BY_JOB, - variables: {jobId: jobid}, - }); + //Check to see if the space remaining on the new job is sufficient. If it isn't cancel this. + const newJobData = await client.query({ + query: GET_DOC_SIZE_BY_JOB, + variables: { jobId: jobid } + }); - const transferedDocSizeTotal = selectedImages.reduce( - (acc, val) => acc + val.size, - 0 - ); + const transferedDocSizeTotal = selectedImages.reduce((acc, val) => acc + val.size, 0); - const shouldPreventTransfer = - bodyshop.jobsizelimit - - newJobData.data.documents_aggregate.aggregate.sum.size < - transferedDocSizeTotal; + const shouldPreventTransfer = + bodyshop.jobsizelimit - newJobData.data.documents_aggregate.aggregate.sum.size < transferedDocSizeTotal; - if (shouldPreventTransfer) { - notification.open({ - key: "cannotuploaddocuments", - type: "error", - message: t("documents.labels.reassign_limitexceeded_title"), - description: t("documents.labels.reassign_limitexceeded"), - }); - setLoading(false); - return; - } + if (shouldPreventTransfer) { + notification.open({ + key: "cannotuploaddocuments", + type: "error", + message: t("documents.labels.reassign_limitexceeded_title"), + description: t("documents.labels.reassign_limitexceeded") + }); + setLoading(false); + return; + } - const res = await axios.post("/media/rename", { - tojobid: jobid, - documents: selectedImages.map((i) => { - //Need to check if the current key folder is null, or another job. - const currentKeys = i.key.split("/"); - currentKeys[1] = jobid; - currentKeys.join("/"); - return { - id: i.id, - from: i.key, - to: currentKeys.join("/"), - extension: i.extension, - type: i.type, - }; - }), - }); - //Add in confirmation & errors. - if (callback) callback(); + const res = await axios.post("/media/rename", { + tojobid: jobid, + documents: selectedImages.map((i) => { + //Need to check if the current key folder is null, or another job. + const currentKeys = i.key.split("/"); + currentKeys[1] = jobid; + currentKeys.join("/"); + return { + id: i.id, + from: i.key, + to: currentKeys.join("/"), + extension: i.extension, + type: i.type + }; + }) + }); + //Add in confirmation & errors. + if (callback) callback(); - if (res.errors) { - notification["error"]({ - message: t("documents.errors.updating", { - message: JSON.stringify(res.errors), - }), - }); - } - if (!res.mutationResult?.errors) { - notification["success"]({ - message: t("documents.successes.updated"), - }); - } - setOpen(false); - setLoading(false); - }; + if (res.errors) { + notification["error"]({ + message: t("documents.errors.updating", { + message: JSON.stringify(res.errors) + }) + }); + } + if (!res.mutationResult?.errors) { + notification["success"]({ + message: t("documents.successes.updated") + }); + } + setOpen(false); + setLoading(false); + }; - const popContent = ( -
-
- - - - - - - - -
- ); + const popContent = ( +
+
+ + + + + + + + +
+ ); - return ( - - - - ); + return ( + + + + ); } 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 fd9be8f23..1598ea66b 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 @@ -1,11 +1,11 @@ -import {EditFilled, FileExcelFilled, SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Col, Row, Space} from "antd"; -import React, {useEffect, useState} from "react"; -import {Gallery} from "react-grid-gallery"; -import {useTranslation} from "react-i18next"; +import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Col, Row, Space } from "antd"; +import React, { useEffect, useState } from "react"; +import { Gallery } from "react-grid-gallery"; +import { useTranslation } from "react-i18next"; import DocumentsUploadComponent from "../documents-upload/documents-upload.component"; -import {DetermineFileType} from "../documents-upload/documents-upload.utility"; -import {GenerateSrcUrl, GenerateThumbUrl} from "./job-documents.utility"; +import { DetermineFileType } from "../documents-upload/documents-upload.utility"; +import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility"; import JobsDocumentsDownloadButton from "./jobs-document-gallery.download.component"; import JobsDocumentsGalleryReassign from "./jobs-document-gallery.reassign.component"; import JobsDocumentsDeleteButton from "./jobs-documents-gallery.delete.component"; @@ -15,238 +15,208 @@ import Lightbox from "react-image-lightbox"; import "react-image-lightbox/style.css"; function JobsDocumentsComponent({ - data, - jobId, - refetch, - billId, - billsCallback, - totalSize, - downloadIdentifier, - ignoreSizeLimit, - }) { - const [galleryImages, setgalleryImages] = useState({images: [], other: []}); - const {t} = useTranslation(); - const [modalState, setModalState] = useState({open: false, index: 0}); + data, + jobId, + refetch, + billId, + billsCallback, + totalSize, + downloadIdentifier, + ignoreSizeLimit +}) { + const [galleryImages, setgalleryImages] = useState({ images: [], other: [] }); + const { t } = useTranslation(); + const [modalState, setModalState] = useState({ open: false, index: 0 }); - useEffect(() => { - let documents = data.reduce( - (acc, value) => { - const fileType = DetermineFileType(value.type); - if (value.type.startsWith("image")) { - acc.images.push({ - // src: GenerateSrcUrl(value), - src: GenerateThumbUrl(value), - // src: GenerateSrcUrl(value), - // thumbnail: GenerateThumbUrl(value), - fullsize: GenerateSrcUrl(value), - height: 225, - width: 225, - isSelected: false, - key: value.key, - extension: value.extension, - id: value.id, - type: value.type, - size: value.size, - tags: [{value: value.type, title: value.type}], - }); - } else { - let thumb; - switch (fileType) { - case "raw": - thumb = `${window.location.origin}/file.png`; - break; - default: - thumb = GenerateThumbUrl(value); - break; + useEffect(() => { + let documents = data.reduce( + (acc, value) => { + const fileType = DetermineFileType(value.type); + if (value.type.startsWith("image")) { + acc.images.push({ + // src: GenerateSrcUrl(value), + src: GenerateThumbUrl(value), + // src: GenerateSrcUrl(value), + // thumbnail: GenerateThumbUrl(value), + fullsize: GenerateSrcUrl(value), + height: 225, + width: 225, + isSelected: false, + key: value.key, + extension: value.extension, + id: value.id, + type: value.type, + size: value.size, + tags: [{ value: value.type, title: value.type }] + }); + } else { + let thumb; + switch (fileType) { + case "raw": + thumb = `${window.location.origin}/file.png`; + break; + default: + thumb = GenerateThumbUrl(value); + break; + } + + const fileName = value.key.split("/").pop(); + acc.other.push({ + source: GenerateSrcUrl(value), + src: thumb, + thumbnail: thumb, + tags: [ + { + value: fileName, + title: fileName + }, + + { value: value.type, title: value.type }, + ...(value.bill + ? [ + { + value: value.bill.vendor.name, + title: t("vendors.fields.name") + }, + { value: value.bill.date, title: t("bills.fields.date") }, + { + value: value.bill.invoice_number, + title: t("bills.fields.invoice_number") } + ] + : []) + ], + height: 225, + width: 225, + isSelected: false, + extension: value.extension, + key: value.key, + id: value.id, + type: value.type, + size: value.size + }); + } - const fileName = value.key.split("/").pop(); - acc.other.push({ - source: GenerateSrcUrl(value), - src: thumb, - thumbnail: thumb, - tags: [ - { - value: fileName, - title: fileName, - }, - - {value: value.type, title: value.type}, - ...(value.bill - ? [ - { - value: value.bill.vendor.name, - title: t("vendors.fields.name"), - }, - {value: value.bill.date, title: t("bills.fields.date")}, - { - value: value.bill.invoice_number, - title: t("bills.fields.invoice_number"), - }, - ] - : []), - ], - height: 225, - width: 225, - isSelected: false, - extension: value.extension, - key: value.key, - id: value.id, - type: value.type, - size: value.size, - }); - } - - return acc; - }, - {images: [], other: []} - ); - setgalleryImages(documents); - }, [data, setgalleryImages, t]); - - return ( -
- -
- - - - - - {!billId && ( - - )} - - - - - - - - - - - { - setModalState({open: true, index: index}); - // window.open( - // item.fullsize, - // "_blank", - // "toolbar=0,location=0,menubar=0" - // ); - }} - onSelect={(index, image) => { - setgalleryImages({ - ...galleryImages, - images: galleryImages.images.map((g, idx) => - index === idx ? {...g, isSelected: !g.isSelected} : g - ), - }); - }} - /> - - - - - { - return { - backgroundImage: , - height: "100%", - width: "100%", - cursor: "pointer", - }; - }} - onClick={(index) => { - window.open( - galleryImages.other[index].source, - "_blank", - "toolbar=0,location=0,menubar=0" - ); - }} - onSelect={(index) => { - setgalleryImages({ - ...galleryImages, - other: galleryImages.other.map((g, idx) => - index === idx ? {...g, isSelected: !g.isSelected} : g - ), - }); - }} - /> - - - {modalState.open && ( - { - const newWindow = window.open( - `${window.location.protocol}//${ - window.location.host - }/edit?documentId=${ - galleryImages.images[modalState.index].id - }`, - "_blank", - "noopener,noreferrer" - ); - if (newWindow) newWindow.opener = null; - }} - />, - ]} - mainSrc={galleryImages.images[modalState.index].fullsize} - nextSrc={ - galleryImages.images[ - (modalState.index + 1) % galleryImages.images.length - ].fullsize - } - prevSrc={ - galleryImages.images[ - (modalState.index + galleryImages.images.length - 1) % - galleryImages.images.length - ].fullsize - } - onCloseRequest={() => setModalState({open: false, index: 0})} - onMovePrevRequest={() => - setModalState({ - ...modalState, - index: - (modalState.index + galleryImages.images.length - 1) % - galleryImages.images.length, - }) - } - onMoveNextRequest={() => - setModalState({ - ...modalState, - index: (modalState.index + 1) % galleryImages.images.length, - }) - } - /> - )} - - + return acc; + }, + { images: [], other: [] } ); + setgalleryImages(documents); + }, [data, setgalleryImages, t]); + + return ( +
+ +
+ + + + + + {!billId && } + + + + + + + + + + + { + setModalState({ open: true, index: index }); + // window.open( + // item.fullsize, + // "_blank", + // "toolbar=0,location=0,menubar=0" + // ); + }} + onSelect={(index, image) => { + setgalleryImages({ + ...galleryImages, + images: galleryImages.images.map((g, idx) => + index === idx ? { ...g, isSelected: !g.isSelected } : g + ) + }); + }} + /> + + + + + { + return { + backgroundImage: , + height: "100%", + width: "100%", + cursor: "pointer" + }; + }} + onClick={(index) => { + window.open(galleryImages.other[index].source, "_blank", "toolbar=0,location=0,menubar=0"); + }} + onSelect={(index) => { + setgalleryImages({ + ...galleryImages, + other: galleryImages.other.map((g, idx) => (index === idx ? { ...g, isSelected: !g.isSelected } : g)) + }); + }} + /> + + + {modalState.open && ( + { + const newWindow = window.open( + `${window.location.protocol}//${window.location.host}/edit?documentId=${ + galleryImages.images[modalState.index].id + }`, + "_blank", + "noopener,noreferrer" + ); + if (newWindow) newWindow.opener = null; + }} + /> + ]} + mainSrc={galleryImages.images[modalState.index].fullsize} + nextSrc={galleryImages.images[(modalState.index + 1) % galleryImages.images.length].fullsize} + prevSrc={ + galleryImages.images[(modalState.index + galleryImages.images.length - 1) % galleryImages.images.length] + .fullsize + } + onCloseRequest={() => setModalState({ open: false, index: 0 })} + onMovePrevRequest={() => + setModalState({ + ...modalState, + index: (modalState.index + galleryImages.images.length - 1) % galleryImages.images.length + }) + } + onMoveNextRequest={() => + setModalState({ + ...modalState, + index: (modalState.index + 1) % galleryImages.images.length + }) + } + /> + )} + + + ); } export default JobsDocumentsComponent; diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.container.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.container.jsx index c2b765570..1d73506a8 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.container.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.container.jsx @@ -1,35 +1,30 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import React from "react"; -import {GET_DOCUMENTS_BY_JOB} from "../../graphql/documents.queries"; +import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import JobDocuments from "./jobs-documents-gallery.component"; -export default function JobsDocumentsContainer({ - jobId, - billId, - documentsList, - billsCallback, - }) { - const {loading, error, data, refetch} = useQuery(GET_DOCUMENTS_BY_JOB, { - variables: {jobId: jobId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - skip: !!billId, - }); +export default function JobsDocumentsContainer({ jobId, billId, documentsList, billsCallback }) { + const { loading, error, data, refetch } = useQuery(GET_DOCUMENTS_BY_JOB, { + variables: { jobId: jobId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + skip: !!billId + }); - if (loading) return ; - if (error) return ; + if (loading) return ; + if (error) return ; - return ( - - ); + return ( + + ); } diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.delete.component.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.delete.component.jsx index dee737273..cfc1fb98a 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.delete.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.delete.component.jsx @@ -1,62 +1,59 @@ -import {QuestionCircleOutlined} from "@ant-design/icons"; -import {Button, notification, Popconfirm} from "antd"; +import { QuestionCircleOutlined } from "@ant-design/icons"; +import { Button, notification, Popconfirm } from "antd"; import axios from "axios"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; //Context: currentUserEmail, bodyshop, jobid, invoiceid -export default function JobsDocumentsDeleteButton({ - galleryImages, - deletionCallback, - }) { - const {t} = useTranslation(); +export default function JobsDocumentsDeleteButton({ galleryImages, deletionCallback }) { + const { t } = useTranslation(); - const imagesToDelete = [ - ...galleryImages.images.filter((image) => image.isSelected), - ...galleryImages.other.filter((image) => image.isSelected), - ]; - const [loading, setLoading] = useState(false); + const imagesToDelete = [ + ...galleryImages.images.filter((image) => image.isSelected), + ...galleryImages.other.filter((image) => image.isSelected) + ]; + const [loading, setLoading] = useState(false); - const handleDelete = async () => { - logImEXEvent("job_documents_delete", {count: imagesToDelete.length}); - setLoading(true); - const res = await axios.post("/media/delete", { - ids: imagesToDelete, - }); + const handleDelete = async () => { + logImEXEvent("job_documents_delete", { count: imagesToDelete.length }); + setLoading(true); + const res = await axios.post("/media/delete", { + ids: imagesToDelete + }); - if (res.data.error) { - notification["error"]({ - message: t("documents.errors.deleting", { - error: JSON.stringify(res.data.error.response.errors), - }), - }); - } else { - notification.open({ - key: "docdeletedsuccesfully", - type: "success", - message: t("documents.successes.delete"), - }); + if (res.data.error) { + notification["error"]({ + message: t("documents.errors.deleting", { + error: JSON.stringify(res.data.error.response.errors) + }) + }); + } else { + notification.open({ + key: "docdeletedsuccesfully", + type: "success", + message: t("documents.successes.delete") + }); - if (deletionCallback) deletionCallback(); - } + if (deletionCallback) deletionCallback(); + } - setLoading(false); - }; + setLoading(false); + }; - return ( - } - onConfirm={handleDelete} - title={t("documents.labels.confirmdelete")} - okText={t("general.actions.delete")} - okButtonProps={{type: "danger"}} - cancelText={t("general.actions.cancel")} - > - - - ); + return ( + } + onConfirm={handleDelete} + title={t("documents.labels.confirmdelete")} + okText={t("general.actions.delete")} + okButtonProps={{ type: "danger" }} + cancelText={t("general.actions.cancel")} + > + + + ); } diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.external.component.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.external.component.jsx index 6fa2344c4..940598052 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.external.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.external.component.jsx @@ -1,54 +1,50 @@ -import React, {useEffect} from "react"; -import {Gallery} from "react-grid-gallery"; -import {useTranslation} from "react-i18next"; -import {GenerateSrcUrl, GenerateThumbUrl} from "./job-documents.utility"; +import React, { useEffect } from "react"; +import { Gallery } from "react-grid-gallery"; +import { useTranslation } from "react-i18next"; +import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility"; function JobsDocumentGalleryExternal({ - data, + data, - externalMediaState, - }) { - const [galleryImages, setgalleryImages] = externalMediaState; - const {t} = useTranslation(); + externalMediaState +}) { + const [galleryImages, setgalleryImages] = externalMediaState; + const { t } = useTranslation(); - useEffect(() => { - let documents = data.reduce((acc, value) => { - if (value.type.startsWith("image")) { - acc.push({ - fullsize: GenerateSrcUrl(value), - src: GenerateThumbUrl(value), - thumbnailHeight: 225, - thumbnailWidth: 225, - isSelected: false, - key: value.key, - extension: value.extension, - id: value.id, - type: value.type, - tags: [{value: value.type, title: value.type}], - size: value.size, - }); - } + useEffect(() => { + let documents = data.reduce((acc, value) => { + if (value.type.startsWith("image")) { + acc.push({ + fullsize: GenerateSrcUrl(value), + src: GenerateThumbUrl(value), + thumbnailHeight: 225, + thumbnailWidth: 225, + isSelected: false, + key: value.key, + extension: value.extension, + id: value.id, + type: value.type, + tags: [{ value: value.type, title: value.type }], + size: value.size + }); + } - return acc; - }, []); - setgalleryImages(documents); - }, [data, setgalleryImages, t]); + return acc; + }, []); + setgalleryImages(documents); + }, [data, setgalleryImages, t]); - return ( -
- { - setgalleryImages( - galleryImages.map((g, idx) => - index === idx ? {...g, isSelected: !g.isSelected} : g - ) - ); - }} - /> -
- ); + return ( +
+ { + setgalleryImages(galleryImages.map((g, idx) => (index === idx ? { ...g, isSelected: !g.isSelected } : g))); + }} + /> +
+ ); } export default JobsDocumentGalleryExternal; diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.selectall.component.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.selectall.component.jsx index 593c15f21..122ce6236 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.selectall.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.selectall.component.jsx @@ -1,67 +1,56 @@ -import {Button, Space} from "antd"; +import { Button, Space } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; -export default function JobsDocumentsGallerySelectAllComponent({ - galleryImages, - setGalleryImages, - }) { - const {t} = useTranslation(); +export default function JobsDocumentsGallerySelectAllComponent({ galleryImages, setGalleryImages }) { + const { t } = useTranslation(); - const handleSelectAll = () => { - setGalleryImages({ - ...galleryImages, - other: galleryImages.other.map((i) => { - return {...i, isSelected: true}; - }), - images: galleryImages.images.map((i) => { - return {...i, isSelected: true}; - }), - }); - }; - const handleSelectAllImages = () => { - setGalleryImages({ - ...galleryImages, + const handleSelectAll = () => { + setGalleryImages({ + ...galleryImages, + other: galleryImages.other.map((i) => { + return { ...i, isSelected: true }; + }), + images: galleryImages.images.map((i) => { + return { ...i, isSelected: true }; + }) + }); + }; + const handleSelectAllImages = () => { + setGalleryImages({ + ...galleryImages, - images: galleryImages.images.map((i) => { - return {...i, isSelected: true}; - }), - }); - }; - const handleSelectAllDocuments = () => { - setGalleryImages({ - ...galleryImages, - other: galleryImages.other.map((i) => { - return {...i, isSelected: true}; - }), - }); - }; - const handleDeselectAll = () => { - setGalleryImages({ - ...galleryImages, - other: galleryImages.other.map((i) => { - return {...i, isSelected: false}; - }), - images: galleryImages.images.map((i) => { - return {...i, isSelected: false}; - }), - }); - }; + images: galleryImages.images.map((i) => { + return { ...i, isSelected: true }; + }) + }); + }; + const handleSelectAllDocuments = () => { + setGalleryImages({ + ...galleryImages, + other: galleryImages.other.map((i) => { + return { ...i, isSelected: true }; + }) + }); + }; + const handleDeselectAll = () => { + setGalleryImages({ + ...galleryImages, + other: galleryImages.other.map((i) => { + return { ...i, isSelected: false }; + }), + images: galleryImages.images.map((i) => { + return { ...i, isSelected: false }; + }) + }); + }; - return ( - - - - - - - ); + return ( + + + + + + + ); } diff --git a/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.container.jsx b/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.container.jsx index f91daae96..cfcab90c6 100644 --- a/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.container.jsx +++ b/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.container.jsx @@ -1,14 +1,14 @@ -import {FileExcelFilled, SyncOutlined} from "@ant-design/icons"; -import {Alert, Button, Card, Space} from "antd"; -import React, {useEffect, useState} from "react"; -import {Gallery} from "react-grid-gallery"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {getBillMedia, getJobMedia, toggleMediaSelected,} from "../../redux/media/media.actions"; -import {selectAllMedia} from "../../redux/media/media.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {CreateExplorerLinkForJob} from "../../utils/localmedia"; +import { FileExcelFilled, SyncOutlined } from "@ant-design/icons"; +import { Alert, Button, Card, Space } from "antd"; +import React, { useEffect, useState } from "react"; +import { Gallery } from "react-grid-gallery"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { getBillMedia, getJobMedia, toggleMediaSelected } from "../../redux/media/media.actions"; +import { selectAllMedia } from "../../redux/media/media.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { CreateExplorerLinkForJob } from "../../utils/localmedia"; import DocumentsLocalUploadComponent from "../documents-local-upload/documents-local-upload.component"; import JobsDocumentsLocalDeleteButton from "./jobs-documents-local-gallery.delete.component"; import JobsLocalGalleryDownloadButton from "./jobs-documents-local-gallery.download"; @@ -19,194 +19,163 @@ import Lightbox from "react-image-lightbox"; import "react-image-lightbox/style.css"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - allMedia: selectAllMedia, + bodyshop: selectBodyshop, + allMedia: selectAllMedia }); const mapDispatchToProps = (dispatch) => ({ - getJobMedia: (id) => dispatch(getJobMedia(id)), - getBillMedia: ({jobid, invoice_number}) => { - dispatch(getBillMedia({jobid, invoice_number})); - }, - toggleMediaSelected: ({jobid, filename}) => - dispatch(toggleMediaSelected({jobid, filename})), + getJobMedia: (id) => dispatch(getJobMedia(id)), + getBillMedia: ({ jobid, invoice_number }) => { + dispatch(getBillMedia({ jobid, invoice_number })); + }, + toggleMediaSelected: ({ jobid, filename }) => dispatch(toggleMediaSelected({ jobid, filename })) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsDocumentsLocalGallery); +export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsLocalGallery); export function JobsDocumentsLocalGallery({ - bodyshop, - toggleMediaSelected, - getJobMedia, - getBillMedia, - allMedia, - job, - invoice_number, - vendorid, - }) { - const {t} = useTranslation(); - const [modalState, setModalState] = useState({open: false, index: 0}); - useEffect(() => { - if (job) { - if (invoice_number) { - getBillMedia({jobid: job.id, invoice_number}); + bodyshop, + toggleMediaSelected, + getJobMedia, + getBillMedia, + allMedia, + job, + invoice_number, + vendorid +}) { + const { t } = useTranslation(); + const [modalState, setModalState] = useState({ open: false, index: 0 }); + useEffect(() => { + if (job) { + if (invoice_number) { + getBillMedia({ jobid: job.id, invoice_number }); + } else { + getJobMedia(job.id); + } + } + }, [job, invoice_number, getJobMedia, getBillMedia]); + let optimized; + const jobMedia = + allMedia && allMedia[job.id] + ? allMedia[job.id].reduce( + (acc, val) => { + if (val.type && val.type.mime && val.type.mime.startsWith("image")) { + acc.images.push({ + ...val, + fullsize: val.src, + src: val.thumbnail, + height: val.thumbnailHeight, + width: val.thumbnailWidth, + ...(val.optimized && { src: val.optimized, fullsize: val.src }) + }); + if (val.optimized) optimized = true; } else { - getJobMedia(job.id); + acc.other.push({ + ...val, + fullsize: val.src, + src: val.thumbnail, + height: val.thumbnailHeight, + width: val.thumbnailWidth, + tags: [{ value: val.filename, title: val.filename }] + }); } - } - }, [job, invoice_number, getJobMedia, getBillMedia]); - let optimized; - const jobMedia = - allMedia && allMedia[job.id] - ? allMedia[job.id].reduce( - (acc, val) => { - if ( - val.type && - val.type.mime && - val.type.mime.startsWith("image") - ) { - acc.images.push({ - ...val, - fullsize: val.src, - src: val.thumbnail, - height: val.thumbnailHeight, - width: val.thumbnailWidth, - ...(val.optimized && {src: val.optimized, fullsize: val.src}), - }); - if (val.optimized) optimized = true; - } else { - acc.other.push({ - ...val, - fullsize: val.src, - src: val.thumbnail, - height: val.thumbnailHeight, - width: val.thumbnailWidth, - tags: [{value: val.filename, title: val.filename}], - }); - } - return acc; - }, - {images: [], other: []} - ) - : {images: [], other: []}; + return acc; + }, + { images: [], other: [] } + ) + : { images: [], other: [] }; - return ( -
- - - - - - - - - - - - - - - { - toggleMediaSelected({jobid: job.id, filename: image.filename}); - }} - {...(optimized && { - customControls: [ - , - ], - })} - onClick={(index) => { - setModalState({open: true, index: index}); - // const media = allMedia[job.id].find( - // (m) => m.optimized === item.src - // ); + return ( +
+ + + + + + + + + + + + + + + { + toggleMediaSelected({ jobid: job.id, filename: image.filename }); + }} + {...(optimized && { + customControls: [ + + ] + })} + onClick={(index) => { + setModalState({ open: true, index: index }); + // const media = allMedia[job.id].find( + // (m) => m.optimized === item.src + // ); - // window.open( - // media ? media.fullsize : item.fullsize, - // "_blank", - // "toolbar=0,location=0,menubar=0" - // ); - }} - /> - - - { - return { - backgroundImage: , - height: "100%", - width: "100%", - cursor: "pointer", - }; - }} - onClick={(index) => { - window.open( - jobMedia.other[index].fullsize, - "_blank", - "toolbar=0,location=0,menubar=0" - ); - }} - onSelect={(index, image) => { - toggleMediaSelected({jobid: job.id, filename: image.filename}); - }} - /> - - {modalState.open && ( - setModalState({open: false, index: 0})} - onMovePrevRequest={() => - setModalState({ - ...modalState, - index: - (modalState.index + jobMedia.images.length - 1) % - jobMedia.images.length, - }) - } - onMoveNextRequest={() => - setModalState({ - ...modalState, - index: (modalState.index + 1) % jobMedia.images.length, - }) - } - /> - )} -
- ); + // window.open( + // media ? media.fullsize : item.fullsize, + // "_blank", + // "toolbar=0,location=0,menubar=0" + // ); + }} + /> +
+ + { + return { + backgroundImage: , + height: "100%", + width: "100%", + cursor: "pointer" + }; + }} + onClick={(index) => { + window.open(jobMedia.other[index].fullsize, "_blank", "toolbar=0,location=0,menubar=0"); + }} + onSelect={(index, image) => { + toggleMediaSelected({ jobid: job.id, filename: image.filename }); + }} + /> + + {modalState.open && ( + setModalState({ open: false, index: 0 })} + onMovePrevRequest={() => + setModalState({ + ...modalState, + index: (modalState.index + jobMedia.images.length - 1) % jobMedia.images.length + }) + } + onMoveNextRequest={() => + setModalState({ + ...modalState, + index: (modalState.index + 1) % jobMedia.images.length + }) + } + /> + )} +
+ ); } diff --git a/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.delete.component.jsx b/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.delete.component.jsx index 29af34e74..e2af7861e 100644 --- a/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.delete.component.jsx +++ b/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.delete.component.jsx @@ -1,81 +1,71 @@ -import {QuestionCircleOutlined} from "@ant-design/icons"; -import {Button, notification, Popconfirm} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { QuestionCircleOutlined } from "@ant-design/icons"; +import { Button, notification, Popconfirm } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import cleanAxios from "../../utils/CleanAxios"; //Context: currentUserEmail, bodyshop, jobid, invoiceid -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {getJobMedia} from "../../redux/media/media.actions"; -import {selectAllMedia} from "../../redux/media/media.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { getJobMedia } from "../../redux/media/media.actions"; +import { selectAllMedia } from "../../redux/media/media.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - allMedia: selectAllMedia, + allMedia: selectAllMedia, - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - getJobMedia: (id) => dispatch(getJobMedia(id)), + getJobMedia: (id) => dispatch(getJobMedia(id)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsDocumentsLocalDeleteButton); +export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsLocalDeleteButton); -export function JobsDocumentsLocalDeleteButton({ - bodyshop, - getJobMedia, - allMedia, - jobid, - }) { - const {t} = useTranslation(); +export function JobsDocumentsLocalDeleteButton({ bodyshop, getJobMedia, allMedia, jobid }) { + const { t } = useTranslation(); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(false); - const handleDelete = async () => { - logImEXEvent("job_documents_delete"); - setLoading(true); + const handleDelete = async () => { + logImEXEvent("job_documents_delete"); + setLoading(true); - const delres = await cleanAxios.post( - `${bodyshop.localmediaserverhttp}/jobs/delete`, - { - jobid: jobid, - files: ((allMedia && allMedia[jobid]) || []) - .filter((i) => i.isSelected) - .map((i) => i.filename), - }, - {headers: {ims_token: bodyshop.localmediatoken}} - ); - - if (delres.errors) { - notification["error"]({ - message: t("documents.errors.deleting", { - message: JSON.stringify(delres.errors), - }), - }); - } else { - notification.open({ - key: "docdeletedsuccesfully", - type: "success", - message: t("documents.successes.delete"), - }); - } - getJobMedia(jobid); - setLoading(false); - }; - - return ( - } - onConfirm={handleDelete} - title={t("documents.labels.confirmdelete")} - okText={t("general.actions.delete")} - okButtonProps={{type: "danger"}} - cancelText={t("general.actions.cancel")} - > - - + const delres = await cleanAxios.post( + `${bodyshop.localmediaserverhttp}/jobs/delete`, + { + jobid: jobid, + files: ((allMedia && allMedia[jobid]) || []).filter((i) => i.isSelected).map((i) => i.filename) + }, + { headers: { ims_token: bodyshop.localmediatoken } } ); + + if (delres.errors) { + notification["error"]({ + message: t("documents.errors.deleting", { + message: JSON.stringify(delres.errors) + }) + }); + } else { + notification.open({ + key: "docdeletedsuccesfully", + type: "success", + message: t("documents.successes.delete") + }); + } + getJobMedia(jobid); + setLoading(false); + }; + + return ( + } + onConfirm={handleDelete} + title={t("documents.labels.confirmdelete")} + okText={t("general.actions.delete")} + okButtonProps={{ type: "danger" }} + cancelText={t("general.actions.cancel")} + > + + + ); } diff --git a/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.download.jsx b/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.download.jsx index 02dff33a5..04a95313f 100644 --- a/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.download.jsx +++ b/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.download.jsx @@ -1,75 +1,63 @@ -import {Button} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; +import { Button } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import cleanAxios from "../../utils/CleanAxios"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectAllMedia} from "../../redux/media/media.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectAllMedia } from "../../redux/media/media.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - allMedia: selectAllMedia, + bodyshop: selectBodyshop, + allMedia: selectAllMedia }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsLocalGalleryDownloadButton); +export default connect(mapStateToProps, mapDispatchToProps)(JobsLocalGalleryDownloadButton); -export function JobsLocalGalleryDownloadButton({ - bodyshop, - galleryImages, - allMedia, - job, - }) { - const {t} = useTranslation(); - const [download, setDownload] = useState(null); +export function JobsLocalGalleryDownloadButton({ bodyshop, galleryImages, allMedia, job }) { + const { t } = useTranslation(); + const [download, setDownload] = useState(null); - function downloadProgress(progressEvent) { - setDownload((currentDownloadState) => { - return { - downloaded: progressEvent.loaded || 0, - speed: - (progressEvent.loaded || 0) - - ((currentDownloadState && currentDownloadState.downloaded) || 0), - }; - }); - } + function downloadProgress(progressEvent) { + setDownload((currentDownloadState) => { + return { + downloaded: progressEvent.loaded || 0, + speed: (progressEvent.loaded || 0) - ((currentDownloadState && currentDownloadState.downloaded) || 0) + }; + }); + } - const handleDownload = async () => { - const theDownloadedZip = await cleanAxios.post( - `${bodyshop.localmediaserverhttp}/jobs/download`, - { - jobid: job.id, - files: ((allMedia && allMedia[job.id]) || []) - .filter((i) => i.isSelected) - .map((i) => i.filename), - }, - { - headers: {ims_token: bodyshop.localmediatoken}, - responseType: "arraybuffer", - onDownloadProgress: downloadProgress, - } - ); - setDownload(null); - standardMediaDownload(theDownloadedZip.data, job.ro_number); - }; - - return ( - + const handleDownload = async () => { + const theDownloadedZip = await cleanAxios.post( + `${bodyshop.localmediaserverhttp}/jobs/download`, + { + jobid: job.id, + files: ((allMedia && allMedia[job.id]) || []).filter((i) => i.isSelected).map((i) => i.filename) + }, + { + headers: { ims_token: bodyshop.localmediatoken }, + responseType: "arraybuffer", + onDownloadProgress: downloadProgress + } ); + setDownload(null); + standardMediaDownload(theDownloadedZip.data, job.ro_number); + }; + + return ( + + ); } function standardMediaDownload(bufferData, filename) { - const a = document.createElement("a"); - const url = window.URL.createObjectURL(new Blob([bufferData])); - a.href = url; - a.download = `${filename}.zip`; - a.click(); + const a = document.createElement("a"); + const url = window.URL.createObjectURL(new Blob([bufferData])); + a.href = url; + a.download = `${filename}.zip`; + a.click(); } diff --git a/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.external.component.jsx b/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.external.component.jsx index a1eef302d..eeaea1e8f 100644 --- a/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.external.component.jsx +++ b/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.external.component.jsx @@ -1,79 +1,61 @@ -import React, {useEffect} from "react"; -import {Gallery} from "react-grid-gallery"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {getJobMedia, toggleMediaSelected,} from "../../redux/media/media.actions"; -import {selectAllMedia} from "../../redux/media/media.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useEffect } from "react"; +import { Gallery } from "react-grid-gallery"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { getJobMedia, toggleMediaSelected } from "../../redux/media/media.actions"; +import { selectAllMedia } from "../../redux/media/media.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - allMedia: selectAllMedia, + bodyshop: selectBodyshop, + allMedia: selectAllMedia }); const mapDispatchToProps = (dispatch) => ({ - getJobMedia: (id) => dispatch(getJobMedia(id)), + getJobMedia: (id) => dispatch(getJobMedia(id)), - toggleMediaSelected: ({jobid, filename}) => - dispatch(toggleMediaSelected({jobid, filename})), + toggleMediaSelected: ({ jobid, filename }) => dispatch(toggleMediaSelected({ jobid, filename })) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobDocumentsLocalGalleryExternal); +export default connect(mapStateToProps, mapDispatchToProps)(JobDocumentsLocalGalleryExternal); -function JobDocumentsLocalGalleryExternal({ - jobId, - externalMediaState, - getJobMedia, - toggleMediaSelected, - allMedia, - }) { - const [galleryImages, setgalleryImages] = externalMediaState; - const {t} = useTranslation(); +function JobDocumentsLocalGalleryExternal({ jobId, externalMediaState, getJobMedia, toggleMediaSelected, allMedia }) { + const [galleryImages, setgalleryImages] = externalMediaState; + const { t } = useTranslation(); - useEffect(() => { - if (jobId) { - getJobMedia(jobId); - } - }, [jobId, getJobMedia]); + useEffect(() => { + if (jobId) { + getJobMedia(jobId); + } + }, [jobId, getJobMedia]); - useEffect(() => { - let documents = - allMedia && allMedia[jobId] - ? allMedia[jobId].reduce((acc, val) => { - if ( - val.type && - val.type.mime && - val.type.mime.startsWith("image") - ) { - acc.push({...val, src: val.thumbnail, fullsize: val.src}); - } - return acc; - }, []) - : []; - console.log( - "🚀 ~ file: jobs-documents-local-gallery.external.component.jsx:48 ~ useEffect ~ documents:", - documents - ); - - setgalleryImages(documents); - }, [allMedia, jobId, setgalleryImages, t]); - - return ( -
- { - setgalleryImages( - galleryImages.map((g, idx) => - index === idx ? {...g, isSelected: !g.isSelected} : g - ) - ); - }} - /> -
+ useEffect(() => { + let documents = + allMedia && allMedia[jobId] + ? allMedia[jobId].reduce((acc, val) => { + if (val.type && val.type.mime && val.type.mime.startsWith("image")) { + acc.push({ ...val, src: val.thumbnail, fullsize: val.src }); + } + return acc; + }, []) + : []; + console.log( + "🚀 ~ file: jobs-documents-local-gallery.external.component.jsx:48 ~ useEffect ~ documents:", + documents ); + + setgalleryImages(documents); + }, [allMedia, jobId, setgalleryImages, t]); + + return ( +
+ { + setgalleryImages(galleryImages.map((g, idx) => (index === idx ? { ...g, isSelected: !g.isSelected } : g))); + }} + /> +
+ ); } diff --git a/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.reassign.component.jsx b/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.reassign.component.jsx index b27474a14..f4cc14a22 100644 --- a/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.reassign.component.jsx +++ b/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.reassign.component.jsx @@ -1,96 +1,86 @@ -import {Button, Form, Popover, Space} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {getJobMedia} from "../../redux/media/media.actions"; -import {selectAllMedia} from "../../redux/media/media.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { Button, Form, Popover, Space } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { getJobMedia } from "../../redux/media/media.actions"; +import { selectAllMedia } from "../../redux/media/media.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import cleanAxios from "../../utils/CleanAxios"; import JobSearchSelect from "../job-search-select/job-search-select.component"; const mapStateToProps = createStructuredSelector({ - allMedia: selectAllMedia, - bodyshop: selectBodyshop, + allMedia: selectAllMedia, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - getJobMedia: (id) => dispatch(getJobMedia(id)), + getJobMedia: (id) => dispatch(getJobMedia(id)) - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsDocumentsLocalGalleryReassign); +export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsLocalGalleryReassign); -export function JobsDocumentsLocalGalleryReassign({ - bodyshop, - jobid, - allMedia, - getJobMedia, - }) { - const {t} = useTranslation(); - const [form] = Form.useForm(); +export function JobsDocumentsLocalGalleryReassign({ bodyshop, jobid, allMedia, getJobMedia }) { + const { t } = useTranslation(); + const [form] = Form.useForm(); - const [open, setOpen] = useState(false); - const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); - const handleFinish = async ({jobid: newJobid}) => { - setLoading(true); - const selectedDocuments = allMedia[jobid].filter((m) => m.isSelected); + const handleFinish = async ({ jobid: newJobid }) => { + setLoading(true); + const selectedDocuments = allMedia[jobid].filter((m) => m.isSelected); - await cleanAxios.post( - `${bodyshop.localmediaserverhttp}/jobs/move`, + await cleanAxios.post( + `${bodyshop.localmediaserverhttp}/jobs/move`, + { + from_jobid: jobid, + jobid: newJobid, + files: selectedDocuments.map((f) => f.filename) + }, + { headers: { ims_token: bodyshop.localmediatoken } } + ); + + getJobMedia(jobid); + setOpen(false); + setLoading(false); + }; + + const popContent = ( +
+
+ f.filename), - }, - {headers: {ims_token: bodyshop.localmediatoken}} - ); + required: true + //message: t("general.validation.required"), + } + ]} + name={"jobid"} + > + + + + + + + +
+ ); - getJobMedia(jobid); - setOpen(false); - setLoading(false); - }; - - const popContent = ( -
-
- - - - - - - - -
- ); - - return ( - - - - ); + return ( + + + + ); } diff --git a/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.selectall.component.jsx b/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.selectall.component.jsx index a8c41d2ac..6b9844436 100644 --- a/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.selectall.component.jsx +++ b/client/src/components/jobs-documents-local-gallery/jobs-documents-local-gallery.selectall.component.jsx @@ -1,51 +1,40 @@ -import {Button, Space} from "antd"; +import { Button, Space } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {deselectAllMediaForJob, selectAllmediaForJob,} from "../../redux/media/media.actions"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { deselectAllMediaForJob, selectAllmediaForJob } from "../../redux/media/media.actions"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser + //currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - selectAllmediaForJob: (jobid) => dispatch(selectAllmediaForJob(jobid)), - deselectAllmediaForJob: (jobid) => dispatch(deselectAllMediaForJob(jobid)), + selectAllmediaForJob: (jobid) => dispatch(selectAllmediaForJob(jobid)), + deselectAllmediaForJob: (jobid) => dispatch(deselectAllMediaForJob(jobid)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsDocumentsLocalGallerySelectAllComponent); +export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsLocalGallerySelectAllComponent); -export function JobsDocumentsLocalGallerySelectAllComponent({ - jobid, - selectAllmediaForJob, - deselectAllmediaForJob, - }) { - const {t} = useTranslation(); +export function JobsDocumentsLocalGallerySelectAllComponent({ jobid, selectAllmediaForJob, deselectAllmediaForJob }) { + const { t } = useTranslation(); - // onSelectImage={(index, image) => { - // toggleMediaSelected({ jobid: job.id, filename: image.filename }); - // }} + // onSelectImage={(index, image) => { + // toggleMediaSelected({ jobid: job.id, filename: image.filename }); + // }} - const handleSelectAll = () => { - selectAllmediaForJob({jobid}); - }; + const handleSelectAll = () => { + selectAllmediaForJob({ jobid }); + }; - const handleDeselectAll = () => { - deselectAllmediaForJob({jobid}); - }; + const handleDeselectAll = () => { + deselectAllmediaForJob({ jobid }); + }; - return ( - - + return ( + + - - - ); + + + ); } diff --git a/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx b/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx index 80580a425..70f7763f2 100644 --- a/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx +++ b/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx @@ -1,237 +1,210 @@ -import {useMutation} from "@apollo/client"; -import {Button, notification} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, notification } from "antd"; import axios from "axios"; import _ from "lodash"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {auth, logImEXEvent} from "../../firebase/firebase.utils"; -import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries"; -import {UPDATE_JOBS} from "../../graphql/jobs.queries"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { auth, logImEXEvent } from "../../firebase/firebase.utils"; +import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { UPDATE_JOBS } from "../../graphql/jobs.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; -import { - selectBodyshop, - selectCurrentUser, -} from "../../redux/user/user.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import client from "../../utils/GraphQLClient"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({ jobid, operation, type }) => - dispatch(insertAuditTrail({ jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); function updateJobCache(items) { - client.cache.modify({ - id: "ROOT_QUERY", - fields: { - jobs(existingJobs = []) { - return existingJobs.filter( - (jobRef) => jobRef.__ref.includes(items) === false - ); - }, - }, - }); + client.cache.modify({ + id: "ROOT_QUERY", + fields: { + jobs(existingJobs = []) { + return existingJobs.filter((jobRef) => jobRef.__ref.includes(items) === false); + } + } + }); } export function JobsExportAllButton({ - bodyshop, - currentUser, - jobIds, - disabled, - loadingCallback, - completedCallback, - refetch, - insertAuditTrail, - }) { - const { t } = useTranslation(); - const [updateJob] = useMutation(UPDATE_JOBS); - const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); + bodyshop, + currentUser, + jobIds, + disabled, + loadingCallback, + completedCallback, + refetch, + insertAuditTrail +}) { + const { t } = useTranslation(); + const [updateJob] = useMutation(UPDATE_JOBS); + const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); - const [loading, setLoading] = useState(false); - const handleQbxml = async () => { - logImEXEvent("jobs_export_all"); - let PartnerResponse; - setLoading(true); - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { - PartnerResponse = await axios.post(`/qbo/receivables`, { - jobIds: jobIds, - elgen: true, - }); - } else { - let QbXmlResponse; - try { - QbXmlResponse = await axios.post( - "/accounting/qbxml/receivables", - {jobIds: jobIds}, - { - headers: { - Authorization: `Bearer ${await auth.currentUser.getIdToken()}`, - }, - } - ); - } catch (error) { - console.log("Error getting QBXML from Server.", error); - notification["error"]({ - message: t("jobs.errors.exporting", { - error: "Unable to retrieve QBXML. " + JSON.stringify(error.message), - }), - }); - setLoading(false); - return; + const [loading, setLoading] = useState(false); + const handleQbxml = async () => { + logImEXEvent("jobs_export_all"); + let PartnerResponse; + setLoading(true); + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + PartnerResponse = await axios.post(`/qbo/receivables`, { + jobIds: jobIds, + elgen: true + }); + } else { + let QbXmlResponse; + try { + QbXmlResponse = await axios.post( + "/accounting/qbxml/receivables", + { jobIds: jobIds }, + { + headers: { + Authorization: `Bearer ${await auth.currentUser.getIdToken()}` } - - try { - PartnerResponse = await axios.post( - "http://localhost:1337/qb/", - // "http://609feaeae986.ngrok.io/qb/", - QbXmlResponse.data, - { - headers: { - Authorization: `Bearer ${await auth.currentUser.getIdToken()}`, - }, - } - ); - } catch (error) { - console.log("Error connecting to quickbooks or partner.", error); - notification["error"]({ - message: t("jobs.errors.exporting-partner"), - }); - setLoading(false); - return; - } - } - - console.log("PartnerResponse", PartnerResponse); - const groupedData = _.groupBy( - PartnerResponse.data, - bodyshop.accountingconfig.qbo ? "jobid" : "id" + } ); + } catch (error) { + console.log("Error getting QBXML from Server.", error); + notification["error"]({ + message: t("jobs.errors.exporting", { + error: "Unable to retrieve QBXML. " + JSON.stringify(error.message) + }) + }); + setLoading(false); + return; + } - await Promise.all( - Object.keys(groupedData).map(async (key) => { - //Check to see if any of them failed. If they didn't don't execute the update. - const failedTransactions = groupedData[key].filter((r) => !r.success); - const successfulTransactions = groupedData[key].filter( - (r) => r.success - ); - if (failedTransactions.length > 0) { - //Uh oh. At least one was no good. - failedTransactions.forEach((ft) => { - notification.open({ - // key: "failedexports", - type: "error", - message: t("jobs.errors.exporting", { - error: ft.errorMessage || "", - }), - }); - //Call is not awaited as it is not critical to finish before proceeding. - }); + try { + PartnerResponse = await axios.post( + "http://localhost:1337/qb/", + // "http://609feaeae986.ngrok.io/qb/", + QbXmlResponse.data, + { + headers: { + Authorization: `Bearer ${await auth.currentUser.getIdToken()}` + } + } + ); + } catch (error) { + console.log("Error connecting to quickbooks or partner.", error); + notification["error"]({ + message: t("jobs.errors.exporting-partner") + }); + setLoading(false); + return; + } + } - if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { - //QBO Logs are handled server side. - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - jobid: key, - successful: false, - message: JSON.stringify( - failedTransactions.map((ft) => ft.errorMessage) - ), - useremail: currentUser.email, - }, - ], - }, - }); - } - } else { - if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { - //QBO Logs are handled server side. - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - jobid: key, - successful: true, - useremail: currentUser.email, - }, - ], - }, - }); + console.log("PartnerResponse", PartnerResponse); + const groupedData = _.groupBy(PartnerResponse.data, bodyshop.accountingconfig.qbo ? "jobid" : "id"); - const jobUpdateResponse = await updateJob({ - variables: { - jobIds: [key], - fields: { - status: - bodyshop.md_ro_statuses.default_exported || "Exported*", - date_exported: new Date(), - }, - }, - }); + await Promise.all( + Object.keys(groupedData).map(async (key) => { + //Check to see if any of them failed. If they didn't don't execute the update. + const failedTransactions = groupedData[key].filter((r) => !r.success); + const successfulTransactions = groupedData[key].filter((r) => r.success); + if (failedTransactions.length > 0) { + //Uh oh. At least one was no good. + failedTransactions.forEach((ft) => { + notification.open({ + // key: "failedexports", + type: "error", + message: t("jobs.errors.exporting", { + error: ft.errorMessage || "" + }) + }); + //Call is not awaited as it is not critical to finish before proceeding. + }); + + if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { + //QBO Logs are handled server side. + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + jobid: key, + successful: false, + message: JSON.stringify(failedTransactions.map((ft) => ft.errorMessage)), + useremail: currentUser.email + } + ] + } + }); + } + } else { + if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { + //QBO Logs are handled server side. + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + jobid: key, + successful: true, + useremail: currentUser.email + } + ] + } + }); + + const jobUpdateResponse = await updateJob({ + variables: { + jobIds: [key], + fields: { + status: bodyshop.md_ro_statuses.default_exported || "Exported*", + date_exported: new Date() + } + } + }); if (!!!jobUpdateResponse.errors) { notification.open({ type: "success", key: "jobsuccessexport", - message: t("jobs.successes.exported"), + message: t("jobs.successes.exported") }); jobUpdateResponse.data.update_jobs.returning.forEach((job) => { insertAuditTrail({ jobid: job.id, operation: AuditTrailMapping.jobexported(), - type: "jobexported", + type: "jobexported" }); }); - updateJobCache( - jobUpdateResponse.data.update_jobs.returning.map( - (job) => job.id - ) - ); + updateJobCache(jobUpdateResponse.data.update_jobs.returning.map((job) => job.id)); } else { notification["error"]({ message: t("jobs.errors.exporting", { - error: JSON.stringify(jobUpdateResponse.error), - }), + error: JSON.stringify(jobUpdateResponse.error) + }) }); } } - if ( - bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo && - successfulTransactions.length > 0 - ) { + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { notification.open({ type: "success", key: "jobsuccessexport", - message: t("jobs.successes.exported"), + message: t("jobs.successes.exported") }); const successfulTransactionsSet = [ ...new Set( successfulTransactions.map( - (st) => - st[ - bodyshop.accountingconfig && bodyshop.accountingconfig.qbo - ? "jobid" - : "id" - ] + (st) => st[bodyshop.accountingconfig && bodyshop.accountingconfig.qbo ? "jobid" : "id"] ) - ), + ) ]; if (successfulTransactionsSet.length > 0) { insertAuditTrail({ jobid: successfulTransactionsSet[0], operation: AuditTrailMapping.jobexported(), - type: "jobexported", + type: "jobexported" }); } updateJobCache(successfulTransactionsSet); @@ -240,19 +213,16 @@ export function JobsExportAllButton({ }) ); - if (!!completedCallback) completedCallback([]); - if (!!loadingCallback) loadingCallback(false); - setLoading(false); - }; + if (!!completedCallback) completedCallback([]); + if (!!loadingCallback) loadingCallback(false); + setLoading(false); + }; - return ( - - ); + return ( + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsExportAllButton); +export default connect(mapStateToProps, mapDispatchToProps)(JobsExportAllButton); diff --git a/client/src/components/jobs-find-modal/jobs-find-modal.component.jsx b/client/src/components/jobs-find-modal/jobs-find-modal.component.jsx index dc1527650..bd7cd9e0f 100644 --- a/client/src/components/jobs-find-modal/jobs-find-modal.component.jsx +++ b/client/src/components/jobs-find-modal/jobs-find-modal.component.jsx @@ -1,307 +1,284 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {Button, Checkbox, Divider, Input, Space, Table} from "antd"; +import { SyncOutlined } from "@ant-design/icons"; +import { Button, Checkbox, Divider, Input, Space, Table } from "antd"; import dayjs from "../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {Link} from "react-router-dom"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; import PhoneFormatter from "../../utils/PhoneFormatter"; import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; export default function JobsFindModalComponent({ - selectedJob, - setSelectedJob, - jobsList, - jobsListLoading, - importOptionsState, - modalSearchState, - jobsListRefetch, - partsQueueToggle, - setPartsQueueToggle, - updateSchComp, - setSchComp, - }) { - const {t} = useTranslation(); - const [modalSearch, setModalSearch] = modalSearchState; - const [importOptions, setImportOptions] = importOptionsState; - const [checkUTT, setCheckUTT] = useState(false); - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - width: "8%", - render: (text, record) => ( - - - {record.ro_number || t("general.labels.na")} - + selectedJob, + setSelectedJob, + jobsList, + jobsListLoading, + importOptionsState, + modalSearchState, + jobsListRefetch, + partsQueueToggle, + setPartsQueueToggle, + updateSchComp, + setSchComp +}) { + const { t } = useTranslation(); + const [modalSearch, setModalSearch] = modalSearchState; + const [importOptions, setImportOptions] = importOptionsState; + const [checkUTT, setCheckUTT] = useState(false); + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + width: "8%", + render: (text, record) => ( + + {record.ro_number || t("general.labels.na")} - ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - ellipsis: true, + ) + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, - width: "25%", + width: "25%", - render: (text, record) => { - return record.owner ? ( - - - - ) : ( - - + render: (text, record) => { + return record.owner ? ( + + + + ) : ( + + - ); - }, - }, - { - title: t("jobs.fields.ownr_ph1"), - dataIndex: "ownr_ph1", - key: "ownr_ph1", - width: "12%", - ellipsis: true, - render: (text, record) => { - return record.ownr_ph1 ? ( - {record.ownr_ph1} - ) : ( - t("general.labels.unknown") - ); - }, - }, - { - title: t("jobs.fields.ownr_ph2"), - dataIndex: "ownr_ph2", - key: "ownr_ph2", - width: "12%", - ellipsis: true, - render: (text, record) => { - return record.ownr_ph2 ? ( - {record.ownr_ph2} - ) : ( - t("general.labels.unknown") - ); - }, - }, - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", - width: "10%", - ellipsis: true, - render: (text, record) => { - return record.status || t("general.labels.na"); - }, - }, + ); + } + }, + { + title: t("jobs.fields.ownr_ph1"), + dataIndex: "ownr_ph1", + key: "ownr_ph1", + width: "12%", + ellipsis: true, + render: (text, record) => { + return record.ownr_ph1 ? {record.ownr_ph1} : t("general.labels.unknown"); + } + }, + { + title: t("jobs.fields.ownr_ph2"), + dataIndex: "ownr_ph2", + key: "ownr_ph2", + width: "12%", + ellipsis: true, + render: (text, record) => { + return record.ownr_ph2 ? {record.ownr_ph2} : t("general.labels.unknown"); + } + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + width: "10%", + ellipsis: true, + render: (text, record) => { + return record.status || t("general.labels.na"); + } + }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - width: "15%", - ellipsis: true, - render: (text, record) => ( - - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - - ), - }, - { - title: t("vehicles.fields.plate_no"), - dataIndex: "plate_no", - key: "plate_no", - width: "8%", - ellipsis: true, - render: (text, record) => { - return record.plate_no ? ( - {record.plate_no} - ) : ( - t("general.labels.unknown") - ); - }, - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - width: "12%", - ellipsis: true, - render: (text, record) => { - return record.clm_no ? ( - {record.clm_no} - ) : ( - t("general.labels.unknown") - ); - }, - }, - ]; + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + width: "15%", + ellipsis: true, + render: (text, record) => ( + + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + + ) + }, + { + title: t("vehicles.fields.plate_no"), + dataIndex: "plate_no", + key: "plate_no", + width: "8%", + ellipsis: true, + render: (text, record) => { + return record.plate_no ? {record.plate_no} : t("general.labels.unknown"); + } + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + width: "12%", + ellipsis: true, + render: (text, record) => { + return record.clm_no ? {record.clm_no} : t("general.labels.unknown"); + } + } + ]; - const handleOnRowClick = (record) => { - if (record) { - if (record.id) { - setSelectedJob(record.id); - if (record.actual_in && record.scheduled_completion) { - setSchComp({ - ...updateSchComp, - actual_in: record.actual_in, - scheduled_completion: record.scheduled_completion, - }); - } else { - if (record.actual_in && !record.scheduled_completion) { - setSchComp({ - ...updateSchComp, - actual_in: record.actual_in, - scheduled_completion: dayjs(), - }); - } - if (!record.actual_in && record.scheduled_completion) { - setSchComp({ - ...updateSchComp, - actual_in: dayjs(), - scheduled_completion: dayjs(record.scheduled_completion), - }); - } - if (!record.actual_in && !record.scheduled_completion) { - setSchComp({ - ...updateSchComp, - actual_in: dayjs(), - scheduled_completion: dayjs(), - }); - } - } - return; - } + const handleOnRowClick = (record) => { + if (record) { + if (record.id) { + setSelectedJob(record.id); + if (record.actual_in && record.scheduled_completion) { + setSchComp({ + ...updateSchComp, + actual_in: record.actual_in, + scheduled_completion: record.scheduled_completion + }); + } else { + if (record.actual_in && !record.scheduled_completion) { + setSchComp({ + ...updateSchComp, + actual_in: record.actual_in, + scheduled_completion: dayjs() + }); + } + if (!record.actual_in && record.scheduled_completion) { + setSchComp({ + ...updateSchComp, + actual_in: dayjs(), + scheduled_completion: dayjs(record.scheduled_completion) + }); + } + if (!record.actual_in && !record.scheduled_completion) { + setSchComp({ + ...updateSchComp, + actual_in: dayjs(), + scheduled_completion: dayjs() + }); + } } - setSelectedJob(null); - }; + return; + } + } + setSelectedJob(null); + }; - return ( -
-
( -
- {t("jobs.labels.existing_jobs")} - - { - setModalSearch(e.target.value); - }} - /> -
- )} - pagination={{position: "bottom"}} - columns={columns} - rowKey="id" - loading={jobsListLoading} - dataSource={jobsList} - rowSelection={{ - onSelect: (props) => { - setSelectedJob(props.id); - if (props.actual_in && props.scheduled_completion) { - setSchComp({ - ...updateSchComp, - actual_in: props.actual_in, - scheduled_completion: props.scheduled_completion, - }); - } else { - if (props.actual_in && !props.scheduled_completion) { - setSchComp({ - ...updateSchComp, - actual_in: props.actual_in, - scheduled_completion: dayjs(), - }); - } - if (!props.actual_in && props.scheduled_completion) { - setSchComp({ - ...updateSchComp, - actual_in: dayjs(), - scheduled_completion: dayjs(props.scheduled_completion), - }); - } - if (!props.actual_in && !props.scheduled_completion) { - setSchComp({ - ...updateSchComp, - actual_in: dayjs(), - scheduled_completion: dayjs(), - }); - } - } - }, - type: "radio", - selectedRowKeys: [selectedJob], - }} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - handleOnRowClick(record); - }, - }; - }} + return ( +
+
( +
+ {t("jobs.labels.existing_jobs")} + + { + setModalSearch(e.target.value); + }} /> - - - setImportOptions({ - ...importOptions, - overrideHeaders: e.target.checked, - }) - } - > - {t("jobs.labels.override_header")} - - setPartsQueueToggle(e.target.checked)} - > - {t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")} - - setSchComp({...updateSchComp, checked: e.target.checked}) +
+ )} + pagination={{ position: "bottom" }} + columns={columns} + rowKey="id" + loading={jobsListLoading} + dataSource={jobsList} + rowSelection={{ + onSelect: (props) => { + setSelectedJob(props.id); + if (props.actual_in && props.scheduled_completion) { + setSchComp({ + ...updateSchComp, + actual_in: props.actual_in, + scheduled_completion: props.scheduled_completion + }); + } else { + if (props.actual_in && !props.scheduled_completion) { + setSchComp({ + ...updateSchComp, + actual_in: props.actual_in, + scheduled_completion: dayjs() + }); + } + if (!props.actual_in && props.scheduled_completion) { + setSchComp({ + ...updateSchComp, + actual_in: dayjs(), + scheduled_completion: dayjs(props.scheduled_completion) + }); + } + if (!props.actual_in && !props.scheduled_completion) { + setSchComp({ + ...updateSchComp, + actual_in: dayjs(), + scheduled_completion: dayjs() + }); + } } + }, + type: "radio", + selectedRowKeys: [selectedJob] + }} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + handleOnRowClick(record); + } + }; + }} + /> + + + + setImportOptions({ + ...importOptions, + overrideHeaders: e.target.checked + }) + } > - {t("jobs.labels.update_scheduled_completion")} + {t("jobs.labels.override_header")} - {updateSchComp.checked === true ? ( - <> - {checkUTT === false ? ( - { - setSchComp({...updateSchComp, scheduled_completion: e}); - }} - /> - ) : null} - { - setCheckUTT(e.target.checked); - setSchComp({ - ...updateSchComp, - scheduled_completion: null, - automatic: true, - }); - }} - > - {t("jobs.labels.calc_scheuled_completion")} - - + setPartsQueueToggle(e.target.checked)}> + {t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")} + + setSchComp({ ...updateSchComp, checked: e.target.checked })} + > + {t("jobs.labels.update_scheduled_completion")} + + {updateSchComp.checked === true ? ( + <> + {checkUTT === false ? ( + { + setSchComp({ ...updateSchComp, scheduled_completion: e }); + }} + /> ) : null} - - - ); + { + setCheckUTT(e.target.checked); + setSchComp({ + ...updateSchComp, + scheduled_completion: null, + automatic: true + }); + }} + > + {t("jobs.labels.calc_scheuled_completion")} + + + ) : null} + + + ); } diff --git a/client/src/components/jobs-find-modal/jobs-find-modal.container.jsx b/client/src/components/jobs-find-modal/jobs-find-modal.container.jsx index 0abe7904c..c858aba3d 100644 --- a/client/src/components/jobs-find-modal/jobs-find-modal.container.jsx +++ b/client/src/components/jobs-find-modal/jobs-find-modal.container.jsx @@ -1,103 +1,89 @@ -import {useQuery} from "@apollo/client"; -import {Modal} from "antd"; +import { useQuery } from "@apollo/client"; +import { Modal } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {QUERY_ALL_ACTIVE_JOBS} from "../../graphql/jobs.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import JobsFindModalComponent from "./jobs-find-modal.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); export default connect( - mapStateToProps, - null + mapStateToProps, + null )(function JobsFindModalContainer({ - bodyshop, - loading, - error, - selectedJob, - setSelectedJob, - importOptionsState, - modalSearchState, - partsQueueToggle, - setPartsQueueToggle, - updateSchComp, - setSchComp, ...modalProps - }) { - const {t} = useTranslation(); + bodyshop, + loading, + error, + selectedJob, + setSelectedJob, + importOptionsState, + modalSearchState, + partsQueueToggle, + setPartsQueueToggle, + updateSchComp, + setSchComp, + ...modalProps +}) { + const { t } = useTranslation(); - const jobsList = useQuery(QUERY_ALL_ACTIVE_JOBS, { - variables: { - statuses: bodyshop.md_ro_statuses.active_statuses || ["Open"], - }, - skip: !modalProps.open, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const jobsList = useQuery(QUERY_ALL_ACTIVE_JOBS, { + variables: { + statuses: bodyshop.md_ro_statuses.active_statuses || ["Open"] + }, + skip: !modalProps.open, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const modalSearch = modalSearchState[0]; + const modalSearch = modalSearchState[0]; - const jobsData = - jobsList.data && jobsList.data.jobs - ? modalSearch - ? jobsList.data.jobs.filter( - (j) => - (j.ro_number || "") - .toLowerCase() - .includes(modalSearch.toLowerCase()) || - (j.ownr_fn || "") - .toLowerCase() - .includes(modalSearch.toLowerCase()) || - (j.ownr_ln || "") - .toLowerCase() - .includes(modalSearch.toLowerCase()) || - (j.status || "") - .toLowerCase() - .includes(modalSearch.toLowerCase()) || - (j.v_make_desc || "") - .toLowerCase() - .includes(modalSearch.toLowerCase()) || - (j.v_model_desc || "") - .toLowerCase() - .includes(modalSearch.toLowerCase()) || - (j.clm_no || "") - .toLowerCase() - .includes(modalSearch.toLowerCase()) || - (j.plate_no || "") - .toLowerCase() - .includes(modalSearch.toLowerCase()) - ) - : jobsList.data.jobs - : null; + const jobsData = + jobsList.data && jobsList.data.jobs + ? modalSearch + ? jobsList.data.jobs.filter( + (j) => + (j.ro_number || "").toLowerCase().includes(modalSearch.toLowerCase()) || + (j.ownr_fn || "").toLowerCase().includes(modalSearch.toLowerCase()) || + (j.ownr_ln || "").toLowerCase().includes(modalSearch.toLowerCase()) || + (j.status || "").toLowerCase().includes(modalSearch.toLowerCase()) || + (j.v_make_desc || "").toLowerCase().includes(modalSearch.toLowerCase()) || + (j.v_model_desc || "").toLowerCase().includes(modalSearch.toLowerCase()) || + (j.clm_no || "").toLowerCase().includes(modalSearch.toLowerCase()) || + (j.plate_no || "").toLowerCase().includes(modalSearch.toLowerCase()) + ) + : jobsList.data.jobs + : null; - return ( - - {loading ? : null} - {error ? : null} - - - ); + return ( + + {loading ? : null} + {error ? : null} + + + ); }); diff --git a/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx b/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx index 3b164466d..1e12f3586 100644 --- a/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx +++ b/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx @@ -1,282 +1,267 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Input, Space, Table, Typography} from "antd"; +import { SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Input, Space, Table, Typography } from "antd"; import axios from "axios"; import _ from "lodash"; import queryString from "query-string"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {pageLimit} from "../../utils/config"; +import { pageLimit } from "../../utils/config"; import useLocalStorage from "../../utils/useLocalStorage"; import StartChatButton from "../chat-open-button/chat-open-button.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function JobsList({bodyshop, refetch, loading, jobs, total}) { - const search = queryString.parse(useLocation().search); - const [openSearchResults, setOpenSearchResults] = useState([]); - const [searchLoading, setSearchLoading] = useState(false); - const [filter, setFilter] = useLocalStorage("filter_jobs_all", null); - const {page, sortcolumn, sortorder} = search; - const history = useNavigate(); +export function JobsList({ bodyshop, refetch, loading, jobs, total }) { + const search = queryString.parse(useLocation().search); + const [openSearchResults, setOpenSearchResults] = useState([]); + const [searchLoading, setSearchLoading] = useState(false); + const [filter, setFilter] = useLocalStorage("filter_jobs_all", null); + const { page, sortcolumn, sortorder } = search; + const history = useNavigate(); - const {t} = useTranslation(); - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number), - sortOrder: sortcolumn === "ro_number" && sortorder, - render: (text, record) => ( - - {record.ro_number || t("general.labels.na")} - - ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "ownr_ln", - key: "ownr_ln", - ellipsis: true, - //sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), + const { t } = useTranslation(); + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: sortcolumn === "ro_number" && sortorder, + render: (text, record) => ( + {record.ro_number || t("general.labels.na")} + ) + }, + { + title: t("jobs.fields.owner"), + dataIndex: "ownr_ln", + key: "ownr_ln", + ellipsis: true, + //sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), - //sortOrder: sortcolumn === "ownr_ln" && sortorder, - render: (text, record) => { - return record.ownerid ? ( - - - - ) : ( - - + //sortOrder: sortcolumn === "ownr_ln" && sortorder, + render: (text, record) => { + return record.ownerid ? ( + + + + ) : ( + + - ); - }, - }, - { - title: t("jobs.fields.ownr_ph1"), - dataIndex: "ownr_ph1", - key: "ownr_ph1", + ); + } + }, + { + title: t("jobs.fields.ownr_ph1"), + dataIndex: "ownr_ph1", + key: "ownr_ph1", - ellipsis: true, - render: (text, record) => ( - - ), - }, - { - title: t("jobs.fields.ownr_ph2"), - dataIndex: "ownr_ph2", - key: "ownr_ph2", + ellipsis: true, + render: (text, record) => + }, + { + title: t("jobs.fields.ownr_ph2"), + dataIndex: "ownr_ph2", + key: "ownr_ph2", - ellipsis: true, - render: (text, record) => ( - - ), - }, - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", + ellipsis: true, + render: (text, record) => + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", - ellipsis: true, - sorter: true, // (a, b) => alphaSort(a.status, b.status), - sortOrder: sortcolumn === "status" && sortorder, - render: (text, record) => { - return record.status || t("general.labels.na"); - }, - filteredValue: filter?.status || null, - filters: bodyshop.md_ro_statuses.statuses.map((s) => { - return {text: s, value: [s]}; - }), - onFilter: (value, record) => value.includes(record.status), - }, + ellipsis: true, + sorter: true, // (a, b) => alphaSort(a.status, b.status), + sortOrder: sortcolumn === "status" && sortorder, + render: (text, record) => { + return record.status || t("general.labels.na"); + }, + filteredValue: filter?.status || null, + filters: bodyshop.md_ro_statuses.statuses.map((s) => { + return { text: s, value: [s] }; + }), + onFilter: (value, record) => value.includes(record.status) + }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", - ellipsis: true, - render: (text, record) => { - return record.vehicleid ? ( - - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - - ) : ( - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - ); - }, - }, - { - title: t("vehicles.fields.plate_no"), - dataIndex: "plate_no", - key: "plate_no", - ellipsis: true, - sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no), - sortOrder: sortcolumn === "plate_no" && sortorder, - render: (text, record) => { - return record.plate_no ? record.plate_no : ""; - }, - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - ellipsis: true, - sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: sortcolumn === "clm_no" && sortorder, - render: (text, record) => - `${record.clm_no || ""}${ - record.po_number ? ` (PO: ${record.po_number})` : "" - }`, - }, - { - title: t("jobs.fields.ins_co_nm"), - dataIndex: "ins_co_nm", - key: "ins_co_nm", - ellipsis: true, - }, - { - title: t("jobs.fields.clm_total"), - dataIndex: "clm_total", - key: "clm_total", + ellipsis: true, + render: (text, record) => { + return record.vehicleid ? ( + + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + + ) : ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + ); + } + }, + { + title: t("vehicles.fields.plate_no"), + dataIndex: "plate_no", + key: "plate_no", + ellipsis: true, + sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no), + sortOrder: sortcolumn === "plate_no" && sortorder, + render: (text, record) => { + return record.plate_no ? record.plate_no : ""; + } + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + ellipsis: true, + sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no), + sortOrder: sortcolumn === "clm_no" && sortorder, + render: (text, record) => `${record.clm_no || ""}${record.po_number ? ` (PO: ${record.po_number})` : ""}` + }, + { + title: t("jobs.fields.ins_co_nm"), + dataIndex: "ins_co_nm", + key: "ins_co_nm", + ellipsis: true + }, + { + title: t("jobs.fields.clm_total"), + dataIndex: "clm_total", + key: "clm_total", - sorter: true, //(a, b) => a.clm_total - b.clm_total, - sortOrder: sortcolumn === "clm_total" && sortorder, - render: (text, record) => { - return record.clm_total ? ( - {record.clm_total} - ) : ( - t("general.labels.unknown") - ); - }, - }, - { - title: t("jobs.fields.owner_owing"), - dataIndex: "owner_owing", - key: "owner_owing", + sorter: true, //(a, b) => a.clm_total - b.clm_total, + sortOrder: sortcolumn === "clm_total" && sortorder, + render: (text, record) => { + return record.clm_total ? ( + {record.clm_total} + ) : ( + t("general.labels.unknown") + ); + } + }, + { + title: t("jobs.fields.owner_owing"), + dataIndex: "owner_owing", + key: "owner_owing", - render: (text, record) => ( - {record.owner_owing} - ), - }, - { - title: t("jobs.fields.comment"), - dataIndex: "comment", - key: "comment", - ellipsis: true, - }, - ]; - - const handleTableChange = (pagination, filters, sorter) => { - search.page = pagination.current; - search.sortcolumn = sorter.column && sorter.column.key; - search.sortorder = sorter.order; - if (filters.status) { - search.statusFilters = JSON.stringify(_.flattenDeep(filters.status)); - } else { - delete search.statusFilters; - } - setFilter(filters); - history({search: queryString.stringify(search)}); - }; - - useEffect(() => { - if (search.search && search.search.trim() !== "") { - searchJobs(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - async function searchJobs(value) { - try { - setSearchLoading(true); - const searchData = await axios.post("/search", { - search: value || search.search, - index: "jobs", - }); - setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source)); - } catch (error) { - console.log("Error while fetching search results", error); - } finally { - setSearchLoading(false); - } + render: (text, record) => {record.owner_owing} + }, + { + title: t("jobs.fields.comment"), + dataIndex: "comment", + key: "comment", + ellipsis: true } + ]; - return ( - - {search.search && ( - <> - - {t("general.labels.searchresults", {search: search.search})} - - - - )} - - { - search.search = value; - history({search: queryString.stringify(search)}); - searchJobs(value); - }} - loading={loading || searchLoading} - enterButton - /> - - } - > -
- - ); + const handleTableChange = (pagination, filters, sorter) => { + search.page = pagination.current; + search.sortcolumn = sorter.column && sorter.column.key; + search.sortorder = sorter.order; + if (filters.status) { + search.statusFilters = JSON.stringify(_.flattenDeep(filters.status)); + } else { + delete search.statusFilters; + } + setFilter(filters); + history({ search: queryString.stringify(search) }); + }; + + useEffect(() => { + if (search.search && search.search.trim() !== "") { + searchJobs(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + async function searchJobs(value) { + try { + setSearchLoading(true); + const searchData = await axios.post("/search", { + search: value || search.search, + index: "jobs" + }); + setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source)); + } catch (error) { + console.log("Error while fetching search results", error); + } finally { + setSearchLoading(false); + } + } + + return ( + + {search.search && ( + <> + + {t("general.labels.searchresults", { search: search.search })} + + + + )} + + { + search.search = value; + history({ search: queryString.stringify(search) }); + searchJobs(value); + }} + loading={loading || searchLoading} + enterButton + /> + + } + > +
+ + ); } export default connect(mapStateToProps, mapDispatchToProps)(JobsList); diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index c4f07ec3e..50c656ad9 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -1,17 +1,17 @@ -import {BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, SyncOutlined,} from "@ant-design/icons"; -import {useQuery} from "@apollo/client"; -import {Button, Card, Grid, Input, Space, Table, Tooltip} from "antd"; +import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, SyncOutlined } from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {QUERY_ALL_ACTIVE_JOBS} from "../../graphql/jobs.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {onlyUnique} from "../../utils/arrayHelper"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { onlyUnique } from "../../utils/arrayHelper"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {alphaSort, statusSort} from "../../utils/sorters"; +import { alphaSort, statusSort } from "../../utils/sorters"; import useLocalStorage from "../../utils/useLocalStorage"; import AlertComponent from "../alert/alert.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component"; @@ -21,438 +21,370 @@ import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-dis import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setJoyRideSteps: (steps) => dispatch(setJoyRideSteps(steps)), - + setJoyRideSteps: (steps) => dispatch(setJoyRideSteps(steps)) }); -export function JobsList({bodyshop,setJoyRideSteps}) { - const searchParams = queryString.parse(useLocation().search); - const {selected} = searchParams; - const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) - .filter((screen) => !!screen[1]) - .slice(-1)[0]; - const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS, { - variables: { - statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], - }, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); +export function JobsList({ bodyshop, setJoyRideSteps }) { + const searchParams = queryString.parse(useLocation().search); + const { selected } = searchParams; + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) + .filter((screen) => !!screen[1]) + .slice(-1)[0]; + const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { + variables: { + statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"] + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const [state, setState] = useState({sortedInfo: {}}); - const [filter, setFilter] = useLocalStorage("filter_jobs_list", null); + const [state, setState] = useState({ sortedInfo: {} }); + const [filter, setFilter] = useLocalStorage("filter_jobs_list", null); - const {t} = useTranslation(); - const history = useNavigate(); - const [searchText, setSearchText] = useState(""); + const { t } = useTranslation(); + const history = useNavigate(); + const [searchText, setSearchText] = useState(""); - if (error) return ; + if (error) return ; - const jobs = data - ? searchText === "" - ? data.jobs - : data.jobs.filter( - (j) => - (j.ro_number || "") - .toString() - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_co_nm || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.comments || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_fn || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_ln || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.plate_no || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_model_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.est_ct_fn || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.est_ct_ln || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_make_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) - ) - : []; + const jobs = data + ? searchText === "" + ? data.jobs + : data.jobs.filter( + (j) => + (j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.comments || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.plate_no || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.est_ct_fn || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.est_ct_ln || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase()) + ) + : []; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, sortedInfo: sorter}); - setFilter(filters); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, sortedInfo: sorter }); + setFilter(filters); + }; - const handleOnRowClick = (record) => { - if (record) { - if (record.id) { - history({ - search: queryString.stringify({ - ...searchParams, - selected: record.id, - }), - }); - } - } - }; + const handleOnRowClick = (record) => { + if (record) { + if (record.id) { + history({ + search: queryString.stringify({ + ...searchParams, + selected: record.id + }) + }); + } + } + }; - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => - parseInt((a.ro_number || "0").replace(/\D/g, "")) - - parseInt((b.ro_number || "0").replace(/\D/g, "")), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => + parseInt((a.ro_number || "0").replace(/\D/g, "")) - parseInt((b.ro_number || "0").replace(/\D/g, "")), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - render: (text, record) => ( - e.stopPropagation()} - > - - {record.ro_number || t("general.labels.na")} - {record.production_vars && record.production_vars.alert ? ( - - ) : null} - {record.suspended && ( - - )} - {record.iouparent && ( - - - - )} - - - ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - ellipsis: true, + render: (text, record) => ( + e.stopPropagation()}> + + {record.ro_number || t("general.labels.na")} + {record.production_vars && record.production_vars.alert ? ( + + ) : null} + {record.suspended && } + {record.iouparent && ( + + + + )} + + + ) + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, - responsive: ["md"], - sorter: (a, b) => - alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, - render: (text, record) => { - return record.ownerid ? ( - e.stopPropagation()} - > - - - ) : ( - - + responsive: ["md"], + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + render: (text, record) => { + return record.ownerid ? ( + e.stopPropagation()}> + + + ) : ( + + - ); - }, - }, - { - title: t("jobs.fields.ownr_ph1"), - dataIndex: "ownr_ph1", - key: "ownr_ph1", - ellipsis: true, - responsive: ["md"], - render: (text, record) => ( - - ), - }, - { - title: t("jobs.fields.ownr_ph2"), - dataIndex: "ownr_ph2", - key: "ownr_ph2", - ellipsis: true, - responsive: ["md"], - render: (text, record) => ( - - ), - }, + ); + } + }, + { + title: t("jobs.fields.ownr_ph1"), + dataIndex: "ownr_ph1", + key: "ownr_ph1", + ellipsis: true, + responsive: ["md"], + render: (text, record) => + }, + { + title: t("jobs.fields.ownr_ph2"), + dataIndex: "ownr_ph2", + key: "ownr_ph2", + ellipsis: true, + responsive: ["md"], + render: (text, record) => + }, - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", - ellipsis: true, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + ellipsis: true, - sorter: (a, b) => - statusSort(a.status, b.status, bodyshop.md_ro_statuses.active_statuses), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - filteredValue: filter?.status || null, - filters: - (jobs && - jobs - .map((j) => j.status) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Status*", - value: [s], - }; - }) - .sort((a, b) => - statusSort( - a.text, - b.text, - bodyshop.md_ro_statuses.active_statuses - ) - )) || - [], - onFilter: (value, record) => value.includes(record.status), - }, + sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.active_statuses), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + filteredValue: filter?.status || null, + filters: + (jobs && + jobs + .map((j) => j.status) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Status*", + value: [s] + }; + }) + .sort((a, b) => statusSort(a.text, b.text, bodyshop.md_ro_statuses.active_statuses))) || + [], + onFilter: (value, record) => value.includes(record.status) + }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - ellipsis: true, - sorter: (a, b) => + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + sorter: (a, b) => alphaSort( - `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${ - a.v_model_desc || "" - }`, + `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`, `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` ), - sortOrder: - state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,render: (text, record) => { - return record.vehicleid ? ( - e.stopPropagation()} - > - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - - ) : ( - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - ); - }, - }, - { - title: t("vehicles.fields.plate_no"), - dataIndex: "plate_no", - key: "plate_no", - ellipsis: true, + sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, + render: (text, record) => { + return record.vehicleid ? ( + e.stopPropagation()}> + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + + ) : ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + ); + } + }, + { + title: t("vehicles.fields.plate_no"), + dataIndex: "plate_no", + key: "plate_no", + ellipsis: true, - responsive: ["md"], - sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), - sortOrder: - state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - ellipsis: true, - responsive: ["md"], - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - render: (text, record) => - `${record.clm_no || ""}${ - record.po_number ? ` (PO: ${record.po_number})` : "" - }`, - }, - { - title: t("jobs.fields.ins_co_nm"), - dataIndex: "ins_co_nm", - key: "ins_co_nm", - ellipsis: true, - sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), - sortOrder: - state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,filteredValue: filter?.ins_co_nm || null, - filters: - (jobs && - jobs - .map((j) => j.ins_co_nm) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Ins. Co.*", - value: [s], - }; - }) - .sort((a, b) => alphaSort(a.text, b.text))) || - [], - onFilter: (value, record) => value.includes(record.ins_co_nm), - responsive: ["md"], - }, - { - title: t("jobs.fields.clm_total"), - dataIndex: "clm_total", - key: "clm_total", - responsive: ["md"], - ellipsis: true, + responsive: ["md"], + sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), + sortOrder: state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + ellipsis: true, + responsive: ["md"], + sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), + sortOrder: state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, + render: (text, record) => `${record.clm_no || ""}${record.po_number ? ` (PO: ${record.po_number})` : ""}` + }, + { + title: t("jobs.fields.ins_co_nm"), + dataIndex: "ins_co_nm", + key: "ins_co_nm", + ellipsis: true, + sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), + sortOrder: state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order, + filteredValue: filter?.ins_co_nm || null, + filters: + (jobs && + jobs + .map((j) => j.ins_co_nm) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Ins. Co.*", + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.ins_co_nm), + responsive: ["md"] + }, + { + title: t("jobs.fields.clm_total"), + dataIndex: "clm_total", + key: "clm_total", + responsive: ["md"], + ellipsis: true, - sorter: (a, b) => a.clm_total - b.clm_total, - sortOrder: - state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, - render: (text, record) => ( - {record.clm_total} - ), - }, - { - title: t("jobs.labels.estimator"), - dataIndex: "jobs.labels.estimator", - key: "jobs.labels.estimator", - ellipsis: true, - responsive: ["xl"],sorter: (a, b) => + sorter: (a, b) => a.clm_total - b.clm_total, + sortOrder: state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, + render: (text, record) => {record.clm_total} + }, + { + title: t("jobs.labels.estimator"), + dataIndex: "jobs.labels.estimator", + key: "jobs.labels.estimator", + ellipsis: true, + responsive: ["xl"], + sorter: (a, b) => alphaSort( `${a.est_ct_fn || ""} ${a.est_ct_ln || ""}`.trim(), `${b.est_ct_fn || ""} ${b.est_ct_ln || ""}`.trim() ), - sortOrder: - state.sortedInfo.columnKey === "estimator" && state.sortedInfo.order, - filterSearch: true, - filteredValue: filter?.estimator || null, - filters: - (jobs && - jobs - .map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim()) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Estimator*", - value: [s], - }; - }) - .sort((a, b) => alphaSort(a.text, b.text))) || - [], - onFilter: (value, record) => - value.includes( - `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim() - ), - render: (text, record) => - `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(), - }, - { - title: t("jobs.fields.comment"), - dataIndex: "comment", - key: "comment", - ellipsis: true, - responsive: ["md"], - }, - // { - // title: t("jobs.fields.owner_owing"), - // dataIndex: "owner_owing", - // key: "owner_owing", - // responsive: ["md"], - // render: (text, record) => ( - // {record.owner_owing} - // ), - // }, - ]; + sortOrder: state.sortedInfo.columnKey === "estimator" && state.sortedInfo.order, + filterSearch: true, + filteredValue: filter?.estimator || null, + filters: + (jobs && + jobs + .map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim()) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Estimator*", + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()), + render: (text, record) => `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim() + }, + { + title: t("jobs.fields.comment"), + dataIndex: "comment", + key: "comment", + ellipsis: true, + responsive: ["md"] + } + // { + // title: t("jobs.fields.owner_owing"), + // dataIndex: "owner_owing", + // key: "owner_owing", + // responsive: ["md"], + // render: (text, record) => ( + // {record.owner_owing} + // ), + // }, + ]; - const scrollMapper = { - xs: true, - sm: true, - md: true, - lg: "100%", - xl: "100%", - xxl: "100%", - }; + const scrollMapper = { + xs: true, + sm: true, + md: true, + lg: "100%", + xl: "100%", + xxl: "100%" + }; - return ( - - {InstanceRenderManager({ - promanager: ( - - ), - })} + return ( + + {InstanceRenderManager({ + promanager: ( + + ) + })} - - { - setSearchText(e.target.value); - }} - value={searchText} - enterButton - /> - - } - > -
{ + + { + setSearchText(e.target.value); + }} + value={searchText} + enterButton + /> + + } + > +
{ + handleOnRowClick(record); + }, + selectedRowKeys: [selected], + type: "radio" + }} + onChange={handleTableChange} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { handleOnRowClick(record); - }, - selectedRowKeys: [selected], - type: 'radio', - }} - onChange={handleTableChange} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - handleOnRowClick(record); - }, - }; - }} - /> - - ); + } + }; + }} + /> + + ); } export default connect(mapStateToProps, mapDispatchToProps)(JobsList); diff --git a/client/src/components/jobs-mark-pst-exempt/jobs-mark-pst-exempt.component.jsx b/client/src/components/jobs-mark-pst-exempt/jobs-mark-pst-exempt.component.jsx index 65f23d32a..360d7c592 100644 --- a/client/src/components/jobs-mark-pst-exempt/jobs-mark-pst-exempt.component.jsx +++ b/client/src/components/jobs-mark-pst-exempt/jobs-mark-pst-exempt.component.jsx @@ -1,57 +1,57 @@ -import {Button, Popconfirm} from "antd"; +import { Button, Popconfirm } from "antd"; import React from "react"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import _ from "lodash"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - jobRO: selectJobReadOnly, + bodyshop: selectBodyshop, + jobRO: selectJobReadOnly }); -export function JobsMarkPstExempt({jobRO, form}) { - const {t} = useTranslation(); +export function JobsMarkPstExempt({ jobRO, form }) { + const { t } = useTranslation(); - const handleConfirm = () => { - const newPartRates = _.cloneDeep(form.getFieldValue("parts_tax_rates")); + const handleConfirm = () => { + const newPartRates = _.cloneDeep(form.getFieldValue("parts_tax_rates")); - Object.keys(newPartRates).forEach((key) => { - newPartRates[key] = { - ...newPartRates[key], - prt_tax_in: false, - prt_tax_rt: 0, - }; - }); + Object.keys(newPartRates).forEach((key) => { + newPartRates[key] = { + ...newPartRates[key], + prt_tax_in: false, + prt_tax_rt: 0 + }; + }); - form.setFieldsValue({ - state_tax_rate: 0, - tax_lbr_rt: 0, - tax_levies_rt: 0, - tax_sub_rt: 0, - tax_shop_mat_rt: 0, - tax_paint_mat_rt: 0, - tax_str_rt: 0, - tax_tow_rt: 0, - parts_tax_rates: newPartRates, - }); - }; + form.setFieldsValue({ + state_tax_rate: 0, + tax_lbr_rt: 0, + tax_levies_rt: 0, + tax_sub_rt: 0, + tax_shop_mat_rt: 0, + tax_paint_mat_rt: 0, + tax_str_rt: 0, + tax_tow_rt: 0, + parts_tax_rates: newPartRates + }); + }; - return ( - - - - ); + return ( + + + + ); } export default connect(mapStateToProps, null)(JobsMarkPstExempt); diff --git a/client/src/components/jobs-notes/jobs-notes.container.jsx b/client/src/components/jobs-notes/jobs-notes.container.jsx index e45385854..96252f891 100644 --- a/client/src/components/jobs-notes/jobs-notes.container.jsx +++ b/client/src/components/jobs-notes/jobs-notes.container.jsx @@ -1,70 +1,68 @@ -import {useMutation, useQuery} from "@apollo/client"; -import {notification} from "antd"; -import React, {useState} from "react"; +import { useMutation, useQuery } from "@apollo/client"; +import { notification } from "antd"; +import React, { useState } from "react"; //import SpinComponent from "../../components/loading-spinner/loading-spinner.component"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; import AlertComponent from "../../components/alert/alert.component"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {DELETE_NOTE, QUERY_NOTES_BY_JOB_PK,} from "../../graphql/notes.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { DELETE_NOTE, QUERY_NOTES_BY_JOB_PK } from "../../graphql/notes.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import JobNotesComponent from "./jobs.notes.component"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser + //currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); export default connect(mapStateToProps, mapDispatchToProps)(JobNotesContainer); -export function JobNotesContainer({jobId, insertAuditTrail}) { - const {loading, error, data, refetch} = useQuery(QUERY_NOTES_BY_JOB_PK, { - variables: {id: jobId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", +export function JobNotesContainer({ jobId, insertAuditTrail }) { + const { loading, error, data, refetch } = useQuery(QUERY_NOTES_BY_JOB_PK, { + variables: { id: jobId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + + const [deleteNote] = useMutation(DELETE_NOTE); + const { t } = useTranslation(); + const [deleteLoading, setDeleteLoading] = useState(false); + const handleNoteDelete = (id) => { + logImEXEvent("job_note_delete"); + setDeleteLoading(true); + deleteNote({ + variables: { + noteId: id + } + }).then((r) => { + refetch(); + notification["success"]({ + message: t("notes.successes.deleted") + }); + insertAuditTrail({ + jobid: jobId, + operation: AuditTrailMapping.jobnotedeleted(), + type: "jobnotedeleted" + }); }); + setDeleteLoading(false); + }; - const [deleteNote] = useMutation(DELETE_NOTE); - const {t} = useTranslation(); - const [deleteLoading, setDeleteLoading] = useState(false); - const handleNoteDelete = (id) => { - logImEXEvent("job_note_delete"); - setDeleteLoading(true); - deleteNote({ - variables: { - noteId: id, - }, - }).then((r) => { - refetch(); - notification["success"]({ - message: t("notes.successes.deleted"), - }); - insertAuditTrail({ - jobid: jobId, - operation: AuditTrailMapping.jobnotedeleted(), - type: "jobnotedeleted",}); - }); - setDeleteLoading(false); - }; - - //if (loading) return ; - if (error) return ; - return ( - - ); + //if (loading) return ; + if (error) return ; + return ( + + ); } diff --git a/client/src/components/jobs-notes/jobs.notes.component.jsx b/client/src/components/jobs-notes/jobs.notes.component.jsx index 51196f4f2..b1458f372 100644 --- a/client/src/components/jobs-notes/jobs.notes.component.jsx +++ b/client/src/components/jobs-notes/jobs.notes.component.jsx @@ -1,210 +1,190 @@ -import {AuditOutlined, DeleteFilled, EditFilled, EyeInvisibleFilled, WarningFilled,} from "@ant-design/icons"; -import {Button, Card, Form, Input, Space, Table} from "antd"; +import { AuditOutlined, DeleteFilled, EditFilled, EyeInvisibleFilled, WarningFilled } from "@ant-design/icons"; +import { Button, Card, Form, Input, Space, Table } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; +import { TemplateList } from "../../utils/TemplateConstants"; import useLocalStorage from "../../utils/useLocalStorage"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import NoteUpsertModal from "../note-upsert-modal/note-upsert-modal.container"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; const mapStateToProps = createStructuredSelector({ - jobRO: selectJobReadOnly, + jobRO: selectJobReadOnly }); const mapDispatchToProps = (dispatch) => ({ - setNoteUpsertContext: (context) => - dispatch(setModalContext({context: context, modal: "noteUpsert"})), + setNoteUpsertContext: (context) => dispatch(setModalContext({ context: context, modal: "noteUpsert" })) }); export function JobNotesComponent({ - jobRO, - loading, - data, - refetch, - handleNoteDelete, - jobId, - setNoteUpsertContext, - deleteLoading, - ro_number, - relatedRos, - }) { - const {t} = useTranslation(); - const [filter, setFilter] = useLocalStorage("filter_job_notes_icons", null); + jobRO, + loading, + data, + refetch, + handleNoteDelete, + jobId, + setNoteUpsertContext, + deleteLoading, + ro_number, + relatedRos +}) { + const { t } = useTranslation(); + const [filter, setFilter] = useLocalStorage("filter_job_notes_icons", null); - const Templates = TemplateList("job_special", { - ro_number, - }); + const Templates = TemplateList("job_special", { + ro_number + }); - const columns = [ + const columns = [ + { + title: "", + dataIndex: "icons", + key: "icons", + width: 80, + filteredValue: filter?.icons || null, + filters: [ { - title: "", - dataIndex: "icons", - key: "icons", - width: 80, - filteredValue: filter?.icons || null, - filters: [ - { - text: t("notes.labels.usernotes"), - value: false, - }, - { - text: t("notes.labels.systemnotes"), - value: true, - }, - ], - onFilter: (value, record) => record.audit === value, - render: (text, record) => ( - - {record.critical ? ( - - ) : null} - {record.private ? : null} - {record.audit ? : null} + text: t("notes.labels.usernotes"), + value: false + }, + { + text: t("notes.labels.systemnotes"), + value: true + } + ], + onFilter: (value, record) => record.audit === value, + render: (text, record) => ( + + {record.critical ? : null} + {record.private ? : null} + {record.audit ? : null} - ), - }, + ) + }, + { + title: t("notes.fields.type"), + dataIndex: "type", + key: "type", + width: 120, + filteredValue: filter?.type || null, + filters: [ + { value: "general", text: t("notes.fields.types.general") }, + { value: "customer", text: t("notes.fields.types.customer") }, + { value: "shop", text: t("notes.fields.types.shop") }, + { value: "office", text: t("notes.fields.types.office") }, + { value: "parts", text: t("notes.fields.types.parts") }, + { value: "paint", text: t("notes.fields.types.paint") }, { - title: t("notes.fields.type"), - dataIndex: "type", - key: "type", - width: 120, - filteredValue: filter?.type || null, - filters: [ - {value: "general", text: t("notes.fields.types.general")}, - {value: "customer", text: t("notes.fields.types.customer")}, - {value: "shop", text: t("notes.fields.types.shop")}, - {value: "office", text: t("notes.fields.types.office")}, - {value: "parts", text: t("notes.fields.types.parts")}, - {value: "paint", text: t("notes.fields.types.paint")}, - { - value: "supplement", - text: t("notes.fields.types.supplement"), - }, - ], - onFilter: (value, record) => value.includes(record.type), - render: (text, record) => t(`notes.fields.types.${record.type}`), - }, - { - title: t("notes.fields.text"), - dataIndex: "text", - key: "text", - ellipsis: true, - render: (text, record) => ( - {text} - ), - }, + value: "supplement", + text: t("notes.fields.types.supplement") + } + ], + onFilter: (value, record) => value.includes(record.type), + render: (text, record) => t(`notes.fields.types.${record.type}`) + }, + { + title: t("notes.fields.text"), + dataIndex: "text", + key: "text", + ellipsis: true, + render: (text, record) => {text} + }, - { - title: t("notes.fields.updatedat"), - dataIndex: "updated_at", - key: "updated_at", - defaultSortOrder: "descend", - width: 200, - sorter: (a, b) => new Date(a.updated_at) - new Date(b.updated_at), - render: (text, record) => ( - {record.updated_at} - ), - }, - { - title: t("notes.fields.createdby"), - dataIndex: "created_by", - key: "created_by", - width: 200, - }, - { - title: t("notes.actions.actions"), - dataIndex: "actions", - key: "actions", - width: 200, - render: (text, record) => ( - - - - - - ), - }, - ]; - - const handleTableChange = (pagination, filters, sorter) => { - setFilter(filters); - }; - - return ( -
- - - - - - { - setNoteUpsertContext({ - actions: {refetch: refetch}, - context: { - jobId: jobId, - relatedRos: relatedRos, - }, - }); - }} - > - {t("notes.actions.new")} - + { + title: t("notes.fields.updatedat"), + dataIndex: "updated_at", + key: "updated_at", + defaultSortOrder: "descend", + width: 200, + sorter: (a, b) => new Date(a.updated_at) - new Date(b.updated_at), + render: (text, record) => {record.updated_at} + }, + { + title: t("notes.fields.createdby"), + dataIndex: "created_by", + key: "created_by", + width: 200 + }, + { + title: t("notes.actions.actions"), + dataIndex: "actions", + key: "actions", + width: 200, + render: (text, record) => ( + + + + - -
- ); + variables: { id: record.id } + }} + messageObject={{ + subject: Templates.individual_job_note.subject + }} + id={jobId} + /> + + ) + } + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setFilter(filters); + }; + + return ( +
+ + + + + + { + setNoteUpsertContext({ + actions: { refetch: refetch }, + context: { + jobId: jobId, + relatedRos: relatedRos + } + }); + }} + > + {t("notes.actions.new")} + + } + > + + +
+ + + ); } export default connect(mapStateToProps, mapDispatchToProps)(JobNotesComponent); diff --git a/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx b/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx index 1862cf300..6396dfaca 100644 --- a/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx +++ b/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx @@ -1,409 +1,345 @@ -import {BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, SyncOutlined,} from "@ant-design/icons"; -import {useQuery} from "@apollo/client"; -import {Button, Card, Grid, Input, Space, Table, Tooltip} from "antd"; +import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, SyncOutlined } from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd"; import queryString from "query-string"; -import React, {useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {QUERY_ALL_ACTIVE_JOBS} from "../../graphql/jobs.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {onlyUnique} from "../../utils/arrayHelper"; -import {pageLimit} from "../../utils/config"; -import {alphaSort, statusSort} from "../../utils/sorters"; +import { onlyUnique } from "../../utils/arrayHelper"; +import { pageLimit } from "../../utils/config"; +import { alphaSort, statusSort } from "../../utils/sorters"; import useLocalStorage from "../../utils/useLocalStorage"; import AlertComponent from "../alert/alert.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -export function JobsReadyList({bodyshop}) { - const searchParams = queryString.parse(useLocation().search); - const {selected} = searchParams; - const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) - .filter((screen) => !!screen[1]) - .slice(-1)[0]; +export function JobsReadyList({ bodyshop }) { + const searchParams = queryString.parse(useLocation().search); + const { selected } = searchParams; + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) + .filter((screen) => !!screen[1]) + .slice(-1)[0]; - const readyStatuses = useMemo(() => { - if (bodyshop.md_ro_statuses.ready_statuses) - return bodyshop.md_ro_statuses.ready_statuses; + const readyStatuses = useMemo(() => { + if (bodyshop.md_ro_statuses.ready_statuses) return bodyshop.md_ro_statuses.ready_statuses; - return bodyshop.md_ro_statuses.post_production_statuses.filter( - (s) => - s !== bodyshop.md_ro_statuses.default_invoiced && - s !== bodyshop.md_ro_statuses.default_exported - ); - }, [bodyshop.md_ro_statuses]); - - const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS, { - variables: { - statuses: readyStatuses, - isConverted: true, - }, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); - - const [state, setState] = useState({sortedInfo: {}}); - const [filter, setFilter] = useLocalStorage("filter_jobs_ready", null); - - const {t} = useTranslation(); - const history = useNavigate(); - const [searchText, setSearchText] = useState(""); - - if (error) return ; - - const jobs = data - ? searchText === "" - ? data.jobs - : data.jobs.filter( - (j) => - (j.ro_number || "") - .toString() - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_co_nm || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.comments || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_fn || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_ln || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.plate_no || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_model_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.est_ct_fn || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.est_ct_ln || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_make_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) - ) - : []; - - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, sortedInfo: sorter}); - setFilter(filters); - }; - - const handleOnRowClick = (record) => { - if (record) { - if (record.id) { - history({ - search: queryString.stringify({ - ...searchParams, - selected: record.id, - }), - }); - } - } - }; - - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - render: (text, record) => ( - e.stopPropagation()} - > - - {record.ro_number || t("general.labels.na")} - {record.production_vars && record.production_vars.alert ? ( - - ) : null} - {record.suspended && ( - - )} - {record.iouparent && ( - - - - )} - - - ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - ellipsis: true, - responsive: ["md"], - sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, - render: (text, record) => { - return record.owner ? ( - e.stopPropagation()} - > - - - ) : ( - - - - ); - }, - }, - { - title: t("jobs.fields.ownr_ph1"), - dataIndex: "ownr_ph1", - key: "ownr_ph1", - ellipsis: true, - responsive: ["md"], - render: (text, record) => ( - - ), - }, - { - title: t("jobs.fields.ownr_ph2"), - dataIndex: "ownr_ph2", - key: "ownr_ph2", - ellipsis: true, - responsive: ["md"], - render: (text, record) => ( - - ), - }, - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", - ellipsis: true, - sorter: (a, b) => alphaSort(a.status, b.status), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - filteredValue: filter?.status || null, - filters: - (jobs && - jobs - .map((j) => j.status) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Status*", - value: [s], - }; - }) - .sort((a, b) => - statusSort( - a.text, - b.text, - bodyshop.md_ro_statuses.active_statuses - ) - )) || - [], - onFilter: (value, record) => value.includes(record.status), - }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - ellipsis: true, - render: (text, record) => { - return record.vehicleid ? ( - e.stopPropagation()} - > - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - - ) : ( - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - ); - }, - }, - { - title: t("vehicles.fields.plate_no"), - dataIndex: "plate_no", - key: "plate_no", - ellipsis: true, - - responsive: ["md"], - sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), - sortOrder: - state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - ellipsis: true, - responsive: ["md"], - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - render: (text, record) => - `${record.clm_no || ""}${ - record.po_number ? ` (PO: ${record.po_number})` : "" - }`, - }, - { - title: t("jobs.fields.ins_co_nm"), - dataIndex: "ins_co_nm", - key: "ins_co_nm", - ellipsis: true, - filteredValue: filter?.ins_co_nm || null, - filters: - (jobs && - jobs - .map((j) => j.ins_co_nm) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Ins Co.*", - value: [s], - }; - }) - .sort((a, b) => alphaSort(a.text, b.text))) || - [], - onFilter: (value, record) => value.includes(record.ins_co_nm), - responsive: ["md"], - }, - { - title: t("jobs.fields.clm_total"), - dataIndex: "clm_total", - key: "clm_total", - responsive: ["md"], - ellipsis: true, - sorter: (a, b) => a.clm_total - b.clm_total, - sortOrder: - state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, - render: (text, record) => ( - {record.clm_total} - ), - }, - { - title: t("jobs.labels.estimator"), - dataIndex: "jobs.labels.estimator", - key: "estimator", - ellipsis: true, - responsive: ["xl"], - filteredValue: filter?.estimator || null, - filterSearch: true, - filters: - (jobs && - jobs - .map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim()) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Estimator*", - value: [s], - }; - }) - .sort((a, b) => alphaSort(a.text, b.text))) || - [], - onFilter: (value, record) => - value.includes( - `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim() - ), - render: (text, record) => - `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(), - }, - { - title: t("jobs.fields.comment"), - dataIndex: "comment", - key: "comment", - ellipsis: true, - responsive: ["md"], - }, - // { - // title: t("jobs.fields.owner_owing"), - // dataIndex: "owner_owing", - // key: "owner_owing", - // responsive: ["md"], - // render: (text, record) => ( - // {record.owner_owing} - // ), - // }, - ]; - - const scrollMapper = { - xs: true, - sm: true, - md: true, - lg: "100%", - xl: "100%", - xxl: "100%", - }; - - return ( - - ({readyStatuses && readyStatuses.join(", ")}) - - { - setSearchText(e.target.value); - }} - value={searchText} - enterButton - /> - - } - > -
{ - handleOnRowClick(record); - }, - selectedRowKeys: [selected], - type: "radio", - }} - onChange={handleTableChange} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - handleOnRowClick(record); - }, - }; - }} - /> - + return bodyshop.md_ro_statuses.post_production_statuses.filter( + (s) => s !== bodyshop.md_ro_statuses.default_invoiced && s !== bodyshop.md_ro_statuses.default_exported ); + }, [bodyshop.md_ro_statuses]); + + const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { + variables: { + statuses: readyStatuses, + isConverted: true + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + + const [state, setState] = useState({ sortedInfo: {} }); + const [filter, setFilter] = useLocalStorage("filter_jobs_ready", null); + + const { t } = useTranslation(); + const history = useNavigate(); + const [searchText, setSearchText] = useState(""); + + if (error) return ; + + const jobs = data + ? searchText === "" + ? data.jobs + : data.jobs.filter( + (j) => + (j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.comments || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.plate_no || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.est_ct_fn || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.est_ct_ln || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase()) + ) + : []; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, sortedInfo: sorter }); + setFilter(filters); + }; + + const handleOnRowClick = (record) => { + if (record) { + if (record.id) { + history({ + search: queryString.stringify({ + ...searchParams, + selected: record.id + }) + }); + } + } + }; + + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => ( + e.stopPropagation()}> + + {record.ro_number || t("general.labels.na")} + {record.production_vars && record.production_vars.alert ? ( + + ) : null} + {record.suspended && } + {record.iouparent && ( + + + + )} + + + ) + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + responsive: ["md"], + sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + render: (text, record) => { + return record.owner ? ( + e.stopPropagation()}> + + + ) : ( + + + + ); + } + }, + { + title: t("jobs.fields.ownr_ph1"), + dataIndex: "ownr_ph1", + key: "ownr_ph1", + ellipsis: true, + responsive: ["md"], + render: (text, record) => + }, + { + title: t("jobs.fields.ownr_ph2"), + dataIndex: "ownr_ph2", + key: "ownr_ph2", + ellipsis: true, + responsive: ["md"], + render: (text, record) => + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + ellipsis: true, + sorter: (a, b) => alphaSort(a.status, b.status), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + filteredValue: filter?.status || null, + filters: + (jobs && + jobs + .map((j) => j.status) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Status*", + value: [s] + }; + }) + .sort((a, b) => statusSort(a.text, b.text, bodyshop.md_ro_statuses.active_statuses))) || + [], + onFilter: (value, record) => value.includes(record.status) + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + render: (text, record) => { + return record.vehicleid ? ( + e.stopPropagation()}> + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + + ) : ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + ); + } + }, + { + title: t("vehicles.fields.plate_no"), + dataIndex: "plate_no", + key: "plate_no", + ellipsis: true, + + responsive: ["md"], + sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), + sortOrder: state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + ellipsis: true, + responsive: ["md"], + sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), + sortOrder: state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, + render: (text, record) => `${record.clm_no || ""}${record.po_number ? ` (PO: ${record.po_number})` : ""}` + }, + { + title: t("jobs.fields.ins_co_nm"), + dataIndex: "ins_co_nm", + key: "ins_co_nm", + ellipsis: true, + filteredValue: filter?.ins_co_nm || null, + filters: + (jobs && + jobs + .map((j) => j.ins_co_nm) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Ins Co.*", + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.ins_co_nm), + responsive: ["md"] + }, + { + title: t("jobs.fields.clm_total"), + dataIndex: "clm_total", + key: "clm_total", + responsive: ["md"], + ellipsis: true, + sorter: (a, b) => a.clm_total - b.clm_total, + sortOrder: state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, + render: (text, record) => {record.clm_total} + }, + { + title: t("jobs.labels.estimator"), + dataIndex: "jobs.labels.estimator", + key: "estimator", + ellipsis: true, + responsive: ["xl"], + filteredValue: filter?.estimator || null, + filterSearch: true, + filters: + (jobs && + jobs + .map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim()) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Estimator*", + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()), + render: (text, record) => `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim() + }, + { + title: t("jobs.fields.comment"), + dataIndex: "comment", + key: "comment", + ellipsis: true, + responsive: ["md"] + } + // { + // title: t("jobs.fields.owner_owing"), + // dataIndex: "owner_owing", + // key: "owner_owing", + // responsive: ["md"], + // render: (text, record) => ( + // {record.owner_owing} + // ), + // }, + ]; + + const scrollMapper = { + xs: true, + sm: true, + md: true, + lg: "100%", + xl: "100%", + xxl: "100%" + }; + + return ( + + ({readyStatuses && readyStatuses.join(", ")}) + + { + setSearchText(e.target.value); + }} + value={searchText} + enterButton + /> + + } + > +
{ + handleOnRowClick(record); + }, + selectedRowKeys: [selected], + type: "radio" + }} + onChange={handleTableChange} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + handleOnRowClick(record); + } + }; + }} + /> + + ); } export default connect(mapStateToProps, null)(JobsReadyList); diff --git a/client/src/components/jobs-related-ros/jobs-related-ros.component.jsx b/client/src/components/jobs-related-ros/jobs-related-ros.component.jsx index 1d77bebc8..dc523dcb6 100644 --- a/client/src/components/jobs-related-ros/jobs-related-ros.component.jsx +++ b/client/src/components/jobs-related-ros/jobs-related-ros.component.jsx @@ -1,26 +1,24 @@ -import {Space, Tag} from "antd"; +import { Space, Tag } from "antd"; import React from "react"; -import {Link} from "react-router-dom"; +import { Link } from "react-router-dom"; -export default function JobsRelatedRos({jobid, job, disabled}) { - if (!(job && job.vehicle && job.vehicle.jobs)) return null; - return ( - - {job.vehicle.jobs - .filter((j) => j.id !== job.id) - .map((j) => ( - - {disabled ? ( - <>{`${j.ro_number || "N/A"}${j.clm_no ? ` | ${j.clm_no}` : ""}${ - j.status ? ` | ${j.status}` : "" - }`} - ) : ( - {`${j.ro_number || "N/A"}${ - j.clm_no ? ` | ${j.clm_no}` : "" - }${j.status ? ` | ${j.status}` : ""}`} - )} - - ))} - - ); +export default function JobsRelatedRos({ jobid, job, disabled }) { + if (!(job && job.vehicle && job.vehicle.jobs)) return null; + return ( + + {job.vehicle.jobs + .filter((j) => j.id !== job.id) + .map((j) => ( + + {disabled ? ( + <>{`${j.ro_number || "N/A"}${j.clm_no ? ` | ${j.clm_no}` : ""}${j.status ? ` | ${j.status}` : ""}`} + ) : ( + {`${j.ro_number || "N/A"}${ + j.clm_no ? ` | ${j.clm_no}` : "" + }${j.status ? ` | ${j.status}` : ""}`} + )} + + ))} + + ); } diff --git a/client/src/components/labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component.jsx b/client/src/components/labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component.jsx index c91c2cfdf..88e5cd654 100644 --- a/client/src/components/labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component.jsx +++ b/client/src/components/labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component.jsx @@ -1,175 +1,137 @@ -import {useMutation} from "@apollo/client"; -import {Button, Card, Form, InputNumber, notification, Popover, Select,} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; +import { useMutation } from "@apollo/client"; +import { Button, Card, Form, InputNumber, notification, Popover, Select } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {insertAuditTrail} from "../../redux/application/application.actions"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser + //currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(LaborAllocationsAdjustmentEdit); +export default connect(mapStateToProps, mapDispatchToProps)(LaborAllocationsAdjustmentEdit); export function LaborAllocationsAdjustmentEdit({ - insertAuditTrail, - jobId, - mod_lbr_ty, - adjustments, - children, - refetchQueryNames, - }) { - const [loading, setLoading] = useState(false); - const [open, setOpen] = useState(false); - const [updateAdjustments] = useMutation(UPDATE_JOB); - const [form] = Form.useForm(); + insertAuditTrail, + jobId, + mod_lbr_ty, + adjustments, + children, + refetchQueryNames +}) { + const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [updateAdjustments] = useMutation(UPDATE_JOB); + const [form] = Form.useForm(); - const {t} = useTranslation(); + const { t } = useTranslation(); - const handleFinish = async (values) => { - setLoading(true); - const result = await updateAdjustments({ - variables: { - jobId: jobId, - job: { - lbr_adjustments: { - ...adjustments, - [values.mod_lbr_ty]: values.hours, - }, - }, - }, - ...(refetchQueryNames ? {refetchQueries: refetchQueryNames} : {}), - }); - - if (!!result.errors) { - notification["error"]({ - message: t("jobs.errors.saving", { - message: JSON.stringify(result.errors), - }), - }); - } else { - notification["success"]({ - message: t("jobs.successes.save"), - }); - insertAuditTrail({ - jobid: jobId, - operation: AuditTrailMapping.jobmodifylbradj({ - mod_lbr_ty: values.mod_lbr_ty, - hours: - values.hours - - ((adjustments && adjustments[mod_lbr_ty]) || 0).toFixed(1), - }), - type: "jobmodifylbradj",}); + const handleFinish = async (values) => { + setLoading(true); + const result = await updateAdjustments({ + variables: { + jobId: jobId, + job: { + lbr_adjustments: { + ...adjustments, + [values.mod_lbr_ty]: values.hours + } } - setLoading(false); - setOpen(false); - }; + }, + ...(refetchQueryNames ? { refetchQueries: refetchQueryNames } : {}) + }); - const overlay = ( - -
-
- - - - - - - - -
-
- ); + if (!!result.errors) { + notification["error"]({ + message: t("jobs.errors.saving", { + message: JSON.stringify(result.errors) + }) + }); + } else { + notification["success"]({ + message: t("jobs.successes.save") + }); + insertAuditTrail({ + jobid: jobId, + operation: AuditTrailMapping.jobmodifylbradj({ + mod_lbr_ty: values.mod_lbr_ty, + hours: values.hours - ((adjustments && adjustments[mod_lbr_ty]) || 0).toFixed(1) + }), + type: "jobmodifylbradj" + }); + } + setLoading(false); + setOpen(false); + }; - return ( - setOpen(vis)} - content={overlay} - trigger="click" + const overlay = ( + +
+
- {children} - - ); + + + + + + + + +
+
+ ); + + return ( + setOpen(vis)} content={overlay} trigger="click"> + {children} + + ); } diff --git a/client/src/components/labor-allocations-table/labor-allocations-table.component.jsx b/client/src/components/labor-allocations-table/labor-allocations-table.component.jsx index 4a4828d23..649f49584 100644 --- a/client/src/components/labor-allocations-table/labor-allocations-table.component.jsx +++ b/client/src/components/labor-allocations-table/labor-allocations-table.component.jsx @@ -1,268 +1,234 @@ -import {EditFilled} from "@ant-design/icons"; -import {Card, Col, Row, Space, Table, Typography} from "antd"; +import { EditFilled } from "@ant-design/icons"; +import { Card, Col, Row, Space, Table, Typography } from "antd"; import _ from "lodash"; -import React, {useEffect, useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {alphaSort} from "../../utils/sorters"; -import LaborAllocationsAdjustmentEdit - from "../labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component"; +import { alphaSort } from "../../utils/sorters"; +import LaborAllocationsAdjustmentEdit from "../labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component"; import "./labor-allocations-table.styles.scss"; -import {CalculateAllocationsTotals} from "./labor-allocations-table.utility"; +import { CalculateAllocationsTotals } from "./labor-allocations-table.utility"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - technician: selectTechnician, + bodyshop: selectBodyshop, + technician: selectTechnician }); -export function LaborAllocationsTable({ - jobId, - joblines, - timetickets, - bodyshop, - adjustments, - technician, - }) { - const {t} = useTranslation(); - const [totals, setTotals] = useState([]); - const [state, setState] = useState({ - sortedInfo: { - columnKey: "cost_center", - field: "cost_center", - order: "ascend", - }, - filteredInfo: {}, - }); +export function LaborAllocationsTable({ jobId, joblines, timetickets, bodyshop, adjustments, technician }) { + const { t } = useTranslation(); + const [totals, setTotals] = useState([]); + const [state, setState] = useState({ + sortedInfo: { + columnKey: "cost_center", + field: "cost_center", + order: "ascend" + }, + filteredInfo: {} + }); - useEffect(() => { - if (!!joblines && !!timetickets && !!bodyshop) ; - setTotals( - CalculateAllocationsTotals(bodyshop, joblines, timetickets, adjustments) - ); - if (!jobId) setTotals([]); - }, [joblines, timetickets, bodyshop, adjustments, jobId]); + useEffect(() => { + if (!!joblines && !!timetickets && !!bodyshop); + setTotals(CalculateAllocationsTotals(bodyshop, joblines, timetickets, adjustments)); + if (!jobId) setTotals([]); + }, [joblines, timetickets, bodyshop, adjustments, jobId]); - const convertedLines = useMemo( - () => joblines && joblines.filter((j) => j.convertedtolbr), - [joblines] + const convertedLines = useMemo(() => joblines && joblines.filter((j) => j.convertedtolbr), [joblines]); + + const columns = [ + { + title: t("timetickets.fields.cost_center"), + dataIndex: "cost_center", + key: "cost_center", + defaultSortOrder: "cost_center", + sorter: (a, b) => alphaSort(a.cost_center, b.cost_center), + sortOrder: state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order, + render: (text, record) => `${record.cost_center} (${record.mod_lbr_ty})` + }, + { + title: t("jobs.labels.hrs_total"), + dataIndex: "total", + key: "total", + sorter: (a, b) => a.total - b.total, + sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order, + render: (text, record) => record.total.toFixed(1) + }, + { + title: t("jobs.labels.hrs_claimed"), + dataIndex: "hrs_claimed", + key: "hrs_claimed", + sorter: (a, b) => a.claimed - b.claimed, + sortOrder: state.sortedInfo.columnKey === "claimed" && state.sortedInfo.order, + render: (text, record) => record.claimed && record.claimed.toFixed(1) + }, + { + title: t("jobs.labels.adjustments"), + dataIndex: "adjustments", + key: "adjustments", + sorter: (a, b) => a.adjustments - b.adjustments, + sortOrder: state.sortedInfo.columnKey === "adjustments" && state.sortedInfo.order, + render: (text, record) => ( + + {record.adjustments.toFixed(1)} + {!technician && ( + + + + )} + + ) + }, + { + title: t("jobs.labels.difference"), + dataIndex: "difference", + + key: "difference", + sorter: (a, b) => a.difference - b.difference, + sortOrder: state.sortedInfo.columnKey === "difference" && state.sortedInfo.order, + render: (text, record) => ( + = 0 ? "green" : "red" + }} + > + {_.round(record.difference, 1)} + + ) + } + ]; + const convertedTableCols = [ + { + title: t("joblines.fields.line_desc"), + dataIndex: "line_desc", + key: "line_desc", + ellipsis: true + }, + { + title: t("joblines.fields.op_code_desc"), + dataIndex: "op_code_desc", + key: "op_code_desc", + ellipsis: true, + render: (text, record) => `${record.op_code_desc || ""}${record.alt_partm ? ` ${record.alt_partm}` : ""}` + }, + + { + title: t("joblines.fields.act_price"), + dataIndex: "act_price", + key: "act_price", + ellipsis: true, + render: (text, record) => ( + <> + + {record.db_ref === "900510" || record.db_ref === "900511" ? record.prt_dsmk_m : record.act_price} + + {record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? ( + {`(${record.prt_dsmk_p}%)`} + ) : ( + <> + )} + + ) + }, + { + title: t("joblines.fields.part_qty"), + dataIndex: "part_qty", + key: "part_qty" + }, + { + title: t("joblines.fields.mod_lbr_ty"), + dataIndex: "conv_mod_lbr_ty", + key: "conv_mod_lbr_ty", + render: (text, record) => record.convertedtolbr_data && record.convertedtolbr_data.mod_lbr_ty + }, + { + title: t("joblines.fields.mod_lb_hrs"), + dataIndex: "conv_mod_lb_hrs", + key: "conv_mod_lb_hrs", + render: (text, record) => + record.convertedtolbr_data && + record.convertedtolbr_data.mod_lb_hrs && + record.convertedtolbr_data.mod_lb_hrs.toFixed(1) + } + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + const summary = + totals && + totals.reduce( + (acc, val) => { + acc.hrs_total += val.total; + acc.hrs_claimed += val.claimed; + acc.adjustments += val.adjustments; + acc.difference += val.difference; + return acc; + }, + { hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0 } ); - const columns = [ - { - title: t("timetickets.fields.cost_center"), - dataIndex: "cost_center", - key: "cost_center", - defaultSortOrder: "cost_center", - sorter: (a, b) => alphaSort(a.cost_center, b.cost_center), - sortOrder: - state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order, - render: (text, record) => `${record.cost_center} (${record.mod_lbr_ty})`, - }, - { - title: t("jobs.labels.hrs_total"), - dataIndex: "total", - key: "total", - sorter: (a, b) => a.total - b.total, - sortOrder: - state.sortedInfo.columnKey === "total" && state.sortedInfo.order, - render: (text, record) => record.total.toFixed(1), - }, - { - title: t("jobs.labels.hrs_claimed"), - dataIndex: "hrs_claimed", - key: "hrs_claimed", - sorter: (a, b) => a.claimed - b.claimed, - sortOrder: - state.sortedInfo.columnKey === "claimed" && state.sortedInfo.order, - render: (text, record) => record.claimed && record.claimed.toFixed(1), - }, - { - title: t("jobs.labels.adjustments"), - dataIndex: "adjustments", - key: "adjustments", - sorter: (a, b) => a.adjustments - b.adjustments, - sortOrder: - state.sortedInfo.columnKey === "adjustments" && state.sortedInfo.order, - render: (text, record) => ( - - {record.adjustments.toFixed(1)} - {!technician && ( - - - - )} - - ), - }, - { - title: t("jobs.labels.difference"), - dataIndex: "difference", - - key: "difference", - sorter: (a, b) => a.difference - b.difference, - sortOrder: - state.sortedInfo.columnKey === "difference" && state.sortedInfo.order, - render: (text, record) => ( - +
+ +
`${record.cost_center} ${record.mod_lbr_ty}`} + pagination={false} + onChange={handleTableChange} + dataSource={totals} + scroll={{ + x: true + }} + summary={() => ( + + + {t("general.labels.totals")} + + {summary.hrs_total.toFixed(1)} + {summary.hrs_claimed.toFixed(1)} + {summary.adjustments.toFixed(1)} + + = 0 ? "green" : "red", + fontWeight: "bold", + color: summary.difference >= 0 ? "green" : "red" }} - > - {_.round(record.difference, 1)} - - ), - }, - ]; - const convertedTableCols = [ - { - title: t("joblines.fields.line_desc"), - dataIndex: "line_desc", - key: "line_desc", - ellipsis: true, - }, - { - title: t("joblines.fields.op_code_desc"), - dataIndex: "op_code_desc", - key: "op_code_desc", - ellipsis: true, - render: (text, record) => - `${record.op_code_desc || ""}${ - record.alt_partm ? ` ${record.alt_partm}` : "" - }`, - }, - - { - title: t("joblines.fields.act_price"), - dataIndex: "act_price", - key: "act_price", - ellipsis: true, - render: (text, record) => ( - <> - - {record.db_ref === "900510" || record.db_ref === "900511" - ? record.prt_dsmk_m - : record.act_price} - - {record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? ( - {`(${record.prt_dsmk_p}%)`} - ) : ( - <> - )} - - ), - }, - { - title: t("joblines.fields.part_qty"), - dataIndex: "part_qty", - key: "part_qty", - }, - { - title: t("joblines.fields.mod_lbr_ty"), - dataIndex: "conv_mod_lbr_ty", - key: "conv_mod_lbr_ty", - render: (text, record) => - record.convertedtolbr_data && record.convertedtolbr_data.mod_lbr_ty, - }, - { - title: t("joblines.fields.mod_lb_hrs"), - dataIndex: "conv_mod_lb_hrs", - key: "conv_mod_lb_hrs", - render: (text, record) => - record.convertedtolbr_data && - record.convertedtolbr_data.mod_lb_hrs && - record.convertedtolbr_data.mod_lb_hrs.toFixed(1), - }, - ]; - - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - - const summary = - totals && - totals.reduce( - (acc, val) => { - acc.hrs_total += val.total; - acc.hrs_claimed += val.claimed; - acc.adjustments += val.adjustments; - acc.difference += val.difference; - return acc; - }, - {hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0} - ); - - return ( - - - -
`${record.cost_center} ${record.mod_lbr_ty}`} - pagination={false} - onChange={handleTableChange} - dataSource={totals} - scroll={{ - x: true, - }} - summary={() => ( - - - - {t("general.labels.totals")} - - - - {summary.hrs_total.toFixed(1)} - - - {summary.hrs_claimed.toFixed(1)} - - - {summary.adjustments.toFixed(1)} - - - = 0 ? "green" : "red", - }} - > - {summary.difference.toFixed(1)} - - - - )} - /> - - - {convertedLines && convertedLines.length > 0 && ( - - -
- - + > + {summary.difference.toFixed(1)} + + + )} - - ); + /> + + + {convertedLines && convertedLines.length > 0 && ( + + +
+ + + )} + + ); } export default connect(mapStateToProps, null)(LaborAllocationsTable); diff --git a/client/src/components/labor-allocations-table/labor-allocations-table.payroll.component.jsx b/client/src/components/labor-allocations-table/labor-allocations-table.payroll.component.jsx index 66bc490c3..164c75fb2 100644 --- a/client/src/components/labor-allocations-table/labor-allocations-table.payroll.component.jsx +++ b/client/src/components/labor-allocations-table/labor-allocations-table.payroll.component.jsx @@ -1,325 +1,292 @@ -import {Button, Card, Col, notification, Row, Space, Table, Typography,} from "antd"; -import {SyncOutlined} from "@ant-design/icons"; +import { Button, Card, Col, notification, Row, Space, Table, Typography } from "antd"; +import { SyncOutlined } from "@ant-design/icons"; import axios from "axios"; import _ from "lodash"; -import React, {useEffect, useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; import "./labor-allocations-table.styles.scss"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - technician: selectTechnician, + bodyshop: selectBodyshop, + technician: selectTechnician }); export function PayrollLaborAllocationsTable({ - jobId, - joblines, - timetickets, - bodyshop, - adjustments, - technician, - refetch, - }) { - const {t} = useTranslation(); - const [totals, setTotals] = useState([]); - const [state, setState] = useState({ - sortedInfo: { - columnKey: "cost_center", - field: "cost_center", - order: "ascend", - }, - filteredInfo: {}, - }); + jobId, + joblines, + timetickets, + bodyshop, + adjustments, + technician, + refetch +}) { + const { t } = useTranslation(); + const [totals, setTotals] = useState([]); + const [state, setState] = useState({ + sortedInfo: { + columnKey: "cost_center", + field: "cost_center", + order: "ascend" + }, + filteredInfo: {} + }); - useEffect(() => { - async function CalculateTotals() { - const {data} = await axios.post("/payroll/calculatelabor", { - jobid: jobId, - }); - setTotals(data); + useEffect(() => { + async function CalculateTotals() { + const { data } = await axios.post("/payroll/calculatelabor", { + jobid: jobId + }); + setTotals(data); + } + + if (!!joblines && !!timetickets && !!bodyshop) { + CalculateTotals(); + } + if (!jobId) setTotals([]); + }, [joblines, timetickets, bodyshop, adjustments, jobId]); + + const convertedLines = useMemo(() => joblines && joblines.filter((j) => j.convertedtolbr), [joblines]); + + const columns = [ + { + title: t("timetickets.fields.employee"), + dataIndex: "employeeid", + key: "employeeid", + render: (text, record) => { + if (record.employeeid === undefined) { + return {t("timetickets.labels.unassigned")}; } + const emp = bodyshop.employees.find((e) => e.id === record.employeeid); + return `${emp?.first_name} ${emp?.last_name}`; + } + }, + { + title: t("joblines.fields.mod_lbr_ty"), + dataIndex: "mod_lbr_ty", + key: "mod_lbr_ty", + render: (text, record) => + record.employeeid === undefined ? ( + {t("timetickets.labels.unassigned")} + ) : ( + t(`joblines.fields.lbr_types.${record.mod_lbr_ty?.toUpperCase()}`) + ) + }, + // { + // title: t("timetickets.fields.rate"), + // dataIndex: "rate", + // key: "rate", + // }, + { + title: t("jobs.labels.hrs_total"), + dataIndex: "expectedHours", + key: "expectedHours", + sorter: (a, b) => a.expectedHours - b.expectedHours, + sortOrder: state.sortedInfo.columnKey === "expectedHours" && state.sortedInfo.order, + render: (text, record) => record.expectedHours.toFixed(5) + }, + { + title: t("jobs.labels.hrs_claimed"), + dataIndex: "claimedHours", + key: "claimedHours", + sorter: (a, b) => a.claimedHours - b.claimedHours, + sortOrder: state.sortedInfo.columnKey === "claimedHours" && state.sortedInfo.order, + render: (text, record) => record.claimedHours && record.claimedHours.toFixed(5) + }, + { + title: t("jobs.labels.difference"), + dataIndex: "difference", - if (!!joblines && !!timetickets && !!bodyshop) { - CalculateTotals(); - } - if (!jobId) setTotals([]); - }, [joblines, timetickets, bodyshop, adjustments, jobId]); + key: "difference", + sorter: (a, b) => a.difference - b.difference, + sortOrder: state.sortedInfo.columnKey === "difference" && state.sortedInfo.order, + render: (text, record) => { + const difference = _.round(record.expectedHours - record.claimedHours, 5); - const convertedLines = useMemo( - () => joblines && joblines.filter((j) => j.convertedtolbr), - [joblines] - ); - - const columns = [ - { - title: t("timetickets.fields.employee"), - dataIndex: "employeeid", - key: "employeeid", - render: (text, record) => { - if (record.employeeid === undefined) { - return ( - - {t("timetickets.labels.unassigned")} - - ); - } - const emp = bodyshop.employees.find((e) => e.id === record.employeeid); - return `${emp?.first_name} ${emp?.last_name}`; - }, - }, - { - title: t("joblines.fields.mod_lbr_ty"), - dataIndex: "mod_lbr_ty", - key: "mod_lbr_ty", - render: (text, record) => - record.employeeid === undefined ? ( - - {t("timetickets.labels.unassigned")} - - ) : ( - t(`joblines.fields.lbr_types.${record.mod_lbr_ty?.toUpperCase()}`) - ), - }, - // { - // title: t("timetickets.fields.rate"), - // dataIndex: "rate", - // key: "rate", - // }, - { - title: t("jobs.labels.hrs_total"), - dataIndex: "expectedHours", - key: "expectedHours", - sorter: (a, b) => a.expectedHours - b.expectedHours, - sortOrder: - state.sortedInfo.columnKey === "expectedHours" && - state.sortedInfo.order, - render: (text, record) => record.expectedHours.toFixed(5), - }, - { - title: t("jobs.labels.hrs_claimed"), - dataIndex: "claimedHours", - key: "claimedHours", - sorter: (a, b) => a.claimedHours - b.claimedHours, - sortOrder: - state.sortedInfo.columnKey === "claimedHours" && state.sortedInfo.order, - render: (text, record) => - record.claimedHours && record.claimedHours.toFixed(5), - }, - { - title: t("jobs.labels.difference"), - dataIndex: "difference", - - key: "difference", - sorter: (a, b) => a.difference - b.difference, - sortOrder: - state.sortedInfo.columnKey === "difference" && state.sortedInfo.order, - render: (text, record) => { - const difference = _.round( - record.expectedHours - record.claimedHours, - 5 - ); - - return ( - = 0 ? "green" : "red", - }} - > - {difference} - - ); - }, - }, - ]; - const convertedTableCols = [ - { - title: t("joblines.fields.line_desc"), - dataIndex: "line_desc", - key: "line_desc", - ellipsis: true, - }, - { - title: t("joblines.fields.op_code_desc"), - dataIndex: "op_code_desc", - key: "op_code_desc", - ellipsis: true, - render: (text, record) => - `${record.op_code_desc || ""}${ - record.alt_partm ? ` ${record.alt_partm}` : "" - }`, - }, - - { - title: t("joblines.fields.act_price"), - dataIndex: "act_price", - key: "act_price", - ellipsis: true, - render: (text, record) => ( - <> - - {record.db_ref === "900510" || record.db_ref === "900511" - ? record.prt_dsmk_m - : record.act_price} - - {record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? ( - {`(${record.prt_dsmk_p}%)`} - ) : ( - <> - )} - - ), - }, - { - title: t("joblines.fields.part_qty"), - dataIndex: "part_qty", - key: "part_qty", - }, - { - title: t("joblines.fields.mod_lbr_ty"), - dataIndex: "conv_mod_lbr_ty", - key: "conv_mod_lbr_ty", - render: (text, record) => - record.convertedtolbr_data && record.convertedtolbr_data.mod_lbr_ty, - }, - { - title: t("joblines.fields.mod_lb_hrs"), - dataIndex: "conv_mod_lb_hrs", - key: "conv_mod_lb_hrs", - render: (text, record) => - record.convertedtolbr_data && - record.convertedtolbr_data.mod_lb_hrs && - record.convertedtolbr_data.mod_lb_hrs.toFixed(5), - }, - ]; - - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - - const summary = - totals && - totals.reduce( - (acc, val) => { - acc.hrs_total += val.expectedHours; - acc.hrs_claimed += val.claimedHours; - // acc.adjustments += val.adjustments; - acc.difference += val.expectedHours - val.claimedHours; - return acc; - }, - {hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0} + return ( + = 0 ? "green" : "red" + }} + > + {difference} + ); + } + } + ]; + const convertedTableCols = [ + { + title: t("joblines.fields.line_desc"), + dataIndex: "line_desc", + key: "line_desc", + ellipsis: true + }, + { + title: t("joblines.fields.op_code_desc"), + dataIndex: "op_code_desc", + key: "op_code_desc", + ellipsis: true, + render: (text, record) => `${record.op_code_desc || ""}${record.alt_partm ? ` ${record.alt_partm}` : ""}` + }, - return ( - - - - - - - } - > -
`${record.employeeid} ${record.mod_lbr_ty}`} - pagination={false} - onChange={handleTableChange} - dataSource={totals} - scroll={{ - x: true, - }} - summary={() => ( - - - - {t("general.labels.totals")} - - - - - {summary.hrs_total.toFixed(5)} - - - {summary.hrs_claimed.toFixed(5)} - - - - {summary.difference.toFixed(5)} - - - )} - /> - - - {convertedLines && convertedLines.length > 0 && ( - - -
- - - )} - + const summary = + totals && + totals.reduce( + (acc, val) => { + acc.hrs_total += val.expectedHours; + acc.hrs_claimed += val.claimedHours; + // acc.adjustments += val.adjustments; + acc.difference += val.expectedHours - val.claimedHours; + return acc; + }, + { hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0 } ); + + return ( + + + + + + + } + > +
`${record.employeeid} ${record.mod_lbr_ty}`} + pagination={false} + onChange={handleTableChange} + dataSource={totals} + scroll={{ + x: true + }} + summary={() => ( + + + {t("general.labels.totals")} + + + {summary.hrs_total.toFixed(5)} + {summary.hrs_claimed.toFixed(5)} + + {summary.difference.toFixed(5)} + + )} + /> + + + {convertedLines && convertedLines.length > 0 && ( + + +
+ + + )} + + ); } export default connect(mapStateToProps, null)(PayrollLaborAllocationsTable); diff --git a/client/src/components/labor-allocations-table/labor-allocations-table.utility.js b/client/src/components/labor-allocations-table/labor-allocations-table.utility.js index f045f89bb..8491ee063 100644 --- a/client/src/components/labor-allocations-table/labor-allocations-table.utility.js +++ b/client/src/components/labor-allocations-table/labor-allocations-table.utility.js @@ -1,44 +1,37 @@ import i18next from "i18next"; -export const CalculateAllocationsTotals = ( - bodyshop, - joblines, - timetickets, - adjustments = [] -) => { - const responsibilitycenters = bodyshop.md_responsibility_centers; - const jobCodes = joblines.map((item) => item.mod_lbr_ty); - //.filter((value, index, self) => self.indexOf(value) === index && !!value); - const ticketCodes = timetickets.map((item) => item.ciecacode); - //.filter((value, index, self) => self.indexOf(value) === index && !!value); - const adjustmentCodes = Object.keys(adjustments); - const allCodes = [...jobCodes, ...ticketCodes, ...adjustmentCodes].filter( - (value, index, self) => self.indexOf(value) === index && !!value - ); +export const CalculateAllocationsTotals = (bodyshop, joblines, timetickets, adjustments = []) => { + const responsibilitycenters = bodyshop.md_responsibility_centers; + const jobCodes = joblines.map((item) => item.mod_lbr_ty); + //.filter((value, index, self) => self.indexOf(value) === index && !!value); + const ticketCodes = timetickets.map((item) => item.ciecacode); + //.filter((value, index, self) => self.indexOf(value) === index && !!value); + const adjustmentCodes = Object.keys(adjustments); + const allCodes = [...jobCodes, ...ticketCodes, ...adjustmentCodes].filter( + (value, index, self) => self.indexOf(value) === index && !!value + ); - const r = allCodes.reduce((acc, value) => { - const r = { - opcode: value, - cost_center: - bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber - ? i18next.t( - `joblines.fields.lbr_types.${value && value.toUpperCase()}` - ) - : responsibilitycenters.defaults.costs[value], - mod_lbr_ty: value, - total: joblines.reduce((acc2, val2) => { - return val2.mod_lbr_ty === value ? acc2 + val2.mod_lb_hrs : acc2; - }, 0), - adjustments: adjustments[value] || 0, - claimed: timetickets.reduce((acc3, val3) => { - return val3.ciecacode === value ? acc3 + val3.productivehrs : acc3; - }, 0), - }; + const r = allCodes.reduce((acc, value) => { + const r = { + opcode: value, + cost_center: + bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber + ? i18next.t(`joblines.fields.lbr_types.${value && value.toUpperCase()}`) + : responsibilitycenters.defaults.costs[value], + mod_lbr_ty: value, + total: joblines.reduce((acc2, val2) => { + return val2.mod_lbr_ty === value ? acc2 + val2.mod_lb_hrs : acc2; + }, 0), + adjustments: adjustments[value] || 0, + claimed: timetickets.reduce((acc3, val3) => { + return val3.ciecacode === value ? acc3 + val3.productivehrs : acc3; + }, 0) + }; - r.difference = r.total + r.adjustments - r.claimed; - acc.push(r); - return acc; - }, []); + r.difference = r.total + r.adjustments - r.claimed; + acc.push(r); + return acc; + }, []); - return r; + return r; }; diff --git a/client/src/components/layout-form-row/layout-form-row.component.jsx b/client/src/components/layout-form-row/layout-form-row.component.jsx index 63baf3713..ae93a0fa5 100644 --- a/client/src/components/layout-form-row/layout-form-row.component.jsx +++ b/client/src/components/layout-form-row/layout-form-row.component.jsx @@ -1,78 +1,60 @@ -import {Col, Divider, Row} from "antd"; +import { Col, Divider, Row } from "antd"; import React from "react"; import "./layout-form-row.styles.scss"; -export default function LayoutFormRow({ - header, - children, - grow = false, - noDivider = false, - ...restProps - }) { - const DividerHeader = () => - !noDivider && ( - - {header} - - ); - - if (!!!children.length) { - //We have only one element. It's going to get the whole thing. - return ( -
- - {children} -
- ); - } - const rowGutter = {gutter: [16, 16]}; - - const colSpan = (spanOverride) => { - if (spanOverride) return {span: spanOverride}; - return { - xs: { - span: !grow ? 24 : Math.max(12, 24 / children.length), - }, - sm: { - span: !grow ? 12 : Math.max(12, 24 / children.length), - }, - md: { - span: !grow ? 8 : Math.max(8, 24 / children.length), - }, - lg: { - span: !grow ? 6 : Math.max(6, 24 / children.length), - }, - xl: { - span: !grow ? 4 : Math.max(4, 24 / children.length), - }, - }; - }; - //{header ? {header} : null} - return ( -
- - - {children.map( - (c, idx) => - c && ( -
- {c} - - ) - )} - - +export default function LayoutFormRow({ header, children, grow = false, noDivider = false, ...restProps }) { + const DividerHeader = () => + !noDivider && ( + + {header} + ); + + if (!!!children.length) { + //We have only one element. It's going to get the whole thing. + return ( +
+ + {children} +
+ ); + } + const rowGutter = { gutter: [16, 16] }; + + const colSpan = (spanOverride) => { + if (spanOverride) return { span: spanOverride }; + return { + xs: { + span: !grow ? 24 : Math.max(12, 24 / children.length) + }, + sm: { + span: !grow ? 12 : Math.max(12, 24 / children.length) + }, + md: { + span: !grow ? 8 : Math.max(8, 24 / children.length) + }, + lg: { + span: !grow ? 6 : Math.max(6, 24 / children.length) + }, + xl: { + span: !grow ? 4 : Math.max(4, 24 / children.length) + } + }; + }; + //{header ? {header} : null} + return ( +
+ + + {children.map( + (c, idx) => + c && ( +
+ {c} + + ) + )} + + + ); } diff --git a/client/src/components/loading-skeleton/loading-skeleton.component.jsx b/client/src/components/loading-skeleton/loading-skeleton.component.jsx index 38613ac65..6d9af741a 100644 --- a/client/src/components/loading-skeleton/loading-skeleton.component.jsx +++ b/client/src/components/loading-skeleton/loading-skeleton.component.jsx @@ -1,12 +1,12 @@ import React from "react"; import "./loading-skeleton.styles.scss"; -import {Skeleton} from "antd"; +import { Skeleton } from "antd"; export default function LoadingSkeleton(props) { - return ( - - {props.children} - - ); + return ( + + {props.children} + + ); } diff --git a/client/src/components/loading-spinner/loading-spinner.component.jsx b/client/src/components/loading-spinner/loading-spinner.component.jsx index b02819ac2..1bbf80dfb 100644 --- a/client/src/components/loading-spinner/loading-spinner.component.jsx +++ b/client/src/components/loading-spinner/loading-spinner.component.jsx @@ -1,24 +1,24 @@ import React from "react"; -import {Spin} from "antd"; +import { Spin } from "antd"; import "./loading-spinner.styles.scss"; -export default function LoadingSpinner({loading = true, message, ...props}) { - return ( -
- - {props.children} - -
- ); +export default function LoadingSpinner({ loading = true, message, ...props }) { + return ( +
+ + {props.children} + +
+ ); } diff --git a/client/src/components/manage-sign-in-button/manage-sign-in-button.component.jsx b/client/src/components/manage-sign-in-button/manage-sign-in-button.component.jsx index 6a8086322..067450179 100644 --- a/client/src/components/manage-sign-in-button/manage-sign-in-button.component.jsx +++ b/client/src/components/manage-sign-in-button/manage-sign-in-button.component.jsx @@ -1,26 +1,26 @@ -import {BuildFilled, LoginOutlined} from "@ant-design/icons"; +import { BuildFilled, LoginOutlined } from "@ant-design/icons"; import React from "react"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {selectCurrentUser} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { selectCurrentUser } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, + currentUser: selectCurrentUser }); -export function ManageSignInButton({currentUser}) { - return currentUser.authorized ? ( - - - Manage - - ) : ( - - - Sign In - - ); +export function ManageSignInButton({ currentUser }) { + return currentUser.authorized ? ( + + + Manage + + ) : ( + + + Sign In + + ); } export default connect(mapStateToProps, null)(ManageSignInButton); diff --git a/client/src/components/no-shop/no-shop.component.jsx b/client/src/components/no-shop/no-shop.component.jsx index 5c1dd2068..1847999e9 100644 --- a/client/src/components/no-shop/no-shop.component.jsx +++ b/client/src/components/no-shop/no-shop.component.jsx @@ -1,12 +1,12 @@ import React from "react"; -import {Result} from "antd"; -import {useTranslation} from "react-i18next"; +import { Result } from "antd"; +import { useTranslation } from "react-i18next"; export default function NoShop() { - const {t} = useTranslation(); - return ( -
- -
- ); + const { t } = useTranslation(); + return ( +
+ +
+ ); } diff --git a/client/src/components/not-found/not-found.component.jsx b/client/src/components/not-found/not-found.component.jsx index 493131154..20adf832f 100644 --- a/client/src/components/not-found/not-found.component.jsx +++ b/client/src/components/not-found/not-found.component.jsx @@ -1,16 +1,12 @@ import React from "react"; -import {Result} from "antd"; -import {useTranslation} from "react-i18next"; +import { Result } from "antd"; +import { useTranslation } from "react-i18next"; export default function NotFound() { - const {t} = useTranslation(); - return ( -
- -
- ); + const { t } = useTranslation(); + return ( +
+ +
+ ); } diff --git a/client/src/components/note-upsert-modal/note-upsert-modal.component.jsx b/client/src/components/note-upsert-modal/note-upsert-modal.component.jsx index 0faba49c3..6b4e4562c 100644 --- a/client/src/components/note-upsert-modal/note-upsert-modal.component.jsx +++ b/client/src/components/note-upsert-modal/note-upsert-modal.component.jsx @@ -1,114 +1,88 @@ -import {Checkbox, Col, Form, Input, Row, Select, Space, Switch, Tag,} from "antd"; +import { Checkbox, Col, Form, Input, Row, Select, Space, Switch, Tag } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectNoteUpsert} from "../../redux/modals/modals.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectNoteUpsert } from "../../redux/modals/modals.selectors"; import NotesPresetButton from "../notes-preset-button/notes-preset-button.component"; const mapStateToProps = createStructuredSelector({ - noteUpsertModal: selectNoteUpsert, + noteUpsertModal: selectNoteUpsert }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(NoteUpsertModalComponent); +export default connect(mapStateToProps, mapDispatchToProps)(NoteUpsertModalComponent); -export function NoteUpsertModalComponent({form, noteUpsertModal}) { - const {t} = useTranslation(); - const {jobId, existingNote, relatedRos} = noteUpsertModal.context; +export function NoteUpsertModalComponent({ form, noteUpsertModal }) { + const { t } = useTranslation(); + const { jobId, existingNote, relatedRos } = noteUpsertModal.context; - const filteredRelatedRos = relatedRos - ? relatedRos.filter((j) => j.id !== jobId) - : []; + const filteredRelatedRos = relatedRos ? relatedRos.filter((j) => j.id !== jobId) : []; - return ( - <> - -
- - - - - - - - - - - - + + + + + + + + + + + +
+
{!existingNote && t("notes.labels.addtorelatedro")}
+ {!existingNote && + filteredRelatedRos.map((j, idx) => ( + + + + + + {`${j.ro_number || "N/A"}${j.clm_no ? ` | ${j.clm_no}` : ""}${j.status ? ` | ${j.status}` : ""}`} + + + ))} +
+ + ); } diff --git a/client/src/components/note-upsert-modal/note-upsert-modal.container.jsx b/client/src/components/note-upsert-modal/note-upsert-modal.container.jsx index fbc5fa7db..f6b149fc4 100644 --- a/client/src/components/note-upsert-modal/note-upsert-modal.container.jsx +++ b/client/src/components/note-upsert-modal/note-upsert-modal.container.jsx @@ -1,145 +1,133 @@ -import {useMutation} from "@apollo/client"; -import {Form, Modal, notification} from "antd"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {INSERT_NEW_NOTE, UPDATE_NOTE} from "../../graphql/notes.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectNoteUpsert} from "../../redux/modals/modals.selectors"; -import {selectCurrentUser} from "../../redux/user/user.selectors"; +import { useMutation } from "@apollo/client"; +import { Form, Modal, notification } from "antd"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { INSERT_NEW_NOTE, UPDATE_NOTE } from "../../graphql/notes.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectNoteUpsert } from "../../redux/modals/modals.selectors"; +import { selectCurrentUser } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import NoteUpsertModalComponent from "./note-upsert-modal.component"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - noteUpsertModal: selectNoteUpsert, + currentUser: selectCurrentUser, + noteUpsertModal: selectNoteUpsert }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("noteUpsert")), - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + toggleModalVisible: () => dispatch(toggleModalVisible("noteUpsert")), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export function NoteUpsertModalContainer({ - currentUser, - noteUpsertModal, - toggleModalVisible, - insertAuditTrail, - }) { - const {t} = useTranslation(); - const [insertNote] = useMutation(INSERT_NEW_NOTE); - const [updateNote] = useMutation(UPDATE_NOTE); +export function NoteUpsertModalContainer({ currentUser, noteUpsertModal, toggleModalVisible, insertAuditTrail }) { + const { t } = useTranslation(); + const [insertNote] = useMutation(INSERT_NEW_NOTE); + const [updateNote] = useMutation(UPDATE_NOTE); - const {open, context, actions} = noteUpsertModal; - const {jobId, existingNote, text} = context; - const {refetch} = actions; + const { open, context, actions } = noteUpsertModal; + const { jobId, existingNote, text } = context; + const { refetch } = actions; - const [form] = Form.useForm(); + const [form] = Form.useForm(); - useEffect(() => { - //Required to prevent infinite looping. - if (existingNote && open) { - form.setFieldsValue(existingNote); - } else if (!existingNote && open) { - form.resetFields(); + useEffect(() => { + //Required to prevent infinite looping. + if (existingNote && open) { + form.setFieldsValue(existingNote); + } else if (!existingNote && open) { + form.resetFields(); - if (text) { - form.setFieldValue("text", text); - } + if (text) { + form.setFieldValue("text", text); + } + } + }, [existingNote, form, open, text]); + + const handleFinish = async (formValues) => { + const { relatedros, ...values } = formValues; + + if (existingNote) { + logImEXEvent("job_note_update"); + + updateNote({ + variables: { + noteId: existingNote.id, + note: values } - }, [existingNote, form, open, text]); + }).then((r) => { + notification["success"]({ + message: t("notes.successes.updated") + }); + insertAuditTrail({ + jobid: context.jobId, + operation: AuditTrailMapping.jobnoteupdated(), + type: "jobnoteupdated" + }); + }); + if (refetch) refetch(); + toggleModalVisible(); + } else { + logImEXEvent("job_note_insert"); + const AdditionalNoteInserts = relatedros ? Object.keys(relatedros).filter((key) => relatedros[key]) : []; - const handleFinish = async (formValues) => { - const {relatedros, ...values} = formValues; + await insertNote({ + variables: { + noteInput: [{ ...values, jobid: jobId, created_by: currentUser.email }] + }, + refetchQueries: ["QUERY_NOTES_BY_JOB_PK"] + }); - if (existingNote) { - logImEXEvent("job_note_update"); - - updateNote({ - variables: { - noteId: existingNote.id, - note: values, - }, - }).then((r) => { - notification["success"]({ - message: t("notes.successes.updated"), - }); - insertAuditTrail({ - jobid: context.jobId, - operation: AuditTrailMapping.jobnoteupdated(), - type: "jobnoteupdated",}); - }); - if (refetch) refetch(); - toggleModalVisible(); - } else { - logImEXEvent("job_note_insert"); - const AdditionalNoteInserts = relatedros - ? Object.keys(relatedros).filter((key) => relatedros[key]) - : []; - - await insertNote({ - variables: { - noteInput: [ - {...values, jobid: jobId, created_by: currentUser.email}, - ], - }, - refetchQueries: ["QUERY_NOTES_BY_JOB_PK"], - }); - - if (AdditionalNoteInserts.length > 0) { - //Insert the others. - AdditionalNoteInserts.forEach(async (newJobId) => { - await insertNote({ - variables: { - noteInput: [ - {...values, jobid: newJobId, created_by: currentUser.email}, - ], - }, - }); - insertAuditTrail({ - jobid: newJobId, - operation: AuditTrailMapping.jobnoteadded(), - type: "jobnoteadded",}); - }); + if (AdditionalNoteInserts.length > 0) { + //Insert the others. + AdditionalNoteInserts.forEach(async (newJobId) => { + await insertNote({ + variables: { + noteInput: [{ ...values, jobid: newJobId, created_by: currentUser.email }] } + }); + insertAuditTrail({ + jobid: newJobId, + operation: AuditTrailMapping.jobnoteadded(), + type: "jobnoteadded" + }); + }); + } - if (refetch) refetch(); - form.resetFields(); - toggleModalVisible(); - notification["success"]({ - message: t("notes.successes.create"), - }); - insertAuditTrail({ - jobid: context.jobId, - operation: AuditTrailMapping.jobnoteadded(), - type: "jobnoteadded",}); - } - }; + if (refetch) refetch(); + form.resetFields(); + toggleModalVisible(); + notification["success"]({ + message: t("notes.successes.create") + }); + insertAuditTrail({ + jobid: context.jobId, + operation: AuditTrailMapping.jobnoteadded(), + type: "jobnoteadded" + }); + } + }; - return ( - { - form.submit(); - }} - onCancel={() => { - toggleModalVisible(); - }} - destroyOnClose - > -
- - -
- ); + return ( + { + form.submit(); + }} + onCancel={() => { + toggleModalVisible(); + }} + destroyOnClose + > +
+ + +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(NoteUpsertModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(NoteUpsertModalContainer); diff --git a/client/src/components/notes-preset-button/notes-preset-button.component.jsx b/client/src/components/notes-preset-button/notes-preset-button.component.jsx index 15c57fffb..10827711f 100644 --- a/client/src/components/notes-preset-button/notes-preset-button.component.jsx +++ b/client/src/components/notes-preset-button/notes-preset-button.component.jsx @@ -1,52 +1,47 @@ -import {DownOutlined} from "@ant-design/icons"; -import {Dropdown} from "antd"; +import { DownOutlined } from "@ant-design/icons"; +import { Dropdown } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function NotesPresetButton({bodyshop, form}) { - const {t} = useTranslation(); +export function NotesPresetButton({ bodyshop, form }) { + const { t } = useTranslation(); - const handleSelect = (item) => { - form.setFieldsValue({text: item.text}); - }; + const handleSelect = (item) => { + form.setFieldsValue({ text: item.text }); + }; - - const menu = { - items: bodyshop.md_notes_presets.map((i, idx) => ({ - key: idx, - label: i.label, - style: {breakInside: "avoid"}, - onClick: () => handleSelect(i), - })), - style: { - columnCount: Math.floor(bodyshop.md_notes_presets.length / 10) + 1, - }, + const menu = { + items: bodyshop.md_notes_presets.map((i, idx) => ({ + key: idx, + label: i.label, + style: { breakInside: "avoid" }, + onClick: () => handleSelect(i) + })), + style: { + columnCount: Math.floor(bodyshop.md_notes_presets.length / 10) + 1 } + }; - return ( - - ); + return ( + + ); } export default connect(mapStateToProps, mapDispatchToProps)(NotesPresetButton); diff --git a/client/src/components/owner-detail-form/owner-detail-form.component.jsx b/client/src/components/owner-detail-form/owner-detail-form.component.jsx index 29838e6ec..419de25fa 100644 --- a/client/src/components/owner-detail-form/owner-detail-form.component.jsx +++ b/client/src/components/owner-detail-form/owner-detail-form.component.jsx @@ -1,107 +1,91 @@ -import {Form, Input, Switch} from "antd"; +import { Form, Input, Switch } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component"; -import FormItemPhone, {PhoneItemFormatterValidation,} from "../form-items-formatted/phone-form-item.component"; +import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; -export default function OwnerDetailFormComponent({form, loading}) { - const {t} = useTranslation(); - const {getFieldValue} = form; - return ( -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PhoneItemFormatterValidation(getFieldValue, "ownr_ph1"), - ]} - > - - - - PhoneItemFormatterValidation(getFieldValue, "ownr_ph2"), - ]} - > - - - - - - - - - - - - -
- ); +export default function OwnerDetailFormComponent({ form, loading }) { + const { t } = useTranslation(); + const { getFieldValue } = form; + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PhoneItemFormatterValidation(getFieldValue, "ownr_ph1")]} + > + + + PhoneItemFormatterValidation(getFieldValue, "ownr_ph2")]} + > + + + + + + + + + + + + +
+ ); } diff --git a/client/src/components/owner-detail-form/owner-detail-form.container.jsx b/client/src/components/owner-detail-form/owner-detail-form.container.jsx index d2a046f16..3958560d4 100644 --- a/client/src/components/owner-detail-form/owner-detail-form.container.jsx +++ b/client/src/components/owner-detail-form/owner-detail-form.container.jsx @@ -1,109 +1,94 @@ -import {Button, Form, notification, Popconfirm} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import { Button, Form, notification, Popconfirm } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; -import React, {useState} from "react"; -import {useNavigate} from "react-router-dom"; -import {useMutation} from "@apollo/client"; -import {useTranslation} from "react-i18next"; -import {DELETE_OWNER, UPDATE_OWNER} from "../../graphql/owners.queries"; +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useMutation } from "@apollo/client"; +import { useTranslation } from "react-i18next"; +import { DELETE_OWNER, UPDATE_OWNER } from "../../graphql/owners.queries"; import OwnerDetailFormComponent from "./owner-detail-form.component"; -function OwnerDetailFormContainer({owner, refetch}) { - const {t} = useTranslation(); - const [form] = Form.useForm(); - const history = useNavigate(); - const [loading, setLoading] = useState(false); - const [updateOwner] = useMutation(UPDATE_OWNER); - const [deleteOwner] = useMutation(DELETE_OWNER); +function OwnerDetailFormContainer({ owner, refetch }) { + const { t } = useTranslation(); + const [form] = Form.useForm(); + const history = useNavigate(); + const [loading, setLoading] = useState(false); + const [updateOwner] = useMutation(UPDATE_OWNER); + const [deleteOwner] = useMutation(DELETE_OWNER); - const handleDelete = async () => { - setLoading(true); - const result = await deleteOwner({ - variables: {id: owner.id}, - }); - console.log(result); - if (result.errors) { - notification["error"]({ - message: t("owners.errors.deleting", { - error: JSON.stringify(result.errors), - }), - }); - setLoading(false); - } else { - notification["success"]({ - message: t("owners.successes.delete"), - }); - setLoading(false); - history(`/manage/owners`); - } - }; + const handleDelete = async () => { + setLoading(true); + const result = await deleteOwner({ + variables: { id: owner.id } + }); + console.log(result); + if (result.errors) { + notification["error"]({ + message: t("owners.errors.deleting", { + error: JSON.stringify(result.errors) + }) + }); + setLoading(false); + } else { + notification["success"]({ + message: t("owners.successes.delete") + }); + setLoading(false); + history(`/manage/owners`); + } + }; - const handleFinish = async (values) => { - setLoading(true); - const result = await updateOwner({ - variables: {ownerId: owner.id, owner: values}, - }); + const handleFinish = async (values) => { + setLoading(true); + const result = await updateOwner({ + variables: { ownerId: owner.id, owner: values } + }); - if (!!result.errors) { - notification["error"]({ - message: t("owners.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - setLoading(false); - return; - } + if (!!result.errors) { + notification["error"]({ + message: t("owners.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + setLoading(false); + return; + } - notification["success"]({ - message: t("owners.successes.save"), - }); + notification["success"]({ + message: t("owners.successes.save") + }); - if (refetch) await refetch(); - form.resetFields(); - form.resetFields(); - setLoading(false); - }; + if (refetch) await refetch(); + form.resetFields(); + form.resetFields(); + setLoading(false); + }; - return ( - <> - - - , - , - ]} - /> -
- - - - ); + return ( + <> + + + , + + ]} + /> +
+ + + + ); } export default OwnerDetailFormContainer; diff --git a/client/src/components/owner-detail-jobs/owner-detail-jobs.component.jsx b/client/src/components/owner-detail-jobs/owner-detail-jobs.component.jsx index c65bd34f8..d9b1ab615 100644 --- a/client/src/components/owner-detail-jobs/owner-detail-jobs.component.jsx +++ b/client/src/components/owner-detail-jobs/owner-detail-jobs.component.jsx @@ -1,148 +1,132 @@ -import {Card, Table} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { Card, Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {alphaSort, statusSort} from "../../utils/sorters"; +import { alphaSort, statusSort } from "../../utils/sorters"; import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -function OwnerDetailJobsComponent({bodyshop, owner}) { - const {t} = useTranslation(); - const [selectedJobs, setSelectedJobs] = useState([]); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); +function OwnerDetailJobsComponent({ bodyshop, owner }) { + const { t } = useTranslation(); + const [selectedJobs, setSelectedJobs] = useState([]); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - ellipsis: true, - render: (text, record) => ( - - {record.ro_number || t("general.labels.na")} - - ), - sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicleid", - key: "vehicleid", - sorter: (a, b) => - alphaSort( - `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${ - a.v_model_desc || "" - }`, - `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` - ), - sortOrder: - state.sortedInfo.columnKey === "vehicleid" && state.sortedInfo.order, - render: (text, record) => - record.vehicleid ? ( - - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`.trim()} - - ) : ( - t("jobs.errors.novehicle") - ), - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", - sorter: (a, b) => - statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - filters: bodyshop.md_ro_statuses.statuses.map((status) => ({ - text: status, - value: status, - })), - onFilter: (value, record) => value.includes(record.status), - }, + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + ellipsis: true, + render: (text, record) => ( + {record.ro_number || t("general.labels.na")} + ), + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicleid", + key: "vehicleid", + sorter: (a, b) => + alphaSort( + `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`, + `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` + ), + sortOrder: state.sortedInfo.columnKey === "vehicleid" && state.sortedInfo.order, + render: (text, record) => + record.vehicleid ? ( + + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`.trim()} + + ) : ( + t("jobs.errors.novehicle") + ) + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), + sortOrder: state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + filters: bodyshop.md_ro_statuses.statuses.map((status) => ({ + text: status, + value: status + })), + onFilter: (value, record) => value.includes(record.status) + }, - { - title: t("jobs.fields.clm_total"), - dataIndex: "clm_total", - key: "clm_total", - render: (text, record) => ( - {record.clm_total} - ), - sorter: (a, b) => a.clm_total - b.clm_total, - sortOrder: - state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, - }, - ]; + { + title: t("jobs.fields.clm_total"), + dataIndex: "clm_total", + key: "clm_total", + render: (text, record) => {record.clm_total}, + sorter: (a, b) => a.clm_total - b.clm_total, + sortOrder: state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order + } + ]; - return ( - - } - > -
{ - setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []); - }, - onSelectAll: (selected, selectedRows, changeRows) => { - setSelectedJobs( - selectedRows - ? selectedRows - .filter((i) => - bodyshop.md_ro_statuses.active_statuses.includes(i.status) - ) - .map((i) => i.id) - : [] - ); - }, - selectedRowKeys: selectedJobs, - getCheckboxProps: (record) => ({ - disabled: bodyshop.md_ro_statuses.active_statuses - ? !bodyshop.md_ro_statuses.active_statuses.includes(record.status) - : true, - }), - }} - /> - - ); + return ( + + } + > +
{ + setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []); + }, + onSelectAll: (selected, selectedRows, changeRows) => { + setSelectedJobs( + selectedRows + ? selectedRows + .filter((i) => bodyshop.md_ro_statuses.active_statuses.includes(i.status)) + .map((i) => i.id) + : [] + ); + }, + selectedRowKeys: selectedJobs, + getCheckboxProps: (record) => ({ + disabled: bodyshop.md_ro_statuses.active_statuses + ? !bodyshop.md_ro_statuses.active_statuses.includes(record.status) + : true + }) + }} + /> + + ); } export default connect(mapStateToProps, null)(OwnerDetailJobsComponent); diff --git a/client/src/components/owner-detail-update-jobs/owner-detail-update-jobs.component.jsx b/client/src/components/owner-detail-update-jobs/owner-detail-update-jobs.component.jsx index c640481a0..5db3a72a2 100644 --- a/client/src/components/owner-detail-update-jobs/owner-detail-update-jobs.component.jsx +++ b/client/src/components/owner-detail-update-jobs/owner-detail-update-jobs.component.jsx @@ -1,53 +1,49 @@ import React from "react"; -import {Button, notification} from "antd"; -import {useTranslation} from "react-i18next"; -import {useMutation} from "@apollo/client"; -import {UPDATE_JOBS} from "../../graphql/jobs.queries"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { Button, notification } from "antd"; +import { useTranslation } from "react-i18next"; +import { useMutation } from "@apollo/client"; +import { UPDATE_JOBS } from "../../graphql/jobs.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; -export default function OwnerDetailUpdateJobsComponent({ - owner, - selectedJobs, - disabled, - }) { - const {t} = useTranslation(); - const [updateJobs] = useMutation(UPDATE_JOBS); - const handlecClick = (e) => { - logImEXEvent("owner_update_jobs", {count: selectedJobs.length}); +export default function OwnerDetailUpdateJobsComponent({ owner, selectedJobs, disabled }) { + const { t } = useTranslation(); + const [updateJobs] = useMutation(UPDATE_JOBS); + const handlecClick = (e) => { + logImEXEvent("owner_update_jobs", { count: selectedJobs.length }); - updateJobs({ - variables: { - jobIds: selectedJobs, - fields: { - ownr_addr1: owner["ownr_addr1"], - ownr_addr2: owner["ownr_addr2"], - ownr_co_nm: owner["ownr_co_nm"], - ownr_city: owner["ownr_city"], - ownr_ctry: owner["ownr_ctry"], - ownr_ea: owner["ownr_ea"], - ownr_fn: owner["ownr_fn"], - ownr_ph1: owner["ownr_ph1"], - ownr_ln: owner["ownr_ln"], - ownr_ph2: owner["ownr_ph2"], - ownr_st: owner["ownr_st"], - ownr_title: owner["ownr_title"], - ownr_zip: owner["ownr_zip"], - }, - }, - }) - .then((response) => { - notification["success"]({message: t("jobs.successes.updated")}); - }) - .catch((error) => { - notification["error"]({ - message: t("jobs.errors.updating", {error: JSON.stringify(error)}), - }); - }); - }; + updateJobs({ + variables: { + jobIds: selectedJobs, + fields: { + ownr_addr1: owner["ownr_addr1"], + ownr_addr2: owner["ownr_addr2"], + ownr_co_nm: owner["ownr_co_nm"], + ownr_city: owner["ownr_city"], + ownr_ctry: owner["ownr_ctry"], + ownr_ea: owner["ownr_ea"], + ownr_fn: owner["ownr_fn"], + ownr_ph1: owner["ownr_ph1"], + ownr_ln: owner["ownr_ln"], + ownr_ph2: owner["ownr_ph2"], + ownr_st: owner["ownr_st"], + ownr_title: owner["ownr_title"], + ownr_zip: owner["ownr_zip"] + } + } + }) + .then((response) => { + notification["success"]({ message: t("jobs.successes.updated") }); + }) + .catch((error) => { + notification["error"]({ + message: t("jobs.errors.updating", { error: JSON.stringify(error) }) + }); + }); + }; - return ( - - ); + return ( + + ); } diff --git a/client/src/components/owner-find-modal/owner-find-modal.component.jsx b/client/src/components/owner-find-modal/owner-find-modal.component.jsx index cc0b7c425..ecaf882f1 100644 --- a/client/src/components/owner-find-modal/owner-find-modal.component.jsx +++ b/client/src/components/owner-find-modal/owner-find-modal.component.jsx @@ -1,121 +1,109 @@ -import {Checkbox, Divider, Table} from "antd"; +import { Checkbox, Divider, Table } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import PhoneFormatter from "../../utils/PhoneFormatter"; export default function OwnerFindModalComponent({ - selectedOwner, - setSelectedOwner, - ownersListLoading, - ownersList, - partsQueueToggle, - setPartsQueueToggle, - }) { - //setSelectedOwner is used to set the record id of the owner to use for adding the job. - const {t} = useTranslation(); - const columns = [ - { - title: t("owners.fields.ownr_ln"), - dataIndex: "ownr_ln", - key: "ownr_ln", - }, - { - title: t("owners.fields.ownr_fn"), - dataIndex: "ownr_fn", - key: "ownr_fn", - }, - { - title: t("owners.fields.ownr_co_nm"), - dataIndex: "ownr_co_nm", - key: "ownr_co_nm", - }, - { - title: t("owners.fields.ownr_addr1"), - dataIndex: "ownr_addr1", - key: "ownr_addr1", - }, - { - title: t("owners.fields.ownr_city"), - dataIndex: "ownr_city", - key: "ownr_city", - }, - { - title: t("owners.fields.ownr_ea"), - dataIndex: "ownr_ea", - key: "ownr_ea", - }, - { - title: t("owners.fields.ownr_ph1"), - dataIndex: "ownr_ph1", - key: "ownr_ph1", - render: (text, record) => ( - {record.ownr_ph1} - ), - }, - { - title: t("owners.fields.ownr_ph2"), - dataIndex: "ownr_ph2", - key: "ownr_ph2", - render: (text, record) => ( - {record.ownr_ph2} - ), - }, - { - title: t("owners.fields.note"), - dataIndex: "note", - key: "note", - render: (text, record) => ( - {record.note} - ), - }, - ]; + selectedOwner, + setSelectedOwner, + ownersListLoading, + ownersList, + partsQueueToggle, + setPartsQueueToggle +}) { + //setSelectedOwner is used to set the record id of the owner to use for adding the job. + const { t } = useTranslation(); + const columns = [ + { + title: t("owners.fields.ownr_ln"), + dataIndex: "ownr_ln", + key: "ownr_ln" + }, + { + title: t("owners.fields.ownr_fn"), + dataIndex: "ownr_fn", + key: "ownr_fn" + }, + { + title: t("owners.fields.ownr_co_nm"), + dataIndex: "ownr_co_nm", + key: "ownr_co_nm" + }, + { + title: t("owners.fields.ownr_addr1"), + dataIndex: "ownr_addr1", + key: "ownr_addr1" + }, + { + title: t("owners.fields.ownr_city"), + dataIndex: "ownr_city", + key: "ownr_city" + }, + { + title: t("owners.fields.ownr_ea"), + dataIndex: "ownr_ea", + key: "ownr_ea" + }, + { + title: t("owners.fields.ownr_ph1"), + dataIndex: "ownr_ph1", + key: "ownr_ph1", + render: (text, record) => {record.ownr_ph1} + }, + { + title: t("owners.fields.ownr_ph2"), + dataIndex: "ownr_ph2", + key: "ownr_ph2", + render: (text, record) => {record.ownr_ph2} + }, + { + title: t("owners.fields.note"), + dataIndex: "note", + key: "note", + render: (text, record) => {record.note} + } + ]; - const handleOnRowClick = (record) => { - if (record) { - if (record.id) { - setSelectedOwner(record.id); - return; + const handleOnRowClick = (record) => { + if (record) { + if (record.id) { + setSelectedOwner(record.id); + return; + } + } + setSelectedOwner(null); + }; + + return ( +
+
{ + setSelectedOwner(props.id); + }, + type: "radio", + selectedRowKeys: [selectedOwner] + }} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + handleOnRowClick(record); } - } - setSelectedOwner(null); - }; - - return ( -
-
{ - setSelectedOwner(props.id); - }, - type: "radio", - selectedRowKeys: [selectedOwner], - }} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - handleOnRowClick(record); - }, - }; - }} - /> - - setSelectedOwner(null)} - > - {t("owners.labels.create_new")} - - setPartsQueueToggle(e.target.checked)} - > - {t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")} - - - ); + }; + }} + /> + + setSelectedOwner(null)}> + {t("owners.labels.create_new")} + + setPartsQueueToggle(e.target.checked)}> + {t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")} + + + ); } diff --git a/client/src/components/owner-find-modal/owner-find-modal.container.jsx b/client/src/components/owner-find-modal/owner-find-modal.container.jsx index 31d863afc..67451f10d 100644 --- a/client/src/components/owner-find-modal/owner-find-modal.container.jsx +++ b/client/src/components/owner-find-modal/owner-find-modal.container.jsx @@ -1,76 +1,63 @@ -import {useLazyQuery} from "@apollo/client"; -import {Input, Modal} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {QUERY_SEARCH_OWNER_BY_IDX} from "../../graphql/owners.queries"; +import { useLazyQuery } from "@apollo/client"; +import { Input, Modal } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { QUERY_SEARCH_OWNER_BY_IDX } from "../../graphql/owners.queries"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import OwnerFindModalComponent from "./owner-find-modal.component"; -import {OwnerNameDisplayFunction} from "../owner-name-display/owner-name-display.component"; +import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; export default function OwnerFindModalContainer({ - loading, - error, - owner, - selectedOwner, - setSelectedOwner, - partsQueueToggle, - setPartsQueueToggle, - ...modalProps - }) { - //use owner object to run query and find what possible owners there are. - const {t} = useTranslation(); - const [searchText, setSearchText] = useState(null); + loading, + error, + owner, + selectedOwner, + setSelectedOwner, + partsQueueToggle, + setPartsQueueToggle, + ...modalProps +}) { + //use owner object to run query and find what possible owners there are. + const { t } = useTranslation(); + const [searchText, setSearchText] = useState(null); - const [callSearchowners, ownersList] = useLazyQuery( - QUERY_SEARCH_OWNER_BY_IDX, - { - variables: { - search: searchText, - }, - } - ); + const [callSearchowners, ownersList] = useLazyQuery(QUERY_SEARCH_OWNER_BY_IDX, { + variables: { + search: searchText + } + }); - useEffect(() => { - if (modalProps.open && owner) { - const s = OwnerNameDisplayFunction(owner, true); + useEffect(() => { + if (modalProps.open && owner) { + const s = OwnerNameDisplayFunction(owner, true); - setSearchText(s.trim()); - callSearchowners({variables: {search: s.trim()}}); - } - }, [callSearchowners, modalProps.open, owner]); + setSearchText(s.trim()); + callSearchowners({ variables: { search: s.trim() } }); + } + }, [callSearchowners, modalProps.open, owner]); - return ( - - {loading ? : null} - {error ? : null} - {owner ? ( - <> - setSearchText(e.target.value)} - onSearch={(val) => - callSearchowners({variables: {search: val.trim()}}) - } - /> - - - ) : null} - - ); + return ( + + {loading ? : null} + {error ? : null} + {owner ? ( + <> + setSearchText(e.target.value)} + onSearch={(val) => callSearchowners({ variables: { search: val.trim() } })} + /> + + + ) : null} + + ); } diff --git a/client/src/components/owner-name-display/owner-name-display.component.jsx b/client/src/components/owner-name-display/owner-name-display.component.jsx index e3d9b51fa..245259491 100644 --- a/client/src/components/owner-name-display/owner-name-display.component.jsx +++ b/client/src/components/owner-name-display/owner-name-display.component.jsx @@ -1,48 +1,36 @@ -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {store} from "../../redux/store"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { store } from "../../redux/store"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(OwnerNameDisplay); -export function OwnerNameDisplay({bodyshop, ownerObject}) { - const emptyTest = - ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm; +export function OwnerNameDisplay({ bodyshop, ownerObject }) { + const emptyTest = ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm; - if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "") - return "N/A"; + if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "") return "N/A"; - if (bodyshop.last_name_first) - return `${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${ - ownerObject?.ownr_co_nm || "" - }`.trim(); + if (bodyshop.last_name_first) + return `${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_co_nm || ""}`.trim(); - return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${ - ownerObject.ownr_co_nm || "" - }`.trim(); + return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${ownerObject.ownr_co_nm || ""}`.trim(); } export function OwnerNameDisplayFunction(ownerObject, forceFirstLast = false) { - const emptyTest = - ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm; + const emptyTest = ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm; - if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "") - return "N/A"; + if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "") return "N/A"; - const rdxStore = store.getState(); + const rdxStore = store.getState(); - if (rdxStore.user?.bodyshop?.last_name_first && !forceFirstLast) - return `${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${ - ownerObject?.ownr_co_nm || "" - }`.trim(); + if (rdxStore.user?.bodyshop?.last_name_first && !forceFirstLast) + return `${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_co_nm || ""}`.trim(); - return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${ - ownerObject?.ownr_co_nm || "" - }`.trim(); + return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${ownerObject?.ownr_co_nm || ""}`.trim(); } diff --git a/client/src/components/owner-search-select/owner-search-select.component.jsx b/client/src/components/owner-search-select/owner-search-select.component.jsx index e82af39cb..282230d67 100644 --- a/client/src/components/owner-search-select/owner-search-select.component.jsx +++ b/client/src/components/owner-search-select/owner-search-select.component.jsx @@ -1,91 +1,87 @@ -import {LoadingOutlined} from "@ant-design/icons"; -import {useLazyQuery} from "@apollo/client"; -import {Empty, Select} from "antd"; +import { LoadingOutlined } from "@ant-design/icons"; +import { useLazyQuery } from "@apollo/client"; +import { Empty, Select } from "antd"; import _ from "lodash"; -import React, {forwardRef, useEffect, useState} from "react"; -import {SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE, SEARCH_OWNERS_FOR_AUTOCOMPLETE,} from "../../graphql/owners.queries"; +import React, { forwardRef, useEffect, useState } from "react"; +import { SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE, SEARCH_OWNERS_FOR_AUTOCOMPLETE } from "../../graphql/owners.queries"; import AlertComponent from "../alert/alert.component"; -import {OwnerNameDisplayFunction} from "../owner-name-display/owner-name-display.component"; +import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; -const {Option} = Select; +const { Option } = Select; -const OwnerSearchSelect = ({value, onChange, onBlur, disabled}, ref) => { - const [callSearch, {loading, error, data}] = useLazyQuery( - SEARCH_OWNERS_FOR_AUTOCOMPLETE - ); +const OwnerSearchSelect = ({ value, onChange, onBlur, disabled }, ref) => { + const [callSearch, { loading, error, data }] = useLazyQuery(SEARCH_OWNERS_FOR_AUTOCOMPLETE); - const [callIdSearch, {loading: idLoading, error: idError, data: idData}] = - useLazyQuery(SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE); + const [callIdSearch, { loading: idLoading, error: idError, data: idData }] = useLazyQuery( + SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE + ); - const executeSearch = (v) => { - if (v && v.variables?.search !== "" && v.variables.search.length >= 2) - callSearch(v); - }; - const debouncedExecuteSearch = _.debounce(executeSearch, 500); + const executeSearch = (v) => { + if (v && v.variables?.search !== "" && v.variables.search.length >= 2) callSearch(v); + }; + const debouncedExecuteSearch = _.debounce(executeSearch, 500); - const handleSearch = (value) => { - debouncedExecuteSearch({variables: {search: value}}); - }; + const handleSearch = (value) => { + debouncedExecuteSearch({ variables: { search: value } }); + }; - const [option, setOption] = useState(value); + const [option, setOption] = useState(value); - useEffect(() => { - if (value === option && value) { - callIdSearch({variables: {id: value}}); - } - }, [value, option, callIdSearch]); + useEffect(() => { + if (value === option && value) { + callIdSearch({ variables: { id: value } }); + } + }, [value, option, callIdSearch]); - // useEffect(() => { - // if (value !== option && onChange) { - // onChange(option); - // } - // }, [value, option, onChange]); + // useEffect(() => { + // if (value !== option && onChange) { + // onChange(option); + // } + // }, [value, option, onChange]); - const handleSelect = (value) => { - setOption(value); - if (value !== option && onChange) { - onChange(value); - } - }; + const handleSelect = (value) => { + setOption(value); + if (value !== option && onChange) { + onChange(value); + } + }; - const theOptions = [ - ...(idData && idData.owners_by_pk ? [idData.owners_by_pk] : []), - ...(data && data.search_owners ? data.search_owners : []), - ]; + const theOptions = [ + ...(idData && idData.owners_by_pk ? [idData.owners_by_pk] : []), + ...(data && data.search_owners ? data.search_owners : []) + ]; - return ( -
- - {idLoading || loading ? : null} - {error ? : null} - {idError ? ( - - ) : null} -
- ); + return ( +
+ + {idLoading || loading ? : null} + {error ? : null} + {idError ? : null} +
+ ); }; export default forwardRef(OwnerSearchSelect); diff --git a/client/src/components/owner-tag-popover/owner-tag-popover.component.jsx b/client/src/components/owner-tag-popover/owner-tag-popover.component.jsx index 546126aef..0a127d747 100644 --- a/client/src/components/owner-tag-popover/owner-tag-popover.component.jsx +++ b/client/src/components/owner-tag-popover/owner-tag-popover.component.jsx @@ -1,76 +1,74 @@ -import {Button, Col, Descriptions, Popover, Row, Tag} from "antd"; +import { Button, Col, Descriptions, Popover, Row, Tag } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {Link} from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; import PhoneFormatter from "../../utils/PhoneFormatter"; -import OwnerNameDisplay, {OwnerNameDisplayFunction,} from "../owner-name-display/owner-name-display.component"; +import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; -export default function OwnerTagPopoverComponent({job}) { - const {t} = useTranslation(); - const content = ( -
- -
- - - - - - {job.ownr_ph1 || ""} - - - {job.ownr_ph2 || ""} - - - {`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${ - job.ownr_city || "" - } ${job.ownr_st || ""} ${job.ownr_zip || ""}`} - - - {job.ownr_ea || ""} - - - - - - - - - - {job.owner.ownr_ph1 || ""} - - - {job.owner.ownr_ph2 || ""} - - - {job.owner.ownr_ph2 || ""} - - - {`${job.owner.ownr_addr1 || ""} ${job.owner.ownr_addr2 || ""} ${ - job.owner.ownr_city || "" - } ${job.owner.ownr_st || ""} ${job.owner.ownr_zip || ""} ${ - job.owner.ownr_ctry || "" - } `} - - - {job.owner.ownr_ea || ""} - - - - - - - - - ); +export default function OwnerTagPopoverComponent({ job }) { + const { t } = useTranslation(); + const content = ( +
+ +
+ + + + + + {job.ownr_ph1 || ""} + + + {job.ownr_ph2 || ""} + + + {`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${ + job.ownr_city || "" + } ${job.ownr_st || ""} ${job.ownr_zip || ""}`} + + + {job.ownr_ea || ""} + + + + + + + + + + {job.owner.ownr_ph1 || ""} + + + {job.owner.ownr_ph2 || ""} + + + {job.owner.ownr_ph2 || ""} + + + {`${job.owner.ownr_addr1 || ""} ${job.owner.ownr_addr2 || ""} ${ + job.owner.ownr_city || "" + } ${job.owner.ownr_st || ""} ${job.owner.ownr_zip || ""} ${job.owner.ownr_ctry || ""} `} + + + {job.owner.ownr_ea || ""} + + + + + + + + + ); - return ( - - - - {job.owner ? OwnerNameDisplayFunction(job) : t("jobs.errors.noowner")} - - - - ); + return ( + + + + {job.owner ? OwnerNameDisplayFunction(job) : t("jobs.errors.noowner")} + + + + ); } diff --git a/client/src/components/owners-list/owners-list.component.jsx b/client/src/components/owners-list/owners-list.component.jsx index 11338c893..73991b009 100644 --- a/client/src/components/owners-list/owners-list.component.jsx +++ b/client/src/components/owners-list/owners-list.component.jsx @@ -1,138 +1,133 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Input, Space, Table, Typography} from "antd"; +import { SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Input, Space, Table, Typography } from "antd"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {Link, useLocation, useNavigate} from "react-router-dom"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link, useLocation, useNavigate } from "react-router-dom"; import PhoneFormatter from "../../utils/PhoneFormatter"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; -import {pageLimit} from "../../utils/config"; +import { pageLimit } from "../../utils/config"; -export default function OwnersListComponent({ - loading, - owners, - total, - refetch, - }) { - const search = queryString.parse(useLocation().search); - const { - page, - // sortcolumn, sortorder - } = search; - const history = useNavigate(); +export default function OwnersListComponent({ loading, owners, total, refetch }) { + const search = queryString.parse(useLocation().search); + const { + page + // sortcolumn, sortorder + } = search; + const history = useNavigate(); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); - const {t} = useTranslation(); + const { t } = useTranslation(); - const columns = [ - { - title: t("owners.fields.name"), - dataIndex: "name", - key: "name", - render: (text, record) => ( - - - - ), - }, - { - title: t("owners.fields.ownr_ph1"), - dataIndex: "ownr_ph1", - key: "ownr_ph1", - render: (text, record) => { - return {record.ownr_ph1}; - }, - }, - { - title: t("owners.fields.ownr_ph2"), - dataIndex: "ownr_ph2", - key: "ownr_ph2", - render: (text, record) => { - return {record.ownr_ph2}; - }, - }, - { - title: t("owners.fields.ownr_ea"), - dataIndex: "ownr_ea", - key: "ownr_ea", - }, - { - title: t("owners.fields.address"), - dataIndex: "address", - key: "address", - render: (text, record) => { - return ( -
{`${record.ownr_addr1 || ""} ${record.ownr_addr2 || ""} ${ - record.ownr_city || "" - } ${record.ownr_st || ""} ${record.ownr_zip || ""}`}
- ); - }, - }, - ]; + const columns = [ + { + title: t("owners.fields.name"), + dataIndex: "name", + key: "name", + render: (text, record) => ( + + + + ) + }, + { + title: t("owners.fields.ownr_ph1"), + dataIndex: "ownr_ph1", + key: "ownr_ph1", + render: (text, record) => { + return {record.ownr_ph1}; + } + }, + { + title: t("owners.fields.ownr_ph2"), + dataIndex: "ownr_ph2", + key: "ownr_ph2", + render: (text, record) => { + return {record.ownr_ph2}; + } + }, + { + title: t("owners.fields.ownr_ea"), + dataIndex: "ownr_ea", + key: "ownr_ea" + }, + { + title: t("owners.fields.address"), + dataIndex: "address", + key: "address", + render: (text, record) => { + return ( +
{`${record.ownr_addr1 || ""} ${record.ownr_addr2 || ""} ${ + record.ownr_city || "" + } ${record.ownr_st || ""} ${record.ownr_zip || ""}`}
+ ); + } + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - search.page = pagination.current; - search.sortcolumn = sorter.columnKey; - search.sortorder = sorter.order; - history({search: queryString.stringify(search)}); - }; - return ( - - {search.search && ( - <> - - {t("general.labels.searchresults", {search: search.search})} - - - - )} - - { - if (value?.length >= 3) { - search.search = value; - } else { - delete search.search; - } - history({search: queryString.stringify(search)}); - }} - enterButton - /> - - } - > -
{ + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + search.page = pagination.current; + search.sortcolumn = sorter.columnKey; + search.sortorder = sorter.order; + history({ search: queryString.stringify(search) }); + }; + return ( + + {search.search && ( + <> + + {t("general.labels.searchresults", { search: search.search })} + + + + )} + + { + if (value?.length >= 3) { + search.search = value; + } else { + delete search.search; + } + history({ search: queryString.stringify(search) }); + }} + enterButton + /> + + } + > +
+ + ); } diff --git a/client/src/components/owners-list/owners-list.container.jsx b/client/src/components/owners-list/owners-list.container.jsx index 343638764..44d089b11 100644 --- a/client/src/components/owners-list/owners-list.container.jsx +++ b/client/src/components/owners-list/owners-list.container.jsx @@ -1,44 +1,37 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import React from "react"; -import {QUERY_ALL_OWNERS_PAGINATED} from "../../graphql/owners.queries"; +import { QUERY_ALL_OWNERS_PAGINATED } from "../../graphql/owners.queries"; import AlertComponent from "../alert/alert.component"; import OwnersListComponent from "./owners-list.component"; import queryString from "query-string"; -import {useLocation} from "react-router-dom"; -import {pageLimit} from "../../utils/config"; +import { useLocation } from "react-router-dom"; +import { pageLimit } from "../../utils/config"; export default function OwnersListContainer() { - const searchParams = queryString.parse(useLocation().search); - const {page, sortcolumn, sortorder, search} = searchParams; - const {loading, error, data, refetch} = useQuery( - QUERY_ALL_OWNERS_PAGINATED, + const searchParams = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder, search } = searchParams; + const { loading, error, data, refetch } = useQuery(QUERY_ALL_OWNERS_PAGINATED, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + search: search || "", + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, + order: [ { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - variables: { - search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, - order: [ - { - [sortcolumn || "created_at"]: sortorder - ? sortorder === "descend" - ? "desc" - : "asc" - : "desc", - }, - ], - }, + [sortcolumn || "created_at"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc" } - ); + ] + } + }); - if (error) return ; - return ( - - ); + if (error) return ; + return ( + + ); } diff --git a/client/src/components/partner-ping/partner-ping.component.jsx b/client/src/components/partner-ping/partner-ping.component.jsx index 23a877e81..f40566fa1 100644 --- a/client/src/components/partner-ping/partner-ping.component.jsx +++ b/client/src/components/partner-ping/partner-ping.component.jsx @@ -1,66 +1,63 @@ import axios from "axios"; -import React, {useEffect} from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {setPartnerVersion} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {store} from '../../redux/store' +import React, { useEffect } from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { setPartnerVersion } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { store } from "../../redux/store"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) - setPartnerVersion: (version) => dispatch(setPartnerVersion(version)), + //setUserLanguage: language => dispatch(setUserLanguage(language)) + setPartnerVersion: (version) => dispatch(setPartnerVersion(version)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(PartnerPingComponent); +export default connect(mapStateToProps, mapDispatchToProps)(PartnerPingComponent); -export function PartnerPingComponent({bodyshop,}) { - useEffect(() => { - // Create an scoped async function in the hook +export function PartnerPingComponent({ bodyshop }) { + useEffect(() => { + // Create an scoped async function in the hook - // Execute the created function directly - checkPartnerStatus(bodyshop); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [bodyshop]); + // Execute the created function directly + checkPartnerStatus(bodyshop); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [bodyshop]); - return <>; + return <>; } export async function checkPartnerStatus(bodyshop) { - if (!bodyshop) return; - try { - //if (process.env.NODE_ENV === "development") return; - const PartnerResponse = await axios.post("http://localhost:1337/ping/"); - // const { - // appver, //qbpath - // } = PartnerResponse.data; - console.log(PartnerResponse.data) - store.dispatch(setPartnerVersion(PartnerResponse.data)); - // if ( - // checkAcctPath && - // !qbpath && - // !( - // bodyshop && - // (bodyshop.cdk_dealerid || - // bodyshop.pbs_serialnumber || - // bodyshop.accountingconfig.qbo) - // ) - // ) { - // notification["error"]({ - // title: "", - // message: i18n.t("general.messages.noacctfilepath"), - // }); - // } - } catch (error) { - console.log("Partner is not running.", error); - // notification["error"]({ - // title: "", - // message: i18n.t("general.messages.partnernotrunning"), - // }); - } + if (!bodyshop) return; + try { + //if (process.env.NODE_ENV === "development") return; + const PartnerResponse = await axios.post("http://localhost:1337/ping/"); + // const { + // appver, //qbpath + // } = PartnerResponse.data; + console.log(PartnerResponse.data); + store.dispatch(setPartnerVersion(PartnerResponse.data)); + // if ( + // checkAcctPath && + // !qbpath && + // !( + // bodyshop && + // (bodyshop.cdk_dealerid || + // bodyshop.pbs_serialnumber || + // bodyshop.accountingconfig.qbo) + // ) + // ) { + // notification["error"]({ + // title: "", + // message: i18n.t("general.messages.noacctfilepath"), + // }); + // } + } catch (error) { + console.log("Partner is not running.", error); + // notification["error"]({ + // title: "", + // message: i18n.t("general.messages.partnernotrunning"), + // }); + } } diff --git a/client/src/components/parts-dispatch-expander/parts-dispatch-expander.component.jsx b/client/src/components/parts-dispatch-expander/parts-dispatch-expander.component.jsx index a32111e9c..477240357 100644 --- a/client/src/components/parts-dispatch-expander/parts-dispatch-expander.component.jsx +++ b/client/src/components/parts-dispatch-expander/parts-dispatch-expander.component.jsx @@ -1,81 +1,75 @@ -import {useMutation} from "@apollo/client"; -import {Button, Card, Col, notification, Row, Table} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Card, Col, notification, Row, Table } from "antd"; import day from "../../utils/day"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {UPDATE_PARTS_DISPATCH_LINE} from "../../graphql/parts-dispatch.queries"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; +import { useTranslation } from "react-i18next"; +import { UPDATE_PARTS_DISPATCH_LINE } from "../../graphql/parts-dispatch.queries"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; -export default function PartsDispatchExpander({dispatch, job}) { - const {t} = useTranslation(); - const [updateDispatchLine] = useMutation(UPDATE_PARTS_DISPATCH_LINE); +export default function PartsDispatchExpander({ dispatch, job }) { + const { t } = useTranslation(); + const [updateDispatchLine] = useMutation(UPDATE_PARTS_DISPATCH_LINE); - const handleAccept = async ({partsDispatchLineId}) => { - const accepted_at = day(); - const result = await updateDispatchLine({ - variables: {id: partsDispatchLineId, line: {accepted_at}}, - optimisticResponse: { - update_parts_dispatch_lines_by_pk: { - accepted_at, - id: partsDispatchLineId, - }, - }, - }); - if (result.errors) { - notification.open({ - type: "error", - message: t("parts_dispatch.errors.accepting", { - error: JSON.stringify(result.errors), - }), - }); + const handleAccept = async ({ partsDispatchLineId }) => { + const accepted_at = day(); + const result = await updateDispatchLine({ + variables: { id: partsDispatchLineId, line: { accepted_at } }, + optimisticResponse: { + update_parts_dispatch_lines_by_pk: { + accepted_at, + id: partsDispatchLineId } - }; + } + }); + if (result.errors) { + notification.open({ + type: "error", + message: t("parts_dispatch.errors.accepting", { + error: JSON.stringify(result.errors) + }) + }); + } + }; - const columns = [ - { - title: t("joblines.fields.part_qty"), - dataIndex: "quantity", - key: "quantity", - width: "10%", - //sorter: (a, b) => alphaSort(a.number, b.number), - }, - { - title: t("joblines.fields.line_desc"), - dataIndex: "joblineid", - key: "joblineid", - //sorter: (a, b) => alphaSort(a.number, b.number), - render: (text, record) => record.jobline.line_desc, - }, - { - title: t("parts_dispatch_lines.fields.accepted_at"), - dataIndex: "accepted_at", - key: "accepted_at", - width: "20%", + const columns = [ + { + title: t("joblines.fields.part_qty"), + dataIndex: "quantity", + key: "quantity", + width: "10%" + //sorter: (a, b) => alphaSort(a.number, b.number), + }, + { + title: t("joblines.fields.line_desc"), + dataIndex: "joblineid", + key: "joblineid", + //sorter: (a, b) => alphaSort(a.number, b.number), + render: (text, record) => record.jobline.line_desc + }, + { + title: t("parts_dispatch_lines.fields.accepted_at"), + dataIndex: "accepted_at", + key: "accepted_at", + width: "20%", - //sorter: (a, b) => alphaSort(a.number, b.number), - render: (text, record) => - record.accepted_at ? ( - {record.accepted_at} - ) : ( - - ), - }, - ]; - return ( - - - -
- - - - ); + //sorter: (a, b) => alphaSort(a.number, b.number), + render: (text, record) => + record.accepted_at ? ( + {record.accepted_at} + ) : ( + + ) + } + ]; + return ( + + + +
+ + + + ); } diff --git a/client/src/components/parts-dispatch-table/parts-dispatch-table.component.jsx b/client/src/components/parts-dispatch-table/parts-dispatch-table.component.jsx index 1be7491da..6e35203a9 100644 --- a/client/src/components/parts-dispatch-table/parts-dispatch-table.component.jsx +++ b/client/src/components/parts-dispatch-table/parts-dispatch-table.component.jsx @@ -1,153 +1,138 @@ -import {MinusCircleTwoTone, PlusCircleTwoTone, SyncOutlined,} from "@ant-design/icons"; -import {Button, Card, Input, Space, Table} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {TemplateList} from "../../utils/TemplateConstants"; -import {alphaSort} from "../../utils/sorters"; +import { MinusCircleTwoTone, PlusCircleTwoTone, SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Input, Space, Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { TemplateList } from "../../utils/TemplateConstants"; +import { alphaSort } from "../../utils/sorters"; import PartsDispatchExpander from "../parts-dispatch-expander/parts-dispatch-expander.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; const mapStateToProps = createStructuredSelector({ - jobRO: selectJobReadOnly, - bodyshop: selectBodyshop, + jobRO: selectJobReadOnly, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({}); -export function PartDispatchTableComponent({ - bodyshop, - jobRO, - job, - billsQuery, - handleOnRowClick, - }) { - const {t} = useTranslation(); +export function PartDispatchTableComponent({ bodyshop, jobRO, job, billsQuery, handleOnRowClick }) { + const { t } = useTranslation(); - const [state, setState] = useState({ - sortedInfo: {}, - }); - // const search = queryString.parse(useLocation().search); - // const selectedBill = search.billid; - const [searchText, setSearchText] = useState(""); + const [state, setState] = useState({ + sortedInfo: {} + }); + // const search = queryString.parse(useLocation().search); + // const selectedBill = search.billid; + const [searchText, setSearchText] = useState(""); - const Templates = TemplateList("job_special", job); + const Templates = TemplateList("job_special", job); - const {refetch} = billsQuery; + const { refetch } = billsQuery; - const recordActions = (record) => ( - - - - ); - const columns = [ - { - title: t("parts_dispatch.fields.number"), - dataIndex: "number", - key: "number", - sorter: (a, b) => alphaSort(a.number, b.number), - width: "10%", - sortOrder: - state.sortedInfo.columnKey === "number" && state.sortedInfo.order, - }, - { - title: t("timetickets.fields.employee"), - dataIndex: "employeeid", - key: "employeeid", - sorter: (a, b) => alphaSort(a.employeeid, b.employeeid), - sortOrder: - state.sortedInfo.columnKey === "employeeid" && state.sortedInfo.order, - render: (text, record) => { - const e = bodyshop.employees.find((e) => e.id === record.employeeid); - return `${e?.first_name || ""} ${e?.last_name || ""}`.trim(); - }, - }, - { - title: t("parts_dispatch.fields.percent_accepted"), - dataIndex: "percent_accepted", - key: "percent_accepted", + const recordActions = (record) => ( + + + + ); + const columns = [ + { + title: t("parts_dispatch.fields.number"), + dataIndex: "number", + key: "number", + sorter: (a, b) => alphaSort(a.number, b.number), + width: "10%", + sortOrder: state.sortedInfo.columnKey === "number" && state.sortedInfo.order + }, + { + title: t("timetickets.fields.employee"), + dataIndex: "employeeid", + key: "employeeid", + sorter: (a, b) => alphaSort(a.employeeid, b.employeeid), + sortOrder: state.sortedInfo.columnKey === "employeeid" && state.sortedInfo.order, + render: (text, record) => { + const e = bodyshop.employees.find((e) => e.id === record.employeeid); + return `${e?.first_name || ""} ${e?.last_name || ""}`.trim(); + } + }, + { + title: t("parts_dispatch.fields.percent_accepted"), + dataIndex: "percent_accepted", + key: "percent_accepted", - render: (text, record) => - record.parts_dispatch_lines.length > 0 - ? ` + render: (text, record) => + record.parts_dispatch_lines.length > 0 + ? ` ${( - (record.parts_dispatch_lines.filter((l) => l.accepted_at) - .length / - record.parts_dispatch_lines.length) * - 100 - ).toFixed(0)}%` - : "0%", - }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", - width: "10%", - render: (text, record) => recordActions(record, true), - }, - ]; + (record.parts_dispatch_lines.filter((l) => l.accepted_at).length / record.parts_dispatch_lines.length) * + 100 + ).toFixed(0)}%` + : "0%" + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + width: "10%", + render: (text, record) => recordActions(record, true) + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - return ( - - + return ( + + - { - e.preventDefault(); - setSearchText(e.target.value); - }} - /> - - } - > -
( - - ), - rowExpandable: (record) => true, + { + e.preventDefault(); + setSearchText(e.target.value); + }} + /> + + } + > +
, + rowExpandable: (record) => true, - expandIcon: ({expanded, onExpand, record}) => - expanded ? ( - onExpand(record, e)}/> - ) : ( - onExpand(record, e)}/> - ), - }} - columns={columns} - rowKey="id" - dataSource={billsQuery.data ? billsQuery.data.parts_dispatch : []} - onChange={handleTableChange} - /> - - ); + expandIcon: ({ expanded, onExpand, record }) => + expanded ? ( + onExpand(record, e)} /> + ) : ( + onExpand(record, e)} /> + ) + }} + columns={columns} + rowKey="id" + dataSource={billsQuery.data ? billsQuery.data.parts_dispatch : []} + onChange={handleTableChange} + /> + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(PartDispatchTableComponent); +export default connect(mapStateToProps, mapDispatchToProps)(PartDispatchTableComponent); diff --git a/client/src/components/parts-order-backorder-eta/parts-order-backorder-eta.component.jsx b/client/src/components/parts-order-backorder-eta/parts-order-backorder-eta.component.jsx index c4f7908a1..dc27b3601 100644 --- a/client/src/components/parts-order-backorder-eta/parts-order-backorder-eta.component.jsx +++ b/client/src/components/parts-order-backorder-eta/parts-order-backorder-eta.component.jsx @@ -1,92 +1,84 @@ -import {useMutation} from "@apollo/client"; -import {Button, Form, notification, Popover, Spin} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {MUTATION_UPDATE_BO_ETA} from "../../graphql/parts-orders.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {DateFormatter} from "../../utils/DateFormatter"; +import { useMutation } from "@apollo/client"; +import { Button, Form, notification, Popover, Spin } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { MUTATION_UPDATE_BO_ETA } from "../../graphql/parts-orders.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { DateFormatter } from "../../utils/DateFormatter"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; -import {CalendarFilled} from "@ant-design/icons"; +import { CalendarFilled } from "@ant-design/icons"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); export function PartsOrderBackorderEta({ - backordered_eta, - partsOrderStatus, - partsLineId, - jobLineId, - disabled, - bodyshop, - }) { - const [visibility, setVisibility] = useState(false); - const [loading, setLoading] = useState(false); - const [updateBoDate] = useMutation(MUTATION_UPDATE_BO_ETA); - const {t} = useTranslation(); - const [form] = Form.useForm(); + backordered_eta, + partsOrderStatus, + partsLineId, + jobLineId, + disabled, + bodyshop +}) { + const [visibility, setVisibility] = useState(false); + const [loading, setLoading] = useState(false); + const [updateBoDate] = useMutation(MUTATION_UPDATE_BO_ETA); + const { t } = useTranslation(); + const [form] = Form.useForm(); - const isAlreadyBackordered = - bodyshop.md_order_statuses.default_bo === partsOrderStatus; + const isAlreadyBackordered = bodyshop.md_order_statuses.default_bo === partsOrderStatus; - const handleFinish = async (values) => { - setLoading(true); - logImEXEvent("job_parts_backorder_update_eta"); + const handleFinish = async (values) => { + setLoading(true); + logImEXEvent("job_parts_backorder_update_eta"); - const result = await updateBoDate({ - variables: { - partsLineId, - partsOrder: {backordered_eta: values.eta}, - }, - }); + const result = await updateBoDate({ + variables: { + partsLineId, + partsOrder: { backordered_eta: values.eta } + } + }); - if (!!result.errors) { - notification["error"]({ - message: t("parts_orders.errors.backordering", { - message: JSON.stringify(result.errors), - }), - }); - } + if (!!result.errors) { + notification["error"]({ + message: t("parts_orders.errors.backordering", { + message: JSON.stringify(result.errors) + }) + }); + } - setVisibility(false); - setLoading(false); - }; + setVisibility(false); + setLoading(false); + }; - const handlePopover = (e) => { - setVisibility(true); - }; + const handlePopover = (e) => { + setVisibility(true); + }; - const popContent = ( -
-
- - - - - - -
- ); + const popContent = ( +
+
+ + + + + + +
+ ); - return ( - - {backordered_eta} - {isAlreadyBackordered && ( - - )} - {loading && } - - ); + return ( + + {backordered_eta} + {isAlreadyBackordered && } + {loading && } + + ); } export default connect(mapStateToProps, null)(PartsOrderBackorderEta); diff --git a/client/src/components/parts-order-cm-received/parts-order-cm-received.component.jsx b/client/src/components/parts-order-cm-received/parts-order-cm-received.component.jsx index 4ad6d294e..d29efc1d0 100644 --- a/client/src/components/parts-order-cm-received/parts-order-cm-received.component.jsx +++ b/client/src/components/parts-order-cm-received/parts-order-cm-received.component.jsx @@ -1,66 +1,62 @@ -import {useMutation} from "@apollo/client"; -import {Checkbox, notification, Space, Spin} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {MUTATION_UPDATE_PO_CM_REECEIVED} from "../../graphql/parts-orders.queries"; +import { useMutation } from "@apollo/client"; +import { Checkbox, notification, Space, Spin } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { MUTATION_UPDATE_PO_CM_REECEIVED } from "../../graphql/parts-orders.queries"; -export default function PartsOrderCmReceived({ - checked, - orderLineId, - partsOrderId, - }) { - const [updateLine] = useMutation(MUTATION_UPDATE_PO_CM_REECEIVED); - const {t} = useTranslation(); +export default function PartsOrderCmReceived({ checked, orderLineId, partsOrderId }) { + const [updateLine] = useMutation(MUTATION_UPDATE_PO_CM_REECEIVED); + const { t } = useTranslation(); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(false); - const handleChange = async (e) => { - setLoading(true); - const result = await updateLine({ - variables: { - partsLineId: orderLineId, - partsOrder: {cm_received: e.target.checked}, - }, - update(cache) { - cache.modify({ - id: cache.identify({ - id: partsOrderId, - __typename: "parts_orders", - }), + const handleChange = async (e) => { + setLoading(true); + const result = await updateLine({ + variables: { + partsLineId: orderLineId, + partsOrder: { cm_received: e.target.checked } + }, + update(cache) { + cache.modify({ + id: cache.identify({ + id: partsOrderId, + __typename: "parts_orders" + }), - fields: { - parts_order_lines(ex, {readField}) { - console.log(ex); - return ex.map((lineref) => { - if (orderLineId.id !== readField("id", lineref)) { - lineref.cm_received = e.target.checked; - } - return lineref; - }); - }, - }, - }); - }, + fields: { + parts_order_lines(ex, { readField }) { + console.log(ex); + return ex.map((lineref) => { + if (orderLineId.id !== readField("id", lineref)) { + lineref.cm_received = e.target.checked; + } + return lineref; + }); + } + } }); + } + }); - if (!!!result.errors) { - notification["success"]({ - message: t("parts_orders.successes.line_updated"), - }); - } else { - notification["error"]({ - message: t("parts_orders.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - }; + if (!!!result.errors) { + notification["success"]({ + message: t("parts_orders.successes.line_updated") + }); + } else { + notification["error"]({ + message: t("parts_orders.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + }; - return ( - - - {loading && } - - ); + return ( + + + {loading && } + + ); } diff --git a/client/src/components/parts-order-delete-line/parts-order-delete-line.component.jsx b/client/src/components/parts-order-delete-line/parts-order-delete-line.component.jsx index 17d4f6fed..a04270ab9 100644 --- a/client/src/components/parts-order-delete-line/parts-order-delete-line.component.jsx +++ b/client/src/components/parts-order-delete-line/parts-order-delete-line.component.jsx @@ -1,47 +1,43 @@ import React from "react"; -import {Button, Popconfirm} from "antd"; -import {DeleteFilled} from "@ant-design/icons"; -import {useTranslation} from "react-i18next"; -import {DELETE_PARTS_ORDER_LINE} from "../../graphql/parts-orders.queries"; -import {useMutation} from "@apollo/client"; +import { Button, Popconfirm } from "antd"; +import { DeleteFilled } from "@ant-design/icons"; +import { useTranslation } from "react-i18next"; +import { DELETE_PARTS_ORDER_LINE } from "../../graphql/parts-orders.queries"; +import { useMutation } from "@apollo/client"; -export default function PartsOrderDeleteLine({ - disabled, - partsLineId, - partsOrderId, - }) { - const {t} = useTranslation(); - const [deletePartsOrderLine] = useMutation(DELETE_PARTS_ORDER_LINE); - return ( - { - //Delete the parts return.! +export default function PartsOrderDeleteLine({ disabled, partsLineId, partsOrderId }) { + const { t } = useTranslation(); + const [deletePartsOrderLine] = useMutation(DELETE_PARTS_ORDER_LINE); + return ( + { + //Delete the parts return.! - await deletePartsOrderLine({ - variables: {partsOrderLineId: partsLineId}, - update(cache) { - cache.modify({ - id: cache.identify({ - __typename: "parts_orders", - id: partsOrderId, - }), - fields: { - parts_order_lines(cached, {readField}) { - return cached.filter((c) => { - return readField("id", c) !== partsLineId; - }); - }, - }, - }); - }, - }); - }} - > - - - ); + await deletePartsOrderLine({ + variables: { partsOrderLineId: partsLineId }, + update(cache) { + cache.modify({ + id: cache.identify({ + __typename: "parts_orders", + id: partsOrderId + }), + fields: { + parts_order_lines(cached, { readField }) { + return cached.filter((c) => { + return readField("id", c) !== partsLineId; + }); + } + } + }); + } + }); + }} + > + + + ); } diff --git a/client/src/components/parts-order-line-backorder-button/parts-order-line-backorder-button.component.jsx b/client/src/components/parts-order-line-backorder-button/parts-order-line-backorder-button.component.jsx index bb87bf91d..00103dbda 100644 --- a/client/src/components/parts-order-line-backorder-button/parts-order-line-backorder-button.component.jsx +++ b/client/src/components/parts-order-line-backorder-button/parts-order-line-backorder-button.component.jsx @@ -1,107 +1,93 @@ -import {useMutation} from "@apollo/client"; -import {Button, Form, notification, Popover} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {MUTATION_BACKORDER_PART_LINE} from "../../graphql/parts-orders.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useMutation } from "@apollo/client"; +import { Button, Form, notification, Popover } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { MUTATION_BACKORDER_PART_LINE } from "../../graphql/parts-orders.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -export function PartsOrderLineBackorderButton({ - partsOrderStatus, - partsLineId, - jobLineId, - disabled, - bodyshop, - }) { - const [visibility, setVisibility] = useState(false); - const [loading, setLoading] = useState(false); - const [backorderLine] = useMutation(MUTATION_BACKORDER_PART_LINE); - const {t} = useTranslation(); - const [form] = Form.useForm(); +export function PartsOrderLineBackorderButton({ partsOrderStatus, partsLineId, jobLineId, disabled, bodyshop }) { + const [visibility, setVisibility] = useState(false); + const [loading, setLoading] = useState(false); + const [backorderLine] = useMutation(MUTATION_BACKORDER_PART_LINE); + const { t } = useTranslation(); + const [form] = Form.useForm(); - const isAlreadyBackordered = - bodyshop.md_order_statuses.default_bo === partsOrderStatus; + const isAlreadyBackordered = bodyshop.md_order_statuses.default_bo === partsOrderStatus; - const handleFinish = async (values) => { - setLoading(true); - logImEXEvent("job_parts_backorder"); + const handleFinish = async (values) => { + setLoading(true); + logImEXEvent("job_parts_backorder"); - const partsOrder = { - status: isAlreadyBackordered - ? bodyshop.md_order_statuses.default_received || "Received*" - : bodyshop.md_order_statuses.default_bo || "Backordered*", - }; - if (!isAlreadyBackordered) { - partsOrder.backordered_on = new Date(); - partsOrder.backordered_eta = values.eta; - } - - const result = await backorderLine({ - variables: { - jobLineId, - partsLineId, - partsOrder: partsOrder, - status: partsOrder.status, - }, - }); - - if (!!result.errors) { - notification["error"]({ - message: t("parts_orders.errors.backordering", { - message: JSON.stringify(result.errors), - }), - }); - } - - setVisibility(false); - setLoading(false); + const partsOrder = { + status: isAlreadyBackordered + ? bodyshop.md_order_statuses.default_received || "Received*" + : bodyshop.md_order_statuses.default_bo || "Backordered*" }; + if (!isAlreadyBackordered) { + partsOrder.backordered_on = new Date(); + partsOrder.backordered_eta = values.eta; + } - const handlePopover = (e) => { - if (isAlreadyBackordered) { - handleFinish(); - //Receive the part. - } else { - //Show the date selector to back order the part. - setVisibility(true); - } - }; + const result = await backorderLine({ + variables: { + jobLineId, + partsLineId, + partsOrder: partsOrder, + status: partsOrder.status + } + }); - const popContent = ( -
-
- - - - - - -
- ); + if (!!result.errors) { + notification["error"]({ + message: t("parts_orders.errors.backordering", { + message: JSON.stringify(result.errors) + }) + }); + } - return ( - - - - ); + setVisibility(false); + setLoading(false); + }; + + const handlePopover = (e) => { + if (isAlreadyBackordered) { + handleFinish(); + //Receive the part. + } else { + //Show the date selector to back order the part. + setVisibility(true); + } + }; + + const popContent = ( +
+
+ + + + + + +
+ ); + + return ( + + + + ); } export default connect(mapStateToProps, null)(PartsOrderLineBackorderButton); diff --git a/client/src/components/parts-order-list-table/parts-order-list-table.component.jsx b/client/src/components/parts-order-list-table/parts-order-list-table.component.jsx index 8c80782d5..e67905e68 100644 --- a/client/src/components/parts-order-list-table/parts-order-list-table.component.jsx +++ b/client/src/components/parts-order-list-table/parts-order-list-table.component.jsx @@ -1,497 +1,441 @@ -import {DeleteFilled, EyeFilled, SyncOutlined} from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {Button, Card, Checkbox, Drawer, Grid, Input, Popconfirm, Space, Table,} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import { DeleteFilled, EyeFilled, SyncOutlined } from "@ant-design/icons"; +import { useMutation } from "@apollo/client"; +import { Button, Card, Checkbox, Drawer, Grid, Input, Popconfirm, Space, Table } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {DELETE_PARTS_ORDER} from "../../graphql/parts-orders.queries"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {DateFormatter} from "../../utils/DateFormatter"; -import {alphaSort} from "../../utils/sorters"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { DateFormatter } from "../../utils/DateFormatter"; +import { alphaSort } from "../../utils/sorters"; +import { TemplateList } from "../../utils/TemplateConstants"; import DataLabel from "../data-label/data-label.component"; import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component"; import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component"; import PartsOrderDeleteLine from "../parts-order-delete-line/parts-order-delete-line.component"; -import PartsOrderLineBackorderButton - from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component"; +import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component"; import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container"; import PrintWrapper from "../print-wrapper/print-wrapper.component"; import FeatureWrapperComponent from "../feature-wrapper/feature-wrapper.component"; const mapStateToProps = createStructuredSelector({ - jobRO: selectJobReadOnly, - bodyshop: selectBodyshop, + jobRO: selectJobReadOnly, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBillEnterContext: (context) => - dispatch(setModalContext({context: context, modal: "billEnter"})), - setPartsReceiveContext: (context) => - dispatch(setModalContext({context: context, modal: "partsReceive"})), + setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })), + setPartsReceiveContext: (context) => dispatch(setModalContext({ context: context, modal: "partsReceive" })) }); export function PartsOrderListTableComponent({ - setBillEnterContext, - bodyshop, - jobRO, - job, - billsQuery, - handleOnRowClick, - setPartsReceiveContext, - }) { - const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) - .filter((screen) => !!screen[1]) - .slice(-1)[0]; + setBillEnterContext, + bodyshop, + jobRO, + job, + billsQuery, + handleOnRowClick, + setPartsReceiveContext +}) { + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) + .filter((screen) => !!screen[1]) + .slice(-1)[0]; - const bpoints = { - xs: "100%", - sm: "100%", - md: "100%", - lg: "75%", - xl: "75%", - xxl: "65%", - }; - const drawerPercentage = selectedBreakpoint - ? bpoints[selectedBreakpoint[0]] - : "100%"; - const responsibilityCenters = bodyshop.md_responsibility_centers; - const Templates = TemplateList("partsorder", {job}); + const bpoints = { + xs: "100%", + sm: "100%", + md: "100%", + lg: "75%", + xl: "75%", + xxl: "65%" + }; + const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%"; + const responsibilityCenters = bodyshop.md_responsibility_centers; + const Templates = TemplateList("partsorder", { job }); - const {t} = useTranslation(); - const [state, setState] = useState({ - sortedInfo: {}, - }); - const search = queryString.parse(useLocation().search); - const selectedpartsorder = search.partsorderid; - const [searchText, setSearchText] = useState(""); + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {} + }); + const search = queryString.parse(useLocation().search); + const selectedpartsorder = search.partsorderid; + const [searchText, setSearchText] = useState(""); - const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER); + const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER); - const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : []; - const {refetch} = billsQuery; + const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : []; + const { refetch } = billsQuery; - const recordActions = (record, showView = false) => ( - - {showView && ( - - )} - - + )} + + + + { + //Delete the parts return.! + + await deletePartsOrder({ + variables: { partsOrderId: record.id }, + update(cache) { + cache.modify({ + fields: { + parts_orders(existingPartsOrders, { readField }) { + return existingPartsOrders.filter((billref) => record.id !== readField("id", billref)); + } } - onClick={() => { - logImEXEvent("parts_order_receive_bill"); - setPartsReceiveContext({ - actions: {refetch: refetch}, - context: { - jobId: job.id, - job: job, - partsorderlines: record.parts_order_lines.map((pol) => { - return { - joblineid: pol.job_line_id, - id: pol.id, - line_desc: pol.line_desc, - quantity: pol.quantity, - act_price: pol.act_price, - oem_partno: pol.oem_partno, - }; - }), - }, - }); - }} - > - {t("parts_orders.actions.receive")} - - - { - //Delete the parts return.! + }); + } + }); + }} + > + + + null}> + - - null}> - + + + + ); - setBillEnterContext({ - actions: {refetch: refetch}, - context: { - job: job, - bill: { - vendorid: record.vendor.id, - is_credit_memo: record.return, - billlines: record.parts_order_lines.map((pol) => { - return { - joblineid: pol.job_line_id, - line_desc: pol.line_desc, - quantity: pol.quantity, + const columns = [ + { + title: t("vendors.fields.name"), + dataIndex: "vendorname", + key: "vendorname", + sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name), + sortOrder: state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order, + render: (text, record) => {record.vendor.name} + }, + { + title: t("parts_orders.fields.order_number"), + dataIndex: "order_number", + key: "order_number", + sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), + sortOrder: state.sortedInfo.columnKey === "invoice_number" && state.sortedInfo.order + }, + { + title: t("parts_orders.fields.order_date"), + dataIndex: "order_date", + key: "order_date", + sorter: (a, b) => a.order_date - b.order_date, + sortOrder: state.sortedInfo.columnKey === "order_date" && state.sortedInfo.order, + render: (text, record) => {record.order_date} + }, + { + title: t("parts_orders.fields.return"), + dataIndex: "return", + key: "return", + sorter: (a, b) => a.return - b.return, + sortOrder: state.sortedInfo.columnKey === "return" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: t("parts_orders.fields.deliver_by"), + dataIndex: "deliver_by", + key: "deliver_by", + sorter: (a, b) => a.deliver_by - b.deliver_by, + sortOrder: state.sortedInfo.columnKey === "deliver_by" && state.sortedInfo.order, + render: (text, record) => {record.deliver_by} + }, + { + title: t("parts_orders.fields.orderedby"), + dataIndex: "orderedby", + key: "orderedby" + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => recordActions(record, true) + } + ]; - actual_price: pol.act_price, + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - cost_center: pol.jobline?.part_type - ? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid - ? pol.jobline.part_type !== "PAE" - ? pol.jobline.part_type - : null - : responsibilityCenters.defaults && - (responsibilityCenters.defaults.costs[ - pol.jobline.part_type - ] || - null) - : null, - }; - }), - }, - }, - }); - }} - > - {t("parts_orders.actions.receivebill")} - - - - - ); + const selectedPartsOrderRecord = parts_orders.find((r) => r.id === selectedpartsorder); + const rowExpander = (record) => { const columns = [ - { - title: t("vendors.fields.name"), - dataIndex: "vendorname", - key: "vendorname", - sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name), - sortOrder: - state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order, - render: (text, record) => {record.vendor.name}, - }, - { - title: t("parts_orders.fields.order_number"), - dataIndex: "order_number", - key: "order_number", - sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), - sortOrder: - state.sortedInfo.columnKey === "invoice_number" && - state.sortedInfo.order, - }, - { - title: t("parts_orders.fields.order_date"), - dataIndex: "order_date", - key: "order_date", - sorter: (a, b) => a.order_date - b.order_date, - sortOrder: - state.sortedInfo.columnKey === "order_date" && state.sortedInfo.order, - render: (text, record) => ( - {record.order_date} - ), - }, - { - title: t("parts_orders.fields.return"), - dataIndex: "return", - key: "return", - sorter: (a, b) => a.return - b.return, - sortOrder: - state.sortedInfo.columnKey === "return" && state.sortedInfo.order, - render: (text, record) => , - }, - { - title: t("parts_orders.fields.deliver_by"), - dataIndex: "deliver_by", - key: "deliver_by", - sorter: (a, b) => a.deliver_by - b.deliver_by, - sortOrder: - state.sortedInfo.columnKey === "deliver_by" && state.sortedInfo.order, - render: (text, record) => ( - {record.deliver_by} - ), - }, - { - title: t("parts_orders.fields.orderedby"), - dataIndex: "orderedby", - key: "orderedby", - }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", - render: (text, record) => recordActions(record, true), - }, + { + title: t("parts_orders.fields.line_desc"), + dataIndex: "line_desc", + key: "line_desc", + sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), + sortOrder: state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order + }, + { + title: t("parts_orders.fields.quantity"), + dataIndex: "quantity", + key: "quantity", + sorter: (a, b) => a.quantity - b.quantity, + sortOrder: state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order + }, + { + title: t("parts_orders.fields.act_price"), + dataIndex: "act_price", + key: "act_price", + sorter: (a, b) => a.act_price - b.act_price, + sortOrder: state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order, + render: (text, record) => {record.act_price} + }, + ...(selectedPartsOrderRecord && selectedPartsOrderRecord.return + ? [ + { + title: t("parts_orders.fields.cost"), + dataIndex: "cost", + key: "cost", + sorter: (a, b) => a.cost - b.cost, + sortOrder: state.sortedInfo.columnKey === "cost" && state.sortedInfo.order, + render: (text, record) => {record.cost} + } + ] + : []), + { + title: t("parts_orders.fields.part_type"), + dataIndex: "part_type", + key: "part_type", + render: (text, record) => (record.part_type ? t(`joblines.fields.part_types.${record.part_type}`) : null) + }, + { + title: t("parts_orders.fields.oem_partno"), + dataIndex: "oem_partno", + key: "oem_partno", + sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno), + sortOrder: state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order + }, + { + title: t("parts_orders.fields.line_remarks"), + dataIndex: "line_remarks", + key: "line_remarks" + }, + { + title: t("parts_orders.fields.status"), + dataIndex: "status", + key: "status" + }, + + ...(selectedPartsOrderRecord && selectedPartsOrderRecord.return + ? [ + { + title: t("parts_orders.fields.cm_received"), + dataIndex: "cm_received", + key: "cm_received", + render: (text, record) => ( + + ) + } + ] + : []), + { + title: t("parts_orders.fields.backordered_on"), + dataIndex: "backordered_on", + key: "backordered_on", + render: (text, record) => {text} + }, + { + title: t("parts_orders.fields.backordered_eta"), + dataIndex: "backordered_eta", + key: "backordered_eta", + render: (text, record) => ( + + ) + }, + + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => ( + + + + + ) + } ]; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - - const selectedPartsOrderRecord = parts_orders.find( - (r) => r.id === selectedpartsorder - ); - - const rowExpander = (record) => { - const columns = [ - { - title: t("parts_orders.fields.line_desc"), - dataIndex: "line_desc", - key: "line_desc", - sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), - sortOrder: - state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order, - }, - { - title: t("parts_orders.fields.quantity"), - dataIndex: "quantity", - key: "quantity", - sorter: (a, b) => a.quantity - b.quantity, - sortOrder: - state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order, - }, - { - title: t("parts_orders.fields.act_price"), - dataIndex: "act_price", - key: "act_price", - sorter: (a, b) => a.act_price - b.act_price, - sortOrder: - state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order, - render: (text, record) => ( - {record.act_price} - ), - }, - ...(selectedPartsOrderRecord && selectedPartsOrderRecord.return - ? [ - { - title: t("parts_orders.fields.cost"), - dataIndex: "cost", - key: "cost", - sorter: (a, b) => a.cost - b.cost, - sortOrder: - state.sortedInfo.columnKey === "cost" && state.sortedInfo.order, - render: (text, record) => ( - {record.cost} - ), - }, - ] - : []), - { - title: t("parts_orders.fields.part_type"), - dataIndex: "part_type", - key: "part_type", - render: (text, record) => - record.part_type - ? t(`joblines.fields.part_types.${record.part_type}`) - : null, - }, - { - title: t("parts_orders.fields.oem_partno"), - dataIndex: "oem_partno", - key: "oem_partno", - sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno), - sortOrder: - state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order, - }, - { - title: t("parts_orders.fields.line_remarks"), - dataIndex: "line_remarks", - key: "line_remarks", - }, - { - title: t("parts_orders.fields.status"), - dataIndex: "status", - key: "status", - }, - - ...(selectedPartsOrderRecord && selectedPartsOrderRecord.return - ? [ - { - title: t("parts_orders.fields.cm_received"), - dataIndex: "cm_received", - key: "cm_received", - render: (text, record) => ( - - ), - }, - ] - : []), - { - title: t("parts_orders.fields.backordered_on"), - dataIndex: "backordered_on", - key: "backordered_on", - render: (text, record) => {text}, - }, - { - title: t("parts_orders.fields.backordered_eta"), - dataIndex: "backordered_eta", - key: "backordered_eta", - render: (text, record) => ( - - ), - }, - - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", - render: (text, record) => ( - - - - - ), - }, - ]; - - return ( -
- -
- -
{record.comments}
-
- - ); - }; - - const filteredPartsOrders = parts_orders - ? searchText === "" - ? parts_orders - : parts_orders.filter( - (b) => - (b.order_number || "") - .toString() - .toLowerCase() - .includes(searchText.toLowerCase()) || - (b.vendor.name || "") - .toLowerCase() - .includes(searchText.toLowerCase()) - ) - : []; - return ( - - - { - e.preventDefault(); - setSearchText(e.target.value); - }} - /> - - } - > - - handleOnRowClick(null)} - open={selectedpartsorder} - closable - width={drawerPercentage} - > - {selectedPartsOrderRecord && rowExpander(selectedPartsOrderRecord)} - -
- +
+ +
+ +
{record.comments}
+
+ ); + }; + + const filteredPartsOrders = parts_orders + ? searchText === "" + ? parts_orders + : parts_orders.filter( + (b) => + (b.order_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) || + (b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) + ) + : []; + + return ( + + + { + e.preventDefault(); + setSearchText(e.target.value); + }} + /> + + } + > + + handleOnRowClick(null)} + open={selectedpartsorder} + closable + width={drawerPercentage} + > + {selectedPartsOrderRecord && rowExpander(selectedPartsOrderRecord)} + +
+ + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(PartsOrderListTableComponent); +export default connect(mapStateToProps, mapDispatchToProps)(PartsOrderListTableComponent); diff --git a/client/src/components/parts-order-modal/parts-order-modal-price-change.component.jsx b/client/src/components/parts-order-modal/parts-order-modal-price-change.component.jsx index 4c7c17e0d..4e89ab287 100644 --- a/client/src/components/parts-order-modal/parts-order-modal-price-change.component.jsx +++ b/client/src/components/parts-order-modal/parts-order-modal-price-change.component.jsx @@ -1,95 +1,88 @@ -import {DownOutlined} from "@ant-design/icons"; -import {Dropdown, InputNumber, Space} from "antd"; +import { DownOutlined } from "@ant-design/icons"; +import { Dropdown, InputNumber, Space } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; -export default function PartsOrderModalPriceChange({form, field}) { - const {t} = useTranslation(); - const menu = { - items: [ - { - key: "5", - label: t("parts_orders.labels.discount", {percent: "5%"}), - }, - { - key: "10", - label: t("parts_orders.labels.discount", {percent: "10%"}), - }, - { - key: "15", - label: t("parts_orders.labels.discount", {percent: "15%"}), - }, - { - key: "20", - label: t("parts_orders.labels.discount", {percent: "20%"}), - }, - { - key: "25", - label: t("parts_orders.labels.discount", {percent: "25%"}), - }, - { - key: "custom", - label: ( - e.stopPropagation()} - addonAfter="%" - onKeyUp={(e) => { - if (e.key === "Enter") { - const values = form.getFieldsValue(); - const {parts_order_lines} = values; +export default function PartsOrderModalPriceChange({ form, field }) { + const { t } = useTranslation(); + const menu = { + items: [ + { + key: "5", + label: t("parts_orders.labels.discount", { percent: "5%" }) + }, + { + key: "10", + label: t("parts_orders.labels.discount", { percent: "10%" }) + }, + { + key: "15", + label: t("parts_orders.labels.discount", { percent: "15%" }) + }, + { + key: "20", + label: t("parts_orders.labels.discount", { percent: "20%" }) + }, + { + key: "25", + label: t("parts_orders.labels.discount", { percent: "25%" }) + }, + { + key: "custom", + label: ( + e.stopPropagation()} + addonAfter="%" + onKeyUp={(e) => { + if (e.key === "Enter") { + const values = form.getFieldsValue(); + const { parts_order_lines } = values; - form.setFieldsValue({ - parts_order_lines: { - data: parts_order_lines.data.map((p, idx) => { - if (idx !== field.name) return p; - console.log( - p, - e.target.value, - (p.act_price || 0) * - ((100 - (e.target.value || 0)) / 100) - ); - return { - ...p, - act_price: - (p.act_price || 0) * - ((100 - (e.target.value || 0)) / 100), - }; - }), - }, - }); - e.target.value = 0; - } - }} - min={0} - max={100} - /> - ), - }, - ], - onClick: ({key}) => { - if (key === "custom") return; - const values = form.getFieldsValue(); - const {parts_order_lines} = values; - form.setFieldsValue({ - parts_order_lines: { + form.setFieldsValue({ + parts_order_lines: { data: parts_order_lines.data.map((p, idx) => { - if (idx !== field.name) return p; - return { - ...p, - act_price: (p.act_price || 0) * ((100 - key) / 100), - }; - }), - }, - }); + if (idx !== field.name) return p; + console.log(p, e.target.value, (p.act_price || 0) * ((100 - (e.target.value || 0)) / 100)); + return { + ...p, + act_price: (p.act_price || 0) * ((100 - (e.target.value || 0)) / 100) + }; + }) + } + }); + e.target.value = 0; + } + }} + min={0} + max={100} + /> + ) + } + ], + onClick: ({ key }) => { + if (key === "custom") return; + const values = form.getFieldsValue(); + const { parts_order_lines } = values; + form.setFieldsValue({ + parts_order_lines: { + data: parts_order_lines.data.map((p, idx) => { + if (idx !== field.name) return p; + return { + ...p, + act_price: (p.act_price || 0) * ((100 - key) / 100) + }; + }) } - }; + }); + } + }; - return ( - - - % - - - - ); + return ( + + + % + + + + ); } diff --git a/client/src/components/parts-order-modal/parts-order-modal.component.jsx b/client/src/components/parts-order-modal/parts-order-modal.component.jsx index d7ffdb102..946b29bad 100644 --- a/client/src/components/parts-order-modal/parts-order-modal.component.jsx +++ b/client/src/components/parts-order-modal/parts-order-modal.component.jsx @@ -1,11 +1,11 @@ -import {DeleteFilled, DownOutlined, WarningFilled} from "@ant-design/icons"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; -import {Checkbox, Divider, Dropdown, Form, Input, InputNumber, Radio, Select, Space, Tag,} from "antd"; +import { DeleteFilled, DownOutlined, WarningFilled } from "@ant-design/icons"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { Checkbox, Divider, Dropdown, Form, Input, InputNumber, Radio, Select, Space, Tag } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; @@ -14,316 +14,249 @@ import VendorSearchSelect from "../vendor-search-select/vendor-search-select.com import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(PartsOrderModalComponent); +export default connect(mapStateToProps, mapDispatchToProps)(PartsOrderModalComponent); -export function PartsOrderModalComponent({bodyshop, vendorList, sendTypeState, isReturn, preferredMake, job, form,}) { - const [sendType, setSendType] = sendTypeState; +export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState, isReturn, preferredMake, job, form }) { + const [sendType, setSendType] = sendTypeState; - const {treatments: {OEConnection, OEConnection_PriceChange}} = useSplitTreatments({ - attributes: {}, - names: ["OEConnection", "OEConnection_PriceChange"], - splitKey: bodyshop.imexshopid, - }); + const { + treatments: { OEConnection, OEConnection_PriceChange } + } = useSplitTreatments({ + attributes: {}, + names: ["OEConnection", "OEConnection_PriceChange"], + splitKey: bodyshop.imexshopid + }); + const { t } = useTranslation(); + const handleClick = ({ item, key, keyPath }) => { + form.setFieldsValue({ comments: item.props.value }); + }; - const {t} = useTranslation(); - const handleClick = ({item, key, keyPath}) => { - form.setFieldsValue({comments: item.props.value}); - }; + const menu = { + items: bodyshop.md_parts_order_comment.map((comment, idx) => ({ + key: idx, + label: comment.label, + value: comment.comment + })), + onClick: handleClick + }; - const menu = { - items: bodyshop.md_parts_order_comment.map((comment, idx) => ({ - key: idx, - label: comment.label, - value: comment.comment, - })), - onClick: handleClick - }; + return ( +
+ + + + + + + + + + + {job && job.special_coverage_policy && ( + + + + {t("jobs.labels.specialcoveragepolicy")} + + + )} + {!isReturn && ( + + + + )} + {OEConnection.treatment === "on" && !isReturn && ( + + + + )} - return ( -
- - - - - + + {t("parts_orders.labels.parts_order")} + {t("parts_orders.labels.sublet_order")} + + + + {t("parts_orders.labels.inthisorder")} + + {(fields, { add, remove, move }) => { + return ( +
+ {fields.map((field, index) => ( + +
+ + - - - + + + + + + + + + + + + { + // + // + // + } + - - - {job && job.special_coverage_policy && ( - - - - {t("jobs.labels.specialcoveragepolicy")} - - - )} - {!isReturn && ( - - - - )} - {OEConnection.treatment === "on" && !isReturn && ( - - - - )} - - - - - {t("parts_orders.labels.parts_order")} - - - {t("parts_orders.labels.sublet_order")} - - - - - - {t("parts_orders.labels.inthisorder")} - - - {(fields, {add, remove, move}) => { - return ( -
- {fields.map((field, index) => ( - -
- - - - - - - - - - - - - - { - // - // - // - } - - - - - - } - /> - - {isReturn && ( - - - - )} - - -
- { - remove(field.name); - }} - /> -
- -
-
-
- ))} -
- ); - }} -
- - {t("parts_orders.fields.comments")} - - e.preventDefault()} - > - - - - - } - > - - - - - {() => { - const is_quote = form.getFieldValue("is_quote"); - if (is_quote) setSendType("oec"); - return ( - setSendType(e.target.value)} + } + ]} + > + + + + } /> + + {isReturn && ( + - - {t("general.labels.none")} - - - {t("parts_orders.labels.email")} - - - {t("parts_orders.labels.print")} - - {OEConnection.treatment === "on" && !isReturn && ( - {t("parts_orders.labels.oec")} - )} - - ); - }} - -
- ); + +
+ )} + + +
+ { + remove(field.name); + }} + /> +
+ +
+
+ + ))} +
+ ); + }} + + + {t("parts_orders.fields.comments")} + + e.preventDefault()}> + + + + + } + > + + + + + {() => { + const is_quote = form.getFieldValue("is_quote"); + if (is_quote) setSendType("oec"); + return ( + setSendType(e.target.value)}> + + {t("general.labels.none")} + + + {t("parts_orders.labels.email")} + + + {t("parts_orders.labels.print")} + + {OEConnection.treatment === "on" && !isReturn && ( + {t("parts_orders.labels.oec")} + )} + + ); + }} + +
+ ); } diff --git a/client/src/components/parts-order-modal/parts-order-modal.container.jsx b/client/src/components/parts-order-modal/parts-order-modal.container.jsx index 5f9b41180..2d8f8c77c 100644 --- a/client/src/components/parts-order-modal/parts-order-modal.container.jsx +++ b/client/src/components/parts-order-modal/parts-order-modal.container.jsx @@ -1,398 +1,354 @@ import { useMutation, useQuery, useApolloClient } from "@apollo/client"; import { Form, Modal, notification } from "antd"; -import dayjs from '../../utils/day'; +import dayjs from "../../utils/day"; import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { logImEXEvent, auth } from "../../firebase/firebase.utils"; import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries"; -import { - INSERT_NEW_PARTS_ORDERS, - QUERY_PARTS_ORDER_OEC, -} from "../../graphql/parts-orders.queries"; +import { INSERT_NEW_PARTS_ORDERS, QUERY_PARTS_ORDER_OEC } from "../../graphql/parts-orders.queries"; import { QUERY_ALL_VENDORS_FOR_ORDER } from "../../graphql/vendors.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; import { setEmailOptions } from "../../redux/email/email.actions"; -import { - setModalContext, - toggleModalVisible, -} from "../../redux/modals/modals.actions"; +import { setModalContext, toggleModalVisible } from "../../redux/modals/modals.actions"; import { selectPartsOrder } from "../../redux/modals/modals.selectors"; -import { - selectBodyshop, - selectCurrentUser, -} from "../../redux/user/user.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import PartsOrderModalComponent from "./parts-order-modal.component"; import axios from "axios"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; import _ from "lodash"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - bodyshop: selectBodyshop, - partsOrderModal: selectPartsOrder, + currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + partsOrderModal: selectPartsOrder }); const mapDispatchToProps = (dispatch) => ({ - setEmailOptions: (e) => dispatch(setEmailOptions(e)), - toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")), - setBillEnterContext: (context) => - dispatch(setModalContext({context: context, modal: "billEnter"})), - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + setEmailOptions: (e) => dispatch(setEmailOptions(e)), + toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")), + setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); export function PartsOrderModalContainer({ - partsOrderModal, - toggleModalVisible, - currentUser, - bodyshop, - setEmailOptions, - setBillEnterContext, - insertAuditTrail, - }) { - const {t} = useTranslation(); - const client = useApolloClient(); + partsOrderModal, + toggleModalVisible, + currentUser, + bodyshop, + setEmailOptions, + setBillEnterContext, + insertAuditTrail +}) { + const { t } = useTranslation(); + const client = useApolloClient(); - const {treatments: {OEConnection_PriceChange}} = useSplitTreatments({ - attributes: {}, - names: ["OEConnection_PriceChange"], - splitKey: bodyshop.imexshopid, - }); + const { + treatments: { OEConnection_PriceChange } + } = useSplitTreatments({ + attributes: {}, + names: ["OEConnection_PriceChange"], + splitKey: bodyshop.imexshopid + }); - const {open, context, actions} = partsOrderModal; - const { - jobId, - linesToOrder, - isReturn, - vendorId, - returnFromBill, - invoiceNumber, - job, - } = context; + const { open, context, actions } = partsOrderModal; + const { jobId, linesToOrder, isReturn, vendorId, returnFromBill, invoiceNumber, job } = context; - const {refetch} = actions; - const [form] = Form.useForm(); - const [saving, setSaving] = useState(false); - const sendTypeState = useState("e"); - const sendType = sendTypeState[0]; + const { refetch } = actions; + const [form] = Form.useForm(); + const [saving, setSaving] = useState(false); + const sendTypeState = useState("e"); + const sendType = sendTypeState[0]; - const {loading, error, data} = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, { - skip: !open, - variables: {jobId: jobId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const { loading, error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, { + skip: !open, + variables: { jobId: jobId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS); - const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS); - const [updateJob] = useMutation(UPDATE_JOB); + const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS); + const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS); + const [updateJob] = useMutation(UPDATE_JOB); - const handleFinish = async ({ - order_type, - removefrompartsqueue, - is_quote, - ...values - }) => { - logImEXEvent("parts_order_insert"); - setSaving(true); - let insertResult; + const handleFinish = async ({ order_type, removefrompartsqueue, is_quote, ...values }) => { + logImEXEvent("parts_order_insert"); + setSaving(true); + let insertResult; - insertResult = await insertPartOrder({ - variables: { - po: [ - { - ...values, - order_date: dayjs().format("YYYY-MM-DD"), - orderedby: currentUser.email, - jobid: jobId, - user_email: currentUser.email, - return: isReturn, - status: is_quote - ? bodyshop.md_order_statuses.default_quote || "Quote" - : bodyshop.md_order_statuses.default_ordered || "Ordered*", - }, - ], - }, - refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"], - }); - if (!!insertResult.errors) { - notification["error"]({ - message: t("parts_orders.errors.creating"), - description: JSON.stringify(insertResult.errors), - }); - return; - } - notification["success"]({ - message: values.isReturn - ? t("parts_orders.successes.return_created") - : t("parts_orders.successes.created"), - }); - insertAuditTrail({ + insertResult = await insertPartOrder({ + variables: { + po: [ + { + ...values, + order_date: dayjs().format("YYYY-MM-DD"), + orderedby: currentUser.email, jobid: jobId, - operation: isReturn - ? AuditTrailMapping.jobspartsreturn( - insertResult.data.insert_parts_orders.returning[0].order_number - ) - : AuditTrailMapping.jobspartsorder( - insertResult.data.insert_parts_orders.returning[0].order_number - ), - type: isReturn ? "jobspartsreturn" : "jobspartsorder", + user_email: currentUser.email, + return: isReturn, + status: is_quote + ? bodyshop.md_order_statuses.default_quote || "Quote" + : bodyshop.md_order_statuses.default_ordered || "Ordered*" + } + ] + }, + refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"] + }); + if (!!insertResult.errors) { + notification["error"]({ + message: t("parts_orders.errors.creating"), + description: JSON.stringify(insertResult.errors) + }); + return; + } + notification["success"]({ + message: values.isReturn ? t("parts_orders.successes.return_created") : t("parts_orders.successes.created") + }); + insertAuditTrail({ + jobid: jobId, + operation: isReturn + ? AuditTrailMapping.jobspartsreturn(insertResult.data.insert_parts_orders.returning[0].order_number) + : AuditTrailMapping.jobspartsorder(insertResult.data.insert_parts_orders.returning[0].order_number), + type: isReturn ? "jobspartsreturn" : "jobspartsorder" }); - const jobLinesResult = await updateJobLines({ - variables: { - ids: values.parts_order_lines.data - .filter((item) => item.job_line_id) - .map((item) => item.job_line_id), - status: isReturn - ? bodyshop.md_order_statuses.default_returned || "Returned*" - : is_quote - ? bodyshop.md_order_statuses.default_quote || "Quote" - : bodyshop.md_order_statuses.default_ordered || "Ordered*", - }, - }); + const jobLinesResult = await updateJobLines({ + variables: { + ids: values.parts_order_lines.data.filter((item) => item.job_line_id).map((item) => item.job_line_id), + status: isReturn + ? bodyshop.md_order_statuses.default_returned || "Returned*" + : is_quote + ? bodyshop.md_order_statuses.default_quote || "Quote" + : bodyshop.md_order_statuses.default_ordered || "Ordered*" + } + }); - if (!isReturn && removefrompartsqueue) { - await updateJob({ - variables: { - jobId: jobId, - job: { - queued_for_parts: false, - }, - }, - }); + if (!isReturn && removefrompartsqueue) { + await updateJob({ + variables: { + jobId: jobId, + job: { + queued_for_parts: false + } } + }); + } - if (!!jobLinesResult.errors) { - notification["error"]({ - message: t("parts_orders.errors.creating"), - description: JSON.stringify(jobLinesResult.errors), - }); - } + if (!!jobLinesResult.errors) { + notification["error"]({ + message: t("parts_orders.errors.creating"), + description: JSON.stringify(jobLinesResult.errors) + }); + } - if (values.vendorid === bodyshop.inhousevendorid) { - setBillEnterContext({ - actions: {refetch: refetch}, - context: { - disableInvNumber: true, - job: {id: jobId}, - bill: { - vendorid: bodyshop.inhousevendorid, - invoice_number: "ih", - isinhouse: true, - date: new dayjs(), - total: 0, - billlines: values.parts_order_lines.data.map((p) => { - return { - joblineid: p.job_line_id, - actual_price: p.act_price, - actual_cost: 0, //p.act_price, - line_desc: p.line_desc, - line_remarks: p.line_remarks, - part_type: p.part_type, - quantity: p.quantity || 1, - applicable_taxes: { - local: false, - state: false, - federal: false, - }, - }; - }), - }, - }, - }); - toggleModalVisible(); - return; - } - - if (refetch) refetch(); - - const Templates = TemplateList("partsorder", context); - - if (sendType === "e") { - const matchingVendor = data.vendors.filter( - (item) => item.id === values.vendorid - )[0]; - - let vendorEmails = - matchingVendor && - matchingVendor.email && - matchingVendor.email.split(RegExp("[;,]")); - - GenerateDocument( - { - name: isReturn - ? Templates.parts_return_slip.key - : order_type === "parts_order" - ? Templates.parts_order.key - : Templates.sublet_order.key, - variables: { - id: insertResult.data.insert_parts_orders.returning[0].id, - }, - }, - { - to: matchingVendor ? vendorEmails : null, - replyTo: bodyshop.email, - subject: isReturn - ? Templates.parts_return_slip.subject - : order_type === "parts_order" - ? Templates.parts_order.subject - : Templates.sublet_order.subject, - }, - "e", - jobId - ); - } else if (sendType === "p") { - GenerateDocument( - { - name: isReturn - ? Templates.parts_return_slip.key - : order_type === "parts_order" - ? Templates.parts_order.key - : Templates.sublet_order.key, - variables: { - id: insertResult.data.insert_parts_orders.returning[0].id, - }, - }, - {}, - "p" - ); - } else if (sendType === "oec") { - //Send to Partner OEC. - try { - const partsOrder = await client.query({ - query: QUERY_PARTS_ORDER_OEC, - variables: { - id: insertResult.data.insert_parts_orders.returning[0].id, - }, - }); - let po; - //Massage the data based on the split. Should they be able to overwrite OEC pricing? - if (OEConnection_PriceChange.treatment === "on") { - //Set the flag to include the override. - po = _.cloneDeep(partsOrder.data.parts_orders_by_pk); - po.parts_order_lines.forEach((pol) => { - pol.priceChange = true; - }); + if (values.vendorid === bodyshop.inhousevendorid) { + setBillEnterContext({ + actions: { refetch: refetch }, + context: { + disableInvNumber: true, + job: { id: jobId }, + bill: { + vendorid: bodyshop.inhousevendorid, + invoice_number: "ih", + isinhouse: true, + date: new dayjs(), + total: 0, + billlines: values.parts_order_lines.data.map((p) => { + return { + joblineid: p.job_line_id, + actual_price: p.act_price, + actual_cost: 0, //p.act_price, + line_desc: p.line_desc, + line_remarks: p.line_remarks, + part_type: p.part_type, + quantity: p.quantity || 1, + applicable_taxes: { + local: false, + state: false, + federal: false } - - const oecResponse = await axios.post( - "http://localhost:1337/oec/", - - po || partsOrder.data.parts_orders_by_pk, - { - headers: { - Authorization: `Bearer ${await auth.currentUser.getIdToken()}`, - }, - } - ); - - if (oecResponse.data && oecResponse.data.success === false) { - notification.open({ - type: "error", - message: t("parts_orders.errors.oec", { - error: oecResponse.data.error, - }), - }); - } - } catch (error) { - console.log("Error OEC.", error); - notification["error"]({ - message: t("parts_orders.errors.oec", { - error: JSON.stringify(error.message), - }), - }); - setSaving(false); - return; - } + }; + }) + } } - setSaving(false); - toggleModalVisible(); - }; + }); + toggleModalVisible(); + return; + } - const initialValues = { - jobid: jobId, - return: isReturn, - deliver_by: isReturn ? dayjs(new Date()) : null, - vendorid: vendorId, - returnfrombill: returnFromBill, + if (refetch) refetch(); - parts_order_lines: { - data: linesToOrder - ? linesToOrder.reduce((acc, value) => { - acc.push({ - line_desc: value.line_desc, - oem_partno: value.oem_partno, - db_price: value.db_price, - act_price: value.act_price, - cost: value.cost, - quantity: value.part_qty, - job_line_id: isReturn ? value.joblineid : value.id, - part_type: value.part_type, - ...(isReturn && {cm_received: false}), - }); - return acc; - }, []) - : [], + const Templates = TemplateList("partsorder", context); + + if (sendType === "e") { + const matchingVendor = data.vendors.filter((item) => item.id === values.vendorid)[0]; + + let vendorEmails = matchingVendor && matchingVendor.email && matchingVendor.email.split(RegExp("[;,]")); + + GenerateDocument( + { + name: isReturn + ? Templates.parts_return_slip.key + : order_type === "parts_order" + ? Templates.parts_order.key + : Templates.sublet_order.key, + variables: { + id: insertResult.data.insert_parts_orders.returning[0].id + } }, - }; - - useEffect(() => { - if (open && !!linesToOrder) { - form.resetFields(); + { + to: matchingVendor ? vendorEmails : null, + replyTo: bodyshop.email, + subject: isReturn + ? Templates.parts_return_slip.subject + : order_type === "parts_order" + ? Templates.parts_order.subject + : Templates.sublet_order.subject + }, + "e", + jobId + ); + } else if (sendType === "p") { + GenerateDocument( + { + name: isReturn + ? Templates.parts_return_slip.key + : order_type === "parts_order" + ? Templates.parts_order.key + : Templates.sublet_order.key, + variables: { + id: insertResult.data.insert_parts_orders.returning[0].id + } + }, + {}, + "p" + ); + } else if (sendType === "oec") { + //Send to Partner OEC. + try { + const partsOrder = await client.query({ + query: QUERY_PARTS_ORDER_OEC, + variables: { + id: insertResult.data.insert_parts_orders.returning[0].id + } + }); + let po; + //Massage the data based on the split. Should they be able to overwrite OEC pricing? + if (OEConnection_PriceChange.treatment === "on") { + //Set the flag to include the override. + po = _.cloneDeep(partsOrder.data.parts_orders_by_pk); + po.parts_order_lines.forEach((pol) => { + pol.priceChange = true; + }); } - }, [open, linesToOrder, form]); - return ( - toggleModalVisible()} - onOk={() => form.submit()} - okButtonProps={{loading: saving}} - cancelButtonProps={{loading: saving}} - destroyOnClose - width="75%" - forceRender - > - {error ? : null} -
- {loading ? ( - - ) : ( - - )} - -
- ); + } + ); + + if (oecResponse.data && oecResponse.data.success === false) { + notification.open({ + type: "error", + message: t("parts_orders.errors.oec", { + error: oecResponse.data.error + }) + }); + } + } catch (error) { + console.log("Error OEC.", error); + notification["error"]({ + message: t("parts_orders.errors.oec", { + error: JSON.stringify(error.message) + }) + }); + setSaving(false); + return; + } + } + setSaving(false); + toggleModalVisible(); + }; + + const initialValues = { + jobid: jobId, + return: isReturn, + deliver_by: isReturn ? dayjs(new Date()) : null, + vendorid: vendorId, + returnfrombill: returnFromBill, + + parts_order_lines: { + data: linesToOrder + ? linesToOrder.reduce((acc, value) => { + acc.push({ + line_desc: value.line_desc, + oem_partno: value.oem_partno, + db_price: value.db_price, + act_price: value.act_price, + cost: value.cost, + quantity: value.part_qty, + job_line_id: isReturn ? value.joblineid : value.id, + part_type: value.part_type, + ...(isReturn && { cm_received: false }) + }); + return acc; + }, []) + : [] + } + }; + + useEffect(() => { + if (open && !!linesToOrder) { + form.resetFields(); + } + }, [open, linesToOrder, form]); + + return ( + toggleModalVisible()} + onOk={() => form.submit()} + okButtonProps={{ loading: saving }} + cancelButtonProps={{ loading: saving }} + destroyOnClose + width="75%" + forceRender + > + {error ? : null} +
+ {loading ? ( + + ) : ( + + )} + +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(PartsOrderModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(PartsOrderModalContainer); diff --git a/client/src/components/parts-queue-card/parts-queue-card.component.jsx b/client/src/components/parts-queue-card/parts-queue-card.component.jsx index d4b988da7..189fa7c13 100644 --- a/client/src/components/parts-queue-card/parts-queue-card.component.jsx +++ b/client/src/components/parts-queue-card/parts-queue-card.component.jsx @@ -1,77 +1,65 @@ -import {useQuery} from "@apollo/client"; -import {Card, Divider, Drawer, Grid} from "antd"; +import { useQuery } from "@apollo/client"; +import { Card, Divider, Drawer, Grid } from "antd"; import queryString from "query-string"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {Link, useNavigate, useLocation} from "react-router-dom"; -import {QUERY_PARTS_QUEUE_CARD_DETAILS} from "../../graphql/jobs.queries"; +import { useTranslation } from "react-i18next"; +import { Link, useNavigate, useLocation } from "react-router-dom"; +import { QUERY_PARTS_QUEUE_CARD_DETAILS } from "../../graphql/jobs.queries"; import AlertComponent from "../alert/alert.component"; import JobsDetailHeader from "../jobs-detail-header/jobs-detail-header.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import PartsQueueJobLinesComponent from "./parts-queue-job-lines.component"; export default function PartsQueueDetailCard() { - const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) - .filter((screen) => !!screen[1]) - .slice(-1)[0]; + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) + .filter((screen) => !!screen[1]) + .slice(-1)[0]; - const bpoints = { - xs: "100%", - sm: "100%", - md: "100%", - lg: "75%", - xl: "75%", - xxl: "60%", - }; - const drawerPercentage = selectedBreakpoint - ? bpoints[selectedBreakpoint[0]] - : "100%"; + const bpoints = { + xs: "100%", + sm: "100%", + md: "100%", + lg: "75%", + xl: "75%", + xxl: "60%" + }; + const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%"; - const searchParams = queryString.parse(useLocation().search); - const {selected} = searchParams; - const history = useNavigate(); - const {loading, error, data} = useQuery(QUERY_PARTS_QUEUE_CARD_DETAILS, { - variables: {id: selected}, - skip: !selected, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", + const searchParams = queryString.parse(useLocation().search); + const { selected } = searchParams; + const history = useNavigate(); + const { loading, error, data } = useQuery(QUERY_PARTS_QUEUE_CARD_DETAILS, { + variables: { id: selected }, + skip: !selected, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + + const { t } = useTranslation(); + const handleDrawerClose = () => { + delete searchParams.selected; + history({ + search: queryString.stringify({ + ...searchParams + }) }); + }; - const {t} = useTranslation(); - const handleDrawerClose = () => { - delete searchParams.selected; - history({ - search: queryString.stringify({ - ...searchParams, - }), - }); - }; - - return ( - + {loading ? : null} + {error ? : null} + {data ? ( + {data.jobs_by_pk.ro_number || t("general.labels.na")} + } > - {loading ? : null} - {error ? : null} - {data ? ( - - {data.jobs_by_pk.ro_number || t("general.labels.na")} - - } - > - - - - - ) : null} - - ); + + + + + ) : null} + + ); } diff --git a/client/src/components/parts-queue-card/parts-queue-job-lines.component.jsx b/client/src/components/parts-queue-card/parts-queue-job-lines.component.jsx index 2a0fa82d6..cadd1f3c4 100644 --- a/client/src/components/parts-queue-card/parts-queue-job-lines.component.jsx +++ b/client/src/components/parts-queue-card/parts-queue-job-lines.component.jsx @@ -1,209 +1,181 @@ -import {Card, Table} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { Card, Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {onlyUnique} from "../../utils/arrayHelper"; -import {alphaSort} from "../../utils/sorters"; +import { onlyUnique } from "../../utils/arrayHelper"; +import { alphaSort } from "../../utils/sorters"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - jobRO: selectJobReadOnly, + bodyshop: selectBodyshop, + jobRO: selectJobReadOnly }); const mapDispatchToProps = (dispatch) => ({}); -export function PartsQueueJobLinesComponent({jobRO, loading, jobLines}) { - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {}, - }); - const {t} = useTranslation(); +export function PartsQueueJobLinesComponent({ jobRO, loading, jobLines }) { + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: {} + }); + const { t } = useTranslation(); - const columns = [ + const columns = [ + { + title: "#", + dataIndex: "line_no", + key: "line_no", + sorter: (a, b) => a.line_no - b.line_no, + sortOrder: state.sortedInfo.columnKey === "line_no" && state.sortedInfo.order + }, + { + title: t("joblines.fields.line_desc"), + dataIndex: "line_desc", + key: "line_desc", + sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), + onCell: (record) => ({ + className: record.manual_line && "job-line-manual", + style: { + ...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {}) + } + }), + sortOrder: state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order, + ellipsis: true + }, + { + title: t("joblines.fields.oem_partno"), + dataIndex: "oem_partno", + key: "oem_partno", + sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno), + sortOrder: state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order, + ellipsis: true, + render: (text, record) => `${record.oem_partno || ""} ${record.alt_partno ? `(${record.alt_partno})` : ""}`.trim() + }, + { + title: t("joblines.fields.part_type"), + dataIndex: "part_type", + key: "part_type", + filteredValue: state.filteredInfo.part_type || null, + sorter: (a, b) => alphaSort(a.part_type, b.part_type), + sortOrder: state.sortedInfo.columnKey === "part_type" && state.sortedInfo.order, + filters: [ { - title: "#", - dataIndex: "line_no", - key: "line_no", - sorter: (a, b) => a.line_no - b.line_no, - sortOrder: - state.sortedInfo.columnKey === "line_no" && state.sortedInfo.order, + text: t("jobs.labels.partsfilter"), + value: ["PAN", "PAC", "PAR", "PAL", "PAA", "PAM", "PAP", "PAS", "PASL", "PAG"] }, { - title: t("joblines.fields.line_desc"), - dataIndex: "line_desc", - key: "line_desc", - sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), - onCell: (record) => ({ - className: record.manual_line && "job-line-manual", - style: { - ...(record.critical ? {boxShadow: " -.5em 0 0 #FFC107"} : {}), - }, - }), - sortOrder: - state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order, - ellipsis: true, + text: t("joblines.fields.part_types.PAN"), + value: ["PAN"] }, { - title: t("joblines.fields.oem_partno"), - dataIndex: "oem_partno", - key: "oem_partno", - sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno), - sortOrder: - state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order, - ellipsis: true, - render: (text, record) => - `${record.oem_partno || ""} ${ - record.alt_partno ? `(${record.alt_partno})` : "" - }`.trim(), + text: t("joblines.fields.part_types.PAP"), + value: ["PAP"] }, { - title: t("joblines.fields.part_type"), - dataIndex: "part_type", - key: "part_type", - filteredValue: state.filteredInfo.part_type || null, - sorter: (a, b) => alphaSort(a.part_type, b.part_type), - sortOrder: - state.sortedInfo.columnKey === "part_type" && state.sortedInfo.order, - filters: [ - { - text: t("jobs.labels.partsfilter"), - value: [ - "PAN", - "PAC", - "PAR", - "PAL", - "PAA", - "PAM", - "PAP", - "PAS", - "PASL", - "PAG", - ], - }, - { - text: t("joblines.fields.part_types.PAN"), - value: ["PAN"], - }, - { - text: t("joblines.fields.part_types.PAP"), - value: ["PAP"], - }, - { - text: t("joblines.fields.part_types.PAL"), - value: ["PAL"], - }, - { - text: t("joblines.fields.part_types.PAA"), - value: ["PAA"], - }, - { - text: t("joblines.fields.part_types.PAG"), - value: ["PAG"], - }, - { - text: t("joblines.fields.part_types.PAS"), - value: ["PAS"], - }, - { - text: t("joblines.fields.part_types.PASL"), - value: ["PASL"], - }, - { - text: t("joblines.fields.part_types.PAC"), - value: ["PAC"], - }, - { - text: t("joblines.fields.part_types.PAR"), - value: ["PAR"], - }, - { - text: t("joblines.fields.part_types.PAM"), - value: ["PAM"], - }, - ], - onFilter: (value, record) => value.includes(record.part_type), - render: (text, record) => - record.part_type - ? t(`joblines.fields.part_types.${record.part_type}`) - : null, + text: t("joblines.fields.part_types.PAL"), + value: ["PAL"] }, { - title: t("joblines.fields.part_qty"), - dataIndex: "part_qty", - key: "part_qty", + text: t("joblines.fields.part_types.PAA"), + value: ["PAA"] }, { - title: t("joblines.fields.act_price"), - dataIndex: "act_price", - key: "act_price", - sorter: (a, b) => a.act_price - b.act_price, - sortOrder: - state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order, - ellipsis: true, - render: (text, record) => ( - - {record.db_ref === "900510" || record.db_ref === "900511" - ? record.prt_dsmk_m - : record.act_price} - - ), + text: t("joblines.fields.part_types.PAG"), + value: ["PAG"] }, { - title: t("joblines.fields.location"), - dataIndex: "location", - key: "location", + text: t("joblines.fields.part_types.PAS"), + value: ["PAS"] }, { - title: t("joblines.fields.status"), - dataIndex: "status", - key: "status", - sorter: (a, b) => alphaSort(a.status, b.status), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - filteredValue: state.filteredInfo.status || null, - filters: - (jobLines && - jobLines - .map((l) => l.status) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Status*", - value: [s], - }; - })) || - [], - onFilter: (value, record) => value.includes(record.status), + text: t("joblines.fields.part_types.PASL"), + value: ["PASL"] }, - ]; + { + text: t("joblines.fields.part_types.PAC"), + value: ["PAC"] + }, + { + text: t("joblines.fields.part_types.PAR"), + value: ["PAR"] + }, + { + text: t("joblines.fields.part_types.PAM"), + value: ["PAM"] + } + ], + onFilter: (value, record) => value.includes(record.part_type), + render: (text, record) => (record.part_type ? t(`joblines.fields.part_types.${record.part_type}`) : null) + }, + { + title: t("joblines.fields.part_qty"), + dataIndex: "part_qty", + key: "part_qty" + }, + { + title: t("joblines.fields.act_price"), + dataIndex: "act_price", + key: "act_price", + sorter: (a, b) => a.act_price - b.act_price, + sortOrder: state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order, + ellipsis: true, + render: (text, record) => ( + + {record.db_ref === "900510" || record.db_ref === "900511" ? record.prt_dsmk_m : record.act_price} + + ) + }, + { + title: t("joblines.fields.location"), + dataIndex: "location", + key: "location" + }, + { + title: t("joblines.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => alphaSort(a.status, b.status), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + filteredValue: state.filteredInfo.status || null, + filters: + (jobLines && + jobLines + .map((l) => l.status) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Status*", + value: [s] + }; + })) || + [], + onFilter: (value, record) => value.includes(record.status) + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setState((state) => ({ - ...state, - filteredInfo: filters, - sortedInfo: sorter, - })); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState((state) => ({ + ...state, + filteredInfo: filters, + sortedInfo: sorter + })); + }; - return ( - -
- - ); + return ( + +
+ + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(PartsQueueJobLinesComponent); +export default connect(mapStateToProps, mapDispatchToProps)(PartsQueueJobLinesComponent); diff --git a/client/src/components/parts-queue-list/parts-queue.list.component.jsx b/client/src/components/parts-queue-list/parts-queue.list.component.jsx index 3b0aa9087..e3363409a 100644 --- a/client/src/components/parts-queue-list/parts-queue.list.component.jsx +++ b/client/src/components/parts-queue-list/parts-queue.list.component.jsx @@ -1,368 +1,318 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {useQuery} from "@apollo/client"; -import {Button, Card, Input, Space, Table} from "antd"; +import { SyncOutlined } from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import { Button, Card, Input, Space, Table } from "antd"; import _ from "lodash"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {QUERY_PARTS_QUEUE} from "../../graphql/jobs.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {DateTimeFormatter, TimeAgoFormatter} from "../../utils/DateFormatter"; -import {onlyUnique} from "../../utils/arrayHelper"; -import {pageLimit} from "../../utils/config"; -import {alphaSort, dateSort} from "../../utils/sorters"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { QUERY_PARTS_QUEUE } from "../../graphql/jobs.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { DateTimeFormatter, TimeAgoFormatter } from "../../utils/DateFormatter"; +import { onlyUnique } from "../../utils/arrayHelper"; +import { pageLimit } from "../../utils/config"; +import { alphaSort, dateSort } from "../../utils/sorters"; import useLocalStorage from "../../utils/useLocalStorage"; import AlertComponent from "../alert/alert.component"; import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component"; import JobRemoveFromPartsQueue from "../job-remove-from-parst-queue/job-remove-from-parts-queue.component"; -import OwnerNameDisplay, {OwnerNameDisplayFunction,} from "../owner-name-display/owner-name-display.component"; +import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -export function PartsQueueListComponent({bodyshop}) { - const searchParams = queryString.parse(useLocation().search); - const { - selected, - sortcolumn, - sortorder, - statusFilters - } = searchParams; - const history = useNavigate(); - const [filter, setFilter] = useLocalStorage("filter_parts_queue", null); +export function PartsQueueListComponent({ bodyshop }) { + const searchParams = queryString.parse(useLocation().search); + const { selected, sortcolumn, sortorder, statusFilters } = searchParams; + const history = useNavigate(); + const [filter, setFilter] = useLocalStorage("filter_parts_queue", null); - const {loading, error, data, refetch} = useQuery(QUERY_PARTS_QUEUE, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - variables: { + const { loading, error, data, refetch } = useQuery(QUERY_PARTS_QUEUE, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + statuses: (statusFilters && JSON.parse(statusFilters)) || + bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"] + } + }); - statuses: (statusFilters && JSON.parse(statusFilters)) || - bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], + const { t } = useTranslation(); + const [searchText, setSearchText] = useState(""); - }, - }); + if (error) return ; - const {t} = useTranslation(); - const [searchText, setSearchText] = useState(""); + const jobs = data + ? searchText === "" + ? data.jobs + : data.jobs.filter( + (j) => + (j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.plate_no || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase()) + ) + : []; - if (error) return ; + const handleTableChange = (pagination, filters, sorter) => { + // searchParams.page = pagination.current; + searchParams.sortcolumn = sorter.columnKey; + searchParams.sortorder = sorter.order; - const jobs = data - ? searchText === "" - ? data.jobs - : data.jobs.filter( - (j) => - (j.ro_number || "") - .toString() - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_co_nm || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_fn || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_ln || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.plate_no || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_model_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_make_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) - ) - : []; + if (filters.status) { + searchParams.statusFilters = JSON.stringify(_.flattenDeep(filters.status)); + } else { + delete searchParams.statusFilters; + } + setFilter(filters); + history({ search: queryString.stringify(searchParams) }); + }; - const handleTableChange = (pagination, filters, sorter) => { - // searchParams.page = pagination.current; - searchParams.sortcolumn = sorter.columnKey; - searchParams.sortorder = sorter.order; + const handleOnRowClick = (record) => { + if (record?.id) { + history({ + search: queryString.stringify({ + ...searchParams, + selected: record.id + }) + }); + } + }; + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: sortcolumn === "ro_number" && sortorder, - if (filters.status) { - searchParams.statusFilters = JSON.stringify( - _.flattenDeep(filters.status) - ); - } else { - delete searchParams.statusFilters; - } - setFilter(filters); - history({search: queryString.stringify(searchParams)}); - }; - - const handleOnRowClick = (record) => { - if (record?.id) { - history({ - search: queryString.stringify({ - ...searchParams, - selected: record.id, - }), - }); - } - }; - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), - sortOrder: sortcolumn === "ro_number" && sortorder, - - render: (text, record) => ( - - {record.ro_number || t("general.labels.na")} - - ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "ownr_ln", - key: "ownr_ln", - sorter: (a, b) => - alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), - sortOrder: sortcolumn === "ownr_ln" && sortorder, - render: (text, record) => { - return record.ownerid ? ( - - - - ) : ( - - + render: (text, record) => ( + {record.ro_number || t("general.labels.na")} + ) + }, + { + title: t("jobs.fields.owner"), + dataIndex: "ownr_ln", + key: "ownr_ln", + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), + sortOrder: sortcolumn === "ownr_ln" && sortorder, + render: (text, record) => { + return record.ownerid ? ( + + + + ) : ( + + - ); - }, - }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - ellipsis: true, - sorter: (a, b) => - alphaSort( - `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${ - a.v_model_desc || "" - }`, - `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` - ), - sortOrder: sortcolumn === "vehicle" && sortorder, - render: (text, record) => { - return record.vehicleid ? ( - - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - - ) : ( - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - ); - }, - }, - { - title: t("jobs.fields.ins_co_nm_short"), - dataIndex: "ins_co_nm", - key: "ins_co_nm", - ellipsis: true, - sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), - sortOrder: sortcolumn === "ins_co_nm" && sortorder, - filteredValue: filter?.ins_co_nm || null, - filters: - (jobs && - jobs - .map((j) => j.ins_co_nm) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Ins. Co.*", - value: [s], - }; - }) - .sort((a, b) => alphaSort(a.text, b.text))) || - [], - onFilter: (value, record) => value.includes(record.ins_co_nm), - }, - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", - sorter: (a, b) => alphaSort(a.status, b.status), - sortOrder: sortcolumn === "status" && sortorder, - filteredValue: statusFilters ? JSON.parse(statusFilters) : null, - filters: - bodyshop.md_ro_statuses.active_statuses.map((s) => { - return { - text: s || "No Status*", - value: [s], - }; - }) || [], - render: (text, record) => { - return record.status || t("general.labels.na"); - }, - }, + ); + } + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + sorter: (a, b) => + alphaSort( + `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`, + `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` + ), + sortOrder: sortcolumn === "vehicle" && sortorder, + render: (text, record) => { + return record.vehicleid ? ( + + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + + ) : ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + ); + } + }, + { + title: t("jobs.fields.ins_co_nm_short"), + dataIndex: "ins_co_nm", + key: "ins_co_nm", + ellipsis: true, + sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), + sortOrder: sortcolumn === "ins_co_nm" && sortorder, + filteredValue: filter?.ins_co_nm || null, + filters: + (jobs && + jobs + .map((j) => j.ins_co_nm) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Ins. Co.*", + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.ins_co_nm) + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => alphaSort(a.status, b.status), + sortOrder: sortcolumn === "status" && sortorder, + filteredValue: statusFilters ? JSON.parse(statusFilters) : null, + filters: + bodyshop.md_ro_statuses.active_statuses.map((s) => { + return { + text: s || "No Status*", + value: [s] + }; + }) || [], + render: (text, record) => { + return record.status || t("general.labels.na"); + } + }, - { - title: t("jobs.fields.scheduled_in"), - dataIndex: "scheduled_in", - key: "scheduled_in", - ellipsis: true, - sorter: (a, b) => dateSort(a.scheduled_in, b.scheduled_in), - sortOrder: sortcolumn === "scheduled_in" && sortorder, - render: (text, record) => ( - {record.scheduled_in} - ), - }, - { - title: t("jobs.fields.scheduled_completion"), - dataIndex: "scheduled_completion", - key: "scheduled_completion", - ellipsis: true, - sorter: (a, b) => - dateSort(a.scheduled_completion, b.scheduled_completion), - sortOrder: sortcolumn === "scheduled_completion" && sortorder, - render: (text, record) => ( - {record.scheduled_completion} - ), - }, - // { - // title: t("vehicles.fields.plate_no"), - // dataIndex: "plate_no", - // key: "plate_no", - // sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), - // sortOrder: sortcolumn === "plate_no" && sortorder, - // render: (text, record) => { - // return record.plate_no ? record.plate_no : ""; - // }, + { + title: t("jobs.fields.scheduled_in"), + dataIndex: "scheduled_in", + key: "scheduled_in", + ellipsis: true, + sorter: (a, b) => dateSort(a.scheduled_in, b.scheduled_in), + sortOrder: sortcolumn === "scheduled_in" && sortorder, + render: (text, record) => {record.scheduled_in} + }, + { + title: t("jobs.fields.scheduled_completion"), + dataIndex: "scheduled_completion", + key: "scheduled_completion", + ellipsis: true, + sorter: (a, b) => dateSort(a.scheduled_completion, b.scheduled_completion), + sortOrder: sortcolumn === "scheduled_completion" && sortorder, + render: (text, record) => {record.scheduled_completion} + }, + // { + // title: t("vehicles.fields.plate_no"), + // dataIndex: "plate_no", + // key: "plate_no", + // sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), + // sortOrder: sortcolumn === "plate_no" && sortorder, + // render: (text, record) => { + // return record.plate_no ? record.plate_no : ""; + // }, - //}, - // { - // title: t("jobs.fields.clm_total"), - // dataIndex: "clm_total", - // key: "clm_total", - // sorter: (a, b) => a.clm_total - b.clm_total, - // sortOrder: sortcolumn === "clm_total" && sortorder, - // render: (text, record) => { - // return record.clm_total ? ( - // {record.clm_total} - // ) : ( - // t("general.labels.unknown") - // ); - // }, - // }, + //}, + // { + // title: t("jobs.fields.clm_total"), + // dataIndex: "clm_total", + // key: "clm_total", + // sorter: (a, b) => a.clm_total - b.clm_total, + // sortOrder: sortcolumn === "clm_total" && sortorder, + // render: (text, record) => { + // return record.clm_total ? ( + // {record.clm_total} + // ) : ( + // t("general.labels.unknown") + // ); + // }, + // }, + { + title: t("jobs.fields.updated_at"), + dataIndex: "updated_at", + key: "updated_at", + sorter: (a, b) => dateSort(a.updated_at, b.updated_at), + sortOrder: sortcolumn === "updated_at" && sortorder, + render: (text, record) => {record.updated_at} + }, + { + title: t("jobs.fields.partsstatus"), + dataIndex: "partsstatus", + key: "partsstatus", + render: (text, record) => + }, + { + title: t("jobs.fields.comment"), + dataIndex: "comment", + key: "comment", + render: (text, record) => + }, + { + title: t("jobs.fields.queued_for_parts"), + dataIndex: "queued_for_parts", + key: "queued_for_parts", + sorter: (a, b) => a.queued_for_parts - b.queued_for_parts, + sortOrder: sortcolumn === "queued_for_parts" && sortorder, + filteredValue: filter?.queued_for_parts || null, + filters: [ { - title: t("jobs.fields.updated_at"), - dataIndex: "updated_at", - key: "updated_at", - sorter: (a, b) => dateSort(a.updated_at, b.updated_at), - sortOrder: sortcolumn === "updated_at" && sortorder, - render: (text, record) => ( - {record.updated_at} - ), + text: "Queued", + value: true }, { - title: t("jobs.fields.partsstatus"), - dataIndex: "partsstatus", - key: "partsstatus", - render: (text, record) => ( - - ), - }, - { - title: t("jobs.fields.comment"), - dataIndex: "comment", - key: "comment", - render: (text, record) => , - }, - { - title: t("jobs.fields.queued_for_parts"), - dataIndex: "queued_for_parts", - key: "queued_for_parts", - sorter: (a, b) => a.queued_for_parts - b.queued_for_parts, - sortOrder: sortcolumn === "queued_for_parts" && sortorder, - filteredValue: filter?.queued_for_parts || null, - filters: [ - { - text: "Queued", - value: true, - }, - { - text: "Unqueued", - value: false, - }, - ], - onFilter: (value, record) => record.queued_for_parts === value, - render: (text, record) => ( - - ), - }, - ]; + text: "Unqueued", + value: false + } + ], + onFilter: (value, record) => record.queued_for_parts === value, + render: (text, record) => + } + ]; - return ( - - - { - setSearchText(e.target.value); - }} - value={searchText} - enterButton - /> - + return ( + + + { + setSearchText(e.target.value); + }} + value={searchText} + enterButton + /> + + } + > +
{ + handleOnRowClick(record); + }, + selectedRowKeys: [selected], + type: "radio" + }} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + handleOnRowClick(record); } - > -
{ - handleOnRowClick(record); - }, - selectedRowKeys: [selected], - type: "radio", - }} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - handleOnRowClick(record); - }, - }; - }} - /> - - ); + }; + }} + /> + + ); } export default connect(mapStateToProps, null)(PartsQueueListComponent); diff --git a/client/src/components/parts-receive-modal/parts-receive-modal.component.jsx b/client/src/components/parts-receive-modal/parts-receive-modal.component.jsx index 5b9af6611..144709832 100644 --- a/client/src/components/parts-receive-modal/parts-receive-modal.component.jsx +++ b/client/src/components/parts-receive-modal/parts-receive-modal.component.jsx @@ -1,137 +1,121 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {Form, Input, InputNumber, Select, Typography} from "antd"; +import { DeleteFilled } from "@ant-design/icons"; +import { Form, Input, InputNumber, Select, Typography } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); export default connect(mapStateToProps, null)(PartsReceiveModalComponent); -export function PartsReceiveModalComponent({bodyshop, form}) { - const {t} = useTranslation(); +export function PartsReceiveModalComponent({ bodyshop, form }) { + const { t } = useTranslation(); - return ( -
- - - { + form.setFieldsValue({ + partsorderlines: form.getFieldValue("partsorderlines").map((l) => { + return { ...l, location: value }; + }) + }); + }} + > + {bodyshop.md_parts_locations.map((loc, idx) => ( + + {loc} + + ))} + + + + {t("parts_orders.labels.inthisorder")} + + {(fields, { add, remove, move }) => { + return ( +
+ {fields.map((field, index) => ( + +
+ + + + + + + + + + + + + + + + + + + ))} + + + + + + + { + remove(field.name); + }} + /> + +
- - - {t("parts_orders.labels.inthisorder")} - - - {(fields, {add, remove, move}) => { - return ( -
- {fields.map((field, index) => ( - -
- - - - - - - - - - - - - - - - - - - - - - - - { - remove(field.name); - }} - /> - -
-
- ))} -
- ); - }} -
-
- ); + ))} +
+ ); + }} + + + ); } diff --git a/client/src/components/parts-receive-modal/parts-receive-modal.container.jsx b/client/src/components/parts-receive-modal/parts-receive-modal.container.jsx index 8f989d670..50ef34625 100644 --- a/client/src/components/parts-receive-modal/parts-receive-modal.container.jsx +++ b/client/src/components/parts-receive-modal/parts-receive-modal.container.jsx @@ -1,117 +1,101 @@ -import {useMutation} from "@apollo/client"; -import {Form, Modal, notification} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {RECEIVE_PARTS_LINE} from "../../graphql/jobs-lines.queries"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectPartsReceive} from "../../redux/modals/modals.selectors"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { useMutation } from "@apollo/client"; +import { Form, Modal, notification } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { RECEIVE_PARTS_LINE } from "../../graphql/jobs-lines.queries"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectPartsReceive } from "../../redux/modals/modals.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import PartsReceiveModalComponent from "./parts-receive-modal.component"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - bodyshop: selectBodyshop, - partsReceiveModal: selectPartsReceive, + currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + partsReceiveModal: selectPartsReceive }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("partsReceive")), + toggleModalVisible: () => dispatch(toggleModalVisible("partsReceive")) }); -export function PartsReceiveModalContainer({ - partsReceiveModal, - toggleModalVisible, - currentUser, - bodyshop, - }) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const {open, context, actions} = partsReceiveModal; - const {partsorderlines} = context; +export function PartsReceiveModalContainer({ partsReceiveModal, toggleModalVisible, currentUser, bodyshop }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const { open, context, actions } = partsReceiveModal; + const { partsorderlines } = context; - const {refetch} = actions; - const [form] = Form.useForm(); + const { refetch } = actions; + const [form] = Form.useForm(); - const [receivePartsLine] = useMutation(RECEIVE_PARTS_LINE); + const [receivePartsLine] = useMutation(RECEIVE_PARTS_LINE); - const handleFinish = async (values) => { - logImEXEvent("parts_order_receive"); - setLoading(true); - const result = await Promise.all( - values.partsorderlines.map((li) => { - return receivePartsLine({ - variables: { - lineId: li.joblineid, - line: { - location: li.location, - status: - bodyshop.md_order_statuses.default_received || "Received*", - }, - orderLineId: li.id, - orderLine: { - status: - bodyshop.md_order_statuses.default_received || "Received*", - }, - }, - }); - }) - ); - - result.forEach((jobLinesResult) => { - if (jobLinesResult.errors) { - notification["error"]({ - message: t("parts_orders.errors.creating"), - description: JSON.stringify(jobLinesResult.errors), - }); + const handleFinish = async (values) => { + logImEXEvent("parts_order_receive"); + setLoading(true); + const result = await Promise.all( + values.partsorderlines.map((li) => { + return receivePartsLine({ + variables: { + lineId: li.joblineid, + line: { + location: li.location, + status: bodyshop.md_order_statuses.default_received || "Received*" + }, + orderLineId: li.id, + orderLine: { + status: bodyshop.md_order_statuses.default_received || "Received*" } + } }); - - notification["success"]({ - message: t("parts_orders.successes.received"), - }); - setLoading(false); - if (refetch) refetch(); - toggleModalVisible(); - }; - - const initialValues = { - partsorderlines: partsorderlines, - }; - - useEffect(() => { - if (open && !!partsorderlines) { - form.resetFields(); - } - }, [open, partsorderlines, form]); - - return ( - toggleModalVisible()} - onOk={() => form.submit()} - okButtonProps={{loading: loading}} - destroyOnClose - forceRender - width="50%" - > -
- - -
+ }) ); + + result.forEach((jobLinesResult) => { + if (jobLinesResult.errors) { + notification["error"]({ + message: t("parts_orders.errors.creating"), + description: JSON.stringify(jobLinesResult.errors) + }); + } + }); + + notification["success"]({ + message: t("parts_orders.successes.received") + }); + setLoading(false); + if (refetch) refetch(); + toggleModalVisible(); + }; + + const initialValues = { + partsorderlines: partsorderlines + }; + + useEffect(() => { + if (open && !!partsorderlines) { + form.resetFields(); + } + }, [open, partsorderlines, form]); + + return ( + toggleModalVisible()} + onOk={() => form.submit()} + okButtonProps={{ loading: loading }} + destroyOnClose + forceRender + width="50%" + > +
+ + +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(PartsReceiveModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(PartsReceiveModalContainer); diff --git a/client/src/components/parts-status-pie/parts-status-pie.component.jsx b/client/src/components/parts-status-pie/parts-status-pie.component.jsx index d2200a8af..5e2f1e254 100644 --- a/client/src/components/parts-status-pie/parts-status-pie.component.jsx +++ b/client/src/components/parts-status-pie/parts-status-pie.component.jsx @@ -1,93 +1,85 @@ -import React, {useCallback, useMemo} from "react"; -import {connect} from "react-redux"; -import {Cell, Pie, PieChart, ResponsiveContainer} from "recharts"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {useTranslation} from "react-i18next"; +import React, { useCallback, useMemo } from "react"; +import { connect } from "react-redux"; +import { Cell, Pie, PieChart, ResponsiveContainer } from "recharts"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -export function PartsStatusPie({bodyshop, joblines_status}) { - const {t} = useTranslation(); - const pieColor = useCallback( - (status) => { - if (status === bodyshop.md_order_statuses.default_ordered) - return "lightgreen"; - if (status === bodyshop.md_order_statuses.default_bo) return "crimson"; +export function PartsStatusPie({ bodyshop, joblines_status }) { + const { t } = useTranslation(); + const pieColor = useCallback( + (status) => { + if (status === bodyshop.md_order_statuses.default_ordered) return "lightgreen"; + if (status === bodyshop.md_order_statuses.default_bo) return "crimson"; - if (status === bodyshop.md_order_statuses.default_canceled) - return "dodgerblue"; + if (status === bodyshop.md_order_statuses.default_canceled) return "dodgerblue"; - if (status === bodyshop.md_order_statuses.default_returned) - return "powderblue"; - if (status === bodyshop.md_order_statuses.default_received) - return "seagreen"; + if (status === bodyshop.md_order_statuses.default_returned) return "powderblue"; + if (status === bodyshop.md_order_statuses.default_received) return "seagreen"; - return "slategray"; - }, - [ - bodyshop.md_order_statuses.default_ordered, - bodyshop.md_order_statuses.default_bo, - bodyshop.md_order_statuses.default_canceled, - bodyshop.md_order_statuses.default_returned, - bodyshop.md_order_statuses.default_received, - ] - ); + return "slategray"; + }, + [ + bodyshop.md_order_statuses.default_ordered, + bodyshop.md_order_statuses.default_bo, + bodyshop.md_order_statuses.default_canceled, + bodyshop.md_order_statuses.default_returned, + bodyshop.md_order_statuses.default_received + ] + ); - const Calculatedata = useCallback( - (data) => { - if (data && data.length > 0) { - const statusMapping = {}; - data.map((i) => { - if (!statusMapping[i.status]) - statusMapping[i.status] = { - name: i.status || t("joblines.labels.nostatus"), - value: 0, - color: pieColor(i.status), - }; - statusMapping[i.status].value = - statusMapping[i.status].value + i.count; - return null; - }); - return Object.keys(statusMapping).map((key) => { - return statusMapping[key]; - }); - } else { - return []; - } - }, - [pieColor, t] - ); + const Calculatedata = useCallback( + (data) => { + if (data && data.length > 0) { + const statusMapping = {}; + data.map((i) => { + if (!statusMapping[i.status]) + statusMapping[i.status] = { + name: i.status || t("joblines.labels.nostatus"), + value: 0, + color: pieColor(i.status) + }; + statusMapping[i.status].value = statusMapping[i.status].value + i.count; + return null; + }); + return Object.keys(statusMapping).map((key) => { + return statusMapping[key]; + }); + } else { + return []; + } + }, + [pieColor, t] + ); - const memoizedData = useMemo( - () => Calculatedata(joblines_status), - [joblines_status, Calculatedata] - ); + const memoizedData = useMemo(() => Calculatedata(joblines_status), [joblines_status, Calculatedata]); - return ( -
- - - `${entry.name} - ${entry.value}`} - labelLine - > - {memoizedData.map((entry, index) => ( - - ))} - - - -
- ); + return ( +
+ + + `${entry.name} - ${entry.value}`} + labelLine + > + {memoizedData.map((entry, index) => ( + + ))} + + + +
+ ); } export default connect(mapStateToProps, null)(PartsStatusPie); diff --git a/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx b/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx index a44eb3d55..4194983bb 100644 --- a/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx +++ b/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx @@ -1,237 +1,209 @@ -import {useMutation} from "@apollo/client"; -import {Button, notification} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, notification } from "antd"; import axios from "axios"; import _ from "lodash"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {auth, logImEXEvent} from "../../firebase/firebase.utils"; -import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries"; -import {UPDATE_BILLS} from "../../graphql/bills.queries"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { auth, logImEXEvent } from "../../firebase/firebase.utils"; +import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { UPDATE_BILLS } from "../../graphql/bills.queries"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import client from "../../utils/GraphQLClient"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); function updateBillCache(items) { - client.cache.modify({ - id: "ROOT_QUERY", - fields: { - bills(existingJobs = []) { - return existingJobs.filter( - (billRef) => billRef.__ref.includes(items) === false - ); - }, - }, - }); + client.cache.modify({ + id: "ROOT_QUERY", + fields: { + bills(existingJobs = []) { + return existingJobs.filter((billRef) => billRef.__ref.includes(items) === false); + } + } + }); } export function PayableExportAll({ - bodyshop, - currentUser, - billids, - disabled, - loadingCallback, - completedCallback, - refetch, - }) { - const {t} = useTranslation(); - const [updateBill] = useMutation(UPDATE_BILLS); - const [loading, setLoading] = useState(false); - const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); + bodyshop, + currentUser, + billids, + disabled, + loadingCallback, + completedCallback, + refetch +}) { + const { t } = useTranslation(); + const [updateBill] = useMutation(UPDATE_BILLS); + const [loading, setLoading] = useState(false); + const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); - const handleQbxml = async () => { - logImEXEvent("accounting_payables_export_all"); - let PartnerResponse; + const handleQbxml = async () => { + logImEXEvent("accounting_payables_export_all"); + let PartnerResponse; - setLoading(true); - if (!!loadingCallback) loadingCallback(true); - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { - PartnerResponse = await axios.post(`/qbo/payables`, { - bills: billids, - elgen: true, - }); - } else { - let QbXmlResponse; - try { - QbXmlResponse = await axios.post( - "/accounting/qbxml/payables", - {bills: billids}, - { - headers: { - Authorization: `Bearer ${await auth.currentUser.getIdToken()}`, - }, - } - ); - } catch (error) { - console.log("Error getting QBXML from Server.", error); - notification["error"]({ - message: t("bills.errors.exporting", { - error: "Unable to retrieve QBXML. " + JSON.stringify(error.message), - }), - }); - if (loadingCallback) loadingCallback(false); - setLoading(false); - return; + setLoading(true); + if (!!loadingCallback) loadingCallback(true); + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + PartnerResponse = await axios.post(`/qbo/payables`, { + bills: billids, + elgen: true + }); + } else { + let QbXmlResponse; + try { + QbXmlResponse = await axios.post( + "/accounting/qbxml/payables", + { bills: billids }, + { + headers: { + Authorization: `Bearer ${await auth.currentUser.getIdToken()}` } - - try { - PartnerResponse = await axios.post( - "http://localhost:1337/qb/", - QbXmlResponse.data - ); - } catch (error) { - console.log("Error connecting to quickbooks or partner.", error); - notification["error"]({ - message: t("bills.errors.exporting-partner"), - }); - if (!!loadingCallback) loadingCallback(false); - setLoading(false); - return; - } - } - - console.log("handleQbxml -> PartnerResponse", PartnerResponse); - const groupedData = _.groupBy( - PartnerResponse.data, - bodyshop.accountingconfig.qbo ? "billid" : "id" + } ); - - const proms = []; - Object.keys(groupedData).forEach((key) => { - proms.push( - (async () => { - const failedTransactions = groupedData[key].filter((r) => !r.success); - const successfulTransactions = groupedData[key].filter( - (r) => r.success - ); - if (failedTransactions.length > 0) { - //Uh oh. At least one was no good. - failedTransactions.map((ft) => - notification["error"]({ - message: t("bills.errors.exporting", { - error: ft.errorMessage || "", - }), - }) - ); - if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { - //QBO Logs are handled server side. - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - billid: key, - successful: false, - message: JSON.stringify( - failedTransactions.map((ft) => ft.errorMessage) - ), - useremail: currentUser.email, - }, - ], - }, - }); - } - } else { - if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { - //QBO Logs are handled server side. - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - billid: key, - successful: true, - useremail: currentUser.email, - }, - ], - }, - }); - - const billUpdateResponse = await updateBill({ - variables: { - billIdList: successfulTransactions.map( - (st) => - st[ - bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo - ? "billid" - : "id" - ] - ), - bill: { - exported: true, - exported_at: new Date(), - }, - }, - }); - if (!!!billUpdateResponse.errors) { - notification.open({ - type: "success", - key: "billsuccessexport", - message: t("bills.successes.exported"), - }); - updateBillCache( - billUpdateResponse.data.update_bills.returning.map( - (bill) => bill.id - ) - ); - } else { - notification["error"]({ - message: t("bills.errors.exporting", { - error: JSON.stringify(billUpdateResponse.errors), - }), - }); - } - } - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { - notification.open({ - type: "success", - key: "billsuccessexport", - message: t("bills.successes.exported"), - }); - updateBillCache([ - ...new Set( - successfulTransactions.map( - (st) => - st[ - bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo - ? "billid" - : "id" - ] - ) - ), - ]); - } - } - })() - ); + } catch (error) { + console.log("Error getting QBXML from Server.", error); + notification["error"]({ + message: t("bills.errors.exporting", { + error: "Unable to retrieve QBXML. " + JSON.stringify(error.message) + }) }); + if (loadingCallback) loadingCallback(false); + setLoading(false); + return; + } - await Promise.all(proms); - if (!!completedCallback) completedCallback([]); + try { + PartnerResponse = await axios.post("http://localhost:1337/qb/", QbXmlResponse.data); + } catch (error) { + console.log("Error connecting to quickbooks or partner.", error); + notification["error"]({ + message: t("bills.errors.exporting-partner") + }); if (!!loadingCallback) loadingCallback(false); setLoading(false); - }; + return; + } + } - if (bodyshop.pbs_serialnumber) - return ( - - - - ); + console.log("handleQbxml -> PartnerResponse", PartnerResponse); + const groupedData = _.groupBy(PartnerResponse.data, bodyshop.accountingconfig.qbo ? "billid" : "id"); + const proms = []; + Object.keys(groupedData).forEach((key) => { + proms.push( + (async () => { + const failedTransactions = groupedData[key].filter((r) => !r.success); + const successfulTransactions = groupedData[key].filter((r) => r.success); + if (failedTransactions.length > 0) { + //Uh oh. At least one was no good. + failedTransactions.map((ft) => + notification["error"]({ + message: t("bills.errors.exporting", { + error: ft.errorMessage || "" + }) + }) + ); + if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { + //QBO Logs are handled server side. + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + billid: key, + successful: false, + message: JSON.stringify(failedTransactions.map((ft) => ft.errorMessage)), + useremail: currentUser.email + } + ] + } + }); + } + } else { + if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { + //QBO Logs are handled server side. + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + billid: key, + successful: true, + useremail: currentUser.email + } + ] + } + }); + + const billUpdateResponse = await updateBill({ + variables: { + billIdList: successfulTransactions.map( + (st) => st[bodyshop.accountingconfig && bodyshop.accountingconfig.qbo ? "billid" : "id"] + ), + bill: { + exported: true, + exported_at: new Date() + } + } + }); + if (!!!billUpdateResponse.errors) { + notification.open({ + type: "success", + key: "billsuccessexport", + message: t("bills.successes.exported") + }); + updateBillCache(billUpdateResponse.data.update_bills.returning.map((bill) => bill.id)); + } else { + notification["error"]({ + message: t("bills.errors.exporting", { + error: JSON.stringify(billUpdateResponse.errors) + }) + }); + } + } + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { + notification.open({ + type: "success", + key: "billsuccessexport", + message: t("bills.successes.exported") + }); + updateBillCache([ + ...new Set( + successfulTransactions.map( + (st) => st[bodyshop.accountingconfig && bodyshop.accountingconfig.qbo ? "billid" : "id"] + ) + ) + ]); + } + } + })() + ); + }); + + await Promise.all(proms); + if (!!completedCallback) completedCallback([]); + if (!!loadingCallback) loadingCallback(false); + setLoading(false); + }; + + if (bodyshop.pbs_serialnumber) return ( - + + + ); + + return ( + + ); } export default connect(mapStateToProps, null)(PayableExportAll); diff --git a/client/src/components/payable-export-button/payable-export-button.component.jsx b/client/src/components/payable-export-button/payable-export-button.component.jsx index cd8b5b085..3249da15d 100644 --- a/client/src/components/payable-export-button/payable-export-button.component.jsx +++ b/client/src/components/payable-export-button/payable-export-button.component.jsx @@ -1,230 +1,207 @@ -import {useMutation} from "@apollo/client"; -import {Button, notification} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, notification } from "antd"; import axios from "axios"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {auth, logImEXEvent} from "../../firebase/firebase.utils"; -import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries"; -import {UPDATE_BILLS} from "../../graphql/bills.queries"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { auth, logImEXEvent } from "../../firebase/firebase.utils"; +import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { UPDATE_BILLS } from "../../graphql/bills.queries"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import client from "../../utils/GraphQLClient"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); function updateBillCache(items) { - client.cache.modify({ - id: "ROOT_QUERY", - fields: { - bills(existingJobs = []) { - return existingJobs.filter( - (billRef) => billRef.__ref.includes(items) === false - ); - }, - }, - }); + client.cache.modify({ + id: "ROOT_QUERY", + fields: { + bills(existingJobs = []) { + return existingJobs.filter((billRef) => billRef.__ref.includes(items) === false); + } + } + }); } export function PayableExportButton({ - bodyshop, - currentUser, - billId, - disabled, - loadingCallback, - setSelectedBills, - refetch, - }) { - const {t} = useTranslation(); - const [updateBill] = useMutation(UPDATE_BILLS); - const [loading, setLoading] = useState(false); - const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); + bodyshop, + currentUser, + billId, + disabled, + loadingCallback, + setSelectedBills, + refetch +}) { + const { t } = useTranslation(); + const [updateBill] = useMutation(UPDATE_BILLS); + const [loading, setLoading] = useState(false); + const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); - const handleQbxml = async () => { - logImEXEvent("accounting_export_payable"); + const handleQbxml = async () => { + logImEXEvent("accounting_export_payable"); - setLoading(true); - if (!!loadingCallback) loadingCallback(true); + setLoading(true); + if (!!loadingCallback) loadingCallback(true); - //Check if it's a QBO Setup. - let PartnerResponse; - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { - PartnerResponse = await axios.post(`/qbo/payables`, { - bills: [billId], - elgen: true, - }); - } else { - //Default is QBD + //Check if it's a QBO Setup. + let PartnerResponse; + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + PartnerResponse = await axios.post(`/qbo/payables`, { + bills: [billId], + elgen: true + }); + } else { + //Default is QBD - let QbXmlResponse; - try { - QbXmlResponse = await axios.post( - "/accounting/qbxml/payables", - {bills: [billId]}, - { - headers: { - Authorization: `Bearer ${await auth.currentUser.getIdToken()}`, - }, - } - ); - } catch (error) { - console.log("Error getting QBXML from Server.", error); - notification["error"]({ - message: t("bills.errors.exporting", { - error: "Unable to retrieve QBXML. " + JSON.stringify(error.message), - }), - }); - if (loadingCallback) loadingCallback(false); - setLoading(false); - return; + let QbXmlResponse; + try { + QbXmlResponse = await axios.post( + "/accounting/qbxml/payables", + { bills: [billId] }, + { + headers: { + Authorization: `Bearer ${await auth.currentUser.getIdToken()}` } - - try { - PartnerResponse = await axios.post( - "http://localhost:1337/qb/", - QbXmlResponse.data - ); - } catch (error) { - console.log("Error connecting to quickbooks or partner.", error); - notification["error"]({ - message: t("bills.errors.exporting-partner"), - }); - if (!!loadingCallback) loadingCallback(false); - setLoading(false); - return; - } - } - - console.log("handleQbxml -> PartnerResponse", PartnerResponse); - const failedTransactions = PartnerResponse.data.filter((r) => !r.success); - const successfulTransactions = PartnerResponse.data.filter( - (r) => r.success + } ); - if (failedTransactions.length > 0) { - //Uh oh. At least one was no good. - failedTransactions.map((ft) => - notification["error"]({ - message: t("bills.errors.exporting", { - error: ft.errorMessage || "", - }), - }) - ); - if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { - //QBO Logs are handled server side. - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - billid: billId, - successful: false, - message: JSON.stringify( - failedTransactions.map((ft) => ft.errorMessage) - ), - useremail: currentUser.email, - }, - ], - }, - }); - } - } - if (successfulTransactions.length > 0) { - if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { - //QBO Logs are handled server side. - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - billid: billId, - successful: true, - useremail: currentUser.email, - }, - ], - }, - }); - const billUpdateResponse = await updateBill({ - variables: { - billIdList: successfulTransactions.map( - (st) => - st[ - bodyshop.accountingconfig && bodyshop.accountingconfig.qbo - ? "billid" - : "id" - ] - ), - bill: { - exported: true, - exported_at: new Date(), - }, - }, - }); - if (!!!billUpdateResponse.errors) { - notification.open({ - type: "success", - key: "billsuccessexport", - message: t("bills.successes.exported"), - }); - updateBillCache( - billUpdateResponse.data.update_bills.returning.map( - (bill) => bill.id - ) - ); - } else { - notification["error"]({ - message: t("bills.errors.exporting", { - error: JSON.stringify(billUpdateResponse.error), - }), - }); - } - } - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { - notification.open({ - type: "success", - key: "billsuccessexport", - message: t("bills.successes.exported"), - }); - updateBillCache([ - ...new Set( - successfulTransactions.map( - (st) => - st[ - bodyshop.accountingconfig && bodyshop.accountingconfig.qbo - ? "billid" - : "id" - ] - ) - ), - ]); - } - - if (setSelectedBills) { - setSelectedBills((selectedBills) => { - return selectedBills.filter((i) => i !== billId); - }); - } - } + } catch (error) { + console.log("Error getting QBXML from Server.", error); + notification["error"]({ + message: t("bills.errors.exporting", { + error: "Unable to retrieve QBXML. " + JSON.stringify(error.message) + }) + }); + if (loadingCallback) loadingCallback(false); + setLoading(false); + return; + } + try { + PartnerResponse = await axios.post("http://localhost:1337/qb/", QbXmlResponse.data); + } catch (error) { + console.log("Error connecting to quickbooks or partner.", error); + notification["error"]({ + message: t("bills.errors.exporting-partner") + }); if (!!loadingCallback) loadingCallback(false); setLoading(false); - }; + return; + } + } - if (bodyshop.pbs_serialnumber) - return ( - - - - ); + console.log("handleQbxml -> PartnerResponse", PartnerResponse); + const failedTransactions = PartnerResponse.data.filter((r) => !r.success); + const successfulTransactions = PartnerResponse.data.filter((r) => r.success); + if (failedTransactions.length > 0) { + //Uh oh. At least one was no good. + failedTransactions.map((ft) => + notification["error"]({ + message: t("bills.errors.exporting", { + error: ft.errorMessage || "" + }) + }) + ); + if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { + //QBO Logs are handled server side. + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + billid: billId, + successful: false, + message: JSON.stringify(failedTransactions.map((ft) => ft.errorMessage)), + useremail: currentUser.email + } + ] + } + }); + } + } + if (successfulTransactions.length > 0) { + if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { + //QBO Logs are handled server side. + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + billid: billId, + successful: true, + useremail: currentUser.email + } + ] + } + }); + const billUpdateResponse = await updateBill({ + variables: { + billIdList: successfulTransactions.map( + (st) => st[bodyshop.accountingconfig && bodyshop.accountingconfig.qbo ? "billid" : "id"] + ), + bill: { + exported: true, + exported_at: new Date() + } + } + }); + if (!!!billUpdateResponse.errors) { + notification.open({ + type: "success", + key: "billsuccessexport", + message: t("bills.successes.exported") + }); + updateBillCache(billUpdateResponse.data.update_bills.returning.map((bill) => bill.id)); + } else { + notification["error"]({ + message: t("bills.errors.exporting", { + error: JSON.stringify(billUpdateResponse.error) + }) + }); + } + } + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { + notification.open({ + type: "success", + key: "billsuccessexport", + message: t("bills.successes.exported") + }); + updateBillCache([ + ...new Set( + successfulTransactions.map( + (st) => st[bodyshop.accountingconfig && bodyshop.accountingconfig.qbo ? "billid" : "id"] + ) + ) + ]); + } + if (setSelectedBills) { + setSelectedBills((selectedBills) => { + return selectedBills.filter((i) => i !== billId); + }); + } + } + + if (!!loadingCallback) loadingCallback(false); + setLoading(false); + }; + + if (bodyshop.pbs_serialnumber) return ( - + + + ); + + return ( + + ); } export default connect(mapStateToProps, null)(PayableExportButton); diff --git a/client/src/components/payable-mark-selected-exported/payable-mark-selected-exported.component.jsx b/client/src/components/payable-mark-selected-exported/payable-mark-selected-exported.component.jsx index e1a1975de..08cd00b43 100644 --- a/client/src/components/payable-mark-selected-exported/payable-mark-selected-exported.component.jsx +++ b/client/src/components/payable-mark-selected-exported/payable-mark-selected-exported.component.jsx @@ -1,106 +1,98 @@ -import {gql, useMutation} from "@apollo/client"; -import {Button, notification, Popconfirm} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { gql, useMutation } from "@apollo/client"; +import { Button, notification, Popconfirm } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(BillMarkSelectedExported); +export default connect(mapStateToProps, mapDispatchToProps)(BillMarkSelectedExported); export function BillMarkSelectedExported({ - bodyshop, - currentUser, - billids, - disabled, - loadingCallback, - completedCallback, - refetch, - }) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const [open, setOpen] = useState(false); - const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); - const [updateBill] = useMutation(gql` - mutation UPDATE_BILL($billIds: [uuid!]!) { - update_bills(where: { id: { _in: $billIds } }, _set: { exported: true }) { - returning { - id - exported - exported_at - } - } + bodyshop, + currentUser, + billids, + disabled, + loadingCallback, + completedCallback, + refetch +}) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); + const [updateBill] = useMutation(gql` + mutation UPDATE_BILL($billIds: [uuid!]!) { + update_bills(where: { id: { _in: $billIds } }, _set: { exported: true }) { + returning { + id + exported + exported_at } - `); + } + } + `); - const handleUpdate = async () => { - setLoading(true); - loadingCallback(true); - const result = await updateBill({ - variables: {billIds: billids}, - update(cache) { - }, - }); + const handleUpdate = async () => { + setLoading(true); + loadingCallback(true); + const result = await updateBill({ + variables: { billIds: billids }, + update(cache) {} + }); - await insertExportLog({ - variables: { - logs: billids.map((id) => { - return { - bodyshopid: bodyshop.id, - billid: id, - successful: true, - message: JSON.stringify([t("general.labels.markedexported")]), - useremail: currentUser.email, - }; - }), - }, - }); + await insertExportLog({ + variables: { + logs: billids.map((id) => { + return { + bodyshopid: bodyshop.id, + billid: id, + successful: true, + message: JSON.stringify([t("general.labels.markedexported")]), + useremail: currentUser.email + }; + }) + } + }); - if (!result.errors) { - notification["success"]({ - message: t("bills.successes.markexported"), - }); - } else { - notification["error"]({ - message: t("bills.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - loadingCallback(false); - completedCallback && completedCallback([]); - setLoading(false); - refetch && refetch(); - setOpen(false); - }; + if (!result.errors) { + notification["success"]({ + message: t("bills.successes.markexported") + }); + } else { + notification["error"]({ + message: t("bills.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + loadingCallback(false); + completedCallback && completedCallback([]); + setLoading(false); + refetch && refetch(); + setOpen(false); + }; - return ( - setOpen(false)} - onConfirm={handleUpdate} - disabled={disabled} - > - - - ); + return ( + setOpen(false)} + onConfirm={handleUpdate} + disabled={disabled} + > + + + ); } diff --git a/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx b/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx index 32e3752da..e745be92c 100644 --- a/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx +++ b/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx @@ -1,180 +1,153 @@ -import {useMutation, useQuery} from "@apollo/client"; -import {Button, Descriptions, InputNumber, Modal, notification, Space,} from "antd"; +import { useMutation, useQuery } from "@apollo/client"; +import { Button, Descriptions, InputNumber, Modal, notification, Space } from "antd"; import axios from "axios"; import dayjs from "../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import { - GET_REFUNDABLE_AMOUNT_BY_JOBID, - INSERT_PAYMENT_RESPONSE, - QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID, + GET_REFUNDABLE_AMOUNT_BY_JOBID, + INSERT_PAYMENT_RESPONSE, + QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID } from "../../graphql/payment_response.queries"; -import {INSERT_NEW_PAYMENT} from "../../graphql/payments.queries"; +import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; -const {confirm} = Modal; +const { confirm } = Modal; const openNotificationWithIcon = (type, t) => { - notification[type]({ - message: t("job_payments.notifications.error.title"), - description: t("job_payments.notifications.error.description"), - }); + notification[type]({ + message: t("job_payments.notifications.error.title"), + description: t("job_payments.notifications.error.description") + }); }; -const PaymentExpandedRowComponent = ({record, bodyshop}) => { - const [refundAmount, setRefundAmount] = useState(0); - const [insertPayment] = useMutation(INSERT_NEW_PAYMENT); - const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE); - const {t} = useTranslation(); +const PaymentExpandedRowComponent = ({ record, bodyshop }) => { + const [refundAmount, setRefundAmount] = useState(0); + const [insertPayment] = useMutation(INSERT_NEW_PAYMENT); + const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE); + const { t } = useTranslation(); - const {loading, error, data} = useQuery( - QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID, - { - variables: { - paymentid: record.id, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }, + const { loading, error, data } = useQuery(QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID, { + variables: { + paymentid: record.id, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + } + }); + + const { data: refundable_amount, refetch } = useQuery(GET_REFUNDABLE_AMOUNT_BY_JOBID, { + variables: { + jobid: record.jobid + } + }); + + const insertPayments = async (payment_response, refund_response) => { + await insertPayment({ + variables: { + paymentInput: { + amount: -refund_response?.data?.amount, + transactionid: payment_response?.response?.receiptelements?.transid, + payer: record.payer, + type: "Refund", + jobid: payment_response.jobid, + date: dayjs(Date.now()) } - ); + }, + update(cache, { data }) { + cache.modify({ + id: cache.identify({ + id: payment_response.jobid, + __typename: "jobs" + }), + fields: { + payments(payments) { + return [...data.insert_payments.returning, ...payments]; + } + } + }); + } + }); - const {data: refundable_amount, refetch} = useQuery( - GET_REFUNDABLE_AMOUNT_BY_JOBID, - { - variables: { - jobid: record.jobid, - }, + await insertPaymentResponse({ + variables: { + paymentResponse: { + amount: -refund_response.data.amount, + bodyshopid: payment_response.bodyshopid, + paymentid: payment_response.paymentid, + jobid: payment_response.jobid, + declinereason: "Refund", + ext_paymentid: payment_response.ext_paymentid, + successful: true, + response: refund_response.data } - ); + } + }); + }; - const insertPayments = async (payment_response, refund_response) => { - await insertPayment({ - variables: { - paymentInput: { - amount: -refund_response?.data?.amount, - transactionid: payment_response?.response?.receiptelements?.transid, - payer: record.payer, - type: "Refund", - jobid: payment_response.jobid, - date: dayjs(Date.now()), - }, - }, - update(cache, {data}) { - cache.modify({ - id: cache.identify({ - id: payment_response.jobid, - __typename: "jobs", - }), - fields: { - payments(payments) { - return [...data.insert_payments.returning, ...payments]; - }, - }, - }); - }, + const showConfirm = (payment_response) => { + confirm({ + title: "Do you want to refund payment?", + content: "The payment will be refunded. Click OK to confirm and Cancel to dismiss.", + async onOk() { + const refundResponse = await axios.post("/intellipay/payment_refund", { + bodyshop, + amount: refundAmount, + paymentid: payment_response.ext_paymentid }); - await insertPaymentResponse({ - variables: { - paymentResponse: { - amount: -refund_response.data.amount, - bodyshopid: payment_response.bodyshopid, - paymentid: payment_response.paymentid, - jobid: payment_response.jobid, - declinereason: "Refund", - ext_paymentid: payment_response.ext_paymentid, - successful: true, - response: refund_response.data, - }, - }, - }); - }; + if (refundResponse.data.status < 0) { + openNotificationWithIcon("error", t); + return; + } - const showConfirm = (payment_response) => { - confirm({ - title: "Do you want to refund payment?", - content: - "The payment will be refunded. Click OK to confirm and Cancel to dismiss.", - async onOk() { - const refundResponse = await axios.post("/intellipay/payment_refund", { - bodyshop, - amount: refundAmount, - paymentid: payment_response.ext_paymentid, - }); + insertPayments(payment_response, refundResponse); - if (refundResponse.data.status < 0) { - openNotificationWithIcon("error", t); - return; - } + // refetch refundable amount + refetch(); + }, + onCancel() {} + }); + }; - insertPayments(payment_response, refundResponse); + if (loading) return null; - // refetch refundable amount - refetch(); - }, - onCancel() { - }, - }); - }; + if (error) return

Error loading data. Please Reload

; - if (loading) return null; + const payment_response = data.payment_response[0]; + const max_refundable_amount = refundable_amount?.payment_response_aggregate.aggregate.sum.amount; - if (error) return

Error loading data. Please Reload

; + return ( +
+ + {record.payer} + + {payment_response?.response?.nameOnCard ?? ""} + + + {record.amount} + + + {{record.created_at}} + + {record.transactionid} + + {payment_response?.response?.paymentreferenceid ?? ""} + + {record.type} + {record.paymentnum} + {payment_response && ( + + + - const payment_response = data.payment_response[0]; - const max_refundable_amount = - refundable_amount?.payment_response_aggregate.aggregate.sum.amount; - - return ( -
- - - {record.payer} - - - {payment_response?.response?.nameOnCard ?? ""} - - - {record.amount} - - - {{record.created_at}} - - - {record.transactionid} - - - {payment_response?.response?.paymentreferenceid ?? ""} - - - {record.type} - - - {record.paymentnum} - - {payment_response && ( - - - - - - - - )} - -
- ); + +
+
+ )} +
+
+ ); }; export default PaymentExpandedRowComponent; diff --git a/client/src/components/payment-export-button/payment-export-button.component.jsx b/client/src/components/payment-export-button/payment-export-button.component.jsx index 164af14f8..876dc5af7 100644 --- a/client/src/components/payment-export-button/payment-export-button.component.jsx +++ b/client/src/components/payment-export-button/payment-export-button.component.jsx @@ -1,221 +1,198 @@ -import {useMutation} from "@apollo/client"; -import {Button, notification} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, notification } from "antd"; import axios from "axios"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {auth, logImEXEvent} from "../../firebase/firebase.utils"; -import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries"; -import {UPDATE_PAYMENTS} from "../../graphql/payments.queries"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { auth, logImEXEvent } from "../../firebase/firebase.utils"; +import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { UPDATE_PAYMENTS } from "../../graphql/payments.queries"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import client from "../../utils/GraphQLClient"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); function updatePaymentCache(items) { - client.cache.modify({ - id: "ROOT_QUERY", - fields: { - payments(existingJobs = []) { - return existingJobs.filter( - (paymentRef) => paymentRef.__ref.includes(items) === false - ); - }, - }, - }); + client.cache.modify({ + id: "ROOT_QUERY", + fields: { + payments(existingJobs = []) { + return existingJobs.filter((paymentRef) => paymentRef.__ref.includes(items) === false); + } + } + }); } export function PaymentExportButton({ - bodyshop, - currentUser, - paymentId, - disabled, - loadingCallback, - setSelectedPayments, - refetch, - }) { - const {t} = useTranslation(); - const [updatePayment] = useMutation(UPDATE_PAYMENTS); - const [loading, setLoading] = useState(false); - const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); + bodyshop, + currentUser, + paymentId, + disabled, + loadingCallback, + setSelectedPayments, + refetch +}) { + const { t } = useTranslation(); + const [updatePayment] = useMutation(UPDATE_PAYMENTS); + const [loading, setLoading] = useState(false); + const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); - const handleQbxml = async () => { - logImEXEvent("accounting_payment_export"); - setLoading(true); - //Check if it's a QBO Setup. - let PartnerResponse; - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { - PartnerResponse = await axios.post(`/qbo/payments`, { - payments: [paymentId], - elgen: true, - }); - } else { - //Default is QBD + const handleQbxml = async () => { + logImEXEvent("accounting_payment_export"); + setLoading(true); + //Check if it's a QBO Setup. + let PartnerResponse; + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + PartnerResponse = await axios.post(`/qbo/payments`, { + payments: [paymentId], + elgen: true + }); + } else { + //Default is QBD - if (!!loadingCallback) loadingCallback(true); + if (!!loadingCallback) loadingCallback(true); - let QbXmlResponse; - try { - QbXmlResponse = await axios.post( - "/accounting/qbxml/payments", - {payments: [paymentId]}, - { - headers: { - Authorization: `Bearer ${await auth.currentUser.getIdToken()}`, - }, - } - ); - console.log("handle -> XML", QbXmlResponse); - } catch (error) { - console.log("Error getting QBXML from Server.", error); - notification["error"]({ - message: t("payments.errors.exporting", { - error: "Unable to retrieve QBXML. " + JSON.stringify(error.message), - }), - }); - if (loadingCallback) loadingCallback(false); - setLoading(false); - return; + let QbXmlResponse; + try { + QbXmlResponse = await axios.post( + "/accounting/qbxml/payments", + { payments: [paymentId] }, + { + headers: { + Authorization: `Bearer ${await auth.currentUser.getIdToken()}` } - - try { - PartnerResponse = await axios.post( - "http://localhost:1337/qb/", - QbXmlResponse.data - ); - } catch (error) { - console.log("Error connecting to quickbooks or partner.", error); - notification["error"]({ - message: t("payments.errors.exporting-partner"), - }); - if (!!loadingCallback) loadingCallback(false); - setLoading(false); - return; - } - } - - console.log("handleQbxml -> PartnerResponse", PartnerResponse); - const failedTransactions = PartnerResponse.data.filter((r) => !r.success); - const successfulTransactions = PartnerResponse.data.filter( - (r) => r.success + } ); - if (failedTransactions.length > 0) { - //Uh oh. At least one was no good. - failedTransactions.map((ft) => - notification["error"]({ - message: t("payments.errors.exporting", { - error: ft.errorMessage || "", - }), - }) - ); - if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { - //QBO Logs are handled server side. - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - paymentid: paymentId, - successful: false, - message: JSON.stringify( - failedTransactions.map((ft) => ft.errorMessage) - ), - useremail: currentUser.email, - }, - ], - }, - }); - } - } else { - if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { - //QBO Logs are handled server side. - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - paymentid: paymentId, - successful: true, - useremail: currentUser.email, - }, - ], - }, - }); + console.log("handle -> XML", QbXmlResponse); + } catch (error) { + console.log("Error getting QBXML from Server.", error); + notification["error"]({ + message: t("payments.errors.exporting", { + error: "Unable to retrieve QBXML. " + JSON.stringify(error.message) + }) + }); + if (loadingCallback) loadingCallback(false); + setLoading(false); + return; + } - const paymentUpdateResponse = await updatePayment({ - variables: { - paymentIdList: successfulTransactions.map( - (st) => - st[ - bodyshop.accountingconfig && bodyshop.accountingconfig.qbo - ? "paymentid" - : "id" - ] - ), - payment: { - exportedat: new Date(), - }, - }, - }); - if (!!!paymentUpdateResponse.errors) { - notification.open({ - type: "success", - key: "paymentsuccessexport", - message: t("payments.successes.exported"), - }); - updatePaymentCache( - paymentUpdateResponse.data.update_payments.returning.map( - (payment) => payment.id - ) - ); - } else { - notification["error"]({ - message: t("payments.errors.exporting", { - error: JSON.stringify(paymentUpdateResponse.error), - }), - }); - } - } - - if (setSelectedPayments) { - setSelectedPayments((selectedBills) => { - return selectedBills.filter((i) => i !== paymentId); - }); - } - } - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { - notification.open({ - type: "success", - key: "paymentsuccessexport", - message: t("payments.successes.exported"), - }); - updatePaymentCache([ - ...new Set( - successfulTransactions.map( - (st) => - st[ - bodyshop.accountingconfig && bodyshop.accountingconfig.qbo - ? "paymentid" - : "id" - ] - ) - ), - ]); - } + try { + PartnerResponse = await axios.post("http://localhost:1337/qb/", QbXmlResponse.data); + } catch (error) { + console.log("Error connecting to quickbooks or partner.", error); + notification["error"]({ + message: t("payments.errors.exporting-partner") + }); if (!!loadingCallback) loadingCallback(false); setLoading(false); - }; + return; + } + } - return ( - - ); + console.log("handleQbxml -> PartnerResponse", PartnerResponse); + const failedTransactions = PartnerResponse.data.filter((r) => !r.success); + const successfulTransactions = PartnerResponse.data.filter((r) => r.success); + if (failedTransactions.length > 0) { + //Uh oh. At least one was no good. + failedTransactions.map((ft) => + notification["error"]({ + message: t("payments.errors.exporting", { + error: ft.errorMessage || "" + }) + }) + ); + if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { + //QBO Logs are handled server side. + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + paymentid: paymentId, + successful: false, + message: JSON.stringify(failedTransactions.map((ft) => ft.errorMessage)), + useremail: currentUser.email + } + ] + } + }); + } + } else { + if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { + //QBO Logs are handled server side. + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + paymentid: paymentId, + successful: true, + useremail: currentUser.email + } + ] + } + }); + + const paymentUpdateResponse = await updatePayment({ + variables: { + paymentIdList: successfulTransactions.map( + (st) => st[bodyshop.accountingconfig && bodyshop.accountingconfig.qbo ? "paymentid" : "id"] + ), + payment: { + exportedat: new Date() + } + } + }); + if (!!!paymentUpdateResponse.errors) { + notification.open({ + type: "success", + key: "paymentsuccessexport", + message: t("payments.successes.exported") + }); + updatePaymentCache(paymentUpdateResponse.data.update_payments.returning.map((payment) => payment.id)); + } else { + notification["error"]({ + message: t("payments.errors.exporting", { + error: JSON.stringify(paymentUpdateResponse.error) + }) + }); + } + } + + if (setSelectedPayments) { + setSelectedPayments((selectedBills) => { + return selectedBills.filter((i) => i !== paymentId); + }); + } + } + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { + notification.open({ + type: "success", + key: "paymentsuccessexport", + message: t("payments.successes.exported") + }); + updatePaymentCache([ + ...new Set( + successfulTransactions.map( + (st) => st[bodyshop.accountingconfig && bodyshop.accountingconfig.qbo ? "paymentid" : "id"] + ) + ) + ]); + } + if (!!loadingCallback) loadingCallback(false); + setLoading(false); + }; + + return ( + + ); } export default connect(mapStateToProps, null)(PaymentExportButton); diff --git a/client/src/components/payment-form/payment-form.component.jsx b/client/src/components/payment-form/payment-form.component.jsx index 6cee80bb5..1aa9d04f3 100644 --- a/client/src/components/payment-form/payment-form.component.jsx +++ b/client/src/components/payment-form/payment-form.component.jsx @@ -1,10 +1,10 @@ -import {useSplitTreatments} from "@splitsoftware/splitio-react"; -import {Form, Input, Radio, Select} from "antd"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { Form, Input, Radio, Select } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import DatePickerFormItem from "../form-date-picker/form-date-picker.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import JobSearchSelect from "../job-search-select/job-search-select.component"; @@ -12,149 +12,134 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import PaymentFormTotalPayments from "./payment-form.totalpayments.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -export function PaymentFormComponent({form, bodyshop, disabled,}) { +export function PaymentFormComponent({ form, bodyshop, disabled }) { + const { + treatments: { Qb_Multi_Ar } + } = useSplitTreatments({ + attributes: {}, + names: ["Qb_Multi_Ar"], + splitKey: bodyshop && bodyshop.imexshopid + }); - const {treatments: {Qb_Multi_Ar}} = useSplitTreatments({ - attributes: {}, - names: ["Qb_Multi_Ar"], - splitKey: bodyshop && bodyshop.imexshopid, - }); + const { t } = useTranslation(); + return ( +
+ + + + + cur.jobid && prev.jobid !== cur.jobid}> + {() => { + return ; + }} + + - const {t} = useTranslation(); + + + + + + + + + + + + + + - return ( -
- - - - - cur.jobid && prev.jobid !== cur.jobid} - > - {() => { - return ( - - ); - }} - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - {t("general.labels.none")} - {t("general.labels.email")} - {t("general.labels.print")} - - - -
- ); + + + + + + + + {t("general.labels.none")} + {t("general.labels.email")} + {t("general.labels.print")} + + + +
+ ); } export default connect(mapStateToProps, null)(PaymentFormComponent); diff --git a/client/src/components/payment-form/payment-form.totalpayments.component.jsx b/client/src/components/payment-form/payment-form.totalpayments.component.jsx index f44bcb665..bcdbc0492 100644 --- a/client/src/components/payment-form/payment-form.totalpayments.component.jsx +++ b/client/src/components/payment-form/payment-form.totalpayments.component.jsx @@ -1,52 +1,44 @@ -import {useQuery} from "@apollo/client"; -import {Statistic} from "antd"; +import { useQuery } from "@apollo/client"; +import { Statistic } from "antd"; import Dinero from "dinero.js"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {QUERY_JOB_PAYMENT_TOTALS} from "../../graphql/payments.queries"; +import { useTranslation } from "react-i18next"; +import { QUERY_JOB_PAYMENT_TOTALS } from "../../graphql/payments.queries"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -export default function PaymentFormTotalPayments({jobid}) { - const {t} = useTranslation(); +export default function PaymentFormTotalPayments({ jobid }) { + const { t } = useTranslation(); - const {loading, error, data} = useQuery(QUERY_JOB_PAYMENT_TOTALS, { - variables: {id: jobid}, - skip: !jobid, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const { loading, error, data } = useQuery(QUERY_JOB_PAYMENT_TOTALS, { + variables: { id: jobid }, + skip: !jobid, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - if (loading) return ; - if (error) return ; + if (loading) return ; + if (error) return ; - if (!data) return <>; - const totalPayments = data.jobs_by_pk.payments.reduce((acc, val) => { - return acc.add( - Dinero({amount: Math.round(((val && val.amount) || 0) * 100)}) - ); - }, Dinero()); + if (!data) return <>; + const totalPayments = data.jobs_by_pk.payments.reduce((acc, val) => { + return acc.add(Dinero({ amount: Math.round(((val && val.amount) || 0) * 100) })); + }, Dinero()); - const balance = - data.jobs_by_pk.job_totals && - Dinero(data.jobs_by_pk.job_totals.totals.total_repairs).subtract( - totalPayments - ); + const balance = + data.jobs_by_pk.job_totals && Dinero(data.jobs_by_pk.job_totals.totals.total_repairs).subtract(totalPayments); - return ( -
- - {balance && ( - - )} - {!balance &&
{t("jobs.errors.nofinancial")}
} -
- ); + return ( +
+ + {balance && ( + + )} + {!balance &&
{t("jobs.errors.nofinancial")}
} +
+ ); } diff --git a/client/src/components/payment-mark-export-button/payment-mark-export-button-component.jsx b/client/src/components/payment-mark-export-button/payment-mark-export-button-component.jsx index daf97b504..6b7dce666 100644 --- a/client/src/components/payment-mark-export-button/payment-mark-export-button-component.jsx +++ b/client/src/components/payment-mark-export-button/payment-mark-export-button-component.jsx @@ -1,100 +1,84 @@ import React from "react"; -import {Button, notification} from "antd"; -import {useMutation} from "@apollo/client"; -import {useTranslation} from "react-i18next"; -import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {connect} from "react-redux"; -import {UPDATE_PAYMENT} from "../../graphql/payments.queries"; -import {selectCurrentUser} from "../../redux/user/user.selectors"; -import {createStructuredSelector} from "reselect"; +import { Button, notification } from "antd"; +import { useMutation } from "@apollo/client"; +import { useTranslation } from "react-i18next"; +import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { connect } from "react-redux"; +import { UPDATE_PAYMENT } from "../../graphql/payments.queries"; +import { selectCurrentUser } from "../../redux/user/user.selectors"; +import { createStructuredSelector } from "reselect"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - setPaymentContext: (context) => - dispatch(setModalContext({context: context, modal: "payment"})), + setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })) }); -const PaymentMarkForExportButton = ({ - bodyshop, - payment, - refetch, - setPaymentContext, - currentUser, - }) => { - const {t} = useTranslation(); - const [insertExportLog, {loading: exportLogLoading}] = - useMutation(INSERT_EXPORT_LOG); - const [updatePayment, {loading: updatePaymentLoading}] = - useMutation(UPDATE_PAYMENT); +const PaymentMarkForExportButton = ({ bodyshop, payment, refetch, setPaymentContext, currentUser }) => { + const { t } = useTranslation(); + const [insertExportLog, { loading: exportLogLoading }] = useMutation(INSERT_EXPORT_LOG); + const [updatePayment, { loading: updatePaymentLoading }] = useMutation(UPDATE_PAYMENT); - const handleClick = async () => { - const today = new Date(); + const handleClick = async () => { + const today = new Date(); - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - paymentid: payment.id, - successful: true, - useremail: currentUser.email, - }, - ], - }, - }); + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + paymentid: payment.id, + successful: true, + useremail: currentUser.email + } + ] + } + }); - const paymentUpdateResponse = await updatePayment({ - variables: { - paymentId: payment.id, - payment: { - exportedat: today, - }, - }, - }); - - if (!!!paymentUpdateResponse.errors) { - notification.open({ - type: "success", - key: "paymentsuccessmarkforexport", - message: t("payments.successes.markexported"), - }); - - if (refetch) refetch(); - - setPaymentContext({ - actions: { - refetch, - }, - context: { - ...payment, - exportedat: today, - }, - }); - } else { - notification["error"]({ - message: t("payments.errors.exporting", { - error: JSON.stringify(paymentUpdateResponse.error), - }), - }); + const paymentUpdateResponse = await updatePayment({ + variables: { + paymentId: payment.id, + payment: { + exportedat: today } - }; + } + }); - return ( - - ); + if (!!!paymentUpdateResponse.errors) { + notification.open({ + type: "success", + key: "paymentsuccessmarkforexport", + message: t("payments.successes.markexported") + }); + + if (refetch) refetch(); + + setPaymentContext({ + actions: { + refetch + }, + context: { + ...payment, + exportedat: today + } + }); + } else { + notification["error"]({ + message: t("payments.errors.exporting", { + error: JSON.stringify(paymentUpdateResponse.error) + }) + }); + } + }; + + return ( + + ); }; -export default connect( - mapStateToProps, - mapDispatchToProps -)(PaymentMarkForExportButton); +export default connect(mapStateToProps, mapDispatchToProps)(PaymentMarkForExportButton); diff --git a/client/src/components/payment-mark-selected-exported/payment-mark-selected-exported.component.jsx b/client/src/components/payment-mark-selected-exported/payment-mark-selected-exported.component.jsx index d103bf803..3727e23eb 100644 --- a/client/src/components/payment-mark-selected-exported/payment-mark-selected-exported.component.jsx +++ b/client/src/components/payment-mark-selected-exported/payment-mark-selected-exported.component.jsx @@ -1,109 +1,98 @@ -import {gql, useMutation} from "@apollo/client"; -import {Button, notification, Popconfirm} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { gql, useMutation } from "@apollo/client"; +import { Button, notification, Popconfirm } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(PaymentMarkSelectedExported); +export default connect(mapStateToProps, mapDispatchToProps)(PaymentMarkSelectedExported); export function PaymentMarkSelectedExported({ - bodyshop, - currentUser, - paymentIds, - disabled, - loadingCallback, - completedCallback, - refetch, - }) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const [open, setOpen] = useState(false); + bodyshop, + currentUser, + paymentIds, + disabled, + loadingCallback, + completedCallback, + refetch +}) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); - const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); - const [updatePayments] = useMutation(gql` - mutation UPDATE_PAYMENTS($paymentIds: [uuid!]!, $exportedat: timestamptz!) { - update_payments( - where: { id: { _in: $paymentIds } } - _set: { exportedat: $exportedat } - ) { - returning { - id - exportedat - } - } + const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); + const [updatePayments] = useMutation(gql` + mutation UPDATE_PAYMENTS($paymentIds: [uuid!]!, $exportedat: timestamptz!) { + update_payments(where: { id: { _in: $paymentIds } }, _set: { exportedat: $exportedat }) { + returning { + id + exportedat } - `); + } + } + `); - const handleUpdate = async () => { - setLoading(true); - loadingCallback(true); - const result = await updatePayments({ - variables: {paymentIds: paymentIds, exportedat: new Date()}, - update(cache) { - }, - }); + const handleUpdate = async () => { + setLoading(true); + loadingCallback(true); + const result = await updatePayments({ + variables: { paymentIds: paymentIds, exportedat: new Date() }, + update(cache) {} + }); - await insertExportLog({ - variables: { - logs: paymentIds.map((id) => { - return { - bodyshopid: bodyshop.id, - paymentid: id, - successful: true, - message: JSON.stringify([t("general.labels.markedexported")]), - useremail: currentUser.email, - }; - }), - }, - }); + await insertExportLog({ + variables: { + logs: paymentIds.map((id) => { + return { + bodyshopid: bodyshop.id, + paymentid: id, + successful: true, + message: JSON.stringify([t("general.labels.markedexported")]), + useremail: currentUser.email + }; + }) + } + }); - if (!result.errors) { - notification["success"]({ - message: t("payments.successes.markexported"), - }); - } else { - notification["error"]({ - message: t("bills.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - } - loadingCallback(false); - completedCallback && completedCallback([]); - setLoading(false); - refetch && refetch(); - setOpen(false); - }; + if (!result.errors) { + notification["success"]({ + message: t("payments.successes.markexported") + }); + } else { + notification["error"]({ + message: t("bills.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + loadingCallback(false); + completedCallback && completedCallback([]); + setLoading(false); + refetch && refetch(); + setOpen(false); + }; - return ( - setOpen(false)} - onConfirm={handleUpdate} - disabled={disabled} - > - - - ); + return ( + setOpen(false)} + onConfirm={handleUpdate} + disabled={disabled} + > + + + ); } diff --git a/client/src/components/payment-modal/payment-modal.container.jsx b/client/src/components/payment-modal/payment-modal.container.jsx index 777ee233c..e398eaf0e 100644 --- a/client/src/components/payment-modal/payment-modal.container.jsx +++ b/client/src/components/payment-modal/payment-modal.container.jsx @@ -1,203 +1,183 @@ -import {useMutation} from "@apollo/client"; +import { useMutation } from "@apollo/client"; -import {Button, Form, Modal, notification, Space} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {INSERT_NEW_PAYMENT, UPDATE_PAYMENT,} from "../../graphql/payments.queries"; -import {setEmailOptions} from "../../redux/email/email.actions"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectPayment} from "../../redux/modals/modals.selectors"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { Button, Form, Modal, notification, Space } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_NEW_PAYMENT, UPDATE_PAYMENT } from "../../graphql/payments.queries"; +import { setEmailOptions } from "../../redux/email/email.actions"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectPayment } from "../../redux/modals/modals.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; import PaymentForm from "../payment-form/payment-form.component"; import PaymentReexportButton from "../payment-reexport-button/payment-reexport-button.component"; import PaymentMarkForExportButton from "../payment-mark-export-button/payment-mark-export-button-component"; const mapStateToProps = createStructuredSelector({ - paymentModal: selectPayment, - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + paymentModal: selectPayment, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - setEmailOptions: (e) => dispatch(setEmailOptions(e)), - toggleModalVisible: () => dispatch(toggleModalVisible("payment")), + setEmailOptions: (e) => dispatch(setEmailOptions(e)), + toggleModalVisible: () => dispatch(toggleModalVisible("payment")) }); -function PaymentModalContainer({ - paymentModal, - toggleModalVisible, - bodyshop, - currentUser, - setEmailOptions, - }) { - const [form] = Form.useForm(); - const [enterAgain, setEnterAgain] = useState(false); - const [insertPayment] = useMutation(INSERT_NEW_PAYMENT); - const [updatePayment] = useMutation(UPDATE_PAYMENT); +function PaymentModalContainer({ paymentModal, toggleModalVisible, bodyshop, currentUser, setEmailOptions }) { + const [form] = Form.useForm(); + const [enterAgain, setEnterAgain] = useState(false); + const [insertPayment] = useMutation(INSERT_NEW_PAYMENT); + const [updatePayment] = useMutation(UPDATE_PAYMENT); - const {t} = useTranslation(); - const {context, actions, open} = paymentModal; + const { t } = useTranslation(); + const { context, actions, open } = paymentModal; - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(false); - const handleFinish = async (values) => { - const {useStripe, sendby, ...paymentObj} = values; + const handleFinish = async (values) => { + const { useStripe, sendby, ...paymentObj } = values; - setLoading(true); - let updatedPayment; //Moved up from if statement for greater scope. - try { - if (!context || (context && !context.id)) { - const newPayment = await insertPayment({ - variables: { - paymentInput: { - ...paymentObj, - }, - }, - }); - - if (!!!newPayment.errors) { - notification["success"]({message: t("payments.successes.payment")}); - } else { - notification["error"]({message: t("payments.errors.payment")}); - } - const Templates = TemplateList("payment"); - if (sendby !== "none") { - GenerateDocument( - { - name: Templates.payment_receipt.key, - variables: { - id: newPayment.data.insert_payments.returning[0].id, - }, - }, - { - // to: [appData.email], - replyTo: bodyshop.email, - subject: Templates.payment_receipt.subject, - }, - sendby === "email" ? "e" : "p", - paymentObj.jobid - ); - } - } else { - updatedPayment = await updatePayment({ - variables: { - paymentId: context.id, - payment: paymentObj, - }, - }); - - if (!!!updatedPayment.errors) { - notification["success"]({message: t("payments.successes.payment")}); - } else { - notification["error"]({message: t("payments.errors.payment")}); - } + setLoading(true); + let updatedPayment; //Moved up from if statement for greater scope. + try { + if (!context || (context && !context.id)) { + const newPayment = await insertPayment({ + variables: { + paymentInput: { + ...paymentObj } + } + }); - if (actions.refetch) - actions.refetch( - updatedPayment && updatedPayment.data.update_payments.returning[0] - ); - - if (enterAgain) { - const prev = form.getFieldsValue(["date"]); - - form.resetFields(); - form.setFieldsValue(prev); - } else { - toggleModalVisible(); - } - setEnterAgain(false); - } catch (error) { - console.log("error", error); - } finally { - setEnterAgain(false); - setLoading(false); + if (!!!newPayment.errors) { + notification["success"]({ message: t("payments.successes.payment") }); + } else { + notification["error"]({ message: t("payments.errors.payment") }); } - }; + const Templates = TemplateList("payment"); + if (sendby !== "none") { + GenerateDocument( + { + name: Templates.payment_receipt.key, + variables: { + id: newPayment.data.insert_payments.returning[0].id + } + }, + { + // to: [appData.email], + replyTo: bodyshop.email, + subject: Templates.payment_receipt.subject + }, + sendby === "email" ? "e" : "p", + paymentObj.jobid + ); + } + } else { + updatedPayment = await updatePayment({ + variables: { + paymentId: context.id, + payment: paymentObj + } + }); - const handleCancel = () => { + if (!!!updatedPayment.errors) { + notification["success"]({ message: t("payments.successes.payment") }); + } else { + notification["error"]({ message: t("payments.errors.payment") }); + } + } + + if (actions.refetch) actions.refetch(updatedPayment && updatedPayment.data.update_payments.returning[0]); + + if (enterAgain) { + const prev = form.getFieldsValue(["date"]); + + form.resetFields(); + form.setFieldsValue(prev); + } else { toggleModalVisible(); - }; + } + setEnterAgain(false); + } catch (error) { + console.log("error", error); + } finally { + setEnterAgain(false); + setLoading(false); + } + }; - useEffect(() => { - if (open) { - form.resetFields(); - form.resetFields(); - form.setFieldsValue(context); - } - }, [open, form, context]); + const handleCancel = () => { + toggleModalVisible(); + }; - useEffect(() => { - if (enterAgain) form.submit(); - }, [enterAgain, form]); + useEffect(() => { + if (open) { + form.resetFields(); + form.resetFields(); + form.setFieldsValue(context); + } + }, [open, form, context]); - return ( - form.submit()} - width="50%" - onCancel={handleCancel} - okButtonProps={{ - loading: loading, - }} - afterClose={() => form.resetFields()} - footer={ - + useEffect(() => { + if (enterAgain) form.submit(); + }, [enterAgain, form]); + + return ( + form.submit()} + width="50%" + onCancel={handleCancel} + okButtonProps={{ + loading: loading + }} + afterClose={() => form.resetFields()} + footer={ + - {paymentModal.context && paymentModal.context.id ? null : ( - - )} - - } - > - {!context || (context && !context.id) ? null : ( - - - - - )} - -
{ + setEnterAgain(true); + }} > - - -
- ); + {t("general.actions.saveandnew")} + + )} +
+ } + > + {!context || (context && !context.id) ? null : ( + + + + + )} + +
+ + +
+ ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(PaymentModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(PaymentModalContainer); diff --git a/client/src/components/payment-reexport-button/payment-reexport-button.component.jsx b/client/src/components/payment-reexport-button/payment-reexport-button.component.jsx index e61d7cada..2c45785f6 100644 --- a/client/src/components/payment-reexport-button/payment-reexport-button.component.jsx +++ b/client/src/components/payment-reexport-button/payment-reexport-button.component.jsx @@ -1,66 +1,61 @@ import React from "react"; -import {Button, notification} from "antd"; -import {useTranslation} from "react-i18next"; -import {UPDATE_PAYMENT} from "../../graphql/payments.queries"; -import {useMutation} from "@apollo/client"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {connect} from "react-redux"; +import { Button, notification } from "antd"; +import { useTranslation } from "react-i18next"; +import { UPDATE_PAYMENT } from "../../graphql/payments.queries"; +import { useMutation } from "@apollo/client"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { connect } from "react-redux"; const mapDispatchToProps = (dispatch) => ({ - setPaymentContext: (context) => - dispatch(setModalContext({context: context, modal: "payment"})), + setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })) }); -const PaymentReexportButton = ({payment, refetch, setPaymentContext}) => { - const {t} = useTranslation(); - const [updatePayment, {loading}] = useMutation(UPDATE_PAYMENT); +const PaymentReexportButton = ({ payment, refetch, setPaymentContext }) => { + const { t } = useTranslation(); + const [updatePayment, { loading }] = useMutation(UPDATE_PAYMENT); - const handleClick = async () => { - const paymentUpdateResponse = await updatePayment({ - variables: { - paymentId: payment.id, - payment: { - exportedat: null, - }, - }, - }); - - if (!!!paymentUpdateResponse.errors) { - notification.open({ - type: "success", - key: "paymentsuccessexport", - message: t("payments.successes.markreexported"), - }); - - if (refetch) refetch(); - - setPaymentContext({ - actions: { - refetch, - }, - context: { - ...payment, - exportedat: null, - }, - }); - } else { - notification["error"]({ - message: t("payments.errors.exporting", { - error: JSON.stringify(paymentUpdateResponse.error), - }), - }); + const handleClick = async () => { + const paymentUpdateResponse = await updatePayment({ + variables: { + paymentId: payment.id, + payment: { + exportedat: null } - }; + } + }); - return ( - - ); + if (!!!paymentUpdateResponse.errors) { + notification.open({ + type: "success", + key: "paymentsuccessexport", + message: t("payments.successes.markreexported") + }); + + if (refetch) refetch(); + + setPaymentContext({ + actions: { + refetch + }, + context: { + ...payment, + exportedat: null + } + }); + } else { + notification["error"]({ + message: t("payments.errors.exporting", { + error: JSON.stringify(paymentUpdateResponse.error) + }) + }); + } + }; + + return ( + + ); }; export default connect(null, mapDispatchToProps)(PaymentReexportButton); diff --git a/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx b/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx index c80c1c1db..ba3334d14 100644 --- a/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx +++ b/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx @@ -1,217 +1,189 @@ -import {useMutation} from "@apollo/client"; -import {Button, notification} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, notification } from "antd"; import axios from "axios"; import _ from "lodash"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries"; -import {UPDATE_PAYMENTS} from "../../graphql/payments.queries"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { UPDATE_PAYMENTS } from "../../graphql/payments.queries"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import client from "../../utils/GraphQLClient"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); function updatePaymentCache(items) { - client.cache.modify({ - id: "ROOT_QUERY", - fields: { - payments(existingJobs = []) { - return existingJobs.filter( - (paymentRef) => paymentRef.__ref.includes(items) === false - ); - }, - }, - }); + client.cache.modify({ + id: "ROOT_QUERY", + fields: { + payments(existingJobs = []) { + return existingJobs.filter((paymentRef) => paymentRef.__ref.includes(items) === false); + } + } + }); } export function PaymentsExportAllButton({ - bodyshop, - currentUser, - paymentIds, - disabled, - loadingCallback, - completedCallback, - refetch, - }) { - const {t} = useTranslation(); - const [updatePayments] = useMutation(UPDATE_PAYMENTS); - const [loading, setLoading] = useState(false); - const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); + bodyshop, + currentUser, + paymentIds, + disabled, + loadingCallback, + completedCallback, + refetch +}) { + const { t } = useTranslation(); + const [updatePayments] = useMutation(UPDATE_PAYMENTS); + const [loading, setLoading] = useState(false); + const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); - const handleQbxml = async () => { - setLoading(true); - if (!!loadingCallback) loadingCallback(true); - let PartnerResponse; - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { - PartnerResponse = await axios.post(`/qbo/payments`, { - payments: paymentIds, - elgen: true, - }); - } else { - let QbXmlResponse; - try { - QbXmlResponse = await axios.post("/accounting/qbxml/payments", { - payments: paymentIds, - }); - } catch (error) { - console.log("Error getting QBXML from Server.", error); - notification["error"]({ - message: t("payments.errors.exporting", { - error: "Unable to retrieve QBXML. " + JSON.stringify(error.message), - }), - }); - if (loadingCallback) loadingCallback(false); - setLoading(false); - return; - } - - try { - PartnerResponse = await axios.post( - "http://localhost:1337/qb/", - QbXmlResponse.data - ); - } catch (error) { - console.log("Error connecting to quickbooks or partner.", error); - notification["error"]({ - message: t("payments.errors.exporting-partner"), - }); - if (!!loadingCallback) loadingCallback(false); - setLoading(false); - return; - } - } - - const groupedData = _.groupBy( - PartnerResponse.data, - bodyshop.accountingconfig.qbo ? "paymentid" : "id" - ); - const proms = []; - Object.keys(groupedData).forEach((key) => { - proms.push( - (async () => { - const failedTransactions = groupedData[key].filter((r) => !r.success); - const successfulTransactions = groupedData[key].filter( - (r) => r.success - ); - if (failedTransactions.length > 0) { - //Uh oh. At least one was no good. - failedTransactions.map((ft) => - notification["error"]({ - message: t("payments.errors.exporting", { - error: ft.errorMessage || "", - }), - }) - ); - if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { - //QBO Logs are handled server side. - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - paymentid: key, - successful: false, - message: JSON.stringify( - failedTransactions.map((ft) => ft.errorMessage) - ), - useremail: currentUser.email, - }, - ], - }, - }); - } - } else { - if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { - //QBO Logs are handled server side. - - await insertExportLog({ - variables: { - logs: [ - { - bodyshopid: bodyshop.id, - paymentid: key, - successful: true, - useremail: currentUser.email, - }, - ], - }, - }); - const paymentUpdateResponse = await updatePayments({ - variables: { - paymentIdList: successfulTransactions.map( - (st) => - st[ - bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo - ? "paymentid" - : "id" - ] - ), - payment: { - exportedat: new Date(), - }, - }, - }); - if (!!!paymentUpdateResponse.errors) { - notification.open({ - type: "success", - key: "paymentsuccessexport", - message: t("payments.successes.exported"), - }); - updatePaymentCache( - paymentUpdateResponse.data.update_payments.returning.map( - (payment) => payment.id - ) - ); - } else { - notification["error"]({ - message: t("payments.errors.exporting", { - error: JSON.stringify(paymentUpdateResponse.error), - }), - }); - } - } - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { - notification.open({ - type: "success", - key: "paymentsuccessexport", - message: t("payments.successes.exported"), - }); - updatePaymentCache([ - ...new Set( - successfulTransactions.map( - (st) => - st[ - bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo - ? "paymentid" - : "id" - ] - ) - ), - ]); - } - } - })() - ); + const handleQbxml = async () => { + setLoading(true); + if (!!loadingCallback) loadingCallback(true); + let PartnerResponse; + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + PartnerResponse = await axios.post(`/qbo/payments`, { + payments: paymentIds, + elgen: true + }); + } else { + let QbXmlResponse; + try { + QbXmlResponse = await axios.post("/accounting/qbxml/payments", { + payments: paymentIds + }); + } catch (error) { + console.log("Error getting QBXML from Server.", error); + notification["error"]({ + message: t("payments.errors.exporting", { + error: "Unable to retrieve QBXML. " + JSON.stringify(error.message) + }) + }); + if (loadingCallback) loadingCallback(false); + setLoading(false); + return; + } + + try { + PartnerResponse = await axios.post("http://localhost:1337/qb/", QbXmlResponse.data); + } catch (error) { + console.log("Error connecting to quickbooks or partner.", error); + notification["error"]({ + message: t("payments.errors.exporting-partner") }); - await Promise.all(proms); - if (!!completedCallback) completedCallback([]); if (!!loadingCallback) loadingCallback(false); setLoading(false); - }; + return; + } + } - return ( - - ); + const groupedData = _.groupBy(PartnerResponse.data, bodyshop.accountingconfig.qbo ? "paymentid" : "id"); + const proms = []; + Object.keys(groupedData).forEach((key) => { + proms.push( + (async () => { + const failedTransactions = groupedData[key].filter((r) => !r.success); + const successfulTransactions = groupedData[key].filter((r) => r.success); + if (failedTransactions.length > 0) { + //Uh oh. At least one was no good. + failedTransactions.map((ft) => + notification["error"]({ + message: t("payments.errors.exporting", { + error: ft.errorMessage || "" + }) + }) + ); + if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { + //QBO Logs are handled server side. + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + paymentid: key, + successful: false, + message: JSON.stringify(failedTransactions.map((ft) => ft.errorMessage)), + useremail: currentUser.email + } + ] + } + }); + } + } else { + if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) { + //QBO Logs are handled server side. + + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + paymentid: key, + successful: true, + useremail: currentUser.email + } + ] + } + }); + const paymentUpdateResponse = await updatePayments({ + variables: { + paymentIdList: successfulTransactions.map( + (st) => st[bodyshop.accountingconfig && bodyshop.accountingconfig.qbo ? "paymentid" : "id"] + ), + payment: { + exportedat: new Date() + } + } + }); + if (!!!paymentUpdateResponse.errors) { + notification.open({ + type: "success", + key: "paymentsuccessexport", + message: t("payments.successes.exported") + }); + updatePaymentCache(paymentUpdateResponse.data.update_payments.returning.map((payment) => payment.id)); + } else { + notification["error"]({ + message: t("payments.errors.exporting", { + error: JSON.stringify(paymentUpdateResponse.error) + }) + }); + } + } + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { + notification.open({ + type: "success", + key: "paymentsuccessexport", + message: t("payments.successes.exported") + }); + updatePaymentCache([ + ...new Set( + successfulTransactions.map( + (st) => st[bodyshop.accountingconfig && bodyshop.accountingconfig.qbo ? "paymentid" : "id"] + ) + ) + ]); + } + } + })() + ); + }); + await Promise.all(proms); + if (!!completedCallback) completedCallback([]); + if (!!loadingCallback) loadingCallback(false); + setLoading(false); + }; + + return ( + + ); } export default connect(mapStateToProps, null)(PaymentsExportAllButton); 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 2496b10b0..092063d08 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 @@ -1,154 +1,144 @@ -import {CopyFilled} from "@ant-design/icons"; -import {Button, Form, message, Popover, Space} from "antd"; +import { CopyFilled } from "@ant-design/icons"; +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 {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {openChatByPhone, setMessage,} from "../../redux/messaging/messaging.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { parsePhoneNumber } from "libphonenumber-js"; +import React, { 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 } from "../../redux/user/user.selectors"; import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), - setMessage: (text) => dispatch(setMessage(text)), + openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), + setMessage: (text) => dispatch(setMessage(text)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(PaymentsGenerateLink); +export default connect(mapStateToProps, mapDispatchToProps)(PaymentsGenerateLink); -export function PaymentsGenerateLink({ - bodyshop, - callback, - job, - openChatByPhone, - setMessage, - }) { - const {t} = useTranslation(); - const [form] = Form.useForm(); +export function PaymentsGenerateLink({ bodyshop, callback, job, openChatByPhone, setMessage }) { + const { t } = useTranslation(); + const [form] = Form.useForm(); - const [open, setOpen] = useState(false); - const [loading, setLoading] = useState(false); - const [paymentLink, setPaymentLink] = useState(null); + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); + const [paymentLink, setPaymentLink] = useState(null); - const handleFinish = async ({amount}) => { - setLoading(true); + const handleFinish = async ({ amount }) => { + setLoading(true); - const p = parsePhoneNumber(job.ownr_ph1, "CA"); - setLoading(true); - const response = await axios.post("/intellipay/generate_payment_url", { - bodyshop, - amount: amount, - account: job.ro_number, - invoice: job.id, - }); - setLoading(false); - setPaymentLink(response.data.shorUrl); + const p = parsePhoneNumber(job.ownr_ph1, "CA"); + setLoading(true); + const response = await axios.post("/intellipay/generate_payment_url", { + bodyshop, + amount: amount, + account: job.ro_number, + invoice: job.id + }); + setLoading(false); + setPaymentLink(response.data.shorUrl); - openChatByPhone({ - phone_num: p.formatInternational(), - jobid: job.id, - }); - setMessage( - t("payments.labels.smspaymentreminder", { - shopname: bodyshop.shopname, - amount: amount, - payment_link: response.data.shorUrl, - }) - ); + openChatByPhone({ + phone_num: p.formatInternational(), + jobid: job.id + }); + setMessage( + t("payments.labels.smspaymentreminder", { + shopname: bodyshop.shopname, + amount: amount, + payment_link: response.data.shorUrl + }) + ); - //Add in confirmation & errors. - if (callback) callback(); + //Add in confirmation & errors. + if (callback) callback(); - // setOpen(false); - setLoading(false); - }; + // setOpen(false); + setLoading(false); + }; - const popContent = ( -
-
- - - - {paymentLink && ( - - { - navigator.clipboard.writeText(paymentLink); - message.success(t("general.actions.copied")); - }} - > -
{ - //Copy the link. - }} - > - {paymentLink} -
- {" "} - -
- -
- )} - - - - + const popContent = ( +
+
+ + + + {paymentLink && ( + + { + navigator.clipboard.writeText(paymentLink); + message.success(t("general.actions.copied")); + }} + > +
{ + //Copy the link. + }} + > + {paymentLink} +
{" "} +
-
- ); - - return ( - - - - ); +
+ )} + + + + + +
+ ); + + return ( + + + + ); } diff --git a/client/src/components/payments-list-paginated/payment-list-paginated.component.jsx b/client/src/components/payments-list-paginated/payment-list-paginated.component.jsx index 9403f66fa..1a5dd6774 100644 --- a/client/src/components/payments-list-paginated/payment-list-paginated.component.jsx +++ b/client/src/components/payments-list-paginated/payment-list-paginated.component.jsx @@ -1,308 +1,290 @@ -import {EditFilled, SyncOutlined} from "@ant-design/icons"; -import {useApolloClient} from "@apollo/client"; -import {Button, Card, Input, Space, Table, Typography} from "antd"; +import { EditFilled, SyncOutlined } from "@ant-design/icons"; +import { useApolloClient } from "@apollo/client"; +import { Button, Card, Input, Space, Table, Typography } from "antd"; import axios from "axios"; import queryString from "query-string"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {QUERY_PAYMENT_BY_ID} from "../../graphql/payments.queries"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { QUERY_PAYMENT_BY_ID } from "../../graphql/payments.queries"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {DateFormatter, DateTimeFormatter} from "../../utils/DateFormatter"; -import {TemplateList} from "../../utils/TemplateConstants"; -import {alphaSort} from "../../utils/sorters"; +import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter"; +import { TemplateList } from "../../utils/TemplateConstants"; +import { alphaSort } from "../../utils/sorters"; import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; -import {pageLimit} from "../../utils/config"; +import { pageLimit } from "../../utils/config"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setPaymentContext: (context) => - dispatch(setModalContext({context: context, modal: "payment"})), - setCaBcEtfTableContext: (context) => - dispatch( - setModalContext({context: context, modal: "ca_bc_eftTableConvert"}) - ), + setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })), + setCaBcEtfTableContext: (context) => dispatch(setModalContext({ context: context, modal: "ca_bc_eftTableConvert" })) }); export function PaymentsListPaginated({ - setPaymentContext, - setCaBcEtfTableContext, - refetch, - loading, - payments, - total, - bodyshop, - }) { - const search = queryString.parse(useLocation().search); - const [openSearchResults, setOpenSearchResults] = useState([]); - const [searchLoading, setSearchLoading] = useState(false); - const {page, sortcolumn, sortorder} = search; - const client = useApolloClient(); - const history = useNavigate(); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); - const Templates = TemplateList("payment"); - const {t} = useTranslation(); - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - // sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), - // sortOrder: sortcolumn === "ro_number" && sortorder, - render: (text, record) => { - return record.job ? ( - - {record.job.ro_number || t("general.labels.na")} - - ) : ( - {t("general.labels.na")} - ); - }, - }, - { - title: t("payments.fields.paymentnum"), - dataIndex: "paymentnum", - key: "paymentnum", - sorter: (a, b) => alphaSort(a.paymentnum, b.paymentnum), - sortOrder: sortcolumn === "paymentnum" && sortorder, - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - ellipsis: true, - // sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln), - // sortOrder: sortcolumn === "owner" && sortorder, - render: (text, record) => { - return record.job?.owner ? ( - - - - ) : ( - - + setPaymentContext, + setCaBcEtfTableContext, + refetch, + loading, + payments, + total, + bodyshop +}) { + const search = queryString.parse(useLocation().search); + const [openSearchResults, setOpenSearchResults] = useState([]); + const [searchLoading, setSearchLoading] = useState(false); + const { page, sortcolumn, sortorder } = search; + const client = useApolloClient(); + const history = useNavigate(); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); + const Templates = TemplateList("payment"); + const { t } = useTranslation(); + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + // sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), + // sortOrder: sortcolumn === "ro_number" && sortorder, + render: (text, record) => { + return record.job ? ( + {record.job.ro_number || t("general.labels.na")} + ) : ( + {t("general.labels.na")} + ); + } + }, + { + title: t("payments.fields.paymentnum"), + dataIndex: "paymentnum", + key: "paymentnum", + sorter: (a, b) => alphaSort(a.paymentnum, b.paymentnum), + sortOrder: sortcolumn === "paymentnum" && sortorder + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + // sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln), + // sortOrder: sortcolumn === "owner" && sortorder, + render: (text, record) => { + return record.job?.owner ? ( + + + + ) : ( + + - ); - }, - }, - { - title: t("payments.fields.date"), - dataIndex: "date", - key: "date", - sorter: (a, b) => alphaSort(a.date, b.date), - sortOrder: - state.sortedInfo.columnKey === "date" && state.sortedInfo.order, - render: (text, record) => {record.date}, - }, - { - title: t("payments.fields.amount"), - dataIndex: "amount", - key: "amount", - render: (text, record) => ( - {record.amount} - ), - }, - { - title: t("payments.fields.memo"), - dataIndex: "memo", - key: "memo", - }, - { - title: t("payments.fields.payer"), - dataIndex: "payer", - key: "payer", - }, - { - title: t("payments.fields.type"), - dataIndex: "type", - key: "type", - }, - { - title: t("payments.fields.transactionid"), - dataIndex: "transactionid", - key: "transactionid", - }, - { - title: t("payments.fields.created_at"), - dataIndex: "created_at", - key: "created_at", - render: (text, record) => ( - {record.created_at} - ), - }, - { - title: t("payments.fields.exportedat"), - dataIndex: "exportedat", - key: "exportedat", - render: (text, record) => ( - {record.exportedat} - ), - }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", - render: (text, record) => ( - - - - - ), - }, - ]; - - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - search.page = pagination.current; - search.sortcolumn = sorter.columnKey; - search.sortorder = sorter.order; - history({search: queryString.stringify(search)}); - }; - - useEffect(() => { - if (search.search && search.search.trim() !== "") { - searchPayments(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - async function searchPayments(value) { - try { - setSearchLoading(true); - const searchData = await axios.post("/search", { - search: value || search.search, - index: "payments", - }); - setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source)); - } catch (error) { - console.log("Error while fetching search results", error); - } finally { - setSearchLoading(false); - } + return updatedRecord; + }) + ); + } + : refetch + }, + context: apolloResults ? apolloResults : record + }); + }} + > + + + + + ) } + ]; - return ( - - {search.search && ( - <> - - {t("general.labels.searchresults", {search: search.search})} - - - - )} - {bodyshop.region_config === "CA_BC" && ( - <> - - - - )} - - { - search.search = value; - history({search: queryString.stringify(search)}); - searchPayments(value); - }} - loading={loading || searchLoading} - enterButton - /> - - } - > -
- - ); + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + search.page = pagination.current; + search.sortcolumn = sorter.columnKey; + search.sortorder = sorter.order; + history({ search: queryString.stringify(search) }); + }; + + useEffect(() => { + if (search.search && search.search.trim() !== "") { + searchPayments(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + async function searchPayments(value) { + try { + setSearchLoading(true); + const searchData = await axios.post("/search", { + search: value || search.search, + index: "payments" + }); + setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source)); + } catch (error) { + console.log("Error while fetching search results", error); + } finally { + setSearchLoading(false); + } + } + + return ( + + {search.search && ( + <> + + {t("general.labels.searchresults", { search: search.search })} + + + + )} + {bodyshop.region_config === "CA_BC" && ( + <> + + + + )} + + { + search.search = value; + history({ search: queryString.stringify(search) }); + searchPayments(value); + }} + loading={loading || searchLoading} + enterButton + /> + + } + > +
+ + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(PaymentsListPaginated); +export default connect(mapStateToProps, mapDispatchToProps)(PaymentsListPaginated); diff --git a/client/src/components/phonebook-form/phonebook-form.component.jsx b/client/src/components/phonebook-form/phonebook-form.component.jsx index aef4d1ab3..fd0f7a330 100644 --- a/client/src/components/phonebook-form/phonebook-form.component.jsx +++ b/client/src/components/phonebook-form/phonebook-form.component.jsx @@ -1,188 +1,148 @@ -import {Button, Form, Input, Space} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import { Button, Form, Input, Space } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { - selectAuthLevel, - selectBodyshop, -} from "../../redux/user/user.selectors"; +import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors"; import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component"; -import PhoneFormItem, {PhoneItemFormatterValidation,} from "../form-items-formatted/phone-form-item.component"; +import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; -import {HasRbacAccess} from "../rbac-wrapper/rbac-wrapper.component"; +import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component"; const mapStateToProps = createStructuredSelector({ - authLevel: selectAuthLevel, - bodyshop: selectBodyshop, + authLevel: selectAuthLevel, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(PhonebookFormComponent); +export default connect(mapStateToProps, mapDispatchToProps)(PhonebookFormComponent); -export function PhonebookFormComponent({ - authLevel, - bodyshop, - form, - formLoading, - handleDelete, - }) { - const {t} = useTranslation(); +export function PhonebookFormComponent({ authLevel, bodyshop, form, formLoading, handleDelete }) { + const { t } = useTranslation(); - const {getFieldValue} = form; + const { getFieldValue } = form; - const hasNoAccess = !HasRbacAccess({ - bodyshop, - authLevel, - action: "phonebook:edit", - }); + const hasNoAccess = !HasRbacAccess({ + bodyshop, + authLevel, + action: "phonebook:edit" + }); - return ( -
- - {() =>`${form.getFieldValue("firstname") || ""} ${ - form.getFieldValue("lastname") || "" - }${ - form.getFieldValue("company") - ? ` - ${form.getFieldValue("company")}` - : ""}` + return ( +
+ + {() => + `${form.getFieldValue("firstname") || ""} ${form.getFieldValue("lastname") || ""}${ + form.getFieldValue("company") ? ` - ${form.getFieldValue("company")}` : "" + }` } - } - extra={ - - - - - } - /> - - - - - - - - - ({ - validator(rule, value) { - const {firstname, lastname, company} = getFieldsValue([ - "firstname", - "lastname", - "company", - ]); + } + extra={ + + + + + } + /> + + + + + + + + + ({ + 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")); - }, - }), - ]} - > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PhoneItemFormatterValidation(getFieldValue, "phone1"), - ]} - > - - - - PhoneItemFormatterValidation(getFieldValue, "phone2"), - ]} - > - - - - PhoneItemFormatterValidation(getFieldValue, "fax"), - ]} - > - - - -
- ); + if ( + (firstname && firstname.trim() !== "") || + (lastname && lastname.trim() !== "") || + (company && company.trim() !== "") + ) { + return Promise.resolve(); + } + return Promise.reject(t("phonebook.labels.onenamerequired")); + } + }) + ]} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PhoneItemFormatterValidation(getFieldValue, "phone1")]} + > + + + PhoneItemFormatterValidation(getFieldValue, "phone2")]} + > + + + PhoneItemFormatterValidation(getFieldValue, "fax")]} + > + + + +
+ ); } diff --git a/client/src/components/phonebook-form/phonebook-form.container.jsx b/client/src/components/phonebook-form/phonebook-form.container.jsx index b0af13d3c..78db9920e 100644 --- a/client/src/components/phonebook-form/phonebook-form.container.jsx +++ b/client/src/components/phonebook-form/phonebook-form.container.jsx @@ -1,155 +1,151 @@ -import {useMutation, useQuery} from "@apollo/client"; -import {Form, notification} from "antd"; +import { useMutation, useQuery } from "@apollo/client"; +import { Form, notification } from "antd"; import queryString from "query-string"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import { - DELETE_PHONEBOOK, - INSERT_NEW_PHONEBOOK, - QUERY_PHONEBOOK_BY_ID, - UPDATE_PHONEBOOK, + DELETE_PHONEBOOK, + INSERT_NEW_PHONEBOOK, + QUERY_PHONEBOOK_BY_ID, + UPDATE_PHONEBOOK } from "../../graphql/phonebook.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import PhonebookFormComponent from "./phonebook-form.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -function PhonebookFormContainer({refetch, bodyshop}) { - const history = useNavigate(); - const search = queryString.parse(useLocation().search); - const {phonebookentry} = search; - const [formLoading, setFormLoading] = useState(false); - const [form] = Form.useForm(); - const {t} = useTranslation(); - const {loading, error, data} = useQuery(QUERY_PHONEBOOK_BY_ID, { - variables: {id: phonebookentry}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - skip: !!!phonebookentry || phonebookentry === "new", +function PhonebookFormContainer({ refetch, bodyshop }) { + const history = useNavigate(); + const search = queryString.parse(useLocation().search); + const { phonebookentry } = search; + const [formLoading, setFormLoading] = useState(false); + const [form] = Form.useForm(); + const { t } = useTranslation(); + const { loading, error, data } = useQuery(QUERY_PHONEBOOK_BY_ID, { + variables: { id: phonebookentry }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + skip: !!!phonebookentry || phonebookentry === "new" + }); + + const [updatePhonebook] = useMutation(UPDATE_PHONEBOOK); + const [insertPhonebook] = useMutation(INSERT_NEW_PHONEBOOK); + const [deletePhonebook] = useMutation(DELETE_PHONEBOOK); + + const handleDelete = async () => { + setFormLoading(true); + const result = await deletePhonebook({ + variables: { id: phonebookentry }, + refetchQueries: ["QUERY_PHONEBOOK_PAGINATED"] }); - const [updatePhonebook] = useMutation(UPDATE_PHONEBOOK); - const [insertPhonebook] = useMutation(INSERT_NEW_PHONEBOOK); - const [deletePhonebook] = useMutation(DELETE_PHONEBOOK); + if (!result.errors) { + notification["success"]({ + message: t("phonebook.successes.deleted") + }); + delete search.phonebookentry; + history({ search: queryString.stringify(search) }); + if (refetch) + refetch().then((r) => { + form.resetFields(); + }); + } else { + notification["error"]({ + message: t("phonebook.errors.deleting", { + message: JSON.stringify(result.errors) + }) + }); + } - const handleDelete = async () => { - setFormLoading(true); - const result = await deletePhonebook({ - variables: {id: phonebookentry}, - refetchQueries: ["QUERY_PHONEBOOK_PAGINATED"], + setFormLoading(false); + }; + + const handleFinish = async (values) => { + setFormLoading(true); + if (phonebookentry && phonebookentry !== "new") { + //It's a phonebook to update. + const result = await updatePhonebook({ + variables: { id: phonebookentry, phonebook: values }, + refetchQueries: ["QUERY_PHONEBOOK_PAGINATED"] + }); + + if (!result.errors) { + notification["success"]({ + message: t("phonebook.successes.saved") }); - if (!result.errors) { - notification["success"]({ - message: t("phonebook.successes.deleted"), - }); - delete search.phonebookentry; - history({search: queryString.stringify(search)}); - if (refetch) - refetch().then((r) => { - form.resetFields(); - }); - } else { - notification["error"]({ - message: t("phonebook.errors.deleting", { - message: JSON.stringify(result.errors), - }), - }); - } + if (refetch) await refetch(); + form.setFieldsValue(data.phonebook_by_pk); + form.resetFields(); setFormLoading(false); - }; + } else { + notification["error"]({ + message: t("phonebook.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); - const handleFinish = async (values) => { - setFormLoading(true); - if (phonebookentry && phonebookentry !== "new") { - //It's a phonebook to update. - const result = await updatePhonebook({ - variables: {id: phonebookentry, phonebook: values}, - refetchQueries: ["QUERY_PHONEBOOK_PAGINATED"], - }); + setFormLoading(false); + } + } else { + //It's a new phonebook to insert. + const result = await insertPhonebook({ + variables: { + phonebook_entry: [{ ...values, bodyshopid: bodyshop.id }] + }, + refetchQueries: ["QUERY_PHONEBOOK_PAGINATED"] + }); - if (!result.errors) { - notification["success"]({ - message: t("phonebook.successes.saved"), - }); + if (!result.errors) { + notification["success"]({ + message: t("phonebook.successes.saved") + }); - if (refetch) await refetch(); - form.setFieldsValue(data.phonebook_by_pk); - form.resetFields(); + if (refetch) await refetch(); + form.resetFields(); + form.resetFields(); + delete search.phonebookentry; + history({ search: queryString.stringify(search) }); + setFormLoading(false); + } else { + notification["error"]({ + message: t("phonebook.errors.saving") + }); + setFormLoading(false); + } + } + }; - setFormLoading(false); - } else { - notification["error"]({ - message: t("phonebook.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); + useEffect(() => { + if (data || phonebookentry === "new") form.resetFields(); + }, [data, form, phonebookentry]); - setFormLoading(false); - } - } else { - //It's a new phonebook to insert. - const result = await insertPhonebook({ - variables: { - phonebook_entry: [{...values, bodyshopid: bodyshop.id}], - }, - refetchQueries: ["QUERY_PHONEBOOK_PAGINATED"], - }); + if (loading) return ; + if (error) return ; - if (!result.errors) { - notification["success"]({ - message: t("phonebook.successes.saved"), - }); - - if (refetch) await refetch(); - form.resetFields(); - form.resetFields(); - delete search.phonebookentry; - history({search: queryString.stringify(search)}); - setFormLoading(false); - } else { - notification["error"]({ - message: t("phonebook.errors.saving"), - }); - setFormLoading(false); - } - } - }; - - useEffect(() => { - if (data || phonebookentry === "new") form.resetFields(); - }, [data, form, phonebookentry]); - - if (loading) return ; - if (error) return ; - - return ( -
- {phonebookentry ? ( - - ) : ( - t("phonebook.labels.noneselected") - )} - - ); + return ( +
+ {phonebookentry ? ( + + ) : ( + t("phonebook.labels.noneselected") + )} + + ); } export default connect(mapStateToProps, null)(PhonebookFormContainer); diff --git a/client/src/components/print-center-item/print-center-item.component.jsx b/client/src/components/print-center-item/print-center-item.component.jsx index 134f2d81e..ff0dfcbb2 100644 --- a/client/src/components/print-center-item/print-center-item.component.jsx +++ b/client/src/components/print-center-item/print-center-item.component.jsx @@ -1,78 +1,75 @@ -import {MailOutlined, PrinterOutlined} from "@ant-design/icons"; -import {Space, Spin} from "antd"; -import React, {useState} from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {setEmailOptions} from "../../redux/email/email.actions"; -import {selectPrintCenter} from "../../redux/modals/modals.selectors"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {GenerateDocument} from "../../utils/RenderTemplate"; +import { MailOutlined, PrinterOutlined } from "@ant-design/icons"; +import { Space, Spin } from "antd"; +import React, { useState } from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { setEmailOptions } from "../../redux/email/email.actions"; +import { selectPrintCenter } from "../../redux/modals/modals.selectors"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { GenerateDocument } from "../../utils/RenderTemplate"; const mapStateToProps = createStructuredSelector({ - printCenterModal: selectPrintCenter, - bodyshop: selectBodyshop, - technician: selectTechnician, + printCenterModal: selectPrintCenter, + bodyshop: selectBodyshop, + technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ - setEmailOptions: (e) => dispatch(setEmailOptions(e)), + setEmailOptions: (e) => dispatch(setEmailOptions(e)) }); export function PrintCenterItemComponent({ - printCenterModal, - setEmailOptions, - item, - id, - bodyshop, - disabled, - technician, - }) { - const [loading, setLoading] = useState(false); - const {context} = printCenterModal; - const renderToNewWindow = async () => { - setLoading(true); - await GenerateDocument( - { - name: item.key, - variables: {id: id}, - }, - {}, - "p" - ); - setLoading(false); - }; - - if (disabled) return
  • {item.title}
  • ; - return ( -
  • - - {item.title} - - {!technician ? ( - { - GenerateDocument( - { - name: item.key, - variables: {id: id}, - }, - { - to: context.job && context.job.ownr_ea, - subject: item.subject, - }, - "e", - id - ); - }} - /> - ) : null} - {loading && } - -
  • + printCenterModal, + setEmailOptions, + item, + id, + bodyshop, + disabled, + technician +}) { + const [loading, setLoading] = useState(false); + const { context } = printCenterModal; + const renderToNewWindow = async () => { + setLoading(true); + await GenerateDocument( + { + name: item.key, + variables: { id: id } + }, + {}, + "p" ); + setLoading(false); + }; + + if (disabled) return
  • {item.title}
  • ; + return ( +
  • + + {item.title} + + {!technician ? ( + { + GenerateDocument( + { + name: item.key, + variables: { id: id } + }, + { + to: context.job && context.job.ownr_ea, + subject: item.subject + }, + "e", + id + ); + }} + /> + ) : null} + {loading && } + +
  • + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(PrintCenterItemComponent); +export default connect(mapStateToProps, mapDispatchToProps)(PrintCenterItemComponent); diff --git a/client/src/components/print-center-jobs-labels/print-center-jobs-labels.component.jsx b/client/src/components/print-center-jobs-labels/print-center-jobs-labels.component.jsx index 9ccb08027..fd53d25ab 100644 --- a/client/src/components/print-center-jobs-labels/print-center-jobs-labels.component.jsx +++ b/client/src/components/print-center-jobs-labels/print-center-jobs-labels.component.jsx @@ -1,124 +1,110 @@ -import {Button, Card, Form, InputNumber, notification, Popover, Radio,} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { Button, Card, Form, InputNumber, notification, Popover, Radio } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(PrintCenterJobsLabels); +export default connect(mapStateToProps, mapDispatchToProps)(PrintCenterJobsLabels); -export function PrintCenterJobsLabels({bodyshop, jobId}) { - const [isModalVisible, setIsModalVisible] = useState(false); - const [loading, setLoading] = useState(false); - const {t} = useTranslation(); - const [form] = Form.useForm(); +export function PrintCenterJobsLabels({ bodyshop, jobId }) { + const [isModalVisible, setIsModalVisible] = useState(false); + const [loading, setLoading] = useState(false); + const { t } = useTranslation(); + const [form] = Form.useForm(); - const handleOk = (e) => { - e.stopPropagation(); - form.submit(); - }; + const handleOk = (e) => { + e.stopPropagation(); + form.submit(); + }; - const handleCancel = () => { - setIsModalVisible(false); - setLoading(false); - }; - const handleFinish = async ({template, ...values}) => { - const {sendtype, ...restVals} = values; - setLoading(true); - try { - await GenerateDocument( - { - name: TemplateList("job_special")[template].key, - variables: {id: jobId}, - context: restVals, - }, - {}, - "p", - jobId - ); - setIsModalVisible(false); - } catch (error) { - notification.open({type: "error", message: JSON.stringify(error)}); - } finally { - setLoading(false); - } + const handleCancel = () => { + setIsModalVisible(false); + setLoading(false); + }; + const handleFinish = async ({ template, ...values }) => { + const { sendtype, ...restVals } = values; + setLoading(true); + try { + await GenerateDocument( + { + name: TemplateList("job_special")[template].key, + variables: { id: jobId }, + context: restVals + }, + {}, + "p", + jobId + ); + setIsModalVisible(false); + } catch (error) { + notification.open({ type: "error", message: JSON.stringify(error) }); + } finally { + setLoading(false); + } - form.resetFields(); - }; + form.resetFields(); + }; - const content = ( - -
    - - - - {t("printcenter.jobs.parts_label_multiple")} - - - {t("printcenter.jobs.folder_label_multiple")} - - - - - - - - - - - - -
    - ); - return ( - - - - ); + const content = ( + +
    + + + {t("printcenter.jobs.parts_label_multiple")} + {t("printcenter.jobs.folder_label_multiple")} + + + + + + + + + + + +
    + ); + return ( + + + + ); } diff --git a/client/src/components/print-center-jobs/print-center-jobs.component.jsx b/client/src/components/print-center-jobs/print-center-jobs.component.jsx index 2616051eb..3465e7ca8 100644 --- a/client/src/components/print-center-jobs/print-center-jobs.component.jsx +++ b/client/src/components/print-center-jobs/print-center-jobs.component.jsx @@ -1,144 +1,118 @@ -import {useSplitTreatments} from "@splitsoftware/splitio-react"; -import {Card, Col, Input, Row, Space, Typography} from "antd"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { Card, Col, Input, Row, Space, Typography } from "antd"; import _ from "lodash"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectPrintCenter} from "../../redux/modals/modals.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {TemplateList} from "../../utils/TemplateConstants"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectPrintCenter } from "../../redux/modals/modals.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { TemplateList } from "../../utils/TemplateConstants"; import Jobd3RdPartyModal from "../job-3rd-party-modal/job-3rd-party-modal.component"; import PrintCenterItem from "../print-center-item/print-center-item.component"; import PrintCenterJobsLabels from "../print-center-jobs-labels/print-center-jobs-labels.component"; import PrintCenterSpeedPrint from "../print-center-speed-print/print-center-speed-print.component"; const mapStateToProps = createStructuredSelector({ - printCenterModal: selectPrintCenter, - bodyshop: selectBodyshop, + printCenterModal: selectPrintCenter, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({}); -export function PrintCenterJobsComponent({printCenterModal, bodyshop}) { - const [search, setSearch] = useState(""); - const {id: jobId, job} = printCenterModal.context; - const tempList = TemplateList("job", {}); - const {t} = useTranslation(); - const {treatments: {Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, - }); +export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) { + const [search, setSearch] = useState(""); + const { id: jobId, job } = printCenterModal.context; + const tempList = TemplateList("job", {}); + const { t } = useTranslation(); + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); + const Templates = + bodyshop.cdk_dealerid === null && bodyshop.pbs_serialnumber === null + ? Object.keys(tempList) + .map((key) => { + return tempList[key]; + }) + .filter( + (temp) => + (!temp.regions || + (temp.regions && temp.regions[bodyshop.region_config]) || + (temp.regions && bodyshop.region_config.includes(Object.keys(temp.regions)) === true)) && + (!temp.dms || temp.dms === false) + ) + : Object.keys(tempList) + .map((key) => { + return tempList[key]; + }) + .filter( + (temp) => + !temp.regions || + (temp.regions && temp.regions[bodyshop.region_config]) || + (temp.regions && bodyshop.region_config.includes(Object.keys(temp.regions)) === true) + ); + const JobsReportsList = + Enhanced_Payroll.treatment === "on" + ? Object.keys(Templates) + .map((key) => { + return Templates[key]; + }) + .filter((temp) => temp.enhanced_payroll === undefined || temp.enhanced_payroll === true) + : Object.keys(Templates) + .map((key) => { + return Templates[key]; + }) + .filter((temp) => temp.enhanced_payroll === undefined || temp.enhanced_payroll === false); + const filteredJobsReportsList = + search !== "" + ? JobsReportsList.filter((r) => r.title.toLowerCase().includes(search.toLowerCase())) + : JobsReportsList; - const Templates = - bodyshop.cdk_dealerid === null && bodyshop.pbs_serialnumber === null - ? Object.keys(tempList) - .map((key) => { - return tempList[key]; - }) - .filter( - (temp) => - (!temp.regions || - (temp.regions && temp.regions[bodyshop.region_config]) || - (temp.regions && - bodyshop.region_config.includes(Object.keys(temp.regions)) === - true)) && - (!temp.dms || temp.dms === false) - ) - : Object.keys(tempList) - .map((key) => { - return tempList[key]; - }) - .filter( - (temp) => - !temp.regions || - (temp.regions && temp.regions[bodyshop.region_config]) || - (temp.regions && - bodyshop.region_config.includes(Object.keys(temp.regions)) === - true) - ); - const JobsReportsList = - Enhanced_Payroll.treatment === "on" - ? Object.keys(Templates) - .map((key) => { - return Templates[key]; - }) - .filter( - (temp) => - temp.enhanced_payroll === undefined || - temp.enhanced_payroll === true - ) - : Object.keys(Templates) - .map((key) => { - return Templates[key]; - }) - .filter( - (temp) => - temp.enhanced_payroll === undefined || - temp.enhanced_payroll === false - ); - const filteredJobsReportsList = - search !== "" - ? JobsReportsList.filter((r) => - r.title.toLowerCase().includes(search.toLowerCase()) - ) - : JobsReportsList; + //Group it, create cards, and then filter out. - //Group it, create cards, and then filter out. + const grouped = _.groupBy(filteredJobsReportsList, "group"); - const grouped = _.groupBy(filteredJobsReportsList, "group"); - - return ( -
    + return ( +
    + +
    + + + + + + + setSearch(e.target.value)} value={search} /> + + } + > - - - - - - - - setSearch(e.target.value)} - value={search} - /> - - } + {Object.keys(grouped).map((key) => ( + + + {t(`printcenter.labels.groups.${key}`)} +
      - - {Object.keys(grouped).map((key) => ( -
    - - - {t(`printcenter.labels.groups.${key}`)} - -
      - {grouped[key].map((item) => ( - - ))} -
    -
    - - ))} - - + {grouped[key].map((item) => ( + + ))} + + + ))} - - ); + + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(PrintCenterJobsComponent); +export default connect(mapStateToProps, mapDispatchToProps)(PrintCenterJobsComponent); diff --git a/client/src/components/print-center-modal/print-center-modal.component.jsx b/client/src/components/print-center-modal/print-center-modal.component.jsx index d1be2a518..868341863 100644 --- a/client/src/components/print-center-modal/print-center-modal.component.jsx +++ b/client/src/components/print-center-modal/print-center-modal.component.jsx @@ -1,23 +1,19 @@ import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import PrintCenterJobs from "../print-center-jobs/print-center-jobs.component"; -export default function PrintCenterModalComponent({context}) { - const {t} = useTranslation(); - const {type} = context; +export default function PrintCenterModalComponent({ context }) { + const { t } = useTranslation(); + const { type } = context; - let ModalContent; + let ModalContent; - switch (type) { - case "job": - ModalContent = PrintCenterJobs; - break; - default: - break; - } - return ( -
    - {ModalContent ? : t("printcenter.errors.nocontexttype")} -
    - ); + switch (type) { + case "job": + ModalContent = PrintCenterJobs; + break; + default: + break; + } + return
    {ModalContent ? : t("printcenter.errors.nocontexttype")}
    ; } diff --git a/client/src/components/print-center-modal/print-center-modal.container.jsx b/client/src/components/print-center-modal/print-center-modal.container.jsx index e99eb87e9..97edbb1e3 100644 --- a/client/src/components/print-center-modal/print-center-modal.container.jsx +++ b/client/src/components/print-center-modal/print-center-modal.container.jsx @@ -1,48 +1,42 @@ -import {Modal} from "antd"; +import { Modal } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectPrintCenter} from "../../redux/modals/modals.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectPrintCenter } from "../../redux/modals/modals.selectors"; import PrintCenterModalComponent from "./print-center-modal.component"; import "./print-center-modal.styles.scss"; const mapStateToProps = createStructuredSelector({ - printCenterModal: selectPrintCenter, + printCenterModal: selectPrintCenter }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("printCenter")), + toggleModalVisible: () => dispatch(toggleModalVisible("printCenter")) }); -export function PrintCenterModalContainer({ - printCenterModal, - toggleModalVisible, - }) { - const {t} = useTranslation(); +export function PrintCenterModalContainer({ printCenterModal, toggleModalVisible }) { + const { t } = useTranslation(); - const {open, context} = printCenterModal; - //const { type } = context; - // const { refetch } = actions; + const { open, context } = printCenterModal; + //const { type } = context; + // const { refetch } = actions; - return ( - toggleModalVisible()} - onCancel={() => toggleModalVisible()} - cancelButtonProps={{style: {display: "none"}}} - okText={t("general.actions.close")} - width="90%" - title={t("printcenter.labels.title")} - destroyOnClose - > - - - ); + return ( + toggleModalVisible()} + onCancel={() => toggleModalVisible()} + cancelButtonProps={{ style: { display: "none" } }} + okText={t("general.actions.close")} + width="90%" + title={t("printcenter.labels.title")} + destroyOnClose + > + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(PrintCenterModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(PrintCenterModalContainer); diff --git a/client/src/components/print-center-speed-print/print-center-speed-print.component.jsx b/client/src/components/print-center-speed-print/print-center-speed-print.component.jsx index e78596d59..ffbe2d161 100644 --- a/client/src/components/print-center-speed-print/print-center-speed-print.component.jsx +++ b/client/src/components/print-center-speed-print/print-center-speed-print.component.jsx @@ -1,89 +1,72 @@ -import {Button, List} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import { Button, List } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {GenerateDocuments} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { GenerateDocuments } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, //currentUser: selectCurrentUser + bodyshop: selectBodyshop //currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function PrintCenterSpeedPrint({bodyshop, jobId}) { - const [loading, setLoading] = useState(false); - const {speedprint} = bodyshop; - const {t} = useTranslation(); +export function PrintCenterSpeedPrint({ bodyshop, jobId }) { + const [loading, setLoading] = useState(false); + const { speedprint } = bodyshop; + const { t } = useTranslation(); - const renderAllTemplates = async (templateKeys) => { - logImEXEvent("speed_print_render_all_templates"); - setLoading(true); - await GenerateDocuments( - templateKeys.map((key) => { - return {name: key, variables: {id: jobId}}; - }) - ); - setLoading(false); - }; - - return ( -
    - - - ( - renderAllTemplates(sp.templates)} - > - Print All - , - ]} - > - - - )} - /> -
    + const renderAllTemplates = async (templateKeys) => { + logImEXEvent("speed_print_render_all_templates"); + setLoading(true); + await GenerateDocuments( + templateKeys.map((key) => { + return { name: key, variables: { id: jobId } }; + }) ); + setLoading(false); + }; + + return ( +
    + + + ( + renderAllTemplates(sp.templates)}> + Print All + + ]} + > + + + )} + /> +
    + ); } const renderTemplateList = (templates) => { - const TemplateListGenerated = TemplateList(); - return ( - + const TemplateListGenerated = TemplateList(); + return ( + {templates.map((template, idx) => { - if (idx === templates.length - 1) - return ( - (TemplateListGenerated[template] && - TemplateListGenerated[template].title) || - "" - ); - return `${ - (TemplateListGenerated[template] && - TemplateListGenerated[template].title) || - "" - }, `; + if (idx === templates.length - 1) + return (TemplateListGenerated[template] && TemplateListGenerated[template].title) || ""; + return `${(TemplateListGenerated[template] && TemplateListGenerated[template].title) || ""}, `; })} - ); + ); }; -export default connect( - mapStateToProps, - mapDispatchToProps -)(PrintCenterSpeedPrint); +export default connect(mapStateToProps, mapDispatchToProps)(PrintCenterSpeedPrint); diff --git a/client/src/components/print-wrapper/print-wrapper.component.jsx b/client/src/components/print-wrapper/print-wrapper.component.jsx index fe1edbd55..cbb7e515e 100644 --- a/client/src/components/print-wrapper/print-wrapper.component.jsx +++ b/client/src/components/print-wrapper/print-wrapper.component.jsx @@ -1,40 +1,40 @@ -import {MailFilled, PrinterFilled} from "@ant-design/icons"; -import {Space, Spin} from "antd"; -import React, {useState} from "react"; -import {GenerateDocument} from "../../utils/RenderTemplate"; +import { MailFilled, PrinterFilled } from "@ant-design/icons"; +import { Space, Spin } from "antd"; +import React, { useState } from "react"; +import { GenerateDocument } from "../../utils/RenderTemplate"; export default function PrintWrapperComponent({ - templateObject, - messageObject = {}, - children, - id, - emailOnly = false, - disabled, - }) { - const [loading, setLoading] = useState(false); - const handlePrint = async (type) => { - if (disabled) return; - setLoading(true); - await GenerateDocument(templateObject, messageObject, type, id); - setLoading(false); - }; + templateObject, + messageObject = {}, + children, + id, + emailOnly = false, + disabled +}) { + const [loading, setLoading] = useState(false); + const handlePrint = async (type) => { + if (disabled) return; + setLoading(true); + await GenerateDocument(templateObject, messageObject, type, id); + setLoading(false); + }; - return ( - - {children || null} - {!emailOnly && ( - handlePrint("p")} - style={{cursor: disabled ? "not-allowed" : null}} - /> - )} - handlePrint("e")} - style={{cursor: disabled ? "not-allowed" : null}} - /> - {loading && } - - ); + return ( + + {children || null} + {!emailOnly && ( + handlePrint("p")} + style={{ cursor: disabled ? "not-allowed" : null }} + /> + )} + handlePrint("e")} + style={{ cursor: disabled ? "not-allowed" : null }} + /> + {loading && } + + ); } diff --git a/client/src/components/production-board-filters/production-board-filters.component.jsx b/client/src/components/production-board-filters/production-board-filters.component.jsx index c32034030..65e26a452 100644 --- a/client/src/components/production-board-filters/production-board-filters.component.jsx +++ b/client/src/components/production-board-filters/production-board-filters.component.jsx @@ -1,51 +1,41 @@ -import {Input, Space, Spin} from "antd"; +import { Input, Space, Spin } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionBoardFilters); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionBoardFilters); -export function ProductionBoardFilters({ - bodyshop, - filter, - setFilter, - loading, - }) { - const {t} = useTranslation(); +export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading }) { + const { t } = useTranslation(); - return ( - - - {loading && } - { - setFilter({...filter, search: e.target.value}); - }} - /> - e.active)} - value={filter.employeeId} - placeholder={t("production.labels.employeesearch")} - onChange={(emp) => setFilter({...filter, employeeId: emp})} - allowClear - /> - - - ); + return ( + + {loading && } + { + setFilter({ ...filter, search: e.target.value }); + }} + /> + e.active)} + value={filter.employeeId} + placeholder={t("production.labels.employeesearch")} + onChange={(emp) => setFilter({ ...filter, employeeId: emp })} + allowClear + /> + + ); } diff --git a/client/src/components/production-board-kanban-card/production-board-kanban-card-color-legend.component.jsx b/client/src/components/production-board-kanban-card/production-board-kanban-card-color-legend.component.jsx index 28cea894d..abae21de0 100644 --- a/client/src/components/production-board-kanban-card/production-board-kanban-card-color-legend.component.jsx +++ b/client/src/components/production-board-kanban-card/production-board-kanban-card-color-legend.component.jsx @@ -1,51 +1,51 @@ -import {Col, List, Space, Typography} from "antd"; +import { Col, List, Space, Typography } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; -const CardColorLegend = ({bodyshop}) => { - const {t} = useTranslation(); - const data = bodyshop.ssbuckets.map((bucket) => { - let color = {r: 255, g: 255, b: 255}; +const CardColorLegend = ({ bodyshop }) => { + const { t } = useTranslation(); + const data = bodyshop.ssbuckets.map((bucket) => { + let color = { r: 255, g: 255, b: 255 }; - if (bucket.color) { - color = bucket.color; + if (bucket.color) { + color = bucket.color; - if (bucket.color.rgb) { - color = bucket.color.rgb; - } - } + if (bucket.color.rgb) { + color = bucket.color.rgb; + } + } - return { - label: bucket.label, - color, - }; - }); + return { + label: bucket.label, + color + }; + }); - return ( -
    - {t("production.labels.legend")} - + {t("production.labels.legend")} + ( + + +
    ( - - -
    -
    {item.label}
    -
    -
    - )} - /> - - ); + >
    +
    {item.label}
    +
    +
    + )} + /> + + ); }; export default CardColorLegend; diff --git a/client/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx b/client/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx index b3cb2ff54..fcb0b8fce 100644 --- a/client/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx +++ b/client/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx @@ -1,12 +1,17 @@ -import {BranchesOutlined, CalendarOutlined, DownloadOutlined, EyeFilled, PauseCircleOutlined,} from "@ant-design/icons"; -import {Card, Col, Row, Space, Tooltip} from "antd"; +import { + BranchesOutlined, + CalendarOutlined, + DownloadOutlined, + EyeFilled, + PauseCircleOutlined +} from "@ant-design/icons"; +import { Card, Col, Row, Space, Tooltip } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {Link} from "react-router-dom"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; import ProductionAlert from "../production-list-columns/production-list-columns.alert.component"; -import ProductionListColumnProductionNote - from "../production-list-columns/production-list-columns.productionnote.component"; +import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component"; import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component"; import "./production-board-card.styles.scss"; import dayjs from "../../utils/day"; @@ -14,178 +19,135 @@ import OwnerNameDisplay from "../owner-name-display/owner-name-display.component import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component"; const cardColor = (ssbuckets, totalHrs) => { - const bucket = ssbuckets.filter( - (bucket) => - bucket.gte <= totalHrs && (!!bucket.lt ? bucket.lt > totalHrs : true) - )[0]; + const bucket = ssbuckets.filter((bucket) => bucket.gte <= totalHrs && (!!bucket.lt ? bucket.lt > totalHrs : true))[0]; - let color = {r: 255, g: 255, b: 255}; + let color = { r: 255, g: 255, b: 255 }; - if (bucket && bucket.color) { - color = bucket.color; + if (bucket && bucket.color) { + color = bucket.color; - if (bucket.color.rgb) { - color = bucket.color.rgb; - } + if (bucket.color.rgb) { + color = bucket.color.rgb; } + } - return color; + return color; }; function getContrastYIQ(bgColor) { - const yiq = (bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000; + const yiq = (bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000; - return yiq >= 128 ? "black" : "white"; + return yiq >= 128 ? "black" : "white"; } -export default function ProductionBoardCard( - technician, - card, - bodyshop, - cardSettings -) { - const {t} = useTranslation(); +export default function ProductionBoardCard(technician, card, bodyshop, cardSettings) { + const { t } = useTranslation(); - let employee_body, employee_prep, employee_refinish, employee_csr; - if (card.employee_body) { - employee_body = bodyshop.employees.find((e) => e.id === card.employee_body); - } - if (card.employee_prep) { - employee_prep = bodyshop.employees.find((e) => e.id === card.employee_prep); - } - if (card.employee_refinish) { - employee_refinish = bodyshop.employees.find( - (e) => e.id === card.employee_refinish - ); - } - if (card.employee_csr) { - employee_csr = bodyshop.employees.find((e) => e.id === card.employee_csr); - } - // if (card.employee_csr) { - // employee_csr = bodyshop.employees.find((e) => e.id === card.employee_csr); - // } + let employee_body, employee_prep, employee_refinish, employee_csr; + if (card.employee_body) { + employee_body = bodyshop.employees.find((e) => e.id === card.employee_body); + } + if (card.employee_prep) { + employee_prep = bodyshop.employees.find((e) => e.id === card.employee_prep); + } + if (card.employee_refinish) { + employee_refinish = bodyshop.employees.find((e) => e.id === card.employee_refinish); + } + if (card.employee_csr) { + employee_csr = bodyshop.employees.find((e) => e.id === card.employee_csr); + } + // if (card.employee_csr) { + // employee_csr = bodyshop.employees.find((e) => e.id === card.employee_csr); + // } - const pastDueAlert = - !!card.scheduled_completion && - ((dayjs().isSameOrAfter(dayjs(card.scheduled_completion), "day") && - "production-completion-past") || - (dayjs() - .add(1, "day") - .isSame(dayjs(card.scheduled_completion), "day") && - "production-completion-soon")); + const pastDueAlert = + !!card.scheduled_completion && + ((dayjs().isSameOrAfter(dayjs(card.scheduled_completion), "day") && "production-completion-past") || + (dayjs().add(1, "day").isSame(dayjs(card.scheduled_completion), "day") && "production-completion-soon")); - const totalHrs = - card.labhrs.aggregate.sum.mod_lb_hrs + card.larhrs.aggregate.sum.mod_lb_hrs; - const bgColor = cardColor(bodyshop.ssbuckets, totalHrs); + const totalHrs = card.labhrs.aggregate.sum.mod_lb_hrs + card.larhrs.aggregate.sum.mod_lb_hrs; + const bgColor = cardColor(bodyshop.ssbuckets, totalHrs); - return ( - - - {card.suspended && ( - - )} - {card.iouparent && ( - - - - )} - - + return ( + + + {card.suspended && } + {card.iouparent && ( + + + + )} + + {card.ro_number || t("general.labels.na")} - - } - extra={ - - - - } - > - - {cardSettings && cardSettings.ownr_nm && ( -
    - {cardSettings && cardSettings.compact ? ( -
    {`${card.ownr_ln || ""} ${ - card.ownr_co_nm || "" - }`}
    - ) : ( -
    - -
    - )} - - )} - -
    {`${card.v_model_yr || ""} ${ - card.v_make_desc || "" - } ${card.v_model_desc || ""}`}
    - - {cardSettings && cardSettings.ins_co_nm && card.ins_co_nm && ( - -
    {card.ins_co_nm || ""}
    - - )} - {cardSettings && cardSettings.clm_no && card.clm_no && ( - -
    {card.clm_no || ""}
    - - )} + + } + extra={ + + + + } + > + + {cardSettings && cardSettings.ownr_nm && ( + + {cardSettings && cardSettings.compact ? ( +
    {`${card.ownr_ln || ""} ${card.ownr_co_nm || ""}`}
    + ) : ( +
    + +
    + )} + + )} + +
    {`${card.v_model_yr || ""} ${ + card.v_make_desc || "" + } ${card.v_model_desc || ""}`}
    + + {cardSettings && cardSettings.ins_co_nm && card.ins_co_nm && ( + +
    {card.ins_co_nm || ""}
    + + )} + {cardSettings && cardSettings.clm_no && card.clm_no && ( + +
    {card.clm_no || ""}
    + + )} - {cardSettings && cardSettings.employeeassignments && ( - - - {`B: ${ - employee_body - ? `${employee_body.first_name.substr( - 0, - 3 - )} ${employee_body.last_name.charAt(0)}` - : "" - } ${card.labhrs.aggregate.sum.mod_lb_hrs || "?"}h`} - {`P: ${ - employee_prep - ? `${employee_prep.first_name.substr( - 0, - 3 - )} ${employee_prep.last_name.charAt(0)}` - : "" - }`} - {`R: ${ - employee_refinish - ? `${employee_refinish.first_name.substr( - 0, - 3 - )} ${employee_refinish.last_name.charAt(0)}` - : "" - } ${card.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`} - {`C: ${ - employee_csr - ? `${employee_csr.first_name} ${employee_csr.last_name}` - : "" - }`} - - - )} - {/* {cardSettings && cardSettings.laborhrs && ( + {cardSettings && cardSettings.employeeassignments && ( + + + {`B: ${ + employee_body ? `${employee_body.first_name.substr(0, 3)} ${employee_body.last_name.charAt(0)}` : "" + } ${card.labhrs.aggregate.sum.mod_lb_hrs || "?"}h`} + {`P: ${ + employee_prep ? `${employee_prep.first_name.substr(0, 3)} ${employee_prep.last_name.charAt(0)}` : "" + }`} + {`R: ${ + employee_refinish + ? `${employee_refinish.first_name.substr(0, 3)} ${employee_refinish.last_name.charAt(0)}` + : "" + } ${card.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`} + {`C: ${ + employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : "" + }`} + + + )} + {/* {cardSettings && cardSettings.laborhrs && ( {`B: ${ @@ -197,53 +159,43 @@ export default function ProductionBoardCard( )} */} - {cardSettings && cardSettings.actual_in && card.actual_in && ( - - - - - {card.actual_in} - - - - )} - {cardSettings && - cardSettings.scheduled_completion && - card.scheduled_completion && ( - - - - - {card.scheduled_completion} - - - - )} - {cardSettings && cardSettings.ats && card.alt_transport && ( - -
    {card.alt_transport || ""}
    - - )} - {cardSettings && cardSettings.sublets && ( - - - - )} - {cardSettings && cardSettings.production_note && ( - - {cardSettings && cardSettings.production_note && ( - - )} - - )} - {cardSettings && cardSettings.partsstatus && ( - - - - )} - - - ); + {cardSettings && cardSettings.actual_in && card.actual_in && ( + + + + {card.actual_in} + + + )} + {cardSettings && cardSettings.scheduled_completion && card.scheduled_completion && ( + + + + {card.scheduled_completion} + + + )} + {cardSettings && cardSettings.ats && card.alt_transport && ( + +
    {card.alt_transport || ""}
    + + )} + {cardSettings && cardSettings.sublets && ( + + + + )} + {cardSettings && cardSettings.production_note && ( + + {cardSettings && cardSettings.production_note && } + + )} + {cardSettings && cardSettings.partsstatus && ( + + + + )} + + + ); } diff --git a/client/src/components/production-board-kanban/production-board-kanban.card-settings.component.jsx b/client/src/components/production-board-kanban/production-board-kanban.card-settings.component.jsx index b7a09cdaa..2fee8de4f 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.card-settings.component.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.card-settings.component.jsx @@ -1,173 +1,125 @@ -import {useMutation} from "@apollo/client"; -import {Button, Card, Col, Form, notification, Popover, Row, Switch,} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {UPDATE_KANBAN_SETTINGS} from "../../graphql/user.queries"; +import { useMutation } from "@apollo/client"; +import { Button, Card, Col, Form, notification, Popover, Row, Switch } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { UPDATE_KANBAN_SETTINGS } from "../../graphql/user.queries"; -export default function ProductionBoardKanbanCardSettings({ - associationSettings, - }) { - const [form] = Form.useForm(); - const [open, setOpen] = useState(false); - const [loading, setLoading] = useState(false); - const [updateKbSettings] = useMutation(UPDATE_KANBAN_SETTINGS); +export default function ProductionBoardKanbanCardSettings({ associationSettings }) { + const [form] = Form.useForm(); + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); + const [updateKbSettings] = useMutation(UPDATE_KANBAN_SETTINGS); - useEffect(() => { - form.setFieldsValue( - associationSettings && associationSettings.kanban_settings - ); - }, [form, associationSettings, open]); + useEffect(() => { + form.setFieldsValue(associationSettings && associationSettings.kanban_settings); + }, [form, associationSettings, open]); - const {t} = useTranslation(); + const { t } = useTranslation(); - const handleFinish = async (values) => { - setLoading(true); - const result = await updateKbSettings({ - variables: { - id: associationSettings && associationSettings.id, - ks: values, - }, - }); - if (result.errors) { - notification.open({ - type: "error", - message: t("production.errors.settings", { - error: JSON.stringify(result.errors), - }), - }); - } - setOpen(false); - setLoading(false); - }; + const handleFinish = async (values) => { + setLoading(true); + const result = await updateKbSettings({ + variables: { + id: associationSettings && associationSettings.id, + ks: values + } + }); + if (result.errors) { + notification.open({ + type: "error", + message: t("production.errors.settings", { + error: JSON.stringify(result.errors) + }) + }); + } + setOpen(false); + setLoading(false); + }; - const overlay = ( -
    - -
    - -
    - - - - - - - - - - - - - {/* + + + + + + + + + + + + + + + + + {/* */} - - - - - - - - - - - - - - - - - - - - - {/* + + + + + + + + + + + + + + + + + + + + {/* */} - - - - - - - - - - - - - - - - ); - return ( - - - - ); + + + + + + + + + + + + + + + + ); + return ( + + + + ); } diff --git a/client/src/components/production-board-kanban/production-board-kanban.component.jsx b/client/src/components/production-board-kanban/production-board-kanban.component.jsx index 378f3b8cf..f191bd339 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.component.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.component.jsx @@ -1,19 +1,19 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {useApolloClient} from "@apollo/client"; -import Board, {moveCard} from "@asseinfo/react-kanban"; -import {Button, Grid, notification, Space, Statistic} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Sticky, StickyContainer} from "react-sticky"; -import {createStructuredSelector} from "reselect"; +import { SyncOutlined } from "@ant-design/icons"; +import { useApolloClient } from "@apollo/client"; +import Board, { moveCard } from "@asseinfo/react-kanban"; +import { Button, Grid, notification, Space, Statistic } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Sticky, StickyContainer } from "react-sticky"; +import { createStructuredSelector } from "reselect"; import styled from "styled-components"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {generate_UPDATE_JOB_KANBAN} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { generate_UPDATE_JOB_KANBAN } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component"; import ProductionBoardFilters from "../production-board-filters/production-board-filters.component"; @@ -23,296 +23,243 @@ import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-se //import "@asseinfo/react-kanban/dist/styles.css"; import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component"; import "./production-board-kanban.styles.scss"; -import {createBoardData} from "./production-board-kanban.utils.js"; +import { createBoardData } from "./production-board-kanban.utils.js"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - technician: selectTechnician, + bodyshop: selectBodyshop, + technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); export function ProductionBoardKanbanComponent({ - data, - bodyshop, - refetch, - technician, - insertAuditTrail, - associationSettings, - }) { - const [boardLanes, setBoardLanes] = useState({ - columns: [{id: "Loading...", title: "Loading...", cards: []}], - }); + data, + bodyshop, + refetch, + technician, + insertAuditTrail, + associationSettings +}) { + const [boardLanes, setBoardLanes] = useState({ + columns: [{ id: "Loading...", title: "Loading...", cards: [] }] + }); - const [filter, setFilter] = useState({search: "", employeeId: null}); + const [filter, setFilter] = useState({ search: "", employeeId: null }); - const [isMoving, setIsMoving] = useState(false); + const [isMoving, setIsMoving] = useState(false); - const {t} = useTranslation(); - useEffect(() => { - const boardData = createBoardData( - [ - ...bodyshop.md_ro_statuses.production_statuses, - ...(bodyshop.md_ro_statuses.additional_board_statuses || []), - ], - data, - filter - ); - - boardData.columns = boardData.columns.map((d) => { - return {...d, title: `${d.title} (${d.cards.length})`}; - }); - setBoardLanes(boardData); - setIsMoving(false); - }, [data, setBoardLanes, setIsMoving, bodyshop.md_ro_statuses, filter]); - - const client = useApolloClient(); - - const handleDragEnd = async (card, source, destination) => { - logImEXEvent("kanban_drag_end"); - - setIsMoving(true); - setBoardLanes(moveCard(boardLanes, source, destination)); - const sameColumnTransfer = source.fromColumnId === destination.toColumnId; - const sourceColumn = boardLanes.columns.find( - (x) => x.id === source.fromColumnId - ); - const destinationColumn = boardLanes.columns.find( - (x) => x.id === destination.toColumnId - ); - - const movedCardWillBeFirst = destination.toPosition === 0; - - const movedCardWillBeLast = - destinationColumn.cards.length - destination.toPosition < 1; - - const lastCardInDestinationColumn = - destinationColumn.cards[destinationColumn.cards.length - 1]; - - const oldChildCard = sourceColumn.cards[source.fromPosition + 1]; - - const newChildCard = movedCardWillBeLast - ? null - : destinationColumn.cards[ - sameColumnTransfer - ? source.fromPosition - destination.toPosition > 0 - ? destination.toPosition - : destination.toPosition + 1 - : destination.toPosition - ]; - - const oldChildCardNewParent = oldChildCard ? card.kanbanparent : null; - - let movedCardNewKanbanParent; - if (movedCardWillBeFirst) { - //console.log("==> New Card is first."); - movedCardNewKanbanParent = "-1"; - } else if (movedCardWillBeLast) { - // console.log("==> New Card is last."); - movedCardNewKanbanParent = lastCardInDestinationColumn.id; - } else if (!!newChildCard) { - // console.log("==> New Card is somewhere in the middle"); - movedCardNewKanbanParent = newChildCard.kanbanparent; - } else { - console.log("==> !!!!!!Couldn't find a parent.!!!! <=="); - } - const newChildCardNewParent = newChildCard ? card.id : null; - const update = await client.mutate({ - mutation: generate_UPDATE_JOB_KANBAN( - oldChildCard ? oldChildCard.id : null, - oldChildCardNewParent, - card.id, - movedCardNewKanbanParent, - destination.toColumnId, - newChildCard ? newChildCard.id : null, - newChildCardNewParent - ), - }); - insertAuditTrail({ - jobid: card.id, - operation: AuditTrailMapping.jobstatuschange(destination.toColumnId), - type: "jobstatuschange", - }); - - if (update.errors) { - notification["error"]({ - message: t("production.errors.boardupdate", { - message: JSON.stringify(update.errors), - }), - }); - } - }; - - const totalHrs = data - .reduce( - (acc, val) => - acc + - (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) + - (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), - 0 - ) - .toFixed(1); - const totalLAB = data - .reduce( - (acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), - 0 - ) - .toFixed(1); - const totalLAR = data - .reduce( - (acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), - 0 - ) - .toFixed(1); - const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) - .filter((screen) => !!screen[1]) - .slice(-1)[0]; - - const standardSizes = { - xs: "250", - sm: "250", - md: "250", - lg: "250", - xl: "250", - xxl: "250", - }; - const compactSizes = { - xs: "150", - sm: "150", - md: "150", - lg: "150", - xl: "155", - xxl: "155", - }; - - const width = selectedBreakpoint - ? associationSettings && - associationSettings.kanban_settings && - associationSettings.kanban_settings.compact - ? compactSizes[selectedBreakpoint[0]] - : standardSizes[selectedBreakpoint[0]] - : "250"; - - const stickyHeader = { - renderColumnHeader: ({title}) => ( - - {({ - style, - - // the following are also available but unused in this example - isSticky, - wasSticky, - distanceFromTop, - distanceFromBottom, - calculatedHeight, - }) => ( -
    - {title} -
    - )} -
    - ), - }; - - const cardSettings = - associationSettings && - associationSettings.kanban_settings && - Object.keys(associationSettings.kanban_settings).length > 0 - ? associationSettings.kanban_settings - : { - ats: true, - clm_no: true, - compact: false, - ownr_nm: true, - sublets: true, - ins_co_nm: true, - production_note: true, - employeeassignments: true, - scheduled_completion: true, - stickyheader: false, - cardcolor: false, - }; - - return ( - - - - - - - - - - } - extra={ - - - - - - } - /> - - {cardSettings.cardcolor && ( - - )} - - - - - ProductionBoardCard(technician, card, bodyshop, cardSettings) - } - onCardDragEnd={handleDragEnd} - /> - - + const { t } = useTranslation(); + useEffect(() => { + const boardData = createBoardData( + [...bodyshop.md_ro_statuses.production_statuses, ...(bodyshop.md_ro_statuses.additional_board_statuses || [])], + data, + filter ); + + boardData.columns = boardData.columns.map((d) => { + return { ...d, title: `${d.title} (${d.cards.length})` }; + }); + setBoardLanes(boardData); + setIsMoving(false); + }, [data, setBoardLanes, setIsMoving, bodyshop.md_ro_statuses, filter]); + + const client = useApolloClient(); + + const handleDragEnd = async (card, source, destination) => { + logImEXEvent("kanban_drag_end"); + + setIsMoving(true); + setBoardLanes(moveCard(boardLanes, source, destination)); + const sameColumnTransfer = source.fromColumnId === destination.toColumnId; + const sourceColumn = boardLanes.columns.find((x) => x.id === source.fromColumnId); + const destinationColumn = boardLanes.columns.find((x) => x.id === destination.toColumnId); + + const movedCardWillBeFirst = destination.toPosition === 0; + + const movedCardWillBeLast = destinationColumn.cards.length - destination.toPosition < 1; + + const lastCardInDestinationColumn = destinationColumn.cards[destinationColumn.cards.length - 1]; + + const oldChildCard = sourceColumn.cards[source.fromPosition + 1]; + + const newChildCard = movedCardWillBeLast + ? null + : destinationColumn.cards[ + sameColumnTransfer + ? source.fromPosition - destination.toPosition > 0 + ? destination.toPosition + : destination.toPosition + 1 + : destination.toPosition + ]; + + const oldChildCardNewParent = oldChildCard ? card.kanbanparent : null; + + let movedCardNewKanbanParent; + if (movedCardWillBeFirst) { + //console.log("==> New Card is first."); + movedCardNewKanbanParent = "-1"; + } else if (movedCardWillBeLast) { + // console.log("==> New Card is last."); + movedCardNewKanbanParent = lastCardInDestinationColumn.id; + } else if (!!newChildCard) { + // console.log("==> New Card is somewhere in the middle"); + movedCardNewKanbanParent = newChildCard.kanbanparent; + } else { + console.log("==> !!!!!!Couldn't find a parent.!!!! <=="); + } + const newChildCardNewParent = newChildCard ? card.id : null; + const update = await client.mutate({ + mutation: generate_UPDATE_JOB_KANBAN( + oldChildCard ? oldChildCard.id : null, + oldChildCardNewParent, + card.id, + movedCardNewKanbanParent, + destination.toColumnId, + newChildCard ? newChildCard.id : null, + newChildCardNewParent + ) + }); + insertAuditTrail({ + jobid: card.id, + operation: AuditTrailMapping.jobstatuschange(destination.toColumnId), + type: "jobstatuschange" + }); + + if (update.errors) { + notification["error"]({ + message: t("production.errors.boardupdate", { + message: JSON.stringify(update.errors) + }) + }); + } + }; + + const totalHrs = data + .reduce( + (acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), + 0 + ) + .toFixed(1); + const totalLAB = data.reduce((acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1); + const totalLAR = data.reduce((acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1); + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) + .filter((screen) => !!screen[1]) + .slice(-1)[0]; + + const standardSizes = { + xs: "250", + sm: "250", + md: "250", + lg: "250", + xl: "250", + xxl: "250" + }; + const compactSizes = { + xs: "150", + sm: "150", + md: "150", + lg: "150", + xl: "155", + xxl: "155" + }; + + const width = selectedBreakpoint + ? associationSettings && associationSettings.kanban_settings && associationSettings.kanban_settings.compact + ? compactSizes[selectedBreakpoint[0]] + : standardSizes[selectedBreakpoint[0]] + : "250"; + + const stickyHeader = { + renderColumnHeader: ({ title }) => ( + + {({ + style, + + // the following are also available but unused in this example + isSticky, + wasSticky, + distanceFromTop, + distanceFromBottom, + calculatedHeight + }) => ( +
    + {title} +
    + )} +
    + ) + }; + + const cardSettings = + associationSettings && + associationSettings.kanban_settings && + Object.keys(associationSettings.kanban_settings).length > 0 + ? associationSettings.kanban_settings + : { + ats: true, + clm_no: true, + compact: false, + ownr_nm: true, + sublets: true, + ins_co_nm: true, + production_note: true, + employeeassignments: true, + scheduled_completion: true, + stickyheader: false, + cardcolor: false + }; + + return ( + + + + + + + + + + } + extra={ + + + + + + } + /> + + {cardSettings.cardcolor && } + + + + ProductionBoardCard(technician, card, bodyshop, cardSettings)} + onCardDragEnd={handleDragEnd} + /> + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionBoardKanbanComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionBoardKanbanComponent); const Container = styled.div` - .react-kanban-card-skeleton, - .react-kanban-card, - .react-kanban-card-adder-form { - box-sizing: border-box; - max-width: ${(props) => props.width}px; - min-width: ${(props) => props.width}px; - } + .react-kanban-card-skeleton, + .react-kanban-card, + .react-kanban-card-adder-form { + box-sizing: border-box; + max-width: ${(props) => props.width}px; + min-width: ${(props) => props.width}px; + } `; diff --git a/client/src/components/production-board-kanban/production-board-kanban.container.jsx b/client/src/components/production-board-kanban/production-board-kanban.container.jsx index 10ce9825b..a93f176e2 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.container.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.container.jsx @@ -1,98 +1,93 @@ -import {useApolloClient, useQuery, useSubscription} from "@apollo/client"; +import { useApolloClient, useQuery, useSubscription } from "@apollo/client"; import _ from "lodash"; -import React, {useEffect, useState} from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect, useState } from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; import { - QUERY_EXACT_JOB_IN_PRODUCTION, - QUERY_EXACT_JOBS_IN_PRODUCTION, - QUERY_JOBS_IN_PRODUCTION, - SUBSCRIPTION_JOBS_IN_PRODUCTION, + QUERY_EXACT_JOB_IN_PRODUCTION, + QUERY_EXACT_JOBS_IN_PRODUCTION, + QUERY_JOBS_IN_PRODUCTION, + SUBSCRIPTION_JOBS_IN_PRODUCTION } from "../../graphql/jobs.queries"; -import {QUERY_KANBAN_SETTINGS} from "../../graphql/user.queries"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { QUERY_KANBAN_SETTINGS } from "../../graphql/user.queries"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import ProductionBoardKanbanComponent from "./production-board-kanban.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); -export function ProductionBoardKanbanContainer({bodyshop, currentUser}) { - const {refetch, loading, data} = useQuery(QUERY_JOBS_IN_PRODUCTION, { - pollInterval: 3600000, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", +export function ProductionBoardKanbanContainer({ bodyshop, currentUser }) { + const { refetch, loading, data } = useQuery(QUERY_JOBS_IN_PRODUCTION, { + pollInterval: 3600000, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const client = useApolloClient(); + const [joblist, setJoblist] = useState([]); + const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION); + + useEffect(() => { + if (!(data && data.jobs)) return; + setJoblist( + data.jobs.map((j) => { + return { id: j.id, updated_at: j.updated_at }; + }) + ); + }, [data]); + + useEffect(() => { + if (!updatedJobs || joblist.length === 0) return; + + const jobDiff = _.differenceWith( + joblist, + updatedJobs.jobs, + (a, b) => a.id === b.id && a.updated_at === b.updated_at + ); + + jobDiff.forEach((job) => { + getUpdatedJobData(job.id); }); - const client = useApolloClient(); - const [joblist, setJoblist] = useState([]); - const {data: updatedJobs} = useSubscription( - SUBSCRIPTION_JOBS_IN_PRODUCTION - ); + if (jobDiff.length > 1) { + getUpdatedJobsData(jobDiff.map((j) => j.id)); + } else if (jobDiff.length === 1) { + jobDiff.forEach((job) => { + getUpdatedJobData(job.id); + }); + } - useEffect(() => { - if (!(data && data.jobs)) return; - setJoblist( - data.jobs.map((j) => { - return {id: j.id, updated_at: j.updated_at}; - }) - ); - }, [data]); + setJoblist(updatedJobs.jobs); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [updatedJobs]); - useEffect(() => { - if (!updatedJobs || joblist.length === 0) return; + const getUpdatedJobData = async (jobId) => { + client.query({ + query: QUERY_EXACT_JOB_IN_PRODUCTION, + variables: { id: jobId } + }); + }; + const getUpdatedJobsData = async (jobIds) => { + client.query({ + query: QUERY_EXACT_JOBS_IN_PRODUCTION, + variables: { ids: jobIds } + }); + }; - const jobDiff = _.differenceWith( - joblist, - updatedJobs.jobs, - (a, b) => a.id === b.id && a.updated_at === b.updated_at - ); + const { loading: associationSettingsLoading, data: associationSettings } = useQuery(QUERY_KANBAN_SETTINGS, { + variables: { email: currentUser.email } + }); - jobDiff.forEach((job) => { - getUpdatedJobData(job.id); - }); - if (jobDiff.length > 1) { - getUpdatedJobsData(jobDiff.map((j) => j.id)); - } else if (jobDiff.length === 1) { - jobDiff.forEach((job) => { - getUpdatedJobData(job.id); - }); - } - - setJoblist(updatedJobs.jobs); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [updatedJobs]); - - const getUpdatedJobData = async (jobId) => { - client.query({ - query: QUERY_EXACT_JOB_IN_PRODUCTION, - variables: {id: jobId}, - }); - }; - const getUpdatedJobsData = async (jobIds) => { - client.query({ - query: QUERY_EXACT_JOBS_IN_PRODUCTION, - variables: {ids: jobIds}, - }); - }; - - const {loading: associationSettingsLoading, data: associationSettings} = - useQuery(QUERY_KANBAN_SETTINGS, { - variables: {email: currentUser.email}, - }); - - return ( - - ); + return ( + + ); } export default connect(mapStateToProps, null)(ProductionBoardKanbanContainer); diff --git a/client/src/components/production-board-kanban/production-board-kanban.utils.js b/client/src/components/production-board-kanban/production-board-kanban.utils.js index 59cb6225d..55784d81c 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.utils.js +++ b/client/src/components/production-board-kanban/production-board-kanban.utils.js @@ -1,105 +1,104 @@ -import {groupBy} from "lodash"; +import { groupBy } from "lodash"; const sortByParentId = (arr) => { - // return arr.reduce((accumulator, currentValue) => { - // //Find the parent item. - // let item = accumulator.find((x) => x.id === currentValue.kanbanparent); - // //Get index of parent item - // let index = accumulator.indexOf(item); + // return arr.reduce((accumulator, currentValue) => { + // //Find the parent item. + // let item = accumulator.find((x) => x.id === currentValue.kanbanparent); + // //Get index of parent item + // let index = accumulator.indexOf(item); - // index = index !== -1 ? index + 1 : 0; - // accumulator.splice(index, 0, currentValue); - // return accumulator; - // }, []); + // index = index !== -1 ? index + 1 : 0; + // accumulator.splice(index, 0, currentValue); + // return accumulator; + // }, []); - let parentId = "-1"; - const sortedList = []; - const byParentsIdsList = groupBy(arr, "kanbanparent"); // Create a new array with objects indexed by parentId - //console.log("sortByParentId -> byParentsIdsList", byParentsIdsList); + let parentId = "-1"; + const sortedList = []; + const byParentsIdsList = groupBy(arr, "kanbanparent"); // Create a new array with objects indexed by parentId + //console.log("sortByParentId -> byParentsIdsList", byParentsIdsList); - while (byParentsIdsList[parentId]) { - sortedList.push(byParentsIdsList[parentId][0]); - parentId = byParentsIdsList[parentId][0].id; - } + while (byParentsIdsList[parentId]) { + sortedList.push(byParentsIdsList[parentId][0]); + parentId = byParentsIdsList[parentId][0].id; + } - if (byParentsIdsList["null"]) - byParentsIdsList["null"].map((i) => sortedList.push(i)); + if (byParentsIdsList["null"]) byParentsIdsList["null"].map((i) => sortedList.push(i)); - //Validate that the 2 arrays are of the same length and no children are missing. - if (arr.length !== sortedList.length) { - arr.map((origItem) => { - if (!!!sortedList.find((s) => s.id === origItem.id)) { - sortedList.push(origItem); - console.log("DATA CONSISTENCY ERROR: ", origItem.ro_number); - } - return 1; - }); - } + //Validate that the 2 arrays are of the same length and no children are missing. + if (arr.length !== sortedList.length) { + arr.map((origItem) => { + if (!!!sortedList.find((s) => s.id === origItem.id)) { + sortedList.push(origItem); + console.log("DATA CONSISTENCY ERROR: ", origItem.ro_number); + } + return 1; + }); + } - return sortedList; + return sortedList; }; export const createBoardData = (AllStatuses, Jobs, filter) => { - const {search, employeeId} = filter; - const boardLanes = { - columns: AllStatuses.map((s) => { - return { - id: s, - title: s, - cards: [], - }; - }), - }; + const { search, employeeId } = filter; + const boardLanes = { + columns: AllStatuses.map((s) => { + return { + id: s, + title: s, + cards: [] + }; + }) + }; - const filteredJobs = - (search === "" || !search) && !employeeId - ? Jobs - : Jobs.filter((j) => { - let include = false; - if (search && search !== "") { - include = CheckSearch(search, j); - } + const filteredJobs = + (search === "" || !search) && !employeeId + ? Jobs + : Jobs.filter((j) => { + let include = false; + if (search && search !== "") { + include = CheckSearch(search, j); + } - if (!!employeeId) { - include = - include || - j.employee_body === employeeId || - j.employee_prep === employeeId || - j.employee_csr === employeeId || - j.employee_refinish === employeeId; - } + if (!!employeeId) { + include = + include || + j.employee_body === employeeId || + j.employee_prep === employeeId || + j.employee_csr === employeeId || + j.employee_refinish === employeeId; + } - return include; - }); + return include; + }); - const DataGroupedByStatus = groupBy(filteredJobs, (d) => d.status); + const DataGroupedByStatus = groupBy(filteredJobs, (d) => d.status); - Object.keys(DataGroupedByStatus).map((statusGroupKey) => { - try { - const needle = boardLanes.columns.find((l) => l.id === statusGroupKey); - if (!needle?.cards) return null; - needle.cards = sortByParentId(DataGroupedByStatus[statusGroupKey]); - } catch (error) { - console.log("Error while creating board card", error); - } - return null; - }); + Object.keys(DataGroupedByStatus).map((statusGroupKey) => { + try { + const needle = boardLanes.columns.find((l) => l.id === statusGroupKey); + if (!needle?.cards) return null; + needle.cards = sortByParentId(DataGroupedByStatus[statusGroupKey]); + } catch (error) { + console.log("Error while creating board card", error); + } + return null; + }); - return boardLanes; + return boardLanes; }; const CheckSearch = (search, job) => { - return ( - (job.ro_number || "").toLowerCase().includes(search.toLowerCase()) || - (job.ownr_fn || "").toLowerCase().includes(search.toLowerCase()) || - (job.ownr_co_nm || "").toLowerCase().includes(search.toLowerCase()) || - (job.ownr_ln || "").toLowerCase().includes(search.toLowerCase()) || - (job.status || "").toLowerCase().includes(search.toLowerCase()) || - (job.v_make_desc || "").toLowerCase().includes(search.toLowerCase()) || - (job.v_model_desc || "").toLowerCase().includes(search.toLowerCase()) || - (job.clm_no || "").toLowerCase().includes(search.toLowerCase()) || - (job.plate_no || "").toLowerCase().includes(search.toLowerCase()) - ); + return ( + (job.ro_number || "").toLowerCase().includes(search.toLowerCase()) || + (job.ownr_fn || "").toLowerCase().includes(search.toLowerCase()) || + (job.ownr_co_nm || "").toLowerCase().includes(search.toLowerCase()) || + (job.ownr_ln || "").toLowerCase().includes(search.toLowerCase()) || + (job.status || "").toLowerCase().includes(search.toLowerCase()) || + (job.v_make_desc || "").toLowerCase().includes(search.toLowerCase()) || + (job.v_model_desc || "").toLowerCase().includes(search.toLowerCase()) || + (job.clm_no || "").toLowerCase().includes(search.toLowerCase()) || + (job.plate_no || "").toLowerCase().includes(search.toLowerCase()) + ); }; // export const updateBoardOnMove = (board, card, source, destination) => { diff --git a/client/src/components/production-list-columns/production-list-columns.add.component.jsx b/client/src/components/production-list-columns/production-list-columns.add.component.jsx index c7f14ea7c..1d1ea8867 100644 --- a/client/src/components/production-list-columns/production-list-columns.add.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.add.component.jsx @@ -1,89 +1,79 @@ import React from "react"; -import {Button, Dropdown} from "antd"; +import { Button, Dropdown } from "antd"; import dataSource from "./production-list-columns.data"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - technician: selectTechnician, - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + technician: selectTechnician, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionColumnsComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionColumnsComponent); -export function ProductionColumnsComponent({ - columnState, - technician, - bodyshop, - data, - tableState, - refetch, - }) { - const [columns, setColumns] = columnState; - const {t} = useTranslation(); +export function ProductionColumnsComponent({ columnState, technician, bodyshop, data, tableState, refetch }) { + const [columns, setColumns] = columnState; + const { t } = useTranslation(); const { - treatments: { Enhanced_Payroll }, + treatments: { Enhanced_Payroll } } = useSplitTreatments({ attributes: {}, - names: ['Enhanced_Payroll'], - splitKey: bodyshop.imexshopid, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid }); - const handleAdd = (e) => { - setColumns([ - ...columns, - ...dataSource({ - bodyshop, - technician, - state: tableState, - data, - activeStatuses: bodyshop.md_ro_statuses.active_statuses, - treatments:{Enhanced_Payroll} - }).filter((i) => i.key === e.key), - ]); - }; - - const columnKeys = columns.map((i) => i.key); - const cols = dataSource({ + const handleAdd = (e) => { + setColumns([ + ...columns, + ...dataSource({ + bodyshop, technician, - data, state: tableState, + data, activeStatuses: bodyshop.md_ro_statuses.active_statuses, - refetch, - treatments:{Enhanced_Payroll} - }); + treatments: { Enhanced_Payroll } + }).filter((i) => i.key === e.key) + ]); + }; - const menu = { - items: cols - .filter((i) => !columnKeys.includes(i.key)) - .map((item) => ({ - key: item.key, - label: item.title, - style: {breakInside: "avoid"}, - })), - style: { - columnCount: Math.max(Math.floor(cols.length / 10), 1), - }, - onClick: handleAdd, - }; + const columnKeys = columns.map((i) => i.key); + const cols = dataSource({ + technician, + data, + state: tableState, + activeStatuses: bodyshop.md_ro_statuses.active_statuses, + refetch, + treatments: { Enhanced_Payroll } + }); - return ( -
    - - - -
    - ); + const menu = { + items: cols + .filter((i) => !columnKeys.includes(i.key)) + .map((item) => ({ + key: item.key, + label: item.title, + style: { breakInside: "avoid" } + })), + style: { + columnCount: Math.max(Math.floor(cols.length / 10), 1) + }, + onClick: handleAdd + }; + + return ( +
    + + + +
    + ); } // ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export function ProductionListColumnAlert({record, insertAuditTrail}) { - const {t} = useTranslation(); +export function ProductionListColumnAlert({ record, insertAuditTrail }) { + const { t } = useTranslation(); - const [updateAlert] = useMutation(UPDATE_JOB); + const [updateAlert] = useMutation(UPDATE_JOB); - const handleAlertToggle = (e) => { - logImEXEvent("production_toggle_alert"); - //e.stopPropagation(); - updateAlert({ - variables: { - jobId: record.id, - job: { - production_vars: { - ...record.production_vars, - alert: - !!record.production_vars && !!record.production_vars.alert - ? !record.production_vars.alert - : true, - }, - }, - }, - }); - insertAuditTrail({ - jobid: record.id, - operation: AuditTrailMapping.alertToggle( - !!record.production_vars && !!record.production_vars.alert - ? !record.production_vars.alert - : true - ), - type: "alertToggle",}).then(() => { - if (record.refetch) record.refetch(); - }); - }; + const handleAlertToggle = (e) => { + logImEXEvent("production_toggle_alert"); + //e.stopPropagation(); + updateAlert({ + variables: { + jobId: record.id, + job: { + production_vars: { + ...record.production_vars, + alert: !!record.production_vars && !!record.production_vars.alert ? !record.production_vars.alert : true + } + } + } + }); + insertAuditTrail({ + jobid: record.id, + operation: AuditTrailMapping.alertToggle( + !!record.production_vars && !!record.production_vars.alert ? !record.production_vars.alert : true + ), + type: "alertToggle" + }).then(() => { + if (record.refetch) record.refetch(); + }); + }; - const menu = { - items: [ - { - key: "toggleAlert", - label: record.production_vars && record.production_vars.alert - ? t("production.labels.alertoff") - : t("production.labels.alerton"), - onClick: handleAlertToggle, - }, - ] - }; + const menu = { + items: [ + { + key: "toggleAlert", + label: + record.production_vars && record.production_vars.alert + ? t("production.labels.alertoff") + : t("production.labels.alerton"), + onClick: handleAlertToggle + } + ] + }; - return ( - -
    - {record.production_vars && record.production_vars.alert ? ( - - ) : null} -
    -
    - ); + return ( + +
    + {record.production_vars && record.production_vars.alert ? ( + + ) : null} +
    +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionListColumnAlert); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionListColumnAlert); diff --git a/client/src/components/production-list-columns/production-list-columns.bodypriority.component.jsx b/client/src/components/production-list-columns/production-list-columns.bodypriority.component.jsx index dcebf1001..cc63dd922 100644 --- a/client/src/components/production-list-columns/production-list-columns.bodypriority.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.bodypriority.component.jsx @@ -1,61 +1,57 @@ -import {useMutation} from "@apollo/client"; -import {Dropdown} from "antd"; +import { useMutation } from "@apollo/client"; +import { Dropdown } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { useTranslation } from "react-i18next"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; -export default function ProductionListColumnBodyPriority({record}) { - const {t} = useTranslation(); +export default function ProductionListColumnBodyPriority({ record }) { + const { t } = useTranslation(); - const [updateAlert] = useMutation(UPDATE_JOB); + const [updateAlert] = useMutation(UPDATE_JOB); - const handleSetBodyPriority = (e) => { - logImEXEvent("production_set_body_priority"); - // e.stopPropagation(); - const {key} = e; - updateAlert({ - variables: { - jobId: record.id, - job: { - production_vars: { - ...record.production_vars, - bodypriority: key === "clearBodyPriority" ? null : key, - }, - }, - }, - }).then(() => { - if (record.refetch) record.refetch(); - }); - }; + const handleSetBodyPriority = (e) => { + logImEXEvent("production_set_body_priority"); + // e.stopPropagation(); + const { key } = e; + updateAlert({ + variables: { + jobId: record.id, + job: { + production_vars: { + ...record.production_vars, + bodypriority: key === "clearBodyPriority" ? null : key + } + } + } + }).then(() => { + if (record.refetch) record.refetch(); + }); + }; - const menu = { - items: [ - { - key: "clearBodyPriority", - label: t("production.actions.bodypriority-clear"), - }, - { - key: "set", - label: t("production.actions.bodypriority-set"), - children: new Array(15).fill().map((value, index) => ({ - key: index + 1, - label: ( -
    - {index + 1} -
    - ), - })), - }, - ], - onClick: handleSetBodyPriority - }; + const menu = { + items: [ + { + key: "clearBodyPriority", + label: t("production.actions.bodypriority-clear") + }, + { + key: "set", + label: t("production.actions.bodypriority-set"), + children: new Array(15).fill().map((value, index) => ({ + key: index + 1, + label:
    {index + 1}
    + })) + } + ], + onClick: handleSetBodyPriority + }; - return ( - -
    - {record.production_vars && record.production_vars.bodypriority} -
    -
    - ); + return ( + +
    + {record.production_vars && record.production_vars.bodypriority} +
    +
    + ); } diff --git a/client/src/components/production-list-columns/production-list-columns.comment.component.jsx b/client/src/components/production-list-columns/production-list-columns.comment.component.jsx index c86f4735c..063b1c0ba 100644 --- a/client/src/components/production-list-columns/production-list-columns.comment.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.comment.component.jsx @@ -1,81 +1,79 @@ import Icon from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {Button, Input, Popover, Tooltip} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {FaRegStickyNote} from "react-icons/fa"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; +import { useMutation } from "@apollo/client"; +import { Button, Input, Popover, Tooltip } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { FaRegStickyNote } from "react-icons/fa"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; -export default function ProductionListColumnComment({record}) { - const {t} = useTranslation(); +export default function ProductionListColumnComment({ record }) { + const { t } = useTranslation(); - const [note, setNote] = useState(record.comment || ""); + const [note, setNote] = useState(record.comment || ""); - const [open, setOpen] = useState(false); + const [open, setOpen] = useState(false); - const [updateAlert] = useMutation(UPDATE_JOB); + const [updateAlert] = useMutation(UPDATE_JOB); - const handleSaveNote = (e) => { - e.stopPropagation(); - setOpen(false); - updateAlert({ - variables: { - jobId: record.id, - job: { - comment: note, - }, - }, - }).then(() => { - if (record.refetch) record.refetch(); - }); - }; + const handleSaveNote = (e) => { + e.stopPropagation(); + setOpen(false); + updateAlert({ + variables: { + jobId: record.id, + job: { + comment: note + } + } + }).then(() => { + if (record.refetch) record.refetch(); + }); + }; - const handleChange = (e) => { - e.stopPropagation(); - setNote(e.target.value); - }; + const handleChange = (e) => { + e.stopPropagation(); + setNote(e.target.value); + }; - const handleOpenChange = (flag) => { - setOpen(flag); - if (flag) setNote(record.comment || ""); - }; + const handleOpenChange = (flag) => { + setOpen(flag); + if (flag) setNote(record.comment || ""); + }; - return ( - - -
    - -
    - - } - trigger={["click"]} - > -
    - - {record.comment || " "} -
    -
    - ); + return ( + + +
    + +
    + + } + trigger={["click"]} + > +
    + + {record.comment || " "} +
    +
    + ); } diff --git a/client/src/components/production-list-columns/production-list-columns.data.jsx b/client/src/components/production-list-columns/production-list-columns.data.jsx index abff04e3c..884c1246e 100644 --- a/client/src/components/production-list-columns/production-list-columns.data.jsx +++ b/client/src/components/production-list-columns/production-list-columns.data.jsx @@ -1,17 +1,15 @@ -import {BranchesOutlined, PauseCircleOutlined} from "@ant-design/icons"; -import {Checkbox,Space, Tooltip} from "antd"; +import { BranchesOutlined, PauseCircleOutlined } from "@ant-design/icons"; +import { Checkbox, Space, Tooltip } from "antd"; import i18n from "i18next"; -import {Link} from "react-router-dom"; +import { Link } from "react-router-dom"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {TimeFormatter} from "../../utils/DateFormatter"; +import { TimeFormatter } from "../../utils/DateFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter"; -import {onlyUnique} from "../../utils/arrayHelper"; -import {alphaSort, dateSort, statusSort} from "../../utils/sorters"; +import { onlyUnique } from "../../utils/arrayHelper"; +import { alphaSort, dateSort, statusSort } from "../../utils/sorters"; import JobAltTransportChange from "../job-at-change/job-at-change.component"; import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component"; -import OwnerNameDisplay, { - OwnerNameDisplayFunction, -} from "../owner-name-display/owner-name-display.component"; +import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component"; import ProductionListColumnAlert from "./production-list-columns.alert.component"; import ProductionListColumnBodyPriority from "./production-list-columns.bodypriority.component"; @@ -26,658 +24,527 @@ import ProductionListColumnNote from "./production-list-columns.productionnote.c import ProductionListColumnCategory from "./production-list-columns.status.category"; import ProductionListColumnStatus from "./production-list-columns.status.component"; import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component"; -import {store} from "../../redux/store"; -import {setModalContext} from "../../redux/modals/modals.actions"; +import { store } from "../../redux/store"; +import { setModalContext } from "../../redux/modals/modals.actions"; -const r = ({technician, state, activeStatuses, data, bodyshop, refetch, treatments}) => { -const {Enhanced_Payroll} = treatments; - return [ - { - title: i18n.t("jobs.actions.viewdetail"), - dataIndex: "viewdetail", - key: "viewdetail", - ellipsis: true, - render: (text, record) => ( - - {i18n.t("general.labels.view")} - - ), - }, - ...Enhanced_Payroll.treatment === "on" ? [ { +const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatments }) => { + const { Enhanced_Payroll } = treatments; + return [ + { + title: i18n.t("jobs.actions.viewdetail"), + dataIndex: "viewdetail", + key: "viewdetail", + ellipsis: true, + render: (text, record) => {i18n.t("general.labels.view")} + }, + ...(Enhanced_Payroll.treatment === "on" + ? [ + { title: i18n.t("timetickets.actions.claimtasks"), dataIndex: "claimtasks", key: "claimtasks", ellipsis: true, render: (text, record) => ( -
    { - store.dispatch( - setModalContext({ - context: { - actions: {}, - context: {jobid: record.id}, - }, - modal: "timeTicketTask", - }) - ); - }} - > - {i18n.t("timetickets.actions.claimtasks")} -
    - ), - },] : [], - { - title: i18n.t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - ellipsis: true, - sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - render: (text, record) => - technician ? ( - - {record.ro_number} - {record.suspended && ( - - )} - - ) : ( - - - {record.ro_number} - {record.suspended && ( - - )} - {record.iouparent && ( - - - - )} - - - ), - }, - { - title: i18n.t("jobs.fields.owner"), - dataIndex: "ownr", - key: "ownr", - ellipsis: true, - render: (text, record) => - technician ? ( - - ) : ( - - - - ), - sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), - sortOrder: - state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order, - }, - { - title: i18n.t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - ellipsis: true, - sorter: (a, b) => - alphaSort( - `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${ - a.v_model_desc || "" - }`, +
    { + store.dispatch( + setModalContext({ + context: { + actions: {}, + context: { jobid: record.id } + }, + modal: "timeTicketTask" + }) + ); + }} + > + {i18n.t("timetickets.actions.claimtasks")} +
    + ) + } + ] + : []), + { + title: i18n.t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + ellipsis: true, + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => + technician ? ( + + {record.ro_number} + {record.suspended && } + + ) : ( + + + {record.ro_number} + {record.suspended && } + {record.iouparent && ( + + + + )} + + + ) + }, + { + title: i18n.t("jobs.fields.owner"), + dataIndex: "ownr", + key: "ownr", + ellipsis: true, + render: (text, record) => + technician ? ( + + ) : ( + + + + ), + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), + sortOrder: state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order + }, + { + title: i18n.t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + sorter: (a, b) => + alphaSort( + `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`, `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` - ), - sortOrder: - state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, - render: (text, record) => - technician ? ( - <>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - } ${record.v_color || ""} ${record.plate_no || ""}`} - ) : ( - {`${ - record.v_model_yr || "" - } ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${ - record.v_color || "" - } ${record.plate_no || ""}`} - ), - }, - { - title: i18n.t("jobs.fields.actual_in"), - dataIndex: "actual_in", - key: "actual_in", - ellipsis: true, - sorter: (a, b) => dateSort(a.actual_in, b.actual_in), - sortOrder: - state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order, - render: (text, record) => ( - - ), - }, - { - title: i18n.t("jobs.fields.actual_in") + " (HH:MM)", - dataIndex: "actual_in_time", - key: "actual_in_time", - ellipsis: true, + ), + sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, + render: (text, record) => + technician ? ( + <>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ + record.v_model_desc || "" + } ${record.v_color || ""} ${record.plate_no || ""}`} + ) : ( + {`${ + record.v_model_yr || "" + } ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${ + record.v_color || "" + } ${record.plate_no || ""}`} + ) + }, + { + title: i18n.t("jobs.fields.actual_in"), + dataIndex: "actual_in", + key: "actual_in", + ellipsis: true, + sorter: (a, b) => dateSort(a.actual_in, b.actual_in), + sortOrder: state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: i18n.t("jobs.fields.actual_in") + " (HH:MM)", + dataIndex: "actual_in_time", + key: "actual_in_time", + ellipsis: true, - render: (text, record) => ( - {record.actual_in} - ), - }, - { - title: i18n.t("jobs.fields.scheduled_completion"), - dataIndex: "scheduled_completion", - key: "scheduled_completion", - ellipsis: true, - sorter: (a, b) => - dateSort(a.scheduled_completion, b.scheduled_completion), - sortOrder: - state.sortedInfo.columnKey === "scheduled_completion" && - state.sortedInfo.order, - render: (text, record) => ( - - ), - }, - { - title: i18n.t("jobs.fields.scheduled_completion") + " (HH:MM)", - dataIndex: "scheduled_completion_time", - key: "scheduled_completion_time", - ellipsis: true, + render: (text, record) => {record.actual_in} + }, + { + title: i18n.t("jobs.fields.scheduled_completion"), + dataIndex: "scheduled_completion", + key: "scheduled_completion", + ellipsis: true, + sorter: (a, b) => dateSort(a.scheduled_completion, b.scheduled_completion), + sortOrder: state.sortedInfo.columnKey === "scheduled_completion" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: i18n.t("jobs.fields.scheduled_completion") + " (HH:MM)", + dataIndex: "scheduled_completion_time", + key: "scheduled_completion_time", + ellipsis: true, - render: (text, record) => ( - {record.scheduled_completion} - ), - }, - { - title: i18n.t("jobs.fields.date_last_contacted"), - dataIndex: "date_last_contacted", - key: "date_last_contacted", - ellipsis: true, - sorter: (a, b) => dateSort(a.date_last_contacted, b.date_last_contacted), - sortOrder: - state.sortedInfo.columnKey === "date_last_contacted" && - state.sortedInfo.order, - render: (text, record) => , - }, - { - title: i18n.t("jobs.fields.date_next_contact"), - dataIndex: "date_next_contact", - key: "date_next_contact", - ellipsis: true, - sorter: (a, b) => dateSort(a.date_next_contact, b.date_next_contact), - sortOrder: - state.sortedInfo.columnKey === "date_next_contact" && - state.sortedInfo.order, - render: (text, record) => ( - - ), - }, - { - title: i18n.t("jobs.fields.scheduled_delivery"), - dataIndex: "scheduled_delivery", - key: "scheduled_delivery", - ellipsis: true, - sorter: (a, b) => dateSort(a.scheduled_delivery, b.scheduled_delivery), - sortOrder: - state.sortedInfo.columnKey === "scheduled_delivery" && - state.sortedInfo.order, - render: (text, record) => ( - - ), - }, - { - title: i18n.t("jobs.fields.scheduled_delivery") + " (HH:MM)", - dataIndex: "scheduled_delivery_time", - key: "scheduled_delivery_time", - ellipsis: true, + render: (text, record) => {record.scheduled_completion} + }, + { + title: i18n.t("jobs.fields.date_last_contacted"), + dataIndex: "date_last_contacted", + key: "date_last_contacted", + ellipsis: true, + sorter: (a, b) => dateSort(a.date_last_contacted, b.date_last_contacted), + sortOrder: state.sortedInfo.columnKey === "date_last_contacted" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: i18n.t("jobs.fields.date_next_contact"), + dataIndex: "date_next_contact", + key: "date_next_contact", + ellipsis: true, + sorter: (a, b) => dateSort(a.date_next_contact, b.date_next_contact), + sortOrder: state.sortedInfo.columnKey === "date_next_contact" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: i18n.t("jobs.fields.scheduled_delivery"), + dataIndex: "scheduled_delivery", + key: "scheduled_delivery", + ellipsis: true, + sorter: (a, b) => dateSort(a.scheduled_delivery, b.scheduled_delivery), + sortOrder: state.sortedInfo.columnKey === "scheduled_delivery" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: i18n.t("jobs.fields.scheduled_delivery") + " (HH:MM)", + dataIndex: "scheduled_delivery_time", + key: "scheduled_delivery_time", + ellipsis: true, - render: (text, record) => ( - {record.scheduled_delivery} - ), - }, - { - title: i18n.t("jobs.fields.ins_co_nm"), - dataIndex: "ins_co_nm", - key: "ins_co_nm", - ellipsis: true, - sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), - sortOrder: - state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order, - }, - { - title: i18n.t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - ellipsis: true, - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - }, - { - title: i18n.t("jobs.fields.clm_total"), - dataIndex: "clm_total", - key: "clm_total", - ellipsis: true, - sorter: (a, b) => a.clm_total - b.clm_total, - sortOrder: - state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, - render: (text, record) => ( - {record.clm_total} - ), - }, - { - title: i18n.t("jobs.fields.owner_owing"), - dataIndex: "owner_owing", - key: "owner_owing", - ellipsis: true, - sorter: (a, b) => a.owner_owing - b.owner_owing, - sortOrder: - state.sortedInfo.columnKey === "owner_owing" && state.sortedInfo.order, - render: (text, record) => ( - {record.owner_owing} - ), - }, - { - title: i18n.t("jobs.fields.ownr_ph1"), - dataIndex: "ownr_ph1", - key: "ownr_ph1", - ellipsis: true, - render: (text, record) => ( - {record.ownr_ph1} - ), - }, - { - title: i18n.t("jobs.fields.ownr_ph2"), - dataIndex: "ownr_ph2", - key: "ownr_ph2", - ellipsis: true, - render: (text, record) => ( - {record.ownr_ph2} - ), - }, - { - title: i18n.t("jobs.fields.specialcoveragepolicy"), - dataIndex: "special_coverage_policy", - key: "special_coverage_policy", - ellipsis: true, - sorter: (a, b) => - Number(a.special_coverage_policy) - Number(b.special_coverage_policy), - sortOrder: - state.sortedInfo.columnKey === "special_coverage_policy" && - state.sortedInfo.order, + render: (text, record) => {record.scheduled_delivery} + }, + { + title: i18n.t("jobs.fields.ins_co_nm"), + dataIndex: "ins_co_nm", + key: "ins_co_nm", + ellipsis: true, + sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), + sortOrder: state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order + }, + { + title: i18n.t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + ellipsis: true, + sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), + sortOrder: state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order + }, + { + title: i18n.t("jobs.fields.clm_total"), + dataIndex: "clm_total", + key: "clm_total", + ellipsis: true, + sorter: (a, b) => a.clm_total - b.clm_total, + sortOrder: state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, + render: (text, record) => {record.clm_total} + }, + { + title: i18n.t("jobs.fields.owner_owing"), + dataIndex: "owner_owing", + key: "owner_owing", + ellipsis: true, + sorter: (a, b) => a.owner_owing - b.owner_owing, + sortOrder: state.sortedInfo.columnKey === "owner_owing" && state.sortedInfo.order, + render: (text, record) => {record.owner_owing} + }, + { + title: i18n.t("jobs.fields.ownr_ph1"), + dataIndex: "ownr_ph1", + key: "ownr_ph1", + ellipsis: true, + render: (text, record) => {record.ownr_ph1} + }, + { + title: i18n.t("jobs.fields.ownr_ph2"), + dataIndex: "ownr_ph2", + key: "ownr_ph2", + ellipsis: true, + render: (text, record) => {record.ownr_ph2} + }, + { + title: i18n.t("jobs.fields.specialcoveragepolicy"), + dataIndex: "special_coverage_policy", + key: "special_coverage_policy", + ellipsis: true, + sorter: (a, b) => Number(a.special_coverage_policy) - Number(b.special_coverage_policy), + sortOrder: state.sortedInfo.columnKey === "special_coverage_policy" && state.sortedInfo.order, filters: [ { text: "True", value: true }, - { text: "False", value: false }, + { text: "False", value: false } ], - onFilter: (value, record) => - value.includes(record.special_coverage_policy), - render: (text, record) => ( - - ), + onFilter: (value, record) => value.includes(record.special_coverage_policy), + render: (text, record) => }, - { - title: i18n.t("jobs.fields.alt_transport"), - dataIndex: "alt_transport", - key: "alt_transport", - ellipsis: true, - sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), - sortOrder: - state.sortedInfo.columnKey === "alt_transport" && - state.sortedInfo.order, - filters: + { + title: i18n.t("jobs.fields.alt_transport"), + dataIndex: "alt_transport", + key: "alt_transport", + ellipsis: true, + sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), + sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order, + filters: (bodyshop && bodyshop.appt_alt_transport.map((s) => { return { text: s, - value: [s], + value: [s] }; })) || [], - onFilter: (value, record) => value.includes(record.alt_transport),render: (text, record) => ( -
    - {record.alt_transport} - -
    - ), - }, - { - title: i18n.t("jobs.fields.status"), - dataIndex: "status", - key: "status", - ellipsis: true, - sorter: (a, b) => statusSort(a.status, b.status, activeStatuses), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - render: (text, record) => , - }, - { - title: i18n.t("jobs.fields.category"), - dataIndex: "category", - key: "category", - ellipsis: true, + onFilter: (value, record) => value.includes(record.alt_transport), + render: (text, record) => ( +
    + {record.alt_transport} + +
    + ) + }, + { + title: i18n.t("jobs.fields.status"), + dataIndex: "status", + key: "status", + ellipsis: true, + sorter: (a, b) => statusSort(a.status, b.status, activeStatuses), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: i18n.t("jobs.fields.category"), + dataIndex: "category", + key: "category", + ellipsis: true, - filters: - (bodyshop && - bodyshop.md_categories.map((s) => { - return { - text: s, - value: [s], - }; - })) || - [], - onFilter: (value, record) => value.includes(record.category), - sorter: (a, b) => alphaSort(a.category, b.category), - sortOrder: - state.sortedInfo.columnKey === "category" && state.sortedInfo.order, - render: (text, record) => ( - - ), - }, - { - title: i18n.t("production.labels.bodyhours"), - dataIndex: "labhrs", - key: "labhrs", - sorter: (a, b) => - a.labhrs.aggregate.sum.mod_lb_hrs - b.labhrs.aggregate.sum.mod_lb_hrs, - sortOrder: - state.sortedInfo.columnKey === "labhrs" && state.sortedInfo.order, - render: (text, record) => record.labhrs.aggregate.sum.mod_lb_hrs, - }, - { - title: i18n.t("production.labels.refinishhours"), - dataIndex: "larhrs", - key: "larhrs", - sorter: (a, b) => - a.larhrs.aggregate.sum.mod_lb_hrs - b.larhrs.aggregate.sum.mod_lb_hrs, - sortOrder: - state.sortedInfo.columnKey === "larhrs" && state.sortedInfo.order, - render: (text, record) => record.larhrs.aggregate.sum.mod_lb_hrs, - }, - { - title: i18n.t("production.labels.totalhours"), - dataIndex: "totalhours", - key: "totalhours", - sorter: (a, b) => - a.labhrs.aggregate.sum.mod_lb_hrs + - a.larhrs.aggregate.sum.mod_lb_hrs - - (b.labhrs.aggregate.sum.mod_lb_hrs + b.larhrs.aggregate.sum.mod_lb_hrs), - sortOrder: - state.sortedInfo.columnKey === "totalhours" && state.sortedInfo.order, - render: (text, record) => - ( - record.labhrs.aggregate.sum.mod_lb_hrs + - record.larhrs.aggregate.sum.mod_lb_hrs - ).toFixed(1), - }, - { - title: i18n.t("production.labels.alert"), - dataIndex: "alert", - key: "alert", + filters: + (bodyshop && + bodyshop.md_categories.map((s) => { + return { + text: s, + value: [s] + }; + })) || + [], + onFilter: (value, record) => value.includes(record.category), + sorter: (a, b) => alphaSort(a.category, b.category), + sortOrder: state.sortedInfo.columnKey === "category" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: i18n.t("production.labels.bodyhours"), + dataIndex: "labhrs", + key: "labhrs", + sorter: (a, b) => a.labhrs.aggregate.sum.mod_lb_hrs - b.labhrs.aggregate.sum.mod_lb_hrs, + sortOrder: state.sortedInfo.columnKey === "labhrs" && state.sortedInfo.order, + render: (text, record) => record.labhrs.aggregate.sum.mod_lb_hrs + }, + { + title: i18n.t("production.labels.refinishhours"), + dataIndex: "larhrs", + key: "larhrs", + sorter: (a, b) => a.larhrs.aggregate.sum.mod_lb_hrs - b.larhrs.aggregate.sum.mod_lb_hrs, + sortOrder: state.sortedInfo.columnKey === "larhrs" && state.sortedInfo.order, + render: (text, record) => record.larhrs.aggregate.sum.mod_lb_hrs + }, + { + title: i18n.t("production.labels.totalhours"), + dataIndex: "totalhours", + key: "totalhours", sorter: (a, b) => - Number(a.production_vars?.alert || false) - - Number(b.production_vars?.alert || false), - sortOrder: - state.sortedInfo.columnKey === "alert" && state.sortedInfo.order, - render: (text, record) => , - }, - { - title: i18n.t("production.labels.note"), - dataIndex: "note", - key: "note", - ellipsis: true, - render: (text, record) => , - }, - { - title: i18n.t("production.labels.comment"), - dataIndex: "comment", - key: "comment", - ellipsis: true, - render: (text, record) => , - }, - { - title: i18n.t("production.labels.touchtime"), - dataIndex: "tt", - key: "tt", - render: (text, record) => { - return ; - }, - }, - { - title: i18n.t("production.labels.bodypriority"), - dataIndex: "bodypriority", - key: "bodypriority", + a.labhrs.aggregate.sum.mod_lb_hrs + + a.larhrs.aggregate.sum.mod_lb_hrs - + (b.labhrs.aggregate.sum.mod_lb_hrs + b.larhrs.aggregate.sum.mod_lb_hrs), + sortOrder: state.sortedInfo.columnKey === "totalhours" && state.sortedInfo.order, + render: (text, record) => + (record.labhrs.aggregate.sum.mod_lb_hrs + record.larhrs.aggregate.sum.mod_lb_hrs).toFixed(1) + }, + { + title: i18n.t("production.labels.alert"), + dataIndex: "alert", + key: "alert", + sorter: (a, b) => Number(a.production_vars?.alert || false) - Number(b.production_vars?.alert || false), + sortOrder: state.sortedInfo.columnKey === "alert" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: i18n.t("production.labels.note"), + dataIndex: "note", + key: "note", + ellipsis: true, + render: (text, record) => + }, + { + title: i18n.t("production.labels.comment"), + dataIndex: "comment", + key: "comment", + ellipsis: true, + render: (text, record) => + }, + { + title: i18n.t("production.labels.touchtime"), + dataIndex: "tt", + key: "tt", + render: (text, record) => { + return ; + } + }, + { + title: i18n.t("production.labels.bodypriority"), + dataIndex: "bodypriority", + key: "bodypriority", - sorter: (a, b) => - ((a.production_vars && a.production_vars.bodypriority) || 11) - - ((b.production_vars && b.production_vars.bodypriority) || 11), - sortOrder: - state.sortedInfo.columnKey === "bodypriority" && state.sortedInfo.order, - render: (text, record) => ( - - ), - }, - { - title: i18n.t("production.labels.paintpriority"), - dataIndex: "paintpriority", - key: "paintpriority", + sorter: (a, b) => + ((a.production_vars && a.production_vars.bodypriority) || 11) - + ((b.production_vars && b.production_vars.bodypriority) || 11), + sortOrder: state.sortedInfo.columnKey === "bodypriority" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: i18n.t("production.labels.paintpriority"), + dataIndex: "paintpriority", + key: "paintpriority", - sorter: (a, b) => - ((a.production_vars && a.production_vars.paintpriority) || 11) - - ((b.production_vars && b.production_vars.paintpriority) || 11), - sortOrder: - state.sortedInfo.columnKey === "paintpriority" && - state.sortedInfo.order, - render: (text, record) => ( - - ), - }, - { - title: i18n.t("production.labels.detailpriority"), - dataIndex: "detailpriority", - key: "detailpriority", + sorter: (a, b) => + ((a.production_vars && a.production_vars.paintpriority) || 11) - + ((b.production_vars && b.production_vars.paintpriority) || 11), + sortOrder: state.sortedInfo.columnKey === "paintpriority" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: i18n.t("production.labels.detailpriority"), + dataIndex: "detailpriority", + key: "detailpriority", - sorter: (a, b) => - ((a.production_vars && a.production_vars.detailpriority) || 11) - - ((b.production_vars && b.production_vars.detailpriority) || 11), - sortOrder: - state.sortedInfo.columnKey === "detailpriority" && - state.sortedInfo.order, - render: (text, record) => ( - - ), - }, - { - title: i18n.t("production.labels.sublets"), - dataIndex: "sublets", - key: "sublets", - render: (text, record) => ( - - ), - }, - { - title: i18n.t("jobs.fields.employee_body"), - dataIndex: "employee_body", - key: "employee_body", - sortOrder: - state.sortedInfo.columnKey === "employee_body" && - state.sortedInfo.order, - sorter: (a, b) => - alphaSort( - bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name, - bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name - ), - render: (text, record) => ( - - ), - }, - { - title: i18n.t("jobs.fields.employee_prep"), - dataIndex: "employee_prep", - key: "employee_prep", - sortOrder: - state.sortedInfo.columnKey === "employee_prep" && - state.sortedInfo.order, - sorter: (a, b) => - alphaSort( - bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name, - bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name - ), - render: (text, record) => ( - - ), - }, - { - title: i18n.t("jobs.fields.employee_csr"), - dataIndex: "employee_csr", - key: "employee_csr", - sortOrder: - state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order, - sorter: (a, b) => - alphaSort( - bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name, - bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name - ), - render: (text, record) => ( - - ), - }, - { - title: i18n.t("jobs.fields.employee_refinish"), - dataIndex: "employee_refinish", - key: "employee_refinish", - sortOrder: - state.sortedInfo.columnKey === "employee_refinish" && - state.sortedInfo.order, - sorter: (a, b) => - alphaSort( - bodyshop.employees?.find((e) => e.id === a.employee_refinish) - ?.first_name, - bodyshop.employees?.find((e) => e.id === b.employee_refinish) - ?.first_name - ), - render: (text, record) => ( - - ), - }, - { - title: i18n.t("jobs.labels.parts_received"), - dataIndex: "parts_received", - key: "parts_received", - render: (text, record) => ( - - ), - }, - { - title: i18n.t("jobs.fields.partsstatus"), - dataIndex: "partsstatus", - key: "partsstatus", - render: (text, record) => ( - - ), - }, - { - title: i18n.t("jobs.labels.estimator"), - dataIndex: "estimator", - key: "estimator", - sorter: (a, b) => - alphaSort( - `${a.est_ct_fn || ""} ${a.est_ct_ln || ""}`.trim(), - `${b.est_ct_fn || ""} ${b.est_ct_ln || ""}`.trim() - ), - sortOrder: - state.sortedInfo.columnKey === "estimator" && state.sortedInfo.order, - filters: - (data && - data - .map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim()) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "N/A", - value: [s], - }; - })) || - [], - onFilter: (value, record) => - value.includes( - `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim() - ), - render: (text, record) => - `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(), - }, + sorter: (a, b) => + ((a.production_vars && a.production_vars.detailpriority) || 11) - + ((b.production_vars && b.production_vars.detailpriority) || 11), + sortOrder: state.sortedInfo.columnKey === "detailpriority" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: i18n.t("production.labels.sublets"), + dataIndex: "sublets", + key: "sublets", + render: (text, record) => + }, + { + title: i18n.t("jobs.fields.employee_body"), + dataIndex: "employee_body", + key: "employee_body", + sortOrder: state.sortedInfo.columnKey === "employee_body" && state.sortedInfo.order, + sorter: (a, b) => + alphaSort( + bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name, + bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name + ), + render: (text, record) => ( + + ) + }, + { + title: i18n.t("jobs.fields.employee_prep"), + dataIndex: "employee_prep", + key: "employee_prep", + sortOrder: state.sortedInfo.columnKey === "employee_prep" && state.sortedInfo.order, + sorter: (a, b) => + alphaSort( + bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name, + bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name + ), + render: (text, record) => ( + + ) + }, + { + title: i18n.t("jobs.fields.employee_csr"), + dataIndex: "employee_csr", + key: "employee_csr", + sortOrder: state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order, + sorter: (a, b) => + alphaSort( + bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name, + bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name + ), + render: (text, record) => ( + + ) + }, + { + title: i18n.t("jobs.fields.employee_refinish"), + dataIndex: "employee_refinish", + key: "employee_refinish", + sortOrder: state.sortedInfo.columnKey === "employee_refinish" && state.sortedInfo.order, + sorter: (a, b) => + alphaSort( + bodyshop.employees?.find((e) => e.id === a.employee_refinish)?.first_name, + bodyshop.employees?.find((e) => e.id === b.employee_refinish)?.first_name + ), + render: (text, record) => ( + + ) + }, + { + title: i18n.t("jobs.labels.parts_received"), + dataIndex: "parts_received", + key: "parts_received", + render: (text, record) => + }, + { + title: i18n.t("jobs.fields.partsstatus"), + dataIndex: "partsstatus", + key: "partsstatus", + render: (text, record) => + }, + { + title: i18n.t("jobs.labels.estimator"), + dataIndex: "estimator", + key: "estimator", + sorter: (a, b) => + alphaSort( + `${a.est_ct_fn || ""} ${a.est_ct_ln || ""}`.trim(), + `${b.est_ct_fn || ""} ${b.est_ct_ln || ""}`.trim() + ), + sortOrder: state.sortedInfo.columnKey === "estimator" && state.sortedInfo.order, + filters: + (data && + data + .map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim()) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "N/A", + value: [s] + }; + })) || + [], + onFilter: (value, record) => value.includes(`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()), + render: (text, record) => `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim() + }, - //Added as a place holder for St Claude. Not implemented as it requires another join for a field used by only 1 client. - // { - // title: i18n.t("vehicles.fields.v_paint_codes", { number: "" }), - // dataIndex: "v_paint_codes", - // key: "v_paint_codes", - // render: (text, record) => - // record.vehicle?.v_paint_codes ? ( - // - // {Object.keys(record.vehicle.v_paint_codes) - // .filter( - // (key) => - // record.vehicle.v_paint_codes[key] !== "" && - // record.vehicle.v_paint_codes[key] !== null && - // record.vehicle.v_paint_codes[key] !== undefined - // ) - // .map((key, idx) => ( - // {record.vehicle.v_paint_codes[key]} - // ))} - // - // ) : null, - // }, - { - title: i18n.t("jobs.fields.date_repairstarted"), - dataIndex: "date_repairstarted", - key: "date_repairstarted", - ellipsis: true, - sorter: (a, b) => dateSort(a.date_repairstarted, b.date_repairstarted), - sortOrder: - state.sortedInfo.columnKey === "date_repairstarted" && - state.sortedInfo.order, - render: (text, record) => ( - - ), - }, - { - title: i18n.t("jobs.fields.date_repairstarted") + " (HH:MM)", - dataIndex: "date_repairstarted_time", - key: "date_repairstarted_time", - ellipsis: true, + //Added as a place holder for St Claude. Not implemented as it requires another join for a field used by only 1 client. + // { + // title: i18n.t("vehicles.fields.v_paint_codes", { number: "" }), + // dataIndex: "v_paint_codes", + // key: "v_paint_codes", + // render: (text, record) => + // record.vehicle?.v_paint_codes ? ( + // + // {Object.keys(record.vehicle.v_paint_codes) + // .filter( + // (key) => + // record.vehicle.v_paint_codes[key] !== "" && + // record.vehicle.v_paint_codes[key] !== null && + // record.vehicle.v_paint_codes[key] !== undefined + // ) + // .map((key, idx) => ( + // {record.vehicle.v_paint_codes[key]} + // ))} + // + // ) : null, + // }, + { + title: i18n.t("jobs.fields.date_repairstarted"), + dataIndex: "date_repairstarted", + key: "date_repairstarted", + ellipsis: true, + sorter: (a, b) => dateSort(a.date_repairstarted, b.date_repairstarted), + sortOrder: state.sortedInfo.columnKey === "date_repairstarted" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: i18n.t("jobs.fields.date_repairstarted") + " (HH:MM)", + dataIndex: "date_repairstarted_time", + key: "date_repairstarted_time", + ellipsis: true, - render: (text, record) => ( - {record.date_repairstarted} - ), - }, - ]; + render: (text, record) => {record.date_repairstarted} + } + ]; }; export default r; diff --git a/client/src/components/production-list-columns/production-list-columns.date.component.jsx b/client/src/components/production-list-columns/production-list-columns.date.component.jsx index 282fd3812..6b1a5340a 100644 --- a/client/src/components/production-list-columns/production-list-columns.date.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.date.component.jsx @@ -1,111 +1,103 @@ -import {useMutation} from "@apollo/client"; -import {Button, Card, Dropdown, TimePicker} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Card, Dropdown, TimePicker } from "antd"; import dayjs from "../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {DateFormatter} from "../../utils/DateFormatter"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { DateFormatter } from "../../utils/DateFormatter"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; -export default function ProductionListDate({ - record, - field, - time, - pastIndicator, - }) { - const [updateAlert] = useMutation(UPDATE_JOB); - const [open, setOpen] = useState(false); - const {t} = useTranslation(); +export default function ProductionListDate({ record, field, time, pastIndicator }) { + const [updateAlert] = useMutation(UPDATE_JOB); + const [open, setOpen] = useState(false); + const { t } = useTranslation(); - const handleChange = (date) => { - logImEXEvent("product_toggle_date", {field}); - // if (date.isSame(record[field] && dayjs(record[field]))) { - // return; - // } + const handleChange = (date) => { + logImEXEvent("product_toggle_date", { field }); + // if (date.isSame(record[field] && dayjs(record[field]))) { + // return; + // } - //e.stopPropagation(); + //e.stopPropagation(); - updateAlert({ - variables: { - jobId: record.id, - job: { - [field]: date, - }, - }, - optimisticResponse: { - update_jobs: { - [field]: date, - }, - }, - }).then(() => { - if (record.refetch) record.refetch(); - if (!time) { - setOpen(false); - } - }); - }; + updateAlert({ + variables: { + jobId: record.id, + job: { + [field]: date + } + }, + optimisticResponse: { + update_jobs: { + [field]: date + } + } + }).then(() => { + if (record.refetch) record.refetch(); + if (!time) { + setOpen(false); + } + }); + }; - let className = ""; - if (pastIndicator) { - className = - !!record[field] && - ((dayjs().isSameOrAfter(dayjs(record[field]), "day") && - "production-completion-past") || - (dayjs().add(1, "day").isSame(dayjs(record[field]), "day") && - "production-completion-soon")); - } - // TODO - Client Update = Why is the overlay a card? + let className = ""; + if (pastIndicator) { + className = + !!record[field] && + ((dayjs().isSameOrAfter(dayjs(record[field]), "day") && "production-completion-past") || + (dayjs().add(1, "day").isSame(dayjs(record[field]), "day") && "production-completion-soon")); + } + // TODO - Client Update = Why is the overlay a card? - const overlayMenu = { - items: [ - { - key: 'overlayItem1', - label: - e.stopPropagation()}> - e.stopPropagation()} - value={(record[field] && dayjs(record[field])) || null} - onChange={handleChange} - format="MM/DD/YYYY" - isDateOnly={!time} - /> - {time && ( - e.stopPropagation()} - value={(record[field] && dayjs(record[field])) || null} - onChange={handleChange} - minuteStep={15} - format="hh:mm a" - /> - )} - - - } - ] - } + const overlayMenu = { + items: [ + { + key: "overlayItem1", + label: ( + e.stopPropagation()}> + e.stopPropagation()} + value={(record[field] && dayjs(record[field])) || null} + onChange={handleChange} + format="MM/DD/YYYY" + isDateOnly={!time} + /> + {time && ( + e.stopPropagation()} + value={(record[field] && dayjs(record[field])) || null} + onChange={handleChange} + minuteStep={15} + format="hh:mm a" + /> + )} + + + ) + } + ] + }; - return ( - setOpen(v)} - open={open} - style={{ - height: "19px", - }} - menu={overlayMenu} - > -
    setOpen(true)} - style={{ - height: "19px", - }} - className={className} - > - {record[field]} -
    -
    - ); + return ( + setOpen(v)} + open={open} + style={{ + height: "19px" + }} + menu={overlayMenu} + > +
    setOpen(true)} + style={{ + height: "19px" + }} + className={className} + > + {record[field]} +
    +
    + ); } diff --git a/client/src/components/production-list-columns/production-list-columns.detailpriority.component.jsx b/client/src/components/production-list-columns/production-list-columns.detailpriority.component.jsx index cb03f11ed..bc7776d05 100644 --- a/client/src/components/production-list-columns/production-list-columns.detailpriority.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.detailpriority.component.jsx @@ -1,62 +1,57 @@ -import {useMutation} from "@apollo/client"; -import {Dropdown} from "antd"; +import { useMutation } from "@apollo/client"; +import { Dropdown } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { useTranslation } from "react-i18next"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; -export default function ProductionListColumnDetailPriority({record}) { - const {t} = useTranslation(); +export default function ProductionListColumnDetailPriority({ record }) { + const { t } = useTranslation(); - const [updateAlert] = useMutation(UPDATE_JOB); + const [updateAlert] = useMutation(UPDATE_JOB); - const handleSetDetailPriority = (e) => { - logImEXEvent("production_set_detail_priority"); - // e.stopPropagation(); - const {key} = e; - updateAlert({ - variables: { - jobId: record.id, - job: { - production_vars: { - ...record.production_vars, - detailpriority: key === "clearDetailPriority" ? null : key, - }, - }, - }, - }).then(() => { - if (record.refetch) record.refetch(); - }); - }; + const handleSetDetailPriority = (e) => { + logImEXEvent("production_set_detail_priority"); + // e.stopPropagation(); + const { key } = e; + updateAlert({ + variables: { + jobId: record.id, + job: { + production_vars: { + ...record.production_vars, + detailpriority: key === "clearDetailPriority" ? null : key + } + } + } + }).then(() => { + if (record.refetch) record.refetch(); + }); + }; - const menu = { - items: [ - { - key: "clearDetailPriority", - label: t("production.actions.detailpriority-clear"), - }, - { - key: "set", - label: t("production.actions.detailpriority-set"), - children: new Array(15).fill().map((value, index) => ({ - key: index + 1, - label: ( -
    - {index + 1} -
    - ), - })), - }, - ], - onClick: handleSetDetailPriority - } + const menu = { + items: [ + { + key: "clearDetailPriority", + label: t("production.actions.detailpriority-clear") + }, + { + key: "set", + label: t("production.actions.detailpriority-set"), + children: new Array(15).fill().map((value, index) => ({ + key: index + 1, + label:
    {index + 1}
    + })) + } + ], + onClick: handleSetDetailPriority + }; - return ( - -
    - {record.production_vars && record.production_vars.detailpriority} -
    -
    - ); + return ( + +
    + {record.production_vars && record.production_vars.detailpriority} +
    +
    + ); } diff --git a/client/src/components/production-list-columns/production-list-columns.empassignment.component.jsx b/client/src/components/production-list-columns/production-list-columns.empassignment.component.jsx index 4e93cdeb2..d4c92220e 100644 --- a/client/src/components/production-list-columns/production-list-columns.empassignment.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.empassignment.component.jsx @@ -1,200 +1,177 @@ -import {DeleteFilled, PlusCircleFilled} from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {Button, Col, notification, Popover, Row, Select, Space, Spin,} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { DeleteFilled, PlusCircleFilled } from "@ant-design/icons"; +import { useMutation } from "@apollo/client"; +import { Button, Col, notification, Popover, Row, Select, Space, Spin } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; -const iconStyle = {marginLeft: ".3rem"}; +const iconStyle = { marginLeft: ".3rem" }; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export function ProductionListEmpAssignment({ - insertAuditTrail, - bodyshop, - record, - refetch, - type, - }) { - const {t} = useTranslation(); - const [updateJob] = useMutation(UPDATE_JOB); - const [loading, setLoading] = useState(false); +export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record, refetch, type }) { + const { t } = useTranslation(); + const [updateJob] = useMutation(UPDATE_JOB); + const [loading, setLoading] = useState(false); - const handleAdd = async (assignment) => { - setLoading(true); - const {operation, employeeid, name} = assignment; - logImEXEvent("job_assign_employee", {operation}); + const handleAdd = async (assignment) => { + setLoading(true); + const { operation, employeeid, name } = assignment; + logImEXEvent("job_assign_employee", { operation }); - let empAssignment = determineFieldName(operation); + let empAssignment = determineFieldName(operation); - const result = await updateJob({ - variables: {jobId: record.id, job: {[empAssignment]: employeeid}}, + const result = await updateJob({ + variables: { jobId: record.id, job: { [empAssignment]: employeeid } } - // awaitRefetchQueries: true, - }); - - insertAuditTrail({ - jobid: record.id, - operation: AuditTrailMapping.jobassignmentchange(empAssignment, name), - type: "jobassignmentchange", + // awaitRefetchQueries: true, }); - if (!!result.errors) { - notification["error"]({ - message: t("jobs.errors.assigning", { - message: JSON.stringify(result.errors), - }), - }); - } - - await refetch(); - - setLoading(false); - }; - const handleRemove = async (operation) => { - setLoading(true); - logImEXEvent("job_unassign_employee", {operation}); - - let empAssignment = determineFieldName(operation); - const result = await updateJob({ - variables: {jobId: record.id, job: {[empAssignment]: null}}, - - awaitRefetchQueries: true, - }); - - insertAuditTrail({ - jobid: record.id, - operation: AuditTrailMapping.jobassignmentremoved(empAssignment), - type: "jobassignmentremoved", + insertAuditTrail({ + jobid: record.id, + operation: AuditTrailMapping.jobassignmentchange(empAssignment, name), + type: "jobassignmentchange" }); - if (!!result.errors) { - notification["error"]({ - message: t("jobs.errors.assigning", { - message: JSON.stringify(result.errors), - }), - }); - } + if (!!result.errors) { + notification["error"]({ + message: t("jobs.errors.assigning", { + message: JSON.stringify(result.errors) + }) + }); + } - await refetch(); + await refetch(); - setLoading(false); - }; + setLoading(false); + }; + const handleRemove = async (operation) => { + setLoading(true); + logImEXEvent("job_unassign_employee", { operation }); - const [assignment, setAssignment] = useState({ - operation: null, - employeeid: null, + let empAssignment = determineFieldName(operation); + const result = await updateJob({ + variables: { jobId: record.id, job: { [empAssignment]: null } }, + + awaitRefetchQueries: true }); - const [visibility, setVisibility] = useState(false); - const onChange = (e, option) => { - setAssignment({...assignment, employeeid: e, name: option.name}); - }; + insertAuditTrail({ + jobid: record.id, + operation: AuditTrailMapping.jobassignmentremoved(empAssignment), + type: "jobassignmentremoved" + }); - const popContent = ( - -
    - - - - - - - - - - ); + if (!!result.errors) { + notification["error"]({ + message: t("jobs.errors.assigning", { + message: JSON.stringify(result.errors) + }) + }); + } - let theEmployee; + await refetch(); - if (record[type]) - theEmployee = bodyshop.employees.find((e) => e.id === record[type]); + setLoading(false); + }; - return ( - - - {record[type] ? ( -
    - {`${theEmployee.first_name || ""} ${ - theEmployee.last_name || "" - }`} - handleRemove(type)} - /> -
    - ) : ( - { - setAssignment({operation: type}); - setVisibility(true); - }} - /> - )} -
    -
    - ); + const [assignment, setAssignment] = useState({ + operation: null, + employeeid: null + }); + + const [visibility, setVisibility] = useState(false); + const onChange = (e, option) => { + setAssignment({ ...assignment, employeeid: e, name: option.name }); + }; + + const popContent = ( + + + + + + + + + + + + ); + + let theEmployee; + + if (record[type]) theEmployee = bodyshop.employees.find((e) => e.id === record[type]); + + return ( + + + {record[type] ? ( +
    + {`${theEmployee.first_name || ""} ${theEmployee.last_name || ""}`} + handleRemove(type)} /> +
    + ) : ( + { + setAssignment({ operation: type }); + setVisibility(true); + }} + /> + )} +
    +
    + ); } const determineFieldName = (operation) => { - switch (operation) { - case "employee_body": - return "employee_body"; - case "employee_prep": - return "employee_prep"; - case "employee_refinish": - return "employee_refinish"; - case "employee_csr": - return "employee_csr"; - default: - return null; - } + switch (operation) { + case "employee_body": + return "employee_body"; + case "employee_prep": + return "employee_prep"; + case "employee_refinish": + return "employee_refinish"; + case "employee_csr": + return "employee_csr"; + default: + return null; + } }; -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionListEmpAssignment); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionListEmpAssignment); diff --git a/client/src/components/production-list-columns/production-list-columns.lastcontacted.component.jsx b/client/src/components/production-list-columns/production-list-columns.lastcontacted.component.jsx index 335a3ceb7..4b4b03a72 100644 --- a/client/src/components/production-list-columns/production-list-columns.lastcontacted.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.lastcontacted.component.jsx @@ -1,154 +1,134 @@ -import {useMutation} from "@apollo/client"; -import {Button, Card, Dropdown, Form, Input, notification, Space} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Card, Dropdown, Form, Input, notification, Space } from "antd"; import dayjs from "../../utils/day"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {INSERT_NEW_NOTE} from "../../graphql/notes.queries"; -import {selectCurrentUser} from "../../redux/user/user.selectors"; -import {DateFormatter} from "../../utils/DateFormatter"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { INSERT_NEW_NOTE } from "../../graphql/notes.queries"; +import { selectCurrentUser } from "../../redux/user/user.selectors"; +import { DateFormatter } from "../../utils/DateFormatter"; import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionLastContacted); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionLastContacted); -export function ProductionLastContacted({currentUser, record}) { - const [updateAlert] = useMutation(UPDATE_JOB); - const [insertNote] = useMutation(INSERT_NEW_NOTE); - const [open, setOpen] = useState(false); - const {t} = useTranslation(); - const [form] = Form.useForm(); - const handleFinish = async ({ - date_last_contacted, - date_next_contact, - note, - }) => { - logImEXEvent("production_last_contacted"); +export function ProductionLastContacted({ currentUser, record }) { + const [updateAlert] = useMutation(UPDATE_JOB); + const [insertNote] = useMutation(INSERT_NEW_NOTE); + const [open, setOpen] = useState(false); + const { t } = useTranslation(); + const [form] = Form.useForm(); + const handleFinish = async ({ date_last_contacted, date_next_contact, note }) => { + logImEXEvent("production_last_contacted"); - //e.stopPropagation(); - const res = await updateAlert({ - variables: { - jobId: record.id, - job: { - date_last_contacted, - ...(date_next_contact ? {date_next_contact} : {}), - }, - }, - }); - if (res.errors) { - notification.open({ - type: "error", - message: t("jobs.errors.saving", { - error: JSON.stringify(res.errors), - }), - }); + //e.stopPropagation(); + const res = await updateAlert({ + variables: { + jobId: record.id, + job: { + date_last_contacted, + ...(date_next_contact ? { date_next_contact } : {}) } - if (note && note.trim() !== "") { - //Insert a note. - const res2 = await insertNote({ - variables: { - noteInput: { - jobid: record.id, - text: note, - created_by: currentUser.email, - }, - }, - }); - if (res2.errors) { - notification.open({ - type: "error", - message: t("notes.errors.inserting", { - error: JSON.stringify(res.errors), - }), - }); - } - } - if (record.refetch) record.refetch(); - - setOpen(false); - }; - - useEffect(() => { - if (open) { - form.setFieldsValue({ - note: null, - date_last_contacted: - record.date_last_contacted && dayjs(record.date_last_contacted), - }); - } - }, [open, form, record.date_last_contacted]); - - const overlayMenu = { - items: [ - { - key: 'overlay-item-1', - label: - e.stopPropagation()} - > -
    - - - - - - - - - - - - - - -
    - } - ] + } + }); + if (res.errors) { + notification.open({ + type: "error", + message: t("jobs.errors.saving", { + error: JSON.stringify(res.errors) + }) + }); } + if (note && note.trim() !== "") { + //Insert a note. + const res2 = await insertNote({ + variables: { + noteInput: { + jobid: record.id, + text: note, + created_by: currentUser.email + } + } + }); + if (res2.errors) { + notification.open({ + type: "error", + message: t("notes.errors.inserting", { + error: JSON.stringify(res.errors) + }) + }); + } + } + if (record.refetch) record.refetch(); - return ( -
    - -
    setOpen(true)} - style={{ - height: "19px", - }} - > - - {record.date_last_contacted} - -
    -
    + setOpen(false); + }; + + useEffect(() => { + if (open) { + form.setFieldsValue({ + note: null, + date_last_contacted: record.date_last_contacted && dayjs(record.date_last_contacted) + }); + } + }, [open, form, record.date_last_contacted]); + + const overlayMenu = { + items: [ + { + key: "overlay-item-1", + label: ( + e.stopPropagation()}> +
    + + + + + + + + + + + + + + +
    + ) + } + ] + }; + + return ( +
    + +
    setOpen(true)} + style={{ + height: "19px" + }} + > + {record.date_last_contacted}
    - ); +
    +
    + ); } diff --git a/client/src/components/production-list-columns/production-list-columns.paintpriority.component.jsx b/client/src/components/production-list-columns/production-list-columns.paintpriority.component.jsx index c6b371cac..cdb0a48ab 100644 --- a/client/src/components/production-list-columns/production-list-columns.paintpriority.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.paintpriority.component.jsx @@ -1,61 +1,57 @@ -import {useMutation} from "@apollo/client"; -import {Dropdown} from "antd"; +import { useMutation } from "@apollo/client"; +import { Dropdown } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { useTranslation } from "react-i18next"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; -export default function ProductionListColumnPaintPriority({record}) { - const {t} = useTranslation(); +export default function ProductionListColumnPaintPriority({ record }) { + const { t } = useTranslation(); - const [updateAlert] = useMutation(UPDATE_JOB); + const [updateAlert] = useMutation(UPDATE_JOB); - const handleSetPaintPriority = (e) => { - logImEXEvent("production_set_paint_priority"); - // e.stopPropagation(); - const {key} = e; - updateAlert({ - variables: { - jobId: record.id, - job: { - production_vars: { - ...record.production_vars, - paintpriority: key === "clearPaintPriority" ? null : key, - }, - }, - }, - }).then(() => { - if (record.refetch) record.refetch(); - }); - }; + const handleSetPaintPriority = (e) => { + logImEXEvent("production_set_paint_priority"); + // e.stopPropagation(); + const { key } = e; + updateAlert({ + variables: { + jobId: record.id, + job: { + production_vars: { + ...record.production_vars, + paintpriority: key === "clearPaintPriority" ? null : key + } + } + } + }).then(() => { + if (record.refetch) record.refetch(); + }); + }; - const menu = { - items: [ - { - key: "clearPaintPriority", - label: t("production.actions.paintpriority-clear"), - }, - { - key: "set", - label: t("production.actions.paintpriority-set"), - children: new Array(15).fill().map((value, index) => ({ - key: index + 1, - label: ( -
    - {index + 1} -
    - ), - })), - }, - ], - onClick: handleSetPaintPriority - }; + const menu = { + items: [ + { + key: "clearPaintPriority", + label: t("production.actions.paintpriority-clear") + }, + { + key: "set", + label: t("production.actions.paintpriority-set"), + children: new Array(15).fill().map((value, index) => ({ + key: index + 1, + label:
    {index + 1}
    + })) + } + ], + onClick: handleSetPaintPriority + }; - return ( - -
    - {record.production_vars && record.production_vars.paintpriority} -
    -
    - ); + return ( + +
    + {record.production_vars && record.production_vars.paintpriority} +
    +
    + ); } diff --git a/client/src/components/production-list-columns/production-list-columns.partsreceived.component.jsx b/client/src/components/production-list-columns/production-list-columns.partsreceived.component.jsx index 8a12a6ed9..b94082964 100644 --- a/client/src/components/production-list-columns/production-list-columns.partsreceived.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.partsreceived.component.jsx @@ -1,41 +1,33 @@ -import {useMemo} from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useMemo } from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionListColumnPartsReceived); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionListColumnPartsReceived); -export function ProductionListColumnPartsReceived({bodyshop, record}) { - const amount = useMemo(() => { - const amount = record.joblines_status.reduce( - (acc, val) => { - acc.total += val.count; - acc.received = - val.status === bodyshop.md_order_statuses.default_received - ? acc.received + val.count - : acc.received; - return acc; - }, - {total: 0, received: 0} - ); +export function ProductionListColumnPartsReceived({ bodyshop, record }) { + const amount = useMemo(() => { + const amount = record.joblines_status.reduce( + (acc, val) => { + acc.total += val.count; + acc.received = + val.status === bodyshop.md_order_statuses.default_received ? acc.received + val.count : acc.received; + return acc; + }, + { total: 0, received: 0 } + ); - return { - ...amount, - percent: - amount.total !== 0 - ? ((amount.received / amount.total) * 100).toFixed(0) + "%" - : "N/A", - }; - }, [record, bodyshop.md_order_statuses]); + return { + ...amount, + percent: amount.total !== 0 ? ((amount.received / amount.total) * 100).toFixed(0) + "%" : "N/A" + }; + }, [record, bodyshop.md_order_statuses]); - return `${amount.percent} (${amount.received}/${amount.total})`; + return `${amount.percent} (${amount.received}/${amount.total})`; } diff --git a/client/src/components/production-list-columns/production-list-columns.productionnote.component.jsx b/client/src/components/production-list-columns/production-list-columns.productionnote.component.jsx index b9131208f..b5f5a1556 100644 --- a/client/src/components/production-list-columns/production-list-columns.productionnote.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.productionnote.component.jsx @@ -1,117 +1,110 @@ import Icon from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {Button, Input, Popover, Space} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {FaRegStickyNote} from "react-icons/fa"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; +import { useMutation } from "@apollo/client"; +import { Button, Input, Popover, Space } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { FaRegStickyNote } from "react-icons/fa"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; const mapStateToProps = createStructuredSelector({}); const mapDispatchToProps = (dispatch) => ({ - setNoteUpsertContext: (context) => - dispatch(setModalContext({context: context, modal: "noteUpsert"})), + setNoteUpsertContext: (context) => dispatch(setModalContext({ context: context, modal: "noteUpsert" })) }); -function ProductionListColumnProductionNote({record, setNoteUpsertContext}) { - const {t} = useTranslation(); +function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) { + const { t } = useTranslation(); - const [note, setNote] = useState( - (record.production_vars && record.production_vars.note) || "" - ); + const [note, setNote] = useState((record.production_vars && record.production_vars.note) || ""); - const [open, setOpen] = useState(false); + const [open, setOpen] = useState(false); - const [updateAlert] = useMutation(UPDATE_JOB); + const [updateAlert] = useMutation(UPDATE_JOB); - const handleSaveNote = (e) => { - logImEXEvent("production_add_note"); - e.stopPropagation(); - setOpen(false); - updateAlert({ - variables: { - jobId: record.id, - job: { - production_vars: { - ...record.production_vars, - note: note, - }, - }, - }, - }).then(() => { - if (record.refetch) record.refetch(); - }); - }; + const handleSaveNote = (e) => { + logImEXEvent("production_add_note"); + e.stopPropagation(); + setOpen(false); + updateAlert({ + variables: { + jobId: record.id, + job: { + production_vars: { + ...record.production_vars, + note: note + } + } + } + }).then(() => { + if (record.refetch) record.refetch(); + }); + }; - const handleChange = (e) => { - e.stopPropagation(); - setNote(e.target.value); - }; + const handleChange = (e) => { + e.stopPropagation(); + setNote(e.target.value); + }; - const handleOpenChange = (flag) => { - setOpen(flag); - if (flag) - setNote((record.production_vars && record.production_vars.note) || ""); - }; + const handleOpenChange = (flag) => { + setOpen(flag); + if (flag) setNote((record.production_vars && record.production_vars.note) || ""); + }; - return ( - - - - - - -
    - } - trigger={["click"]} - > -
    + + + +
    - - ); + {t("notes.actions.savetojobnotes")} + + + + } + trigger={["click"]} + > +
    + + {(record.production_vars && record.production_vars.note) || " "} +
    + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionListColumnProductionNote); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionListColumnProductionNote); diff --git a/client/src/components/production-list-columns/production-list-columns.status.category.jsx b/client/src/components/production-list-columns/production-list-columns.status.category.jsx index f400c6e94..ceae8b63c 100644 --- a/client/src/components/production-list-columns/production-list-columns.status.category.jsx +++ b/client/src/components/production-list-columns/production-list-columns.status.category.jsx @@ -1,65 +1,61 @@ -import {useMutation} from "@apollo/client"; -import {Dropdown, Spin} from "antd"; -import React, {useState} from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useMutation } from "@apollo/client"; +import { Dropdown, Spin } from "antd"; +import React, { useState } from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export function ProductionListColumnCategory({record, bodyshop}) { - const [updateJob] = useMutation(UPDATE_JOB); - const [loading, setLoading] = useState(false); +export function ProductionListColumnCategory({ record, bodyshop }) { + const [updateJob] = useMutation(UPDATE_JOB); + const [loading, setLoading] = useState(false); - const handleSetStatus = async (e) => { - logImEXEvent("production_change_status"); + const handleSetStatus = async (e) => { + logImEXEvent("production_change_status"); - setLoading(true); - const {key} = e; - await updateJob({ - variables: { - jobId: record.id, - job: { - category: key, - }, - }, - }); - - setLoading(false); - }; - - const menu = { - items: bodyshop.md_categories.map((item) => ({ - key: item, - label: item, - })), - onClick: handleSetStatus, - style: { - maxHeight: "200px", - overflowY: "auto", + setLoading(true); + const { key } = e; + await updateJob({ + variables: { + jobId: record.id, + job: { + category: key } - }; + } + }); - return ( - -
    - {record.category} - {loading && } -
    -
    - ); + setLoading(false); + }; + + const menu = { + items: bodyshop.md_categories.map((item) => ({ + key: item, + label: item + })), + onClick: handleSetStatus, + style: { + maxHeight: "200px", + overflowY: "auto" + } + }; + + return ( + +
    + {record.category} + {loading && } +
    +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionListColumnCategory); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionListColumnCategory); diff --git a/client/src/components/production-list-columns/production-list-columns.status.component.jsx b/client/src/components/production-list-columns/production-list-columns.status.component.jsx index 219c5c90f..23dc378ef 100644 --- a/client/src/components/production-list-columns/production-list-columns.status.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.status.component.jsx @@ -1,74 +1,67 @@ -import {useMutation} from "@apollo/client"; -import {Dropdown, Spin} from "antd"; -import React, {useState} from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions";import { selectBodyshop } from "../../redux/user/user.selectors"; +import { useMutation } from "@apollo/client"; +import { Dropdown, Spin } from "antd"; +import React, { useState } from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export function ProductionListColumnStatus({ - record, - bodyshop, - insertAuditTrail, - }) { - const [updateJob] = useMutation(UPDATE_JOB); - const [loading, setLoading] = useState(false); +export function ProductionListColumnStatus({ record, bodyshop, insertAuditTrail }) { + const [updateJob] = useMutation(UPDATE_JOB); + const [loading, setLoading] = useState(false); - const handleSetStatus = async (e) => { - logImEXEvent("production_change_status"); - // e.stopPropagation(); - setLoading(true); - const {key} = e; - await updateJob({ - variables: { - jobId: record.id, - job: { - status: key, - }, - }, - }); - insertAuditTrail({ - jobid: record.id, - operation: AuditTrailMapping.jobstatuschange(key), - type: "jobstatuschange", + const handleSetStatus = async (e) => { + logImEXEvent("production_change_status"); + // e.stopPropagation(); + setLoading(true); + const { key } = e; + await updateJob({ + variables: { + jobId: record.id, + job: { + status: key + } + } + }); + insertAuditTrail({ + jobid: record.id, + operation: AuditTrailMapping.jobstatuschange(key), + type: "jobstatuschange" }); - setLoading(false); - }; + setLoading(false); + }; - const menu = { - items: bodyshop.md_ro_statuses.production_statuses.map((item) => ({ - key: item, - label: item, - })), - onClick: handleSetStatus, - style: { - maxHeight: "200px", - overflowY: "auto", - } - }; + const menu = { + items: bodyshop.md_ro_statuses.production_statuses.map((item) => ({ + key: item, + label: item + })), + onClick: handleSetStatus, + style: { + maxHeight: "200px", + overflowY: "auto" + } + }; - return ( - -
    - {record.status} - {loading && } -
    -
    - ); + return ( + +
    + {record.status} + {loading && } +
    +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionListColumnStatus); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionListColumnStatus); diff --git a/client/src/components/production-list-columns/prodution-list-columns.touchtime.component.jsx b/client/src/components/production-list-columns/prodution-list-columns.touchtime.component.jsx index 30219a299..65a4857e0 100644 --- a/client/src/components/production-list-columns/prodution-list-columns.touchtime.component.jsx +++ b/client/src/components/production-list-columns/prodution-list-columns.touchtime.component.jsx @@ -1,39 +1,30 @@ import dayjs from "../../utils/day"; -import React, {useMemo} from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useMemo } from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionlistColumnTouchTime); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionlistColumnTouchTime); -export function ProductionlistColumnTouchTime({bodyshop, job}) { - let ct = useMemo(() => { - if (!!job.actual_in) { - const totalHrs = - (job.larhrs.aggregate.sum.mod_lb_hrs || 0) + - (job.labhrs.aggregate.sum.mod_lb_hrs || 0); +export function ProductionlistColumnTouchTime({ bodyshop, job }) { + let ct = useMemo(() => { + if (!!job.actual_in) { + const totalHrs = (job.larhrs.aggregate.sum.mod_lb_hrs || 0) + (job.labhrs.aggregate.sum.mod_lb_hrs || 0); - const Difference_In_Days = dayjs().diff( - dayjs(job.actual_in), - "day", - true - ); + const Difference_In_Days = dayjs().diff(dayjs(job.actual_in), "day", true); - return (totalHrs / Difference_In_Days).toFixed(2); - } - return 0; - }, [job]); + return (totalHrs / Difference_In_Days).toFixed(2); + } + return 0; + }, [job]); - const underTarget = ct < bodyshop.target_touchtime; + const underTarget = ct < bodyshop.target_touchtime; - return {ct}; + return {ct}; } diff --git a/client/src/components/production-list-detail/production-list-detail.component.jsx b/client/src/components/production-list-detail/production-list-detail.component.jsx index 345c0d980..a5b06d109 100644 --- a/client/src/components/production-list-detail/production-list-detail.component.jsx +++ b/client/src/components/production-list-detail/production-list-detail.component.jsx @@ -1,19 +1,19 @@ -import {PrinterFilled} from "@ant-design/icons"; -import {useQuery} from "@apollo/client"; -import {Button, Descriptions, Drawer, Space} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import { PrinterFilled } from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import { Button, Descriptions, Drawer, Space } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; import queryString from "query-string"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {QUERY_JOB_CARD_DETAILS} from "../../graphql/jobs.queries"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {DateFormatter} from "../../utils/DateFormatter"; +import { DateFormatter } from "../../utils/DateFormatter"; import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import AlertComponent from "../alert/alert.component"; import StartChatButton from "../chat-open-button/chat-open-button.component"; @@ -29,168 +29,127 @@ import OwnerNameDisplay from "../owner-name-display/owner-name-display.component import ProductionRemoveButton from "../production-remove-button/production-remove-button.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - technician: selectTechnician, + bodyshop: selectBodyshop, + technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ - setPrintCenterContext: (context) => - dispatch(setModalContext({context: context, modal: "printCenter"})), + setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" })) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionListDetail); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionListDetail); -export function ProductionListDetail({ - bodyshop, - jobs, - setPrintCenterContext, - technician, - }) { - const search = queryString.parse(useLocation().search); - const history = useNavigate(); - const {selected} = search; +export function ProductionListDetail({ bodyshop, jobs, setPrintCenterContext, technician }) { + const search = queryString.parse(useLocation().search); + const history = useNavigate(); + const { selected } = search; - const {t} = useTranslation(); - const theJob = jobs.find((j) => j.id === selected) || {}; + const { t } = useTranslation(); + const theJob = jobs.find((j) => j.id === selected) || {}; - const handleClose = () => { - delete search.selected; - history({search: queryString.stringify(search)}); - }; - const {loading, error, data, refetch} = useQuery(QUERY_JOB_CARD_DETAILS, { - variables: {id: selected}, - skip: !selected, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const handleClose = () => { + delete search.selected; + history({ search: queryString.stringify(search) }); + }; + const { loading, error, data, refetch } = useQuery(QUERY_JOB_CARD_DETAILS, { + variables: { id: selected }, + skip: !selected, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - return ( - - {!technician ? ( - - ) : null} - - {!technician ? ( - - ) : null} - + return ( + + {!technician ? : null} + + {!technician ? : null} + + } + /> + } + placement="right" + width={"50%"} + onClose={handleClose} + open={selected} + > + {loading && } + {error && } + {!loading && data && ( +
    + + + +
    + + {theJob.ro_number || ""} + + + {data.jobs_by_pk.alt_transport || ""} + + + + {theJob.clm_no || ""} + {theJob.ins_co_nm || ""} + + + + {!technician ? ( + <> + + + + ) : ( + <> + {data.jobs_by_pk.ownr_ph1} + {data.jobs_by_pk.ownr_ph2} + + )} + + + + {`${theJob.v_model_yr || ""} ${theJob.v_color || ""} ${ + theJob.v_make_desc || "" + } ${theJob.v_model_desc || ""}`} + + + {theJob.clm_total} + + + {theJob.actual_in} + + + {theJob.scheduled_completion} + + +
    + +
    + + {!bodyshop.uselocalmediaserver && ( + <> +
    + + + )} +
    + )} + + ); } diff --git a/client/src/components/production-list-save-config-button/production-list-save-config-button.component.jsx b/client/src/components/production-list-save-config-button/production-list-save-config-button.component.jsx index ad96eec51..bdbb2b5a6 100644 --- a/client/src/components/production-list-save-config-button/production-list-save-config-button.component.jsx +++ b/client/src/components/production-list-save-config-button/production-list-save-config-button.component.jsx @@ -1,102 +1,89 @@ -import {useMutation} from "@apollo/client"; -import React, {useState} from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {Button, Form, Input, notification, Popover, Space} from "antd"; -import {useTranslation} from "react-i18next"; -import {UPDATE_SHOP} from "../../graphql/bodyshop.queries"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { useMutation } from "@apollo/client"; +import React, { useState } from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { Button, Form, Input, notification, Popover, Space } from "antd"; +import { useTranslation } from "react-i18next"; +import { UPDATE_SHOP } from "../../graphql/bodyshop.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function ProductionListSaveConfigButton({ - columns, - bodyshop, - tableState, - }) { - const [updateShop] = useMutation(UPDATE_SHOP); - const [loading, setLoading] = useState(false); - const [open, setOpen] = useState(false); - const [form] = Form.useForm(); +export function ProductionListSaveConfigButton({ columns, bodyshop, tableState }) { + const [updateShop] = useMutation(UPDATE_SHOP); + const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [form] = Form.useForm(); - const {t} = useTranslation(); + const { t } = useTranslation(); - const handleSaveConfig = async (values) => { - logImEXEvent("production_save_config"); - setLoading(true); - const result = await updateShop({ - variables: { - id: bodyshop.id, - shop: { - production_config: [ - ...bodyshop.production_config.filter((b) => b.name !== values.name), - //Assign it to the name - { - name: values.name, - columns: { - columnKeys: columns.map((i) => { - return {key: i.key, width: i.width}; - }), - tableState, - }, - }, - ], - }, - }, - }); - if (!!!result.errors) { - notification["success"]({message: t("bodyshop.successes.save")}); - } else { - notification["error"]({ - message: t("bodyshop.errors.saving", { - error: JSON.stringify(result.errors), + const handleSaveConfig = async (values) => { + logImEXEvent("production_save_config"); + setLoading(true); + const result = await updateShop({ + variables: { + id: bodyshop.id, + shop: { + production_config: [ + ...bodyshop.production_config.filter((b) => b.name !== values.name), + //Assign it to the name + { + name: values.name, + columns: { + columnKeys: columns.map((i) => { + return { key: i.key, width: i.width }; }), - }); + tableState + } + } + ] } - form.resetFields(); - setOpen(false); - setLoading(false); - }; - const popMenu = ( -
    -
    - - - + } + }); + if (!!!result.errors) { + notification["success"]({ message: t("bodyshop.successes.save") }); + } else { + notification["error"]({ + message: t("bodyshop.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + form.resetFields(); + setOpen(false); + setLoading(false); + }; + const popMenu = ( +
    + + + + - - - - - -
    - ); + + + + + +
    + ); - return ( - - - - ); + return ( + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionListSaveConfigButton); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionListSaveConfigButton); diff --git a/client/src/components/production-list-table/production-list-print.component.jsx b/client/src/components/production-list-table/production-list-print.component.jsx index e5cd71ecf..08c7f206d 100644 --- a/client/src/components/production-list-table/production-list-print.component.jsx +++ b/client/src/components/production-list-table/production-list-print.component.jsx @@ -1,125 +1,119 @@ -import {Button, Dropdown} from "antd"; -import React, {useState} from "react"; -import {TemplateList} from "../../utils/TemplateConstants"; -import {useTranslation} from "react-i18next"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { Button, Dropdown } from "antd"; +import React, { useState } from "react"; +import { TemplateList } from "../../utils/TemplateConstants"; +import { useTranslation } from "react-i18next"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const ProdTemplates = TemplateList("production"); -const { - production_by_technician_one, - production_by_category_one, - production_by_repair_status_one, -} = TemplateList("special"); +const { production_by_technician_one, production_by_category_one, production_by_repair_status_one } = + TemplateList("special"); const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionListPrint); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionListPrint); +export function ProductionListPrint({ bodyshop }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); -export function ProductionListPrint({bodyshop}) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - - const menu = { - items: [ - ...Object.keys(ProdTemplates).map((key) => { - return { - key: key, - label: ProdTemplates[key].title, - onClick: async () => { - setLoading(true); - await GenerateDocument( - { - name: ProdTemplates[key].key, - // variables: { id: contract.id }, - }, - {}, - "p" - ); - setLoading(false); - } - }; - }), - { - label: t("reportcenter.templates.production_by_technician_one"), - children: bodyshop.employees - .filter((e) => e.active) - .map((e) => { - return { - key: e.id, - label: `${e.first_name} ${e.last_name}`, - onClick: async () => { - setLoading(true); - await GenerateDocument( - { - name: production_by_technician_one.key, - variables: {id: e.id}, - }, - {}, - "p" - ); - setLoading(false); - } - }; - }) - }, - { - label: t("reportcenter.templates.production_by_category_one"), - children: bodyshop.md_categories.map((e) => { - return { - key: e, - label: e, - onClick: async () => { - setLoading(true); - await GenerateDocument( - { - name: production_by_category_one.key, - variables: {category: e}, - }, - {}, - "p" - ); - setLoading(false); - } - }; - }) - }, - { - label: t("reportcenter.templates.production_by_repair_status_one"), - children: bodyshop.md_ro_statuses.production_statuses.map((e) => { - return { - key: e, - label: e, - onClick: async () => { - setLoading(true); - await GenerateDocument( - { - name: production_by_repair_status_one.key, - variables: {status: e}, - }, - {}, - "p" - ); - setLoading(false); - } - }; - }) + const menu = { + items: [ + ...Object.keys(ProdTemplates).map((key) => { + return { + key: key, + label: ProdTemplates[key].title, + onClick: async () => { + setLoading(true); + await GenerateDocument( + { + name: ProdTemplates[key].key + // variables: { id: contract.id }, + }, + {}, + "p" + ); + setLoading(false); + } + }; + }), + { + label: t("reportcenter.templates.production_by_technician_one"), + children: bodyshop.employees + .filter((e) => e.active) + .map((e) => { + return { + key: e.id, + label: `${e.first_name} ${e.last_name}`, + onClick: async () => { + setLoading(true); + await GenerateDocument( + { + name: production_by_technician_one.key, + variables: { id: e.id } + }, + {}, + "p" + ); + setLoading(false); + } + }; + }) + }, + { + label: t("reportcenter.templates.production_by_category_one"), + children: bodyshop.md_categories.map((e) => { + return { + key: e, + label: e, + onClick: async () => { + setLoading(true); + await GenerateDocument( + { + name: production_by_category_one.key, + variables: { category: e } + }, + {}, + "p" + ); + setLoading(false); } - ] - }; + }; + }) + }, + { + label: t("reportcenter.templates.production_by_repair_status_one"), + children: bodyshop.md_ro_statuses.production_statuses.map((e) => { + return { + key: e, + label: e, + onClick: async () => { + setLoading(true); + await GenerateDocument( + { + name: production_by_repair_status_one.key, + variables: { status: e } + }, + {}, + "p" + ); + setLoading(false); + } + }; + }) + } + ] + }; - return ( - - - ); + return ( + + + + ); } diff --git a/client/src/components/production-list-table/production-list-table-view-select.component.jsx b/client/src/components/production-list-table/production-list-table-view-select.component.jsx index e9c96ef58..9a98ede10 100644 --- a/client/src/components/production-list-table/production-list-table-view-select.component.jsx +++ b/client/src/components/production-list-table/production-list-table-view-select.component.jsx @@ -1,173 +1,157 @@ -import {DeleteOutlined} from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {Popconfirm, Select} from "antd"; +import { DeleteOutlined } from "@ant-design/icons"; +import { useMutation } from "@apollo/client"; +import { Popconfirm, Select } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {UPDATE_ACTIVE_PROD_LIST_VIEW} from "../../graphql/associations.queries"; -import {UPDATE_SHOP} from "../../graphql/bodyshop.queries"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { UPDATE_ACTIVE_PROD_LIST_VIEW } from "../../graphql/associations.queries"; +import { UPDATE_SHOP } from "../../graphql/bodyshop.queries"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import ProductionListColumns from "../production-list-columns/production-list-columns.data"; -import {useSplitTreatments} from '@splitsoftware/splitio-react'; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - technician: selectTechnician, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + technician: selectTechnician, + currentUser: selectCurrentUser }); -export function ProductionListTable({ - refetch, - bodyshop, - technician, - currentUser, - state, - data, - setColumns, - setState, - }) { - const {t} = useTranslation(); - const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW); - const [updateShop] = useMutation(UPDATE_SHOP); +export function ProductionListTable({ refetch, bodyshop, technician, currentUser, state, data, setColumns, setState }) { + const { t } = useTranslation(); + const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW); + const [updateShop] = useMutation(UPDATE_SHOP); - const {treatments: {Enhanced_Payroll}} = useSplitTreatments({ + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ attributes: {}, names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, -}); + splitKey: bodyshop.imexshopid + }); - const handleSelect = async (value, option) => { - setColumns( - bodyshop.production_config - .filter((pc) => pc.name === value)[0] - .columns.columnKeys.map((k) => { - return { - ...ProductionListColumns({ - bodyshop, - refetch, - technician, - state, - data: data, - activeStatuses: bodyshop.md_ro_statuses.active_statuses, - treatments:{Enhanced_Payroll} - }).find((e) => e.key === k.key), - width: k.width, - }; - }) - ); - setState( - bodyshop.production_config.filter((pc) => pc.name === value)[0].columns - .tableState - ); + const handleSelect = async (value, option) => { + setColumns( + bodyshop.production_config + .filter((pc) => pc.name === value)[0] + .columns.columnKeys.map((k) => { + return { + ...ProductionListColumns({ + bodyshop, + refetch, + technician, + state, + data: data, + activeStatuses: bodyshop.md_ro_statuses.active_statuses, + treatments: { Enhanced_Payroll } + }).find((e) => e.key === k.key), + width: k.width + }; + }) + ); + setState(bodyshop.production_config.filter((pc) => pc.name === value)[0].columns.tableState); - const assoc = bodyshop.associations.find( - (a) => a.useremail === currentUser.email - ); + const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email); - if (assoc) { - await updateDefaultProdView({ - variables: {assocId: assoc.id, view: value}, - update(cache) { - cache.modify({ - id: cache.identify(bodyshop), - fields: { - associations(existingAssociations, {readField}) { - return existingAssociations.map((a) => { - if (a.useremail !== currentUser.email) return a; - return {...a, default_prod_list_view: value}; - }); - }, - }, - }); - }, - }); + if (assoc) { + await updateDefaultProdView({ + variables: { assocId: assoc.id, view: value }, + update(cache) { + cache.modify({ + id: cache.identify(bodyshop), + fields: { + associations(existingAssociations, { readField }) { + return existingAssociations.map((a) => { + if (a.useremail !== currentUser.email) return a; + return { ...a, default_prod_list_view: value }; + }); + } + } + }); } - }; + }); + } + }; - const handleTrash = async (name) => { - await updateShop({ - variables: { - id: bodyshop.id, - shop: { - production_config: bodyshop.production_config.filter( - (b) => b.name !== name - ), - }, - }, - awaitRefetchQueries: true, - }); + const handleTrash = async (name) => { + await updateShop({ + variables: { + id: bodyshop.id, + shop: { + production_config: bodyshop.production_config.filter((b) => b.name !== name) + } + }, + awaitRefetchQueries: true + }); - setColumns( - bodyshop.production_config[0].columns.columnKeys.map((k) => { - return { - ...ProductionListColumns({ - technician, - state, - refetch, - data: data, - activeStatuses: bodyshop.md_ro_statuses.active_statuses, - treatments: {Enhanced_Payroll} - }).find((e) => e.key === k.key), - width: k.width, - }; - }) - ); - - setState(bodyshop.production_config[0].columns.tableState); - }; - const assoc = bodyshop.associations.find( - (a) => a.useremail === currentUser.email + setColumns( + bodyshop.production_config[0].columns.columnKeys.map((k) => { + return { + ...ProductionListColumns({ + technician, + state, + refetch, + data: data, + activeStatuses: bodyshop.md_ro_statuses.active_statuses, + treatments: { Enhanced_Payroll } + }).find((e) => e.key === k.key), + width: k.width + }; + }) ); - const defaultView = assoc && assoc.default_prod_list_view; - return ( -
    - + {bodyshop.production_config.map((config) => ( + +
    - {bodyshop.production_config.map((config) => ( - -
    {config.name} - handleTrash(config.name)} - > - { - e.stopPropagation(); - }} - /> - -
    -
    - ))} - -
    - ); + handleTrash(config.name)} + > + { + e.stopPropagation(); + }} + /> + +
    + + ))} + +
    + ); } export default connect(mapStateToProps, null)(ProductionListTable); diff --git a/client/src/components/production-list-table/production-list-table.component.jsx b/client/src/components/production-list-table/production-list-table.component.jsx index 7c2e24038..12938829d 100644 --- a/client/src/components/production-list-table/production-list-table.component.jsx +++ b/client/src/components/production-list-table/production-list-table.component.jsx @@ -1,349 +1,286 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; -import {Button, Dropdown, Input, Space, Statistic, Table} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; -import React, {useEffect, useMemo, useState} from "react"; +import { SyncOutlined } from "@ant-design/icons"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { Button, Dropdown, Input, Space, Statistic, Table } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; +import React, { useEffect, useMemo, useState } from "react"; import ReactDragListView from "react-drag-listview"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component"; import ProductionListColumns from "../production-list-columns/production-list-columns.data"; import ProductionListDetail from "../production-list-detail/production-list-detail.component"; -import ProductionListSaveConfigButton - from "../production-list-save-config-button/production-list-save-config-button.component"; +import ProductionListSaveConfigButton from "../production-list-save-config-button/production-list-save-config-button.component"; import ProductionListPrint from "./production-list-print.component"; import ProductionListTableViewSelect from "./production-list-table-view-select.component"; import ResizeableTitle from "./production-list-table.resizeable.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - technician: selectTechnician, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + technician: selectTechnician, + currentUser: selectCurrentUser }); -export function ProductionListTable({loading, data, refetch, bodyshop, technician, currentUser}) { +export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser }) { + const [searchText, setSearchText] = useState(""); - const [searchText, setSearchText] = useState(""); + const { + treatments: { Production_List_Status_Colors, Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Production_List_Status_Colors", "Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); - const {treatments: {Production_List_Status_Colors, Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["Production_List_Status_Colors","Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, + const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email); + + const defaultView = assoc && assoc.default_prod_list_view; + + const [state, setState] = useState( + (bodyshop.production_config && + bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) || + bodyshop.production_config[0]?.columns.tableState || { + sortedInfo: {}, + filteredInfo: { text: "" } + } + ); + + const { t } = useTranslation(); + + const matchingColumnConfig = useMemo(() => { + return bodyshop.production_config.find((p) => p.name === defaultView); + }, [bodyshop.production_config, defaultView]); + + const [columns, setColumns] = useState( + (state && + matchingColumnConfig && + matchingColumnConfig.columns.columnKeys.map((k) => { + return { + ...ProductionListColumns({ + bodyshop, + refetch, + technician, + state, + data, + activeStatuses: bodyshop.md_ro_statuses.active_statuses, + treatments: { Production_List_Status_Colors, Enhanced_Payroll } + }).find((e) => e.key === k.key), + width: k.width ?? 100 + }; + })) || + [] + ); + + useEffect(() => { + const newColumns = + (state && + matchingColumnConfig && + matchingColumnConfig.columns.columnKeys.map((k) => { + return { + ...ProductionListColumns({ + bodyshop, + technician, + refetch, + state, + data: data, + activeStatuses: bodyshop.md_ro_statuses.active_statuses, + treatments: { Production_List_Status_Colors, Enhanced_Payroll } + }).find((e) => e.key === k.key), + width: k.width ?? 100 + }; + })) || + []; + setColumns(newColumns); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + //state, + matchingColumnConfig, + bodyshop, + technician, + data + ]); //State removed from dependency array as it causes race condition when removing columns from table view and is not needed. + + const handleTableChange = (pagination, filters, sorter) => { + setState({ + ...state, + filteredInfo: filters, + sortedInfo: { columnKey: sorter.columnKey, order: sorter.order } }); + }; - const assoc = bodyshop.associations.find( - (a) => a.useremail === currentUser.email - ); + const onDragEnd = (fromIndex, toIndex) => { + const columnsCopy = columns.slice(); + const item = columnsCopy.splice(fromIndex, 1)[0]; + columnsCopy.splice(toIndex, 0, item); + setColumns(columnsCopy); + }; - const defaultView = assoc && assoc.default_prod_list_view; + const removeColumn = (e) => { + const { key } = e; + const newColumns = columns.filter((i) => i.key !== key); + setColumns(newColumns); + }; - const [state, setState] = useState( - (bodyshop.production_config && - bodyshop.production_config.find((p) => p.name === defaultView)?.columns - .tableState) || - bodyshop.production_config[0]?.columns.tableState || { - sortedInfo: {}, - filteredInfo: {text: ""}, + const handleResize = + (index) => + (e, { size }) => { + const nextColumns = [...columns]; + nextColumns[index] = { + ...nextColumns[index], + width: size.width + }; + setColumns(nextColumns); + }; + + const headerItem = (col) => { + const menu = { + onClick: removeColumn, + items: [ + { + key: col.key, + label: t("production.actions.removecolumn") } - ); - - const {t} = useTranslation(); - - const matchingColumnConfig = useMemo(() => { - return bodyshop.production_config.find((p) => p.name === defaultView); - }, [bodyshop.production_config, defaultView]); - - const [columns, setColumns] = useState( - (state && - matchingColumnConfig && - matchingColumnConfig.columns.columnKeys.map((k) => { - return { - ...ProductionListColumns({ - bodyshop, - refetch, - technician, - state, - data, - activeStatuses: bodyshop.md_ro_statuses.active_statuses, - treatments: {Production_List_Status_Colors, Enhanced_Payroll} - }).find((e) => e.key === k.key), - width: k.width ?? 100, - }; - })) || - [] - ); - - useEffect(() => { - const newColumns = - (state && - matchingColumnConfig && - matchingColumnConfig.columns.columnKeys.map((k) => { - return { - ...ProductionListColumns({ - bodyshop, - technician, - refetch, - state, - data: data, - activeStatuses: bodyshop.md_ro_statuses.active_statuses, - treatments: {Production_List_Status_Colors, Enhanced_Payroll} - }).find((e) => e.key === k.key), - width: k.width ?? 100, - }; - })) || - []; - setColumns(newColumns); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - //state, - matchingColumnConfig, - bodyshop, - technician, - data, - ]); //State removed from dependency array as it causes race condition when removing columns from table view and is not needed. - - - const handleTableChange = (pagination, filters, sorter) => { - setState({ - ...state, - filteredInfo: filters, - sortedInfo: {columnKey: sorter.columnKey, order: sorter.order}, - }); + ] }; - const onDragEnd = (fromIndex, toIndex) => { - const columnsCopy = columns.slice(); - const item = columnsCopy.splice(fromIndex, 1)[0]; - columnsCopy.splice(toIndex, 0, item); - setColumns(columnsCopy); - }; - - const removeColumn = (e) => { - const {key} = e; - const newColumns = columns.filter((i) => i.key !== key); - setColumns(newColumns); - }; - - const handleResize = - (index) => - (e, {size}) => { - const nextColumns = [...columns]; - nextColumns[index] = { - ...nextColumns[index], - width: size.width, - }; - setColumns(nextColumns); - }; - - - const headerItem = (col) => { - const menu = { - onClick: removeColumn, - items: [ - { - key: col.key, - label: t("production.actions.removecolumn"), - }, - ] - } - - return ( - - {col.title} - - ) - }; - - const dataSource = - searchText === "" - ? data - : data.filter( - (j) => - (j.ro_number || "") - .toString() - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_co_nm || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_fn || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_ln || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.status || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.ins_co_nm || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.v_model_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_make_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) - ); - - // const handleSelectRecord = (record) => { - // if (selected !== record.id) { - // setSelected(record.id); - // } else { - // setSelected(null); - // } - // }; - - if (!!!columns) return
    No columns found.
    ; - - const totalHrs = data - .reduce( - (acc, val) => - acc + - (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) + - (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), - 0 - ) - .toFixed(1); - const totalLAB = data - .reduce( - (acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), - 0 - ) - .toFixed(1); - const totalLAR = data - .reduce( - (acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), - 0 - ) - .toFixed(1); return ( -
    - - - - - - - } - extra={ - - - - - - - - setSearchText(e.target.value)} - placeholder={t("general.labels.search")} - value={searchText} - /> - - - } - /> - - -
    { - if (!bodyshop.md_ro_statuses.production_colors) return null; - - const color = bodyshop.md_ro_statuses.production_colors.find( - (x) => x.status === record.status - ); - - if (!color) { - if (index % 2 === 0) - return { - style: { - backgroundColor: `rgb(236, 236, 236)`, - }, - }; - - return null; - } - - return { - className: "rowWithColor", - style: { - "--bgColor": `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`, - }, - }; - }, - })} - components={{ - header: { - cell: ResizeableTitle, - }, - }} - columns={columns.map((c, index) => { - return { - ...c, - filteredValue: state.filteredInfo[c.key] || null, - sortOrder: - state.sortedInfo.columnKey === c.key && state.sortedInfo.order, - title: headerItem(c), - ellipsis: true, - width: c.width ?? 100, - onHeaderCell: (column) => ({ - width: column.width, - onResize: handleResize(index), - }), - }; - })} - rowKey="id" - loading={loading} - dataSource={dataSource} - scroll={{x: 1000}} - onChange={handleTableChange} - /> - - + + {col.title} + ); + }; + + const dataSource = + searchText === "" + ? data + : data.filter( + (j) => + (j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.status || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.ins_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase()) + ); + + // const handleSelectRecord = (record) => { + // if (selected !== record.id) { + // setSelected(record.id); + // } else { + // setSelected(null); + // } + // }; + + if (!!!columns) return
    No columns found.
    ; + + const totalHrs = data + .reduce( + (acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), + 0 + ) + .toFixed(1); + const totalLAB = data.reduce((acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1); + const totalLAR = data.reduce((acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1); + return ( +
    + + + + + + + } + extra={ + + + + + + + + setSearchText(e.target.value)} + placeholder={t("general.labels.search")} + value={searchText} + /> + + + } + /> + + +
    { + if (!bodyshop.md_ro_statuses.production_colors) return null; + + const color = bodyshop.md_ro_statuses.production_colors.find((x) => x.status === record.status); + + if (!color) { + if (index % 2 === 0) + return { + style: { + backgroundColor: `rgb(236, 236, 236)` + } + }; + + return null; + } + + return { + className: "rowWithColor", + style: { + "--bgColor": `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})` + } + }; + } + })} + components={{ + header: { + cell: ResizeableTitle + } + }} + columns={columns.map((c, index) => { + return { + ...c, + filteredValue: state.filteredInfo[c.key] || null, + sortOrder: state.sortedInfo.columnKey === c.key && state.sortedInfo.order, + title: headerItem(c), + ellipsis: true, + width: c.width ?? 100, + onHeaderCell: (column) => ({ + width: column.width, + onResize: handleResize(index) + }) + }; + })} + rowKey="id" + loading={loading} + dataSource={dataSource} + scroll={{ x: 1000 }} + onChange={handleTableChange} + /> + + + ); } export default connect(mapStateToProps, null)(ProductionListTable); diff --git a/client/src/components/production-list-table/production-list-table.container.jsx b/client/src/components/production-list-table/production-list-table.container.jsx index af856c1b8..29ddf015a 100644 --- a/client/src/components/production-list-table/production-list-table.container.jsx +++ b/client/src/components/production-list-table/production-list-table.container.jsx @@ -1,74 +1,66 @@ -import {useApolloClient, useQuery, useSubscription} from "@apollo/client"; -import React, {useEffect, useState} from "react"; +import { useApolloClient, useQuery, useSubscription } from "@apollo/client"; +import React, { useEffect, useState } from "react"; import { - QUERY_EXACT_JOB_IN_PRODUCTION, - QUERY_EXACT_JOBS_IN_PRODUCTION, - QUERY_JOBS_IN_PRODUCTION, - SUBSCRIPTION_JOBS_IN_PRODUCTION, + QUERY_EXACT_JOB_IN_PRODUCTION, + QUERY_EXACT_JOBS_IN_PRODUCTION, + QUERY_JOBS_IN_PRODUCTION, + SUBSCRIPTION_JOBS_IN_PRODUCTION } from "../../graphql/jobs.queries"; import ProductionListTable from "./production-list-table.component"; import _ from "lodash"; export default function ProductionListTableContainer() { - const {refetch, loading, data} = useQuery(QUERY_JOBS_IN_PRODUCTION, { - pollInterval: 3600000, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", + const { refetch, loading, data } = useQuery(QUERY_JOBS_IN_PRODUCTION, { + pollInterval: 3600000, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const client = useApolloClient(); + const [joblist, setJoblist] = useState([]); + const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION); + + useEffect(() => { + if (!(data && data.jobs)) return; + setJoblist( + data.jobs.map((j) => { + return { id: j.id, updated_at: j.updated_at }; + }) + ); + }, [data]); + + useEffect(() => { + if (!updatedJobs || joblist.length === 0) return; + + const jobDiff = _.differenceWith( + joblist, + updatedJobs.jobs, + (a, b) => a.id === b.id && a.updated_at === b.updated_at + ); + + if (jobDiff.length > 1) { + getUpdatedJobsData(jobDiff.map((j) => j.id)); + } else if (jobDiff.length === 1) { + jobDiff.forEach((job) => { + getUpdatedJobData(job.id); + }); + } + + setJoblist(updatedJobs.jobs); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [updatedJobs]); + + const getUpdatedJobData = async (jobId) => { + client.query({ + query: QUERY_EXACT_JOB_IN_PRODUCTION, + variables: { id: jobId } }); - const client = useApolloClient(); - const [joblist, setJoblist] = useState([]); - const {data: updatedJobs} = useSubscription( - SUBSCRIPTION_JOBS_IN_PRODUCTION - ); + }; + const getUpdatedJobsData = async (jobIds) => { + client.query({ + query: QUERY_EXACT_JOBS_IN_PRODUCTION, + variables: { ids: jobIds } + }); + }; - useEffect(() => { - if (!(data && data.jobs)) return; - setJoblist( - data.jobs.map((j) => { - return {id: j.id, updated_at: j.updated_at}; - }) - ); - }, [data]); - - useEffect(() => { - if (!updatedJobs || joblist.length === 0) return; - - const jobDiff = _.differenceWith( - joblist, - updatedJobs.jobs, - (a, b) => a.id === b.id && a.updated_at === b.updated_at - ); - - if (jobDiff.length > 1) { - getUpdatedJobsData(jobDiff.map((j) => j.id)); - } else if (jobDiff.length === 1) { - jobDiff.forEach((job) => { - getUpdatedJobData(job.id); - }); - } - - setJoblist(updatedJobs.jobs); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [updatedJobs]); - - const getUpdatedJobData = async (jobId) => { - client.query({ - query: QUERY_EXACT_JOB_IN_PRODUCTION, - variables: {id: jobId}, - }); - }; - const getUpdatedJobsData = async (jobIds) => { - client.query({ - query: QUERY_EXACT_JOBS_IN_PRODUCTION, - variables: {ids: jobIds}, - }); - }; - - return ( - - ); + return ; } diff --git a/client/src/components/production-list-table/production-list-table.resizeable.component.jsx b/client/src/components/production-list-table/production-list-table.resizeable.component.jsx index f4bc60a84..618e9e8cd 100644 --- a/client/src/components/production-list-table/production-list-table.resizeable.component.jsx +++ b/client/src/components/production-list-table/production-list-table.resizeable.component.jsx @@ -1,29 +1,29 @@ import React from "react"; -import {Resizable} from "react-resizable"; +import { Resizable } from "react-resizable"; export default function ResizableComponent(props) { - const {onResize, width, ...restProps} = props; + const { onResize, width, ...restProps } = props; - if (!width) { - return - - - {t("user.actions.updateprofile")} - - } - > - - - - - - - - - - - - - - - {t("user.actions.changepassword")} - - } - > - - - } - type="password" - placeholder={t("general.labels.password")} - /> - - ({ - validator(rule, value) { - if (!value || getFieldValue("password") === value) { - return Promise.resolve(); - } - return Promise.reject( - t("general.labels.passwordsdonotmatch") - ); - }, - }), - ]} - > - } - type="password" - placeholder={t("general.labels.password")} - /> - - - - - - - ); + return ( + <> + + + + {t("user.actions.updateprofile")} + + } + > + + + + + + + + + + + + + + + {t("user.actions.changepassword")} + + } + > + + + } type="password" placeholder={t("general.labels.password")} /> + + ({ + validator(rule, value) { + if (!value || getFieldValue("password") === value) { + return Promise.resolve(); + } + return Promise.reject(t("general.labels.passwordsdonotmatch")); + } + }) + ]} + > + } type="password" placeholder={t("general.labels.password")} /> + + + + + + + ); }); diff --git a/client/src/components/profile-shops/profile-shops.component.jsx b/client/src/components/profile-shops/profile-shops.component.jsx index 3afc52b8c..a5b246638 100644 --- a/client/src/components/profile-shops/profile-shops.component.jsx +++ b/client/src/components/profile-shops/profile-shops.component.jsx @@ -1,78 +1,54 @@ -import {Button, Card, Col, Input, Table, Typography} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; +import { Button, Card, Col, Input, Table, Typography } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; -export default function ProfileShopsComponent({ - loading, - data, - updateActiveShop, - }) { - const {t} = useTranslation(); - const [search, setSearch] = useState(""); - const columns = [ - { - title: t("associations.fields.shopname"), - dataIndex: "shopname", - key: "shopname", - width: "25%", - render: (text, record) => {record.bodyshop.shopname}, - }, - { - title: t("associations.fields.active"), - dataIndex: "active", - key: "active", - width: "25%", - render: (text, record) => {record.active ? "Yes" : "No"}, - }, - { - title: t("associations.labels.actions"), - dataIndex: "actions", - key: "actions", - width: "25%", - render: (text, record) => ( - - {record.active ? null : ( - - )} - - ), - }, - ]; +export default function ProfileShopsComponent({ loading, data, updateActiveShop }) { + const { t } = useTranslation(); + const [search, setSearch] = useState(""); + const columns = [ + { + title: t("associations.fields.shopname"), + dataIndex: "shopname", + key: "shopname", + width: "25%", + render: (text, record) => {record.bodyshop.shopname} + }, + { + title: t("associations.fields.active"), + dataIndex: "active", + key: "active", + width: "25%", + render: (text, record) => {record.active ? "Yes" : "No"} + }, + { + title: t("associations.labels.actions"), + dataIndex: "actions", + key: "actions", + width: "25%", + render: (text, record) => ( + {record.active ? null : } + ) + } + ]; - const filteredData = - search === "" - ? data - : data.filter((d) => - d.bodyshop.shopname.toLowerCase().includes(search.toLowerCase()) - ); + const filteredData = + search === "" ? data : data.filter((d) => d.bodyshop.shopname.toLowerCase().includes(search.toLowerCase())); - return ( - - - {t("profile.labels.activeshop")} - - } - extra={ - setSearch(e.target.value)} - allowClear - placeholder={t("general.labels.search")} - /> - } - > -
    ; - } + if (!width) { + return ; + } - return ( - { - e.stopPropagation(); - }} - /> - } - > - - - ); + return ( + { + e.stopPropagation(); + }} + /> + } + > + + + ); } diff --git a/client/src/components/production-remove-button/production-remove-button.component.jsx b/client/src/components/production-remove-button/production-remove-button.component.jsx index 15d7ad87f..c440485b6 100644 --- a/client/src/components/production-remove-button/production-remove-button.component.jsx +++ b/client/src/components/production-remove-button/production-remove-button.component.jsx @@ -1,44 +1,44 @@ -import {useMutation} from "@apollo/client"; -import {Button, notification} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; +import { useMutation } from "@apollo/client"; +import { Button, notification } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; import queryString from "query-string"; -import {useLocation, useNavigate} from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; -export default function ProductionRemoveButton({jobId}) { - const [removeJobFromProduction] = useMutation(UPDATE_JOB); - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const search = queryString.parse(useLocation().search); - const history = useNavigate(); +export default function ProductionRemoveButton({ jobId }) { + const [removeJobFromProduction] = useMutation(UPDATE_JOB); + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const search = queryString.parse(useLocation().search); + const history = useNavigate(); - const handleRemoveFromProd = async () => { - logImEXEvent("production_remove_job"); - setLoading(true); - const result = await removeJobFromProduction({ - variables: {jobId: jobId, job: {inproduction: false}}, - }); + const handleRemoveFromProd = async () => { + logImEXEvent("production_remove_job"); + setLoading(true); + const result = await removeJobFromProduction({ + variables: { jobId: jobId, job: { inproduction: false } } + }); - if (!!!result.errors) { - notification["success"]({message: t("production.successes.removed")}); - delete search.selected; - history({search: queryString.stringify(search)}); - } else { - notification["error"]({ - message: t("production.errors.removing", { - error: JSON.stringify(result.errors), - }), - }); - } + if (!!!result.errors) { + notification["success"]({ message: t("production.successes.removed") }); + delete search.selected; + history({ search: queryString.stringify(search) }); + } else { + notification["error"]({ + message: t("production.errors.removing", { + error: JSON.stringify(result.errors) + }) + }); + } - setLoading(false); - }; + setLoading(false); + }; - return ( - - ); + return ( + + ); } diff --git a/client/src/components/production-sublets-manage/production-sublets-manage.component.jsx b/client/src/components/production-sublets-manage/production-sublets-manage.component.jsx index ae44f2244..a7a9ddad9 100644 --- a/client/src/components/production-sublets-manage/production-sublets-manage.component.jsx +++ b/client/src/components/production-sublets-manage/production-sublets-manage.component.jsx @@ -1,107 +1,100 @@ -import {CheckCircleFilled, EyeInvisibleFilled} from "@ant-design/icons"; -import {Button, List, notification, Popover} from "antd"; -import React, {useMemo, useState} from "react"; -import {useMutation} from "@apollo/client"; -import {useTranslation} from "react-i18next"; -import {UPDATE_JOB_LINE_SUBLET} from "../../graphql/jobs-lines.queries"; +import { CheckCircleFilled, EyeInvisibleFilled } from "@ant-design/icons"; +import { Button, List, notification, Popover } from "antd"; +import React, { useMemo, useState } from "react"; +import { useMutation } from "@apollo/client"; +import { useTranslation } from "react-i18next"; +import { UPDATE_JOB_LINE_SUBLET } from "../../graphql/jobs-lines.queries"; -export default function ProductionSubletsManageComponent({subletJobLines}) { - const {t} = useTranslation(); - const [updateJobLine] = useMutation(UPDATE_JOB_LINE_SUBLET); - const [loading, setLoading] = useState(false); - const subletCount = useMemo(() => { - return { - total: subletJobLines.filter((s) => !s.sublet_ignored).length, - outstanding: subletJobLines.filter( - (s) => !s.sublet_ignored && !s.sublet_completed - ).length, - }; - }, [subletJobLines]); - - const handleSubletMark = async (sublet, action) => { - setLoading(true); - - const result = await updateJobLine({ - variables: { - jobId: sublet.jobid, - now: new Date(), - lineId: sublet.id, - line: { - sublet_completed: - action === "complete" ? !sublet.sublet_completed : false, - sublet_ignored: action === "ignore" ? !sublet.sublet_ignored : false, - }, - }, - }); - - if (!!result.errors) { - notification["error"]({ - message: t("joblines.errors.updating", { - message: JSON.stringify(result.errors), - }), - }); - } else { - notification["success"]({ - message: t("joblines.successes.updated"), - }); - } - setLoading(false); +export default function ProductionSubletsManageComponent({ subletJobLines }) { + const { t } = useTranslation(); + const [updateJobLine] = useMutation(UPDATE_JOB_LINE_SUBLET); + const [loading, setLoading] = useState(false); + const subletCount = useMemo(() => { + return { + total: subletJobLines.filter((s) => !s.sublet_ignored).length, + outstanding: subletJobLines.filter((s) => !s.sublet_ignored && !s.sublet_completed).length }; + }, [subletJobLines]); - const popContent = ( -
    - e.stopPropagation()} - dataSource={subletJobLines} - renderItem={(s) => ( - { - e.stopPropagation(); - handleSubletMark(s, "complete"); - }} - type={s.sublet_completed ? "primary" : "ghost"} - > - - , - , - ]} - > - - - )} - /> -
    - ); + const handleSubletMark = async (sublet, action) => { + setLoading(true); - return ( - - 0 ? "tomato" : ""}}>{`${ - subletCount.total - subletCount.outstanding + const result = await updateJobLine({ + variables: { + jobId: sublet.jobid, + now: new Date(), + lineId: sublet.id, + line: { + sublet_completed: action === "complete" ? !sublet.sublet_completed : false, + sublet_ignored: action === "ignore" ? !sublet.sublet_ignored : false + } + } + }); + + if (!!result.errors) { + notification["error"]({ + message: t("joblines.errors.updating", { + message: JSON.stringify(result.errors) + }) + }); + } else { + notification["success"]({ + message: t("joblines.successes.updated") + }); + } + setLoading(false); + }; + + const popContent = ( +
    + e.stopPropagation()} + dataSource={subletJobLines} + renderItem={(s) => ( + { + e.stopPropagation(); + handleSubletMark(s, "complete"); + }} + type={s.sublet_completed ? "primary" : "ghost"} + > + + , + + ]} + > + + + )} + /> +
    + ); + + return ( + + 0 ? "tomato" : "" }}>{`${ + subletCount.total - subletCount.outstanding } / ${subletCount.total}`} - - ); +
    + ); } diff --git a/client/src/components/profile-my/profile-my.component.jsx b/client/src/components/profile-my/profile-my.component.jsx index 6ec7d4038..16af7809a 100644 --- a/client/src/components/profile-my/profile-my.component.jsx +++ b/client/src/components/profile-my/profile-my.component.jsx @@ -1,143 +1,120 @@ -import {Button, Card, Col, Form, Input, notification} from "antd"; -import {LockOutlined} from "@ant-design/icons"; +import { Button, Card, Col, Form, Input, notification } from "antd"; +import { LockOutlined } from "@ant-design/icons"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {updateUserDetails} from "../../redux/user/user.actions"; -import {selectCurrentUser} from "../../redux/user/user.selectors"; -import {logImEXEvent, updateCurrentPassword,} from "../../firebase/firebase.utils"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { updateUserDetails } from "../../redux/user/user.actions"; +import { selectCurrentUser } from "../../redux/user/user.selectors"; +import { logImEXEvent, updateCurrentPassword } from "../../firebase/firebase.utils"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - updateUserDetails: (userDetails) => dispatch(updateUserDetails(userDetails)), + updateUserDetails: (userDetails) => dispatch(updateUserDetails(userDetails)) }); export default connect( - mapStateToProps, - mapDispatchToProps -)(function ProfileMyComponent({currentUser, updateUserDetails}) { - const {t} = useTranslation(); + mapStateToProps, + mapDispatchToProps +)(function ProfileMyComponent({ currentUser, updateUserDetails }) { + const { t } = useTranslation(); - const handleFinish = (values) => { - logImEXEvent("profile_update"); + const handleFinish = (values) => { + logImEXEvent("profile_update"); - updateUserDetails({ - displayName: values.displayName, - photoURL: values.photoURL, - }); - }; + updateUserDetails({ + displayName: values.displayName, + photoURL: values.photoURL + }); + }; - const handleChangePassword = async ({password}) => { - logImEXEvent("password_update"); - try { - await updateCurrentPassword(password); - notification.success({ - message: t("user.successess.passwordchanged"), - }); - } catch (error) { - notification.error({ - message: error.message, - }); - } - }; + const handleChangePassword = async ({ password }) => { + logImEXEvent("password_update"); + try { + await updateCurrentPassword(password); + notification.success({ + message: t("user.successess.passwordchanged") + }); + } catch (error) { + notification.error({ + message: error.message + }); + } + }; - return ( - <> -
    - - - ); + return ( + + {t("profile.labels.activeshop")}} + extra={ + setSearch(e.target.value)} + allowClear + placeholder={t("general.labels.search")} + /> + } + > +
    + + + ); } diff --git a/client/src/components/profile-shops/profile-shops.container.jsx b/client/src/components/profile-shops/profile-shops.container.jsx index 7e5556fb0..4c5150913 100644 --- a/client/src/components/profile-shops/profile-shops.container.jsx +++ b/client/src/components/profile-shops/profile-shops.container.jsx @@ -1,71 +1,68 @@ -import {useMutation, useQuery} from "@apollo/client"; +import { useMutation, useQuery } from "@apollo/client"; import React from "react"; -import {logImEXEvent, messaging} from "../../firebase/firebase.utils"; -import {QUERY_ALL_ASSOCIATIONS, UPDATE_ACTIVE_ASSOCIATION,} from "../../graphql/associations.queries"; +import { logImEXEvent, messaging } from "../../firebase/firebase.utils"; +import { QUERY_ALL_ASSOCIATIONS, UPDATE_ACTIVE_ASSOCIATION } from "../../graphql/associations.queries"; 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"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProfileShopsContainer); +export default connect(mapStateToProps, mapDispatchToProps)(ProfileShopsContainer); -export function ProfileShopsContainer({bodyshop, currentUser}) { - const {loading, error, data} = useQuery(QUERY_ALL_ASSOCIATIONS, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - variables: { - email: currentUser.email, - }, - skip: !currentUser, +export function ProfileShopsContainer({ bodyshop, currentUser }) { + const { loading, error, data } = useQuery(QUERY_ALL_ASSOCIATIONS, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + email: currentUser.email + }, + skip: !currentUser + }); + const [updateActiveAssociation] = useMutation(UPDATE_ACTIVE_ASSOCIATION); + + const updateActiveShop = async (newActiveAssocId) => { + logImEXEvent("profile_change_active_shop"); + + try { + const fcm_tokens = await getToken(messaging); + + await axios.post("/notifications/unsubscribe", { + fcm_tokens, + imexshopid: bodyshop.imexshopid, + type: "messaging" + }); + } catch (error) { + console.log("No FCM token. Skipping unsubscribe."); + } + + await updateActiveAssociation({ + variables: { + newActiveAssocId: newActiveAssocId + } }); - const [updateActiveAssociation] = useMutation(UPDATE_ACTIVE_ASSOCIATION); - const updateActiveShop = async (newActiveAssocId) => { - logImEXEvent("profile_change_active_shop"); + //Force window refresh. - try { - const fcm_tokens = await getToken(messaging); + window.location.reload(); + }; - await axios.post("/notifications/unsubscribe", { - fcm_tokens, - imexshopid: bodyshop.imexshopid, - type: "messaging", - }); - } catch (error) { - console.log("No FCM token. Skipping unsubscribe."); - } - - await updateActiveAssociation({ - variables: { - newActiveAssocId: newActiveAssocId, - }, - }); - - //Force window refresh. - - window.location.reload(); - }; - - if (error) return ; - return ( - - ); + if (error) return ; + return ( + + ); } diff --git a/client/src/components/qbo-authorize/qbo-authorize.component.jsx b/client/src/components/qbo-authorize/qbo-authorize.component.jsx index 3c333f436..add5ddb38 100644 --- a/client/src/components/qbo-authorize/qbo-authorize.component.jsx +++ b/client/src/components/qbo-authorize/qbo-authorize.component.jsx @@ -1,59 +1,54 @@ -import {Space} from "antd"; +import { Space } from "antd"; import Axios from "axios"; import queryString from "query-string"; -import React, {useEffect} from "react"; -import {useCookies} from "react-cookie"; -import {useLocation, useNavigate} from "react-router-dom"; +import React, { useEffect } from "react"; +import { useCookies } from "react-cookie"; +import { useLocation, useNavigate } from "react-router-dom"; import QboSignIn from "../../assets/qbo/C2QB_green_btn_med_default.svg"; export default function QboAuthorizeComponent() { - const location = useLocation(); - const history = useNavigate(); - const [setCookie] = useCookies(["access_token", "refresh_token"]); + const location = useLocation(); + const history = useNavigate(); + const [setCookie] = useCookies(["access_token", "refresh_token"]); - const handleQbSignIn = async () => { - const result = await Axios.post("/qbo/authorize"); - window.location.href = result.data; - }; - const qs = queryString.parse(location.search); + const handleQbSignIn = async () => { + const result = await Axios.post("/qbo/authorize"); + window.location.href = result.data; + }; + const qs = queryString.parse(location.search); - const {error} = qs; + const { error } = qs; - useEffect(() => { - const {code, state, realmId} = qs; - const hasBeenCalledBack = code && realmId && state; + useEffect(() => { + const { code, state, realmId } = qs; + const hasBeenCalledBack = code && realmId && state; - if (hasBeenCalledBack) { - // setCookie("qbo_code", code, { path: "/" }); - // setCookie("qbo_state", state, { path: "/" }); + if (hasBeenCalledBack) { + // setCookie("qbo_code", code, { path: "/" }); + // setCookie("qbo_state", state, { path: "/" }); - // let expires = new Date(); - // expires.setTime(expires.getTime() + 8726400 * 1000); + // let expires = new Date(); + // expires.setTime(expires.getTime() + 8726400 * 1000); - // setCookie("qbo_realmId", realmId, { - // path: "/", - // expires, + // setCookie("qbo_realmId", realmId, { + // path: "/", + // expires, - // ...(process.env.NODE_ENV !== "development" - // ? { domain: `.${window.location.host}` } - // : {}), - // }); + // ...(process.env.NODE_ENV !== "development" + // ? { domain: `.${window.location.host}` } + // : {}), + // }); - history({pathname: `/manage/accounting/receivables`}); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [qs, location, setCookie]); + history({ pathname: `/manage/accounting/receivables` }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [qs, location, setCookie]); - return ( - - Sign In to QuickBooks Online + return ( + + Sign In to QuickBooks Online - {error && JSON.parse(decodeURIComponent(error)).error_description} - - ); + {error && JSON.parse(decodeURIComponent(error)).error_description} + + ); } diff --git a/client/src/components/rbac-wrapper/rbac-defaults.js b/client/src/components/rbac-wrapper/rbac-defaults.js index 2d193c484..7e53cd6df 100644 --- a/client/src/components/rbac-wrapper/rbac-defaults.js +++ b/client/src/components/rbac-wrapper/rbac-defaults.js @@ -1,76 +1,76 @@ const ret = { - "accounting:payables": 1, - "accounting:payments": 1, - "accounting:receivables": 1, - "accounting:exportlogs": 3, + "accounting:payables": 1, + "accounting:payments": 1, + "accounting:receivables": 1, + "accounting:exportlogs": 3, - "csi:page": 6, - "csi:export": 5, + "csi:page": 6, + "csi:export": 5, - "contracts:create": 2, - "contracts:detail": 2, - "contracts:list": 2, + "contracts:create": 2, + "contracts:detail": 2, + "contracts:list": 2, - "courtesycar:create": 2, - "courtesycar:detail": 2, - "courtesycar:list": 2, + "courtesycar:create": 2, + "courtesycar:detail": 2, + "courtesycar:list": 2, - "jobs:admin": 5, - "jobs:list-active": 1, - "jobs:list-all": 2, - "jobs:available-list": 2, - "jobs:create": 1, - "jobs:intake": 1, - "jobs:close": 5, - "jobs:detail": 1, - "jobs:partsqueue": 4, - "jobs:checklist-view": 2, - "jobs:list-ready": 1, - "jobs:void": 5, + "jobs:admin": 5, + "jobs:list-active": 1, + "jobs:list-all": 2, + "jobs:available-list": 2, + "jobs:create": 1, + "jobs:intake": 1, + "jobs:close": 5, + "jobs:detail": 1, + "jobs:partsqueue": 4, + "jobs:checklist-view": 2, + "jobs:list-ready": 1, + "jobs:void": 5, - "bills:enter": 2, - "bills:view": 2, - "bills:list": 2, - "bills:delete": 3, - "bills:reexport": 3, + "bills:enter": 2, + "bills:view": 2, + "bills:list": 2, + "bills:delete": 3, + "bills:reexport": 3, - "employees:page": 5, + "employees:page": 5, - "owners:list": 2, - "owners:detail": 3, + "owners:list": 2, + "owners:detail": 3, - "payments:enter": 3, - "payments:list": 3, + "payments:enter": 3, + "payments:list": 3, - "phonebook:view": 1, - "phonebook:edit": 2, + "phonebook:view": 1, + "phonebook:edit": 2, - "production:board": 1, - "production:list": 1, + "production:board": 1, + "production:list": 1, - "schedule:view": 2, + "schedule:view": 2, - "scoreboard:view": 3, + "scoreboard:view": 3, - "shiftclock:view": 2, + "shiftclock:view": 2, - "shop:config": 4, - "shop:dashboard": 3, - "shop:rbac": 5, - "shop:reportcenter": 2, - "shop:templates": 4, - "shop:vendors": 2, + "shop:config": 4, + "shop:dashboard": 3, + "shop:rbac": 5, + "shop:reportcenter": 2, + "shop:templates": 4, + "shop:vendors": 2, - "temporarydocs:view": 2, + "temporarydocs:view": 2, - "timetickets:enter": 3, - "timetickets:list": 3, - "timetickets:edit": 4, - "timetickets:shiftedit": 5, + "timetickets:enter": 3, + "timetickets:list": 3, + "timetickets:edit": 4, + "timetickets:shiftedit": 5, - "users:editaccess": 4, + "users:editaccess": 4, - "inventory:list": 1, - "inventory:delete": 2, + "inventory:list": 1, + "inventory:delete": 2 }; export default ret; diff --git a/client/src/components/rbac-wrapper/rbac-wrapper.component.jsx b/client/src/components/rbac-wrapper/rbac-wrapper.component.jsx index 768093aa4..e97d5c998 100644 --- a/client/src/components/rbac-wrapper/rbac-wrapper.component.jsx +++ b/client/src/components/rbac-wrapper/rbac-wrapper.component.jsx @@ -1,63 +1,36 @@ import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectAuthLevel, selectBodyshop,} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import rbacDefaults from "./rbac-defaults"; const mapStateToProps = createStructuredSelector({ - authLevel: selectAuthLevel, - bodyshop: selectBodyshop, + authLevel: selectAuthLevel, + bodyshop: selectBodyshop }); -function RbacWrapper({ - authLevel, - bodyshop, - requiredAuthLevel, - noauth, - children, - action, - dispatch, - ...restProps - }) { - const {t} = useTranslation(); +function RbacWrapper({ authLevel, bodyshop, requiredAuthLevel, noauth, children, action, dispatch, ...restProps }) { + const { t } = useTranslation(); - if ( - (requiredAuthLevel && requiredAuthLevel <= authLevel) || - ((bodyshop.md_rbac && bodyshop.md_rbac[action]) || rbacDefaults[action]) <= - authLevel || - (bodyshop.md_rbac && - !bodyshop.md_rbac[action] && - rbacDefaults[action] <= authLevel) - ) - return children; - //return
    {React.cloneElement(children, restProps)}
    ; + if ( + (requiredAuthLevel && requiredAuthLevel <= authLevel) || + ((bodyshop.md_rbac && bodyshop.md_rbac[action]) || rbacDefaults[action]) <= authLevel || + (bodyshop.md_rbac && !bodyshop.md_rbac[action] && rbacDefaults[action] <= authLevel) + ) + return children; + //return
    {React.cloneElement(children, restProps)}
    ; - return ( - noauth || ( - - ) - ); + return noauth || ; } -export function HasRbacAccess({ - authLevel, - bodyshop, - requiredAuthLevel, - action, - }) { - return ( - (requiredAuthLevel && requiredAuthLevel <= authLevel) || - ((bodyshop.md_rbac && bodyshop.md_rbac[action]) || rbacDefaults[action]) <= - authLevel || - (bodyshop.md_rbac && - !bodyshop.md_rbac[action] && - rbacDefaults[action] <= authLevel) - ); +export function HasRbacAccess({ authLevel, bodyshop, requiredAuthLevel, action }) { + return ( + (requiredAuthLevel && requiredAuthLevel <= authLevel) || + ((bodyshop.md_rbac && bodyshop.md_rbac[action]) || rbacDefaults[action]) <= authLevel || + (bodyshop.md_rbac && !bodyshop.md_rbac[action] && rbacDefaults[action] <= authLevel) + ); } export default connect(mapStateToProps, null)(RbacWrapper); diff --git a/client/src/components/report-center-modal/report-center-modal-filters-sorters-component.jsx b/client/src/components/report-center-modal/report-center-modal-filters-sorters-component.jsx index 9a2daff8a..ace62cf27 100644 --- a/client/src/components/report-center-modal/report-center-modal-filters-sorters-component.jsx +++ b/client/src/components/report-center-modal/report-center-modal-filters-sorters-component.jsx @@ -1,22 +1,22 @@ -import {Button, Card, Checkbox, Col, Form, Input, InputNumber, Row, Select} from "antd"; -import React, {useCallback, useEffect, useMemo, useState} from "react"; -import {fetchFilterData} from "../../utils/RenderTemplate"; -import {DeleteFilled} from "@ant-design/icons"; -import {useTranslation} from "react-i18next"; -import {getOrderOperatorsByType, getWhereOperatorsByType} from "../../utils/graphQLmodifier"; +import { Button, Card, Checkbox, Col, Form, Input, InputNumber, Row, Select } from "antd"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { fetchFilterData } from "../../utils/RenderTemplate"; +import { DeleteFilled } from "@ant-design/icons"; +import { useTranslation } from "react-i18next"; +import { getOrderOperatorsByType, getWhereOperatorsByType } from "../../utils/graphQLmodifier"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; -import {generateInternalReflections} from "./report-center-modal-utils"; -import {FormDatePicker} from "../form-date-picker/form-date-picker.component.jsx"; +import { generateInternalReflections } from "./report-center-modal-utils"; +import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx"; -export default function ReportCenterModalFiltersSortersComponent({form, bodyshop}) { - return ( - - {() => { - const key = form.getFieldValue("key"); - return ; - }} - - ); +export default function ReportCenterModalFiltersSortersComponent({ form, bodyshop }) { + return ( + + {() => { + const key = form.getFieldValue("key"); + return ; + }} + + ); } /** @@ -27,236 +27,242 @@ export default function ReportCenterModalFiltersSortersComponent({form, bodyshop * @returns {JSX.Element} * @constructor */ -function FiltersSection({filters, form, bodyshop}) { - const {t} = useTranslation(); +function FiltersSection({ filters, form, bodyshop }) { + const { t } = useTranslation(); - return ( - - - {(fields, {add, remove}) => { - return ( -
    - {fields.map((field, index) => ( - - -
    - - trigger.parentNode} + onChange={() => { + // Clear related Fields + form.setFieldValue(["filters", field.name, "value"], null); + form.setFieldValue(["filters", field.name, "operator"], null); + }} + options={filters.map((f) => { + return { + value: f.name, + label: f?.translation + ? t(f.translation) === f.translation + ? f.label + : t(f.translation) + : f.label + }; + })} + /> + + + + + {() => { + const name = form.getFieldValue(["filters", field.name, "field"]); + const type = filters.find((f) => f.name === name)?.type; - return - trigger.parentNode} + options={getWhereOperatorsByType(type)} + onChange={() => { + // Clear related Fields - form.setFieldValue(['filters', field.name, 'value'], undefined); - }} - /> - - } - } - - - - - { - () => { - // Because it looks cleaner than inlining. - const name = form.getFieldValue(['filters', field.name, "field"]); - const type = filters.find(f => f.name === name)?.type; - const reflector = filters.find(f => f.name === name)?.reflector; - const operator = form.getFieldValue(['filters', field.name, "operator"]); - const operatorType = operator ? getWhereOperatorsByType(type).find((o) => o.value === operator)?.type : null; - - return - { - (() => { - const generateReflections = (reflector) => { - if (!reflector) return []; - - const {name} = reflector; - const path = name?.split('.'); - const upperPath = path?.[0]; - const finalPath = path?.slice(1).join('.'); - - return generateInternalReflections({ - bodyshop, - upperPath, - finalPath, - t - }); - }; - - const reflections = reflector ? generateReflections(reflector) : []; - const fieldPath = [[field.name, "value"]]; - // We have reflections so we will use a select box - if (reflections.length > 0) { - // We have reflections and the operator type is array, so we will use a select box with multiple options - if (operatorType === "array") { - return ( - trigger.parentNode} - onChange={(value) => { - form.setFieldValue(fieldPath, value); - }} - /> - ); - } - - // We have a type of number, so we will use a number input - if (type === "number") { - return ( - form.setFieldValue(fieldPath, value)}/> - ); - } - - // We have a type of date, so we will use a date picker - if (type === "date") { - return ( - form.setFieldValue(fieldPath, date)} - /> - ); - } - - // we have a type of boolean, so we will use a select box with a true or false option. - if (type === "boolean" || type === "bool") { - return ( - form.setFieldValue(fieldPath, e.target.value)} - /> - ); - })() - } - - } - } - - - - - { - remove(field.name); - }} - /> - - - - ))} - - + form.setFieldValue(["filters", field.name, "value"], undefined); + }} + /> - - ); - }} - - - ); + ); + }} + + + + + {() => { + // Because it looks cleaner than inlining. + const name = form.getFieldValue(["filters", field.name, "field"]); + const type = filters.find((f) => f.name === name)?.type; + const reflector = filters.find((f) => f.name === name)?.reflector; + const operator = form.getFieldValue(["filters", field.name, "operator"]); + const operatorType = operator + ? getWhereOperatorsByType(type).find((o) => o.value === operator)?.type + : null; + + return ( + + {(() => { + const generateReflections = (reflector) => { + if (!reflector) return []; + + const { name } = reflector; + const path = name?.split("."); + const upperPath = path?.[0]; + const finalPath = path?.slice(1).join("."); + + return generateInternalReflections({ + bodyshop, + upperPath, + finalPath, + t + }); + }; + + const reflections = reflector ? generateReflections(reflector) : []; + const fieldPath = [[field.name, "value"]]; + // We have reflections so we will use a select box + if (reflections.length > 0) { + // We have reflections and the operator type is array, so we will use a select box with multiple options + if (operatorType === "array") { + return ( + trigger.parentNode} + onChange={(value) => { + form.setFieldValue(fieldPath, value); + }} + /> + ); + } + + // We have a type of number, so we will use a number input + if (type === "number") { + return ( + form.setFieldValue(fieldPath, value)} + /> + ); + } + + // We have a type of date, so we will use a date picker + if (type === "date") { + return ( + form.setFieldValue(fieldPath, date)} + /> + ); + } + + // we have a type of boolean, so we will use a select box with a true or false option. + if (type === "boolean" || type === "bool") { + return ( + form.setFieldValue(fieldPath, e.target.value)} + /> + ); + })()} + + ); + }} + + + + { + remove(field.name); + }} + /> + + + + ))} + + + + + ); + }} + + + ); } /** @@ -266,88 +272,90 @@ function FiltersSection({filters, form, bodyshop}) { * @returns {JSX.Element} * @constructor */ -function SortersSection({sorters}) { - const {t} = useTranslation(); - return ( - - - {(fields, {add, remove}) => { - return ( -
    - Sorters - {fields.map((field, index) => ( - - -
    - - trigger.parentNode} - /> - - +function SortersSection({ sorters }) { + const { t } = useTranslation(); + return ( + + + {(fields, { add, remove }) => { + return ( +
    + Sorters + {fields.map((field, index) => ( + + +
    + + trigger.parentNode} + /> + + - - { - remove(field.name); - }} - /> - - - - ))} - - - - - ); - }} - - - ); + + { + remove(field.name); + }} + /> + + + + ))} + + + + + ); + }} + + + ); } /** @@ -358,75 +366,73 @@ function SortersSection({sorters}) { * @returns {JSX.Element|null} * @constructor */ -function RenderFilters({templateId, form, bodyshop}) { - const [state, setState] = useState(null); - const [visible, setVisible] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const {t} = useTranslation(); +function RenderFilters({ templateId, form, bodyshop }) { + const [state, setState] = useState(null); + const [visible, setVisible] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const { t } = useTranslation(); - const fetch = useCallback(async () => { - // Reset all the filters and Sorters. - form.resetFields(['filters']); - form.resetFields(['sorters']); - form.resetFields(['defaultSorters']); + const fetch = useCallback(async () => { + // Reset all the filters and Sorters. + form.resetFields(["filters"]); + form.resetFields(["sorters"]); + form.resetFields(["defaultSorters"]); - setIsLoading(true); + setIsLoading(true); - const data = await fetchFilterData({name: templateId}); + const data = await fetchFilterData({ name: templateId }); - // We have Success - if (data?.success) { - if (data?.data?.sorters && data?.data?.sorters.length > 0) { - const defaultSorters = data?.data?.sorters.filter((sorter) => sorter.hasOwnProperty('default')).map((sorter) => { - return { - field: sorter.name, - direction: sorter.default.direction - }; - }).sort((a, b) => a.default.order - b.default.order); + // We have Success + if (data?.success) { + if (data?.data?.sorters && data?.data?.sorters.length > 0) { + const defaultSorters = data?.data?.sorters + .filter((sorter) => sorter.hasOwnProperty("default")) + .map((sorter) => { + return { + field: sorter.name, + direction: sorter.default.direction + }; + }) + .sort((a, b) => a.default.order - b.default.order); - form.setFieldValue('defaultSorters', JSON.stringify(defaultSorters)); - } - // Set the state - setState(data.data); - } - // Something went wrong fetching filter data - else { - setState(null); - } - setIsLoading(false); - }, [templateId, form]); + form.setFieldValue("defaultSorters", JSON.stringify(defaultSorters)); + } + // Set the state + setState(data.data); + } + // Something went wrong fetching filter data + else { + setState(null); + } + setIsLoading(false); + }, [templateId, form]); - useEffect(() => { - if (templateId) { - fetch(); + useEffect(() => { + if (templateId) { + fetch(); + } + }, [templateId, fetch]); - } - }, [templateId, fetch]); + const filters = useMemo(() => state?.filters || [], [state]); + const sorters = useMemo(() => state?.sorters || [], [state]); - const filters = useMemo(() => state?.filters || [], [state]); - const sorters = useMemo(() => state?.sorters || [], [state]); + if (!templateId) return null; + if (isLoading) return ; + if (!state) return null; - if (!templateId) return null; - if (isLoading) return ; - if (!state) return null; - - return ( -
    - setVisible(e.target.checked)} - children={t('reportcenter.labels.advanced_filters')} - /> - {visible && ( -
    - {filters.length > 0 && ( - - )} - {sorters.length > 0 && ( - - )} -
    - )} + return ( +
    + setVisible(e.target.checked)} + children={t("reportcenter.labels.advanced_filters")} + /> + {visible && ( +
    + {filters.length > 0 && } + {sorters.length > 0 && }
    - ); -} \ No newline at end of file + )} +
    + ); +} diff --git a/client/src/components/report-center-modal/report-center-modal-utils.js b/client/src/components/report-center-modal/report-center-modal-utils.js index d78405cfd..15171e89b 100644 --- a/client/src/components/report-center-modal/report-center-modal-utils.js +++ b/client/src/components/report-center-modal/report-center-modal-utils.js @@ -1,4 +1,4 @@ -import {uniqBy} from "lodash"; +import { uniqBy } from "lodash"; /** * Get value from path @@ -6,7 +6,7 @@ import {uniqBy} from "lodash"; * @param path * @returns {*} */ -const getValueFromPath = (obj, path) => path.split('.').reduce((prev, curr) => prev?.[curr], obj); +const getValueFromPath = (obj, path) => path.split(".").reduce((prev, curr) => prev?.[curr], obj); /** * Generate options from array @@ -15,12 +15,15 @@ const getValueFromPath = (obj, path) => path.split('.').reduce((prev, curr) => p * @returns {unknown[]} */ const generateOptionsFromArray = (bodyshop, path) => { - const options = getValueFromPath(bodyshop, path); - return uniqBy(options.map((value) => ({ - label: value, - value: value, - })), 'value'); -} + const options = getValueFromPath(bodyshop, path); + return uniqBy( + options.map((value) => ({ + label: value, + value: value + })), + "value" + ); +}; /** * Valid internal reflections @@ -28,12 +31,12 @@ const generateOptionsFromArray = (bodyshop, path) => { * @type {{special: string[], bodyshop: [{name: string, type: string}]}} */ const VALID_INTERNAL_REFLECTIONS = { - bodyshop: [ - { - name: 'md_ro_statuses.statuses', - type: 'kv-to-v' - } - ], + bodyshop: [ + { + name: "md_ro_statuses.statuses", + type: "kv-to-v" + } + ] }; /** @@ -45,12 +48,15 @@ const VALID_INTERNAL_REFLECTIONS = { * @returns {{label: *, value: *}[]} */ const generateOptionsFromObject = (bodyshop, path, labelPath, valuePath) => { - const options = getValueFromPath(bodyshop, path); - return uniqBy(Object.values(options).map((value) => ({ - label: value[labelPath], - value: value[valuePath], - })), 'value'); -} + const options = getValueFromPath(bodyshop, path); + return uniqBy( + Object.values(options).map((value) => ({ + label: value[labelPath], + value: value[valuePath] + })), + "value" + ); +}; /** * Generate special reflections @@ -60,66 +66,69 @@ const generateOptionsFromObject = (bodyshop, path, labelPath, valuePath) => { * @returns {{label: *, value: *}[]|{label: *, value: *}[]|{label: string, value: *}[]|*[]} */ const generateSpecialReflections = (bodyshop, finalPath, t) => { - switch (finalPath) { - case 'payment_payers': - return [ - { - label: t("payments.labels.customer"), - value: t("payments.labels.customer"), - }, - { - label: t("payments.labels.insurance"), - value: t("payments.labels.insurance"), - }, - // This is a weird one supposedly only used by one shop and could potentially be - // placed behind a SplitSDK - { - label: t("payments.labels.external"), - value: t("payments.labels.external"), - } - ]; - case 'payment_types': - return generateOptionsFromArray(bodyshop, 'md_payment_types'); - case 'alt_transports': - return generateOptionsFromArray(bodyshop, 'appt_alt_transport'); - case 'lost_sale_reasons': - return generateOptionsFromArray(bodyshop, 'md_lost_sale_reasons'); - // Special case because Referral Sources is an Array, not an Object. - case 'referral_source': - return generateOptionsFromArray(bodyshop, 'md_referral_sources'); - case 'class': - return generateOptionsFromArray(bodyshop, 'md_classes'); - case 'cost_centers': - return generateOptionsFromObject(bodyshop, 'md_responsibility_centers.costs', 'name', 'name'); - // Special case because Categories is an Array, not an Object. - case 'categories': - return generateOptionsFromArray(bodyshop, 'md_categories'); - case 'insurance_companies': - return generateOptionsFromObject(bodyshop, 'md_ins_cos', 'name', 'name'); - case 'employee_teams': - return generateOptionsFromObject(bodyshop, 'employee_teams', 'name', 'id'); - // Special case because Employees uses a concatenation of first_name and last_name - case 'employees': - const employeesOptions = getValueFromPath(bodyshop, 'employees'); - return uniqBy(Object.values(employeesOptions).map((value) => ({ - label: `${value.first_name} ${value.last_name}`, - value: value.id, - })), 'value'); - case 'last_names': - return generateOptionsFromObject(bodyshop, 'employees', 'last_name', 'last_name'); - case 'first_names': - return generateOptionsFromObject(bodyshop, 'employees', 'first_name', 'first_name'); - case 'job_statuses': - const statusOptions = getValueFromPath(bodyshop, 'md_ro_statuses.statuses'); - return Object.values(statusOptions).map((value) => ({ - label: value, - value - })); - default: - console.error('Invalid Special reflection provided by Report Filters'); - return []; - } -} + switch (finalPath) { + case "payment_payers": + return [ + { + label: t("payments.labels.customer"), + value: t("payments.labels.customer") + }, + { + label: t("payments.labels.insurance"), + value: t("payments.labels.insurance") + }, + // This is a weird one supposedly only used by one shop and could potentially be + // placed behind a SplitSDK + { + label: t("payments.labels.external"), + value: t("payments.labels.external") + } + ]; + case "payment_types": + return generateOptionsFromArray(bodyshop, "md_payment_types"); + case "alt_transports": + return generateOptionsFromArray(bodyshop, "appt_alt_transport"); + case "lost_sale_reasons": + return generateOptionsFromArray(bodyshop, "md_lost_sale_reasons"); + // Special case because Referral Sources is an Array, not an Object. + case "referral_source": + return generateOptionsFromArray(bodyshop, "md_referral_sources"); + case "class": + return generateOptionsFromArray(bodyshop, "md_classes"); + case "cost_centers": + return generateOptionsFromObject(bodyshop, "md_responsibility_centers.costs", "name", "name"); + // Special case because Categories is an Array, not an Object. + case "categories": + return generateOptionsFromArray(bodyshop, "md_categories"); + case "insurance_companies": + return generateOptionsFromObject(bodyshop, "md_ins_cos", "name", "name"); + case "employee_teams": + return generateOptionsFromObject(bodyshop, "employee_teams", "name", "id"); + // Special case because Employees uses a concatenation of first_name and last_name + case "employees": + const employeesOptions = getValueFromPath(bodyshop, "employees"); + return uniqBy( + Object.values(employeesOptions).map((value) => ({ + label: `${value.first_name} ${value.last_name}`, + value: value.id + })), + "value" + ); + case "last_names": + return generateOptionsFromObject(bodyshop, "employees", "last_name", "last_name"); + case "first_names": + return generateOptionsFromObject(bodyshop, "employees", "first_name", "first_name"); + case "job_statuses": + const statusOptions = getValueFromPath(bodyshop, "md_ro_statuses.statuses"); + return Object.values(statusOptions).map((value) => ({ + label: value, + value + })); + default: + console.error("Invalid Special reflection provided by Report Filters"); + return []; + } +}; /** * Generate bodyshop reflections @@ -128,16 +137,16 @@ const generateSpecialReflections = (bodyshop, finalPath, t) => { * @returns {{label: *, value: *}[]|*[]} */ const generateBodyshopReflections = (bodyshop, finalPath) => { - const options = getValueFromPath(bodyshop, finalPath); - const reflectionRenderer = VALID_INTERNAL_REFLECTIONS.bodyshop.find(reflection => reflection.name === finalPath); - if (reflectionRenderer?.type === 'kv-to-v') { - return Object.values(options).map((value) => ({ - label: value, - value - })); - } - return []; -} + const options = getValueFromPath(bodyshop, finalPath); + const reflectionRenderer = VALID_INTERNAL_REFLECTIONS.bodyshop.find((reflection) => reflection.name === finalPath); + if (reflectionRenderer?.type === "kv-to-v") { + return Object.values(options).map((value) => ({ + label: value, + value + })); + } + return []; +}; /** * Generate internal reflections based on the path and bodyshop @@ -147,15 +156,15 @@ const generateBodyshopReflections = (bodyshop, finalPath) => { * @param t - i18n * @returns {{label: *, value: *}[]|[]|{label: *, value: *}[]|{label: string, value: *}[]|{label: *, value: *}[]|*[]} */ -const generateInternalReflections = ({bodyshop, upperPath, finalPath, t}) => { - switch (upperPath) { - case 'special': - return generateSpecialReflections(bodyshop, finalPath, t); - case 'bodyshop': - return generateBodyshopReflections(bodyshop, finalPath); - default: - return []; - } +const generateInternalReflections = ({ bodyshop, upperPath, finalPath, t }) => { + switch (upperPath) { + case "special": + return generateSpecialReflections(bodyshop, finalPath, t); + case "bodyshop": + return generateBodyshopReflections(bodyshop, finalPath); + default: + return []; + } }; -export {generateInternalReflections} \ No newline at end of file +export { generateInternalReflections }; diff --git a/client/src/components/report-center-modal/report-center-modal.component.jsx b/client/src/components/report-center-modal/report-center-modal.component.jsx index 5830c1c04..b1847d9e3 100644 --- a/client/src/components/report-center-modal/report-center-modal.component.jsx +++ b/client/src/components/report-center-modal/report-center-modal.component.jsx @@ -1,19 +1,19 @@ -import {useLazyQuery} from "@apollo/client"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; -import {Button, Card, Col, DatePicker, Form, Input, Radio, Row, Typography,} from "antd"; +import { useLazyQuery } from "@apollo/client"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { Button, Card, Col, DatePicker, Form, Input, Radio, Row, Typography } from "antd"; import _ from "lodash"; import dayjs from "../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {QUERY_ACTIVE_EMPLOYEES} from "../../graphql/employees.queries"; -import {QUERY_ALL_VENDORS} from "../../graphql/vendors.queries"; -import {selectReportCenter} from "../../redux/modals/modals.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { QUERY_ACTIVE_EMPLOYEES } from "../../graphql/employees.queries"; +import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries"; +import { selectReportCenter } from "../../redux/modals/modals.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import DatePickerRanges from "../../utils/DatePickerRanges"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component"; import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component"; import "./report-center-modal.styles.scss"; @@ -21,331 +21,275 @@ import ReportCenterModalFiltersSortersComponent from "./report-center-modal-filt import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; const mapStateToProps = createStructuredSelector({ - reportCenterModal: selectReportCenter, - bodyshop: selectBodyshop, + reportCenterModal: selectReportCenter, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ReportCenterModalComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ReportCenterModalComponent); -export function ReportCenterModalComponent({reportCenterModal, bodyshop}) { - const [form] = Form.useForm(); - const [search, setSearch] = useState(""); - const {treatments: {Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, - }); +export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) { + const [form] = Form.useForm(); + const [search, setSearch] = useState(""); + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); - const [loading, setLoading] = useState(false); - const {t} = useTranslation(); - const Templates = TemplateList("report_center"); - const ReportsList = - Enhanced_Payroll.treatment === "on" - ? Object.keys(Templates) - .map((key) => { - return Templates[key]; - }) - .filter( - (temp) => - temp.enhanced_payroll === undefined || - temp.enhanced_payroll === true - ) - : Object.keys(Templates) - .map((key) => { - return Templates[key]; - }) - .filter( - (temp) => - temp.enhanced_payroll === undefined || - temp.enhanced_payroll === false - ); + const [loading, setLoading] = useState(false); + const { t } = useTranslation(); + const Templates = TemplateList("report_center"); + const ReportsList = + Enhanced_Payroll.treatment === "on" + ? Object.keys(Templates) + .map((key) => { + return Templates[key]; + }) + .filter((temp) => temp.enhanced_payroll === undefined || temp.enhanced_payroll === true) + : Object.keys(Templates) + .map((key) => { + return Templates[key]; + }) + .filter((temp) => temp.enhanced_payroll === undefined || temp.enhanced_payroll === false); - const {open} = reportCenterModal; + const { open } = reportCenterModal; - const [callVendorQuery, {data: vendorData, called: vendorCalled}] = - useLazyQuery(QUERY_ALL_VENDORS, { - skip: !( - open && - Templates[form.getFieldValue("key")] && - Templates[form.getFieldValue("key")].idtype - ), - }); + const [callVendorQuery, { data: vendorData, called: vendorCalled }] = useLazyQuery(QUERY_ALL_VENDORS, { + skip: !(open && Templates[form.getFieldValue("key")] && Templates[form.getFieldValue("key")].idtype) + }); - const [callEmployeeQuery, {data: employeeData, called: employeeCalled}] = - useLazyQuery(QUERY_ACTIVE_EMPLOYEES, { - skip: !( - open && - Templates[form.getFieldValue("key")] && - Templates[form.getFieldValue("key")].idtype - ), - }); + const [callEmployeeQuery, { data: employeeData, called: employeeCalled }] = useLazyQuery(QUERY_ACTIVE_EMPLOYEES, { + skip: !(open && Templates[form.getFieldValue("key")] && Templates[form.getFieldValue("key")].idtype) + }); - const handleFinish = async (values) => { - setLoading(true); - const start = values.dates ? values.dates[0] : null; - const end = values.dates ? values.dates[1] : null; - const {id} = values; + const handleFinish = async (values) => { + setLoading(true); + const start = values.dates ? values.dates[0] : null; + const end = values.dates ? values.dates[1] : null; + const { id } = values; - const templateConfig = - { - name: values.key, - variables: { - ...(start - ? {start: dayjs(start).startOf("day").format("YYYY-MM-DD")} - : {}), - ...(end - ? {end: dayjs(end).endOf("day").format("YYYY-MM-DD")} - : {}), - ...(start ? {starttz: dayjs(start).startOf("day")} : {}), - ...(end ? {endtz: dayjs(end).endOf("day")} : {}), + const templateConfig = { + name: values.key, + variables: { + ...(start ? { start: dayjs(start).startOf("day").format("YYYY-MM-DD") } : {}), + ...(end ? { end: dayjs(end).endOf("day").format("YYYY-MM-DD") } : {}), + ...(start ? { starttz: dayjs(start).startOf("day") } : {}), + ...(end ? { endtz: dayjs(end).endOf("day") } : {}), - ...(id ? {id: id} : {}), - }, - filters: values.filters, - sorters: values.sorters, - }; - - if (_.isString(values.defaultSorters) && !_.isEmpty(values.defaultSorters)) { - templateConfig.defaultSorters = JSON.parse(values.defaultSorters); - } - - await GenerateDocument( - templateConfig, - { - to: values.to, - subject: Templates[values.key]?.subject, - }, - values.sendbyexcel === "excel" - ? "x" - : values.sendby === "email" - ? "e" - : "p", - id - ); - setLoading(false); + ...(id ? { id: id } : {}) + }, + filters: values.filters, + sorters: values.sorters }; - const FilteredReportsList = - search !== "" - ? ReportsList.filter((r) => - r.title.toLowerCase().includes(search.toLowerCase()) - ) - : ReportsList; + if (_.isString(values.defaultSorters) && !_.isEmpty(values.defaultSorters)) { + templateConfig.defaultSorters = JSON.parse(values.defaultSorters); + } - //Group it, create cards, and then filter out. + await GenerateDocument( + templateConfig, + { + to: values.to, + subject: Templates[values.key]?.subject + }, + values.sendbyexcel === "excel" ? "x" : values.sendby === "email" ? "e" : "p", + id + ); + setLoading(false); + }; - const grouped = _.groupBy(FilteredReportsList, "group"); + const FilteredReportsList = + search !== "" ? ReportsList.filter((r) => r.title.toLowerCase().includes(search.toLowerCase())) : ReportsList; - const groupExcludeKeyFilter = [ - ...(!HasFeatureAccess({ featureName: 'bills', bodyshop }) ? ['purchases'] : []), - ...(!HasFeatureAccess({ featureName: 'timetickets', bodyshop }) ? ['payroll'] : []), - ]; + //Group it, create cards, and then filter out. - return ( -
    -
    - setSearch(e.target.value)} - value={search} - /> -
    - - - {t(`reportcenter.labels.groups.${key}`)} - -
      - {grouped[key].map((item) => ( -
    • - - {item.title} - -
    • - ))} -
    -
    - - ))} - - - - - {() => { - const key = form.getFieldValue("key"); - if (!key) return null; - //Kind of Id - const rangeFilter = Templates[key] && Templates[key].rangeFilter; - if (!rangeFilter) return null; - return ( -
    - {t("reportcenter.labels.filterson", { - object: rangeFilter.object, - field: rangeFilter.field, - })} -
    - ); - }} -
    - - - {() => { - const key = form.getFieldValue("key"); - const currentId = form.getFieldValue("id"); - if (!key) return null; - //Kind of Id - const idtype = Templates[key] && Templates[key].idtype; - if (!idtype && currentId) { - form.setFieldsValue({id: null}); - return null; - } - if (!vendorCalled && idtype === "vendor") callVendorQuery(); - if (!employeeCalled && idtype === "employee") callEmployeeQuery(); - if (idtype === "vendor") - return ( - - - - ); - if (idtype === "employee") - return ( - - - - ); - else return null; - }} - - - {() => { - const key = form.getFieldValue("key"); - const datedisable = Templates[key] && Templates[key].datedisable; - if (datedisable !== true) { - return ( - - - - ); - } else return null; - }} - - - {() => { - const key = form.getFieldValue("key"); - //Kind of Id - const reporttype = Templates[key] && Templates[key].reporttype; - - if (reporttype === "excel") - return ( - - - {t("general.labels.excel")} - - - ); - if (reporttype !== "excel") - return ( - - - {t("general.labels.email")} - {t("general.labels.print")} - - - ); - }} - - -
    + {Object.keys(grouped) + .filter((key) => !groupExcludeKeyFilter.includes(key)) + .map((key) => ( +
    + + {t(`reportcenter.labels.groups.${key}`)} +
      + {grouped[key].map((item) => ( +
    • + + {item.title} + +
    • + ))} +
    +
    + + ))} + + + + + {() => { + const key = form.getFieldValue("key"); + if (!key) return null; + //Kind of Id + const rangeFilter = Templates[key] && Templates[key].rangeFilter; + if (!rangeFilter) return null; + return ( +
    + {t("reportcenter.labels.filterson", { + object: rangeFilter.object, + field: rangeFilter.field + })} +
    + ); + }} +
    + + + {() => { + const key = form.getFieldValue("key"); + const currentId = form.getFieldValue("id"); + if (!key) return null; + //Kind of Id + const idtype = Templates[key] && Templates[key].idtype; + if (!idtype && currentId) { + form.setFieldsValue({ id: null }); + return null; + } + if (!vendorCalled && idtype === "vendor") callVendorQuery(); + if (!employeeCalled && idtype === "employee") callEmployeeQuery(); + if (idtype === "vendor") + return ( + - - - - - ); -} + + + ); + if (idtype === "employee") + return ( + + + + ); + else return null; + }} + + + {() => { + const key = form.getFieldValue("key"); + const datedisable = Templates[key] && Templates[key].datedisable; + if (datedisable !== true) { + return ( + + + + ); + } else return null; + }} + + + {() => { + const key = form.getFieldValue("key"); + //Kind of Id + const reporttype = Templates[key] && Templates[key].reporttype; + if (reporttype === "excel") + return ( + + + {t("general.labels.excel")} + + + ); + if (reporttype !== "excel") + return ( + + + {t("general.labels.email")} + {t("general.labels.print")} + + + ); + }} + + +
    + +
    + + + ); +} diff --git a/client/src/components/report-center-modal/report-center-modal.container.jsx b/client/src/components/report-center-modal/report-center-modal.container.jsx index 6537c4964..292271eba 100644 --- a/client/src/components/report-center-modal/report-center-modal.container.jsx +++ b/client/src/components/report-center-modal/report-center-modal.container.jsx @@ -1,47 +1,41 @@ -import {Modal} from "antd"; +import { Modal } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectReportCenter} from "../../redux/modals/modals.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectReportCenter } from "../../redux/modals/modals.selectors"; import RbacWrapperComponent from "../rbac-wrapper/rbac-wrapper.component"; import ReportCenterModalComponent from "./report-center-modal.component"; const mapStateToProps = createStructuredSelector({ - reportCenterModal: selectReportCenter, + reportCenterModal: selectReportCenter }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("reportCenter")), + toggleModalVisible: () => dispatch(toggleModalVisible("reportCenter")) }); -export function ReportCenterModalContainer({ - reportCenterModal, - toggleModalVisible, - }) { - const {t} = useTranslation(); +export function ReportCenterModalContainer({ reportCenterModal, toggleModalVisible }) { + const { t } = useTranslation(); - const {open} = reportCenterModal; + const { open } = reportCenterModal; - return ( - toggleModalVisible()} - onCancel={() => toggleModalVisible()} - cancelButtonProps={{style: {display: "none"}}} - destroyOnClose - width="80%" - > - - - - - ); + return ( + toggleModalVisible()} + onCancel={() => toggleModalVisible()} + cancelButtonProps={{ style: { display: "none" } }} + destroyOnClose + width="80%" + > + + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ReportCenterModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(ReportCenterModalContainer); diff --git a/client/src/components/schedule-ats-summary/schedule-ats-summary.component.jsx b/client/src/components/schedule-ats-summary/schedule-ats-summary.component.jsx index 3b9d0c828..5e7619495 100644 --- a/client/src/components/schedule-ats-summary/schedule-ats-summary.component.jsx +++ b/client/src/components/schedule-ats-summary/schedule-ats-summary.component.jsx @@ -1,50 +1,50 @@ -import {Space} from "antd"; -import React, {useMemo} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectScheduleLoad} from "../../redux/application/application.selectors"; +import { Space } from "antd"; +import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectScheduleLoad } from "../../redux/application/application.selectors"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - scheduleLoad: selectScheduleLoad, + //currentUser: selectCurrentUser + scheduleLoad: selectScheduleLoad }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function ScheduleAtsSummary({scheduleLoad, appointments}) { - const {t} = useTranslation(); +export function ScheduleAtsSummary({ scheduleLoad, appointments }) { + const { t } = useTranslation(); - const atsSummary = useMemo(() => { - let atsSummary = {}; - if (!appointments || appointments.length === 0) { - return {}; + const atsSummary = useMemo(() => { + let atsSummary = {}; + if (!appointments || appointments.length === 0) { + return {}; + } + appointments + .filter((a) => a.isintake) + .forEach((a) => { + if (!a.job.alt_transport) return; + if (!atsSummary[a.job.alt_transport]) { + atsSummary[a.job.alt_transport] = 1; + } else { + atsSummary[a.job.alt_transport] = atsSummary[a.job.alt_transport] + 1; } - appointments - .filter((a) => a.isintake) - .forEach((a) => { - if (!a.job.alt_transport) return; - if (!atsSummary[a.job.alt_transport]) { - atsSummary[a.job.alt_transport] = 1; - } else { - atsSummary[a.job.alt_transport] = atsSummary[a.job.alt_transport] + 1; - } - }); - return atsSummary; - }, [appointments]); + }); + return atsSummary; + }, [appointments]); - if (Object.keys(atsSummary).length > 0) - return ( - - {t("schedule.labels.atssummary")} - {Object.keys(atsSummary).map((key) => ( - {`${key}: ${atsSummary[key]}`} - ))} - - ); + if (Object.keys(atsSummary).length > 0) + return ( + + {t("schedule.labels.atssummary")} + {Object.keys(atsSummary).map((key) => ( + {`${key}: ${atsSummary[key]}`} + ))} + + ); - return null; + return null; } export default connect(mapStateToProps, mapDispatchToProps)(ScheduleAtsSummary); diff --git a/client/src/components/schedule-block-day/schedule-block-day.component.jsx b/client/src/components/schedule-block-day/schedule-block-day.component.jsx index 82149eb6e..e579697db 100644 --- a/client/src/components/schedule-block-day/schedule-block-day.component.jsx +++ b/client/src/components/schedule-block-day/schedule-block-day.component.jsx @@ -1,75 +1,69 @@ -import {useMutation} from "@apollo/client"; -import {Dropdown, notification} from "antd"; +import { useMutation } from "@apollo/client"; +import { Dropdown, notification } from "antd"; import dayjs from "../../utils/day"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {INSERT_APPOINTMENT_BLOCK} from "../../graphql/appointments.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_APPOINTMENT_BLOCK } from "../../graphql/appointments.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { logImEXEvent } from "../../firebase/firebase.utils"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({}); -export function ScheduleBlockDay({ - date, - children, - refetch, - bodyshop, - alreadyBlocked, - }) { - const {t} = useTranslation(); - const [insertBlock] = useMutation(INSERT_APPOINTMENT_BLOCK); +export function ScheduleBlockDay({ date, children, refetch, bodyshop, alreadyBlocked }) { + const { t } = useTranslation(); + const [insertBlock] = useMutation(INSERT_APPOINTMENT_BLOCK); - const handleMenu = async (e) => { - e.domEvent.stopPropagation(); + const handleMenu = async (e) => { + e.domEvent.stopPropagation(); - if (e.key === "block") { - const blockAppt = { - title: t("appointments.labels.blocked"), - block: true, - isintake: false, - bodyshopid: bodyshop.id, - start: dayjs(date).startOf("day"), - end: dayjs(date).endOf("day"), - }; - logImEXEvent("dashboard_change_layout"); + if (e.key === "block") { + const blockAppt = { + title: t("appointments.labels.blocked"), + block: true, + isintake: false, + bodyshopid: bodyshop.id, + start: dayjs(date).startOf("day"), + end: dayjs(date).endOf("day") + }; + logImEXEvent("dashboard_change_layout"); - const result = await insertBlock({ - variables: {app: [blockAppt]}, - }); + const result = await insertBlock({ + variables: { app: [blockAppt] } + }); - if (!!result.errors) { - notification["error"]({ - message: t("appointments.errors.blocking", { - message: JSON.stringify(result.errors), - }), - }); - } + if (!!result.errors) { + notification["error"]({ + message: t("appointments.errors.blocking", { + message: JSON.stringify(result.errors) + }) + }); + } - if (!!refetch) refetch(); - } - }; - - const menu = { - items: [ - { - key: "block", - label: t("appointments.actions.block"), - }, - ], - onClick: handleMenu, + if (!!refetch) refetch(); } + }; - return ( - - {children} - - ); + const menu = { + items: [ + { + key: "block", + label: t("appointments.actions.block") + } + ], + onClick: handleMenu + }; + + return ( + + {children} + + ); } export default connect(mapStateToProps, mapDispatchToProps)(ScheduleBlockDay); diff --git a/client/src/components/schedule-calendar-wrapper/schedule-calendar-header-graph.component.jsx b/client/src/components/schedule-calendar-wrapper/schedule-calendar-header-graph.component.jsx index 9d827b75a..dd23c669f 100644 --- a/client/src/components/schedule-calendar-wrapper/schedule-calendar-header-graph.component.jsx +++ b/client/src/components/schedule-calendar-wrapper/schedule-calendar-header-graph.component.jsx @@ -1,86 +1,71 @@ -import {RadarChartOutlined} from "@ant-design/icons"; -import {Popover, Space} from "antd"; -import React, {useMemo} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Legend, PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart, Tooltip,} from "recharts"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { RadarChartOutlined } from "@ant-design/icons"; +import { Popover, Space } from "antd"; +import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Legend, PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart, Tooltip } from "recharts"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function ScheduleCalendarHeaderGraph({bodyshop, loadData}) { - const {ssbuckets} = bodyshop; - const {t} = useTranslation(); - const data = useMemo(() => { - return ( - (loadData && - loadData.expectedLoad && - Object.keys(loadData.expectedLoad).map((key) => { - const metadataBucket = ssbuckets.filter((b) => b.id === key)[0]; - - return { - bucket: loadData.expectedLoad[key].label, - current: loadData.expectedLoad[key].count, - target: metadataBucket && metadataBucket.target, - }; - })) || - [] - ); - }, [loadData, ssbuckets]); - - const popContent = ( -
    - - {t("appointments.labels.expectedprodhrs")} - {loadData?.expectedHours?.toFixed(1)} - {t("appointments.labels.expectedjobs")} - {loadData?.expectedJobCount} - - - - - - - - - - -
    - ); - +export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) { + const { ssbuckets } = bodyshop; + const { t } = useTranslation(); + const data = useMemo(() => { return ( - - - + (loadData && + loadData.expectedLoad && + Object.keys(loadData.expectedLoad).map((key) => { + const metadataBucket = ssbuckets.filter((b) => b.id === key)[0]; + + return { + bucket: loadData.expectedLoad[key].label, + current: loadData.expectedLoad[key].count, + target: metadataBucket && metadataBucket.target + }; + })) || + [] ); + }, [loadData, ssbuckets]); + + const popContent = ( +
    + + {t("appointments.labels.expectedprodhrs")} + {loadData?.expectedHours?.toFixed(1)} + {t("appointments.labels.expectedjobs")} + {loadData?.expectedJobCount} + + + + + + + + + + +
    + ); + + return ( + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScheduleCalendarHeaderGraph); +export default connect(mapStateToProps, mapDispatchToProps)(ScheduleCalendarHeaderGraph); diff --git a/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.jsx b/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.jsx index a91946e45..7acbd6668 100644 --- a/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.jsx +++ b/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.jsx @@ -1,226 +1,203 @@ import Icon from "@ant-design/icons"; -import {Popover, Space} from "antd"; +import { Popover, Space } from "antd"; import _ from "lodash"; import dayjs from "../../utils/day"; -import React, {useMemo} from "react"; -import {useTranslation} from "react-i18next"; -import {MdFileDownload, MdFileUpload} from "react-icons/md"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {selectScheduleLoad, selectScheduleLoadCalculating,} from "../../redux/application/application.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; +import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { MdFileDownload, MdFileUpload } from "react-icons/md"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { selectScheduleLoad, selectScheduleLoadCalculating } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import ScheduleBlockDay from "../schedule-block-day/schedule-block-day.component"; import ScheduleCalendarHeaderGraph from "./schedule-calendar-header-graph.component"; -import InstanceRenderMgr from '../../utils/instanceRenderMgr' +import InstanceRenderMgr from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - load: selectScheduleLoad, - calculating: selectScheduleLoadCalculating, + bodyshop: selectBodyshop, + load: selectScheduleLoad, + calculating: selectScheduleLoadCalculating }); const mapDispatchToProps = (dispatch) => ({}); export function ScheduleCalendarHeaderComponent({ - bodyshop, - label, - refetch, - date, - load, - calculating, - events, - ...otherProps - }) { - const ATSToday = useMemo(() => { - if (!events) return []; - return _.groupBy( - events.filter( - (e) => - !e.vacation && - e.isintake && - dayjs(date).isSame(dayjs(e.start), "day") - ), - "job.alt_transport" - ); - }, [events, date]); - - const isDayBlocked = useMemo(() => { - if (!events) return []; - return ( - events && - events.filter( - (e) => dayjs(date).isSame(dayjs(e.start), "day") && e.block - ) - ); - }, [events, date]); - - const {t} = useTranslation(); - const loadData = load[date.toISOString().substr(0, 10)]; - - const jobsOutPopup = () => ( -
    e.stopPropagation()}> -
    - - {loadData && loadData.allJobsOut ? ( - loadData.allJobsOut.map((j) => ( - - - - - - - )) - ) : ( - - - - )} - -
    - {j.ro_number} - - - - {`(${( - j.labhrs.aggregate.sum.mod_lb_hrs + - j.larhrs.aggregate.sum.mod_lb_hrs - ).toFixed(1)} ${t("general.labels.hours")})`} - - - {j.scheduled_completion} - -
    {t("appointments.labels.nocompletingjobs")}
    - + bodyshop, + label, + refetch, + date, + load, + calculating, + events, + ...otherProps +}) { + const ATSToday = useMemo(() => { + if (!events) return []; + return _.groupBy( + events.filter((e) => !e.vacation && e.isintake && dayjs(date).isSame(dayjs(e.start), "day")), + "job.alt_transport" ); + }, [events, date]); - const jobsInPopup = () => ( -
    e.stopPropagation()}> - - - {loadData && loadData.allJobsIn ? ( - loadData.allJobsIn.map((j) => ( - - - - - - - )) - ) : ( - - - - )} - -
    - {j.ro_number} - {j.status} - - - - {`(${( - j.labhrs.aggregate.sum.mod_lb_hrs + - j.larhrs.aggregate.sum.mod_lb_hrs - ).toFixed(1)} ${t("general.labels.hours")})`} - - {j.scheduled_in} -
    {t("appointments.labels.noarrivingjobs")}
    -
    - ); + const isDayBlocked = useMemo(() => { + if (!events) return []; + return events && events.filter((e) => dayjs(date).isSame(dayjs(e.start), "day") && e.block); + }, [events, date]); - const LoadComponent = loadData ? ( -
    - - - - {(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(2)} - - - - {(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(2)} - - - -
    -
      - {Object.keys(ATSToday).map((key, idx) => ( -
    • {`${ - key === "null" || key === "undefined" ? "N/A" : key - }: ${ATSToday[key].length}`}
    • - ))} -
    -
    -
    - ) : null; + const { t } = useTranslation(); + const loadData = load[date.toISOString().substr(0, 10)]; - const isShopOpen = (date) => { - let day; - switch (dayjs(date).day()) { - case 0: - day = "sunday"; - break; - case 1: - day = "monday"; - break; - case 2: - day = "tuesday"; - break; - case 3: - day = "wednesday"; - break; - case 4: - day = "thursday"; - break; - case 5: - day = "friday"; - break; - case 6: - day = "saturday"; - break; - default: - day = "sunday"; - break; - } + const jobsOutPopup = () => ( +
    e.stopPropagation()}> + + + {loadData && loadData.allJobsOut ? ( + loadData.allJobsOut.map((j) => ( + + + + + + + )) + ) : ( + + + + )} + +
    + {j.ro_number} + + + + {`(${(j.labhrs.aggregate.sum.mod_lb_hrs + j.larhrs.aggregate.sum.mod_lb_hrs).toFixed( + 1 + )} ${t("general.labels.hours")})`} + + {j.scheduled_completion} +
    {t("appointments.labels.nocompletingjobs")}
    +
    + ); - return bodyshop.workingdays[day]; - }; + const jobsInPopup = () => ( +
    e.stopPropagation()}> + + + {loadData && loadData.allJobsIn ? ( + loadData.allJobsIn.map((j) => ( + + + + + + + )) + ) : ( + + + + )} + +
    + {j.ro_number} + {j.status} + + + + {`(${(j.labhrs.aggregate.sum.mod_lb_hrs + j.larhrs.aggregate.sum.mod_lb_hrs).toFixed( + 1 + )} ${t("general.labels.hours")})`} + + {j.scheduled_in} +
    {t("appointments.labels.noarrivingjobs")}
    +
    + ); - return ( -
    - 0} - date={date} - refetch={refetch} + const LoadComponent = loadData ? ( +
    + + -
    - {label} - {InstanceRenderMgr({ - imex: calculating ? : LoadComponent, - rome: "USE_IMEX", - promanager: <>, - })} -
    - + + {(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(2)} +
    + + + {(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(2)} + + +
    +
    +
      + {Object.keys(ATSToday).map((key, idx) => ( +
    • {`${key === "null" || key === "undefined" ? "N/A" : key}: ${ATSToday[key].length}`}
    • + ))} +
    - ); +
    + ) : null; + + const isShopOpen = (date) => { + let day; + switch (dayjs(date).day()) { + case 0: + day = "sunday"; + break; + case 1: + day = "monday"; + break; + case 2: + day = "tuesday"; + break; + case 3: + day = "wednesday"; + break; + case 4: + day = "thursday"; + break; + case 5: + day = "friday"; + break; + case 6: + day = "saturday"; + break; + default: + day = "sunday"; + break; + } + + return bodyshop.workingdays[day]; + }; + + return ( +
    + 0} date={date} refetch={refetch}> +
    + {label} + {InstanceRenderMgr({ + imex: calculating ? : LoadComponent, + rome: "USE_IMEX", + promanager: <> + })} +
    +
    +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScheduleCalendarHeaderComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ScheduleCalendarHeaderComponent); diff --git a/client/src/components/schedule-calendar-wrapper/schedule-calendar-util.js b/client/src/components/schedule-calendar-wrapper/schedule-calendar-util.js index 263aab865..554c837db 100644 --- a/client/src/components/schedule-calendar-wrapper/schedule-calendar-util.js +++ b/client/src/components/schedule-calendar-wrapper/schedule-calendar-util.js @@ -1,29 +1,29 @@ import dayjs from "../../utils/day"; export function getRange(dateParam, viewParam) { - let start, end; - let date = dateParam || new Date(); - let view = viewParam || "week"; - // if view is day: from dayjs(date).startOf('day') to dayjs(date).endOf('day'); - if (view === "day") { - start = dayjs(date).startOf("day"); - end = dayjs(date).endOf("day"); - } - // if view is week: from dayjs(date).startOf('isoWeek') to dayjs(date).endOf('isoWeek'); - else if (view === "week") { - start = dayjs(date).startOf("week"); - end = dayjs(date).endOf("week"); - } - //if view is month: from dayjs(date).startOf('month').subtract(7, 'day') to dayjs(date).endOf('month').add(7, 'day'); i do additional 7 days math because you can see adjacent weeks on month view (that is the way how i generate my recurrent events for the Big Calendar, but if you need only start-end of month - just remove that math); - else if (view === "month") { - start = dayjs(date).startOf("month").subtract(7, "day"); - end = dayjs(date).endOf("month").add(7, "day"); - } - // if view is agenda: from dayjs(date).startOf('day') to dayjs(date).endOf('day').add(1, 'month'); - else if (view === "agenda") { - start = dayjs(date).startOf("day"); - end = dayjs(date).endOf("day").add(1, "month"); - } + let start, end; + let date = dateParam || new Date(); + let view = viewParam || "week"; + // if view is day: from dayjs(date).startOf('day') to dayjs(date).endOf('day'); + if (view === "day") { + start = dayjs(date).startOf("day"); + end = dayjs(date).endOf("day"); + } + // if view is week: from dayjs(date).startOf('isoWeek') to dayjs(date).endOf('isoWeek'); + else if (view === "week") { + start = dayjs(date).startOf("week"); + end = dayjs(date).endOf("week"); + } + //if view is month: from dayjs(date).startOf('month').subtract(7, 'day') to dayjs(date).endOf('month').add(7, 'day'); i do additional 7 days math because you can see adjacent weeks on month view (that is the way how i generate my recurrent events for the Big Calendar, but if you need only start-end of month - just remove that math); + else if (view === "month") { + start = dayjs(date).startOf("month").subtract(7, "day"); + end = dayjs(date).endOf("month").add(7, "day"); + } + // if view is agenda: from dayjs(date).startOf('day') to dayjs(date).endOf('day').add(1, 'month'); + else if (view === "agenda") { + start = dayjs(date).startOf("day"); + end = dayjs(date).endOf("day").add(1, "month"); + } - return {start, end}; + return { start, end }; } diff --git a/client/src/components/schedule-calendar-wrapper/scheduler-calendar-wrapper.component.jsx b/client/src/components/schedule-calendar-wrapper/scheduler-calendar-wrapper.component.jsx index 4acff1345..fc79cd4a9 100644 --- a/client/src/components/schedule-calendar-wrapper/scheduler-calendar-wrapper.component.jsx +++ b/client/src/components/schedule-calendar-wrapper/scheduler-calendar-wrapper.component.jsx @@ -17,7 +17,7 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, - problemJobs: selectProblemJobs, + problemJobs: selectProblemJobs }); const localizer = dayjsLocalizer(dayjs); @@ -39,14 +39,11 @@ export function ScheduleCalendarWrapperComponent({ ...(event.color && !((search.view || defaultView) === "agenda") ? { style: { - backgroundColor: - event.color && event.color.hex ? event.color.hex : event.color, - }, + backgroundColor: event.color && event.color.hex ? event.color.hex : event.color + } } : {}), - className: `${event.arrived ? "imex-event-arrived" : ""} ${ - event.block ? "imex-event-block" : "" - }`, + className: `${event.arrived ? "imex-event-arrived" : ""} ${event.block ? "imex-event-block" : ""}` }; }; @@ -61,11 +58,7 @@ export function ScheduleCalendarWrapperComponent({ - {t("appointments.labels.severalerrorsfound")} - - } + header={{t("appointments.labels.severalerrorsfound")}} > {problemJobs.map((problem) => ( @@ -75,15 +68,10 @@ export function ScheduleCalendarWrapperComponent({ message={ , - ]} + components={[]} values={{ ro_number: problem.ro_number, - code: problem.code, + code: problem.code }} /> } @@ -93,10 +81,7 @@ export function ScheduleCalendarWrapperComponent({ ) : ( - + {problemJobs.map((problem) => ( , - ]} + components={[]} values={{ ro_number: problem.ro_number, - code: problem.code, + code: problem.code }} /> } @@ -122,7 +102,7 @@ export function ScheduleCalendarWrapperComponent({ ), rome: "USE_IMEX", - promanager: , + promanager: })} - Event({ bodyshop: bodyshop, event: e.event, refetch: refetch }), - header: (p) => ( - - ), + event: (e) => Event({ bodyshop: bodyshop, event: e.event, refetch: refetch }), + header: (p) => }} {...otherProps} /> diff --git a/client/src/components/schedule-calendar/schedule-calendar.component.jsx b/client/src/components/schedule-calendar/schedule-calendar.component.jsx index a1c928f72..476094dd8 100644 --- a/client/src/components/schedule-calendar/schedule-calendar.component.jsx +++ b/client/src/components/schedule-calendar/schedule-calendar.component.jsx @@ -1,8 +1,8 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Checkbox, Col, Row, Select, Space,} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; -import {t} from "i18next"; -import React, {useMemo} from "react"; +import { SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Checkbox, Col, Row, Select, Space } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; +import { t } from "i18next"; +import React, { useMemo } from "react"; import useLocalStorage from "../../utils/useLocalStorage"; import ScheduleAtsSummary from "../schedule-ats-summary/schedule-ats-summary.component"; import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component"; @@ -10,164 +10,142 @@ import ScheduleModal from "../schedule-job-modal/schedule-job-modal.container"; import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component"; import ScheduleProductionList from "../schedule-production-list/schedule-production-list.component"; import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verify-integrity.component"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import _ from "lodash"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScheduleCalendarComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ScheduleCalendarComponent); -export function ScheduleCalendarComponent({data, refetch, bodyshop}) { - const [filter, setFilter] = useLocalStorage("filter_events", { - intake: true, - manual: true, - employeevacation: true, - ins_co_nm: null, +export function ScheduleCalendarComponent({ data, refetch, bodyshop }) { + const [filter, setFilter] = useLocalStorage("filter_events", { + intake: true, + manual: true, + employeevacation: true, + ins_co_nm: null + }); + const [estimatorsFilter, setEstimatiorsFilter] = useLocalStorage("estimators", []); + + const estimators = useMemo(() => { + return _.uniq([ + ...data + .filter((d) => d.__typename === "appointments") + .map((app) => `${app.job?.est_ct_fn || ""} ${app.job?.est_ct_ln || ""}`.trim()) + .filter((e) => e.length > 0), + ...bodyshop.md_estimators.map((e) => `${e.est_ct_fn || ""} ${e.est_ct_ln || ""}`.trim()) + ]); + }, [data, bodyshop.md_estimators]); + + const filteredData = useMemo(() => { + return data.filter((d) => { + const estFilter = + d.__typename === "appointments" + ? estimatorsFilter.length === 0 + ? true + : !!estimatorsFilter.find((e) => e === `${d.job?.est_ct_fn || ""} ${d.job?.est_ct_ln || ""}`.trim()) + : true; + + return ( + (d.block || + (filter.intake && d.isintake) || + (filter.manual && !d.isintake && d.block === false) || + (d.__typename === "employee_vacation" && filter.employeevacation && !!d.employee)) && + (filter.ins_co_nm && filter.ins_co_nm.length > 0 ? filter.ins_co_nm.includes(d.job?.ins_co_nm) : true) && + estFilter + ); }); - const [estimatorsFilter, setEstimatiorsFilter] = useLocalStorage( - "estimators", - [] - ); + }, [data, filter, estimatorsFilter]); - const estimators = useMemo(() => { - return _.uniq([ - ...data - .filter((d) => d.__typename === "appointments") - .map((app) => - `${app.job?.est_ct_fn || ""} ${app.job?.est_ct_ln || ""}`.trim() - ) - .filter((e) => e.length > 0), - ...bodyshop.md_estimators.map((e) => - `${e.est_ct_fn || ""} ${e.est_ct_ln || ""}`.trim() - ), - ]); - }, [data, bodyshop.md_estimators]); + return ( + + - const filteredData = useMemo(() => { - return data.filter((d) => { - const estFilter = - d.__typename === "appointments" - ? estimatorsFilter.length === 0 - ? true - : !!estimatorsFilter.find( - (e) => - e === - `${d.job?.est_ct_fn || ""} ${d.job?.est_ct_ln || ""}`.trim() - ) - : true; + + + + setFilter({ ...filter, ins_co_nm: [] })} + value={filter?.ins_co_nm ? filter.ins_co_nm : []} + onChange={(e) => { + setFilter({ ...filter, ins_co_nm: e }); + }} + options={bodyshop.md_ins_cos.map((i) => ({ + label: i.name, + value: i.name + }))} + /> + { + setFilter({ ...filter, intake: e.target.checked }); + }} + > + {t("schedule.labels.intake")} + + { + setFilter({ ...filter, manual: e.target.checked }); + }} + > + {t("schedule.labels.manual")} + + { + setFilter({ ...filter, employeevacation: e.target.checked }); + }} + > + {t("schedule.labels.employeevacation")} + + + + - return ( - (d.block || - (filter.intake && d.isintake) || - (filter.manual && !d.isintake && d.block === false) || - (d.__typename === "employee_vacation" && - filter.employeevacation && - !!d.employee)) && - (filter.ins_co_nm && filter.ins_co_nm.length > 0 - ? filter.ins_co_nm.includes(d.job?.ins_co_nm) - : true) && - estFilter - ); - }); - }, [data, filter, estimatorsFilter]); + + + } + /> + - return ( - - - - - - - setFilter({...filter, ins_co_nm: []})} - value={filter?.ins_co_nm ? filter.ins_co_nm : []} - onChange={(e) => { - setFilter({...filter, ins_co_nm: e}); - }} - options={bodyshop.md_ins_cos.map((i) => ({ - label: i.name, - value: i.name, - }))} - /> - { - setFilter({...filter, intake: e.target.checked}); - }} - > - {t("schedule.labels.intake")} - - { - setFilter({...filter, manual: e.target.checked}); - }} - > - {t("schedule.labels.manual")} - - { - setFilter({...filter, employeevacation: e.target.checked}); - }} - > - {t("schedule.labels.employeevacation")} - - - - - - - - } - /> - - - - - - - - - ); + + + + + + + ); } diff --git a/client/src/components/schedule-calendar/schedule-calendar.container.jsx b/client/src/components/schedule-calendar/schedule-calendar.container.jsx index 979f5f25c..8bf50ca7f 100644 --- a/client/src/components/schedule-calendar/schedule-calendar.container.jsx +++ b/client/src/components/schedule-calendar/schedule-calendar.container.jsx @@ -1,86 +1,70 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import queryString from "query-string"; -import React, {useEffect, useMemo} from "react"; -import {useLocation} from "react-router-dom"; -import {QUERY_ALL_ACTIVE_APPOINTMENTS} from "../../graphql/appointments.queries"; +import React, { useEffect, useMemo } from "react"; +import { useLocation } from "react-router-dom"; +import { QUERY_ALL_ACTIVE_APPOINTMENTS } from "../../graphql/appointments.queries"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -import {getRange} from "../schedule-calendar-wrapper/schedule-calendar-util"; +import { getRange } from "../schedule-calendar-wrapper/schedule-calendar-util"; import ScheduleCalendarComponent from "./schedule-calendar.component"; -import {calculateScheduleLoad} from "../../redux/application/application.actions"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; +import { calculateScheduleLoad } from "../../redux/application/application.actions"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; import dayjs from "../../utils/day"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser + //currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - calculateScheduleLoad: (endDate) => dispatch(calculateScheduleLoad(endDate)), + calculateScheduleLoad: (endDate) => dispatch(calculateScheduleLoad(endDate)) }); -export function ScheduleCalendarContainer({calculateScheduleLoad}) { - const search = queryString.parse(useLocation().search); +export function ScheduleCalendarContainer({ calculateScheduleLoad }) { + const search = queryString.parse(useLocation().search); - const {date, view} = search; - const range = useMemo(() => getRange(date, view), [date, view]); + const { date, view } = search; + const range = useMemo(() => getRange(date, view), [date, view]); - const {loading, error, data, refetch} = useQuery( - QUERY_ALL_ACTIVE_APPOINTMENTS, - { - variables: { - start: range.start.toDate(), - end: range.end.toDate(), - startd: range.start, - endd: range.end, - }, - skip: !!!range.start || !!!range.end, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - } - ); + const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_APPOINTMENTS, { + variables: { + start: range.start.toDate(), + end: range.end.toDate(), + startd: range.start, + endd: range.end + }, + skip: !!!range.start || !!!range.end, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - useEffect(() => { - if (data && range.end) calculateScheduleLoad(range.end); - }, [data, range, calculateScheduleLoad]); + useEffect(() => { + if (data && range.end) calculateScheduleLoad(range.end); + }, [data, range, calculateScheduleLoad]); - if (loading) return ; - if (error) return ; - let normalizedData = [ - ...data.appointments.map((e) => { - //Required because Hasura returns a string instead of a date object. - return Object.assign( - {}, - e, - {start: new Date(e.start)}, - {end: new Date(e.end)} - ); - }), - ...data.employee_vacation.map((e) => { - //Required because Hasura returns a string instead of a date object. - return { - ...e, - title: `${ - (e.employee.first_name && e.employee.first_name.substr(0, 1)) || "" - } ${e.employee.last_name || ""} OUT`, - color: "red", - start: dayjs(e.start).startOf("day").toDate(), - end: dayjs(e.end).startOf("day").toDate(), - allDay: true, - vacation: true, - }; - }), - ]; + if (loading) return ; + if (error) return ; + let normalizedData = [ + ...data.appointments.map((e) => { + //Required because Hasura returns a string instead of a date object. + return Object.assign({}, e, { start: new Date(e.start) }, { end: new Date(e.end) }); + }), + ...data.employee_vacation.map((e) => { + //Required because Hasura returns a string instead of a date object. + return { + ...e, + title: `${ + (e.employee.first_name && e.employee.first_name.substr(0, 1)) || "" + } ${e.employee.last_name || ""} OUT`, + color: "red", + start: dayjs(e.start).startOf("day").toDate(), + end: dayjs(e.end).startOf("day").toDate(), + allDay: true, + vacation: true + }; + }) + ]; - return ( - - ); + return ; } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScheduleCalendarContainer); +export default connect(mapStateToProps, mapDispatchToProps)(ScheduleCalendarContainer); diff --git a/client/src/components/schedule-day-view/schedule-day-view.component.jsx b/client/src/components/schedule-day-view/schedule-day-view.component.jsx index cd1eaa856..0200fe4c1 100644 --- a/client/src/components/schedule-day-view/schedule-day-view.component.jsx +++ b/client/src/components/schedule-day-view/schedule-day-view.component.jsx @@ -1,18 +1,10 @@ import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component"; -export default function ScheduleDayViewComponent({data, day}) { - const {t} = useTranslation(); - if (data) - return ( - - ); - else return
    {t("appointments.labels.nodateselected")}
    ; +export default function ScheduleDayViewComponent({ data, day }) { + const { t } = useTranslation(); + if (data) + return ; + else return
    {t("appointments.labels.nodateselected")}
    ; } diff --git a/client/src/components/schedule-day-view/schedule-day-view.container.jsx b/client/src/components/schedule-day-view/schedule-day-view.container.jsx index 1c43f5774..5b7d6d152 100644 --- a/client/src/components/schedule-day-view/schedule-day-view.container.jsx +++ b/client/src/components/schedule-day-view/schedule-day-view.container.jsx @@ -1,57 +1,50 @@ import React from "react"; import ScheduleDayViewComponent from "./schedule-day-view.component"; -import {useQuery} from "@apollo/client"; -import {QUERY_APPOINTMENT_BY_DATE} from "../../graphql/appointments.queries"; +import { useQuery } from "@apollo/client"; +import { QUERY_APPOINTMENT_BY_DATE } from "../../graphql/appointments.queries"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import dayjs from "../../utils/day"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; -export default function ScheduleDayViewContainer({day}) { - const {loading, error, data} = useQuery(QUERY_APPOINTMENT_BY_DATE, { - variables: { - start: dayjs(day).startOf("day"), - end: dayjs(day).endOf("day"), - startd: dayjs(day).startOf("day").format("YYYY-MM-DD"), - endd: dayjs(day).add(1, "day").format("YYYY-MM-DD"), - }, - skip: !dayjs(day).isValid(), - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); - const {t} = useTranslation(); - if (!day) return
    {t("appointments.labels.nodateselected")}
    ; - if (loading) return ; - if (error) return
    {error.message}
    ; - let normalizedData; +export default function ScheduleDayViewContainer({ day }) { + const { loading, error, data } = useQuery(QUERY_APPOINTMENT_BY_DATE, { + variables: { + start: dayjs(day).startOf("day"), + end: dayjs(day).endOf("day"), + startd: dayjs(day).startOf("day").format("YYYY-MM-DD"), + endd: dayjs(day).add(1, "day").format("YYYY-MM-DD") + }, + skip: !dayjs(day).isValid(), + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const { t } = useTranslation(); + if (!day) return
    {t("appointments.labels.nodateselected")}
    ; + if (loading) return ; + if (error) return
    {error.message}
    ; + let normalizedData; - if (data) { - normalizedData = [ - ...data.appointments.map((e) => { - //Required becuase Hasura returns a string instead of a date object. - return Object.assign( - {}, - e, - {start: new Date(e.start)}, - {end: new Date(e.end)} - ); - }), - ...data.employee_vacation.map((e) => { - //Required becuase Hasura returns a string instead of a date object. - return { - ...e, - title: `${ - (e.employee.first_name && e.employee.first_name.substr(0, 1)) || "" - } ${e.employee.last_name || ""} OUT`, - color: "red", - start: dayjs(e.start).startOf("day").toDate(), - end: dayjs(e.end).startOf("day").toDate(), - vacation: true, - }; - }), - ]; - } + if (data) { + normalizedData = [ + ...data.appointments.map((e) => { + //Required becuase Hasura returns a string instead of a date object. + return Object.assign({}, e, { start: new Date(e.start) }, { end: new Date(e.end) }); + }), + ...data.employee_vacation.map((e) => { + //Required becuase Hasura returns a string instead of a date object. + return { + ...e, + title: `${ + (e.employee.first_name && e.employee.first_name.substr(0, 1)) || "" + } ${e.employee.last_name || ""} OUT`, + color: "red", + start: dayjs(e.start).startOf("day").toDate(), + end: dayjs(e.end).startOf("day").toDate(), + vacation: true + }; + }) + ]; + } - return ( - - ); + return ; } diff --git a/client/src/components/schedule-existing-appointments-list/schedule-existing-appointments-list.component.jsx b/client/src/components/schedule-existing-appointments-list/schedule-existing-appointments-list.component.jsx index cdd16431a..41d563c35 100644 --- a/client/src/components/schedule-existing-appointments-list/schedule-existing-appointments-list.component.jsx +++ b/client/src/components/schedule-existing-appointments-list/schedule-existing-appointments-list.component.jsx @@ -1,45 +1,38 @@ import React from "react"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import AlertComponent from "../alert/alert.component"; -import {Timeline} from "antd"; -import {useTranslation} from "react-i18next"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; +import { Timeline } from "antd"; +import { useTranslation } from "react-i18next"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; -export default function ScheduleExistingAppointmentsList({ - existingAppointments, - }) { - const {t} = useTranslation(); - if (existingAppointments.loading) return ; - if (existingAppointments.error) - return ( - - ); +export default function ScheduleExistingAppointmentsList({ existingAppointments }) { + const { t } = useTranslation(); + if (existingAppointments.loading) return ; + if (existingAppointments.error) return ; - return ( -
    - {t("appointments.labels.priorappointments")} - ({ - key: item.id, - color: item.canceled ? "red" : item.arrived ? "green" : "blue", - children: ( - <> - {item.canceled - ? t("appointments.labels.cancelledappointment") - : item.arrived - ? t("appointments.labels.arrivedon") - : t("appointments.labels.scheduledfor")} - {item.start} - - ), - })) - : [] - } - />
    - ); + return ( +
    + {t("appointments.labels.priorappointments")} + ({ + key: item.id, + color: item.canceled ? "red" : item.arrived ? "green" : "blue", + children: ( + <> + {item.canceled + ? t("appointments.labels.cancelledappointment") + : item.arrived + ? t("appointments.labels.arrivedon") + : t("appointments.labels.scheduledfor")} + {item.start} + + ) + })) + : [] + } + /> +
    + ); } diff --git a/client/src/components/schedule-job-modal/schedule-job-modal.component.jsx b/client/src/components/schedule-job-modal/schedule-job-modal.component.jsx index 721c2d870..ea942e5d2 100644 --- a/client/src/components/schedule-job-modal/schedule-job-modal.component.jsx +++ b/client/src/components/schedule-job-modal/schedule-job-modal.component.jsx @@ -1,217 +1,188 @@ -import {Button, Col, Form, Input, Row, Select, Space, Switch, Typography,} from "antd"; +import { Button, Col, Form, Input, Row, Select, Space, Switch, Typography } from "antd"; import axios from "axios"; import dayjs from "../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {calculateScheduleLoad} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {DateFormatter} from "../../utils/DateFormatter"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { calculateScheduleLoad } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { DateFormatter } from "../../utils/DateFormatter"; import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; import EmailInput from "../form-items-formatted/email-form-item.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import ScheduleDayViewContainer from "../schedule-day-view/schedule-day-view.container"; -import ScheduleExistingAppointmentsList - from "../schedule-existing-appointments-list/schedule-existing-appointments-list.component"; +import ScheduleExistingAppointmentsList from "../schedule-existing-appointments-list/schedule-existing-appointments-list.component"; import "./schedule-job-modal.scss"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) - calculateScheduleLoad: (endDate) => dispatch(calculateScheduleLoad(endDate)), + //setUserLanguage: language => dispatch(setUserLanguage(language)) + calculateScheduleLoad: (endDate) => dispatch(calculateScheduleLoad(endDate)) }); export function ScheduleJobModalComponent({ - bodyshop, - form, - existingAppointments, - lbrHrsData, - jobId, - calculateScheduleLoad, - }) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const [smartOptions, setSmartOptions] = useState([]); + bodyshop, + form, + existingAppointments, + lbrHrsData, + jobId, + calculateScheduleLoad +}) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [smartOptions, setSmartOptions] = useState([]); - const handleSmartScheduling = async () => { - setLoading(true); - try { - const response = await axios.post("/scheduling/job", { - jobId: jobId, - }); - if (response.data) setSmartOptions(response.data); - } catch (error) { - console.log("error", error, error.message); - } finally { - setLoading(false); - } - }; + const handleSmartScheduling = async () => { + setLoading(true); + try { + const response = await axios.post("/scheduling/job", { + jobId: jobId + }); + if (response.data) setSmartOptions(response.data); + } catch (error) { + console.log("error", error, error.message); + } finally { + setLoading(false); + } + }; - const handleDateBlur = () => { - const values = form.getFieldsValue(); + const handleDateBlur = () => { + const values = form.getFieldsValue(); - if (lbrHrsData) { - const totalHours = - lbrHrsData.jobs_by_pk.labhrs.aggregate.sum.mod_lb_hrs + - lbrHrsData.jobs_by_pk.larhrs.aggregate.sum.mod_lb_hrs; + if (lbrHrsData) { + const totalHours = + lbrHrsData.jobs_by_pk.labhrs.aggregate.sum.mod_lb_hrs + lbrHrsData.jobs_by_pk.larhrs.aggregate.sum.mod_lb_hrs; - if (values.start && !values.scheduled_completion) - form.setFieldsValue({ - scheduled_completion: dayjs(values.start).businessDaysAdd( - totalHours / bodyshop.target_touchtime, - "day" - ), - }); - } - }; + if (values.start && !values.scheduled_completion) + form.setFieldsValue({ + scheduled_completion: dayjs(values.start).businessDaysAdd(totalHours / bodyshop.target_touchtime, "day") + }); + } + }; - return ( - - - - - {lbrHrsData?.jobs_by_pk?.ro_number} - - {`B/R Hrs:${ - lbrHrsData?.jobs_by_pk.labhrs?.aggregate?.sum?.mod_lb_hrs || 0 - }/${ - lbrHrsData?.jobs_by_pk.larhrs?.aggregate?.sum?.mod_lb_hrs || 0 - }`} - - - - - - - - - - {InstanceRenderManager({ - imex: ( - <> - - {t("appointments.labels.smartscheduling")} - - - - {smartOptions.map((d, idx) => ( - - ))} - - - ), - rome: "USE_IMEX", - promanager: <>, - })} - - - - - - - - - - - - - - - - - - - - - - {t("appointments.labels.history")} - - - - prev.start !== cur.start}> - {() => { - const values = form.getFieldsValue(); - if (values.start) { - calculateScheduleLoad(dayjs(values.start).add(3, "day")); + return ( + + + + {lbrHrsData?.jobs_by_pk?.ro_number} + {`B/R Hrs:${ + lbrHrsData?.jobs_by_pk.labhrs?.aggregate?.sum?.mod_lb_hrs || 0 + }/${lbrHrsData?.jobs_by_pk.larhrs?.aggregate?.sum?.mod_lb_hrs || 0}`} + + + - -
    - ); - }} + ]} + > + - - - ); + + + + + {InstanceRenderManager({ + imex: ( + <> + {t("appointments.labels.smartscheduling")} + + + {smartOptions.map((d, idx) => ( + + ))} + + + ), + rome: "USE_IMEX", + promanager: <> + })} + + + + + + + + + + + + + + + + + + + + + + {t("appointments.labels.history")} + + + + prev.start !== cur.start}> + {() => { + const values = form.getFieldsValue(); + if (values.start) { + calculateScheduleLoad(dayjs(values.start).add(3, "day")); + } + return ( +
    + +
    + ); + }} +
    + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScheduleJobModalComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ScheduleJobModalComponent); diff --git a/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx b/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx index 316497833..eac29d274 100644 --- a/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx +++ b/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx @@ -1,246 +1,241 @@ -import {useMutation, useQuery} from "@apollo/client"; -import {Form, Modal, notification} from "antd"; +import { useMutation, useQuery } from "@apollo/client"; +import { Form, Modal, notification } from "antd"; import dayjs from "../../utils/day"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import { - CANCEL_APPOINTMENT_BY_ID, - INSERT_APPOINTMENT, - QUERY_APPOINTMENTS_BY_JOBID, + CANCEL_APPOINTMENT_BY_ID, + INSERT_APPOINTMENT, + QUERY_APPOINTMENTS_BY_JOBID } from "../../graphql/appointments.queries"; -import {QUERY_LBR_HRS_BY_PK, UPDATE_JOBS} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {setEmailOptions} from "../../redux/email/email.actions"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectSchedule} from "../../redux/modals/modals.selectors"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { QUERY_LBR_HRS_BY_PK, UPDATE_JOBS } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { setEmailOptions } from "../../redux/email/email.actions"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectSchedule } from "../../redux/modals/modals.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; -import {DateTimeFormat} from "../../utils/DateFormatter"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { DateTimeFormat } from "../../utils/DateFormatter"; +import { TemplateList } from "../../utils/TemplateConstants"; import ScheduleJobModalComponent from "./schedule-job-modal.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - scheduleModal: selectSchedule, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + scheduleModal: selectSchedule, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("schedule")), - setEmailOptions: (e) => dispatch(setEmailOptions(e)), - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + toggleModalVisible: () => dispatch(toggleModalVisible("schedule")), + setEmailOptions: (e) => dispatch(setEmailOptions(e)), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); export function ScheduleJobModalContainer({ - scheduleModal, - bodyshop, - toggleModalVisible, - setEmailOptions, - currentUser, - insertAuditTrail, - }) { - const {open, context, actions} = scheduleModal; - const {jobId, job, previousEvent} = context; + scheduleModal, + bodyshop, + toggleModalVisible, + setEmailOptions, + currentUser, + insertAuditTrail +}) { + const { open, context, actions } = scheduleModal; + const { jobId, job, previousEvent } = context; - const {refetch} = actions; - const [form] = Form.useForm(); + const { refetch } = actions; + const [form] = Form.useForm(); - const {data: lbrHrsData} = useQuery(QUERY_LBR_HRS_BY_PK, { - variables: {id: job && job.id}, - skip: !job || !job.id, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", + const { data: lbrHrsData } = useQuery(QUERY_LBR_HRS_BY_PK, { + variables: { id: job && job.id }, + skip: !job || !job.id, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + + const [loading, setLoading] = useState(false); + const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID); + const [insertAppointment] = useMutation(INSERT_APPOINTMENT); + const [updateJobStatus] = useMutation(UPDATE_JOBS); + + useEffect(() => { + if (job) form.resetFields(); + }, [job, form]); + + const { t } = useTranslation(); + + const existingAppointments = useQuery(QUERY_APPOINTMENTS_BY_JOBID, { + variables: { jobid: jobId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + skip: !open || !!!jobId + }); + + useEffect(() => { + if ( + existingAppointments.data && + existingAppointments.data.appointments.length > 0 && + !existingAppointments.data.appointments[0].canceled + ) { + form.setFieldsValue({ + color: existingAppointments.data.appointments[0].color, + + note: existingAppointments.data.appointments[0].note + }); + } + }, [existingAppointments.data, form]); + + const handleFinish = async (values) => { + logImEXEvent("schedule_new_appointment"); + + setLoading(true); + if (!!previousEvent) { + const cancelAppt = await cancelAppointment({ + variables: { appid: previousEvent } + }); + + if (!!cancelAppt.errors) { + notification["error"]({ + message: t("appointments.errors.canceling", { + message: JSON.stringify(cancelAppt.errors) + }) + }); + return; + } + + notification["success"]({ + message: t("appointments.successes.canceled") + }); + } + + if (existingAppointments.data.appointments.length > 0) { + await Promise.all( + existingAppointments.data.appointments.map((app) => { + return cancelAppointment({ + variables: { appid: app.id } + }); + }) + ); + } + + const appt = await insertAppointment({ + variables: { + app: { + jobid: jobId, + bodyshopid: bodyshop.id, + start: dayjs(values.start), + end: dayjs(values.start).add(bodyshop.appt_length || 60, "minute"), + color: values.color, + note: values.note, + created_by: currentUser.email + }, + jobId: jobId, + altTransport: values.alt_transport + } }); - const [loading, setLoading] = useState(false); - const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID); - const [insertAppointment] = useMutation(INSERT_APPOINTMENT); - const [updateJobStatus] = useMutation(UPDATE_JOBS); + if (!appt.errors) { + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.appointmentinsert(DateTimeFormat(values.start)), + type: "appointmentinsert" + }); + } - useEffect(() => { - if (job) form.resetFields(); - }, [job, form]); - - const {t} = useTranslation(); - - const existingAppointments = useQuery(QUERY_APPOINTMENTS_BY_JOBID, { - variables: {jobid: jobId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - skip: !open || !!!jobId, + if (!!appt.errors) { + notification["error"]({ + message: t("appointments.errors.saving", { + message: JSON.stringify(appt.errors) + }) + }); + return; + } + notification["success"]({ + message: t("appointments.successes.created") }); - - useEffect(() => { - if ( - existingAppointments.data && - existingAppointments.data.appointments.length > 0 && - !existingAppointments.data.appointments[0].canceled - ) { - form.setFieldsValue({ - color: existingAppointments.data.appointments[0].color, - - note: existingAppointments.data.appointments[0].note, - }); + if (jobId) { + const jobUpdate = await updateJobStatus({ + variables: { + jobIds: [jobId], + fields: { + status: bodyshop.md_ro_statuses.default_scheduled, + date_scheduled: new Date(), + scheduled_in: values.start, + scheduled_completion: values.scheduled_completion, + lost_sale_reason: null, + date_lost_sale: null + } } - }, [existingAppointments.data, form]); + }); - const handleFinish = async (values) => { - logImEXEvent("schedule_new_appointment"); - - setLoading(true); - if (!!previousEvent) { - const cancelAppt = await cancelAppointment({ - variables: {appid: previousEvent}, - }); - - if (!!cancelAppt.errors) { - notification["error"]({ - message: t("appointments.errors.canceling", { - message: JSON.stringify(cancelAppt.errors), - }), - }); - return; - } - - notification["success"]({ - message: t("appointments.successes.canceled"), - }); - } - - if (existingAppointments.data.appointments.length > 0) { - await Promise.all( - existingAppointments.data.appointments.map((app) => { - return cancelAppointment({ - variables: {appid: app.id}, - }); - }) - ); - } - - const appt = await insertAppointment({ - variables: { - app: { - jobid: jobId, - bodyshopid: bodyshop.id, - start: dayjs(values.start), - end: dayjs(values.start).add(bodyshop.appt_length || 60, "minute"), - color: values.color, - note: values.note, - created_by: currentUser.email, - }, - jobId: jobId, - altTransport: values.alt_transport, - }, + if (!!jobUpdate.errors) { + notification["error"]({ + message: t("appointments.errors.saving", { + message: JSON.stringify(jobUpdate.errors) + }) }); - - if (!appt.errors) { - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.appointmentinsert( - DateTimeFormat(values.start) - ), - type: "appointmentinsert",}); + return; + } + } + setLoading(false); + toggleModalVisible(); + if (values.notifyCustomer) { + setEmailOptions({ + jobid: jobId, + messageOptions: { + to: [values.email], + replyTo: bodyshop.email, + subject: TemplateList("appointment").appointment_confirmation.subject + }, + template: { + name: TemplateList("appointment").appointment_confirmation.key, + variables: { + id: appt.data.insert_appointments.returning[0].id + } } + }); + } + if (refetch) refetch(); + }; - if (!!appt.errors) { - notification["error"]({ - message: t("appointments.errors.saving", { - message: JSON.stringify(appt.errors), - }), - }); - return; - } - notification["success"]({ - message: t("appointments.successes.created"), - }); - if (jobId) { - const jobUpdate = await updateJobStatus({ - variables: { - jobIds: [jobId], - fields: { - status: bodyshop.md_ro_statuses.default_scheduled, - date_scheduled: new Date(), - scheduled_in: values.start, - scheduled_completion: values.scheduled_completion, - lost_sale_reason: null, - date_lost_sale: null, - }, - }, - }); - - if (!!jobUpdate.errors) { - notification["error"]({ - message: t("appointments.errors.saving", { - message: JSON.stringify(jobUpdate.errors), - }), - }); - return; - } - } - setLoading(false); - toggleModalVisible(); - if (values.notifyCustomer) { - setEmailOptions({ - jobid: jobId, - messageOptions: { - to: [values.email], - replyTo: bodyshop.email, - subject: TemplateList("appointment").appointment_confirmation.subject, - }, - template: { - name: TemplateList("appointment").appointment_confirmation.key, - variables: { - id: appt.data.insert_appointments.returning[0].id, - }, - }, - }); - } - if (refetch) refetch(); - }; - - return ( - toggleModalVisible()} - onOk={() => form.submit()} - width={"90%"} - maskClosable={false} - destroyOnClose - okButtonProps={{ - loading: loading, - }} - closable={false} - > -
    - - -
    - ); + return ( + toggleModalVisible()} + onOk={() => form.submit()} + width={"90%"} + maskClosable={false} + destroyOnClose + okButtonProps={{ + loading: loading + }} + closable={false} + > +
    + + +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScheduleJobModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(ScheduleJobModalContainer); diff --git a/client/src/components/schedule-manual-event/schedule-manual-event.component.jsx b/client/src/components/schedule-manual-event/schedule-manual-event.component.jsx index 1d41163da..e6bb2896d 100644 --- a/client/src/components/schedule-manual-event/schedule-manual-event.component.jsx +++ b/client/src/components/schedule-manual-event/schedule-manual-event.component.jsx @@ -1,163 +1,154 @@ -import {useMutation} from "@apollo/client"; -import {Button, Card, Form, Input, Popover, Select, Space} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Card, Form, Input, Popover, Select, Space } from "antd"; import dayjs from "../../utils/day"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {INSERT_MANUAL_APPT, UPDATE_APPOINTMENT,} from "../../graphql/appointments.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { INSERT_MANUAL_APPT, UPDATE_APPOINTMENT } from "../../graphql/appointments.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScheduleManualEvent); +export default connect(mapStateToProps, mapDispatchToProps)(ScheduleManualEvent); -export function ScheduleManualEvent({bodyshop, event}) { - const {t} = useTranslation(); - const [insertAppointment] = useMutation(INSERT_MANUAL_APPT); - const [updateAppointment] = useMutation(UPDATE_APPOINTMENT); - const [loading, setLoading] = useState(false); - const [form] = Form.useForm(); - const [visibility, setVisibility] = useState(false); - // const [callQuery, { loading: entryLoading, data: entryData }] = useLazyQuery( - // QUERY_SCOREBOARD_ENTRY - // ); +export function ScheduleManualEvent({ bodyshop, event }) { + const { t } = useTranslation(); + const [insertAppointment] = useMutation(INSERT_MANUAL_APPT); + const [updateAppointment] = useMutation(UPDATE_APPOINTMENT); + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + const [visibility, setVisibility] = useState(false); + // const [callQuery, { loading: entryLoading, data: entryData }] = useLazyQuery( + // QUERY_SCOREBOARD_ENTRY + // ); - useEffect(() => { - if (visibility && event) { - form.setFieldsValue(event); - } - }, [visibility, form, event]); + useEffect(() => { + if (visibility && event) { + form.setFieldsValue(event); + } + }, [visibility, form, event]); - const handleFinish = async (values) => { - logImEXEvent("schedule_manual_event"); + const handleFinish = async (values) => { + logImEXEvent("schedule_manual_event"); - setLoading(true); - try { - if (event && event.id) { - updateAppointment({ - variables: {appid: event.id, app: values}, - refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"], - }); - } else { - insertAppointment({ - variables: { - apt: {...values, isintake: false, bodyshopid: bodyshop.id}, - }, - refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"], - }); - } - form.resetFields(); - setVisibility(false); - } catch (error) { - console.log(error); - } finally { - setLoading(false); - } - }; + setLoading(true); + try { + if (event && event.id) { + updateAppointment({ + variables: { appid: event.id, app: values }, + refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"] + }); + } else { + insertAppointment({ + variables: { + apt: { ...values, isintake: false, bodyshopid: bodyshop.id } + }, + refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"] + }); + } + form.resetFields(); + setVisibility(false); + } catch (error) { + console.log(error); + } finally { + setLoading(false); + } + }; - const overlay = ( - -
    -
    - - - - - - - - - - ({ - async validator(rule, value) { - if (value) { - const {start} = form.getFieldsValue(); - if (dayjs(start).isAfter(dayjs(value))) { - return Promise.reject( - t("employees.labels.endmustbeafterstart") - ); - } else { - return Promise.resolve(); - } - } else { - return Promise.resolve(); - } - }, - }), - ]} - > - - - - - + const overlay = ( + +
    + + + + + + + + + + + ({ + async validator(rule, value) { + if (value) { + const { start } = form.getFieldsValue(); + if (dayjs(start).isAfter(dayjs(value))) { + return Promise.reject(t("employees.labels.endmustbeafterstart")); + } else { + return Promise.resolve(); + } + } else { + return Promise.resolve(); + } + } + }) + ]} + > + + + + + - - - - - -
    -
    - ); - - const handleClick = (e) => { - setVisibility(true); - }; - - return ( - - - - ); + + + +
    +
    + ); + + const handleClick = (e) => { + setVisibility(true); + }; + + return ( + + + + ); } diff --git a/client/src/components/schedule-production-list/schedule-production-list.component.jsx b/client/src/components/schedule-production-list/schedule-production-list.component.jsx index b70f562f8..b4e5dcf31 100644 --- a/client/src/components/schedule-production-list/schedule-production-list.component.jsx +++ b/client/src/components/schedule-production-list/schedule-production-list.component.jsx @@ -1,71 +1,62 @@ -import {DownOutlined} from "@ant-design/icons"; -import {Button, Card, Popover} from "antd"; +import { DownOutlined } from "@ant-design/icons"; +import { Button, Card, Popover } from "antd"; import React from "react"; -import {useLazyQuery} from "@apollo/client"; -import {useTranslation} from "react-i18next"; -import {Link} from "react-router-dom"; -import {QUERY_JOBS_IN_PRODUCTION} from "../../graphql/jobs.queries"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; +import { useLazyQuery } from "@apollo/client"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; +import { QUERY_JOBS_IN_PRODUCTION } from "../../graphql/jobs.queries"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; import AlertComponent from "../alert/alert.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import "./schedule-production-list.styles.scss"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; export default function ScheduleProductionList() { - const {t} = useTranslation(); - const [callQuery, {loading, error, data}] = useLazyQuery( - QUERY_JOBS_IN_PRODUCTION - ); - - const content = () => { - return ( - -
    e.stopPropagation()} - className="jobs-in-production-table" - > - {loading ? : null} - {error ? ( - - ) : null} - {data ? ( - - - {data && data.jobs - ? data.jobs.map((j) => ( - - - - - - - - )) - : null} - -
    - {j.ro_number} - {`${j.v_model_yr || ""} ${j.v_make_desc || ""} ${ - j.v_model_desc || "" - }`}{`${j.labhrs.aggregate.sum.mod_lb_hrs || "0"} / ${ - j.larhrs.aggregate.sum.mod_lb_hrs || "0" - }`} - - {j.scheduled_completion} - -
    - ) : null} -
    -
    - ); - }; + const { t } = useTranslation(); + const [callQuery, { loading, error, data }] = useLazyQuery(QUERY_JOBS_IN_PRODUCTION); + const content = () => { return ( - - - + +
    e.stopPropagation()} className="jobs-in-production-table"> + {loading ? : null} + {error ? : null} + {data ? ( + + + {data && data.jobs + ? data.jobs.map((j) => ( + + + + + + + + )) + : null} + +
    + {j.ro_number} + + + {`${j.v_model_yr || ""} ${j.v_make_desc || ""} ${j.v_model_desc || ""}`}{`${j.labhrs.aggregate.sum.mod_lb_hrs || "0"} / ${ + j.larhrs.aggregate.sum.mod_lb_hrs || "0" + }`} + {j.scheduled_completion} +
    + ) : null} +
    +
    ); + }; + + return ( + + + + ); } diff --git a/client/src/components/schedule-verify-integrity/schedule-verify-integrity.component.jsx b/client/src/components/schedule-verify-integrity/schedule-verify-integrity.component.jsx index 2b50a43e6..83693df19 100644 --- a/client/src/components/schedule-verify-integrity/schedule-verify-integrity.component.jsx +++ b/client/src/components/schedule-verify-integrity/schedule-verify-integrity.component.jsx @@ -1,61 +1,58 @@ -import {useApolloClient} from "@apollo/client"; -import {Button} from "antd"; +import { useApolloClient } from "@apollo/client"; +import { Button } from "antd"; import dayjs from "../../utils/day"; -import React, {useState} from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {QUERY_SCHEDULE_LOAD_DATA} from "../../graphql/appointments.queries"; -import {selectCurrentUser} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries"; +import { selectCurrentUser } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScheduleVerifyIntegrity); +export default connect(mapStateToProps, mapDispatchToProps)(ScheduleVerifyIntegrity); -export function ScheduleVerifyIntegrity({currentUser}) { - const [loading, setLoading] = useState(false); +export function ScheduleVerifyIntegrity({ currentUser }) { + const [loading, setLoading] = useState(false); - const client = useApolloClient(); - const handleVerify = async () => { - setLoading(true); - const { - data: {arrJobs, compJobs, prodJobs}, - } = await client.query({ - query: QUERY_SCHEDULE_LOAD_DATA, - variables: {start: dayjs(), end: dayjs().add(180, "day")}, - }); + const client = useApolloClient(); + const handleVerify = async () => { + setLoading(true); + const { + data: { arrJobs, compJobs, prodJobs } + } = await client.query({ + query: QUERY_SCHEDULE_LOAD_DATA, + variables: { start: dayjs(), end: dayjs().add(180, "day") } + }); - //check that the leaving jobs are either in the arriving list, or in production. - const issues = []; + //check that the leaving jobs are either in the arriving list, or in production. + const issues = []; - compJobs.forEach((j) => { - const inProdJobs = prodJobs.find((p) => p.id === j.id); - const inArrJobs = arrJobs.find((p) => p.id === j.id); + compJobs.forEach((j) => { + const inProdJobs = prodJobs.find((p) => p.id === j.id); + const inArrJobs = arrJobs.find((p) => p.id === j.id); - if (!(inProdJobs || inArrJobs)) { - // NOT FOUND! - issues.push(j); - } - }); - console.log( - "The following completing jobs are not in production, or are arriving within the next 180 days. ", - issues - ); + if (!(inProdJobs || inArrJobs)) { + // NOT FOUND! + issues.push(j); + } + }); + console.log( + "The following completing jobs are not in production, or are arriving within the next 180 days. ", + issues + ); - setLoading(false); - }; + setLoading(false); + }; - if (currentUser.email === "patrick@imex.prod") - return ( - - ); - else return null; + if (currentUser.email === "patrick@imex.prod") + return ( + + ); + else return null; } diff --git a/client/src/components/scoreboard-chart/chart-custom-tooltip.jsx b/client/src/components/scoreboard-chart/chart-custom-tooltip.jsx index 461f7053e..c43b26f97 100644 --- a/client/src/components/scoreboard-chart/chart-custom-tooltip.jsx +++ b/client/src/components/scoreboard-chart/chart-custom-tooltip.jsx @@ -1,39 +1,31 @@ import Dinero from "dinero.js"; -const CustomTooltip = ({active, payload, label}) => { - if (active && payload && payload.length) { - return ( -
    -

    {label}

    - {payload.map((data, index) => { - if (data.dataKey === "sales" || data.dataKey === "accSales") - return ( -

    {`${data.name} : ${Dinero({ - amount: Math.round(data.value * 100), - }).toFormat()}`}

    - ); +const CustomTooltip = ({ active, payload, label }) => { + if (active && payload && payload.length) { + return ( +
    +

    {label}

    + {payload.map((data, index) => { + if (data.dataKey === "sales" || data.dataKey === "accSales") + return ( +

    {`${data.name} : ${Dinero({ + amount: Math.round(data.value * 100) + }).toFormat()}`}

    + ); - return ( -

    {`${data.name} : ${data.value}`}

    - ); - })} -
    - ); - } + return

    {`${data.name} : ${data.value}`}

    ; + })} +
    + ); + } - return null; + return null; }; export default CustomTooltip; diff --git a/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx b/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx index e3dd3259b..7b613aaed 100644 --- a/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx +++ b/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx @@ -1,174 +1,141 @@ -import {Card} from "antd"; +import { Card } from "antd"; import Dinero from "dinero.js"; import _ from "lodash"; import dayjs from "../../utils/day"; import React from "react"; -import {connect} from "react-redux"; +import { connect } from "react-redux"; import { - Area, - Bar, - CartesianGrid, - ComposedChart, - Legend, - Line, - ResponsiveContainer, - Tooltip, - XAxis, - YAxis, + Area, + Bar, + CartesianGrid, + ComposedChart, + Legend, + Line, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis } from "recharts"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util"; import CustomTooltip from "./chart-custom-tooltip"; const graphProps = { - strokeWidth: 3, + strokeWidth: 3 }; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardChart); -export function ScoreboardChart({sbEntriesByDate, bodyshop}) { - const listOfBusDays = Utils.ListOfDaysInCurrentMonth(); +export function ScoreboardChart({ sbEntriesByDate, bodyshop }) { + const listOfBusDays = Utils.ListOfDaysInCurrentMonth(); - const data = listOfBusDays.reduce((acc, val) => { - //Sum up the current day. - let dayhrs; - if (!!sbEntriesByDate[val]) { - dayhrs = sbEntriesByDate[val].reduce( - (dayAcc, dayVal) => { - return { - bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs, - painthrs: dayAcc.painthrs + dayVal.painthrs, - sales: - dayAcc.sales + dayVal.job.job_totals.totals.subtotal.amount / 100, - }; - }, - {bodyhrs: 0, painthrs: 0, sales: 0} - ); - } else { - dayhrs = { - bodyhrs: 0, - painthrs: 0, - sales: 0, - }; - } + const data = listOfBusDays.reduce((acc, val) => { + //Sum up the current day. + let dayhrs; + if (!!sbEntriesByDate[val]) { + dayhrs = sbEntriesByDate[val].reduce( + (dayAcc, dayVal) => { + return { + bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs, + painthrs: dayAcc.painthrs + dayVal.painthrs, + sales: dayAcc.sales + dayVal.job.job_totals.totals.subtotal.amount / 100 + }; + }, + { bodyhrs: 0, painthrs: 0, sales: 0 } + ); + } else { + dayhrs = { + bodyhrs: 0, + painthrs: 0, + sales: 0 + }; + } - const theValue = { - date: dayjs(val).format("D ddd"), - paintHrs: _.round(dayhrs.painthrs, 1), - bodyHrs: _.round(dayhrs.bodyhrs, 1), - accTargetHrs: _.round( - Utils.AsOfDateTargetHours( - bodyshop.scoreboard_target.dailyBodyTarget + - bodyshop.scoreboard_target.dailyPaintTarget, - val - ) + - bodyshop.scoreboard_target.dailyBodyTarget + - bodyshop.scoreboard_target.dailyPaintTarget, - 1 - ), - accHrs: _.round( - acc.length > 0 - ? acc[acc.length - 1].accHrs + dayhrs.painthrs + dayhrs.bodyhrs - : dayhrs.painthrs + dayhrs.bodyhrs, - 1 - ), - sales: _.round(dayhrs.sales, 2), - accSales: _.round( - acc.length > 0 - ? acc[acc.length - 1].accSales + dayhrs.sales - : dayhrs.sales, - 2 - ), - }; + const theValue = { + date: dayjs(val).format("D ddd"), + paintHrs: _.round(dayhrs.painthrs, 1), + bodyHrs: _.round(dayhrs.bodyhrs, 1), + accTargetHrs: _.round( + Utils.AsOfDateTargetHours( + bodyshop.scoreboard_target.dailyBodyTarget + bodyshop.scoreboard_target.dailyPaintTarget, + val + ) + + bodyshop.scoreboard_target.dailyBodyTarget + + bodyshop.scoreboard_target.dailyPaintTarget, + 1 + ), + accHrs: _.round( + acc.length > 0 + ? acc[acc.length - 1].accHrs + dayhrs.painthrs + dayhrs.bodyhrs + : dayhrs.painthrs + dayhrs.bodyhrs, + 1 + ), + sales: _.round(dayhrs.sales, 2), + accSales: _.round(acc.length > 0 ? acc[acc.length - 1].accSales + dayhrs.sales : dayhrs.sales, 2) + }; - return [...acc, theValue]; - }, []); + return [...acc, theValue]; + }, []); - return ( - - - - - - - Dinero({amount: Math.round(value * 100)}).toFormat() - } - orientation="right" - /> - - }/> - + return ( + + + + + + Dinero({ amount: Math.round(value * 100) }).toFormat()} + orientation="right" + /> + + } /> + - - - + + + - - { - // - } - - - - - ); + + { + // + } + + + + + ); } diff --git a/client/src/components/scoreboard-day-stats/scoreboard-day-stats.component.jsx b/client/src/components/scoreboard-day-stats/scoreboard-day-stats.component.jsx index a673b4c20..31d18a594 100644 --- a/client/src/components/scoreboard-day-stats/scoreboard-day-stats.component.jsx +++ b/client/src/components/scoreboard-day-stats/scoreboard-day-stats.component.jsx @@ -1,55 +1,51 @@ -import {Card, Divider, Statistic} from "antd"; +import { Card, Divider, Statistic } from "antd"; import dayjs from "../../utils/day"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function ScoreboardDayStats({bodyshop, date, entries}) { - const {dailyPaintTarget, dailyBodyTarget} = bodyshop.scoreboard_target; +export function ScoreboardDayStats({ bodyshop, date, entries }) { + const { dailyPaintTarget, dailyBodyTarget } = bodyshop.scoreboard_target; - //let totalHrs = 0; - const paintHrs = entries.reduce((acc, value) => { - // totalHrs = +value.painthrs; - return acc + value.painthrs; - }, 0); + //let totalHrs = 0; + const paintHrs = entries.reduce((acc, value) => { + // totalHrs = +value.painthrs; + return acc + value.painthrs; + }, 0); - const bodyHrs = entries.reduce((acc, value) => { - //totalHrs = +value.bodyhrs; - return acc + value.bodyhrs; - }, 0); + const bodyHrs = entries.reduce((acc, value) => { + //totalHrs = +value.bodyhrs; + return acc + value.bodyhrs; + }, 0); - const numJobs = entries.length; + const numJobs = entries.length; - return ( - - bodyHrs ? "red" : "green"}} - label="Body" - value={bodyHrs.toFixed(1)} - /> - paintHrs ? "red" : "green"}} - label="Refinish" - value={paintHrs.toFixed(1)} - /> - - - - - - ); + return ( + + bodyHrs ? "red" : "green" }} + label="Body" + value={bodyHrs.toFixed(1)} + /> + paintHrs ? "red" : "green" }} + label="Refinish" + value={paintHrs.toFixed(1)} + /> + + + + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardDayStats); diff --git a/client/src/components/scoreboard-display/scoreboard-display.component.jsx b/client/src/components/scoreboard-display/scoreboard-display.component.jsx index 5479e0efb..c74cecdc0 100644 --- a/client/src/components/scoreboard-display/scoreboard-display.component.jsx +++ b/client/src/components/scoreboard-display/scoreboard-display.component.jsx @@ -1,123 +1,118 @@ -import {Col, Row} from "antd"; -import React, {useEffect} from "react"; +import { Col, Row } from "antd"; +import React, { useEffect } from "react"; import ScoreboardChart from "../scoreboard-chart/scoreboard-chart.component"; import ScoreboardLastDays from "../scoreboard-last-days/scoreboard-last-days.component"; import ScoreboardTargetsTable from "../scoreboard-targets-table/scoreboard-targets-table.component"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import dayjs from "../../utils/day"; -import {useApolloClient, useQuery} from "@apollo/client"; -import {GET_BLOCKED_DAYS, QUERY_SCOREBOARD,} from "../../graphql/scoreboard.queries"; +import { useApolloClient, useQuery } from "@apollo/client"; +import { GET_BLOCKED_DAYS, QUERY_SCOREBOARD } from "../../graphql/scoreboard.queries"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScoreboardDisplayComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardDisplayComponent); -export function ScoreboardDisplayComponent({bodyshop}) { - const scoreboardSubscription = useQuery(QUERY_SCOREBOARD, { - variables: { +export function ScoreboardDisplayComponent({ bodyshop }) { + const scoreboardSubscription = useQuery(QUERY_SCOREBOARD, { + variables: { + start: dayjs().startOf("month"), + end: dayjs().endOf("month") + }, + pollInterval: 60000 + }); + + const { data } = scoreboardSubscription; + const client = useApolloClient(); + const scoreBoardlist = (data && data.scoreboard) || []; + + const sbEntriesByDate = {}; + + scoreBoardlist.forEach((i) => { + const entryDate = i.date; + if (!!!sbEntriesByDate[entryDate]) { + sbEntriesByDate[entryDate] = []; + } + sbEntriesByDate[entryDate].push(i); + }); + + useEffect(() => { + //Update the locals. + async function setDayJSSettings() { + let appointments; + + if (!bodyshop.scoreboard_target.ignoreblockeddays) { + const { data } = await client.query({ + query: GET_BLOCKED_DAYS, + variables: { start: dayjs().startOf("month"), - end: dayjs().endOf("month"), - }, - pollInterval: 60000, - }); + end: dayjs().endOf("month") + } + }); + appointments = data.appointments; + } - const {data} = scoreboardSubscription; - const client = useApolloClient(); - const scoreBoardlist = (data && data.scoreboard) || []; - - const sbEntriesByDate = {}; - - scoreBoardlist.forEach((i) => { - const entryDate = i.date; - if (!!!sbEntriesByDate[entryDate]) { - sbEntriesByDate[entryDate] = []; - } - sbEntriesByDate[entryDate].push(i); - }); - - useEffect(() => { - //Update the locals. - async function setDayJSSettings() { - let appointments; - - if (!bodyshop.scoreboard_target.ignoreblockeddays) { - const {data} = await client.query({ - query: GET_BLOCKED_DAYS, - variables: { - start: dayjs().startOf("month"), - end: dayjs().endOf("month"), - }, - }); - appointments = data.appointments; + dayjs.updateLocale("ca", { + workingWeekdays: translateSettingsToWorkingDays(bodyshop.workingdays), + ...(appointments + ? { + holidays: appointments.map((h) => dayjs(h.start).format("MM-DD-YYYY")) } + : {}), + holidayFormat: "MM-DD-YYYY" + }); + } - dayjs.updateLocale("ca", { - workingWeekdays: translateSettingsToWorkingDays(bodyshop.workingdays), - ...(appointments - ? { - holidays: appointments.map((h) => - dayjs(h.start).format("MM-DD-YYYY") - ), - } - : {}), - holidayFormat: "MM-DD-YYYY", - }); - } + setDayJSSettings(); + }, [client, bodyshop]); - setDayJSSettings(); - }, [client, bodyshop]); + return ( + + + + - return ( - - - - + + + - - - - - - - - - ); + + + + + ); } function translateSettingsToWorkingDays(workingdays) { - const days = []; + const days = []; - if (workingdays.monday) { - days.push(1); - } - if (workingdays.tuesday) { - days.push(2); - } - if (workingdays.wednesday) { - days.push(3); - } - if (workingdays.thursday) { - days.push(4); - } - if (workingdays.friday) { - days.push(5); - } - if (workingdays.saturday) { - days.push(6); - } - if (workingdays.sunday) { - days.push(0); - } - return days; + if (workingdays.monday) { + days.push(1); + } + if (workingdays.tuesday) { + days.push(2); + } + if (workingdays.wednesday) { + days.push(3); + } + if (workingdays.thursday) { + days.push(4); + } + if (workingdays.friday) { + days.push(5); + } + if (workingdays.saturday) { + days.push(6); + } + if (workingdays.sunday) { + days.push(0); + } + return days; } diff --git a/client/src/components/scoreboard-entry-edit/scoreboard-entry-edit.component.jsx b/client/src/components/scoreboard-entry-edit/scoreboard-entry-edit.component.jsx index a26beadd6..4c099a13c 100644 --- a/client/src/components/scoreboard-entry-edit/scoreboard-entry-edit.component.jsx +++ b/client/src/components/scoreboard-entry-edit/scoreboard-entry-edit.component.jsx @@ -1,116 +1,108 @@ -import {useMutation} from "@apollo/client"; -import {Button, Card, Dropdown, Form, InputNumber, notification, Space,} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Card, Dropdown, Form, InputNumber, notification, Space } from "antd"; import dayjs from "../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {UPDATE_SCOREBOARD_ENTRY} from "../../graphql/scoreboard.queries"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { UPDATE_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; -export default function ScoreboardEntryEdit({entry}) { - const [open, setOpen] = useState(false); - const [loading, setLoading] = useState(false); - const {t} = useTranslation(); - const [updateScoreboardentry] = useMutation(UPDATE_SCOREBOARD_ENTRY); +export default function ScoreboardEntryEdit({ entry }) { + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); + const { t } = useTranslation(); + const [updateScoreboardentry] = useMutation(UPDATE_SCOREBOARD_ENTRY); - const handleFinish = async (values) => { - setLoading(true); - values.date = dayjs(values.date).format("YYYY-MM-DD"); - const result = await updateScoreboardentry({ - variables: {sbId: entry.id, sbInput: values}, - }); + const handleFinish = async (values) => { + setLoading(true); + values.date = dayjs(values.date).format("YYYY-MM-DD"); + const result = await updateScoreboardentry({ + variables: { sbId: entry.id, sbInput: values } + }); - if (!!result.errors) { - notification["error"]({ - message: t("scoreboard.errors.updating", { - message: JSON.stringify(result.errors), - }), - }); - return; - } else { - notification["success"]({ - message: t("scoreboard.successes.updated"), - }); - setOpen(false); - } - setLoading(false); - }; - - - const menu = { - items: [ - { - key: '1', - label: ( - -
    e.stopPropagation()} - > - - - - - - - - - - - - - -
    -
    - ) - } - ] + if (!!result.errors) { + notification["error"]({ + message: t("scoreboard.errors.updating", { + message: JSON.stringify(result.errors) + }) + }); + return; + } else { + notification["success"]({ + message: t("scoreboard.successes.updated") + }); + setOpen(false); } + setLoading(false); + }; - return ( -
    - - - -
    - ); + + + +
    + ) + } + ] + }; + + return ( +
    + + + +
    + ); } diff --git a/client/src/components/scoreboard-jobs-list/scoreboard-jobs-list.component.jsx b/client/src/components/scoreboard-jobs-list/scoreboard-jobs-list.component.jsx index fa00ace09..15d096145 100644 --- a/client/src/components/scoreboard-jobs-list/scoreboard-jobs-list.component.jsx +++ b/client/src/components/scoreboard-jobs-list/scoreboard-jobs-list.component.jsx @@ -1,184 +1,163 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {useQuery} from "@apollo/client"; -import {Button, Card, Input, Modal, Space, Table, Typography} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {Link} from "react-router-dom"; -import {QUERY_SCOREBOARD_PAGINATED} from "../../graphql/scoreboard.queries"; -import {DateFormatter} from "../../utils/DateFormatter"; -import {pageLimit} from "../../utils/config"; -import {alphaSort, dateSort} from "../../utils/sorters"; +import { SyncOutlined } from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import { Button, Card, Input, Modal, Space, Table, Typography } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; +import { QUERY_SCOREBOARD_PAGINATED } from "../../graphql/scoreboard.queries"; +import { DateFormatter } from "../../utils/DateFormatter"; +import { pageLimit } from "../../utils/config"; +import { alphaSort, dateSort } from "../../utils/sorters"; import AlertComponent from "../alert/alert.component"; -import OwnerNameDisplay, {OwnerNameDisplayFunction,} from "../owner-name-display/owner-name-display.component"; +import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import ScoreboardEntryEdit from "../scoreboard-entry-edit/scoreboard-entry-edit.component"; import ScoreboardRemoveButton from "../scoreboard-remove-button/scorebard-remove-button.component"; +export default function ScoreboardJobsList({ scoreBoardlist }) { + const { t } = useTranslation(); + const [state, setState] = useState({ + open: false, + search: "", + current: 1, + pageSize: pageLimit + }); -export default function ScoreboardJobsList({scoreBoardlist}) { - const {t} = useTranslation(); - const [state, setState] = useState({ - open: false, - search: "", - current: 1, - pageSize: pageLimit, - }); - - const {loading, error, data, refetch} = useQuery( - QUERY_SCOREBOARD_PAGINATED, + const { loading, error, data, refetch } = useQuery(QUERY_SCOREBOARD_PAGINATED, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + skip: !state.open, + variables: { + search: state.search !== "" ? `%${state.search}%` : null, + offset: state.current ? (state.current - 1) * state.pageSize : 0, + limit: state.pageSize, + order: [ { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - skip: !state.open, - variables: { - search: state.search !== "" ? `%${state.search}%` : null, - offset: state.current ? (state.current - 1) * state.pageSize : 0, - limit: state.pageSize, - order: [ - { - date: "desc", - }, - ], - }, + date: "desc" } - ); + ] + } + }); - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), render: (text, record) => ( - - {record.job.ro_number || t("general.labels.na")} - - ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - ellipsis: true, sorter: (a, b) => - alphaSort( - OwnerNameDisplayFunction(a.job), - OwnerNameDisplayFunction(b.job) - ), - render: (text, record) => , - }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - ellipsis: true, - sorter: (a, b) => - alphaSort( - `${a.job.v_model_yr || ""} ${a.job.v_make_desc || ""} ${ - a.job.v_model_desc || "" - }`, - `${b.job.v_model_yr || ""} ${b.job.v_make_desc || ""} ${ - b.job.v_model_desc || "" - }` - ), render: (text, record) => ( - {`${record.job.v_model_yr || ""} ${ - record.job.v_make_desc || "" - } ${record.job.v_model_desc || ""}`} - ), - }, - { - title: t("scoreboard.fields.date"), - dataIndex: "date", - key: "date", - sorter: (a, b) => dateSort(a.date, b.date), - render: (text, record) => {record.date}, - }, - { - title: t("scoreboard.fields.bodyhrs"), - dataIndex: "bodyhrs", - key: "bodyhrs", - sorter: (a, b) => Number(a.bodyhrs) - Number(b.bodyhrs), - }, - { - title: t("scoreboard.fields.painthrs"), - dataIndex: "painthrs", - key: "painthrs", - sorter: (a, b) => Number(a.painthrs) - Number(b.painthrs), - }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", - render: (text, record) => ( - - - - - ), - }, - ]; + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), + render: (text, record) => ( + {record.job.ro_number || t("general.labels.na")} + ) + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a.job), OwnerNameDisplayFunction(b.job)), + render: (text, record) => + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + sorter: (a, b) => + alphaSort( + `${a.job.v_model_yr || ""} ${a.job.v_make_desc || ""} ${a.job.v_model_desc || ""}`, + `${b.job.v_model_yr || ""} ${b.job.v_make_desc || ""} ${b.job.v_model_desc || ""}` + ), + render: (text, record) => ( + {`${record.job.v_model_yr || ""} ${record.job.v_make_desc || ""} ${record.job.v_model_desc || ""}`} + ) + }, + { + title: t("scoreboard.fields.date"), + dataIndex: "date", + key: "date", + sorter: (a, b) => dateSort(a.date, b.date), + render: (text, record) => {record.date} + }, + { + title: t("scoreboard.fields.bodyhrs"), + dataIndex: "bodyhrs", + key: "bodyhrs", + sorter: (a, b) => Number(a.bodyhrs) - Number(b.bodyhrs) + }, + { + title: t("scoreboard.fields.painthrs"), + dataIndex: "painthrs", + key: "painthrs", + sorter: (a, b) => Number(a.painthrs) - Number(b.painthrs) + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => ( + + + + + ) + } + ]; - return ( - <> - - setState((state) => ({ - ...state, - open: false, - current: 1, - search: "", - })) - } - > - {error && ( - - )} - - - - {t("general.labels.searchresults", {search: state.search})} - - { - setState((state) => ({...state, search: value})); - }} - //value={state.search} - enterButton - /> - - } - > - e.stopPropagation()} - onChange={(tableArgs) => - setState((state) => ({...state, ...tableArgs})) - } - pagination={{ - position: "top", - pageSize: state.pageSize || pageLimit, - current: state.current || 1, - total: data ? data.scoreboard_aggregate.aggregate.count : 0, - }} - /> - - - - - ); + return ( + <> + + setState((state) => ({ + ...state, + open: false, + current: 1, + search: "" + })) + } + > + {error && } + + + + {t("general.labels.searchresults", { search: state.search })} + + { + setState((state) => ({ ...state, search: value })); + }} + //value={state.search} + enterButton + /> + + } + > +
    e.stopPropagation()} + onChange={(tableArgs) => setState((state) => ({ ...state, ...tableArgs }))} + pagination={{ + position: "top", + pageSize: state.pageSize || pageLimit, + current: state.current || 1, + total: data ? data.scoreboard_aggregate.aggregate.count : 0 + }} + /> + + + + + ); } diff --git a/client/src/components/scoreboard-last-days/scoreboard-last-days.component.jsx b/client/src/components/scoreboard-last-days/scoreboard-last-days.component.jsx index 8430e6a00..d511bc96e 100644 --- a/client/src/components/scoreboard-last-days/scoreboard-last-days.component.jsx +++ b/client/src/components/scoreboard-last-days/scoreboard-last-days.component.jsx @@ -1,41 +1,36 @@ import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import dayjs from "../../utils/day"; import ScoreboardDayStat from "../scoreboard-day-stats/scoreboard-day-stats.component"; -import {Col, Row} from "antd"; +import { Col, Row } from "antd"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function ScoreboardLastDays({bodyshop, sbEntriesByDate}) { +export function ScoreboardLastDays({ bodyshop, sbEntriesByDate }) { + const { lastNumberWorkingDays } = bodyshop.scoreboard_target; - const {lastNumberWorkingDays} = bodyshop.scoreboard_target; + const ArrayOfDate = []; + for (var i = lastNumberWorkingDays - 1; i >= 0; i--) { + ArrayOfDate.push(dayjs().businessDaysSubtract(i, "day").format("YYYY-MM-DD")); + } - const ArrayOfDate = []; - for (var i = lastNumberWorkingDays - 1; i >= 0; i--) { - ArrayOfDate.push(dayjs().businessDaysSubtract(i, "day").format("YYYY-MM-DD")); - } - - return ( - - {ArrayOfDate.map((a) => ( - - {!!sbEntriesByDate ? ( - - ) : ( - - )} - - ))} - - ); + return ( + + {ArrayOfDate.map((a) => ( + + {!!sbEntriesByDate ? : } + + ))} + + ); } export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardLastDays); diff --git a/client/src/components/scoreboard-remove-button/scorebard-remove-button.component.jsx b/client/src/components/scoreboard-remove-button/scorebard-remove-button.component.jsx index b8a0c8276..5aef10d17 100644 --- a/client/src/components/scoreboard-remove-button/scorebard-remove-button.component.jsx +++ b/client/src/components/scoreboard-remove-button/scorebard-remove-button.component.jsx @@ -1,42 +1,42 @@ -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {Button, notification} from "antd"; -import {DeleteFilled} from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {DELETE_SCOREBOARD_ENTRY} from "../../graphql/scoreboard.queries"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Button, notification } from "antd"; +import { DeleteFilled } from "@ant-design/icons"; +import { useMutation } from "@apollo/client"; +import { DELETE_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; -export default function ScoreboardRemoveButton({scoreboardId}) { - const {t} = useTranslation(); - const [deleteScoreboardEntry] = useMutation(DELETE_SCOREBOARD_ENTRY); - const [loading, setLoading] = useState(false); - const handleDelete = async (e) => { - logImEXEvent("scoreboard_remove_job"); +export default function ScoreboardRemoveButton({ scoreboardId }) { + const { t } = useTranslation(); + const [deleteScoreboardEntry] = useMutation(DELETE_SCOREBOARD_ENTRY); + const [loading, setLoading] = useState(false); + const handleDelete = async (e) => { + logImEXEvent("scoreboard_remove_job"); - e.stopPropagation(); - setLoading(true); - const result = await deleteScoreboardEntry({ - variables: {sbId: scoreboardId}, - awaitRefetchQueries: true, - refetchQueries: ["QUERY_SCOREBOARD_PAGINATED"], - }); + e.stopPropagation(); + setLoading(true); + const result = await deleteScoreboardEntry({ + variables: { sbId: scoreboardId }, + awaitRefetchQueries: true, + refetchQueries: ["QUERY_SCOREBOARD_PAGINATED"] + }); - if (!!result.errors) { - notification["error"]({ - message: t("scoreboard.errors.removing", { - message: JSON.stringify(result.errors), - }), - }); - } else { - notification["success"]({ - message: t("scoreboard.successes.removed"), - }); - } - setLoading(false); - }; - return ( - - ); + if (!!result.errors) { + notification["error"]({ + message: t("scoreboard.errors.removing", { + message: JSON.stringify(result.errors) + }) + }); + } else { + notification["success"]({ + message: t("scoreboard.successes.removed") + }); + } + setLoading(false); + }; + return ( + + ); } diff --git a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx index 75f98c69f..c017b0267 100644 --- a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx +++ b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx @@ -1,30 +1,30 @@ -import {CalendarOutlined} from "@ant-design/icons"; -import {Card, Col, Divider, Row, Statistic} from "antd"; +import { CalendarOutlined } from "@ant-design/icons"; +import { Card, Col, Divider, Row, Statistic } from "antd"; import _ from "lodash"; import dayjs from "../../utils/day"; -import React, {useMemo} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import ScoreboardJobsList from "../scoreboard-jobs-list/scoreboard-jobs-list.component"; import * as Util from "./scoreboard-targets-table.util"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); const rowGutter = [16, 16]; -const statSpans = {xs: 24, sm: 3}; +const statSpans = { xs: 24, sm: 3 }; -export function ScoreboardTargetsTable({bodyshop, scoreBoardlist}) { - const {t} = useTranslation(); +export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) { + const { t } = useTranslation(); - const values = useMemo(() => { - const dateHash = _.groupBy(scoreBoardlist, "date"); + const values = useMemo(() => { + const dateHash = _.groupBy(scoreBoardlist, "date"); let ret = { todayBody: 0, @@ -35,7 +35,7 @@ export function ScoreboardTargetsTable({bodyshop, scoreBoardlist}) { weeklyBody: 0, toDateBody: 0, toDatePaint: 0, - toDateJobs: 0, + toDateJobs: 0 }; const today = dayjs(); @@ -71,14 +71,11 @@ export function ScoreboardTargetsTable({bodyshop, scoreBoardlist}) { startOfMonth = startOfMonth.add(1, "day"); } - return ret; - }, [scoreBoardlist]); + return ret; + }, [scoreBoardlist]); return ( - } - > + }> - + - + - + @@ -153,31 +132,16 @@ export function ScoreboardTargetsTable({bodyshop, scoreBoardlist}) { - + - + - + @@ -188,67 +152,40 @@ export function ScoreboardTargetsTable({bodyshop, scoreBoardlist}) { - + - + - + - + @@ -256,10 +193,7 @@ export function ScoreboardTargetsTable({bodyshop, scoreBoardlist}) { - + @@ -279,7 +213,4 @@ export function ScoreboardTargetsTable({bodyshop, scoreBoardlist}) { ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScoreboardTargetsTable); +export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardTargetsTable); diff --git a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js index ffcd5cf55..47394d966 100644 --- a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js +++ b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js @@ -1,54 +1,48 @@ import dayjs from "../../utils/day"; -export const CalculateWorkingDaysThisMonth = () => - dayjs().endOf("month").businessDaysInMonth().length; +export const CalculateWorkingDaysThisMonth = () => dayjs().endOf("month").businessDaysInMonth().length; -export const CalculateWorkingDaysInPeriod = (start, end) => - dayjs(end).businessDiff(dayjs(start)); +export const CalculateWorkingDaysInPeriod = (start, end) => dayjs(end).businessDiff(dayjs(start)); -export const CalculateWorkingDaysAsOfToday = () => - dayjs().businessDaysInMonth().length; +export const CalculateWorkingDaysAsOfToday = () => dayjs().businessDaysInMonth().length; export const CalculateWorkingDaysLastMonth = () => - dayjs().subtract(1, "month").endOf("month").businessDaysInMonth().length; + dayjs().subtract(1, "month").endOf("month").businessDaysInMonth().length; export const WeeklyTargetHrs = (dailyTargetHrs) => - dailyTargetHrs * CalculateWorkingDaysInPeriod(dayjs().startOf("week"), dayjs().endOf("week")); + dailyTargetHrs * CalculateWorkingDaysInPeriod(dayjs().startOf("week"), dayjs().endOf("week")); export const WeeklyTargetHrsInPeriod = (dailyTargetHrs, start, end) => - dailyTargetHrs * CalculateWorkingDaysInPeriod(start, end); + dailyTargetHrs * CalculateWorkingDaysInPeriod(start, end); -export const MonthlyTargetHrs = (dailyTargetHrs) => - dailyTargetHrs * CalculateWorkingDaysThisMonth(); +export const MonthlyTargetHrs = (dailyTargetHrs) => dailyTargetHrs * CalculateWorkingDaysThisMonth(); -export const LastMonthTargetHrs = (dailyTargetHrs) => - dailyTargetHrs * CalculateWorkingDaysLastMonth(); +export const LastMonthTargetHrs = (dailyTargetHrs) => dailyTargetHrs * CalculateWorkingDaysLastMonth(); -export const AsOfTodayTargetHrs = (dailyTargetHrs) => - dailyTargetHrs * CalculateWorkingDaysAsOfToday(); +export const AsOfTodayTargetHrs = (dailyTargetHrs) => dailyTargetHrs * CalculateWorkingDaysAsOfToday(); export const AsOfDateTargetHours = (dailyTargetHours, date) => - dailyTargetHours * dayjs(date).businessDiff(dayjs().startOf("month")); + dailyTargetHours * dayjs(date).businessDiff(dayjs().startOf("month")); export const ListOfDaysInCurrentMonth = () => { - const days = []; - let dateStart = dayjs().startOf("month"); - const dateEnd = dayjs().endOf("month"); - while (dateEnd.diff(dateStart, "day") > 0) { - days.push(dateStart.format("YYYY-MM-DD")); - dateStart = dateStart.add(1, "day"); - } - days.push(dateEnd.format("YYYY-MM-DD")); - return days; + const days = []; + let dateStart = dayjs().startOf("month"); + const dateEnd = dayjs().endOf("month"); + while (dateEnd.diff(dateStart, "day") > 0) { + days.push(dateStart.format("YYYY-MM-DD")); + dateStart = dateStart.add(1, "day"); + } + days.push(dateEnd.format("YYYY-MM-DD")); + return days; }; -export const ListDaysBetween = ({start, end}) => { - const days = []; - let dateStart = dayjs(start); - const dateEnd = dayjs(end); - while (dateEnd.diff(dateStart, "day") >= 0) { - days.push(dateStart.format("YYYY-MM-DD")); - dateStart = dateStart.add(1, "day"); - } - return days; -}; \ No newline at end of file +export const ListDaysBetween = ({ start, end }) => { + const days = []; + let dateStart = dayjs(start); + const dateEnd = dayjs(end); + while (dateEnd.diff(dateStart, "day") >= 0) { + days.push(dateStart.format("YYYY-MM-DD")); + dateStart = dateStart.add(1, "day"); + } + return days; +}; diff --git a/client/src/components/scoreboard-timetickets-stats/chart-custom-tooltip.jsx b/client/src/components/scoreboard-timetickets-stats/chart-custom-tooltip.jsx index 0145f2b49..37e450cc7 100644 --- a/client/src/components/scoreboard-timetickets-stats/chart-custom-tooltip.jsx +++ b/client/src/components/scoreboard-timetickets-stats/chart-custom-tooltip.jsx @@ -1,26 +1,26 @@ -const CustomTooltip = ({active, payload, label}) => { - if (active && payload && payload.length) { - return ( -
    -

    {label}

    - {payload.map((data, index) => { - return ( -

    {`${ - data.name - } : ${data.value.toFixed(1)}`}

    - ); - })} -
    - ); - } +const CustomTooltip = ({ active, payload, label }) => { + if (active && payload && payload.length) { + return ( +
    +

    {label}

    + {payload.map((data, index) => { + return ( +

    {`${ + data.name + } : ${data.value.toFixed(1)}`}

    + ); + })} +
    + ); + } - return null; + return null; }; export default CustomTooltip; diff --git a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.chart.component.jsx b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.chart.component.jsx index 30d08761a..45a43940d 100644 --- a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.chart.component.jsx +++ b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.chart.component.jsx @@ -1,44 +1,34 @@ -import {Card} from "antd"; +import { Card } from "antd"; import React from "react"; -import {Area, CartesianGrid, ComposedChart, Legend, Line, ResponsiveContainer, Tooltip, XAxis, YAxis,} from "recharts"; +import { Area, CartesianGrid, ComposedChart, Legend, Line, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; import CustomTooltip from "./chart-custom-tooltip"; const graphProps = { - strokeWidth: 3, + strokeWidth: 3 }; -export default function ScoreboardTimeTicketsChart({data, chartTitle}) { - return ( - - - - - - - }/> - - +export default function ScoreboardTimeTicketsChart({ data, chartTitle }) { + return ( + + + + + + + } /> + + - - - - - ); + + + + + ); } diff --git a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx index 72f22da28..489467740 100644 --- a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx +++ b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx @@ -1,13 +1,13 @@ -import {useQuery} from "@apollo/client"; -import {Col, Row} from "antd"; +import { useQuery } from "@apollo/client"; +import { Col, Row } from "antd"; import _ from "lodash"; import dayjs from "../../utils/day"; -import React, {useMemo} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {QUERY_TIME_TICKETS_IN_RANGE_SB} from "../../graphql/timetickets.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util"; @@ -16,397 +16,320 @@ import ScoreboardTicketsStats from "./scoreboard-timetickets.stats.component"; import ScoreboardTimeticketsTargetsTable from "./scoreboard-timetickets.targets-table.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScoreboardTimeTicketsStats); +export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardTimeTicketsStats); -export function ScoreboardTimeTicketsStats({bodyshop}) { - const {t} = useTranslation(); - const startDate = dayjs().startOf("month") - const endDate = dayjs().endOf("month"); +export function ScoreboardTimeTicketsStats({ bodyshop }) { + const { t } = useTranslation(); + const startDate = dayjs().startOf("month"); + const endDate = dayjs().endOf("month"); - const fixedPeriods = useMemo(() => { - const endOfThisMonth = dayjs().endOf("month"); - const startofthisMonth = dayjs().startOf("month"); + const fixedPeriods = useMemo(() => { + const endOfThisMonth = dayjs().endOf("month"); + const startofthisMonth = dayjs().startOf("month"); - const endOfLastmonth = dayjs().subtract(1, "month").endOf("month"); - const startOfLastmonth = dayjs().subtract(1, "month").startOf("month"); + const endOfLastmonth = dayjs().subtract(1, "month").endOf("month"); + const startOfLastmonth = dayjs().subtract(1, "month").startOf("month"); - const endOfThisWeek = dayjs().endOf("week"); - const startOfThisWeek = dayjs().startOf("week"); + const endOfThisWeek = dayjs().endOf("week"); + const startOfThisWeek = dayjs().startOf("week"); - const endOfLastWeek = dayjs().subtract(1, "week").endOf("week"); - const startOfLastWeek = dayjs().subtract(1, "week").startOf("week"); + const endOfLastWeek = dayjs().subtract(1, "week").endOf("week"); + const startOfLastWeek = dayjs().subtract(1, "week").startOf("week"); - const endOfPriorWeek = dayjs().subtract(2, "week").endOf("week"); - const startOfPriorWeek = dayjs().subtract(2, "week").startOf("week"); + const endOfPriorWeek = dayjs().subtract(2, "week").endOf("week"); + const startOfPriorWeek = dayjs().subtract(2, "week").startOf("week"); - const allDates = [ - endOfThisMonth, - startofthisMonth, - endOfLastmonth, - startOfLastmonth, - endOfThisWeek, - startOfThisWeek, - endOfLastWeek, - startOfLastWeek, - endOfPriorWeek, - startOfPriorWeek, - ]; - const start = dayjs.min(allDates); - const end = dayjs.max(allDates); - return { - start, - end, - endOfThisMonth, - startofthisMonth, - endOfLastmonth, - startOfLastmonth, - endOfThisWeek, - startOfThisWeek, - endOfLastWeek, - startOfLastWeek, - endOfPriorWeek, - startOfPriorWeek, - }; - }, []); + const allDates = [ + endOfThisMonth, + startofthisMonth, + endOfLastmonth, + startOfLastmonth, + endOfThisWeek, + startOfThisWeek, + endOfLastWeek, + startOfLastWeek, + endOfPriorWeek, + startOfPriorWeek + ]; + const start = dayjs.min(allDates); + const end = dayjs.max(allDates); + return { + start, + end, + endOfThisMonth, + startofthisMonth, + endOfLastmonth, + startOfLastmonth, + endOfThisWeek, + startOfThisWeek, + endOfLastWeek, + startOfLastWeek, + endOfPriorWeek, + startOfPriorWeek + }; + }, []); - const {loading, error, data} = useQuery(QUERY_TIME_TICKETS_IN_RANGE_SB, { - variables: { - start: startDate.format("YYYY-MM-DD"), - end: endDate.format("YYYY-MM-DD"), - fixedStart: fixedPeriods.start.format("YYYY-MM-DD"), - fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"), - jobStart: startDate, - jobEnd: endDate, + const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE_SB, { + variables: { + start: startDate.format("YYYY-MM-DD"), + end: endDate.format("YYYY-MM-DD"), + fixedStart: fixedPeriods.start.format("YYYY-MM-DD"), + fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"), + jobStart: startDate, + jobEnd: endDate + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + pollInterval: 60000, + skip: !fixedPeriods + }); + + const calculatedData = useMemo(() => { + if (!data) return []; + const ret = { + totalThisWeek: 0, + totalThisWeekLAB: 0, + totalThisWeekLAR: 0, + totalLastWeek: 0, + totalLastWeekLAB: 0, + totalLastWeekLAR: 0, + totalPriorWeek: 0, + totalPriorWeekLAB: 0, + totalPriorWeekLAR: 0, + totalThisMonth: 0, + totalThisMonthLAB: 0, + totalThisMonthLAR: 0, + totalLastMonth: 0, + totalLastMonthLAB: 0, + totalLastMonthLAR: 0, + actualTotalOverPeriod: 0, + actualTotalOverPeriodLAB: 0, + actualTotalOverPeriodLAR: 0, + totalEffieciencyOverPeriod: 0, + totalEffieciencyOverPeriodLAB: 0, + totalEffieciencyOverPeriodLAR: 0, + seperatedThisWeek: { + sunday: { + total: 0, + lab: 0, + lar: 0 }, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - pollInterval: 60000, - skip: !fixedPeriods, + monday: { + total: 0, + lab: 0, + lar: 0 + }, + tuesday: { + total: 0, + lab: 0, + lar: 0 + }, + wednesday: { + total: 0, + lab: 0, + lar: 0 + }, + thursday: { + total: 0, + lab: 0, + lar: 0 + }, + friday: { + total: 0, + lab: 0, + lar: 0 + }, + saturday: { + total: 0, + lab: 0, + lar: 0 + } + } + }; + + data.fixedperiod.forEach((ticket) => { + const ticketDate = dayjs(ticket.date); + if (ticketDate.isBetween(fixedPeriods.startOfThisWeek, fixedPeriods.endOfThisWeek, undefined, "[]")) { + ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs; + if (ticket.ciecacode !== "LAR") ret.totalThisWeekLAB = ret.totalThisWeekLAB + ticket.productivehrs; + if (ticket.ciecacode === "LAR") ret.totalThisWeekLAR = ret.totalThisWeekLAR + ticket.productivehrs; + + //Seperate out to Day of Week + ret.seperatedThisWeek[dayjs(ticket.date).format("dddd").toLowerCase()].total = + ret.seperatedThisWeek[dayjs(ticket.date).format("dddd").toLowerCase()].total + ticket.productivehrs; + if (ticket.ciecacode !== "LAR") + ret.seperatedThisWeek[dayjs(ticket.date).format("dddd").toLowerCase()].lab = + ret.seperatedThisWeek[dayjs(ticket.date).format("dddd").toLowerCase()].lab + ticket.productivehrs; + if (ticket.ciecacode === "LAR") + ret.seperatedThisWeek[dayjs(ticket.date).format("dddd").toLowerCase()].lar = + ret.seperatedThisWeek[dayjs(ticket.date).format("dddd").toLowerCase()].lar + ticket.productivehrs; + } else if (ticketDate.isBetween(fixedPeriods.startOfLastWeek, fixedPeriods.endOfLastWeek, undefined, "[]")) { + ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs; + if (ticket.ciecacode !== "LAR") ret.totalLastWeekLAB = ret.totalLastWeekLAB + ticket.productivehrs; + if (ticket.ciecacode === "LAR") ret.totalLastWeekLAR = ret.totalLastWeekLAR + ticket.productivehrs; + } else if (ticketDate.isBetween(fixedPeriods.startOfPriorWeek, fixedPeriods.endOfPriorWeek, undefined, "[]")) { + ret.totalPriorWeek = ret.totalPriorWeek + ticket.productivehrs; + if (ticket.ciecacode !== "LAR") ret.totalPriorWeekLAB = ret.totalPriorWeekLAB + ticket.productivehrs; + if (ticket.ciecacode === "LAR") ret.totalPriorWeekLAR = ret.totalPriorWeekLAR + ticket.productivehrs; + } + if (ticketDate.isBetween(fixedPeriods.startofthisMonth, fixedPeriods.endOfThisMonth, undefined, "[]")) { + ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs; + ret.actualTotalOverPeriod = ret.actualTotalOverPeriod + (ticket.actualhrs || 0); + if (ticket.ciecacode !== "LAR") { + ret.totalThisMonthLAB = ret.totalThisMonthLAB + ticket.productivehrs; + ret.actualTotalOverPeriodLAB = ret.actualTotalOverPeriodLAB + (ticket.actualhrs || 0); + } + if (ticket.ciecacode === "LAR") { + ret.totalThisMonthLAR = ret.totalThisMonthLAR + ticket.productivehrs; + ret.actualTotalOverPeriodLAR = ret.actualTotalOverPeriodLAR + (ticket.actualhrs || 0); + } + } else if (ticketDate.isBetween(fixedPeriods.startOfLastmonth, fixedPeriods.endOfLastmonth, undefined, "[]")) { + ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs; + if (ticket.ciecacode !== "LAR") ret.totalLastMonthLAB = ret.totalLastMonthLAB + ticket.productivehrs; + if (ticket.ciecacode === "LAR") ret.totalLastMonthLAR = ret.totalLastMonthLAR + ticket.productivehrs; + } }); - const calculatedData = useMemo(() => { - if (!data) return []; - const ret = { - totalThisWeek: 0, - totalThisWeekLAB: 0, - totalThisWeekLAR: 0, - totalLastWeek: 0, - totalLastWeekLAB: 0, - totalLastWeekLAR: 0, - totalPriorWeek: 0, - totalPriorWeekLAB: 0, - totalPriorWeekLAR: 0, - totalThisMonth: 0, - totalThisMonthLAB: 0, - totalThisMonthLAR: 0, - totalLastMonth: 0, - totalLastMonthLAB: 0, - totalLastMonthLAR: 0, - actualTotalOverPeriod: 0, - actualTotalOverPeriodLAB: 0, - actualTotalOverPeriodLAR: 0, - totalEffieciencyOverPeriod: 0, - totalEffieciencyOverPeriodLAB: 0, - totalEffieciencyOverPeriodLAR: 0, - seperatedThisWeek: { - sunday: { - total: 0, - lab: 0, - lar: 0, - }, - monday: { - total: 0, - lab: 0, - lar: 0, - }, - tuesday: { - total: 0, - lab: 0, - lar: 0, - }, - wednesday: { - total: 0, - lab: 0, - lar: 0, - }, - thursday: { - total: 0, - lab: 0, - lar: 0, - }, - friday: { - total: 0, - lab: 0, - lar: 0, - }, - saturday: { - total: 0, - lab: 0, - lar: 0, - }, - }, - }; + ret.totalEffieciencyOverPeriod = ret.actualTotalOverPeriod + ? (ret.totalThisMonth / ret.actualTotalOverPeriod) * 100 + : 0; + ret.totalEffieciencyOverPeriodLAB = ret.actualTotalOverPeriodLAB + ? (ret.totalThisMonthLAB / ret.actualTotalOverPeriodLAB) * 100 + : 0; + ret.totalEffieciencyOverPeriodLAR = ret.actualTotalOverPeriodLAR + ? (ret.totalThisMonthLAR / ret.actualTotalOverPeriodLAR) * 100 + : 0; - data.fixedperiod.forEach((ticket) => { - const ticketDate = dayjs(ticket.date); - if ( - ticketDate.isBetween( - fixedPeriods.startOfThisWeek, - fixedPeriods.endOfThisWeek, - undefined, - "[]" - ) - ) { - ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs; - if (ticket.ciecacode !== "LAR") - ret.totalThisWeekLAB = ret.totalThisWeekLAB + ticket.productivehrs; - if (ticket.ciecacode === "LAR") - ret.totalThisWeekLAR = ret.totalThisWeekLAR + ticket.productivehrs; + roundObject(ret); - //Seperate out to Day of Week - ret.seperatedThisWeek[ - dayjs(ticket.date).format("dddd").toLowerCase() - ].total = - ret.seperatedThisWeek[ - dayjs(ticket.date).format("dddd").toLowerCase() - ].total + ticket.productivehrs; - if (ticket.ciecacode !== "LAR") - ret.seperatedThisWeek[ - dayjs(ticket.date).format("dddd").toLowerCase() - ].lab = - ret.seperatedThisWeek[ - dayjs(ticket.date).format("dddd").toLowerCase() - ].lab + ticket.productivehrs; - if (ticket.ciecacode === "LAR") - ret.seperatedThisWeek[ - dayjs(ticket.date).format("dddd").toLowerCase() - ].lar = - ret.seperatedThisWeek[ - dayjs(ticket.date).format("dddd").toLowerCase() - ].lar + ticket.productivehrs; - } else if ( - ticketDate.isBetween( - fixedPeriods.startOfLastWeek, - fixedPeriods.endOfLastWeek, - undefined, - "[]" - ) - ) { - ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs; - if (ticket.ciecacode !== "LAR") - ret.totalLastWeekLAB = ret.totalLastWeekLAB + ticket.productivehrs; - if (ticket.ciecacode === "LAR") - ret.totalLastWeekLAR = ret.totalLastWeekLAR + ticket.productivehrs; - } else if ( - ticketDate.isBetween( - fixedPeriods.startOfPriorWeek, - fixedPeriods.endOfPriorWeek, - undefined, - "[]" - ) - ) { - ret.totalPriorWeek = ret.totalPriorWeek + ticket.productivehrs; - if (ticket.ciecacode !== "LAR") - ret.totalPriorWeekLAB = ret.totalPriorWeekLAB + ticket.productivehrs; - if (ticket.ciecacode === "LAR") - ret.totalPriorWeekLAR = ret.totalPriorWeekLAR + ticket.productivehrs; - } - if ( - ticketDate.isBetween( - fixedPeriods.startofthisMonth, - fixedPeriods.endOfThisMonth, - undefined, - "[]" - ) - ) { - ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs; - ret.actualTotalOverPeriod = - ret.actualTotalOverPeriod + (ticket.actualhrs || 0); - if (ticket.ciecacode !== "LAR") { - ret.totalThisMonthLAB = ret.totalThisMonthLAB + ticket.productivehrs; - ret.actualTotalOverPeriodLAB = - ret.actualTotalOverPeriodLAB + (ticket.actualhrs || 0); - } - if (ticket.ciecacode === "LAR") { - ret.totalThisMonthLAR = ret.totalThisMonthLAR + ticket.productivehrs; - ret.actualTotalOverPeriodLAR = - ret.actualTotalOverPeriodLAR + (ticket.actualhrs || 0); - } - } else if ( - ticketDate.isBetween( - fixedPeriods.startOfLastmonth, - fixedPeriods.endOfLastmonth, - undefined, - "[]" - ) - ) { - ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs; - if (ticket.ciecacode !== "LAR") - ret.totalLastMonthLAB = ret.totalLastMonthLAB + ticket.productivehrs; - if (ticket.ciecacode === "LAR") - ret.totalLastMonthLAR = ret.totalLastMonthLAR + ticket.productivehrs; - } + const ticketsGroupedByDate = _.groupBy(data.timetickets, "date"); + + const listOfDays = Utils.ListOfDaysInCurrentMonth(); + + const combinedData = [], + labData = [], + larData = []; + var acc_comb = 0; + var acc_lab = 0; + var acc_lar = 0; + + listOfDays.forEach((day) => { + const r = { + date: dayjs(day).format("MM/DD"), + actualhrs: 0, + productivehrs: 0 + }; + + const combined = { + accTargetHrs: _.round( + Utils.AsOfDateTargetHours( + bodyshop.scoreboard_target.dailyBodyTarget + bodyshop.scoreboard_target.dailyPaintTarget, + day + ) + + (bodyshop.scoreboard_target.dailyBodyTarget + bodyshop.scoreboard_target.dailyPaintTarget), + 1 + ), + accHrs: 0 + }; + const lab = { + accTargetHrs: _.round( + Utils.AsOfDateTargetHours(bodyshop.scoreboard_target.dailyBodyTarget, day) + + bodyshop.scoreboard_target.dailyBodyTarget, + 1 + ), + accHrs: 0 + }; + const lar = { + accTargetHrs: _.round( + Utils.AsOfDateTargetHours(bodyshop.scoreboard_target.dailyPaintTarget, day) + + bodyshop.scoreboard_target.dailyPaintTarget, + 1 + ), + accHrs: 0 + }; + + if (ticketsGroupedByDate[day]) { + ticketsGroupedByDate[day].forEach((ticket) => { + r.actualhrs = r.actualhrs + ticket.actualhrs; + r.productivehrs = r.productivehrs + ticket.productivehrs; + acc_comb = acc_comb + ticket.productivehrs; + + if (ticket.ciecacode !== "LAR") acc_lab = acc_lab + ticket.productivehrs; + if (ticket.ciecacode === "LAR") acc_lar = acc_lar + ticket.productivehrs; }); + } + combined.accHrs = acc_comb; + lab.accHrs = acc_lab; + lar.accHrs = acc_lar; - ret.totalEffieciencyOverPeriod = ret.actualTotalOverPeriod - ? (ret.totalThisMonth / ret.actualTotalOverPeriod) * 100 - : 0; - ret.totalEffieciencyOverPeriodLAB = ret.actualTotalOverPeriodLAB - ? (ret.totalThisMonthLAB / ret.actualTotalOverPeriodLAB) * 100 - : 0; - ret.totalEffieciencyOverPeriodLAR = ret.actualTotalOverPeriodLAR - ? (ret.totalThisMonthLAR / ret.actualTotalOverPeriodLAR) * 100 - : 0; + combinedData.push({ ...r, ...combined }); + labData.push({ ...r, ...lab }); + larData.push({ ...r, ...lar }); + }); - roundObject(ret); + const jobData = {}; - const ticketsGroupedByDate = _.groupBy(data.timetickets, "date"); + const dataJobs = data.jobs.map((job) => ({ + ...job, + tthrs: job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0) + })); - const listOfDays = Utils.ListOfDaysInCurrentMonth(); + jobData.tthrs = dataJobs.reduce((acc, val) => acc + val.tthrs, 0).toFixed(1); - const combinedData = [], - labData = [], - larData = []; - var acc_comb = 0; - var acc_lab = 0; - var acc_lar = 0; + jobData.count = dataJobs.length.toFixed(0); - listOfDays.forEach((day) => { - const r = { - date: dayjs(day).format("MM/DD"), - actualhrs: 0, - productivehrs: 0, - }; + return { + fixed: ret, + combinedData: combinedData, + labData: labData, + larData: larData, + jobData: jobData + }; + }, [fixedPeriods, data, bodyshop]); - const combined = { - accTargetHrs: _.round( - Utils.AsOfDateTargetHours( - bodyshop.scoreboard_target.dailyBodyTarget + - bodyshop.scoreboard_target.dailyPaintTarget, - day - ) + - (bodyshop.scoreboard_target.dailyBodyTarget + - bodyshop.scoreboard_target.dailyPaintTarget), - 1 - ), - accHrs: 0, - }; - const lab = { - accTargetHrs: _.round( - Utils.AsOfDateTargetHours( - bodyshop.scoreboard_target.dailyBodyTarget, - day - ) + bodyshop.scoreboard_target.dailyBodyTarget, - 1 - ), - accHrs: 0, - }; - const lar = { - accTargetHrs: _.round( - Utils.AsOfDateTargetHours( - bodyshop.scoreboard_target.dailyPaintTarget, - day - ) + bodyshop.scoreboard_target.dailyPaintTarget, - 1 - ), - accHrs: 0, - }; - - if (ticketsGroupedByDate[day]) { - ticketsGroupedByDate[day].forEach((ticket) => { - r.actualhrs = r.actualhrs + ticket.actualhrs; - r.productivehrs = r.productivehrs + ticket.productivehrs; - acc_comb = acc_comb + ticket.productivehrs; - - if (ticket.ciecacode !== "LAR") - acc_lab = acc_lab + ticket.productivehrs; - if (ticket.ciecacode === "LAR") - acc_lar = acc_lar + ticket.productivehrs; - }); - } - combined.accHrs = acc_comb; - lab.accHrs = acc_lab; - lar.accHrs = acc_lar; - - combinedData.push({...r, ...combined}); - labData.push({...r, ...lab}); - larData.push({...r, ...lar}); - }); - - const jobData = {}; - - const dataJobs = data.jobs.map((job) => ({ - ...job, - tthrs: job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0) - })); - - jobData.tthrs = dataJobs - .reduce((acc, val) => acc + val.tthrs, 0) - .toFixed(1); - - jobData.count = dataJobs.length.toFixed(0); - - return { - fixed: ret, - combinedData: combinedData, - labData: labData, - larData: larData, - jobData: jobData, - }; - }, [fixedPeriods, data, bodyshop]); - - if (error) return ; - if (loading) return ; - return ( - -
    - - - - - - - - - - - - - - - - ); + if (error) return ; + if (loading) return ; + return ( + + + + + + + + + + + + + + + + + + ); } function roundObject(inputObj) { - for (var key of Object.keys(inputObj)) { - if (typeof inputObj[key] === "number") { - inputObj[key] = inputObj[key].toFixed(1); - } else if (Array.isArray(inputObj[key])) { - inputObj[key].forEach((item) => roundObject(item)); - } else if (typeof inputObj[key] === "object") { - roundObject(inputObj[key]); - } + for (var key of Object.keys(inputObj)) { + if (typeof inputObj[key] === "number") { + inputObj[key] = inputObj[key].toFixed(1); + } else if (Array.isArray(inputObj[key])) { + inputObj[key].forEach((item) => roundObject(item)); + } else if (typeof inputObj[key] === "object") { + roundObject(inputObj[key]); } + } } diff --git a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.stats.component.jsx b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.stats.component.jsx index cfd6f81d8..9a3682161 100644 --- a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.stats.component.jsx +++ b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.stats.component.jsx @@ -1,642 +1,524 @@ -import {Card, Col, Form, Row, Space, Statistic, Switch, Typography,} from "antd"; +import { Card, Col, Form, Row, Space, Statistic, Switch, Typography } from "antd"; import dayjs from "../../utils/day"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import * as Util from "../scoreboard-targets-table/scoreboard-targets-table.util"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScoreboardTicketsStats); +export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardTicketsStats); function useLocalStorage(key, initialValue) { - const [storedValue, setStoredValue] = useState(() => { - const item = localStorage.getItem(key); - return item ? JSON.parse(item) : initialValue; - }); + const [storedValue, setStoredValue] = useState(() => { + const item = localStorage.getItem(key); + return item ? JSON.parse(item) : initialValue; + }); - useEffect(() => { - localStorage.setItem(key, JSON.stringify(storedValue)); - }, [key, storedValue]); + useEffect(() => { + localStorage.setItem(key, JSON.stringify(storedValue)); + }, [key, storedValue]); - return [storedValue, setStoredValue]; + return [storedValue, setStoredValue]; } -export function ScoreboardTicketsStats({data, jobData, bodyshop}) { - const {t} = useTranslation(); - const [isLarge, setIsLarge] = useLocalStorage("isLargeStatistic", false); +export function ScoreboardTicketsStats({ data, jobData, bodyshop }) { + const { t } = useTranslation(); + const [isLarge, setIsLarge] = useLocalStorage("isLargeStatistic", false); - const statisticSize = isLarge ? 36 : 24; - const statisticWeight = isLarge ? 550 : "normal"; - const daySpan = - Util.CalculateWorkingDaysInPeriod( - dayjs().startOf("week"), - dayjs().endOf("week") - ) > 5 - ? 3 - : 4; + const statisticSize = isLarge ? 36 : 24; + const statisticWeight = isLarge ? 550 : "normal"; + const daySpan = Util.CalculateWorkingDaysInPeriod(dayjs().startOf("week"), dayjs().endOf("week")) > 5 ? 3 : 4; - return ( - - setIsLarge(!isLarge)} - defaultChecked={isLarge} - /> - - } - > - - - {/* Daily Stats */} - - - {[ - "sunday", - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday", - ].map((day) => { - if (bodyshop.workingdays[day] === true) { - return ( - - - - - = - bodyshop.scoreboard_target.dailyBodyTarget + - bodyshop.scoreboard_target.dailyPaintTarget - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - - - {t("scoreboard.labels.body")} - - } - value={data.seperatedThisWeek[day].lab} - valueStyle={{ - color: - parseFloat(data.seperatedThisWeek[day].lab) >= - bodyshop.scoreboard_target.dailyBodyTarget - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - {t("scoreboard.labels.refinish")} - - } - value={data.seperatedThisWeek[day].lar} - valueStyle={{ - color: - parseFloat(data.seperatedThisWeek[day].lar) >= - bodyshop.scoreboard_target.dailyPaintTarget - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - - ); - } else { - return null; - } - })} + return ( + + setIsLarge(!isLarge)} defaultChecked={isLarge} /> + + } + > + + + {/* Daily Stats */} + + + {["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"].map((day) => { + if (bodyshop.workingdays[day] === true) { + return ( + + + + + = + bodyshop.scoreboard_target.dailyBodyTarget + + bodyshop.scoreboard_target.dailyPaintTarget + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + - {/* Weekly Stats */} - {/* This Week */} - - - - - = - Util.WeeklyTargetHrsInPeriod( - bodyshop.scoreboard_target.dailyBodyTarget, - dayjs().startOf("week"), - dayjs().endOf("week"), - bodyshop - ) + - Util.WeeklyTargetHrsInPeriod( - bodyshop.scoreboard_target.dailyPaintTarget, - dayjs().startOf("week"), - dayjs().endOf("week"), - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - - - {t("scoreboard.labels.body")} - - } - value={data.totalThisWeekLAB} - valueStyle={{ - color: - parseFloat(data.totalThisWeekLAB) >= - Util.WeeklyTargetHrsInPeriod( - bodyshop.scoreboard_target.dailyBodyTarget, - dayjs().startOf("week"), - dayjs().endOf("week"), - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - {t("scoreboard.labels.refinish")} - - } - value={data.totalThisWeekLAR} - valueStyle={{ - color: - parseFloat(data.totalThisWeekLAR) >= - Util.WeeklyTargetHrsInPeriod( - bodyshop.scoreboard_target.dailyPaintTarget, - dayjs().startOf("week"), - dayjs().endOf("week"), - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - - {/* Last Week */} - - - - - = - Util.WeeklyTargetHrsInPeriod( - bodyshop.scoreboard_target.dailyBodyTarget, - dayjs().subtract(1, "week").startOf("week"), - dayjs().subtract(1, "week").endOf("week"), - bodyshop - ) + - Util.WeeklyTargetHrsInPeriod( - bodyshop.scoreboard_target.dailyPaintTarget, - dayjs().subtract(1, "week").startOf("week"), - dayjs().subtract(1, "week").endOf("week"), - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - - - {t("scoreboard.labels.body")} - - } - value={data.totalLastWeekLAB} - valueStyle={{ - color: - parseFloat(data.totalLastWeekLAB) >= - Util.WeeklyTargetHrsInPeriod( - bodyshop.scoreboard_target.dailyBodyTarget, - dayjs().subtract(1, "week").startOf("week"), - dayjs().subtract(1, "week").endOf("week"), - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - {t("scoreboard.labels.refinish")} - - } - value={data.totalLastWeekLAR} - valueStyle={{ - color: - parseFloat(data.totalLastWeekLAR) >= - Util.WeeklyTargetHrsInPeriod( - bodyshop.scoreboard_target.dailyPaintTarget, - dayjs().subtract(1, "week").startOf("week"), - dayjs().subtract(1, "week").endOf("week"), - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - - {/* Prior Week */} - - - - - = - Util.WeeklyTargetHrsInPeriod( - bodyshop.scoreboard_target.dailyBodyTarget, - dayjs().subtract(2, "week").startOf("week"), - dayjs().subtract(2, "week").endOf("week"), - bodyshop - ) + - Util.WeeklyTargetHrsInPeriod( - bodyshop.scoreboard_target.dailyPaintTarget, - dayjs().subtract(2, "week").startOf("week"), - dayjs().subtract(2, "week").endOf("week"), - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - - - {t("scoreboard.labels.body")} - - } - value={data.totalPriorWeekLAB} - valueStyle={{ - color: - parseFloat(data.totalPriorWeekLAB) >= - Util.WeeklyTargetHrsInPeriod( - bodyshop.scoreboard_target.dailyBodyTarget, - dayjs().subtract(2, "week").startOf("week"), - dayjs().subtract(2, "week").endOf("week"), - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - {t("scoreboard.labels.refinish")} - - } - value={data.totalPriorWeekLAR} - valueStyle={{ - color: - parseFloat(data.totalPriorWeekLAR) >= - Util.WeeklyTargetHrsInPeriod( - bodyshop.scoreboard_target.dailyPaintTarget, - dayjs().subtract(2, "week").startOf("week"), - dayjs().subtract(2, "week").endOf("week"), - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - + + {t("scoreboard.labels.body")}} + value={data.seperatedThisWeek[day].lab} + valueStyle={{ + color: + parseFloat(data.seperatedThisWeek[day].lab) >= + bodyshop.scoreboard_target.dailyBodyTarget + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + {t("scoreboard.labels.refinish")}} + value={data.seperatedThisWeek[day].lar} + valueStyle={{ + color: + parseFloat(data.seperatedThisWeek[day].lar) >= + bodyshop.scoreboard_target.dailyPaintTarget + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + - {/* Monthly Stats */} - - {/* This Month */} - - - - - = - Util.MonthlyTargetHrs( - bodyshop.scoreboard_target.dailyBodyTarget, - bodyshop - ) + - Util.MonthlyTargetHrs( - bodyshop.scoreboard_target.dailyPaintTarget, - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - - - {t("scoreboard.labels.body")} - - } - value={data.totalThisMonthLAB} - valueStyle={{ - color: - parseFloat(data.totalThisMonthLAB) >= - Util.MonthlyTargetHrs( - bodyshop.scoreboard_target.dailyBodyTarget, - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - {t("scoreboard.labels.refinish")} - - } - value={data.totalThisMonthLAR} - valueStyle={{ - color: - parseFloat(data.totalThisMonthLAR) >= - Util.MonthlyTargetHrs( - bodyshop.scoreboard_target.dailyPaintTarget, - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - - {/* Last Month */} - - - - - = - Util.LastMonthTargetHrs( - bodyshop.scoreboard_target.dailyBodyTarget, - bodyshop - ) + - Util.LastMonthTargetHrs( - bodyshop.scoreboard_target.dailyPaintTarget, - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - - - {t("scoreboard.labels.body")} - - } - value={data.totalLastMonthLAB} - valueStyle={{ - color: - parseFloat(data.totalLastMonthLAB) >= - Util.LastMonthTargetHrs( - bodyshop.scoreboard_target.dailyBodyTarget, - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - {t("scoreboard.labels.refinish")} - - } - value={data.totalLastMonthLAR} - valueStyle={{ - color: - parseFloat(data.totalLastMonthLAR) >= - Util.LastMonthTargetHrs( - bodyshop.scoreboard_target.dailyPaintTarget, - bodyshop - ) - ? "green" - : "red", - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - - {/* Efficiency Over Period */} - - - - - - - - - - - {t("scoreboard.labels.body")} - - } - value={`${data.totalEffieciencyOverPeriodLAB || 0}%`} - valueStyle={{ - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - {t("scoreboard.labels.refinish")} - - } - value={`${data.totalEffieciencyOverPeriodLAR || 0}%`} - valueStyle={{ - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - - - - - - - - - - - - {t("scoreboard.labels.totalhrs")} - - } - value={jobData.tthrs} - valueStyle={{ - fontSize: statisticSize, - fontWeight: statisticWeight, - }} - /> - - - - - - - {/* Disclaimer */} - - *{t("scoreboard.labels.calendarperiod")} - - + + + ); + } else { + return null; + } + })} - - ); + {/* Weekly Stats */} + + {/* This Week */} + + + + + = + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyBodyTarget, + dayjs().startOf("week"), + dayjs().endOf("week"), + bodyshop + ) + + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyPaintTarget, + dayjs().startOf("week"), + dayjs().endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + + + {t("scoreboard.labels.body")}} + value={data.totalThisWeekLAB} + valueStyle={{ + color: + parseFloat(data.totalThisWeekLAB) >= + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyBodyTarget, + dayjs().startOf("week"), + dayjs().endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + {t("scoreboard.labels.refinish")}} + value={data.totalThisWeekLAR} + valueStyle={{ + color: + parseFloat(data.totalThisWeekLAR) >= + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyPaintTarget, + dayjs().startOf("week"), + dayjs().endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + + + {/* Last Week */} + + + + + = + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyBodyTarget, + dayjs().subtract(1, "week").startOf("week"), + dayjs().subtract(1, "week").endOf("week"), + bodyshop + ) + + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyPaintTarget, + dayjs().subtract(1, "week").startOf("week"), + dayjs().subtract(1, "week").endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + + + {t("scoreboard.labels.body")}} + value={data.totalLastWeekLAB} + valueStyle={{ + color: + parseFloat(data.totalLastWeekLAB) >= + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyBodyTarget, + dayjs().subtract(1, "week").startOf("week"), + dayjs().subtract(1, "week").endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + {t("scoreboard.labels.refinish")}} + value={data.totalLastWeekLAR} + valueStyle={{ + color: + parseFloat(data.totalLastWeekLAR) >= + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyPaintTarget, + dayjs().subtract(1, "week").startOf("week"), + dayjs().subtract(1, "week").endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + + + {/* Prior Week */} + + + + + = + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyBodyTarget, + dayjs().subtract(2, "week").startOf("week"), + dayjs().subtract(2, "week").endOf("week"), + bodyshop + ) + + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyPaintTarget, + dayjs().subtract(2, "week").startOf("week"), + dayjs().subtract(2, "week").endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + + + {t("scoreboard.labels.body")}} + value={data.totalPriorWeekLAB} + valueStyle={{ + color: + parseFloat(data.totalPriorWeekLAB) >= + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyBodyTarget, + dayjs().subtract(2, "week").startOf("week"), + dayjs().subtract(2, "week").endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + {t("scoreboard.labels.refinish")}} + value={data.totalPriorWeekLAR} + valueStyle={{ + color: + parseFloat(data.totalPriorWeekLAR) >= + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyPaintTarget, + dayjs().subtract(2, "week").startOf("week"), + dayjs().subtract(2, "week").endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + + + + {/* Monthly Stats */} + + {/* This Month */} + + + + + = + Util.MonthlyTargetHrs(bodyshop.scoreboard_target.dailyBodyTarget, bodyshop) + + Util.MonthlyTargetHrs(bodyshop.scoreboard_target.dailyPaintTarget, bodyshop) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + + + {t("scoreboard.labels.body")}} + value={data.totalThisMonthLAB} + valueStyle={{ + color: + parseFloat(data.totalThisMonthLAB) >= + Util.MonthlyTargetHrs(bodyshop.scoreboard_target.dailyBodyTarget, bodyshop) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + {t("scoreboard.labels.refinish")}} + value={data.totalThisMonthLAR} + valueStyle={{ + color: + parseFloat(data.totalThisMonthLAR) >= + Util.MonthlyTargetHrs(bodyshop.scoreboard_target.dailyPaintTarget, bodyshop) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + + + {/* Last Month */} + + + + + = + Util.LastMonthTargetHrs(bodyshop.scoreboard_target.dailyBodyTarget, bodyshop) + + Util.LastMonthTargetHrs(bodyshop.scoreboard_target.dailyPaintTarget, bodyshop) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + + + {t("scoreboard.labels.body")}} + value={data.totalLastMonthLAB} + valueStyle={{ + color: + parseFloat(data.totalLastMonthLAB) >= + Util.LastMonthTargetHrs(bodyshop.scoreboard_target.dailyBodyTarget, bodyshop) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + {t("scoreboard.labels.refinish")}} + value={data.totalLastMonthLAR} + valueStyle={{ + color: + parseFloat(data.totalLastMonthLAR) >= + Util.LastMonthTargetHrs(bodyshop.scoreboard_target.dailyPaintTarget, bodyshop) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + + + {/* Efficiency Over Period */} + + + + + + + + + + {t("scoreboard.labels.body")}} + value={`${data.totalEffieciencyOverPeriodLAB || 0}%`} + valueStyle={{ + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + {t("scoreboard.labels.refinish")}} + value={`${data.totalEffieciencyOverPeriodLAR || 0}%`} + valueStyle={{ + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + + + + + + + + + + + + {t("scoreboard.labels.totalhrs")}} + value={jobData.tthrs} + valueStyle={{ + fontSize: statisticSize, + fontWeight: statisticWeight + }} + /> + + + + + + + {/* Disclaimer */} + *{t("scoreboard.labels.calendarperiod")} + + + + ); } diff --git a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.targets-table.component.jsx b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.targets-table.component.jsx index 493c50117..7988e69b0 100644 --- a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.targets-table.component.jsx +++ b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.targets-table.component.jsx @@ -1,286 +1,240 @@ -import {CalendarOutlined} from "@ant-design/icons"; -import {Card, Col, Divider, Row, Statistic} from "antd"; +import { CalendarOutlined } from "@ant-design/icons"; +import { Card, Col, Divider, Row, Statistic } from "antd"; import dayjs from "../../utils/day"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import * as Util from "../scoreboard-targets-table/scoreboard-targets-table.util"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); const rowGutter = [16, 16]; -const statSpans = {xs: 24, sm: 3}; +const statSpans = { xs: 24, sm: 3 }; -export function ScoreboardTimeTicketsTargetsTable({bodyshop}) { - const {t} = useTranslation(); +export function ScoreboardTimeTicketsTargetsTable({ bodyshop }) { + const { t } = useTranslation(); - return ( - - - - } - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); + return ( + + + + } + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScoreboardTimeTicketsTargetsTable); +export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardTimeTicketsTargetsTable); diff --git a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.bar.component.jsx b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.bar.component.jsx index fc1af1324..f68b779f9 100644 --- a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.bar.component.jsx +++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.bar.component.jsx @@ -1,78 +1,66 @@ -import {Card} from "antd"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import { Card } from "antd"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import { - Bar, - CartesianGrid, - ComposedChart, - Legend, - ReferenceLine, - ResponsiveContainer, - Tooltip, - XAxis, - YAxis, + Bar, + CartesianGrid, + ComposedChart, + Legend, + ReferenceLine, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis } from "recharts"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import TimeTicketsDatesSelector from "../ticket-tickets-dates-selector/time-tickets-dates-selector.component"; const graphProps = { - strokeWidth: 3, + strokeWidth: 3 }; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScoreboardTicketsBar); +export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardTicketsBar); -export function ScoreboardTicketsBar({data, bodyshop}) { - const {t} = useTranslation(); - return ( - } - > - - - - - - - - - {data && - data.employees.map((e, idx) => ( - - {/* */} - - ))} - - - - ); +export function ScoreboardTicketsBar({ data, bodyshop }) { + const { t } = useTranslation(); + return ( + }> + + + + + + + + + {data && + data.employees.map((e, idx) => ( + + {/* */} + + ))} + + + + ); } diff --git a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx index 9a7d9fbf5..2587cb74e 100644 --- a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx +++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx @@ -1,11 +1,11 @@ -import {useQuery} from "@apollo/client"; -import {Col, Row} from "antd"; +import { useQuery } from "@apollo/client"; +import { Col, Row } from "antd"; import _ from "lodash"; import dayjs from "../../utils/day"; import queryString from "query-string"; -import React, {useMemo} from "react"; -import {useLocation} from "react-router-dom"; -import {QUERY_TIME_TICKETS_IN_RANGE_SB} from "../../graphql/timetickets.queries"; +import React, { useMemo } from "react"; +import { useLocation } from "react-router-dom"; +import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util"; @@ -13,273 +13,224 @@ import ScoreboardTicketsBar from "./scoreboard-timetickets.bar.component"; import ScoreboardTicketsStats from "./scoreboard-timetickets.stats.component"; export default function ScoreboardTimeTickets() { - const searchParams = queryString.parse(useLocation().search); - const {start, end} = searchParams; - const startDate = start - ? dayjs(start) - : dayjs().startOf("week").subtract(7, "day"); - const endDate = end ? dayjs(end) : dayjs().endOf("week"); + const searchParams = queryString.parse(useLocation().search); + const { start, end } = searchParams; + const startDate = start ? dayjs(start) : dayjs().startOf("week").subtract(7, "day"); + const endDate = end ? dayjs(end) : dayjs().endOf("week"); - const fixedPeriods = useMemo(() => { - const endOfThisMonth = dayjs().endOf("month"); - const startofthisMonth = dayjs().startOf("month"); + const fixedPeriods = useMemo(() => { + const endOfThisMonth = dayjs().endOf("month"); + const startofthisMonth = dayjs().startOf("month"); - const endOfLastmonth = dayjs().subtract(1, "month").endOf("month"); - const startOfLastmonth = dayjs().subtract(1, "month").startOf("month"); + const endOfLastmonth = dayjs().subtract(1, "month").endOf("month"); + const startOfLastmonth = dayjs().subtract(1, "month").startOf("month"); - const endOfThisWeek = dayjs().endOf("week"); - const startOfThisWeek = dayjs().startOf("week"); + const endOfThisWeek = dayjs().endOf("week"); + const startOfThisWeek = dayjs().startOf("week"); - const endOfLastWeek = dayjs().subtract(1, "week").endOf("week"); - const startOfLastWeek = dayjs().subtract(1, "week").startOf("week"); + const endOfLastWeek = dayjs().subtract(1, "week").endOf("week"); + const startOfLastWeek = dayjs().subtract(1, "week").startOf("week"); - const allDates = [ - endOfThisMonth, - startofthisMonth, - endOfLastmonth, - startOfLastmonth, - endOfThisWeek, - startOfThisWeek, - endOfLastWeek, - startOfLastWeek, - ]; - const start = dayjs.min(allDates); - const end = dayjs.max(allDates); - return { - start, - end, - endOfThisMonth, - startofthisMonth, - endOfLastmonth, - startOfLastmonth, - endOfThisWeek, - startOfThisWeek, - endOfLastWeek, - startOfLastWeek, + const allDates = [ + endOfThisMonth, + startofthisMonth, + endOfLastmonth, + startOfLastmonth, + endOfThisWeek, + startOfThisWeek, + endOfLastWeek, + startOfLastWeek + ]; + const start = dayjs.min(allDates); + const end = dayjs.max(allDates); + return { + start, + end, + endOfThisMonth, + startofthisMonth, + endOfLastmonth, + startOfLastmonth, + endOfThisWeek, + startOfThisWeek, + endOfLastWeek, + startOfLastWeek + }; + }, []); + + const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE_SB, { + variables: { + start: startDate.format("YYYY-MM-DD"), + end: endDate.format("YYYY-MM-DD"), + fixedStart: fixedPeriods.start.format("YYYY-MM-DD"), + fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"), + jobStart: startDate, + jobEnd: endDate + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + pollInterval: 60000, + skip: !fixedPeriods + }); + + const calculatedData = useMemo(() => { + if (!data) return []; + const ret = { + totalThisWeek: 0, + totalLastWeek: 0, + totalThisMonth: 0, + totalLastMonth: 0, + totalOverPeriod: 0, + actualTotalOverPeriod: 0, + totalEffieciencyOverPeriod: 0, + employees: {} + }; + data.fixedperiod.forEach((ticket) => { + const ticketDate = dayjs(ticket.date); + + if (!ret.employees[ticket.employee.employee_number]) { + ret.employees[ticket.employee.employee_number] = { + totalThisWeek: 0, + totalLastWeek: 0, + totalThisMonth: 0, + totalLastMonth: 0, + totalOverPeriod: 0, + actualTotalOverPeriod: 0, + totalEffieciencyOverPeriod: 0 }; - }, []); + } - const {loading, error, data} = useQuery(QUERY_TIME_TICKETS_IN_RANGE_SB, { - variables: { - start: startDate.format("YYYY-MM-DD"), - end: endDate.format("YYYY-MM-DD"), - fixedStart: fixedPeriods.start.format("YYYY-MM-DD"), - fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"), - jobStart: startDate, - jobEnd: endDate, - }, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - pollInterval: 60000, - skip: !fixedPeriods, + if (ticketDate.isBetween(fixedPeriods.startOfThisWeek, fixedPeriods.endOfThisWeek, undefined, "[]")) { + ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs; + ret.employees[ticket.employee.employee_number].totalThisWeek = + ret.employees[ticket.employee.employee_number].totalThisWeek + ticket.productivehrs; + } else if (ticketDate.isBetween(fixedPeriods.startOfLastWeek, fixedPeriods.endOfLastWeek, undefined, "[]")) { + ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs; + ret.employees[ticket.employee.employee_number].totalLastWeek = + ret.employees[ticket.employee.employee_number].totalLastWeek + ticket.productivehrs; + } + if (ticketDate.isBetween(fixedPeriods.startofthisMonth, fixedPeriods.endOfThisMonth, undefined, "[]")) { + ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs; + ret.employees[ticket.employee.employee_number].totalThisMonth = + ret.employees[ticket.employee.employee_number].totalThisMonth + ticket.productivehrs; + } else if (ticketDate.isBetween(fixedPeriods.startOfLastmonth, fixedPeriods.endOfLastmonth, undefined, "[]")) { + ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs; + ret.employees[ticket.employee.employee_number].totalLastMonth = + ret.employees[ticket.employee.employee_number].totalLastMonth + ticket.productivehrs; + } }); - const calculatedData = useMemo(() => { - if (!data) return []; - const ret = { - totalThisWeek: 0, - totalLastWeek: 0, - totalThisMonth: 0, - totalLastMonth: 0, - totalOverPeriod: 0, - actualTotalOverPeriod: 0, - totalEffieciencyOverPeriod: 0, - employees: {}, - }; - data.fixedperiod.forEach((ticket) => { - const ticketDate = dayjs(ticket.date); + const ticketsGroupedByDate = _.groupBy(data.timetickets, "date"); + const listOfDays = Utils.ListDaysBetween({ + start: startDate, + end: endDate + }); - if (!ret.employees[ticket.employee.employee_number]) { - ret.employees[ticket.employee.employee_number] = { - totalThisWeek: 0, - totalLastWeek: 0, - totalThisMonth: 0, - totalLastMonth: 0, - totalOverPeriod: 0, - actualTotalOverPeriod: 0, - totalEffieciencyOverPeriod: 0, - }; - } + const employees = []; + const ret2 = []; + let totals = { + totalproductive: 0, + totalactual: 0, + employees: {} + }; - if ( - ticketDate.isBetween( - fixedPeriods.startOfThisWeek, - fixedPeriods.endOfThisWeek, - undefined, - "[]" - ) - ) { - ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs; - ret.employees[ticket.employee.employee_number].totalThisWeek = - ret.employees[ticket.employee.employee_number].totalThisWeek + - ticket.productivehrs; - } else if ( - ticketDate.isBetween( - fixedPeriods.startOfLastWeek, - fixedPeriods.endOfLastWeek, - undefined, - "[]" - ) - ) { - ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs; - ret.employees[ticket.employee.employee_number].totalLastWeek = - ret.employees[ticket.employee.employee_number].totalLastWeek + - ticket.productivehrs; - } - if ( - ticketDate.isBetween( - fixedPeriods.startofthisMonth, - fixedPeriods.endOfThisMonth, - undefined, - "[]" - ) - ) { - ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs; - ret.employees[ticket.employee.employee_number].totalThisMonth = - ret.employees[ticket.employee.employee_number].totalThisMonth + - ticket.productivehrs; - } else if ( - ticketDate.isBetween( - fixedPeriods.startOfLastmonth, - fixedPeriods.endOfLastmonth, - undefined, - "[]" - ) - ) { - ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs; - ret.employees[ticket.employee.employee_number].totalLastMonth = - ret.employees[ticket.employee.employee_number].totalLastMonth + - ticket.productivehrs; - } - }); + listOfDays.forEach((day) => { + const r = { + date: dayjs(day).format("MM/DD"), + actualtotal: 0, + productivetotal: 0, + employees: {} + }; - const ticketsGroupedByDate = _.groupBy(data.timetickets, "date"); - const listOfDays = Utils.ListDaysBetween({ - start: startDate, - end: endDate, - }); + if (ticketsGroupedByDate[day]) { + ticketsGroupedByDate[day].forEach((ticket) => { + r.actualtotal = r.actualtotal + ticket.actualhrs; + r.productivetotal = r.productivetotal + ticket.productivehrs; + totals.totalactual = totals.totalactual + ticket.actualhrs; + totals.totalproductive = totals.totalproductive + ticket.productivehrs; - const employees = []; - const ret2 = []; - let totals = { - totalproductive: 0, - totalactual: 0, - employees: {}, - }; + employees.push(ticket.employee.employee_number); + //Add to table data. + ret.employees[ticket.employee.employee_number].totalOverPeriod = + ret.employees[ticket.employee.employee_number].totalOverPeriod + ticket.productivehrs; + ret.employees[ticket.employee.employee_number].actualTotalOverPeriod = + ret.employees[ticket.employee.employee_number].actualTotalOverPeriod + (ticket.actualhrs || 0); - listOfDays.forEach((day) => { - const r = { - date: dayjs(day).format("MM/DD"), - actualtotal: 0, - productivetotal: 0, - employees: {}, + if (!totals.employees[ticket.employee.employee_number]) + totals.employees[ticket.employee.employee_number] = { + totalactual: 0, + totalproductive: 0 }; - if (ticketsGroupedByDate[day]) { - ticketsGroupedByDate[day].forEach((ticket) => { - r.actualtotal = r.actualtotal + ticket.actualhrs; - r.productivetotal = r.productivetotal + ticket.productivehrs; - totals.totalactual = totals.totalactual + ticket.actualhrs; - totals.totalproductive = - totals.totalproductive + ticket.productivehrs; + if (!r.employees[ticket.employee.employee_number]) + r.employees[ticket.employee.employee_number] = { + actual: 0, + productive: 0 + }; - employees.push(ticket.employee.employee_number); - //Add to table data. - ret.employees[ticket.employee.employee_number].totalOverPeriod = - ret.employees[ticket.employee.employee_number].totalOverPeriod + - ticket.productivehrs; - ret.employees[ticket.employee.employee_number].actualTotalOverPeriod = - ret.employees[ticket.employee.employee_number] - .actualTotalOverPeriod + (ticket.actualhrs || 0); + //Add to totals. + totals.employees[ticket.employee.employee_number].totalproductive = + totals.employees[ticket.employee.employee_number].totalproductive + ticket.productivehrs; - if (!totals.employees[ticket.employee.employee_number]) - totals.employees[ticket.employee.employee_number] = { - totalactual: 0, - totalproductive: 0, - }; + totals.employees[ticket.employee.employee_number].totalactual = + totals.employees[ticket.employee.employee_number].totalactual + ticket.actualhrs; + //Add to dailys. + r.employees[ticket.employee.employee_number].productive = + r.employees[ticket.employee.employee_number].productive + ticket.productivehrs; - if (!r.employees[ticket.employee.employee_number]) - r.employees[ticket.employee.employee_number] = { - actual: 0, - productive: 0, - }; - - //Add to totals. - totals.employees[ticket.employee.employee_number].totalproductive = - totals.employees[ticket.employee.employee_number].totalproductive + - ticket.productivehrs; - - totals.employees[ticket.employee.employee_number].totalactual = - totals.employees[ticket.employee.employee_number].totalactual + - ticket.actualhrs; - //Add to dailys. - r.employees[ticket.employee.employee_number].productive = - r.employees[ticket.employee.employee_number].productive + - ticket.productivehrs; - - r.employees[ticket.employee.employee_number].actual = - r.employees[ticket.employee.employee_number].actual + - ticket.actualhrs; - }); - } - - ret2.push(r); + r.employees[ticket.employee.employee_number].actual = + r.employees[ticket.employee.employee_number].actual + ticket.actualhrs; }); + } - // Add total efficiency of employees - const totalActualAndProductive = Object.keys(ret.employees) - .map((key) => { - return {employee_number: key, ...ret.employees[key]}; - }) - .reduce( - (acc, e) => { - return { - totalOverPeriod: acc.totalOverPeriod + e.totalOverPeriod, - actualTotalOverPeriod: - acc.actualTotalOverPeriod + e.actualTotalOverPeriod, - }; - }, - {totalOverPeriod: 0, actualTotalOverPeriod: 0} - ); + ret2.push(r); + }); - ret.totalEffieciencyOverPeriod = - totalActualAndProductive.actualTotalOverPeriod - ? (totalActualAndProductive.totalOverPeriod / - totalActualAndProductive.actualTotalOverPeriod) * - 100 - : 0; + // Add total efficiency of employees + const totalActualAndProductive = Object.keys(ret.employees) + .map((key) => { + return { employee_number: key, ...ret.employees[key] }; + }) + .reduce( + (acc, e) => { + return { + totalOverPeriod: acc.totalOverPeriod + e.totalOverPeriod, + actualTotalOverPeriod: acc.actualTotalOverPeriod + e.actualTotalOverPeriod + }; + }, + { totalOverPeriod: 0, actualTotalOverPeriod: 0 } + ); - roundObject(ret); - roundObject(totals); - roundObject(ret2); + ret.totalEffieciencyOverPeriod = totalActualAndProductive.actualTotalOverPeriod + ? (totalActualAndProductive.totalOverPeriod / totalActualAndProductive.actualTotalOverPeriod) * 100 + : 0; - return { - fixed: ret, - timeperiod: { - totals, - chartData: ret2, - employees: _.uniq(employees), - colors: getColorArray(employees.length), - }, - }; - }, [fixedPeriods, data, startDate, endDate]); + roundObject(ret); + roundObject(totals); + roundObject(ret2); - if (error) return ; - if (loading) return ; - return ( - - - - - - - - - ); + return { + fixed: ret, + timeperiod: { + totals, + chartData: ret2, + employees: _.uniq(employees), + colors: getColorArray(employees.length) + } + }; + }, [fixedPeriods, data, startDate, endDate]); + + if (error) return ; + if (loading) return ; + return ( + + + + + + + + + ); } //Include a filter by employee. @@ -290,63 +241,60 @@ export default function ScoreboardTimeTickets() { //Hours produced by employee by day for time period. function getColorArray(num) { - return [ - "#3366cc", - "#dc3912", - "#ff9900", - "#109618", - "#990099", - "#0099c6", - "#dd4477", - "#66aa00", - "#b82e2e", - "#316395", - "#3366cc", - "#994499", - "#22aa99", - "#aaaa11", - "#6633cc", - "#e67300", - "#8b0707", - "#651067", - "#329262", - "#5574a6", - "#3b3eac", - "#b77322", - "#16d620", - "#b91383", - "#f4359e", - "#9c5935", - "#a9c413", - "#2a778d", - "#668d1c", - "#bea413", - "#0c5922", - "#743411", - ]; - // var result = []; - // for (var i = 0; i < num; i += 1) { - // var letters = "0123456789ABCDEF".split(""); - // var color = "#"; - // for (var j = 0; j < 6; j += 1) { - // color += letters[Math.floor(Math.random() * 16)]; - // } - // result.push(color); - // } - // return result; + return [ + "#3366cc", + "#dc3912", + "#ff9900", + "#109618", + "#990099", + "#0099c6", + "#dd4477", + "#66aa00", + "#b82e2e", + "#316395", + "#3366cc", + "#994499", + "#22aa99", + "#aaaa11", + "#6633cc", + "#e67300", + "#8b0707", + "#651067", + "#329262", + "#5574a6", + "#3b3eac", + "#b77322", + "#16d620", + "#b91383", + "#f4359e", + "#9c5935", + "#a9c413", + "#2a778d", + "#668d1c", + "#bea413", + "#0c5922", + "#743411" + ]; + // var result = []; + // for (var i = 0; i < num; i += 1) { + // var letters = "0123456789ABCDEF".split(""); + // var color = "#"; + // for (var j = 0; j < 6; j += 1) { + // color += letters[Math.floor(Math.random() * 16)]; + // } + // result.push(color); + // } + // return result; } function roundObject(inputObj) { - for (var key of Object.keys(inputObj)) { - if (typeof inputObj[key] === "number" && inputObj[key] !== 0) { - inputObj[key] = - inputObj[key] && inputObj[key].toFixed - ? inputObj[key].toFixed(1) - : inputObj[key]; //Math.round(inputObj[key] * 100) / 100; - } else if (Array.isArray(inputObj[key])) { - inputObj[key].forEach((item) => roundObject(item)); - } else if (typeof inputObj[key] === "object") { - roundObject(inputObj[key]); - } + for (var key of Object.keys(inputObj)) { + if (typeof inputObj[key] === "number" && inputObj[key] !== 0) { + inputObj[key] = inputObj[key] && inputObj[key].toFixed ? inputObj[key].toFixed(1) : inputObj[key]; //Math.round(inputObj[key] * 100) / 100; + } else if (Array.isArray(inputObj[key])) { + inputObj[key].forEach((item) => roundObject(item)); + } else if (typeof inputObj[key] === "object") { + roundObject(inputObj[key]); } + } } diff --git a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx index 6240ff64c..baa58f685 100644 --- a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx +++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx @@ -1,139 +1,116 @@ -import {Card, Col, Row, Statistic, Table, Typography} from "antd"; +import { Card, Col, Row, Statistic, Table, Typography } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScoreboardTicketsStats); +export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardTicketsStats); -export function ScoreboardTicketsStats({data, bodyshop}) { - const {t} = useTranslation(); - const columns = [ - { - title: t("employees.fields.employee_number"), - dataIndex: "employee_number", - key: "employee_number", - sorter: (a, b) => a.employee_number - b.employee_number, - }, - { - title: t("scoreboard.labels.thisweek"), - dataIndex: "totalThisWeek", - key: "totalThisWeek", - sorter: (a, b) => a.totalThisWeek - b.totalThisWeek, - }, - { - title: t("scoreboard.labels.lastweek"), - dataIndex: "totalLastWeek", - key: "totalLastWeek", - sorter: (a, b) => a.totalLastWeek - b.totalLastWeek, - }, - { - title: t("scoreboard.labels.thismonth"), - dataIndex: "totalThisMonth", - key: "totalThisMonth", - sorter: (a, b) => a.totalThisMonth - b.totalThisMonth, - }, - { - title: t("scoreboard.labels.lastmonth"), - dataIndex: "totalLastMonth", - key: "totalLastMonth", - sorter: (a, b) => a.totalLastMonth - b.totalLastMonth, - }, - { - title: t("scoreboard.labels.totaloverperiod"), - dataIndex: "totalOverPeriod", - key: "totalOverPeriod", - sorter: (a, b) => a.totalOverPeriod - b.totalOverPeriod, - }, - { - title: t("scoreboard.labels.efficiencyoverperiod"), - dataIndex: "efficiencyoverperiod", - key: "efficiencyoverperiod", - render: (text, record) => - `${( - (record.totalOverPeriod / (record.actualTotalOverPeriod || 0.1)) * - 100 - ).toFixed(1)} %`, - }, - ]; +export function ScoreboardTicketsStats({ data, bodyshop }) { + const { t } = useTranslation(); + const columns = [ + { + title: t("employees.fields.employee_number"), + dataIndex: "employee_number", + key: "employee_number", + sorter: (a, b) => a.employee_number - b.employee_number + }, + { + title: t("scoreboard.labels.thisweek"), + dataIndex: "totalThisWeek", + key: "totalThisWeek", + sorter: (a, b) => a.totalThisWeek - b.totalThisWeek + }, + { + title: t("scoreboard.labels.lastweek"), + dataIndex: "totalLastWeek", + key: "totalLastWeek", + sorter: (a, b) => a.totalLastWeek - b.totalLastWeek + }, + { + title: t("scoreboard.labels.thismonth"), + dataIndex: "totalThisMonth", + key: "totalThisMonth", + sorter: (a, b) => a.totalThisMonth - b.totalThisMonth + }, + { + title: t("scoreboard.labels.lastmonth"), + dataIndex: "totalLastMonth", + key: "totalLastMonth", + sorter: (a, b) => a.totalLastMonth - b.totalLastMonth + }, + { + title: t("scoreboard.labels.totaloverperiod"), + dataIndex: "totalOverPeriod", + key: "totalOverPeriod", + sorter: (a, b) => a.totalOverPeriod - b.totalOverPeriod + }, + { + title: t("scoreboard.labels.efficiencyoverperiod"), + dataIndex: "efficiencyoverperiod", + key: "efficiencyoverperiod", + render: (text, record) => + `${((record.totalOverPeriod / (record.actualTotalOverPeriod || 0.1)) * 100).toFixed(1)} %` + } + ]; - const tableData = data - ? Object.keys(data.employees).map((key) => { - return {employee_number: key, ...data.employees[key]}; - }) - : []; + const tableData = data + ? Object.keys(data.employees).map((key) => { + return { employee_number: key, ...data.employees[key] }; + }) + : []; - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {t("scoreboard.labels.calendarperiod")} - - - -
    - - - - ); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + {t("scoreboard.labels.calendarperiod")} + + +
    + + + + ); } diff --git a/client/src/components/shop-csi-config-form/shop-csi-config-form.component.jsx b/client/src/components/shop-csi-config-form/shop-csi-config-form.component.jsx index 603643c94..25c590a78 100644 --- a/client/src/components/shop-csi-config-form/shop-csi-config-form.component.jsx +++ b/client/src/components/shop-csi-config-form/shop-csi-config-form.component.jsx @@ -1,24 +1,20 @@ -import {Form} from "antd"; +import { Form } from "antd"; import React from "react"; import ConfigFormComponents from "../config-form-components/config-form-components.component"; -export default function ShopCsiConfigForm({selectedCsi}) { - const readOnly = !!selectedCsi; - const [form] = Form.useForm(); - const handleFinish = (values) => { - }; +export default function ShopCsiConfigForm({ selectedCsi }) { + const readOnly = !!selectedCsi; + const [form] = Form.useForm(); + const handleFinish = (values) => {}; - return ( -
    - {readOnly} - {selectedCsi && ( -
    - - - )} -
    - ); + return ( +
    + {readOnly} + {selectedCsi && ( +
    + + + )} +
    + ); } diff --git a/client/src/components/shop-csi-config/shop-csi-config.component.jsx b/client/src/components/shop-csi-config/shop-csi-config.component.jsx index 5958d69cd..d714df6d3 100644 --- a/client/src/components/shop-csi-config/shop-csi-config.component.jsx +++ b/client/src/components/shop-csi-config/shop-csi-config.component.jsx @@ -1,53 +1,46 @@ -import {CheckCircleFilled} from "@ant-design/icons"; -import {useQuery} from "@apollo/client"; -import {Button, Col, List, Row} from "antd"; -import React, {useState} from "react"; +import { CheckCircleFilled } from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import { Button, Col, List, Row } from "antd"; +import React, { useState } from "react"; -import {useTranslation} from "react-i18next"; -import {GET_ALL_QUESTION_SETS} from "../../graphql/csi.queries"; -import {DateFormatter} from "../../utils/DateFormatter"; +import { useTranslation } from "react-i18next"; +import { GET_ALL_QUESTION_SETS } from "../../graphql/csi.queries"; +import { DateFormatter } from "../../utils/DateFormatter"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import ShopCsiConfigForm from "../shop-csi-config-form/shop-csi-config-form.component"; export default function ShopCsiConfig() { - const {loading, error, data} = useQuery(GET_ALL_QUESTION_SETS, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); - const [selectedCsi, setselectedCsi] = useState(null); - const {t} = useTranslation(); + const { loading, error, data } = useQuery(GET_ALL_QUESTION_SETS, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const [selectedCsi, setselectedCsi] = useState(null); + const { t } = useTranslation(); - if (loading) return ; - if (error) return ; - return ( -
    - - -
    - ( - - {item.created_at} - {item.csis_aggregate.aggregate.count} - - {item.current ? ( - - ) : ( - - )} - - )} - /> - - - - - - - - ); + if (loading) return ; + if (error) return ; + return ( +
    + +
    + ( + + {item.created_at} + {item.csis_aggregate.aggregate.count} + + {item.current ? : } + + )} + /> + + + + + + + + ); } diff --git a/client/src/components/shop-employees/shop-employees-add-vacation.component.jsx b/client/src/components/shop-employees/shop-employees-add-vacation.component.jsx index 1365f1e04..61d9975aa 100644 --- a/client/src/components/shop-employees/shop-employees-add-vacation.component.jsx +++ b/client/src/components/shop-employees/shop-employees-add-vacation.component.jsx @@ -1,125 +1,117 @@ -import {useMutation} from "@apollo/client"; -import {Button, Card, Form, notification, Popover, Space} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Card, Form, notification, Popover, Space } from "antd"; import dayjs from "../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {INSERT_VACATION} from "../../graphql/employees.queries"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { INSERT_VACATION } from "../../graphql/employees.queries"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; -export default function ShopEmployeeAddVacation({employee}) { - const {t} = useTranslation(); - const [insertVacation] = useMutation(INSERT_VACATION); +export default function ShopEmployeeAddVacation({ employee }) { + const { t } = useTranslation(); + const [insertVacation] = useMutation(INSERT_VACATION); - const [loading, setLoading] = useState(false); - const [form] = Form.useForm(); - const [visibility, setVisibility] = useState(false); + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + const [visibility, setVisibility] = useState(false); - const handleFinish = async (values) => { - logImEXEvent("employee_add_vacation"); + const handleFinish = async (values) => { + logImEXEvent("employee_add_vacation"); - setLoading(true); - let result; + setLoading(true); + let result; - result = await insertVacation({ - variables: {vacation: {...values, employeeid: employee.id}}, - update(cache, {data}) { - cache.modify({ - id: cache.identify({id: employee.id, __typename: "employees"}), - fields: { - employee_vacations(ex) { - return [data.insert_employee_vacation_one, ...ex]; - }, - }, - }); - }, + result = await insertVacation({ + variables: { vacation: { ...values, employeeid: employee.id } }, + update(cache, { data }) { + cache.modify({ + id: cache.identify({ id: employee.id, __typename: "employees" }), + fields: { + employee_vacations(ex) { + return [data.insert_employee_vacation_one, ...ex]; + } + } }); + } + }); - if (!!result.errors) { - notification["error"]({ - message: t("employees.errors.adding", { - message: JSON.stringify(result.errors), - }), - }); - } else { - notification["success"]({ - message: t("employees.successes.vacationadded"), - }); - } - form.resetFields(); - setLoading(false); - setVisibility(false); - }; + if (!!result.errors) { + notification["error"]({ + message: t("employees.errors.adding", { + message: JSON.stringify(result.errors) + }) + }); + } else { + notification["success"]({ + message: t("employees.successes.vacationadded") + }); + } + form.resetFields(); + setLoading(false); + setVisibility(false); + }; - const overlay = ( - -
    - - - - ({ - async validator(rule, value) { - if (value) { - const {start} = form.getFieldsValue(); - if (dayjs(start).isAfter(dayjs(value))) { - return Promise.reject( - t("employees.labels.endmustbeafterstart") - ); - } else { - return Promise.resolve(); - } - } else { - return Promise.resolve(); - } - }, - }), - ]} - > - - + const overlay = ( + + + + + + ({ + async validator(rule, value) { + if (value) { + const { start } = form.getFieldsValue(); + if (dayjs(start).isAfter(dayjs(value))) { + return Promise.reject(t("employees.labels.endmustbeafterstart")); + } else { + return Promise.resolve(); + } + } else { + return Promise.resolve(); + } + } + }) + ]} + > + + - - - - - - - ); + + + + + +
    + ); - const handleClick = (e) => { - setVisibility(true); - }; + const handleClick = (e) => { + setVisibility(true); + }; - return ( - - - - ); + return ( + + + + ); } diff --git a/client/src/components/shop-employees/shop-employees-form.component.jsx b/client/src/components/shop-employees/shop-employees-form.component.jsx index 472da39e1..3da210fb0 100644 --- a/client/src/components/shop-employees/shop-employees-form.component.jsx +++ b/client/src/components/shop-employees/shop-employees-form.component.jsx @@ -1,431 +1,390 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {useApolloClient, useMutation, useQuery} from "@apollo/client"; -import {Button, Card, Form, Input, InputNumber, notification, Select, Switch, Table,} from "antd"; -import {useForm} from "antd/es/form/Form"; +import { DeleteFilled } from "@ant-design/icons"; +import { useApolloClient, useMutation, useQuery } from "@apollo/client"; +import { Button, Card, Form, Input, InputNumber, notification, Select, Switch, Table } from "antd"; +import { useForm } from "antd/es/form/Form"; import dayjs from "../../utils/day"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import { - CHECK_EMPLOYEE_NUMBER, - DELETE_VACATION, - INSERT_EMPLOYEES, - QUERY_EMPLOYEE_BY_ID, - QUERY_USERS_BY_EMAIL, - UPDATE_EMPLOYEE, + CHECK_EMPLOYEE_NUMBER, + DELETE_VACATION, + INSERT_EMPLOYEES, + QUERY_EMPLOYEE_BY_ID, + QUERY_USERS_BY_EMAIL, + UPDATE_EMPLOYEE } from "../../graphql/employees.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CiecaSelect from "../../utils/Ciecaselect"; -import {DateFormatter} from "../../utils/DateFormatter"; +import { DateFormatter } from "../../utils/DateFormatter"; import AlertComponent from "../alert/alert.component"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component"; import queryString from "query-string"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function ShopEmployeesFormComponent({bodyshop}) { - const {t} = useTranslation(); - const [form] = useForm(); - const history = useNavigate(); - const search = queryString.parse(useLocation().search); - const [deleteVacation] = useMutation(DELETE_VACATION); - const {error, data} = useQuery(QUERY_EMPLOYEE_BY_ID, { - variables: {id: search.employeeId}, - skip: !search.employeeId || search.employeeId === "new", - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); +export function ShopEmployeesFormComponent({ bodyshop }) { + const { t } = useTranslation(); + const [form] = useForm(); + const history = useNavigate(); + const search = queryString.parse(useLocation().search); + const [deleteVacation] = useMutation(DELETE_VACATION); + const { error, data } = useQuery(QUERY_EMPLOYEE_BY_ID, { + variables: { id: search.employeeId }, + skip: !search.employeeId || search.employeeId === "new", + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const {treatments: {Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, - }); + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); - const client = useApolloClient(); - useEffect(() => { - if (data && data.employees_by_pk) form.setFieldsValue(data.employees_by_pk); - else { - form.resetFields(); + const client = useApolloClient(); + useEffect(() => { + if (data && data.employees_by_pk) form.setFieldsValue(data.employees_by_pk); + else { + form.resetFields(); + } + }, [form, data, search.employeeId]); + + const [updateEmployee] = useMutation(UPDATE_EMPLOYEE); + const [insertEmployees] = useMutation(INSERT_EMPLOYEES); + + const handleFinish = (values) => { + if (search.employeeId && search.employeeId !== "new") { + //Update a record. + logImEXEvent("shop_employee_update"); + + updateEmployee({ + variables: { + id: search.employeeId, + employee: { + ...values, + user_email: values.user_email === "" ? null : values.user_email + } } - }, [form, data, search.employeeId]); - - const [updateEmployee] = useMutation(UPDATE_EMPLOYEE); - const [insertEmployees] = useMutation(INSERT_EMPLOYEES); - - const handleFinish = (values) => { - if (search.employeeId && search.employeeId !== "new") { - //Update a record. - logImEXEvent("shop_employee_update"); - - updateEmployee({ - variables: { - id: search.employeeId, - employee: { - ...values, - user_email: values.user_email === "" ? null : values.user_email, - }, - }, + }) + .then((r) => { + notification["success"]({ + message: t("employees.successes.save") + }); + }) + .catch((error) => { + notification["error"]({ + message: t("employees.errors.save", { + message: JSON.stringify(error) }) - .then((r) => { - notification["success"]({ - message: t("employees.successes.save"), - }); - }) - .catch((error) => { - notification["error"]({ - message: t("employees.errors.save", { - message: JSON.stringify(error), - }), - }); - }); - } else { - //New record, insert it. - logImEXEvent("shop_employee_insert"); + }); + }); + } else { + //New record, insert it. + logImEXEvent("shop_employee_insert"); - insertEmployees({ - variables: {employees: [{...values, shopid: bodyshop.id}]}, - refetchQueries: ["QUERY_EMPLOYEES"], - }).then((r) => { - search.employeeId = r.data.insert_employees.returning[0].id; - history({search: queryString.stringify(search)}); - notification["success"]({ - message: t("employees.successes.save"), + insertEmployees({ + variables: { employees: [{ ...values, shopid: bodyshop.id }] }, + refetchQueries: ["QUERY_EMPLOYEES"] + }).then((r) => { + search.employeeId = r.data.insert_employees.returning[0].id; + history({ search: queryString.stringify(search) }); + notification["success"]({ + message: t("employees.successes.save") + }); + }); + } + }; + + if (!search.employeeId) return null; + if (error) return ; + + const columns = [ + { + title: t("employees.fields.vacation.start"), + dataIndex: "start", + key: "start", + render: (text, record) => {text} + }, + { + title: t("employees.fields.vacation.end"), + dataIndex: "end", + key: "end", + render: (text, record) => {text} + }, + { + title: t("employees.fields.vacation.length"), + dataIndex: "length", + key: "length", + render: (text, record) => dayjs(record.end).diff(dayjs(record.start), "day", true).toFixed(1) + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => ( + - ), - }, - ]; - - return ( - form.submit()}> - {t("general.actions.save")} - - } + }} > -
    - - - - - - - - ({ - async validator(rule, value) { - if (value) { - const response = await client.query({ - query: CHECK_EMPLOYEE_NUMBER, - variables: { - employeenumber: value, - }, - }); + + + ) + } + ]; - if ( - response.data.employees_aggregate.aggregate.count === 0 - ) { - return Promise.resolve(); - } else if ( - response.data.employees_aggregate.nodes.length === 1 && - response.data.employees_aggregate.nodes[0].id === - form.getFieldValue("id") - ) { - return Promise.resolve(); - } - return Promise.reject( - t("employees.validation.unique_employee_number") - ); - } else { - return Promise.resolve(); - } - }, - }), - ]} - > - - - - - - - - - - - - - - - - - - - - ({ - async validator(rule, value) { - const user_email = getFieldValue("user_email"); + return ( + form.submit()}> + {t("general.actions.save")} + + } + > + + + + + + + + + ({ + async validator(rule, value) { + if (value) { + const response = await client.query({ + query: CHECK_EMPLOYEE_NUMBER, + variables: { + employeenumber: value + } + }); - if (user_email && value) { - const response = await client.query({ - query: QUERY_USERS_BY_EMAIL, - variables: { - email: user_email, - }, - }); + if (response.data.employees_aggregate.aggregate.count === 0) { + return Promise.resolve(); + } else if ( + response.data.employees_aggregate.nodes.length === 1 && + response.data.employees_aggregate.nodes[0].id === form.getFieldValue("id") + ) { + return Promise.resolve(); + } + return Promise.reject(t("employees.validation.unique_employee_number")); + } else { + return Promise.resolve(); + } + } + }) + ]} + > + + + + + + + + + + + + + + + + + + + + ({ + async validator(rule, value) { + const user_email = getFieldValue("user_email"); - if (response.data.users.length === 1) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.useremailmustexist") - ); - } else { - return Promise.resolve(); - } - }, - }), + if (user_email && value) { + const response = await client.query({ + query: QUERY_USERS_BY_EMAIL, + variables: { + email: user_email + } + }); + + if (response.data.users.length === 1) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.useremailmustexist")); + } else { + return Promise.resolve(); + } + } + }) + ]} + > + + + + + + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + - - - - - - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - + + {t("timetickets.labels.shift")} + - {bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on" - ? CiecaSelect(false, true) - : bodyshop.md_responsibility_centers.costs.map( - (c) => ( - - {c.name} - - ) - )} - - - - - - { - remove(field.name); - }} - /> - - - - ))} - - - -
    - ); + {bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on" + ? CiecaSelect(false, true) + : bodyshop.md_responsibility_centers.costs.map((c) => ( + + {c.name} + + ))} + +
    + + + + { + remove(field.name); + }} + /> + + + + ))} + + + +
    + ); + }} +
    + -
    ( - - )} - columns={columns} - rowKey={"id"} - dataSource={data ? data.employees_by_pk.employee_vacations : []} - /> - - ); +
    } + columns={columns} + rowKey={"id"} + dataSource={data ? data.employees_by_pk.employee_vacations : []} + /> + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ShopEmployeesFormComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ShopEmployeesFormComponent); diff --git a/client/src/components/shop-employees/shop-employees-list.component.jsx b/client/src/components/shop-employees/shop-employees-list.component.jsx index 9505cbcf2..2add19f34 100644 --- a/client/src/components/shop-employees/shop-employees-list.component.jsx +++ b/client/src/components/shop-employees/shop-employees-list.component.jsx @@ -1,142 +1,130 @@ -import {Button, Table} from "antd"; +import { Button, Table } from "antd"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {useLocation, useNavigate} from "react-router-dom"; -import {alphaSort} from "../../utils/sorters"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useLocation, useNavigate } from "react-router-dom"; +import { alphaSort } from "../../utils/sorters"; -export default function ShopEmployeesListComponent({loading, employees}) { - const {t} = useTranslation(); - const history = useNavigate(); - const search = queryString.parse(useLocation().search); +export default function ShopEmployeesListComponent({ loading, employees }) { + const { t } = useTranslation(); + const history = useNavigate(); + const search = queryString.parse(useLocation().search); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); - const handleOnRowClick = (record) => { - if (record) { - search.employeeId = record.id; - history({search: queryString.stringify(search)}); - } else { - delete search.employeeId; - history({search: queryString.stringify(search)}); + const handleOnRowClick = (record) => { + if (record) { + search.employeeId = record.id; + history({ search: queryString.stringify(search) }); + } else { + delete search.employeeId; + history({ search: queryString.stringify(search) }); + } + }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + const columns = [ + { + title: t("employees.fields.employee_number"), + dataIndex: "employee_number", + key: "employee_number", + sorter: (a, b) => alphaSort(a.employee_number, b.employee_number), + sortOrder: state.sortedInfo.columnKey === "employee_number" && state.sortedInfo.order + }, + { + title: t("employees.labels.name"), + dataIndex: "employee_name", + key: "employee_name", + sorter: (a, b) => + alphaSort( + `${a.first_name || ""} ${a.last_name || ""}`.trim(), + `${b.first_name || ""} ${b.last_name || ""}`.trim() + ), + sortOrder: state.sortedInfo.columnKey === "employee_name" && state.sortedInfo.order, + render: (text, record) => `${record.first_name || ""} ${record.last_name || ""}`.trim() + }, + { + title: t("employees.labels.rate_type"), + dataIndex: "rate_type", + key: "rate_type", + sorter: (a, b) => Number(a.flat_rate) - Number(b.flat_rate), + sortOrder: state.sortedInfo.columnKey === "rate_type" && state.sortedInfo.order, + filters: [ + { + text: t("employees.labels.flat_rate"), + value: true + }, + { + text: t("employees.labels.straight_time"), + value: false } - }; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - const columns = [ + ], + onFilter: (value, record) => value === record.flate_rate, + render: (text, record) => + record.flat_rate ? t("employees.labels.flat_rate") : t("employees.labels.straight_time") + }, + { + title: t("employees.labels.status"), + dataIndex: "active", + key: "active", + sorter: (a, b) => Number(a.active) - Number(b.active), + sortOrder: state.sortedInfo.columnKey === "active" && state.sortedInfo.order, + filters: [ { - title: t("employees.fields.employee_number"), - dataIndex: "employee_number", - key: "employee_number", - sorter: (a, b) => alphaSort(a.employee_number, b.employee_number), - sortOrder: - state.sortedInfo.columnKey === "employee_number" && - state.sortedInfo.order, + text: t("employees.labels.active"), + value: true }, { - title: t("employees.labels.name"), - dataIndex: "employee_name", - key: "employee_name", - sorter: (a, b) => - alphaSort( - `${a.first_name || ""} ${a.last_name || ""}`.trim(), - `${b.first_name || ""} ${b.last_name || ""}`.trim() - ), - sortOrder: - state.sortedInfo.columnKey === "employee_name" && - state.sortedInfo.order, - render: (text, record) => - `${record.first_name || ""} ${record.last_name || ""}`.trim(), - }, - { - title: t("employees.labels.rate_type"), - dataIndex: "rate_type", - key: "rate_type", - sorter: (a, b) => Number(a.flat_rate) - Number(b.flat_rate), - sortOrder: - state.sortedInfo.columnKey === "rate_type" && state.sortedInfo.order, - filters: [ - { - text: t("employees.labels.flat_rate"), - value: true, - }, - { - text: t("employees.labels.straight_time"), - value: false, - }, - ], - onFilter: (value, record) => value === record.flate_rate, - render: (text, record) => - record.flat_rate - ? t("employees.labels.flat_rate") - : t("employees.labels.straight_time"), - }, - { - title: t("employees.labels.status"), - dataIndex: "active", - key: "active", - sorter: (a, b) => Number(a.active) - Number(b.active), - sortOrder: - state.sortedInfo.columnKey === "active" && state.sortedInfo.order, - filters: [ - { - text: t("employees.labels.active"), - value: true, - }, - { - text: t("employees.labels.inactive"), - value: false, - }, - ], - onFilter: (value, record) => value === record.active, - render: (text, record) => - record.active - ? t("employees.labels.active") - : t("employees.labels.inactive"), - }, - ]; - return ( -
    -
    { - return ( - - ); - }} - loading={loading} - pagination={{position: "top"}} - columns={columns} - rowKey="id" - dataSource={employees} - rowSelection={{ - onSelect: (props) => { - search.employeeId = props.id; - history({search: queryString.stringify(search)}); - }, - type: "radio", - selectedRowKeys: [search.employeeId], - }} - onChange={handleTableChange} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - handleOnRowClick(record); - }, - }; - }} - /> - - ); + text: t("employees.labels.inactive"), + value: false + } + ], + onFilter: (value, record) => value === record.active, + render: (text, record) => (record.active ? t("employees.labels.active") : t("employees.labels.inactive")) + } + ]; + return ( +
    +
    { + return ( + + ); + }} + loading={loading} + pagination={{ position: "top" }} + columns={columns} + rowKey="id" + dataSource={employees} + rowSelection={{ + onSelect: (props) => { + search.employeeId = props.id; + history({ search: queryString.stringify(search) }); + }, + type: "radio", + selectedRowKeys: [search.employeeId] + }} + onChange={handleTableChange} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + handleOnRowClick(record); + } + }; + }} + /> + + ); } diff --git a/client/src/components/shop-employees/shop-employees.container.jsx b/client/src/components/shop-employees/shop-employees.container.jsx index 919222538..1a1bdd2de 100644 --- a/client/src/components/shop-employees/shop-employees.container.jsx +++ b/client/src/components/shop-employees/shop-employees.container.jsx @@ -1,37 +1,34 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {QUERY_EMPLOYEES} from "../../graphql/employees.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { QUERY_EMPLOYEES } from "../../graphql/employees.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import ShopEmployeesFormComponent from "./shop-employees-form.component"; import ShopEmployeesListComponent from "./shop-employees-list.component"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -function ShopEmployeesContainer({bodyshop}) { - const {loading, error, data} = useQuery(QUERY_EMPLOYEES, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); +function ShopEmployeesContainer({ bodyshop }) { + const { loading, error, data } = useQuery(QUERY_EMPLOYEES, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - if (error) return ; + if (error) return ; - return ( -
    - - - - -
    - ); + return ( +
    + + + + +
    + ); } export default connect(mapStateToProps, null)(ShopEmployeesContainer); diff --git a/client/src/components/shop-info/shop-info.component.jsx b/client/src/components/shop-info/shop-info.component.jsx index ae116411b..90f4a5034 100644 --- a/client/src/components/shop-info/shop-info.component.jsx +++ b/client/src/components/shop-info/shop-info.component.jsx @@ -1,10 +1,10 @@ -import {useSplitTreatments} from "@splitsoftware/splitio-react"; -import {Button, Card, Tabs} from "antd"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { Button, Card, Tabs } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import ShopInfoGeneral from "./shop-info.general.component"; import ShopInfoIntakeChecklistComponent from "./shop-info.intake.component"; import ShopInfoLaborRates from "./shop-info.laborrates.component"; @@ -15,116 +15,121 @@ import ShopInfoResponsibilityCenterComponent from "./shop-info.responsibilitycen import ShopInfoROStatusComponent from "./shop-info.rostatus.component"; import ShopInfoSchedulingComponent from "./shop-info.scheduling.component"; import ShopInfoSpeedPrint from "./shop-info.speedprint.component"; -import {useLocation, useNavigate} from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; import ShopInfoTaskPresets from "./shop-info.task-presets.component"; import queryString from "query-string"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoComponent); -export function ShopInfoComponent({bodyshop, form, saveLoading}) { +export function ShopInfoComponent({ bodyshop, form, saveLoading }) { + const { + treatments: { CriticalPartsScanning, Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["CriticalPartsScanning", "Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); - const {treatments: {CriticalPartsScanning, Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["CriticalPartsScanning","Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, - }); + const { t } = useTranslation(); + const history = useNavigate(); + const location = useLocation(); + const search = queryString.parse(location.search); - const {t} = useTranslation(); - const history = useNavigate(); - const location = useLocation(); - const search = queryString.parse(location.search); - - const tabItems = [ + const tabItems = [ + { + key: "general", + label: t("bodyshop.labels.shopinfo"), + children: + }, + { + key: "speedprint", + label: t("bodyshop.labels.speedprint"), + children: + }, + { + key: "rbac", + label: t("bodyshop.labels.rbac"), + children: + }, + { + key: "roStatus", + label: t("bodyshop.labels.jobstatuses"), + children: + }, + { + key: "scheduling", + label: t("bodyshop.labels.scheduling"), + children: + }, + { + key: "orderStatus", + label: t("bodyshop.labels.orderstatuses"), + children: + }, + { + key: "responsibilityCenters", + label: t("bodyshop.labels.responsibilitycenters.title"), + children: + }, + ...InstanceRenderManager({ + imex: [ { - key: "general", - label: t("bodyshop.labels.shopinfo"), - children: , - }, - { - key: "speedprint", - label: t("bodyshop.labels.speedprint"), - children: , - }, - { - key: "rbac", - label: t("bodyshop.labels.rbac"), - children: , - }, - { - key: "roStatus", - label: t("bodyshop.labels.jobstatuses"), - children: , - }, - { - key: "scheduling", - label: t("bodyshop.labels.scheduling"), - children: , - }, - { - key: "orderStatus", - label: t("bodyshop.labels.orderstatuses"), - children: , - }, - { - key: "responsibilityCenters", - label: t("bodyshop.labels.responsibilitycenters.title"), - children: , - }, - ...InstanceRenderManager({imex: [ { key: "checklists", label: t("bodyshop.labels.checklists"), - children: , - }], rome: "USE_IMEX", promanager:[]}) - , - { - key: "laborrates", - label: t("bodyshop.labels.laborrates"), - children: , - }, - ...(CriticalPartsScanning.treatment === "on" - ? [ - { - key: "partsscan", - label: t("bodyshop.labels.partsscan"), - children: , - }, - ] - : []), - ...Enhanced_Payroll.treatment === "on" ? [ - { - key: 'task-presets', - label: t("bodyshop.labels.task-presets"), - children: - }]: [] - ]; - return ( - form.submit()} - > - {t("general.actions.save")} - - } - > - - history({ - search: `?tab=${search.tab}&subtab=${key}`, - }) - } - items={tabItems} - /> - - ); + children: + } + ], + rome: "USE_IMEX", + promanager: [] + }), + { + key: "laborrates", + label: t("bodyshop.labels.laborrates"), + children: + }, + ...(CriticalPartsScanning.treatment === "on" + ? [ + { + key: "partsscan", + label: t("bodyshop.labels.partsscan"), + children: + } + ] + : []), + ...(Enhanced_Payroll.treatment === "on" + ? [ + { + key: "task-presets", + label: t("bodyshop.labels.task-presets"), + children: + } + ] + : []) + ]; + return ( + form.submit()}> + {t("general.actions.save")} + + } + > + + history({ + search: `?tab=${search.tab}&subtab=${key}` + }) + } + items={tabItems} + /> + + ); } diff --git a/client/src/components/shop-info/shop-info.container.jsx b/client/src/components/shop-info/shop-info.container.jsx index 368e5f438..1b3e94eab 100644 --- a/client/src/components/shop-info/shop-info.container.jsx +++ b/client/src/components/shop-info/shop-info.container.jsx @@ -1,86 +1,81 @@ -import {useMutation, useQuery} from "@apollo/client"; -import {Form, notification} from "antd"; +import { useMutation, useQuery } from "@apollo/client"; +import { Form, notification } from "antd"; import dayjs from "../../utils/day"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {QUERY_BODYSHOP, UPDATE_SHOP} from "../../graphql/bodyshop.queries"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries"; import AlertComponent from "../alert/alert.component"; import FormsFieldChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import ShopInfoComponent from "./shop-info.component"; export default function ShopInfoContainer() { - const [form] = Form.useForm(); - const {t} = useTranslation(); - const [saveLoading, setSaveLoading] = useState(false); - const [updateBodyshop] = useMutation(UPDATE_SHOP); - const {loading, error, data, refetch} = useQuery(QUERY_BODYSHOP, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const [form] = Form.useForm(); + const { t } = useTranslation(); + const [saveLoading, setSaveLoading] = useState(false); + const [updateBodyshop] = useMutation(UPDATE_SHOP); + const { loading, error, data, refetch } = useQuery(QUERY_BODYSHOP, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const handleFinish = (values) => { + setSaveLoading(true); + logImEXEvent("shop_update"); - const handleFinish = (values) => { - setSaveLoading(true); - logImEXEvent("shop_update"); + updateBodyshop({ + variables: { id: data.bodyshops[0].id, shop: values } + }) + .then((r) => { + notification["success"]({ message: t("bodyshop.successes.save") }); + refetch().then((_) => form.resetFields()); + }) + .catch((error) => { + notification["error"]({ + message: t("bodyshop.errors.saving", { message: error }) + }); + }); + setSaveLoading(false); + }; - updateBodyshop({ - variables: {id: data.bodyshops[0].id, shop: values}, - }) - .then((r) => { - notification["success"]({message: t("bodyshop.successes.save")}); - refetch().then((_) => form.resetFields()); - }) - .catch((error) => { - notification["error"]({ - message: t("bodyshop.errors.saving", {message: error}), - }); - }); - setSaveLoading(false); - }; + useEffect(() => { + if (data) form.resetFields(); + }, [form, data]); - useEffect(() => { - if (data) form.resetFields(); - }, [form, data]); - - if (error) return ; - if (loading) return ; - return ( -
    - - - - ); + if (error) return ; + if (loading) return ; + return ( +
    + + + + ); } 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 159057fd2..a961c33e9 100644 --- a/client/src/components/shop-info/shop-info.general.component.jsx +++ b/client/src/components/shop-info/shop-info.general.component.jsx @@ -1,1636 +1,1395 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; -import {Button, DatePicker, Form, Input, InputNumber, Radio, Select, Space, Switch,} from "antd"; +import { DeleteFilled } from "@ant-design/icons"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { Button, DatePicker, Form, Input, InputNumber, Radio, Select, Space, Switch } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import DatePickerRanges from "../../utils/DatePickerRanges"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component"; -import PhoneFormItem, {PhoneItemFormatterValidation,} from "../form-items-formatted/phone-form-item.component"; +import PhoneFormItem, { PhoneItemFormatterValidation } 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 InstanceRenderManager from '../../utils/instanceRenderMgr' +import InstanceRenderManager from "../../utils/instanceRenderMgr"; // TODO: Client Update, this might break -const timeZonesList = Intl.supportedValuesOf('timeZone'); +const timeZonesList = Intl.supportedValuesOf("timeZone"); const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoGeneral); -export function ShopInfoGeneral({form, bodyshop}) { +export function ShopInfoGeneral({ form, bodyshop }) { + const { t } = useTranslation(); - const {t} = useTranslation(); + const { + treatments: { ClosingPeriod } + } = useSplitTreatments({ + attributes: {}, + names: ["ClosingPeriod"], + splitKey: bodyshop && bodyshop.imexshopid + }); - const {treatments: {ClosingPeriod}} = useSplitTreatments({ - attributes: {}, - names: ["ClosingPeriod"], - splitKey: bodyshop && bodyshop.imexshopid, - }); + return ( +
    + + + + + + + + + + + + + + + + + + + + + + + + + - return ( -
    - + PhoneItemFormatterValidation(getFieldValue, "phone")]} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {InstanceRenderManager({ + imex: ( + + {() => ( - - - - - - - - - - - - - - - - - - - - - - - - - - PhoneItemFormatterValidation(getFieldValue, "phone"), - ]} - > - - - - - - - - - - - - - - - - - - - - - - - - - - - + + )} + + ) + })} + + + + + + 2 + 3 + + + + {() => { + return ( + + + {t("bodyshop.labels.2tiername")} + {t("bodyshop.labels.2tiersource")} + + + ); + }} + + + + + + + + + + + + + + {InstanceRenderManager({ + imex: ( + + + + ) + })} + + + + {InstanceRenderManager({ + imex: ( + - {() => ( - - - - )} - - }) + required: true + //message: t("general.validation.required"), } - - - - - - 2 - 3 - - - - {() => { - return ( - - - {t("bodyshop.labels.2tiername")} - - {t("bodyshop.labels.2tiersource")} - - - - ); - }} - - - - - - - - - - - - - - { - InstanceRenderManager({imex: - - - }) + ]} + > + + + ) + })} + + + + + + + + + + + + + + {ReceivableCustomFieldSelect} + + + {ReceivableCustomFieldSelect} + + + {ReceivableCustomFieldSelect} + + { + return { + required: getFieldValue("enforce_class"), + //message: t("general.validation.required"), + type: "array" + }; + } + ]} + > + + + + + + + + + + + + + + + + + + ({ + validator(rule, value) { + if (!value && !getFieldValue(["md_hour_split", "paint"])) { + return Promise.resolve(); } - - - - { - InstanceRenderManager({imex: - + + + ({ + validator(rule, value) { + if (!value && !getFieldValue(["md_hour_split", "paint"])) { + return Promise.resolve(); + } + if (value + getFieldValue(["md_hour_split", "prep"]) === 1) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.larsplit")); + } + }) + ]} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + - + - }) - } - - + } + ]} + > + + + + { + remove(field.name); + }} + /> + + + + + ))} + + - + ); + }} + + + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + - - - + + + - + } + ]} + > + + + + { + remove(field.name); + }} + /> + + + + + ))} + + - - - - - {ReceivableCustomFieldSelect} - - - {ReceivableCustomFieldSelect} - - - {ReceivableCustomFieldSelect} - - { - return { - required: getFieldValue("enforce_class"), - //message: t("general.validation.required"), - type: "array", - }; - }, - ]} - > - + + + + + + + + + + + + + + + + + + + - - + + { + remove(field.name); + }} + /> + + + + + ))} + + +
    + ); + }} +
    +
    + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + + + + + + PhoneItemFormatterValidation(getFieldValue, [field.name, "est_ph"]) + ]} + > + + + + + + + { + remove(field.name); + }} + /> + + + + + ))} + + + +
    + ); + }} +
    +
    + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + + + PhoneItemFormatterValidation(getFieldValue, [field.name, "ins_ph"]) + ]} + > + + + + + + + { + remove(field.name); + }} + /> + + + + + ))} + + + +
    + ); + }} +
    +
    + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - - { - remove(field.name); - }} - /> - - - - - ))} - - - -
    - ); + + { + remove(field.name); + }} + /> + + +
    +
    + ))} + + - -
    - ); + style={{ width: "100%" }} + > + {t("general.actions.add")} + +
    +
    + ); + }} +
    +
    + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + remove(field.name); + }} + /> + + + + + ))} + + - -
    - ); - }} -
    -
    - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - - - - - - - - - - - - - - - + style={{ width: "100%" }} + > + {t("general.actions.add")} + + +
    + ); + }} +
    +
    + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + + - - { - remove(field.name); - }} - /> - - - - - ))} - - - -
    - ); + + { + remove(field.name); + }} + /> + + +
    + + ))} + + - -
    - ); - }} - -
    - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - - PhoneItemFormatterValidation(getFieldValue, [ - field.name, - "ins_ph", - ]), - ]} - > - - - - - - - { - remove(field.name); - }} - /> - - - - - ))} - - - -
    - ); - }} -
    -
    - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style={{ width: "100%" }} + > + {t("general.actions.add")} + + +
    + ); + }} +
    +
    + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - remove(field.name); - }} - /> - - - - - ))} - - - -
    - ); - }} -
    -
    - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - - - { - remove(field.name); - }} - /> - - - - - ))} - - - -
    - ); - }} -
    -
    - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - VIN - Claim No. - Deductible Amount - + ); diff --git a/client/src/components/shop-info/shop-info.intake.component.jsx b/client/src/components/shop-info/shop-info.intake.component.jsx index f2bcd11ca..72597a15e 100644 --- a/client/src/components/shop-info/shop-info.intake.component.jsx +++ b/client/src/components/shop-info/shop-info.intake.component.jsx @@ -1,9 +1,9 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {Button, Form, Input, InputNumber, Select, Space, Switch} from "antd"; +import { DeleteFilled } from "@ant-design/icons"; +import { Button, Form, Input, InputNumber, Select, Space, Switch } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import styled from "styled-components"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { TemplateList } from "../../utils/TemplateConstants"; import ConfigFormTypes from "../config-form-components/config-form-types"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; @@ -14,356 +14,312 @@ const SelectorDiv = styled.div` } `; -export default function ShopInfoIntakeChecklistComponent({form}) { - const {t} = useTranslation(); +export default function ShopInfoIntakeChecklistComponent({ form }) { + const { t } = useTranslation(); - const TemplateListGenerated = TemplateList(); - return ( -
    - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - - - - - - {() => { - if ( - form.getFieldValue([ - "intakechecklist", - "form", - index, - "type", - ]) !== "slider" - ) - return null; - return ( - <> - - - - - - - - ); - }} - - - - - - { - remove(field.name); - }} - /> - - - - - ))} - - - -
    - ); - }} -
    -
    - - + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + - - - - - - - - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - - - - - - - - {() => { - if ( - form.getFieldValue([ - "deliverchecklist", - "form", - index, - "type", - ]) !== "slider" - ) - return null; - return ( - <> - - - - - - - - ); - }} - - - - - { - remove(field.name); - }} - /> - - - - ))} - - - -
    - ); - }} -
    -
    - - + + + - + {Object.keys(ConfigFormTypes).map((i, idx) => ( + + {i} - ))} - + ))} + + + + + + + + {() => { + if (form.getFieldValue(["intakechecklist", "form", index, "type"]) !== "slider") return null; + return ( + <> + + + + + + + + ); + }} + + + + + + { + remove(field.name); + }} + /> + + +
    +
    + ))} + + - -
    - ); +
    + ); + }} + +
    + + + + + + + + + + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + + + + + + + + + + {() => { + if (form.getFieldValue(["deliverchecklist", "form", index, "type"]) !== "slider") return null; + return ( + <> + + + + + + + + ); + }} + + + + + { + remove(field.name); + }} + /> + + + + ))} + + + +
    + ); + }} +
    +
    + + + + + +
    + ); } diff --git a/client/src/components/shop-info/shop-info.laborrates.component.jsx b/client/src/components/shop-info/shop-info.laborrates.component.jsx index 42e3dc56c..0c7c9b556 100644 --- a/client/src/components/shop-info/shop-info.laborrates.component.jsx +++ b/client/src/components/shop-info/shop-info.laborrates.component.jsx @@ -1,339 +1,335 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {Button, Form, Input} from "antd"; +import { DeleteFilled } from "@ant-design/icons"; +import { Button, Form, Input } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import CurrencyInput from "../form-items-formatted/currency-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"; -export default function ShopInfoLaborRates({form}) { - const {t} = useTranslation(); +export default function ShopInfoLaborRates({ form }) { + const { t } = useTranslation(); - return ( -
    - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - // - // - // - // - // - // - } - - - - - - - { - remove(field.name); - }} - /> - - - - ))} - - - -
    - ); - }} -
    -
    - ); + return ( +
    + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + // + // + // + // + // + // + } + + + + + + + { + remove(field.name); + }} + /> + + + + ))} + + + +
    + ); + }} +
    +
    + ); } diff --git a/client/src/components/shop-info/shop-info.orderstatus.component.jsx b/client/src/components/shop-info/shop-info.orderstatus.component.jsx index 1f9f070da..56c2442c7 100644 --- a/client/src/components/shop-info/shop-info.orderstatus.component.jsx +++ b/client/src/components/shop-info/shop-info.orderstatus.component.jsx @@ -1,97 +1,96 @@ -import {Form, Input} from "antd"; +import { Form, Input } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ShopInfoOrderStatusComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoOrderStatusComponent); -export function ShopInfoOrderStatusComponent({bodyshop, form}) { - const {t} = useTranslation(); +export function ShopInfoOrderStatusComponent({ bodyshop, form }) { + const { t } = useTranslation(); - const {treatments: {OEConnection}} = useSplitTreatments({ - attributes: {}, - names: ["OEConnection"], - splitKey: bodyshop.imexshopid, - }); + const { + treatments: { OEConnection } + } = useSplitTreatments({ + attributes: {}, + names: ["OEConnection"], + splitKey: bodyshop.imexshopid + }); - return ( - - - - - - - + return ( + + + + + + + - - - - - - - {OEConnection.treatment === "on" && ( - - - - )} - - ); + + + + + + + {OEConnection.treatment === "on" && ( + + + + )} + + ); } diff --git a/client/src/components/shop-info/shop-info.parts-scan.jsx b/client/src/components/shop-info/shop-info.parts-scan.jsx index 3ab0c600d..87ac86fbd 100644 --- a/client/src/components/shop-info/shop-info.parts-scan.jsx +++ b/client/src/components/shop-info/shop-info.parts-scan.jsx @@ -1,81 +1,77 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {Button, Form, Input, Space} from "antd"; +import { DeleteFilled } from "@ant-design/icons"; +import { Button, Form, Input, Space } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; -export default function ShopInfoPartsScan({form}) { - const {t} = useTranslation(); +export default function ShopInfoPartsScan({ form }) { + const { t } = useTranslation(); - return ( -
    - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - + return ( +
    + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + + - - { - remove(field.name); - }} - /> - - - - - ))} - - - -
    - ); + + { + remove(field.name); + }} + /> + + +
    + + ))} + +
    - ); + style={{ width: "100%" }} + > + {t("bodyshop.actions.addpartsrule")} + +
    +
    + ); + }} +
    +
    +
    + ); } diff --git a/client/src/components/shop-info/shop-info.rbac.component.jsx b/client/src/components/shop-info/shop-info.rbac.component.jsx index e05c32e33..f26dc1938 100644 --- a/client/src/components/shop-info/shop-info.rbac.component.jsx +++ b/client/src/components/shop-info/shop-info.rbac.component.jsx @@ -1,821 +1,820 @@ -import {Form, InputNumber} from "antd"; +import { Form, InputNumber } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ShopInfoRbacComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoRbacComponent); -export function ShopInfoRbacComponent({form, bodyshop}) { - const {t} = useTranslation(); - const {treatments: {Simple_Inventory}} = useSplitTreatments({ - attributes: {}, - names: ["Simple_Inventory"], - splitKey: bodyshop && bodyshop.imexshopid, - }); -//TODO:AIO Ensure that there are no duplicates here, it seems like there may be. - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {Simple_Inventory.treatment === "on" && ( + <> + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {Simple_Inventory.treatment === "on" && ( - <> - - - - - - - - )} - - - ); + + + + + )} +
    + + ); } 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 177d622c8..b918af5ea 100644 --- a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx +++ b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx @@ -1,384 +1,349 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {Button, Form, Input, InputNumber, Select, Space, Switch, Typography,} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; +import { DeleteFilled } from "@ant-design/icons"; +import { Button, Form, Input, InputNumber, Select, Space, Switch, Typography } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import styled from "styled-components"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import DataLabel from "../data-label/data-label.component"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; import ShopInfoResponsibilitycentersTaxesComponent from "./shop-info.responsibilitycenters.taxes.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; const SelectorDiv = styled.div` - .ant-form-item .ant-select { - width: 125px; - } + .ant-form-item .ant-select { + width: 125px; + } `; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ShopInfoResponsibilityCenterComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoResponsibilityCenterComponent); -export function ShopInfoResponsibilityCenterComponent({bodyshop, form}) { - const {t} = useTranslation(); +export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) { + const { t } = useTranslation(); - const {treatments: {Qb_Multi_Ar, DmsAp}} = useSplitTreatments({ - attributes: {}, - names: ["Qb_Multi_Ar", "DmsAp"], - splitKey: bodyshop && bodyshop.imexshopid, - }); + const { + treatments: { Qb_Multi_Ar, DmsAp } + } = useSplitTreatments({ + attributes: {}, + names: ["Qb_Multi_Ar", "DmsAp"], + splitKey: bodyshop && bodyshop.imexshopid + }); - const [costOptions, setCostOptions] = useState( - [ - ...((form.getFieldValue(["md_responsibility_centers", "costs"]) && - form - .getFieldValue(["md_responsibility_centers", "costs"]) - .map((i) => i && i.name)) || - []), - ] || [] - ); + const [costOptions, setCostOptions] = useState( + [ + ...((form.getFieldValue(["md_responsibility_centers", "costs"]) && + form.getFieldValue(["md_responsibility_centers", "costs"]).map((i) => i && i.name)) || + []) + ] || [] + ); - const [profitOptions, setProfitOptions] = useState( - [ - ...((form.getFieldValue(["md_responsibility_centers", "profits"]) && - form - .getFieldValue(["md_responsibility_centers", "profits"]) - .map((i) => i && i.name)) || - []), - ] || [] - ); + const [profitOptions, setProfitOptions] = useState( + [ + ...((form.getFieldValue(["md_responsibility_centers", "profits"]) && + form.getFieldValue(["md_responsibility_centers", "profits"]).map((i) => i && i.name)) || + []) + ] || [] + ); - const handleBlur = () => { - setCostOptions([ - ...(form - .getFieldValue(["md_responsibility_centers", "costs"]) - .map((i) => i && i.name) || []), - ]); - setProfitOptions([ - ...(form - .getFieldValue(["md_responsibility_centers", "profits"]) - .map((i) => i && i.name) || []), - ]); - }; + const handleBlur = () => { + setCostOptions([...(form.getFieldValue(["md_responsibility_centers", "costs"]).map((i) => i && i.name) || [])]); + setProfitOptions([...(form.getFieldValue(["md_responsibility_centers", "profits"]).map((i) => i && i.name) || [])]); + }; - return ( -
    - {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( - <> - {bodyshop.cdk_dealerid && ( - - {form.getFieldValue("cdk_dealerid")} - - )} - {bodyshop.pbs_serialnumber && ( - - {form.getFieldValue("pbs_serialnumber")} - - )} - - - - - - - - - - - - - - - - - {bodyshop.pbs_serialnumber && ( - - - - )} - {bodyshop.pbs_serialnumber && ( - - - - )} - {bodyshop.pbs_serialnumber && ( - - - - )} - - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - - - - - - { - remove(field.name); - }} - /> - - - - - ))} - - - -
    - ); - }} -
    -
    - - - {(fields, {add, remove}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - - { - remove(field.name); - }} - /> - - - ))} - - - -
    - ); - }} -
    -
    - - )} - - {bodyshop.pbs_serialnumber && ( - <> - - {form.getFieldValue("pbs_serialnumber")} - - - )} - + {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( + <> + {bodyshop.cdk_dealerid && ( + {form.getFieldValue("cdk_dealerid")} + )} + {bodyshop.pbs_serialnumber && ( + + {form.getFieldValue("pbs_serialnumber")} + + )} + + - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - {/* + + + + + + + + + + + + + + {bodyshop.pbs_serialnumber && ( + + + + )} + {bodyshop.pbs_serialnumber && ( + + + + )} + {bodyshop.pbs_serialnumber && ( + + + + )} + + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + + + + + + + + { + remove(field.name); + }} + /> + + + + + ))} + + + +
    + ); + }} +
    +
    + + + {(fields, { add, remove }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + + + + { + remove(field.name); + }} + /> + + + ))} + + + +
    + ); + }} +
    +
    + + )} + + {bodyshop.pbs_serialnumber && ( + <> + + {form.getFieldValue("pbs_serialnumber")} + + + )} + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + + + {/* */} - - - - {/* + + + {/* */} - {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( - - - - )} - {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( - - - - )} - {bodyshop.cdk_dealerid && ( - - - - )} - - { - remove(field.name); - }} - /> - - - - - ))} - - - -
    - ); + {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( + + + + )} + {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( + + + + )} + {bodyshop.cdk_dealerid && ( + + + + )} + + { + remove(field.name); + }} + /> + + +
    +
    + ))} + + + +
    + ); + }} +
    +
    - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - {/* + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + {/* */} - - - - - - - {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( - - - - )} - {bodyshop.cdk_dealerid && ( - - - - )} - - { - remove(field.name); - }} - /> - - - - - ))} - - - -
    - ); - }} -
    -
    - - - {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( - <> - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - -
    - - - - - - - - { - remove(field.name); - }} - /> - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-ats`} - name={[field.name, "costs", "ATS"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-LAA`} - name={[field.name, "costs", "LAA"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-LAB`} - name={[field.name, "costs", "LAB"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-LAD`} - name={[field.name, "costs", "LAD"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-LAE`} - name={[field.name, "costs", "LAE"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-LAF`} - name={[field.name, "costs", "LAF"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-LAG`} - name={[field.name, "costs", "LAG"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-LAM`} - name={[field.name, "costs", "LAM"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-LAR`} - name={[field.name, "costs", "LAR"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-LAS`} - name={[field.name, "costs", "LAS"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-LAU`} - name={[field.name, "costs", "LAU"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-LA1`} - name={[field.name, "costs", "LA1"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-LA2`} - name={[field.name, "costs", "LA2"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-LA3`} - name={[field.name, "costs", "LA3"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-LA4`} - name={[field.name, "costs", "LA4"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-PAA`} - name={[field.name, "costs", "PAA"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-PAC`} - name={[field.name, "costs", "PAC"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-PAG`} - name={[field.name, "costs", "PAG"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-PAL`} - name={[field.name, "costs", "PAL"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-PAM`} - name={[field.name, "costs", "PAM"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-PAN`} - name={[field.name, "costs", "PAN"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-PAO`} - name={[field.name, "costs", "PAO"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-PAP`} - name={[field.name, "costs", "PAP"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-PAR`} - name={[field.name, "costs", "PAR"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-PAS`} - name={[field.name, "costs", "PAS"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-PASL`} - name={[field.name, "costs", "PASL"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-TOW`} - name={[field.name, "costs", "TOW"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-MAPA`} - name={[field.name, "costs", "MAPA"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}costs-MASH`} - name={[field.name, "costs", "MASH"]} - > - - - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-ATS`} - name={[field.name, "profits", "ATS"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-LAA`} - name={[field.name, "profits", "LAA"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-LAB`} - name={[field.name, "profits", "LAB"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-LAD`} - name={[field.name, "profits", "LAD"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-LAE`} - name={[field.name, "profits", "LAE"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-LAF`} - name={[field.name, "profits", "LAF"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-LAG`} - name={[field.name, "profits", "LAG"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-LAM`} - name={[field.name, "profits", "LAM"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-LAR`} - name={[field.name, "profits", "LAR"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-LAS`} - name={[field.name, "profits", "LAS"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-LAU`} - name={[field.name, "profits", "LAU"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-LA1`} - name={[field.name, "profits", "LA1"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-LA2`} - name={[field.name, "profits", "LA2"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-LA3`} - name={[field.name, "profits", "LA3"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-LA4`} - name={[field.name, "profits", "LA4"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-PAA`} - name={[field.name, "profits", "PAA"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-PAC`} - name={[field.name, "profits", "PAC"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-PAG`} - name={[field.name, "profits", "PAG"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-PAL`} - name={[field.name, "profits", "PAL"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-PAM`} - name={[field.name, "profits", "PAM"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-PAN`} - name={[field.name, "profits", "PAN"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-PAO`} - name={[field.name, "profits", "PAO"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-PAP`} - name={[field.name, "profits", "PAP"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-PAR`} - name={[field.name, "profits", "PAR"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-PAS`} - name={[field.name, "profits", "PAS"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-PASL`} - name={[field.name, "profits", "PASL"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-TOW`} - name={[field.name, "profits", "TOW"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-MAPA`} - name={[field.name, "profits", "MAPA"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - key={`${index}profits-MASH`} - name={[field.name, "profits", "MASH"]} - > - - - -
    -
    - ))} - - - -
    - ); - }} -
    - - )} - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "ATS"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "LAA"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "LAB"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "LAD"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "LAE"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "LAF"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "LAG"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "LAM"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "LAR"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "LAS"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "LAU"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "LA1"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "LA2"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "LA3"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "LA4"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "PAA"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "PAC"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "PAG"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "PAL"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "PAM"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "PAN"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "PAO"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "PAP"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "PAR"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "PAS"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "PASL"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "TOW"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "MAPA"]} - > - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "costs", "MASH"]} - > - - - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "ATS"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "LAA"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "LAB"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "LAD"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "LAE"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "LAF"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "LAG"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "LAM"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "LAR"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "LAS"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "LAU"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "LA1"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "LA2"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "LA3"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "LA4"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "PAA"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "PAC"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "PAG"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "PAL"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "PAM"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "PAN"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "PAO"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "PAP"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "PAR"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "PAS"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "PASL"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "TOW"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "MAPA"]} - > - - - ({ - validator(rule, value) { - if (profitOptions.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - t("bodyshop.validation.centermustexist") - ); - }, - }), - ]} - name={["md_responsibility_centers", "defaults", "profits", "MASH"]} - > - - - -
    - - - - - - {/* - - */} - {/* - - */} - - - - - - - {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( - - - - )} - - - - - {DmsAp.treatment === "on" && ( - - - - - {/* - - */} - {/* - - */} - - - - - - - {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( - - - - )} - - - - - )} - - - { - InstanceRenderManager({imex: - - + } + ]} + > + + + + + + {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( + + + + )} + {bodyshop.cdk_dealerid && ( + + + + )} + + { + remove(field.name); + }} + /> + + + +
    + ))} + + - {/* + ); + }} + + + + + {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( + <> + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + +
    + + + + + + + + { + remove(field.name); + }} + /> + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-ats`} + name={[field.name, "costs", "ATS"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-LAA`} + name={[field.name, "costs", "LAA"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-LAB`} + name={[field.name, "costs", "LAB"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-LAD`} + name={[field.name, "costs", "LAD"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-LAE`} + name={[field.name, "costs", "LAE"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-LAF`} + name={[field.name, "costs", "LAF"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-LAG`} + name={[field.name, "costs", "LAG"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-LAM`} + name={[field.name, "costs", "LAM"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-LAR`} + name={[field.name, "costs", "LAR"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-LAS`} + name={[field.name, "costs", "LAS"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-LAU`} + name={[field.name, "costs", "LAU"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-LA1`} + name={[field.name, "costs", "LA1"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-LA2`} + name={[field.name, "costs", "LA2"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-LA3`} + name={[field.name, "costs", "LA3"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-LA4`} + name={[field.name, "costs", "LA4"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-PAA`} + name={[field.name, "costs", "PAA"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-PAC`} + name={[field.name, "costs", "PAC"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-PAG`} + name={[field.name, "costs", "PAG"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-PAL`} + name={[field.name, "costs", "PAL"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-PAM`} + name={[field.name, "costs", "PAM"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-PAN`} + name={[field.name, "costs", "PAN"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-PAO`} + name={[field.name, "costs", "PAO"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-PAP`} + name={[field.name, "costs", "PAP"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-PAR`} + name={[field.name, "costs", "PAR"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-PAS`} + name={[field.name, "costs", "PAS"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-PASL`} + name={[field.name, "costs", "PASL"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-TOW`} + name={[field.name, "costs", "TOW"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-MAPA`} + name={[field.name, "costs", "MAPA"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}costs-MASH`} + name={[field.name, "costs", "MASH"]} + > + + + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-ATS`} + name={[field.name, "profits", "ATS"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-LAA`} + name={[field.name, "profits", "LAA"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-LAB`} + name={[field.name, "profits", "LAB"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-LAD`} + name={[field.name, "profits", "LAD"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-LAE`} + name={[field.name, "profits", "LAE"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-LAF`} + name={[field.name, "profits", "LAF"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-LAG`} + name={[field.name, "profits", "LAG"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-LAM`} + name={[field.name, "profits", "LAM"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-LAR`} + name={[field.name, "profits", "LAR"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-LAS`} + name={[field.name, "profits", "LAS"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-LAU`} + name={[field.name, "profits", "LAU"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-LA1`} + name={[field.name, "profits", "LA1"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-LA2`} + name={[field.name, "profits", "LA2"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-LA3`} + name={[field.name, "profits", "LA3"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-LA4`} + name={[field.name, "profits", "LA4"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-PAA`} + name={[field.name, "profits", "PAA"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-PAC`} + name={[field.name, "profits", "PAC"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-PAG`} + name={[field.name, "profits", "PAG"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-PAL`} + name={[field.name, "profits", "PAL"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-PAM`} + name={[field.name, "profits", "PAM"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-PAN`} + name={[field.name, "profits", "PAN"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-PAO`} + name={[field.name, "profits", "PAO"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-PAP`} + name={[field.name, "profits", "PAP"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-PAR`} + name={[field.name, "profits", "PAR"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-PAS`} + name={[field.name, "profits", "PAS"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-PASL`} + name={[field.name, "profits", "PASL"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-TOW`} + name={[field.name, "profits", "TOW"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-MAPA`} + name={[field.name, "profits", "MAPA"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + key={`${index}profits-MASH`} + name={[field.name, "profits", "MASH"]} + > + + + +
    +
    + ))} + + + +
    + ); + }} +
    + + )} + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "ATS"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "LAA"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "LAB"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "LAD"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "LAE"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "LAF"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "LAG"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "LAM"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "LAR"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "LAS"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "LAU"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "LA1"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "LA2"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "LA3"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "LA4"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "PAA"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "PAC"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "PAG"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "PAL"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "PAM"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "PAN"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "PAO"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "PAP"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "PAR"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "PAS"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "PASL"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "TOW"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "MAPA"]} + > + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "costs", "MASH"]} + > + + + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "ATS"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "LAA"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "LAB"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "LAD"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "LAE"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "LAF"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "LAG"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "LAM"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "LAR"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "LAS"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "LAU"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "LA1"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "LA2"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "LA3"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "LA4"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "PAA"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "PAC"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "PAG"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "PAL"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "PAM"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "PAN"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "PAO"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "PAP"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "PAR"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "PAS"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "PASL"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "TOW"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "MAPA"]} + > + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); + } + }) + ]} + name={["md_responsibility_centers", "defaults", "profits", "MASH"]} + > + + + +
    + + + + + + {/* + + */} + {/* + + */} + + + + + + + {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( + + + + )} + + + + + {DmsAp.treatment === "on" && ( + + + + + {/* + + */} + {/* + + */} + + + + + + + {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( + + + + )} + + + + + )} + + {InstanceRenderManager({ + imex: ( + + + + + {/* */} - - - - - - - {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( - - - - )} - - - - , - rome: }) - } + + + + + + + {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( + + + + )} + + + + + ), + rome: + })} - - - - - - - {/**/} - {/* */} - {/* */} - {/*
    */} - {/* /!* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* *!/*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (*/} - {/* */} - {/* */} - {/* */} - {/* )}*/} - {/* */} - {/* */} - {/* */} - {/**/} - AR
    } id="AR"> - {/* + + + + + + {/**/} + {/* */} + {/* */} + {/* */} + {/* /!* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* *!/*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (*/} + {/* */} + {/* */} + {/* */} + {/* )}*/} + {/* */} + {/* */} + {/* */} + {/**/} + AR
    } id="AR"> + {/* */} - - - - {/* + + + {/* */} - + - {DmsAp.treatment === "on" && ( - - {/* + {/* */} - {/* */} - - - - - - - - - - - )} - Refund
    } id="refund"> - {/* + + + + + + + + + + )} + Refund} id="refund"> + {/* */} - {/* */} - {/* */} - - - - - {Qb_Multi_Ar.treatment === "on" && ( - Multiple Payers Item} - id="accountitem" - > + + + + + {Qb_Multi_Ar.treatment === "on" && ( + Multiple Payers Item} id="accountitem"> + + + + + )} + {t("bodyshop.labels.responsibilitycenters.sales_tax_codes")} + + {(fields, { add, remove }) => { + return ( +
    + {fields.map((field, index) => ( + + - + - - )} - - {t("bodyshop.labels.responsibilitycenters.sales_tax_codes")} - - - {(fields, {add, remove}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - - - - - - - - - - { - remove(field.name); - }} - /> - - - ))} - - - -
    - ); - }} -
    -
    - ); + + + + + + + + + + + + + { + remove(field.name); + }} + /> + + + ))} + + + + + ); + }} +
    + + ); } diff --git a/client/src/components/shop-info/shop-info.responsibilitycenters.taxes.component.jsx b/client/src/components/shop-info/shop-info.responsibilitycenters.taxes.component.jsx index c31fa6c02..13ce11d7b 100644 --- a/client/src/components/shop-info/shop-info.responsibilitycenters.taxes.component.jsx +++ b/client/src/components/shop-info/shop-info.responsibilitycenters.taxes.component.jsx @@ -1,3042 +1,1935 @@ -import {Collapse, Divider, Form, Input, InputNumber, Space, Switch,} from "antd"; +import { Collapse, Divider, Form, Input, InputNumber, Space, Switch } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ShopInfoResponsibilityCenters); +export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoResponsibilityCenters); -export function ShopInfoResponsibilityCenters({bodyshop, form}) { - const {t} = useTranslation(); - //Iteratively build the form items. - const formItems = []; - for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { - const section = []; +export function ShopInfoResponsibilityCenters({ bodyshop, form }) { + const { t } = useTranslation(); + //Iteratively build the form items. + const formItems = []; + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + const section = []; - section.push( - TaxFormItems({ - typeNum: tyCounter, - rootElements: true, - bodyshop, - }) - ); - for (let iterator = 1; iterator <= 5; iterator++) { - section.push( - TaxFormItems({ - typeNum: tyCounter, - typeNumIterator: iterator, - rootElements: false, - }) - ); - } - formItems.push({section} ); - formItems.push(); + section.push( + TaxFormItems({ + typeNum: tyCounter, + rootElements: true, + bodyshop + }) + ); + for (let iterator = 1; iterator <= 5; iterator++) { + section.push( + TaxFormItems({ + typeNum: tyCounter, + typeNumIterator: iterator, + rootElements: false + }) + ); } - return ( - <> - {section}); + formItems.push(); + } + return ( + <> + + {t("jobs.labels.cieca_pft")} + + {formItems} + + + + + - {t("jobs.labels.cieca_pft")} - - {formItems} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {() => { - return ( - - - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {() => { + return ( + + + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } -function TaxFormItems({typeNum, typeNumIterator, rootElements, bodyshop}) { - const {t} = useTranslation(); +function TaxFormItems({ typeNum, typeNumIterator, rootElements, bodyshop }) { + const { t } = useTranslation(); - if (rootElements) - return ( - <> - - - - - - - - - - - - - - - {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( - - - - )} - - ); + if (rootElements) return ( - <> - - - - - - - - - - - - - + <> + + + + + + + + + + + + + + + {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( + + + + )} + ); + return ( + <> + + + + + + + + + + + + + + ); } diff --git a/client/src/components/shop-info/shop-info.rostatus.component.jsx b/client/src/components/shop-info/shop-info.rostatus.component.jsx index 782b7920f..26bff4c66 100644 --- a/client/src/components/shop-info/shop-info.rostatus.component.jsx +++ b/client/src/components/shop-info/shop-info.rostatus.component.jsx @@ -1,415 +1,401 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {Button, Form, Select, Space} from "antd"; -import React, {useState} from "react"; -import {ChromePicker} from "react-color"; -import {useTranslation} from "react-i18next"; +import { DeleteFilled } from "@ant-design/icons"; +import { Button, Form, Select, Space } from "antd"; +import React, { useState } from "react"; +import { ChromePicker } from "react-color"; +import { useTranslation } from "react-i18next"; import styled from "styled-components"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ShopInfoROStatusComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoROStatusComponent); const SelectorDiv = styled.div` - .ant-form-item .ant-select { - width: 200px; - } + .ant-form-item .ant-select { + width: 200px; + } `; -export function ShopInfoROStatusComponent({bodyshop, form}) { +export function ShopInfoROStatusComponent({ bodyshop, form }) { + const { t } = useTranslation(); - const {t} = useTranslation(); + const { + treatments: { Production_List_Status_Colors } + } = useSplitTreatments({ + attributes: {}, + names: ["Production_List_Status_Colors"], + splitKey: bodyshop.imexshopid + }); - const {treatments: {Production_List_Status_Colors}} = useSplitTreatments({ - attributes: {}, - names: ["Production_List_Status_Colors"], - splitKey: bodyshop.imexshopid, - }); + const [options, setOptions] = useState(form.getFieldValue(["md_ro_statuses", "statuses"]) || []); - const [options, setOptions] = useState( - form.getFieldValue(["md_ro_statuses", "statuses"]) || [] - ); + const handleBlur = () => { + setOptions(form.getFieldValue(["md_ro_statuses", "statuses"])); + }; - const handleBlur = () => { - setOptions(form.getFieldValue(["md_ro_statuses", "statuses"])); - }; - - return ( - - - - {options.map((item, idx) => ( - - {item} - + return ( + + + + {options.map((item, idx) => ( + + {item} + + ))} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {Production_List_Status_Colors.treatment === "on" && ( + + + {(fields, { add, remove, move }) => { + return ( +
    + + {fields.map((field, index) => ( + + +
    + + + + { + remove(field.name); + }} + /> +
    + + + +
    +
    ))} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {Production_List_Status_Colors.treatment === "on" && ( - - - {(fields, {add, remove, move}) => { - return ( -
    - - {fields.map((field, index) => ( - - -
    - - - - { - remove(field.name); - }} - /> -
    - - - -
    -
    - ))} -
    - - - -
    - ); - }} -
    -
    - )} - - ); +
    + + + +
    + ); + }} +
    +
    + )} +
    + ); } -export const ColorPicker = ({value, onChange, style, ...restProps}) => { - const handleChange = (color) => { - if (onChange) onChange(color.rgb); - }; - return ( - - ); +export const ColorPicker = ({ value, onChange, style, ...restProps }) => { + const handleChange = (color) => { + if (onChange) onChange(color.rgb); + }; + return ; }; diff --git a/client/src/components/shop-info/shop-info.scheduling.component.jsx b/client/src/components/shop-info/shop-info.scheduling.component.jsx index ac2daca8c..b441db818 100644 --- a/client/src/components/shop-info/shop-info.scheduling.component.jsx +++ b/client/src/components/shop-info/shop-info.scheduling.component.jsx @@ -1,341 +1,304 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {Button, Divider, Form, Input, InputNumber, Select, Space, Switch, TimePicker,} from "antd"; +import { DeleteFilled } from "@ant-design/icons"; +import { Button, Divider, Form, Input, InputNumber, Select, Space, Switch, TimePicker } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-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 {ColorPicker} from "./shop-info.rostatus.component"; +import { ColorPicker } from "./shop-info.rostatus.component"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; -export default function ShopInfoSchedulingComponent({form}) { - const {t} = useTranslation(); +export default function ShopInfoSchedulingComponent({ form }) { + const { t } = useTranslation(); - return ( -
    - - - - - - - - - - - - - - - {t("bodyshop.labels.workingdays")} - - - - - - - - - - - - - - - - - - - - - - - - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - - { - remove(field.name); - }} - /> - - - - - ))} - - - -
    - ); - }} -
    -
    + return ( +
    + + - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - - - - - - - - - - - - - - - - {t("bodyshop.fields.ssbuckets.color")} - - - } - key={`${index}color`} - name={[field.name, "color"]} - > - - - - { - remove(field.name); - }} - /> - - - - - - ))} - - - -
    - ); - }} -
    -
    , rome: "USE_IMEX", promanager: null}) + required: true + //message: t("general.validation.required"), } - -
    - ); + ]} + > + + + + + + + + + + + + + {t("bodyshop.labels.workingdays")} + + + + + + + + + + + + + + + + + + + + + + + + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + + + + { + remove(field.name); + }} + /> + + + + + ))} + + + +
    + ); + }} +
    +
    + {InstanceRenderManager({ + imex: ( + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + + + + + + + + + + + + + + + + + + {t("bodyshop.fields.ssbuckets.color")} + + + } + key={`${index}color`} + name={[field.name, "color"]} + > + + + + { + remove(field.name); + }} + /> + + + + + + ))} + + + +
    + ); + }} +
    +
    + ), + rome: "USE_IMEX", + promanager: null + })} +
    + ); } diff --git a/client/src/components/shop-info/shop-info.speedprint.component.jsx b/client/src/components/shop-info/shop-info.speedprint.component.jsx index 45efcee31..1654a39b0 100644 --- a/client/src/components/shop-info/shop-info.speedprint.component.jsx +++ b/client/src/components/shop-info/shop-info.speedprint.component.jsx @@ -1,101 +1,94 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {Button, Form, Input, Select, Space} from "antd"; +import { DeleteFilled } from "@ant-design/icons"; +import { Button, Form, Input, Select, Space } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { useTranslation } from "react-i18next"; +import { TemplateList } from "../../utils/TemplateConstants"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; -export default function ShopInfoSpeedPrint({bodyshop, form}) { - const {t} = useTranslation(); - const TemplateListGenerated = TemplateList("job"); - return ( - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - +export default function ShopInfoSpeedPrint({ bodyshop, form }) { + const { t } = useTranslation(); + const TemplateListGenerated = TemplateList("job"); + return ( + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + + + + + - - - + + + - - { - remove(field.name); - }} - /> - - - - - ))} - - - -
    - ); - }} -
    - ); + + { + remove(field.name); + }} + /> + + +
    +
    + ))} + + + +
    + ); + }} +
    + ); } diff --git a/client/src/components/shop-info/shop-info.task-presets.component.jsx b/client/src/components/shop-info/shop-info.task-presets.component.jsx index 4b7f59a5a..20b2b11a8 100644 --- a/client/src/components/shop-info/shop-info.task-presets.component.jsx +++ b/client/src/components/shop-info/shop-info.task-presets.component.jsx @@ -1,259 +1,211 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {Button, Checkbox, Col, Form, Input, InputNumber, Row, Select, Space, Switch,} from "antd"; +import { DeleteFilled } from "@ant-design/icons"; +import { Button, Checkbox, Col, Form, Input, InputNumber, Row, Select, Space, Switch } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ShopInfoTaskPresets); +export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoTaskPresets); -export function ShopInfoTaskPresets({bodyshop, form}) { - const {t} = useTranslation(); +export function ShopInfoTaskPresets({ bodyshop, form }) { + const { t } = useTranslation(); - return ( - <> - - - - - - - - + return ( + <> + + + + + + + + - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - -
    - - {t("joblines.fields.lbr_types.LAA")} - - - - - {t("joblines.fields.lbr_types.LAB")} - - - - - {t("joblines.fields.lbr_types.LAD")} - - - - - {t("joblines.fields.lbr_types.LAE")} - - - - - {t("joblines.fields.lbr_types.LAF")} - - - - - {t("joblines.fields.lbr_types.LAG")} - - - - - {t("joblines.fields.lbr_types.LAM")} - - - - - {t("joblines.fields.lbr_types.LAR")} - - - - - {t("joblines.fields.lbr_types.LAS")} - - - - - {t("joblines.fields.lbr_types.LAU")} - - - - - {t("joblines.fields.lbr_types.LA1")} - - - - - {t("joblines.fields.lbr_types.LA2")} - - - - - {t("joblines.fields.lbr_types.LA3")} - - - - - {t("joblines.fields.lbr_types.LA4")} - - - - - - - - - - - - - + + + + + + + {t("joblines.fields.lbr_types.LAA")} + + + + + {t("joblines.fields.lbr_types.LAB")} + + + + + {t("joblines.fields.lbr_types.LAD")} + + + + + {t("joblines.fields.lbr_types.LAE")} + + + + + {t("joblines.fields.lbr_types.LAF")} + + + + + {t("joblines.fields.lbr_types.LAG")} + + + + + {t("joblines.fields.lbr_types.LAM")} + + + + + {t("joblines.fields.lbr_types.LAR")} + + + + + {t("joblines.fields.lbr_types.LAS")} + + + + + {t("joblines.fields.lbr_types.LAU")} + + + + + {t("joblines.fields.lbr_types.LA1")} + + + + + {t("joblines.fields.lbr_types.LA2")} + + + + + {t("joblines.fields.lbr_types.LA3")} + + + + + {t("joblines.fields.lbr_types.LA4")} + + + + + + + + + + + + + + + + + + + + + + + {(fields, { add, remove, move }) => { + return ( +
    + {fields.map((field, index) => ( + + + + - - - - - - + + + - - - - - {(fields, {add, remove, move}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - - - - - - - - - - - - - - - - - + > + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - remove(field.name); - }} - /> - - - - - ))} - - - -
    - ); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + remove(field.name); + }} + /> + + + +
    + ))} + + + +
    + ); + }} +
    + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ShopEmployeeTeamsFormComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ShopEmployeeTeamsFormComponent); diff --git a/client/src/components/shop-teams/shop-employee-teams.list.jsx b/client/src/components/shop-teams/shop-employee-teams.list.jsx index 9254b8841..1a4f0a2cf 100644 --- a/client/src/components/shop-teams/shop-employee-teams.list.jsx +++ b/client/src/components/shop-teams/shop-employee-teams.list.jsx @@ -1,71 +1,68 @@ -import {Button, Table} from "antd"; +import { Button, Table } from "antd"; import queryString from "query-string"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {useLocation, useNavigate} from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { useLocation, useNavigate } from "react-router-dom"; -export default function ShopEmployeeTeamsListComponent({ - loading, - employee_teams, - }) { - const {t} = useTranslation(); - const history = useNavigate(); - const search = queryString.parse(useLocation().search); +export default function ShopEmployeeTeamsListComponent({ loading, employee_teams }) { + const { t } = useTranslation(); + const history = useNavigate(); + const search = queryString.parse(useLocation().search); - const handleOnRowClick = (record) => { - if (record) { - search.employeeTeamId = record.id; - history({search: queryString.stringify(search)}); - } else { - delete search.employeeTeamId; - history({search: queryString.stringify(search)}); - } - }; - const columns = [ - { - title: t("employee_teams.fields.name"), - dataIndex: "name", - key: "name", - }, - ]; + const handleOnRowClick = (record) => { + if (record) { + search.employeeTeamId = record.id; + history({ search: queryString.stringify(search) }); + } else { + delete search.employeeTeamId; + history({ search: queryString.stringify(search) }); + } + }; + const columns = [ + { + title: t("employee_teams.fields.name"), + dataIndex: "name", + key: "name" + } + ]; - return ( -
    -
    { - return ( - - ); - }} - loading={loading} - pagination={{position: "top"}} - columns={columns} - rowKey="id" - dataSource={employee_teams} - rowSelection={{ - onSelect: (props) => { - search.employeeTeamId = props.id; - history({search: queryString.stringify(search)}); - }, - type: "radio", - selectedRowKeys: [search.employeeTeamId], - }} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - handleOnRowClick(record); - }, - }; - }} - /> - - ); + return ( +
    +
    { + return ( + + ); + }} + loading={loading} + pagination={{ position: "top" }} + columns={columns} + rowKey="id" + dataSource={employee_teams} + rowSelection={{ + onSelect: (props) => { + search.employeeTeamId = props.id; + history({ search: queryString.stringify(search) }); + }, + type: "radio", + selectedRowKeys: [search.employeeTeamId] + }} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + handleOnRowClick(record); + } + }; + }} + /> + + ); } diff --git a/client/src/components/shop-teams/shop-teams.container.jsx b/client/src/components/shop-teams/shop-teams.container.jsx index 95f1ce400..8c9f277cf 100644 --- a/client/src/components/shop-teams/shop-teams.container.jsx +++ b/client/src/components/shop-teams/shop-teams.container.jsx @@ -1,44 +1,41 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {QUERY_TEAMS} from "../../graphql/employee_teams.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { QUERY_TEAMS } from "../../graphql/employee_teams.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; import ShopEmployeeTeamsListComponent from "./shop-employee-teams.list"; import ShopEmployeeTeamsFormComponent from "./shop-employee-teams.form.component"; -import {Col, Row} from "antd"; +import { Col, Row } from "antd"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -function ShopTeamsContainer({bodyshop}) { - const {loading, error, data} = useQuery(QUERY_TEAMS, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); +function ShopTeamsContainer({ bodyshop }) { + const { loading, error, data } = useQuery(QUERY_TEAMS, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - if (error) return ; + if (error) return ; - return ( -
    - - -
    - - - - - - - - - ); + return ( +
    + + +
    + + + + + + + + + ); } export default connect(mapStateToProps, null)(ShopTeamsContainer); diff --git a/client/src/components/shop-template-add/shop-template-add.component.jsx b/client/src/components/shop-template-add/shop-template-add.component.jsx index c917fd663..a3aa6f87a 100644 --- a/client/src/components/shop-template-add/shop-template-add.component.jsx +++ b/client/src/components/shop-template-add/shop-template-add.component.jsx @@ -1,88 +1,85 @@ -import {DownOutlined} from "@ant-design/icons"; -import {useMutation} from "@apollo/client"; -import {Dropdown} from "antd"; +import { DownOutlined } from "@ant-design/icons"; +import { useMutation } from "@apollo/client"; +import { Dropdown } from "antd"; import queryString from "query-string"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {INSERT_TEMPLATE, QUERY_TEMPLATES_BY_NAME_FOR_DUPE,} from "../../graphql/templates.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { INSERT_TEMPLATE, QUERY_TEMPLATES_BY_NAME_FOR_DUPE } from "../../graphql/templates.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import client from "../../utils/GraphQLClient"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { TemplateList } from "../../utils/TemplateConstants"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); export default connect(mapStateToProps, null)(ShopTemplateAddComponent); -export function ShopTemplateAddComponent({bodyshop, shopTemplateList, refetch}) { - const {t} = useTranslation(); - const search = queryString.parse(useLocation().search); - const history = useNavigate(); - const [insertTemplate] = useMutation(INSERT_TEMPLATE); +export function ShopTemplateAddComponent({ bodyshop, shopTemplateList, refetch }) { + const { t } = useTranslation(); + const search = queryString.parse(useLocation().search); + const history = useNavigate(); + const [insertTemplate] = useMutation(INSERT_TEMPLATE); - const shopTemplateKeys = shopTemplateList.map((template) => template.name); - const availableTemplateKeys = Object.keys(TemplateList()).filter( - (tkey) => !shopTemplateKeys.includes(tkey) - ); + const shopTemplateKeys = shopTemplateList.map((template) => template.name); + const availableTemplateKeys = Object.keys(TemplateList()).filter((tkey) => !shopTemplateKeys.includes(tkey)); - const handleAdd = async (item) => { - logImEXEvent("shop_template_add"); + const handleAdd = async (item) => { + logImEXEvent("shop_template_add"); - const defaultTemplateData = await client.query({ - query: QUERY_TEMPLATES_BY_NAME_FOR_DUPE, - variables: {name: item.key}, - }); + const defaultTemplateData = await client.query({ + query: QUERY_TEMPLATES_BY_NAME_FOR_DUPE, + variables: { name: item.key } + }); - const template = defaultTemplateData.data.templates.filter( - (t) => t.bodyshopid === null - )[0]; + const template = defaultTemplateData.data.templates.filter((t) => t.bodyshopid === null)[0]; - const result = await insertTemplate({ - variables: { - template: { - name: item.key, - bodyshopid: bodyshop.id, - jsontemplate: template && template.jsontemplate, - html: template && template.html, - query: template && template.query, - }, - }, - }); + const result = await insertTemplate({ + variables: { + template: { + name: item.key, + bodyshopid: bodyshop.id, + jsontemplate: template && template.jsontemplate, + html: template && template.html, + query: template && template.query + } + } + }); - const returningId = result.data.insert_templates.returning[0].id; - search.customTemplateId = returningId; - history({search: queryString.stringify(search)}); - if (!!refetch) refetch(); - }; - const TemplateListGenerated = TemplateList(); - const menu = { - onClick: handleAdd, + const returningId = result.data.insert_templates.returning[0].id; + search.customTemplateId = returningId; + history({ search: queryString.stringify(search) }); + if (!!refetch) refetch(); + }; + const TemplateListGenerated = TemplateList(); + const menu = { + onClick: handleAdd, - items: availableTemplateKeys.length > 0 - ? availableTemplateKeys.map((tkey) => ({ - key: tkey, - label: TemplateListGenerated[tkey].title, - })) - : [ - { - key: "no-templates", - label: t("bodyshop.labels.notemplatesavailable"), - disabled: true, - }, - ] - }; + items: + availableTemplateKeys.length > 0 + ? availableTemplateKeys.map((tkey) => ({ + key: tkey, + label: TemplateListGenerated[tkey].title + })) + : [ + { + key: "no-templates", + label: t("bodyshop.labels.notemplatesavailable"), + disabled: true + } + ] + }; - return ( - - - {t("bodyshop.actions.addtemplate")} - - - ); + return ( + + + {t("bodyshop.actions.addtemplate")} + + + ); } diff --git a/client/src/components/shop-template-delete/shop-template-delete.component.jsx b/client/src/components/shop-template-delete/shop-template-delete.component.jsx index 68f156091..68932d275 100644 --- a/client/src/components/shop-template-delete/shop-template-delete.component.jsx +++ b/client/src/components/shop-template-delete/shop-template-delete.component.jsx @@ -1,48 +1,48 @@ -import {useMutation} from "@apollo/client"; -import {Button, notification, Popconfirm} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, notification, Popconfirm } from "antd"; import queryString from "query-string"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {useLocation, useNavigate} from "react-router-dom"; -import {DELETE_TEMPLATE} from "../../graphql/templates.queries"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { useTranslation } from "react-i18next"; +import { useLocation, useNavigate } from "react-router-dom"; +import { DELETE_TEMPLATE } from "../../graphql/templates.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; -export default function ShopTemplateDeleteComponent({templateId, refetch}) { - const {t} = useTranslation(); - const search = queryString.parse(useLocation().search); - const history = useNavigate(); - const [deleteTemplate] = useMutation(DELETE_TEMPLATE); +export default function ShopTemplateDeleteComponent({ templateId, refetch }) { + const { t } = useTranslation(); + const search = queryString.parse(useLocation().search); + const history = useNavigate(); + const [deleteTemplate] = useMutation(DELETE_TEMPLATE); - const handleDelete = async () => { - logImEXEvent("shop_template_delete"); + const handleDelete = async () => { + logImEXEvent("shop_template_delete"); - const result = await deleteTemplate({ - variables: { - templateId: templateId, - }, - }); + const result = await deleteTemplate({ + variables: { + templateId: templateId + } + }); - if (!!result.errors) { - notification["error"]({ - message: t("bodyshop.errors.deletingtemplate", { - message: JSON.stringify(result.errors), - }), - }); - } else { - delete search.customTemplateId; - history({search: queryString.stringify(search)}); - if (!!refetch) refetch(); - } - }; + if (!!result.errors) { + notification["error"]({ + message: t("bodyshop.errors.deletingtemplate", { + message: JSON.stringify(result.errors) + }) + }); + } else { + delete search.customTemplateId; + history({ search: queryString.stringify(search) }); + if (!!refetch) refetch(); + } + }; - return ( - - - - ); + return ( + + + + ); } diff --git a/client/src/components/shop-template-editor-save-button/shop-template-editor-save-button.component.jsx b/client/src/components/shop-template-editor-save-button/shop-template-editor-save-button.component.jsx index fc7cf5366..5037eed5c 100644 --- a/client/src/components/shop-template-editor-save-button/shop-template-editor-save-button.component.jsx +++ b/client/src/components/shop-template-editor-save-button/shop-template-editor-save-button.component.jsx @@ -1,57 +1,53 @@ -import React, {useState} from "react"; -import {Button, notification} from "antd"; -import {useMutation} from "@apollo/client"; -import {useTranslation} from "react-i18next"; -import {UPDATE_TEMPLATE} from "../../graphql/templates.queries"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import React, { useState } from "react"; +import { Button, notification } from "antd"; +import { useMutation } from "@apollo/client"; +import { useTranslation } from "react-i18next"; +import { UPDATE_TEMPLATE } from "../../graphql/templates.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import axios from "axios"; -export default function ShopTemplateSaveButton({ - templateId, - gql, - emailEditorRef, - }) { - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const [updateTemplate] = useMutation(UPDATE_TEMPLATE); +export default function ShopTemplateSaveButton({ templateId, gql, emailEditorRef }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [updateTemplate] = useMutation(UPDATE_TEMPLATE); - const handleSave = async () => { - logImEXEvent("shop_template_update"); - setLoading(true); - emailEditorRef.current.exportHtml(async (data) => { - const response = await axios.post("/render/inlinecss", { - html: data.html, - url: `${window.location.protocol}://${window.location.host}/`, - }); + const handleSave = async () => { + logImEXEvent("shop_template_update"); + setLoading(true); + emailEditorRef.current.exportHtml(async (data) => { + const response = await axios.post("/render/inlinecss", { + html: data.html, + url: `${window.location.protocol}://${window.location.host}/` + }); - const result = await updateTemplate({ - variables: { - templateId: templateId, - template: { - query: gql, - html: response.data, - jsontemplate: data.design, - }, - }, - }); - if (!!!result.errors) { - notification["success"]({ - message: t("templates.successes.updated"), - }); - } else { - notification["error"]({ - message: t("templates.errors.updating", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); + const result = await updateTemplate({ + variables: { + templateId: templateId, + template: { + query: gql, + html: response.data, + jsontemplate: data.design + } + } + }); + if (!!!result.errors) { + notification["success"]({ + message: t("templates.successes.updated") }); - }; + } else { + notification["error"]({ + message: t("templates.errors.updating", { + error: JSON.stringify(result.errors) + }) + }); + } + setLoading(false); + }); + }; - return ( - - ); + return ( + + ); } diff --git a/client/src/components/shop-template-test-render/shop-template-test-render.component.jsx b/client/src/components/shop-template-test-render/shop-template-test-render.component.jsx index 6f7b8f496..441d0ca65 100644 --- a/client/src/components/shop-template-test-render/shop-template-test-render.component.jsx +++ b/client/src/components/shop-template-test-render/shop-template-test-render.component.jsx @@ -1,77 +1,69 @@ -import {Button} from "antd"; -import {JsonEditor as Editor} from "jsoneditor-react"; +import { Button } from "antd"; +import { JsonEditor as Editor } from "jsoneditor-react"; import "jsoneditor-react/es/editor.min.css"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function ShopTemplateTestRender({ - bodyshop, - query, - emailEditorRef, - style, - }) { - const [variables, setVariables] = useState({id: "uuid"}); - const [loading, setLoading] = useState(false); - const {t} = useTranslation(); +export function ShopTemplateTestRender({ bodyshop, query, emailEditorRef, style }) { + const [variables, setVariables] = useState({ id: "uuid" }); + const [loading, setLoading] = useState(false); + const { t } = useTranslation(); - const handleTestRender = async () => { + const handleTestRender = async () => { + try { + setLoading(true); + + emailEditorRef.current.exportHtml(async (data) => { try { - setLoading(true); + // const inlineHtml = await axios.post("/render/inlinecss", { + // html: data.html, + // url: `${window.location.protocol}://${window.location.host}/`, + // }); - emailEditorRef.current.exportHtml(async (data) => { - try { - // const inlineHtml = await axios.post("/render/inlinecss", { - // html: data.html, - // url: `${window.location.protocol}://${window.location.host}/`, - // }); + // const { data: contextData } = await client.query({ + // query: gql(query), + // variables: variables, + // + // }); - // const { data: contextData } = await client.query({ - // query: gql(query), - // variables: variables, - // - // }); + // const renderResponse = await axios.post("/render", { + // view: inlineHtml.data, + // context: { ...contextData, bodyshop: bodyshop }, + // }); + // displayTemplateInWindowNoprint(renderResponse.data); - // const renderResponse = await axios.post("/render", { - // view: inlineHtml.data, - // context: { ...contextData, bodyshop: bodyshop }, - // }); - // displayTemplateInWindowNoprint(renderResponse.data); - - setLoading(false); - } catch (error) { - setLoading(false); - alert(error); - } - }); + setLoading(false); } catch (error) { - setLoading(false); - alert(error); + setLoading(false); + alert(error); } - }; + }); + } catch (error) { + setLoading(false); + alert(error); + } + }; - return ( -
    -
    - setVariables(e)}/> -
    - -
    - ); + return ( +
    +
    + setVariables(e)} /> +
    + +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ShopTemplateTestRender); +export default connect(mapStateToProps, mapDispatchToProps)(ShopTemplateTestRender); diff --git a/client/src/components/shop-templates-list/shop-templates-list.container.jsx b/client/src/components/shop-templates-list/shop-templates-list.container.jsx index 87d47f9f7..975a090f6 100644 --- a/client/src/components/shop-templates-list/shop-templates-list.container.jsx +++ b/client/src/components/shop-templates-list/shop-templates-list.container.jsx @@ -1,79 +1,66 @@ -import {useQuery} from "@apollo/client"; -import {Button, Drawer, List} from "antd"; +import { useQuery } from "@apollo/client"; +import { Button, Drawer, List } from "antd"; import React from "react"; -import {QUERY_CUSTOM_TEMPLATES} from "../../graphql/templates.queries"; +import { QUERY_CUSTOM_TEMPLATES } from "../../graphql/templates.queries"; import AlertComponent from "../alert/alert.component"; import Skeleton from "../loading-skeleton/loading-skeleton.component"; -import {useTranslation} from "react-i18next"; -import {useLocation, useNavigate} from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { useLocation, useNavigate } from "react-router-dom"; import queryString from "query-string"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { TemplateList } from "../../utils/TemplateConstants"; import ShopTemplateAdd from "../shop-template-add/shop-template-add.component"; import ShopTemplateDeleteComponent from "../shop-template-delete/shop-template-delete.component"; -export default function ShopTemplatesListContainer({openState}) { - const [open, setOpen] = openState; - const {loading, error, data, refetch} = useQuery(QUERY_CUSTOM_TEMPLATES, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); - const {t} = useTranslation(); - const search = queryString.parse(useLocation().search); - const history = useNavigate(); - if (error) return ; +export default function ShopTemplatesListContainer({ openState }) { + const [open, setOpen] = openState; + const { loading, error, data, refetch } = useQuery(QUERY_CUSTOM_TEMPLATES, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const { t } = useTranslation(); + const search = queryString.parse(useLocation().search); + const history = useNavigate(); + if (error) return ; - const handleEdit = (record) => { - if (record) { - if (record.id) { - search.customTemplateId = record.id; - history({search: queryString.stringify(search)}); - } - } else { - delete search.customTemplateId; - history({search: queryString.stringify(search)}); - } - }; + const handleEdit = (record) => { + if (record) { + if (record.id) { + search.customTemplateId = record.id; + history({ search: queryString.stringify(search) }); + } + } else { + delete search.customTemplateId; + history({ search: queryString.stringify(search) }); + } + }; - return ( - ( setOpen(false)} - > -
    -
    {t("bodyshop.labels.customtemplates")}
    - - ( - handleEdit(item)}> - {t("general.actions.edit")} - , - , - ]} - > - -
    -
    {TemplateList()[item.name].title}
    -
    {TemplateList()[item.name].description}
    -
    {TemplateList()[item.name].drivingid}
    -
    -
    -
    - )} - /> -
    -
    ) - ); + return ( + setOpen(false)}> +
    +
    {t("bodyshop.labels.customtemplates")}
    + + ( + handleEdit(item)}>{t("general.actions.edit")}, + + ]} + > + +
    +
    {TemplateList()[item.name].title}
    +
    {TemplateList()[item.name].description}
    +
    {TemplateList()[item.name].drivingid}
    +
    +
    +
    + )} + /> +
    +
    + ); } diff --git a/client/src/components/shop-users-auth-edit/shop-users-auth-edit.component.jsx b/client/src/components/shop-users-auth-edit/shop-users-auth-edit.component.jsx index 2154ae4b2..065555fb8 100644 --- a/client/src/components/shop-users-auth-edit/shop-users-auth-edit.component.jsx +++ b/client/src/components/shop-users-auth-edit/shop-users-auth-edit.component.jsx @@ -1,51 +1,47 @@ -import {InputNumber, notification} from "antd"; -import React, {useState} from "react"; -import {useMutation} from "@apollo/client"; -import {useTranslation} from "react-i18next"; -import {UPDATE_ASSOCIATION} from "../../graphql/user.queries"; +import { InputNumber, notification } from "antd"; +import React, { useState } from "react"; +import { useMutation } from "@apollo/client"; +import { useTranslation } from "react-i18next"; +import { UPDATE_ASSOCIATION } from "../../graphql/user.queries"; -export default function ShopUsersAuthEdit({association}) { - const {t} = useTranslation(); - const [updateAssociation] = useMutation(UPDATE_ASSOCIATION); - const [open, setOpen] = useState(false); - const [value, setValue] = useState(association.authlevel); +export default function ShopUsersAuthEdit({ association }) { + const { t } = useTranslation(); + const [updateAssociation] = useMutation(UPDATE_ASSOCIATION); + const [open, setOpen] = useState(false); + const [value, setValue] = useState(association.authlevel); - const handleSave = async () => { - setOpen(false); - const result = await updateAssociation({ - variables: { - assocId: association.id, - assoc: {authlevel: value}, - }, - }); + const handleSave = async () => { + setOpen(false); + const result = await updateAssociation({ + variables: { + assocId: association.id, + assoc: { authlevel: value } + } + }); - if (!!result.errors) { - notification["error"]({ - message: t("user.errors.updating", { - message: JSON.stringify(result.errors), - }), - }); - } - }; + if (!!result.errors) { + notification["error"]({ + message: t("user.errors.updating", { + message: JSON.stringify(result.errors) + }) + }); + } + }; - return ( + return ( +
    + {open && (
    - {open && ( -
    - setValue(val)} - defaultValue={association.authlevel} - onBlur={handleSave} - /> -
    - )} - {!open && ( -
    - {association.authlevel || t("general.labels.na")} -
    - )} + setValue(val)} + defaultValue={association.authlevel} + onBlur={handleSave} + />
    - ); + )} + {!open &&
    {association.authlevel || t("general.labels.na")}
    } +
    + ); } diff --git a/client/src/components/shop-users/shop-users.component.jsx b/client/src/components/shop-users/shop-users.component.jsx index 5de8b6b27..6e3343b3e 100644 --- a/client/src/components/shop-users/shop-users.component.jsx +++ b/client/src/components/shop-users/shop-users.component.jsx @@ -1,78 +1,74 @@ -import {useQuery} from "@apollo/client"; -import {Button, Table} from "antd"; +import { useQuery } from "@apollo/client"; +import { Button, Table } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {QUERY_SHOP_ASSOCIATIONS} from "../../graphql/user.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { QUERY_SHOP_ASSOCIATIONS } from "../../graphql/user.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; import ShopUsersAuthEdit from "../shop-users-auth-edit/shop-users-auth-edit.component"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ShopInfoUsersComponent); +export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoUsersComponent); -export function ShopInfoUsersComponent({bodyshop}) { - const {t} = useTranslation(); - const {loading, error, data} = useQuery(QUERY_SHOP_ASSOCIATIONS, { - variables: {shopid: bodyshop.id}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); - const columns = [ - { - title: t("user.fields.email"), - dataIndex: "email", - key: "email", - render: (text, record) => record.user.email, - }, - { - title: t("user.fields.authlevel"), - dataIndex: "authlevel", - key: "authlevel", - render: (text, record) => ( - - - - ), - }, - { - title: t("user.labels.actions"), - dataIndex: "actions", - key: "actions", - render: (text, record) => ( -
    - -
    - ), - }, - ]; - - if (error) { - return ; - } - return ( +export function ShopInfoUsersComponent({ bodyshop }) { + const { t } = useTranslation(); + const { loading, error, data } = useQuery(QUERY_SHOP_ASSOCIATIONS, { + variables: { shopid: bodyshop.id }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const columns = [ + { + title: t("user.fields.email"), + dataIndex: "email", + key: "email", + render: (text, record) => record.user.email + }, + { + title: t("user.fields.authlevel"), + dataIndex: "authlevel", + key: "authlevel", + render: (text, record) => ( + + + + ) + }, + { + title: t("user.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => (
    -
    + - ); + ) + } + ]; + + if (error) { + return ; + } + return ( +
    +
    + + ); } diff --git a/client/src/components/sign-in-form/sign-in-form.component.jsx b/client/src/components/sign-in-form/sign-in-form.component.jsx index 100caa623..0319cff6a 100644 --- a/client/src/components/sign-in-form/sign-in-form.component.jsx +++ b/client/src/components/sign-in-form/sign-in-form.component.jsx @@ -1,114 +1,107 @@ -import {LockOutlined, UserOutlined} from "@ant-design/icons"; -import {Button, Form, Input, Typography} from "antd"; +import { LockOutlined, UserOutlined } from "@ant-design/icons"; +import { Button, Form, Input, Typography } from "antd"; import queryString from "query-string"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import RomeLogo from "../../assets/RomeOnlineBlue.png"; import ImEXOnlineLogo from "../../assets/logo192.png"; -import ProManagerLogo from '../../assets/promanager/ProManagerLogo.gif'; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; -import {emailSignInStart, sendPasswordReset,} from "../../redux/user/user.actions"; -import {selectCurrentUser, selectLoginLoading, selectSignInError,} from "../../redux/user/user.selectors"; +import ProManagerLogo from "../../assets/promanager/ProManagerLogo.gif"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; +import { emailSignInStart, sendPasswordReset } from "../../redux/user/user.actions"; +import { selectCurrentUser, selectLoginLoading, selectSignInError } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import "./sign-in-form.styles.scss"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - signInError: selectSignInError, - loginLoading: selectLoginLoading, + currentUser: selectCurrentUser, + signInError: selectSignInError, + loginLoading: selectLoginLoading }); const mapDispatchToProps = (dispatch) => ({ - emailSignInStart: (email, password) => - dispatch(emailSignInStart({email, password})), - sendPasswordReset: (email) => dispatch(sendPasswordReset(email)), + emailSignInStart: (email, password) => dispatch(emailSignInStart({ email, password })), + sendPasswordReset: (email) => dispatch(sendPasswordReset(email)) }); -export function SignInComponent({ - emailSignInStart, - currentUser, - signInError, - sendPasswordReset, - loginLoading, - }) { - const {redirect} = queryString.parse(useLocation().search); - const navigate = useNavigate(); +export function SignInComponent({ emailSignInStart, currentUser, signInError, sendPasswordReset, loginLoading }) { + const { redirect } = queryString.parse(useLocation().search); + const navigate = useNavigate(); - const {t} = useTranslation(); - const handleFinish = (values) => { - const {email, password} = values; - emailSignInStart(email, password); - }; - const [form] = Form.useForm(); + const { t } = useTranslation(); + const handleFinish = (values) => { + const { email, password } = values; + emailSignInStart(email, password); + }; + const [form] = Form.useForm(); - useEffect(() => { - if (currentUser.authorized === true) { - navigate(redirect || "/manage/"); - } - }, [currentUser, redirect, navigate]); + useEffect(() => { + if (currentUser.authorized === true) { + navigate(redirect || "/manage/"); + } + }, [currentUser, redirect, navigate]); - - return ( -
    -
    - {InstanceRenderManager({imex:t("titles.imexonline"), - { - InstanceRenderManager({imex: t("titles.imexonline"), rome: t("titles.romeonline"), promanager:null}) - } -
    -
    - - } - placeholder={t("general.labels.username")} - /> - - - } - type="password" - placeholder={t("general.labels.password")} - /> - - {signInError ? ( - - ) : null} - - - - - -
    - ); + return ( +
    +
    + {InstanceRenderManager({ + + {InstanceRenderManager({ + imex: t("titles.imexonline"), + rome: t("titles.romeonline"), + promanager: null + })} + +
    +
    + + } placeholder={t("general.labels.username")} /> + + + } type="password" placeholder={t("general.labels.password")} /> + + {signInError ? ( + + ) : null} + + + + + +
    + ); } export default connect(mapStateToProps, mapDispatchToProps)(SignInComponent); diff --git a/client/src/components/tech-header/tech-header.component.jsx b/client/src/components/tech-header/tech-header.component.jsx index 26ce84883..7b9929059 100644 --- a/client/src/components/tech-header/tech-header.component.jsx +++ b/client/src/components/tech-header/tech-header.component.jsx @@ -1,32 +1,32 @@ -import {Layout, Typography} from "antd"; +import { Layout, Typography } from "antd"; import React from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {useTranslation} from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { useTranslation } from "react-i18next"; -const {Header} = Layout; +const { Header } = Layout; const mapStateToProps = createStructuredSelector({ - technician: selectTechnician, + technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function TechHeader({technician}) { - const {t} = useTranslation(); - return ( -
    - - {!!technician - ? t("tech.labels.loggedin", { - name: `${technician.first_name} ${technician.last_name}`, - }) - : t("tech.labels.notloggedin")} - -
    - ); +export function TechHeader({ technician }) { + const { t } = useTranslation(); + return ( +
    + + {!!technician + ? t("tech.labels.loggedin", { + name: `${technician.first_name} ${technician.last_name}` + }) + : t("tech.labels.notloggedin")} + +
    + ); } export default connect(mapStateToProps, mapDispatchToProps)(TechHeader); diff --git a/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.component.jsx b/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.component.jsx index abfda8993..f6eeeb487 100644 --- a/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.component.jsx +++ b/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.component.jsx @@ -1,96 +1,86 @@ -import {Divider, Form, Select} from "antd"; +import { Divider, Form, Select } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import JobSearchSelect from "../job-search-select/job-search-select.component"; import JobsDetailLaborContainer from "../jobs-detail-labor/jobs-detail-labor.container"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - technician: selectTechnician, + bodyshop: selectBodyshop, + technician: selectTechnician }); -export function TechClockInComponent({form, bodyshop, technician}) { - const {t} = useTranslation(); +export function TechClockInComponent({ form, bodyshop, technician }) { + const { t } = useTranslation(); + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); - const {treatments: {Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, - }); + const emps = bodyshop.employees.filter((e) => e.id === technician.id)[0]; - const emps = bodyshop.employees.filter((e) => e.id === technician.id)[0]; + return ( +
    + + + + - return ( -
    - - - - - - - - - - - - prevValues.jobid !== curValues.jobid - } - > - {() => { - if (!form.getFieldValue("jobid")) return null; - return ( - - ); - }} - -
    - ); + + + +
    + + prevValues.jobid !== curValues.jobid}> + {() => { + if (!form.getFieldValue("jobid")) return null; + return ; + }} + +
    + ); } export default connect(mapStateToProps, null)(TechClockInComponent); diff --git a/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx b/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx index 0b630220d..978cdf5cf 100644 --- a/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx +++ b/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx @@ -1,168 +1,141 @@ -import {useMutation} from "@apollo/client"; -import {Button, Card, Form, notification, Space} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Card, Form, notification, Space } from "antd"; import axios from "axios"; import dayjs from "../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {INSERT_NEW_TIME_TICKET} from "../../graphql/timetickets.queries"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component"; import TechClockInComponent from "./tech-job-clock-in-form.component"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - technician: selectTechnician, - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + technician: selectTechnician, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - setTimeTicketContext: (context) => - dispatch(setModalContext({context: context, modal: "timeTicket"})), + setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })) }); -export function TechClockInContainer({ - setTimeTicketContext, - technician, - bodyshop, - currentUser, - }) { - const {treatments: {Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, - }); +export function TechClockInContainer({ setTimeTicketContext, technician, bodyshop, currentUser }) { + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); - const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET, { - refetchQueries: ["QUERY_ACTIVE_TIME_TICKETS"], - }); - const {t} = useTranslation(); + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET, { + refetchQueries: ["QUERY_ACTIVE_TIME_TICKETS"] + }); + const { t } = useTranslation(); - const emps = bodyshop.employees.filter( - (e) => e.id === (technician && technician.id) - )[0]; + const emps = bodyshop.employees.filter((e) => e.id === (technician && technician.id))[0]; - const TechForm = () => { - if (technician) { - return - - - } else { - return
    - } + const TechForm = () => { + if (technician) { + return ( +
    + + + ); + } else { + return
    ; } + }; - const handleFinish = async (values) => { - setLoading(true); - const theTime = (await axios.post("/utils/time")).data; - const result = await insertTimeTicket({ - variables: { - timeTicketInput: [ - { - bodyshopid: bodyshop.id, - employeeid: technician.id, - date: - typeof bodyshop.timezone === "string" - // TODO: Client Update - This may be broken - ? dayjs.tz(theTime, bodyshop.timezone).format("YYYY-MM-DD") - : typeof bodyshop.timezone === "number" - ? dayjs(theTime) - .format("YYYY-MM-DD") - .utcOffset(bodyshop.timezone) - : dayjs(theTime).format("YYYY-MM-DD"), - clockon: dayjs(theTime), - jobid: values.jobid, - cost_center: values.cost_center, - ciecacode: - bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === 'on' - ? values.cost_center - : Object.keys( - bodyshop.md_responsibility_centers.defaults.costs - ).find((key) => { - return ( - bodyshop.md_responsibility_centers.defaults.costs[key] === - values.cost_center - ); - }), - created_by: currentUser.email.concat( - " | ", - technician.employee_number - .concat(" ", technician.first_name, " ", technician.last_name) - .trim() - ), - }, - ], - }, - }); + const handleFinish = async (values) => { + setLoading(true); + const theTime = (await axios.post("/utils/time")).data; + const result = await insertTimeTicket({ + variables: { + timeTicketInput: [ + { + bodyshopid: bodyshop.id, + employeeid: technician.id, + date: + typeof bodyshop.timezone === "string" + ? // TODO: Client Update - This may be broken + dayjs.tz(theTime, bodyshop.timezone).format("YYYY-MM-DD") + : typeof bodyshop.timezone === "number" + ? dayjs(theTime).format("YYYY-MM-DD").utcOffset(bodyshop.timezone) + : dayjs(theTime).format("YYYY-MM-DD"), + clockon: dayjs(theTime), + jobid: values.jobid, + cost_center: values.cost_center, + ciecacode: + bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on" + ? values.cost_center + : Object.keys(bodyshop.md_responsibility_centers.defaults.costs).find((key) => { + return bodyshop.md_responsibility_centers.defaults.costs[key] === values.cost_center; + }), + created_by: currentUser.email.concat( + " | ", + technician.employee_number.concat(" ", technician.first_name, " ", technician.last_name).trim() + ) + } + ] + } + }); - if (!!result.errors) { - notification["error"]({ - message: t("timetickets.errors.clockingin", { - message: JSON.stringify(result.errors), - }), - }); - } else { - notification["success"]({ - message: t("timetickets.successes.clockedin"), - }); - } - form.resetFields(); - setLoading(false); - }; + if (!!result.errors) { + notification["error"]({ + message: t("timetickets.errors.clockingin", { + message: JSON.stringify(result.errors) + }) + }); + } else { + notification["success"]({ + message: t("timetickets.successes.clockedin") + }); + } + form.resetFields(); + setLoading(false); + }; - return ( - - - - - - } - > - - - ); + return ( + + + + + + } + > + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(TechClockInContainer); +export default connect(mapStateToProps, mapDispatchToProps)(TechClockInContainer); diff --git a/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx b/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx index 6db770386..abc6488ac 100644 --- a/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx +++ b/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx @@ -1,304 +1,265 @@ -import {useMutation, useQuery} from "@apollo/client"; -import {Button, Card, Col, Form, InputNumber, notification, Popover, Row, Select,} from "antd"; +import { useMutation, useQuery } from "@apollo/client"; +import { Button, Card, Col, Form, InputNumber, notification, Popover, Row, Select } from "antd"; import axios from "axios"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {GET_LINE_TICKET_BY_PK} from "../../graphql/jobs-lines.queries"; -import {UPDATE_JOB_STATUS} from "../../graphql/jobs.queries"; -import {UPDATE_TIME_TICKET} from "../../graphql/timetickets.queries"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {CalculateAllocationsTotals} from "../labor-allocations-table/labor-allocations-table.utility"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries"; +import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries"; +import { UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility"; import TechJobClockoutDelete from "../tech-job-clock-out-delete/tech-job-clock-out-delete.component"; -import {LaborAllocationContainer} from "../time-ticket-modal/time-ticket-modal.component"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { LaborAllocationContainer } from "../time-ticket-modal/time-ticket-modal.component"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - technician: selectTechnician, + bodyshop: selectBodyshop, + technician: selectTechnician }); export function TechClockOffButton({ - bodyshop, - technician, - jobId, - timeTicketId, - completedCallback, - isShiftTicket, - otherBtnProps, - }) { - const [loading, setLoading] = useState(false); - const [updateTimeticket] = useMutation(UPDATE_TIME_TICKET); - const [updateJobStatus] = useMutation(UPDATE_JOB_STATUS); - const [form] = Form.useForm(); - const {treatments: {Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, + bodyshop, + technician, + jobId, + timeTicketId, + completedCallback, + isShiftTicket, + otherBtnProps +}) { + const [loading, setLoading] = useState(false); + const [updateTimeticket] = useMutation(UPDATE_TIME_TICKET); + const [updateJobStatus] = useMutation(UPDATE_JOB_STATUS); + const [form] = Form.useForm(); + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); + + const { queryLoading, data: lineTicketData } = useQuery(GET_LINE_TICKET_BY_PK, { + variables: { + id: jobId + }, + skip: !jobId, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const { t } = useTranslation(); + const emps = bodyshop.employees.filter((e) => e.id === (technician && technician.id))[0]; + + const handleFinish = async (values) => { + logImEXEvent("tech_clock_out_job"); + const status = values.status; + delete values.status; + setLoading(true); + const result = await updateTimeticket({ + variables: { + timeticketId: timeTicketId, + timeticket: { + clockoff: (await axios.post("/utils/time")).data, + ...values, + rate: emps && emps.rates.filter((r) => r.cost_center === values.cost_center)[0]?.rate, + flat_rate: emps && emps.flat_rate, + ciecacode: + bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on" + ? values.cost_center + : Object.keys(bodyshop.md_responsibility_centers.defaults.costs).find((key) => { + return bodyshop.md_responsibility_centers.defaults.costs[key] === values.cost_center; + }) + } + } }); - const {queryLoading, data: lineTicketData} = useQuery( - GET_LINE_TICKET_BY_PK, - { - variables: { - id: jobId, - }, - skip: !jobId, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", + if (!!result.errors) { + notification["error"]({ + message: t("timetickets.errors.clockingout", { + message: JSON.stringify(result.errors) + }) + }); + } else { + notification["success"]({ + message: t("timetickets.successes.clockedout") + }); + } + if (!isShiftTicket) { + const job_update_result = await updateJobStatus({ + variables: { + jobId: jobId, + status: status } - ); - const {t} = useTranslation(); - const emps = bodyshop.employees.filter( - (e) => e.id === (technician && technician.id) - )[0]; + }); - const handleFinish = async (values) => { - logImEXEvent("tech_clock_out_job"); - const status = values.status; - delete values.status; - setLoading(true); - const result = await updateTimeticket({ - variables: { - timeticketId: timeTicketId, - timeticket: { - clockoff: (await axios.post("/utils/time")).data, - ...values, - rate: - emps && - emps.rates.filter((r) => r.cost_center === values.cost_center)[0] - ?.rate, - flat_rate: emps && emps.flat_rate, - ciecacode: - bodyshop.cdk_dealerid || - bodyshop.pbs_serialnumber || - Enhanced_Payroll.treatment === "on" - ? values.cost_center - : Object.keys( - bodyshop.md_responsibility_centers.defaults.costs - ).find((key) => { - return ( - bodyshop.md_responsibility_centers.defaults.costs[key] === - values.cost_center - ); - }), - }, - }, + if (!!job_update_result.errors) { + notification["error"]({ + message: t("jobs.errors.updating", { + message: JSON.stringify(result.errors) + }) }); + } else { + notification["success"]({ + message: t("jobs.successes.updated") + }); + } + } + setLoading(false); + if (completedCallback) completedCallback(); + }; - if (!!result.errors) { - notification["error"]({ - message: t("timetickets.errors.clockingout", { - message: JSON.stringify(result.errors), - }), - }); - } else { - notification["success"]({ - message: t("timetickets.successes.clockedout"), - }); - } - if (!isShiftTicket) { - const job_update_result = await updateJobStatus({ - variables: { - jobId: jobId, - status: status, - }, - }); + const overlay = ( + +
    +
    + +
    + {!isShiftTicket ? ( +
    + + + + ({ + validator(rule, value) { + if (!bodyshop.tt_enforce_hours_for_tech_console) { + return Promise.resolve(); + } + if (!value || getFieldValue("cost_center") === null || !lineTicketData) + return Promise.resolve(); - if (!!job_update_result.errors) { - notification["error"]({ - message: t("jobs.errors.updating", { - message: JSON.stringify(result.errors), - }), - }); - } else { - notification["success"]({ - message: t("jobs.successes.updated"), - }); - } - } - setLoading(false); - if (completedCallback) completedCallback(); - }; + //Check the cost center, + const totals = CalculateAllocationsTotals( + bodyshop, + lineTicketData.joblines, + lineTicketData.timetickets, + lineTicketData.jobs_by_pk.lbr_adjustments + ); - const overlay = ( - -
    - total[fieldTypeToCheck] === getFieldValue("cost_center")) + ?.difference * 10 + ) / 10; + + if (value > costCenterDiff) + return Promise.reject(t("timetickets.validation.hoursenteredmorethanavailable")); + else { + return Promise.resolve(); + } + } + }) + ]} + > + + +
    + ) : null} + + + + + {isShiftTicket ? ( +
    + ) : ( + - -
    - {!isShiftTicket ? ( -
    - - - - ({ - validator(rule, value) { - if (!bodyshop.tt_enforce_hours_for_tech_console) { - return Promise.resolve(); - } - if ( - !value || - getFieldValue("cost_center") === null || - !lineTicketData - ) - return Promise.resolve(); + + + )} + + + + {!isShiftTicket && ( +
    + + + )} + + + + + ); - //Check the cost center, - const totals = CalculateAllocationsTotals( - bodyshop, - lineTicketData.joblines, - lineTicketData.timetickets, - lineTicketData.jobs_by_pk.lbr_adjustments - ); - - const fieldTypeToCheck = - bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber - ? "mod_lbr_ty" - : "cost_center"; - - const costCenterDiff = - Math.round( - totals.find( - (total) => - total[fieldTypeToCheck] === - getFieldValue("cost_center") - )?.difference * 10 - ) / 10; - - if (value > costCenterDiff) - return Promise.reject( - t( - "timetickets.validation.hoursenteredmorethanavailable" - ) - ); - else { - return Promise.resolve(); - } - }, - }), - ]} - > - - - - ) : null} - - - - - {isShiftTicket ? ( -
    - ) : ( - - - - )} - - - - {!isShiftTicket && ( -
    - - - )} - - - - - ); - - return ( - - - - ); + return ( + + + + ); } export default connect(mapStateToProps, null)(TechClockOffButton); diff --git a/client/src/components/tech-job-clock-out-delete/tech-job-clock-out-delete.component.jsx b/client/src/components/tech-job-clock-out-delete/tech-job-clock-out-delete.component.jsx index d28bfde5f..b0c33eb6b 100644 --- a/client/src/components/tech-job-clock-out-delete/tech-job-clock-out-delete.component.jsx +++ b/client/src/components/tech-job-clock-out-delete/tech-job-clock-out-delete.component.jsx @@ -1,47 +1,44 @@ import React from "react"; -import {notification, Popconfirm} from "antd"; -import {DeleteFilled} from "@ant-design/icons"; -import {DELETE_TIME_TICKET} from "../../graphql/timetickets.queries"; -import {useTranslation} from "react-i18next"; -import {useMutation} from "@apollo/client"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { notification, Popconfirm } from "antd"; +import { DeleteFilled } from "@ant-design/icons"; +import { DELETE_TIME_TICKET } from "../../graphql/timetickets.queries"; +import { useTranslation } from "react-i18next"; +import { useMutation } from "@apollo/client"; +import { logImEXEvent } from "../../firebase/firebase.utils"; -export default function TechJobClockoutDelete({ - timeTicketId, - completedCallback, - }) { - const [deleteTimeTicket] = useMutation(DELETE_TIME_TICKET); - const {t} = useTranslation(); - const handleDelete = async () => { - logImEXEvent("tech_clock_delete"); +export default function TechJobClockoutDelete({ timeTicketId, completedCallback }) { + const [deleteTimeTicket] = useMutation(DELETE_TIME_TICKET); + const { t } = useTranslation(); + const handleDelete = async () => { + logImEXEvent("tech_clock_delete"); - const result = await deleteTimeTicket({ - variables: {id: timeTicketId}, - refetchQueries: ["QUERY_ACTIVE_TIME_TICKETS"], - }); + const result = await deleteTimeTicket({ + variables: { id: timeTicketId }, + refetchQueries: ["QUERY_ACTIVE_TIME_TICKETS"] + }); - if (!!result.errors) { - notification["error"]({ - message: t("timetickets.errors.deleting", { - message: JSON.stringify(result.errors), - }), - }); - } else { - notification["success"]({ - message: t("timetickets.successes.deleted"), - }); - } - if (completedCallback) completedCallback(); - }; + if (!!result.errors) { + notification["error"]({ + message: t("timetickets.errors.deleting", { + message: JSON.stringify(result.errors) + }) + }); + } else { + notification["success"]({ + message: t("timetickets.successes.deleted") + }); + } + if (completedCallback) completedCallback(); + }; - return ( - - - - ); + return ( + + + + ); } diff --git a/client/src/components/tech-job-clocked-in-list/tech-job-clocked-in-list.component.jsx b/client/src/components/tech-job-clocked-in-list/tech-job-clocked-in-list.component.jsx index aa16bcd41..19317bb34 100644 --- a/client/src/components/tech-job-clocked-in-list/tech-job-clocked-in-list.component.jsx +++ b/client/src/components/tech-job-clocked-in-list/tech-job-clocked-in-list.component.jsx @@ -1,101 +1,86 @@ -import {Card, List, Typography} from "antd"; +import { Card, List, Typography } from "antd"; import React from "react"; -import {useQuery} from "@apollo/client"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {QUERY_ACTIVE_TIME_TICKETS} from "../../graphql/timetickets.queries"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; +import { useQuery } from "@apollo/client"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { QUERY_ACTIVE_TIME_TICKETS } from "../../graphql/timetickets.queries"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; import AlertComponent from "../alert/alert.component"; import DataLabel from "../data-label/data-label.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component"; -import {OwnerNameDisplayFunction} from "../owner-name-display/owner-name-display.component"; +import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; const mapStateToProps = createStructuredSelector({ - technician: selectTechnician, + technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function TechClockedInList({technician}) { - const {loading, error, data, refetch} = useQuery( - QUERY_ACTIVE_TIME_TICKETS, - { - variables: { - employeeId: technician?.id, - }, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - skip: !technician, - } - ); +export function TechClockedInList({ technician }) { + const { loading, error, data, refetch } = useQuery(QUERY_ACTIVE_TIME_TICKETS, { + variables: { + employeeId: technician?.id + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + skip: !technician + }); - const {t} = useTranslation(); - if (loading) return ; - if (error) return ; + const { t } = useTranslation(); + if (loading) return ; + if (error) return ; - return ( + return ( +
    + {data?.timetickets?.length > 0 ? (
    - {data?.timetickets?.length > 0 ? ( -
    - - {t("timetickets.labels.alreadyclockedon")} - - ( - - - {`${ - ticket.job.ro_number || t("general.labels.na") - } ${OwnerNameDisplayFunction(ticket.job)}`} - - } - actions={[ - , - ]} - > -
    - {` - ${ticket.job.v_model_yr || ""} ${ - ticket.job.v_make_desc || "" - } ${ticket.job.v_model_desc || ""}`} -
    - - {ticket.clockon} - - - {ticket.cost_center === "timetickets.labels.shift" - ? t(ticket.cost_center) - : ticket.cost_center} - -
    -
    - )} - >
    -
    - ) : null} + {t("timetickets.labels.alreadyclockedon")} + ( + + + {`${ticket.job.ro_number || t("general.labels.na")} ${OwnerNameDisplayFunction(ticket.job)}`} + + } + actions={[ + + ]} + > +
    + {` + ${ticket.job.v_model_yr || ""} ${ticket.job.v_make_desc || ""} ${ticket.job.v_model_desc || ""}`} +
    + + {ticket.clockon} + + + {ticket.cost_center === "timetickets.labels.shift" ? t(ticket.cost_center) : ticket.cost_center} + +
    +
    + )} + >
    - ); + ) : null} +
    + ); } export default connect(mapStateToProps, mapDispatchToProps)(TechClockedInList); diff --git a/client/src/components/tech-job-print-tickets/tech-job-print-tickets.component.jsx b/client/src/components/tech-job-print-tickets/tech-job-print-tickets.component.jsx index 58769e26c..3802c4fb5 100644 --- a/client/src/components/tech-job-print-tickets/tech-job-print-tickets.component.jsx +++ b/client/src/components/tech-job-print-tickets/tech-job-print-tickets.component.jsx @@ -1,147 +1,128 @@ -import {Button, Card, DatePicker, Form, Popover, Radio, Space} from "antd"; +import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd"; import dayjs from "../../utils/day"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; import DatePIckerRanges from "../../utils/DatePickerRanges"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; const mapStateToProps = createStructuredSelector({ - technician: selectTechnician, + technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(TechJobPrintTickets); +export default connect(mapStateToProps, mapDispatchToProps)(TechJobPrintTickets); -export function TechJobPrintTickets({technician, event, attendacePrint}) { - const {t} = useTranslation(); +export function TechJobPrintTickets({ technician, event, attendacePrint }) { + const { t } = useTranslation(); - const [loading, setLoading] = useState(false); - const [form] = Form.useForm(); - const [visibility, setVisibility] = useState(false); - const Templates = TemplateList("report_center"); + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + const [visibility, setVisibility] = useState(false); + const Templates = TemplateList("report_center"); - useEffect(() => { - if (visibility && event) { - form.setFieldsValue(event); - } - }, [visibility, form, event]); + useEffect(() => { + if (visibility && event) { + form.setFieldsValue(event); + } + }, [visibility, form, event]); - const handleFinish = async (values) => { - logImEXEvent("schedule_manual_event"); + const handleFinish = async (values) => { + logImEXEvent("schedule_manual_event"); - setLoading(true); - const start = values.dates[0]; - const end = values.dates[1]; + setLoading(true); + const start = values.dates[0]; + const end = values.dates[1]; - try { - await GenerateDocument( - { - name: - attendacePrint === true - ? Templates.attendance_employee.key - : Templates.timetickets_employee.key, - variables: { - ...(start - ? {start: dayjs(start).startOf("day").format("YYYY-MM-DD")} - : {}), - ...(end - ? {end: dayjs(end).endOf("day").format("YYYY-MM-DD")} - : {}), - ...(start ? {starttz: dayjs(start).startOf("day")} : {}), - ...(end ? {endtz: dayjs(end).endOf("day")} : {}), + try { + await GenerateDocument( + { + name: attendacePrint === true ? Templates.attendance_employee.key : Templates.timetickets_employee.key, + variables: { + ...(start ? { start: dayjs(start).startOf("day").format("YYYY-MM-DD") } : {}), + ...(end ? { end: dayjs(end).endOf("day").format("YYYY-MM-DD") } : {}), + ...(start ? { starttz: dayjs(start).startOf("day") } : {}), + ...(end ? { endtz: dayjs(end).endOf("day") } : {}), - id: technician.id, - }, - }, - { - to: technician.email, - subject: - attendacePrint === true - ? Templates.attendance_employee.subject - : Templates.timetickets_employee.subject, - }, - values.sendby // === "email" ? "e" : "p" - ); - } catch (error) { - console.log(error); - } finally { - setLoading(false); - setVisibility(false); - form.resetFields(); - } - }; + id: technician.id + } + }, + { + to: technician.email, + subject: + attendacePrint === true ? Templates.attendance_employee.subject : Templates.timetickets_employee.subject + }, + values.sendby // === "email" ? "e" : "p" + ); + } catch (error) { + console.log(error); + } finally { + setLoading(false); + setVisibility(false); + form.resetFields(); + } + }; - const overlay = ( - -
    -
    - - - - - {() => { - return ( - - - {t("general.labels.email")} - {t("general.labels.print")} - - - ); - }} - - - - - - -
    -
    - ); - - const handleClick = (e) => { - setVisibility(true); - }; - - return ( - - - - ); + + + + + + ); + + const handleClick = (e) => { + setVisibility(true); + }; + + return ( + + + + ); } diff --git a/client/src/components/tech-job-statistics/tech-job-statistics.component.jsx b/client/src/components/tech-job-statistics/tech-job-statistics.component.jsx index 273187b53..33f997e02 100644 --- a/client/src/components/tech-job-statistics/tech-job-statistics.component.jsx +++ b/client/src/components/tech-job-statistics/tech-job-statistics.component.jsx @@ -1,133 +1,112 @@ -import {useQuery} from "@apollo/client"; -import {Card, Col, Space, Statistic, Typography} from "antd"; +import { useQuery } from "@apollo/client"; +import { Card, Col, Space, Statistic, Typography } from "antd"; import dayjs from "../../utils/day"; -import {useMemo} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE} from "../../graphql/timetickets.queries"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE } from "../../graphql/timetickets.queries"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -const {Title} = Typography; +const { Title } = Typography; const mapStateToProps = createStructuredSelector({ - technician: selectTechnician, + technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({}); -const TechJobStatistics = ({technician}) => { - const {t} = useTranslation(); +const TechJobStatistics = ({ technician }) => { + const { t } = useTranslation(); - const startDate = dayjs().startOf("week"); - const endDate = dayjs().endOf("week"); + const startDate = dayjs().startOf("week"); + const endDate = dayjs().endOf("week"); - const {loading, error, data} = useQuery( - QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE, - { - variables: { - start: startDate.format("YYYY-MM-DD"), - end: endDate.format("YYYY-MM-DD"), - fixedStart: dayjs().startOf("month").format("YYYY-MM-DD"), - fixedEnd: dayjs().endOf("month").format("YYYY-MM-DD"), - employeeid: technician?.id, - }, - skip: !technician, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - } - ); + const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE, { + variables: { + start: startDate.format("YYYY-MM-DD"), + end: endDate.format("YYYY-MM-DD"), + fixedStart: dayjs().startOf("month").format("YYYY-MM-DD"), + fixedEnd: dayjs().endOf("month").format("YYYY-MM-DD"), + employeeid: technician?.id + }, + skip: !technician, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const totals = useMemo(() => { - if (data && data.timetickets && data.fixedperiod) { - const week = data.timetickets.reduce( - (acc, val) => { - acc.productivehrs = acc.productivehrs + val.productivehrs; - acc.actualhrs = acc.actualhrs + val.actualhrs; - return acc; - }, - {productivehrs: 0, actualhrs: 0} - ); + const totals = useMemo(() => { + if (data && data.timetickets && data.fixedperiod) { + const week = data.timetickets.reduce( + (acc, val) => { + acc.productivehrs = acc.productivehrs + val.productivehrs; + acc.actualhrs = acc.actualhrs + val.actualhrs; + return acc; + }, + { productivehrs: 0, actualhrs: 0 } + ); - const month = data.fixedperiod.reduce( - (acc, val) => { - acc.productivehrs = acc.productivehrs + val.productivehrs; - acc.actualhrs = acc.actualhrs + val.actualhrs; - return acc; - }, - {productivehrs: 0, actualhrs: 0} - ); + const month = data.fixedperiod.reduce( + (acc, val) => { + acc.productivehrs = acc.productivehrs + val.productivehrs; + acc.actualhrs = acc.actualhrs + val.actualhrs; + return acc; + }, + { productivehrs: 0, actualhrs: 0 } + ); - return { - week, - month, - }; - } + return { + week, + month + }; + } - return { - week: {productivehrs: 0, actualhrs: 0}, - month: {productivehrs: 0, actualhrs: 0}, - }; - }, [data]); + return { + week: { productivehrs: 0, actualhrs: 0 }, + month: { productivehrs: 0, actualhrs: 0 } + }; + }, [data]); - if (loading) return ; - if (error) return ; + if (loading) return ; + if (error) return ; - return ( - - -
    - {t("scoreboard.labels.thisweek")} - - - - - - - - {t("scoreboard.labels.thismonth")} - - - - - - - - - ); + return ( + + + + {t("scoreboard.labels.thisweek")} + + + + + + + + {t("scoreboard.labels.thismonth")} + + + + + + + + + ); }; export default connect(mapStateToProps, mapDispatchToProps)(TechJobStatistics); diff --git a/client/src/components/tech-login/tech-login.component.jsx b/client/src/components/tech-login/tech-login.component.jsx index 3f6286a0e..4ebcc34e4 100644 --- a/client/src/components/tech-login/tech-login.component.jsx +++ b/client/src/components/tech-login/tech-login.component.jsx @@ -1,86 +1,82 @@ -import {Button, Form, Input} from "antd"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {techLoginStart} from "../../redux/tech/tech.actions"; -import {selectLoginError, selectLoginLoading, selectTechnician,} from "../../redux/tech/tech.selectors"; +import { Button, Form, Input } from "antd"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { techLoginStart } from "../../redux/tech/tech.actions"; +import { selectLoginError, selectLoginLoading, selectTechnician } from "../../redux/tech/tech.selectors"; import AlertComponent from "../alert/alert.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; import "./tech-login.styles.scss"; const mapStateToProps = createStructuredSelector({ - technician: selectTechnician, - loginError: selectLoginError, - loginLoading: selectLoginLoading, + technician: selectTechnician, + loginError: selectLoginError, + loginLoading: selectLoginLoading }); const mapDispatchToProps = (dispatch) => ({ - techLoginStart: (user) => dispatch(techLoginStart(user)), + techLoginStart: (user) => dispatch(techLoginStart(user)) }); -export function TechLogin({ - technician, - loginError, - loginLoading, - techLoginStart, - }) { - const {t} = useTranslation(); - const navigate = useNavigate(); +export function TechLogin({ technician, loginError, loginLoading, techLoginStart }) { + const { t } = useTranslation(); + const navigate = useNavigate(); - const handleFinish = (values) => { - // Remap these because EmployeeID form name has previously been used in the project - techLoginStart({pin: values.pin, employeeid: values.techEmployeeId}); - }; + const handleFinish = (values) => { + // Remap these because EmployeeID form name has previously been used in the project + techLoginStart({ pin: values.pin, employeeid: values.techEmployeeId }); + }; - useEffect(() => { - if (technician) return navigate("/tech/joblookup"); - }, [technician, navigate]); + useEffect(() => { + if (technician) return navigate("/tech/joblookup"); + }, [technician, navigate]); - useEffect(() => { - document.title = t("titles.techconsole",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - }, [t]); + useEffect(() => { + document.title = t("titles.techconsole", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + }, [t]); - return ( -
    -
    - - - - - - - - - {loginError ? : null} -
    - ); + return ( +
    +
    + + + + + + + + + {loginError ? : null} +
    + ); } export default connect(mapStateToProps, mapDispatchToProps)(TechLogin); diff --git a/client/src/components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component.jsx b/client/src/components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component.jsx index a54453284..2383fdb69 100644 --- a/client/src/components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component.jsx +++ b/client/src/components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component.jsx @@ -1,16 +1,16 @@ -import {PrinterFilled} from "@ant-design/icons"; -import {useQuery} from "@apollo/client"; -import {Button, Divider, Drawer, Grid, Tabs} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import { PrinterFilled } from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import { Button, Divider, Drawer, Grid, Tabs } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; import queryString from "query-string"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {GET_JOB_BY_PK} from "../../graphql/jobs.queries"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { GET_JOB_BY_PK } from "../../graphql/jobs.queries"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import JobLinesContainer from "../job-detail-lines/job-lines.container"; import JobsDetailHeader from "../jobs-detail-header/jobs-detail-header.component"; @@ -19,11 +19,10 @@ import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-docu import JobNotesContainer from "../jobs-notes/jobs-notes.container"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -const mapStateToProps = createStructuredSelector({bodyshop: selectBodyshop}); +const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setPrintCenterContext: (context) => - dispatch(setModalContext({context: context, modal: "printCenter"})), + setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" })) }); // const colBreakPoints = { @@ -35,118 +34,105 @@ const mapDispatchToProps = (dispatch) => ({ // }, // }; -export function TechLookupJobsDrawer({bodyshop, setPrintCenterContext}) { - const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) - .filter((screen) => !!screen[1]) - .slice(-1)[0]; +export function TechLookupJobsDrawer({ bodyshop, setPrintCenterContext }) { + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) + .filter((screen) => !!screen[1]) + .slice(-1)[0]; - const bpoints = { - xs: "100%", - sm: "100%", - md: "100%", - lg: "100%", - xl: "90%", - xxl: "85%", - }; - const drawerPercentage = selectedBreakpoint - ? bpoints[selectedBreakpoint[0]] - : "100%"; + const bpoints = { + xs: "100%", + sm: "100%", + md: "100%", + lg: "100%", + xl: "90%", + xxl: "85%" + }; + const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%"; - const searchParams = queryString.parse(useLocation().search); - const {selected} = searchParams; - const history = useNavigate(); - const {loading, error, data, refetch} = useQuery(GET_JOB_BY_PK, { - variables: {id: selected}, - skip: !selected, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", + const searchParams = queryString.parse(useLocation().search); + const { selected } = searchParams; + const history = useNavigate(); + const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, { + variables: { id: selected }, + skip: !selected, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + + const { t } = useTranslation(); + const handleDrawerClose = () => { + delete searchParams.selected; + history({ + search: queryString.stringify({ + ...searchParams + }) }); + }; - const {t} = useTranslation(); - const handleDrawerClose = () => { - delete searchParams.selected; - history({ - search: queryString.stringify({ - ...searchParams, - }), - }); - }; - - return ( - + {loading ? : null} + {error ? : null} + {data ? ( + window.history.back()} + title={data.jobs_by_pk.ro_number || t("general.labels.na")} + extra={ + + } > - {loading ? : null} - {error ? : null} - {data ? ( - window.history.back()} - title={data.jobs_by_pk.ro_number || t("general.labels.na")} - extra={ - - } - > - - - - ), - }, - { - key: "documents", - label: t("jobs.labels.documents"), - children: bodyshop.uselocalmediaserver ? ( - - ) : ( - - ), - }, - { - key: "notes", - label: t("jobs.labels.notes"), - children: , - }, - ]} - /> - - ) : null} - - ); + + + + ) + }, + { + key: "documents", + label: t("jobs.labels.documents"), + children: bodyshop.uselocalmediaserver ? ( + + ) : ( + + ) + }, + { + key: "notes", + label: t("jobs.labels.notes"), + children: + } + ]} + /> + + ) : null} + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(TechLookupJobsDrawer); +export default connect(mapStateToProps, mapDispatchToProps)(TechLookupJobsDrawer); diff --git a/client/src/components/tech-lookup-jobs-list/tech-lookup-jobs-list.component.jsx b/client/src/components/tech-lookup-jobs-list/tech-lookup-jobs-list.component.jsx index 46a3708e0..0a8ef4acf 100644 --- a/client/src/components/tech-lookup-jobs-list/tech-lookup-jobs-list.component.jsx +++ b/client/src/components/tech-lookup-jobs-list/tech-lookup-jobs-list.component.jsx @@ -1,228 +1,202 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {useQuery} from "@apollo/client"; -import {Button, Card, Input, Space, Table} from "antd"; +import { SyncOutlined } from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import { Button, Card, Input, Space, Table } from "antd"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {QUERY_ALL_ACTIVE_JOBS} from "../../graphql/jobs.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {onlyUnique} from "../../utils/arrayHelper"; -import {alphaSort} from "../../utils/sorters"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { onlyUnique } from "../../utils/arrayHelper"; +import { alphaSort } from "../../utils/sorters"; import AlertComponent from "../alert/alert.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -export function TechLookupJobsList({bodyshop}) { - const searchParams = queryString.parse(useLocation().search); - const {selected} = searchParams; +export function TechLookupJobsList({ bodyshop }) { + const searchParams = queryString.parse(useLocation().search); + const { selected } = searchParams; - const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS, { - variables: { - statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], - isConverted: true, - }, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { + variables: { + statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], + isConverted: true + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); - const {t} = useTranslation(); - const history = useNavigate(); - const [searchText, setSearchText] = useState(""); + const { t } = useTranslation(); + const history = useNavigate(); + const [searchText, setSearchText] = useState(""); - if (error) return ; + if (error) return ; - const jobs = data - ? searchText === "" - ? data.jobs - : data.jobs.filter( - (j) => - (j.ro_number || "") - .toString() - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_fn || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_ln || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_co_nm || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.plate_no || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_model_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_make_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) - ) - : []; + const jobs = data + ? searchText === "" + ? data.jobs + : data.jobs.filter( + (j) => + (j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.plate_no || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase()) + ) + : []; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - const handleOnRowClick = (record) => { - if (record) { - if (record.id) { - history({ - search: queryString.stringify({ - ...searchParams, - selected: record.id, - }), - }); - } - } - }; + const handleOnRowClick = (record) => { + if (record) { + if (record.id) { + history({ + search: queryString.stringify({ + ...searchParams, + selected: record.id + }) + }); + } + } + }; - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - render: (text, record) => record.ro_number || t("general.labels.na"), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, - ellipsis: true, - render: (text, record) => ( - - + render: (text, record) => record.ro_number || t("general.labels.na") + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + ellipsis: true, + render: (text, record) => ( + + - ), - }, - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", - sorter: (a, b) => alphaSort(a.status, b.status), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - filters: - (jobs && - jobs - .map((j) => j.status) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Status*", - value: [s], - }; - })) || - [], - onFilter: (value, record) => value.includes(record.status), - render: (text, record) => { - return record.status || t("general.labels.na"); - }, - }, + ) + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => alphaSort(a.status, b.status), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + filters: + (jobs && + jobs + .map((j) => j.status) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Status*", + value: [s] + }; + })) || + [], + onFilter: (value, record) => value.includes(record.status), + render: (text, record) => { + return record.status || t("general.labels.na"); + } + }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - ellipsis: true, - render: (text, record) => ( - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - ), - }, - { - title: t("vehicles.fields.plate_no"), - dataIndex: "plate_no", - key: "plate_no", - sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), - sortOrder: - state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, - render: (text, record) => { - return record.plate_no ? record.plate_no : ""; - }, - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - ellipsis: true, - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - render: (text, record) => { - return record.clm_no ? ( - {record.clm_no} - ) : ( - t("general.labels.unknown") - ); - }, - }, - ]; + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + render: (text, record) => ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + ) + }, + { + title: t("vehicles.fields.plate_no"), + dataIndex: "plate_no", + key: "plate_no", + sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), + sortOrder: state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, + render: (text, record) => { + return record.plate_no ? record.plate_no : ""; + } + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + ellipsis: true, + sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), + sortOrder: state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, + render: (text, record) => { + return record.clm_no ? {record.clm_no} : t("general.labels.unknown"); + } + } + ]; - return ( - - - { - setSearchText(e.target.value); - }} - value={searchText} - enterButton - /> - + return ( + + + { + setSearchText(e.target.value); + }} + value={searchText} + enterButton + /> + + } + > +
    { + handleOnRowClick(record); + }, + selectedRowKeys: [selected], + type: "radio" + }} + onChange={handleTableChange} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + handleOnRowClick(record); } - > -
    { - handleOnRowClick(record); - }, - selectedRowKeys: [selected], - type: "radio", - }} - onChange={handleTableChange} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - handleOnRowClick(record); - }, - }; - }} - /> - - ); + }; + }} + /> + + ); } export default connect(mapStateToProps, null)(TechLookupJobsList); diff --git a/client/src/components/tech-sider/tech-sider.component.jsx b/client/src/components/tech-sider/tech-sider.component.jsx index c3932e96c..3bd8d154c 100644 --- a/client/src/components/tech-sider/tech-sider.component.jsx +++ b/client/src/components/tech-sider/tech-sider.component.jsx @@ -1,152 +1,149 @@ -import Icon, {CarOutlined, ScheduleOutlined, SearchOutlined, UserAddOutlined,} from "@ant-design/icons"; -import {Layout, Menu} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {FaBusinessTime} from "react-icons/fa"; -import {FiLogIn, FiLogOut} from "react-icons/fi"; -import {MdTimer} from "react-icons/md"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {techLogout} from "../../redux/tech/tech.actions"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {BsKanban} from "react-icons/bs"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {setModalContext} from "../../redux/modals/modals.actions"; +import Icon, { CarOutlined, ScheduleOutlined, SearchOutlined, UserAddOutlined } from "@ant-design/icons"; +import { Layout, Menu } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { FaBusinessTime } from "react-icons/fa"; +import { FiLogIn, FiLogOut } from "react-icons/fi"; +import { MdTimer } from "react-icons/md"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { techLogout } from "../../redux/tech/tech.actions"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { BsKanban } from "react-icons/bs"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { setModalContext } from "../../redux/modals/modals.actions"; -const {Sider} = Layout; +const { Sider } = Layout; const mapStateToProps = createStructuredSelector({ - technician: selectTechnician, - bodyshop: selectBodyshop, + technician: selectTechnician, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - techLogout: () => dispatch(techLogout()), - setTimeTicketTaskContext: (context) => - dispatch(setModalContext({context: context, modal: "timeTicketTask"})), + techLogout: () => dispatch(techLogout()), + setTimeTicketTaskContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicketTask" })) }); -export function TechSider({ - technician, - techLogout, - bodyshop, - setTimeTicketTaskContext, - }) { - const [collapsed, setCollapsed] = useState(true); - const {t} = useTranslation(); - const onCollapse = (collapsed) => { - setCollapsed(collapsed); - }; +export function TechSider({ technician, techLogout, bodyshop, setTimeTicketTaskContext }) { + const [collapsed, setCollapsed] = useState(true); + const { t } = useTranslation(); + const onCollapse = (collapsed) => { + setCollapsed(collapsed); + }; - const {treatments: {Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, - }); + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); - const items = [ - { - key: "1", - icon: , - disabled: !!technician, - label: {t("menus.tech.login")}, - }, - { - key: "2", - icon: , - disabled: !!!technician, - label: {t("menus.tech.joblookup")}, - }]; - - if (Enhanced_Payroll.treatment === 'on') { - items.push({ - key: 'TechAssignedProdJobs', - disabled: !!!technician, - icon: , - label: {t("menus.tech.assignedjobs")} - }, - { - key: '3', - disabled: !!!technician, - icon: , - onClick: () => { - setTimeTicketTaskContext({ - actions: {}, - context: {jobid: null}, - }); - }, - label: t("menus.tech.claimtask") - } - ) - } else { - items.push({ - key: '3', - disabled: !!!technician, - icon: , - label: {t("menus.tech.jobclockin")} - }) + const items = [ + { + key: "1", + icon: , + disabled: !!technician, + label: {t("menus.tech.login")} + }, + { + key: "2", + icon: , + disabled: !!!technician, + label: {t("menus.tech.joblookup")} } + ]; + if (Enhanced_Payroll.treatment === "on") { items.push( - { - key: "4", - icon: , - disabled: !!!technician, - label: {t("menus.tech.shiftclockin")}, + { + key: "TechAssignedProdJobs", + disabled: !!!technician, + icon: , + label: {t("menus.tech.assignedjobs")} + }, + { + key: "3", + disabled: !!!technician, + icon: , + onClick: () => { + setTimeTicketTaskContext({ + actions: {}, + context: { jobid: null } + }); }, - { - key: "5", - icon: , - disabled: !!!technician, - label: {t("menus.tech.productionlist")}, - }, - { - key: 'dispatchedparts', - disabled: !!!technician, - icon: , - label: - {t("menus.tech.dispatchedparts")} - - }, - { - key: "6", - icon: , - disabled: !!!technician, - label: {t("menus.tech.productionboard")}, - }, - { - key: "7", - icon: , - disabled: !!!technician, - label: t("menus.tech.logout"), - }); - - return ( - - { - if (e.key === "7") { - techLogout(); - } - }} - items={items} - /> - + label: t("menus.tech.claimtask") + } ); + } else { + items.push({ + key: "3", + disabled: !!!technician, + icon: , + label: {t("menus.tech.jobclockin")} + }); + } + + items.push( + { + key: "4", + icon: , + disabled: !!!technician, + label: {t("menus.tech.shiftclockin")} + }, + { + key: "5", + icon: , + disabled: !!!technician, + label: {t("menus.tech.productionlist")} + }, + { + key: "dispatchedparts", + disabled: !!!technician, + icon: , + label: {t("menus.tech.dispatchedparts")} + }, + { + key: "6", + icon: , + disabled: !!!technician, + label: {t("menus.tech.productionboard")} + }, + { + key: "7", + icon: , + disabled: !!!technician, + label: t("menus.tech.logout") + } + ); + + return ( + + { + if (e.key === "7") { + techLogout(); + } + }} + items={items} + /> + + ); } export default connect(mapStateToProps, mapDispatchToProps)(TechSider); diff --git a/client/src/components/ticket-tickets-dates-selector/time-tickets-dates-selector.component.jsx b/client/src/components/ticket-tickets-dates-selector/time-tickets-dates-selector.component.jsx index 10ecf17ce..0c1bcec16 100644 --- a/client/src/components/ticket-tickets-dates-selector/time-tickets-dates-selector.component.jsx +++ b/client/src/components/ticket-tickets-dates-selector/time-tickets-dates-selector.component.jsx @@ -1,48 +1,48 @@ import DatePickerRanges from "../../utils/DatePickerRanges"; -import {DatePicker} from "antd"; +import { DatePicker } from "antd"; import dayjs from "../../utils/day"; import queryString from "query-string"; import React from "react"; -import {useLocation, useNavigate} from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; export default function TimeTicketsDatesSelector() { - const searchParams = queryString.parse(useLocation().search); - const {start, end} = searchParams; - const history = useNavigate(); + const searchParams = queryString.parse(useLocation().search); + const { start, end } = searchParams; + const history = useNavigate(); - const handleChange = (dates) => { - if (dates) { - const [start, end] = dates; + const handleChange = (dates) => { + if (dates) { + const [start, end] = dates; - if (!!start && !!end) { - history({ - search: queryString.stringify({ - ...searchParams, - start: start.format("YYYY-MM-DD"), - end: end.format("YYYY-MM-DD"), - }), - }); - } - } else { - history({ - search: queryString.stringify({ - ...searchParams, - start: null, - end: null, - }), - }); - } - }; + if (!!start && !!end) { + history({ + search: queryString.stringify({ + ...searchParams, + start: start.format("YYYY-MM-DD"), + end: end.format("YYYY-MM-DD") + }) + }); + } + } else { + history({ + search: queryString.stringify({ + ...searchParams, + start: null, + end: null + }) + }); + } + }; - return ( - - ); + return ( + + ); } diff --git a/client/src/components/time-ticket-calculator/time-ticket-calculator.component.jsx b/client/src/components/time-ticket-calculator/time-ticket-calculator.component.jsx index 264b6613c..c5b174f26 100644 --- a/client/src/components/time-ticket-calculator/time-ticket-calculator.component.jsx +++ b/client/src/components/time-ticket-calculator/time-ticket-calculator.component.jsx @@ -1,131 +1,120 @@ -import {DownOutlined} from "@ant-design/icons"; -import {Button, Checkbox, Col, Form, InputNumber, Popover, Radio, Row, Space, Spin,} from "antd"; -import React, {useState} from "react"; -import {GET_JOB_INFO_DRAW_CALCULATIONS} from "../../graphql/jobs-lines.queries"; -import {useQuery} from "@apollo/client"; +import { DownOutlined } from "@ant-design/icons"; +import { Button, Checkbox, Col, Form, InputNumber, Popover, Radio, Row, Space, Spin } from "antd"; +import React, { useState } from "react"; +import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries"; +import { useQuery } from "@apollo/client"; export default function TimeTicketCalculatorComponent({ - setProductiveHours, + setProductiveHours, - jobid, - }) { - const {loading, data: lineTicketData} = useQuery(GET_JOB_INFO_DRAW_CALCULATIONS, { - variables: {id: jobid}, - skip: !jobid, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + jobid +}) { + const { loading, data: lineTicketData } = useQuery(GET_JOB_INFO_DRAW_CALCULATIONS, { + variables: { id: jobid }, + skip: !jobid, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const [visible, setVisible] = useState(false); - const handleOpenChange = (flag) => setVisible(flag); - const handleFinish = ({type, hourstype, percent}) => { - //setProductiveHours(values); - //setVisible(false); - const eligibleHours = Array.isArray(hourstype) - ? lineTicketData.joblines.reduce( - (acc, val) => - acc + (hourstype.includes(val.mod_lbr_ty) ? val.mod_lb_hrs : 0), - 0 - ) - : lineTicketData.joblines.reduce( - (acc, val) => - acc + (hourstype === val.mod_lbr_ty ? val.mod_lb_hrs : 0), - 0 - ); - if (type === "draw") { - setProductiveHours(eligibleHours * (percent / 100)); - } else if (type === "cut") { - setProductiveHours(eligibleHours * (percent / 100)); - console.log( - "Cut selected, rate set to: ", - lineTicketData.jobs_by_pk[`rate_${hourstype.toLowerCase()}`] - ); - } - }; + const [visible, setVisible] = useState(false); + const handleOpenChange = (flag) => setVisible(flag); + const handleFinish = ({ type, hourstype, percent }) => { + //setProductiveHours(values); + //setVisible(false); + const eligibleHours = Array.isArray(hourstype) + ? lineTicketData.joblines.reduce((acc, val) => acc + (hourstype.includes(val.mod_lbr_ty) ? val.mod_lb_hrs : 0), 0) + : lineTicketData.joblines.reduce((acc, val) => acc + (hourstype === val.mod_lbr_ty ? val.mod_lb_hrs : 0), 0); + if (type === "draw") { + setProductiveHours(eligibleHours * (percent / 100)); + } else if (type === "cut") { + setProductiveHours(eligibleHours * (percent / 100)); + console.log("Cut selected, rate set to: ", lineTicketData.jobs_by_pk[`rate_${hourstype.toLowerCase()}`]); + } + }; - const popContent = ( - -
    - - - Draw - Cut of Sale - - + const popContent = ( + + + + + Draw + Cut of Sale + + - - {({getFieldValue}) => ( - - {getFieldValue("type") === "draw" ? ( - - -
    - - Body - - - - - Refinish - - - - - Mechanical - - - - - Frame - - - - - Glass - - - - - ) : ( - - Body + + {({ getFieldValue }) => ( + + {getFieldValue("type") === "draw" ? ( + + + + + Body + + + + + Refinish + + + + + Mechanical + + + + + Frame + + + + + Glass + + + + + ) : ( + + Body - Refinish + Refinish - Mechanical + Mechanical - Frame + Frame - Glass - - )} - - )} - + Glass + + )} + + )} + - - - - - - - ); + + + + + + + ); - return ( - - - - ); + return ( + + + + ); } diff --git a/client/src/components/time-ticket-enter-button/time-ticket-enter-button.component.jsx b/client/src/components/time-ticket-enter-button/time-ticket-enter-button.component.jsx index e2e0c4903..96f64d99b 100644 --- a/client/src/components/time-ticket-enter-button/time-ticket-enter-button.component.jsx +++ b/client/src/components/time-ticket-enter-button/time-ticket-enter-button.component.jsx @@ -1,32 +1,26 @@ -import {Button} from "antd"; +import { Button } from "antd"; import React from "react"; -import {connect} from "react-redux"; -import {setModalContext} from "../../redux/modals/modals.actions"; +import { connect } from "react-redux"; +import { setModalContext } from "../../redux/modals/modals.actions"; const mapDispatchToProps = (dispatch) => ({ - setTimeTicketContext: (context) => - dispatch(setModalContext({context: context, modal: "timeTicket"})), + setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })) }); -export function TimeTicketEnterButton({ - actions, - context, - setTimeTicketContext, - disabled, - children, - }) { - return ( - - ); +export function TimeTicketEnterButton({ actions, context, setTimeTicketContext, disabled, children }) { + return ( + + ); } export default connect(null, mapDispatchToProps)(TimeTicketEnterButton); diff --git a/client/src/components/time-ticket-list/time-ticket-list-team-pay.component.jsx b/client/src/components/time-ticket-list/time-ticket-list-team-pay.component.jsx index 41a6732f2..b703d09ba 100644 --- a/client/src/components/time-ticket-list/time-ticket-list-team-pay.component.jsx +++ b/client/src/components/time-ticket-list/time-ticket-list-team-pay.component.jsx @@ -1,268 +1,236 @@ -import {useQuery} from "@apollo/client"; -import {Button, Form, InputNumber, Modal, Radio, Select, Space, Table, Typography,} from "antd"; +import { useQuery } from "@apollo/client"; +import { Button, Form, InputNumber, Modal, Radio, Select, Space, Table, Typography } from "antd"; import Dinero from "dinero.js"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {GET_JOB_INFO_DRAW_CALCULATIONS} from "../../graphql/jobs-lines.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; import JobSearchSelectComponent from "../job-search-select/job-search-select.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({}); -export default connect( - mapStateToProps, - mapDispatchToProps -)(TimeTicketListTeamPay); +export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketListTeamPay); -export function TimeTicketListTeamPay({bodyshop, context, actions}) { - //const { refetch } = actions; - const {jobId} = context; - const [visible, setVisible] = useState(false); - const [form] = Form.useForm(); - const {t} = useTranslation(); - const { - //loading, - data: lineTicketData, - } = useQuery(GET_JOB_INFO_DRAW_CALCULATIONS, { - variables: {id: jobId}, - skip: !jobId, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); +export function TimeTicketListTeamPay({ bodyshop, context, actions }) { + //const { refetch } = actions; + const { jobId } = context; + const [visible, setVisible] = useState(false); + const [form] = Form.useForm(); + const { t } = useTranslation(); + const { + //loading, + data: lineTicketData + } = useQuery(GET_JOB_INFO_DRAW_CALCULATIONS, { + variables: { id: jobId }, + skip: !jobId, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const handleOk = () => { - setVisible(false); - }; + const handleOk = () => { + setVisible(false); + }; - return ( - <> - setVisible(false)} + return ( + <> + setVisible(false)}> +
    + + + {() => ( + + + + )} + + - - - - {() => ( - - - - )} - - - - - + + + - - - ({ + value: team.name, + label: team.name + }))} + /> + - - - Body - Refinish - Mechanical - Frame - Glass - - + + + Body + Refinish + Mechanical + Frame + Glass + + - - - - + + + + - - {({getFieldsValue}) => { - const formData = getFieldsValue(); + + {({ getFieldsValue }) => { + const formData = getFieldsValue(); - let data = []; - let eligibleHours = 0; - const theTeam = Teams.find((team) => team.name === formData.team); - if (theTeam) { - eligibleHours = - lineTicketData.joblines.reduce( - (acc, val) => - acc + - (formData.hourstype === val.mod_lbr_ty - ? val.mod_lb_hrs - : 0), - 0 - ) * (formData.percent / 100 || 0); + let data = []; + let eligibleHours = 0; + const theTeam = Teams.find((team) => team.name === formData.team); + if (theTeam) { + eligibleHours = + lineTicketData.joblines.reduce( + (acc, val) => acc + (formData.hourstype === val.mod_lbr_ty ? val.mod_lb_hrs : 0), + 0 + ) * (formData.percent / 100 || 0); - data = theTeam.employees.map((e) => { - return { - employeeid: e.employeeid, - percentage: e.percentage, - rate: e.rates[formData.hourstype], - cost_center: - bodyshop.md_responsibility_centers.defaults.costs[ - formData.hourstype - ], - productivehrs: - Math.round(eligibleHours * 100 * (e.percentage / 100)) / - 100, - pay: Dinero({ - amount: Math.round( - (e.rates[formData.hourstype] || 0) * 100 - ), - }) - .multiply( - Math.round(eligibleHours * 100 * (e.percentage / 100)) / - 100 - ) - .toFormat("$0.00"), - }; - }); - } + data = theTeam.employees.map((e) => { + return { + employeeid: e.employeeid, + percentage: e.percentage, + rate: e.rates[formData.hourstype], + cost_center: bodyshop.md_responsibility_centers.defaults.costs[formData.hourstype], + productivehrs: Math.round(eligibleHours * 100 * (e.percentage / 100)) / 100, + pay: Dinero({ + amount: Math.round((e.rates[formData.hourstype] || 0) * 100) + }) + .multiply(Math.round(eligibleHours * 100 * (e.percentage / 100)) / 100) + .toFormat("$0.00") + }; + }); + } - return ( -
    ( - - - Tickets to be Created - - {`(${eligibleHours} hours to split)`} - - )} - columns={[ - { - title: t("timetickets.fields.employee"), - dataIndex: "employee", - key: "employee", - render: (text, record) => { - const emp = bodyshop.employees.find( - (e) => e.id === record.employeeid - ); - return `${emp?.first_name} ${emp?.last_name}`; - }, - }, - { - title: t("timetickets.fields.cost_center"), - dataIndex: "cost_center", - key: "cost_center", + return ( +
    ( + + Tickets to be Created + {`(${eligibleHours} hours to split)`} + + )} + columns={[ + { + title: t("timetickets.fields.employee"), + dataIndex: "employee", + key: "employee", + render: (text, record) => { + const emp = bodyshop.employees.find((e) => e.id === record.employeeid); + return `${emp?.first_name} ${emp?.last_name}`; + } + }, + { + title: t("timetickets.fields.cost_center"), + dataIndex: "cost_center", + key: "cost_center", - render: (text, record) => - record.cost_center === "timetickets.labels.shift" - ? t(record.cost_center) - : record.cost_center, - }, - { - title: t("timetickets.fields.productivehrs"), - dataIndex: "productivehrs", - key: "productivehrs", - }, - { - title: "Percentage", - dataIndex: "percentage", - key: "percentage", - }, - { - title: "Rate", - dataIndex: "rate", - key: "rate", - }, - { - title: "Pay", - dataIndex: "pay", - key: "pay", - }, - ]} - /> - ); - }} - - - - - - ); + render: (text, record) => + record.cost_center === "timetickets.labels.shift" ? t(record.cost_center) : record.cost_center + }, + { + title: t("timetickets.fields.productivehrs"), + dataIndex: "productivehrs", + key: "productivehrs" + }, + { + title: "Percentage", + dataIndex: "percentage", + key: "percentage" + }, + { + title: "Rate", + dataIndex: "rate", + key: "rate" + }, + { + title: "Pay", + dataIndex: "pay", + key: "pay" + } + ]} + /> + ); + }} + + + + + + ); } const Teams = [ - { - name: "Team A", - employees: [ - { - employeeid: "9f1bdc23-8dc2-4b6a-8ca1-5f83a6fdcc22", - percentage: 50, - rates: { - LAB: 10, - LAR: 15, - }, - }, - { - employeeid: "201db66c-96c7-41ec-bed4-76842ba93087", - percentage: 50, - rates: { - LAB: 20, - LAR: 25, - }, - }, - ], - }, - { - name: "Team B", - employees: [ - { - employeeid: "9f1bdc23-8dc2-4b6a-8ca1-5f83a6fdcc22", - percentage: 75, - rates: { - LAB: 100, - LAR: 150, - }, - }, - { - employeeid: "201db66c-96c7-41ec-bed4-76842ba93087", - percentage: 25, - rates: { - LAB: 200, - LAR: 250, - }, - }, - ], - }, + { + name: "Team A", + employees: [ + { + employeeid: "9f1bdc23-8dc2-4b6a-8ca1-5f83a6fdcc22", + percentage: 50, + rates: { + LAB: 10, + LAR: 15 + } + }, + { + employeeid: "201db66c-96c7-41ec-bed4-76842ba93087", + percentage: 50, + rates: { + LAB: 20, + LAR: 25 + } + } + ] + }, + { + name: "Team B", + employees: [ + { + employeeid: "9f1bdc23-8dc2-4b6a-8ca1-5f83a6fdcc22", + percentage: 75, + rates: { + LAB: 100, + LAR: 150 + } + }, + { + employeeid: "201db66c-96c7-41ec-bed4-76842ba93087", + percentage: 25, + rates: { + LAB: 200, + LAR: 250 + } + } + ] + } ]; diff --git a/client/src/components/time-ticket-list/time-ticket-list.component.jsx b/client/src/components/time-ticket-list/time-ticket-list.component.jsx index 05c39e0d1..63d4dd2b4 100644 --- a/client/src/components/time-ticket-list/time-ticket-list.component.jsx +++ b/client/src/components/time-ticket-list/time-ticket-list.component.jsx @@ -1,401 +1,364 @@ -import {EditFilled, SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Checkbox, Space, Table} from "antd"; +import { EditFilled, SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Checkbox, Space, Table } from "antd"; import dayjs from "../../utils/day"; -import React, {useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectAuthLevel, selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; -import {DateFormatter, DateTimeFormatter} from "../../utils/DateFormatter"; -import {onlyUnique} from "../../utils/arrayHelper"; -import {alphaSort, dateSort} from "../../utils/sorters"; -import RbacWrapper, {HasRbacAccess,} from "../rbac-wrapper/rbac-wrapper.component"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter"; +import { onlyUnique } from "../../utils/arrayHelper"; +import { alphaSort, dateSort } from "../../utils/sorters"; +import RbacWrapper, { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component"; import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - authLevel: selectAuthLevel, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + authLevel: selectAuthLevel, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - setTimeTicketTaskContext: (context) => - dispatch(setModalContext({context: context, modal: "timeTicketTask"})), + setTimeTicketTaskContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicketTask" })) }); export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketList); export function TimeTicketList({ - bodyshop, - setTimeTicketTaskContext, - authLevel, - currentUser, - disabled, - loading, - timetickets, - refetch, - techConsole, - jobId, - extra, - }) { - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); + bodyshop, + setTimeTicketTaskContext, + authLevel, + currentUser, + disabled, + loading, + timetickets, + refetch, + techConsole, + jobId, + extra +}) { + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); - const {t} = useTranslation(); - const {treatments: {Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, - }); + const { t } = useTranslation(); + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); - const totals = useMemo(() => { - if (timetickets) - return timetickets.reduce( - (acc, val) => { - acc.productivehrs = acc.productivehrs + val.productivehrs; - acc.actualhrs = acc.actualhrs + val.actualhrs; - return acc; - }, - {productivehrs: 0, actualhrs: 0} - ); - return {productivehrs: 0, actualhrs: 0}; - }, [timetickets]); + const totals = useMemo(() => { + if (timetickets) + return timetickets.reduce( + (acc, val) => { + acc.productivehrs = acc.productivehrs + val.productivehrs; + acc.actualhrs = acc.actualhrs + val.actualhrs; + return acc; + }, + { productivehrs: 0, actualhrs: 0 } + ); + return { productivehrs: 0, actualhrs: 0 }; + }, [timetickets]); - const columns = [ - ...(Enhanced_Payroll.treatment === "on" - ? [{ - title: t("timetickets.fields.committed"), - dataIndex: "committed_at", - key: "committed_at", - render: (text, record) => ( - - ), - },] + const columns = [ + ...(Enhanced_Payroll.treatment === "on" + ? [ + { + title: t("timetickets.fields.committed"), + dataIndex: "committed_at", + key: "committed_at", + render: (text, record) => + } + ] + : []), + + { + title: t("timetickets.fields.date"), + dataIndex: "date", + key: "date", + sorter: (a, b) => dateSort(a.date, b.date), + sortOrder: state.sortedInfo.columnKey === "date" && state.sortedInfo.order, + render: (text, record) => {record.date} + }, + { + title: t("timetickets.fields.employee"), + dataIndex: "employee", + key: "employee", + sorter: (a, b) => alphaSort(a.employee.last_name, b.employee.last_name), + sortOrder: state.sortedInfo.columnKey === "employee" && state.sortedInfo.order, + render: (text, record) => `${record.employee.first_name} ${record.employee.last_name}`, + filters: + timetickets + .map((l) => l.employeeid) + .filter(onlyUnique) + .map((s) => { + return { + text: (() => { + const emp = bodyshop.employees.find((e) => e.id === s); + + return `${emp?.first_name} ${emp?.last_name}`; + })(), // + value: [s] + }; + }) || [], + onFilter: (value, record) => value.includes(record.employeeid) + }, + { + title: t("timetickets.fields.cost_center"), + dataIndex: "cost_center", + key: "cost_center", + sorter: (a, b) => alphaSort(a.cost_center, b.cost_center), + render: (text, record) => + record.cost_center === "timetickets.labels.shift" ? t(record.cost_center) : record.cost_center, + sortOrder: state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order, + filters: + timetickets + .map((l) => l.cost_center) + .filter(onlyUnique) + .map((s) => { + return { + text: s === "timetickets.labels.shift" ? t(s) : s, //|| "No Status*", + value: [s] + }; + }) || [], + onFilter: (value, record) => value.includes(record.cost_center) + }, + ...(jobId + ? [] : [ - - ]), - - { - title: t("timetickets.fields.date"), - dataIndex: "date", - key: "date", - sorter: (a, b) => dateSort(a.date, b.date), - sortOrder: - state.sortedInfo.columnKey === "date" && state.sortedInfo.order, - render: (text, record) => {record.date}, - }, - { - title: t("timetickets.fields.employee"), - dataIndex: "employee", - key: "employee", - sorter: (a, b) => alphaSort(a.employee.last_name, b.employee.last_name), - sortOrder: - state.sortedInfo.columnKey === "employee" && state.sortedInfo.order, + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.job && a.job.ro_number, b.job && b.job.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, render: (text, record) => - `${record.employee.first_name} ${record.employee.last_name}`, - filters: - timetickets - .map((l) => l.employeeid) - .filter(onlyUnique) - .map((s) => { - return { - text: (() => { - const emp = bodyshop.employees.find((e) => e.id === s); + record.job && {record.job.ro_number || "N/A"} + } + ]), + { + title: t("timetickets.fields.productivehrs"), + dataIndex: "productivehrs", + key: "productivehrs", + sorter: (a, b) => a.productivehrs - b.productivehrs, + sortOrder: state.sortedInfo.columnKey === "productivehrs" && state.sortedInfo.order + }, + ...(Enhanced_Payroll.treatment === "on" + ? [] + : [ + { + title: t("timetickets.fields.actualhrs"), + dataIndex: "actualhrs", + key: "actualhrs", + sorter: (a, b) => a.actualhrs - b.actualhrs, + sortOrder: state.sortedInfo.columnKey === "actualhrs" && state.sortedInfo.order + } + ]), + { + title: t("timetickets.fields.memo"), + dataIndex: "memo", + key: "memo", + sorter: (a, b) => alphaSort(a.memo, b.memo), + sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order, + render: (text, record) => (record.clockon || record.clockoff ? t(record.memo) : record.memo) + }, + ...(Enhanced_Payroll.treatment === "on" + ? [ + { + title: t("timetickets.fields.task_name"), + dataIndex: "task_name", + key: "task_name", + sorter: (a, b) => alphaSort(a.task_name, b.task_name), + sortOrder: state.sortedInfo.columnKey === "task_name" && state.sortedInfo.order + } + ] + : []), + ...(Enhanced_Payroll.treatment === "on" + ? [] + : [ + { + title: t("timetickets.fields.clockon"), + dataIndex: "clockon", + key: "clockon", - return `${emp?.first_name} ${emp?.last_name}`; - })(), // - value: [s], - }; - }) || [], - onFilter: (value, record) => value.includes(record.employeeid), - }, - { - title: t("timetickets.fields.cost_center"), - dataIndex: "cost_center", - key: "cost_center", - sorter: (a, b) => alphaSort(a.cost_center, b.cost_center), - render: (text, record) => - record.cost_center === "timetickets.labels.shift" - ? t(record.cost_center) - : record.cost_center, - sortOrder: - state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order, - filters: - timetickets - .map((l) => l.cost_center) - .filter(onlyUnique) - .map((s) => { - return { - text: s === "timetickets.labels.shift" ? t(s) : s, //|| "No Status*", - value: [s], - }; - }) || [], - onFilter: (value, record) => value.includes(record.cost_center), - }, - ...(jobId - ? [] - : [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => - alphaSort(a.job && a.job.ro_number, b.job && b.job.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && - state.sortedInfo.order, - render: (text, record) => - record.job && ( - - {record.job.ro_number || "N/A"} - - ), - }, - ]), - { - title: t("timetickets.fields.productivehrs"), - dataIndex: "productivehrs", - key: "productivehrs", - sorter: (a, b) => a.productivehrs - b.productivehrs, - sortOrder: state.sortedInfo.columnKey === "productivehrs" && state.sortedInfo.order, - }, - ...(Enhanced_Payroll.treatment === "on" - ? [] - : [ - { - title: t("timetickets.fields.actualhrs"), - dataIndex: "actualhrs", - key: "actualhrs", - sorter: (a, b) => a.actualhrs - b.actualhrs, - sortOrder: - state.sortedInfo.columnKey === "actualhrs" && - state.sortedInfo.order, - }, - ]), - { - title: t("timetickets.fields.memo"), - dataIndex: "memo", - key: "memo", - sorter: (a, b) => alphaSort(a.memo, b.memo), - sortOrder: - state.sortedInfo.columnKey === "memo" && state.sortedInfo.order, - render: (text, record) => - record.clockon || record.clockoff ? t(record.memo) : record.memo, - }, - ...(Enhanced_Payroll.treatment === "on" - ? [ - { - title: t("timetickets.fields.task_name"), - dataIndex: "task_name", - key: "task_name", - sorter: (a, b) => alphaSort(a.task_name, b.task_name), - sortOrder: - state.sortedInfo.columnKey === "task_name" && - state.sortedInfo.order, - }, - ] - : []), - ...(Enhanced_Payroll.treatment === "on" - ? [] - : [ - { - title: t("timetickets.fields.clockon"), - dataIndex: "clockon", - key: "clockon", + render: (text, record) => {record.clockon} + }, + { + title: t("timetickets.fields.clockoff"), + dataIndex: "clockoff", + key: "clockoff", - render: (text, record) => ( - {record.clockon} - ), - }, - { - title: t("timetickets.fields.clockoff"), - dataIndex: "clockoff", - key: "clockoff", - - render: (text, record) => ( - {record.clockoff} - ), - }, - { - title: t("timetickets.fields.clockhours"), - dataIndex: "clockhours", - key: "clockhours", - render: (text, record) => { - if (record.clockoff && record.clockon) - return ( -
    - {dayjs(record.clockoff) - .diff(dayjs(record.clockon), "hour", true) - .toFixed(2)} -
    - ); - else { - return null; - } - }, - }, - { - title: t("timetickets.fields.created_by"), - dataIndex: "created_by", - key: "created_by", - sorter: (a, b) => alphaSort(a.created_by, b.created_by), - sortOrder: - state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order, - render: (text, record) => record.created_by, - }, - // { - // title: "Pay", - // dataIndex: "pay", - // key: "pay", - // render: (text, record) => - // Dinero({ amount: Math.round(record.rate * 100) }) - // .multiply(record.flat_rate ? record.productivehrs : record.actualhrs) - // .toFormat("$0.00"), - // }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", - render: (text, record) => ( - - {techConsole && ( - - - - )} - {!techConsole && ( - { - return
    ; - }} - > - - - - - )} - - ), - }, - ])] - - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - - return ( - - {jobId && bodyshop.md_tasks_presets.enable_tasks && ( - - )} - {jobId && - (techConsole ? null : ( - - {t("timetickets.actions.enter")} - - ))} - {extra} - - + render: (text, record) => {record.clockoff} + }, + { + title: t("timetickets.fields.clockhours"), + dataIndex: "clockhours", + key: "clockhours", + render: (text, record) => { + if (record.clockoff && record.clockon) + return
    {dayjs(record.clockoff).diff(dayjs(record.clockon), "hour", true).toFixed(2)}
    ; + else { + return null; + } } - > -
    alphaSort(a.created_by, b.created_by), + sortOrder: state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order, + render: (text, record) => record.created_by + }, + // { + // title: "Pay", + // dataIndex: "pay", + // key: "pay", + // render: (text, record) => + // Dinero({ amount: Math.round(record.rate * 100) }) + // .multiply(record.flat_rate ? record.productivehrs : record.actualhrs) + // .toFormat("$0.00"), + // }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => ( + + {techConsole && ( + + + + )} + {!techConsole && ( + { + return
    ; + }} + > + + + + + )} + + ) + } + ]) + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + return ( + + {jobId && bodyshop.md_tasks_presets.enable_tasks && ( + + )} + {jobId && + (techConsole ? null : ( + { - if(Enhanced_Payroll.treatment === "on") return null; - if(Enhanced_Payroll.treatment === "off") - return ( - - - {t("general.labels.totals")} - - - - - - {totals.productivehrs.toFixed(1)} - - - {totals.actualhrs.toFixed(1)} - - - {totals.actualhrs === 0 || !totals.actualhrs - ? "∞" - : `${( - (totals.productivehrs / totals.actualhrs) * - 100 - ).toFixed(2)}% ${t("timetickets.labels.efficiency")}`} - - - - - - ); - }} - /> - - ); + disabled={disabled} + > + {t("timetickets.actions.enter")} + + ))} + {extra} + + + } + > +
    { + if (Enhanced_Payroll.treatment === "on") return null; + if (Enhanced_Payroll.treatment === "off") + return ( + + {t("general.labels.totals")} + + + + {totals.productivehrs.toFixed(1)} + {totals.actualhrs.toFixed(1)} + + {totals.actualhrs === 0 || !totals.actualhrs + ? "∞" + : `${((totals.productivehrs / totals.actualhrs) * 100).toFixed( + 2 + )}% ${t("timetickets.labels.efficiency")}`} + + + + + + ); + }} + /> + + ); } diff --git a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx index ecc19aa91..cc807e5dd 100644 --- a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx +++ b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx @@ -1,409 +1,347 @@ -import {useLazyQuery} from "@apollo/client"; -import {Form, Input, InputNumber, Select, Switch} from "antd"; +import { useLazyQuery } from "@apollo/client"; +import { Form, Input, InputNumber, Select, Switch } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {GET_LINE_TICKET_BY_PK} from "../../graphql/jobs-lines.queries"; -import {selectAuthLevel, selectBodyshop,} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries"; +import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors"; import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; import JobSearchSelect from "../job-search-select/job-search-select.component"; import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component"; -import {CalculateAllocationsTotals} from "../labor-allocations-table/labor-allocations-table.utility"; +import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; -import {HasRbacAccess} from "../rbac-wrapper/rbac-wrapper.component"; +import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component"; import TimeTicketList from "../time-ticket-list/time-ticket-list.component"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - authLevel: selectAuthLevel, + bodyshop: selectBodyshop, + authLevel: selectAuthLevel }); const mapDispatchToProps = (dispatch) => ({}); -export default connect( - mapStateToProps, - mapDispatchToProps -)(TimeTicketModalComponent); +export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketModalComponent); export function TimeTicketModalComponent({ - form, - bodyshop, - authLevel, - employeeAutoCompleteOptions, - isEdit, - disabled, - employeeSelectDisabled, - }) { - const {t} = useTranslation(); - const {treatments: {Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, - }); - - const [loadLineTicketData, {called, loading, data: lineTicketData}] = - useLazyQuery(GET_LINE_TICKET_BY_PK, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); - const CostCenterSelect = ({emps, value, ...props}) => { - return ( - - ); - }; - - const MemoInput = ({value, ...props}) => { - return ( - - ); - }; + form, + bodyshop, + authLevel, + employeeAutoCompleteOptions, + isEdit, + disabled, + employeeSelectDisabled +}) { + const { t } = useTranslation(); + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); + const [loadLineTicketData, { called, loading, data: lineTicketData }] = useLazyQuery(GET_LINE_TICKET_BY_PK, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const CostCenterSelect = ({ emps, value, ...props }) => { return ( -
    - - - {() => ( - - - - )} - - - - - - { - const emps = - employeeAutoCompleteOptions && - employeeAutoCompleteOptions.filter((e) => e.id === value)[0]; + + ); + }; - form.setFieldsValue({flat_rate: emps && emps.flat_rate}); - }} - /> - - prev.employeeid !== cur.employeeid} - > - {() => { - const employeeId = form.getFieldValue("employeeid"); - const emps = - employeeAutoCompleteOptions && - employeeAutoCompleteOptions.filter((e) => e.id === employeeId)[0]; + const MemoInput = ({ value, ...props }) => { + return ( + + ); + }; - return ( - - - - ); - }} - - - - - - - - - - {() => ( - <> - ({ - validator(rule, value) { - if (!bodyshop.tt_enforce_hours_for_tech_console) { - return Promise.resolve(); - } - if ( - !value || - getFieldValue("cost_center") === null || - !lineTicketData - ) - return Promise.resolve(); - - //Check the cost center, - const totals = CalculateAllocationsTotals( - bodyshop, - lineTicketData.joblines, - lineTicketData.timetickets, - lineTicketData.jobs_by_pk.lbr_adjustments - ); - - const fieldTypeToCheck = - bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber - ? "mod_lbr_ty" - : "cost_center"; - - const costCenterDiff = - Math.round( - totals.find( - (total) => - total[fieldTypeToCheck] === - getFieldValue("cost_center") - )?.difference * 10 - ) / 10; - - if (value > costCenterDiff) - return Promise.reject( - t( - "timetickets.validation.hoursenteredmorethanavailable" - ) - ); - else { - return Promise.resolve(); - } - }, - }), - { - required: - form.getFieldValue("cost_center") !== - "timetickets.labels.shift", - //message: t("general.validation.required"), - }, - ]} - > - - - { - // - // form.setFieldsValue({productivehrs}) - // } - // /> - } - - )} - - ({ - async validator(rule, value) { - if (value) { - const prodHrs = getFieldValue("productivehrs"); - if (prodHrs < 0 && value !== 0) - return Promise.reject( - t("timetickets.labels.zeroactualnegativeprod") - ); - else { - return Promise.resolve(); - } - } else { - return Promise.resolve(); - } - }, - }), - ]} - > - - + return ( +
    + + + {() => ( + - - - - ({ - validator(rule, value) { - const clockon = getFieldValue("clockon"); - - if (!value) return Promise.resolve(); - if (!clockon && value) - return Promise.reject( - t("timetickets.validation.clockoffwithoutclockon") - ); - // TODO - Verify this exists - if ( - value && - value.isSameOrAfter && - !value.isSameOrAfter(clockon) - ) - return Promise.reject( - t("timetickets.validation.clockoffmustbeafterclockon") - ); - - return Promise.resolve(); - }, - }), - ]} - > - - - + required: !(form.getFieldValue("cost_center") === "timetickets.labels.shift") + //message: t("general.validation.required"), } - - - - - - {() => ( - - - - )} - - - - {() => { - const jobid = form.getFieldValue("jobid"); - if ( - (!called && jobid) || - (jobid && lineTicketData?.jobs_by_pk?.id !== jobid && !loading) - ) { - loadLineTicketData({variables: {id: jobid}}); - } - return ( - - ); - }} + ]} + > + -
    - ); + )} + + + + + + { + const emps = employeeAutoCompleteOptions && employeeAutoCompleteOptions.filter((e) => e.id === value)[0]; + + form.setFieldsValue({ flat_rate: emps && emps.flat_rate }); + }} + /> + + prev.employeeid !== cur.employeeid}> + {() => { + const employeeId = form.getFieldValue("employeeid"); + const emps = + employeeAutoCompleteOptions && employeeAutoCompleteOptions.filter((e) => e.id === employeeId)[0]; + + return ( + + + + ); + }} + + + + + +
    + + + + {() => ( + <> + ({ + validator(rule, value) { + if (!bodyshop.tt_enforce_hours_for_tech_console) { + return Promise.resolve(); + } + if (!value || getFieldValue("cost_center") === null || !lineTicketData) return Promise.resolve(); + + //Check the cost center, + const totals = CalculateAllocationsTotals( + bodyshop, + lineTicketData.joblines, + lineTicketData.timetickets, + lineTicketData.jobs_by_pk.lbr_adjustments + ); + + const fieldTypeToCheck = + bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? "mod_lbr_ty" : "cost_center"; + + const costCenterDiff = + Math.round( + totals.find((total) => total[fieldTypeToCheck] === getFieldValue("cost_center"))?.difference * + 10 + ) / 10; + + if (value > costCenterDiff) + return Promise.reject(t("timetickets.validation.hoursenteredmorethanavailable")); + else { + return Promise.resolve(); + } + } + }), + { + required: form.getFieldValue("cost_center") !== "timetickets.labels.shift" + //message: t("general.validation.required"), + } + ]} + > + + + { + // + // form.setFieldsValue({productivehrs}) + // } + // /> + } + + )} + + ({ + async validator(rule, value) { + if (value) { + const prodHrs = getFieldValue("productivehrs"); + if (prodHrs < 0 && value !== 0) return Promise.reject(t("timetickets.labels.zeroactualnegativeprod")); + else { + return Promise.resolve(); + } + } else { + return Promise.resolve(); + } + } + }) + ]} + > + + + { + <> + + + + ({ + validator(rule, value) { + const clockon = getFieldValue("clockon"); + + if (!value) return Promise.resolve(); + if (!clockon && value) return Promise.reject(t("timetickets.validation.clockoffwithoutclockon")); + // TODO - Verify this exists + if (value && value.isSameOrAfter && !value.isSameOrAfter(clockon)) + return Promise.reject(t("timetickets.validation.clockoffmustbeafterclockon")); + + return Promise.resolve(); + } + }) + ]} + > + + + + } + + + + + + {() => ( + + + + )} + + + + {() => { + const jobid = form.getFieldValue("jobid"); + if ((!called && jobid) || (jobid && lineTicketData?.jobs_by_pk?.id !== jobid && !loading)) { + loadLineTicketData({ variables: { id: jobid } }); + } + return ; + }} + +
    + ); } -export function LaborAllocationContainer({ - jobid, - loading, - lineTicketData, - hideTimeTickets = false, - }) { - if (loading) return ; - if (!lineTicketData) return null; - return ( -
    - - {!hideTimeTickets && ( - - )} -
    - ); +export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideTimeTickets = false }) { + if (loading) return ; + if (!lineTicketData) return null; + return ( +
    + + {!hideTimeTickets && } +
    + ); } diff --git a/client/src/components/time-ticket-modal/time-ticket-modal.container.jsx b/client/src/components/time-ticket-modal/time-ticket-modal.container.jsx index a8d8db930..5f4bd4385 100644 --- a/client/src/components/time-ticket-modal/time-ticket-modal.container.jsx +++ b/client/src/components/time-ticket-modal/time-ticket-modal.container.jsx @@ -1,281 +1,244 @@ -import {useMutation, useQuery} from "@apollo/client"; -import {Button, Form, Modal, notification, Space} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import { useMutation, useQuery } from "@apollo/client"; +import { Button, Form, Modal, notification, Space } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; import dayjs from "../../utils/day"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {QUERY_ACTIVE_EMPLOYEES} from "../../graphql/employees.queries"; -import {INSERT_NEW_TIME_TICKET, UPDATE_TIME_TICKET,} from "../../graphql/timetickets.queries"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectTimeTicket} from "../../redux/modals/modals.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { QUERY_ACTIVE_EMPLOYEES } from "../../graphql/employees.queries"; +import { INSERT_NEW_TIME_TICKET, UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectTimeTicket } from "../../redux/modals/modals.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import TimeTicketModalComponent from "./time-ticket-modal.component"; import TimeTicketsCommitToggleComponent from "../time-tickets-commit-toggle/time-tickets-commit-toggle.component"; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - timeTicketModal: selectTimeTicket, - bodyshop: selectBodyshop, + timeTicketModal: selectTimeTicket, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("timeTicket")), + toggleModalVisible: () => dispatch(toggleModalVisible("timeTicket")) }); -export function TimeTicketModalContainer({ - timeTicketModal, - toggleModalVisible, - bodyshop, - }) { - const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - const {t} = useTranslation(); - const [enterAgain, setEnterAgain] = useState(false); - const [insertTicket] = useMutation(INSERT_NEW_TIME_TICKET); - const [updateTicket] = useMutation(UPDATE_TIME_TICKET); - const {treatments: {Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, - }); +export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible, bodyshop }) { + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const { t } = useTranslation(); + const [enterAgain, setEnterAgain] = useState(false); + const [insertTicket] = useMutation(INSERT_NEW_TIME_TICKET); + const [updateTicket] = useMutation(UPDATE_TIME_TICKET); + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); - const {data: EmployeeAutoCompleteData} = useQuery(QUERY_ACTIVE_EMPLOYEES, { - skip: !timeTicketModal.open, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const { data: EmployeeAutoCompleteData } = useQuery(QUERY_ACTIVE_EMPLOYEES, { + skip: !timeTicketModal.open, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const handleFinish = (values) => { - setLoading(true); - const emps = EmployeeAutoCompleteData.employees.filter( - (e) => e.id === values.employeeid - ); - if (timeTicketModal.context.id) { - updateTicket({ - variables: { - timeticketId: timeTicketModal.context.id, - timeticket: { - ...values, - rate: - emps.length === 1 - ? emps[0].rates.filter( - (r) => r.cost_center === values.cost_center - )[0]?.rate - : null, - }, - }, - }) - .then(handleMutationSuccess) - .catch(handleMutationError); - } else { - //Get selected employee rate. - insertTicket({ - variables: { - timeTicketInput: [ - { - ...values, - rate: - emps.length === 1 - ? emps[0].rates.filter( - (r) => r.cost_center === values.cost_center - )[0].rate - : null, - bodyshopid: bodyshop.id, - created_by: timeTicketModal.context.created_by, - }, - ], - }, - }) - .then(handleMutationSuccess) - .catch(handleMutationError); + const handleFinish = (values) => { + setLoading(true); + const emps = EmployeeAutoCompleteData.employees.filter((e) => e.id === values.employeeid); + if (timeTicketModal.context.id) { + updateTicket({ + variables: { + timeticketId: timeTicketModal.context.id, + timeticket: { + ...values, + rate: emps.length === 1 ? emps[0].rates.filter((r) => r.cost_center === values.cost_center)[0]?.rate : null + } } - }; - - const handleMutationSuccess = (response) => { - notification["success"]({ - message: t("timetickets.successes.created"), - }); - if (timeTicketModal.actions.refetch) timeTicketModal.actions.refetch(); - if (enterAgain) { - //Capture the existing information and repopulate it. - - const prev = form.getFieldsValue(["date", "employeeid"]); - - form.resetFields(); - - form.setFieldsValue(prev); - } else { - toggleModalVisible(); - } - setEnterAgain(false); - setLoading(false); - }; - - const handleMutationError = (error) => { - setEnterAgain(false); - notification["error"]({ - message: t("timetickets.errors.creating", { - message: JSON.stringify(error), - }), - }); - setLoading(false); - }; - - const handleCancel = () => { - toggleModalVisible(); - }; - - useEffect(() => { - if (enterAgain) form.submit(); - }, [enterAgain, form]); - - useEffect(() => { - if (timeTicketModal.open) form.resetFields(); - }, [timeTicketModal.open, form]); - - const handleFieldsChange = (changedFields, allFields) => { - if (!!changedFields.employeeid && !!EmployeeAutoCompleteData) { - const emps = EmployeeAutoCompleteData.employees.filter( - (e) => e.id === changedFields.employeeid - ); - form.setFieldsValue({ - cost_center: emps.length > 0 ? emps[0].cost_center : "", - ciecacode: - Object.keys(bodyshop.md_responsibility_centers.defaults.costs).find( - (key) => - bodyshop.md_responsibility_centers.defaults.costs[key] === - emps[0].cost_center - ) || "LAB", - }); - } - if (!!changedFields.cost_center && !!EmployeeAutoCompleteData) { - form.setFieldsValue({ - ciecacode: - bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === 'on' - ? changedFields.cost_center - : Object.keys( - bodyshop.md_responsibility_centers.defaults.costs - ).find( - (key) => - bodyshop.md_responsibility_centers.defaults.costs[key] === - changedFields.cost_center - ), - }); - } - }; - - return ( - r.cost_center === values.cost_center)[0].rate : null, + bodyshopid: bodyshop.id, + created_by: timeTicketModal.context.created_by } - width={"90%"} - open={timeTicketModal.open} - forceRender - onCancel={handleCancel} - afterClose={() => form.resetFields()} - footer={ - + ] + } + }) + .then(handleMutationSuccess) + .catch(handleMutationError); + } + }; + + const handleMutationSuccess = (response) => { + notification["success"]({ + message: t("timetickets.successes.created") + }); + if (timeTicketModal.actions.refetch) timeTicketModal.actions.refetch(); + if (enterAgain) { + //Capture the existing information and repopulate it. + + const prev = form.getFieldsValue(["date", "employeeid"]); + + form.resetFields(); + + form.setFieldsValue(prev); + } else { + toggleModalVisible(); + } + setEnterAgain(false); + setLoading(false); + }; + + const handleMutationError = (error) => { + setEnterAgain(false); + notification["error"]({ + message: t("timetickets.errors.creating", { + message: JSON.stringify(error) + }) + }); + setLoading(false); + }; + + const handleCancel = () => { + toggleModalVisible(); + }; + + useEffect(() => { + if (enterAgain) form.submit(); + }, [enterAgain, form]); + + useEffect(() => { + if (timeTicketModal.open) form.resetFields(); + }, [timeTicketModal.open, form]); + + const handleFieldsChange = (changedFields, allFields) => { + if (!!changedFields.employeeid && !!EmployeeAutoCompleteData) { + const emps = EmployeeAutoCompleteData.employees.filter((e) => e.id === changedFields.employeeid); + form.setFieldsValue({ + cost_center: emps.length > 0 ? emps[0].cost_center : "", + ciecacode: + Object.keys(bodyshop.md_responsibility_centers.defaults.costs).find( + (key) => bodyshop.md_responsibility_centers.defaults.costs[key] === emps[0].cost_center + ) || "LAB" + }); + } + if (!!changedFields.cost_center && !!EmployeeAutoCompleteData) { + form.setFieldsValue({ + ciecacode: + bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on" + ? changedFields.cost_center + : Object.keys(bodyshop.md_responsibility_centers.defaults.costs).find( + (key) => bodyshop.md_responsibility_centers.defaults.costs[key] === changedFields.cost_center + ) + }); + } + }; + + return ( + form.resetFields()} + footer={ + - {timeTicketModal.context && timeTicketModal.context.id ? null : ( - - )} - - } - destroyOnClose - > -
    setEnterAgain(false)} - disabled={timeTicketModal.context?.timeticket?.committed_at} - initialValues={ - timeTicketModal.context.timeticket - ? { - ...timeTicketModal.context.timeticket, - jobid: - (timeTicketModal.context.timeticket.job && - timeTicketModal.context.timeticket.job.id) || - timeTicketModal.context.timeticket.jobid || - null, - date: timeTicketModal.context.timeticket.date - ? dayjs(timeTicketModal.context.timeticket.date) - : null, - } - : {jobid: timeTicketModal.context.jobId || null} - } - onValuesChange={handleFieldsChange} + {timeTicketModal.context && timeTicketModal.context.id ? null : ( + - - {timeTicketModal.context && timeTicketModal.context.id ? null : ( - - )} -
    - } - /> - - -
    - ); + {t("general.actions.saveandnew")} + + )} + + } + destroyOnClose + > +
    setEnterAgain(false)} + disabled={timeTicketModal.context?.timeticket?.committed_at} + initialValues={ + timeTicketModal.context.timeticket + ? { + ...timeTicketModal.context.timeticket, + jobid: + (timeTicketModal.context.timeticket.job && timeTicketModal.context.timeticket.job.id) || + timeTicketModal.context.timeticket.jobid || + null, + date: timeTicketModal.context.timeticket.date ? dayjs(timeTicketModal.context.timeticket.date) : null + } + : { jobid: timeTicketModal.context.jobId || null } + } + onValuesChange={handleFieldsChange} + > + + {Enhanced_Payroll.treatment === "on" && ( + + )} + + + {timeTicketModal.context && timeTicketModal.context.id ? null : ( + + )} + + } + /> + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(TimeTicketModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketModalContainer); diff --git a/client/src/components/time-ticket-shift-active/time-ticket-shift-active.component.jsx b/client/src/components/time-ticket-shift-active/time-ticket-shift-active.component.jsx index aadbaf19d..dc67bae09 100644 --- a/client/src/components/time-ticket-shift-active/time-ticket-shift-active.component.jsx +++ b/client/src/components/time-ticket-shift-active/time-ticket-shift-active.component.jsx @@ -1,72 +1,64 @@ -import {Card, List, Typography} from "antd"; +import { Card, List, Typography } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; +import { useTranslation } from "react-i18next"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; import DataLabel from "../data-label/data-label.component"; import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component"; import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component"; -export default function TimeTicketShiftActive({ - timetickets, - refetch, - isTechConsole, - }) { - const {t} = useTranslation(); +export default function TimeTicketShiftActive({ timetickets, refetch, isTechConsole }) { + const { t } = useTranslation(); - return ( -
    - {timetickets.length > 0 ? ( -
    -
    - - {t("timetickets.labels.shiftalreadyclockedon")} - - {isTechConsole ? ( - - ) : null} -
    -
    - ( - - , - ]} - > - - {ticket.clockon} - - - - )} - > -
    -
    - ) : null} + return ( +
    + {timetickets.length > 0 ? ( +
    +
    + {t("timetickets.labels.shiftalreadyclockedon")} + {isTechConsole ? : null} +
    +
    + ( + + + ]} + > + + {ticket.clockon} + + + + )} + > +
    - ); + ) : null} +
    + ); } diff --git a/client/src/components/time-ticket-shift-form/time-ticket-shift-form.component.jsx b/client/src/components/time-ticket-shift-form/time-ticket-shift-form.component.jsx index 560c4d8a1..6e02121c8 100644 --- a/client/src/components/time-ticket-shift-form/time-ticket-shift-form.component.jsx +++ b/client/src/components/time-ticket-shift-form/time-ticket-shift-form.component.jsx @@ -1,55 +1,42 @@ -import {Form, Select} from "antd"; +import { Form, Select } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function TimeTicketShiftFormComponent({bodyshop, form}) { - const {t} = useTranslation(); - return ( -
    - - - -
    - ); +export function TimeTicketShiftFormComponent({ bodyshop, form }) { + const { t } = useTranslation(); + return ( +
    + + + +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(TimeTicketShiftFormComponent); +export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketShiftFormComponent); diff --git a/client/src/components/time-ticket-shift-form/time-ticket-shift-form.container.jsx b/client/src/components/time-ticket-shift-form/time-ticket-shift-form.container.jsx index 940111ad7..937ff3e1a 100644 --- a/client/src/components/time-ticket-shift-form/time-ticket-shift-form.container.jsx +++ b/client/src/components/time-ticket-shift-form/time-ticket-shift-form.container.jsx @@ -1,143 +1,118 @@ -import {useMutation} from "@apollo/client"; -import {Button, Form, notification, Space} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Form, notification, Space } from "antd"; import axios from "axios"; import dayjs from "../../utils/day"; -import React, {useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {INSERT_NEW_TIME_TICKET} from "../../graphql/timetickets.queries"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component"; import TimeTicektShiftComponent from "./time-ticket-shift-form.component"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - bodyshop: selectBodyshop, - technician: selectTechnician, + currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function TimeTicektShiftContainer({ - bodyshop, - technician, - currentUser, - isTechConsole, - checkIfAlreadyClocked, - }) { - console.log( - "🚀 ~ file: time-ticket-shift-form.container.jsx:28 ~ technician:", - technician - ); - const [form] = Form.useForm(); - const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET); - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); +export function TimeTicektShiftContainer({ bodyshop, technician, currentUser, isTechConsole, checkIfAlreadyClocked }) { + console.log("🚀 ~ file: time-ticket-shift-form.container.jsx:28 ~ technician:", technician); + const [form] = Form.useForm(); + const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET); + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); - const employeeId = useMemo(() => { - const assoc = bodyshop.associations.filter( - (a) => a.useremail === currentUser.email - )[0]; + const employeeId = useMemo(() => { + const assoc = bodyshop.associations.filter((a) => a.useremail === currentUser.email)[0]; - return assoc && assoc.user && assoc.user.employee && assoc.user.employee.id; - }, [bodyshop, currentUser.email]); + return assoc && assoc.user && assoc.user.employee && assoc.user.employee.id; + }, [bodyshop, currentUser.email]); - const handleFinish = async (values) => { - setLoading(true); + const handleFinish = async (values) => { + setLoading(true); - const alreadyClocked = await checkIfAlreadyClocked(); + const alreadyClocked = await checkIfAlreadyClocked(); - if (alreadyClocked) { - //Show the error. - notification["error"]({ - message: t("timetickets.errors.shiftalreadyclockedon"), - }); - } else { - const theTime = dayjs((await axios.post("/utils/time")).data); + if (alreadyClocked) { + //Show the error. + notification["error"]({ + message: t("timetickets.errors.shiftalreadyclockedon") + }); + } else { + const theTime = dayjs((await axios.post("/utils/time")).data); - const result = await insertTimeTicket({ - variables: { - timeTicketInput: [ - { - bodyshopid: bodyshop.id, - employeeid: isTechConsole ? technician.id : employeeId, - cost_center: "timetickets.labels.shift", - clockon: theTime, - date: - typeof bodyshop.timezone === "string" - // TODO: Client Update - This may be broken - ? dayjs.tz(theTime, bodyshop.timezone).format("YYYY-MM-DD") - : typeof bodyshop.timezone === "number" - ? dayjs(theTime) - .utcOffset(bodyshop.timezone) - .format("YYYY-MM-DD") - : dayjs(theTime).format("YYYY-MM-DD"), - memo: values.memo, - created_by: isTechConsole - ? currentUser.email.concat( - " | ", - technician.employee_number - .concat( - " ", - technician.first_name, - " ", - technician.last_name - ) - .trim() - ) - : currentUser.displayName - ? currentUser.email.concat(" | ", currentUser.displayName) - : currentUser.email, - }, - ], - }, - awaitRefetchQueries: true, - refetchQueries: ["QUERY_ACTIVE_SHIFT_TIME_TICKETS"], - }); - - if (!!result.errors) { - notification["error"]({ - message: t("timetickets.errors.clockingin", { - message: JSON.stringify(result.errors), - }), - }); - } else { - notification["success"]({ - message: t("timetickets.successes.clockedin"), - }); + const result = await insertTimeTicket({ + variables: { + timeTicketInput: [ + { + bodyshopid: bodyshop.id, + employeeid: isTechConsole ? technician.id : employeeId, + cost_center: "timetickets.labels.shift", + clockon: theTime, + date: + typeof bodyshop.timezone === "string" + ? // TODO: Client Update - This may be broken + dayjs.tz(theTime, bodyshop.timezone).format("YYYY-MM-DD") + : typeof bodyshop.timezone === "number" + ? dayjs(theTime).utcOffset(bodyshop.timezone).format("YYYY-MM-DD") + : dayjs(theTime).format("YYYY-MM-DD"), + memo: values.memo, + created_by: isTechConsole + ? currentUser.email.concat( + " | ", + technician.employee_number.concat(" ", technician.first_name, " ", technician.last_name).trim() + ) + : currentUser.displayName + ? currentUser.email.concat(" | ", currentUser.displayName) + : currentUser.email } - } + ] + }, + awaitRefetchQueries: true, + refetchQueries: ["QUERY_ACTIVE_SHIFT_TIME_TICKETS"] + }); - setLoading(false); - }; + if (!!result.errors) { + notification["error"]({ + message: t("timetickets.errors.clockingin", { + message: JSON.stringify(result.errors) + }) + }); + } else { + notification["success"]({ + message: t("timetickets.successes.clockedin") + }); + } + } - return ( -
    -
    - - - - {isTechConsole === true ? ( - - ) : null} - - -
    - ); + setLoading(false); + }; + + return ( +
    +
    + + + + {isTechConsole === true ? : null} + + +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(TimeTicektShiftContainer); +export default connect(mapStateToProps, mapDispatchToProps)(TimeTicektShiftContainer); diff --git a/client/src/components/time-ticket-shift/time-ticket-shift.container.jsx b/client/src/components/time-ticket-shift/time-ticket-shift.container.jsx index c7aa2e3ea..9319f9595 100644 --- a/client/src/components/time-ticket-shift/time-ticket-shift.container.jsx +++ b/client/src/components/time-ticket-shift/time-ticket-shift.container.jsx @@ -1,91 +1,75 @@ -import {useQuery} from "@apollo/client"; -import {Result} from "antd"; -import React, {useMemo} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {QUERY_ACTIVE_SHIFT_TIME_TICKETS} from "../../graphql/timetickets.queries"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { useQuery } from "@apollo/client"; +import { Result } from "antd"; +import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { QUERY_ACTIVE_SHIFT_TIME_TICKETS } from "../../graphql/timetickets.queries"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import TimeTicketShiftActive from "../time-ticket-shift-active/time-ticket-shift-active.component"; import TimeTicketShiftFormContainer from "../time-ticket-shift-form/time-ticket-shift-form.container"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - technician: selectTechnician, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + technician: selectTechnician, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function TimeTicketShiftContainer({ - bodyshop, - technician, - currentUser, - isTechConsole, - }) { - const {t} = useTranslation(); - const employeeId = useMemo(() => { - const assoc = bodyshop.associations.filter( - (a) => a.useremail === currentUser.email - )[0]; +export function TimeTicketShiftContainer({ bodyshop, technician, currentUser, isTechConsole }) { + const { t } = useTranslation(); + const employeeId = useMemo(() => { + const assoc = bodyshop.associations.filter((a) => a.useremail === currentUser.email)[0]; - return assoc && assoc.user && assoc.user.employee && assoc.user.employee.id; - }, [bodyshop, currentUser.email]); + return assoc && assoc.user && assoc.user.employee && assoc.user.employee.id; + }, [bodyshop, currentUser.email]); - const {loading, error, data, refetch} = useQuery( - QUERY_ACTIVE_SHIFT_TIME_TICKETS, - { - variables: { - employeeId: isTechConsole ? technician && technician.id : employeeId, - }, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - } - ); + const { loading, error, data, refetch } = useQuery(QUERY_ACTIVE_SHIFT_TIME_TICKETS, { + variables: { + employeeId: isTechConsole ? technician && technician.id : employeeId + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - if (loading) return ; - if (error) return ; - - if (!employeeId && !(technician && technician.id)) - return ( -
    - -
    - ); - - const checkIfAlreadyClocked = async () => { - const {data} = await refetch(); - - return data.timetickets.length > 0; - }; + if (loading) return ; + if (error) return ; + if (!employeeId && !(technician && technician.id)) return ( -
    - {data.timetickets.length > 0 ? ( - - ) : ( - - )} -
    +
    + +
    ); + + const checkIfAlreadyClocked = async () => { + const { data } = await refetch(); + + return data.timetickets.length > 0; + }; + + return ( +
    + {data.timetickets.length > 0 ? ( + + ) : ( + + )} +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(TimeTicketShiftContainer); +export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketShiftContainer); diff --git a/client/src/components/time-ticket-task-modal/time-ticket-task-modal.component.jsx b/client/src/components/time-ticket-task-modal/time-ticket-task-modal.component.jsx index 88e5cd49b..591d1e9a1 100644 --- a/client/src/components/time-ticket-task-modal/time-ticket-task-modal.component.jsx +++ b/client/src/components/time-ticket-task-modal/time-ticket-task-modal.component.jsx @@ -1,187 +1,150 @@ -import {Alert, Col, Form, Radio, Row, Skeleton, Space, Spin, Typography,} from "antd"; +import { Alert, Col, Form, Radio, Row, Skeleton, Space, Spin, Typography } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component"; import JobSearchSelectComponent from "../job-search-select/job-search-select.component"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(TimeTicketTaskModalComponent); +export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketTaskModalComponent); -export function TimeTicketTaskModalComponent({ - bodyshop, - form, - loading, - completedTasks, - unassignedHours, - }) { - const {t} = useTranslation(); +export function TimeTicketTaskModalComponent({ bodyshop, form, loading, completedTasks, unassignedHours }) { + const { t } = useTranslation(); - return ( -
    - -
    - - - - - - {loading ? ( - - ) : ( - ({ - value: preset.name, - label: preset.name, - disabled: completedTasks - .map((task) => task.name) - .includes(preset.name), - }))} - /> - )} - - - {() => { - const {task} = form.getFieldsValue(); - const theTaskPreset = bodyshop.md_tasks_presets?.presets?.find( - (tp) => tp.name === task - ); + return ( +
    + +
    + + + + + + {loading ? ( + + ) : ( + ({ + value: preset.name, + label: preset.name, + disabled: completedTasks.map((task) => task.name).includes(preset.name) + }))} + /> + )} + + + {() => { + const { task } = form.getFieldsValue(); + const theTaskPreset = bodyshop.md_tasks_presets?.presets?.find((tp) => tp.name === task); - if (!task) return null; - return ( -
    - - - - - - - - - - - - - - -
    {t("bodyshop.fields.md_tasks_presets.percent")}{`${theTaskPreset.percent || 0}%`}
    - {t("bodyshop.fields.md_tasks_presets.hourstype")} - {theTaskPreset.hourstype.join(", ")}
    - {t("bodyshop.fields.md_tasks_presets.nextstatus")} - {theTaskPreset.nextstatus}
    - ); - }} - - - - - {loading ? ( - - ) : ( - - {(fields, {add, remove, move}) => { - return ( - <> - - {t("timetickets.labels.claimtaskpreview")} - - - - - - - - - - - - {fields.map((field, index) => ( - - - - - - - ))} - -
    {t("timetickets.fields.employee")}{t("timetickets.fields.cost_center")}{t("timetickets.fields.ciecacode")}{t("timetickets.fields.productivehrs")}
    - - - - - - - - - - - - - - - -
    - - - ); - }} -
    - )} - {unassignedHours > 0 && ( - - )} - - + if (!task) return null; + return ( + + + + + + + + + + + + + + + +
    {t("bodyshop.fields.md_tasks_presets.percent")}{`${theTaskPreset.percent || 0}%`}
    {t("bodyshop.fields.md_tasks_presets.hourstype")}{theTaskPreset.hourstype.join(", ")}
    {t("bodyshop.fields.md_tasks_presets.nextstatus")}{theTaskPreset.nextstatus}
    + ); + }} + + + + + {loading ? ( + + ) : ( + + {(fields, { add, remove, move }) => { + return ( + <> + {t("timetickets.labels.claimtaskpreview")} + + + + + + + + + + + {fields.map((field, index) => ( + + + + + + + ))} + +
    {t("timetickets.fields.employee")}{t("timetickets.fields.cost_center")}{t("timetickets.fields.ciecacode")}{t("timetickets.fields.productivehrs")}
    + + + + + + + + + + + + + + + +
    + + + ); + }} +
    + )} + {unassignedHours > 0 && ( + + )} + + - {bodyshop?.md_tasks_presets?.use_approvals && ( - - - - )} - - ); + {bodyshop?.md_tasks_presets?.use_approvals && ( + + + + )} + + ); } diff --git a/client/src/components/time-ticket-task-modal/time-ticket-task-modal.container.jsx b/client/src/components/time-ticket-task-modal/time-ticket-task-modal.container.jsx index 3c5522414..53f57c68c 100644 --- a/client/src/components/time-ticket-task-modal/time-ticket-task-modal.container.jsx +++ b/client/src/components/time-ticket-task-modal/time-ticket-task-modal.container.jsx @@ -1,149 +1,146 @@ -import React, {useCallback, useEffect, useState} from "react"; +import React, { useCallback, useEffect, useState } from "react"; -import {Form, Modal, notification} from "antd"; +import { Form, Modal, notification } from "antd"; import axios from "axios"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {toggleModalVisible} from "../../redux/modals/modals.actions"; -import {selectTimeTicketTasks} from "../../redux/modals/modals.selectors"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectTimeTicketTasks } from "../../redux/modals/modals.selectors"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import TimeTicketTaskModalComponent from "./time-ticket-task-modal.component"; -import {useApolloClient} from "@apollo/client"; -import {QUERY_COMPLETED_TASKS} from "../../graphql/jobs.queries"; +import { useApolloClient } from "@apollo/client"; +import { QUERY_COMPLETED_TASKS } from "../../graphql/jobs.queries"; import "./time-ticket-task-modal.styles.scss"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; const mapStateToProps = createStructuredSelector({ - timeTicketTasksModal: selectTimeTicketTasks, - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, - technician: selectTechnician, + timeTicketTasksModal: selectTimeTicketTasks, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser, + technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("timeTicketTask")), + toggleModalVisible: () => dispatch(toggleModalVisible("timeTicketTask")) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(TimeTickeTaskModalContainer); +export default connect(mapStateToProps, mapDispatchToProps)(TimeTickeTaskModalContainer); export function TimeTickeTaskModalContainer({ - bodyshop, - currentUser, - technician, - timeTicketTasksModal, - toggleModalVisible, - }) { - const [form] = Form.useForm(); - const {context, visible, actions} = timeTicketTasksModal; - const [completedTasks, setCompletedTasks] = useState([]); - const [unassignedHours, setUnassignedHours] = useState(0); - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); - const client = useApolloClient(); + bodyshop, + currentUser, + technician, + timeTicketTasksModal, + toggleModalVisible +}) { + const [form] = Form.useForm(); + const { context, visible, actions } = timeTicketTasksModal; + const [completedTasks, setCompletedTasks] = useState([]); + const [unassignedHours, setUnassignedHours] = useState(0); + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const client = useApolloClient(); - async function handleFinish(values) { - calculateTickets({values, handleFinish: true}); + async function handleFinish(values) { + calculateTickets({ values, handleFinish: true }); + } + + const getCompletedTasks = useCallback( + async (jobid) => { + setLoading(true); + + const { data } = await client.query({ + query: QUERY_COMPLETED_TASKS, + variables: { jobid } + }); + + setCompletedTasks(data.jobs_by_pk.completed_tasks || []); + setLoading(false); + }, + [client] + ); + useEffect(() => { + if (visible) { + form.setFieldsValue({ ...context, task: null, timetickets: null }); + if (context.jobid) { + getCompletedTasks(context.jobid); + } } + }, [context.jobid, visible, getCompletedTasks, form, context]); - const getCompletedTasks = useCallback( - async (jobid) => { - setLoading(true); - - const {data} = await client.query({ - query: QUERY_COMPLETED_TASKS, - variables: {jobid}, - }); - - setCompletedTasks(data.jobs_by_pk.completed_tasks || []); - setLoading(false); - }, - [client] - ); - useEffect(() => { - if (visible) { - form.setFieldsValue({...context, task: null, timetickets: null}); - if (context.jobid) { - getCompletedTasks(context.jobid); - } - } - }, [context.jobid, visible, getCompletedTasks, form, context]); - - async function handleValueChange(changedValues, allValues) { - if (changedValues.jobid) { - getCompletedTasks(changedValues.jobid); - } - if (allValues.jobid && allValues.task) { - calculateTickets({values: allValues, handleFinish: false}); - } + async function handleValueChange(changedValues, allValues) { + if (changedValues.jobid) { + getCompletedTasks(changedValues.jobid); } + if (allValues.jobid && allValues.task) { + calculateTickets({ values: allValues, handleFinish: false }); + } + } - const calculateTickets = async ({values, handleFinish}) => { - setLoading(true); - try { - const {data, ...response} = await axios.post("/payroll/claimtask", { - jobid: values.jobid, - task: values.task, - calculateOnly: !handleFinish, - employee: technician - ? { - name: `${technician.first_name} ${technician.last_name}`.trim(), - employeeid: technician.id, - } - : {name: currentUser.displayName, email: currentUser.email}, - }); - if (response.status === 200 && handleFinish) { - //Close the modal - if (actions?.refetch) actions.refetch(); - toggleModalVisible(); - } else if (handleFinish === false) { - form.setFieldsValue({timetickets: data.ticketsToInsert}); - setUnassignedHours(data.unassignedHours); - } else { - notification.open({ - type: "error", - message: t("timetickets.errors.creating", { - message: JSON.stringify(data), - }), - }); + const calculateTickets = async ({ values, handleFinish }) => { + setLoading(true); + try { + const { data, ...response } = await axios.post("/payroll/claimtask", { + jobid: values.jobid, + task: values.task, + calculateOnly: !handleFinish, + employee: technician + ? { + name: `${technician.first_name} ${technician.last_name}`.trim(), + employeeid: technician.id } - } catch (error) { - notification.open({ - type: "error", - message: t("timetickets.errors.creating", {message: error.message}), - }); - } finally { - setLoading(false); - } - }; + : { name: currentUser.displayName, email: currentUser.email } + }); + if (response.status === 200 && handleFinish) { + //Close the modal + if (actions?.refetch) actions.refetch(); + toggleModalVisible(); + } else if (handleFinish === false) { + form.setFieldsValue({ timetickets: data.ticketsToInsert }); + setUnassignedHours(data.unassignedHours); + } else { + notification.open({ + type: "error", + message: t("timetickets.errors.creating", { + message: JSON.stringify(data) + }) + }); + } + } catch (error) { + notification.open({ + type: "error", + message: t("timetickets.errors.creating", { message: error.message }) + }); + } finally { + setLoading(false); + } + }; - return ( - { - toggleModalVisible(); - form.resetFields(); - }} - width="80%" - onOk={() => form.submit()} - > -
    - - -
    - ); + return ( + { + toggleModalVisible(); + form.resetFields(); + }} + width="80%" + onOk={() => form.submit()} + > +
    + + +
    + ); } diff --git a/client/src/components/time-tickets-attendance-table/time-tickets-attendance-table.component.jsx b/client/src/components/time-tickets-attendance-table/time-tickets-attendance-table.component.jsx index 598bf57b6..90e894873 100644 --- a/client/src/components/time-tickets-attendance-table/time-tickets-attendance-table.component.jsx +++ b/client/src/components/time-tickets-attendance-table/time-tickets-attendance-table.component.jsx @@ -1,43 +1,41 @@ -import {Button} from "antd"; +import { Button } from "antd"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useLocation} from "react-router-dom"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; -import {useTranslation} from "react-i18next"; +import React, { useState } from "react"; +import { useLocation } from "react-router-dom"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; +import { useTranslation } from "react-i18next"; import dayjs from "../../utils/day"; const AttendanceCsv = TemplateList("special").attendance_detail_csv; export default function TimeTicketsAttendanceTable() { - const searchParams = queryString.parse(useLocation().search); - const {start, end} = searchParams; - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); + const searchParams = queryString.parse(useLocation().search); + const { start, end } = searchParams; + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); - const handleClick = async () => { - setLoading(true); + const handleClick = async () => { + setLoading(true); - await GenerateDocument( - { - name: AttendanceCsv.key, - variables: { - start: start - ? start - : dayjs().startOf("week").subtract(7, "day").format("YYYY-MM-DD"), - end: end ? end : dayjs().endOf("week").format("YYYY-MM-DD"), - }, - }, - {}, - "text" - ); - - setLoading(false); - }; - - return ( - + await GenerateDocument( + { + name: AttendanceCsv.key, + variables: { + start: start ? start : dayjs().startOf("week").subtract(7, "day").format("YYYY-MM-DD"), + end: end ? end : dayjs().endOf("week").format("YYYY-MM-DD") + } + }, + {}, + "text" ); + + setLoading(false); + }; + + return ( + + ); } diff --git a/client/src/components/time-tickets-commit-toggle/time-tickets-commit-toggle.component.jsx b/client/src/components/time-tickets-commit-toggle/time-tickets-commit-toggle.component.jsx index 5d6329e47..3ee60583a 100644 --- a/client/src/components/time-tickets-commit-toggle/time-tickets-commit-toggle.component.jsx +++ b/client/src/components/time-tickets-commit-toggle/time-tickets-commit-toggle.component.jsx @@ -1,102 +1,92 @@ -import {useMutation} from "@apollo/client"; -import {Button, notification} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, notification } from "antd"; import day from "../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {UPDATE_TIME_TICKET} from "../../graphql/timetickets.queries"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ - setTimeTicketContext: (context) => - dispatch(setModalContext({context: context, modal: "timeTicket"})), + setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })) }); -export function TimeTicketsCommit({ - bodyshop, - currentUser, - timeticket, - disabled, - refetch, - setTimeTicketContext, - }) { - const {t} = useTranslation(); - const [updateTimeTicket] = useMutation(UPDATE_TIME_TICKET); +export function TimeTicketsCommit({ bodyshop, currentUser, timeticket, disabled, refetch, setTimeTicketContext }) { + const { t } = useTranslation(); + const [updateTimeTicket] = useMutation(UPDATE_TIME_TICKET); - const [loading, setLoading] = useState(false); - const handleCommit = async () => { - setLoading(true); - try { - const ticketUpdate = timeticket.committed_at - ? {commited_by: null, committed_at: null} - : { - commited_by: currentUser.email, - committed_at: day(), - }; + const [loading, setLoading] = useState(false); + const handleCommit = async () => { + setLoading(true); + try { + const ticketUpdate = timeticket.committed_at + ? { commited_by: null, committed_at: null } + : { + commited_by: currentUser.email, + committed_at: day() + }; - const result = await updateTimeTicket({ - variables: { - timeticketId: timeticket.id, - timeticket: ticketUpdate, - }, - update(cache) { - cache.modify({ - fields: { - timeTickets(existingtickets, {readField}) { - return existingtickets.map((ticket) => { - if (timeticket.id === readField("id", ticket)) { - return { - ...ticket, - ...ticketUpdate, - }; - } - return ticket; - }); - }, - }, - }); - }, - }); - if (result.errors) { - notification.open({ - type: "error", - message: t("timetickets.errors.creating", { - message: JSON.stringify(result.errors), - }), - }); - } else { - setTimeTicketContext({ - context: { - id: timeticket.id, - timeticket: result.data.update_timetickets.returning[0], - }, - }); - notification.open({ - type: "success", - message: t("timetickets.successes.committed"), + const result = await updateTimeTicket({ + variables: { + timeticketId: timeticket.id, + timeticket: ticketUpdate + }, + update(cache) { + cache.modify({ + fields: { + timeTickets(existingtickets, { readField }) { + return existingtickets.map((ticket) => { + if (timeticket.id === readField("id", ticket)) { + return { + ...ticket, + ...ticketUpdate + }; + } + return ticket; }); + } } - } catch (error) { - } finally { - setLoading(false); + }); } - }; + }); + if (result.errors) { + notification.open({ + type: "error", + message: t("timetickets.errors.creating", { + message: JSON.stringify(result.errors) + }) + }); + } else { + setTimeTicketContext({ + context: { + id: timeticket.id, + timeticket: result.data.update_timetickets.returning[0] + } + }); + notification.open({ + type: "success", + message: t("timetickets.successes.committed") + }); + } + } catch (error) { + } finally { + setLoading(false); + } + }; - if (!timeticket?.id) return null; + if (!timeticket?.id) return null; - return ( - - ); + return ( + + ); } export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketsCommit); diff --git a/client/src/components/time-tickets-commit/time-tickets-commit.component.jsx b/client/src/components/time-tickets-commit/time-tickets-commit.component.jsx index f080f10b0..52aef7401 100644 --- a/client/src/components/time-tickets-commit/time-tickets-commit.component.jsx +++ b/client/src/components/time-tickets-commit/time-tickets-commit.component.jsx @@ -1,92 +1,88 @@ -import {useMutation} from "@apollo/client"; -import {Button, notification} from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, notification } from "antd"; import day from "../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {UPDATE_TIME_TICKETS} from "../../graphql/timetickets.queries"; -import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { UPDATE_TIME_TICKETS } from "../../graphql/timetickets.queries"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); export function TimeTicketsCommit({ - bodyshop, - currentUser, - timetickets, - disabled, - loadingCallback, - completedCallback, - refetch, - }) { - const {t} = useTranslation(); - const [updateTimeTickets] = useMutation(UPDATE_TIME_TICKETS); + bodyshop, + currentUser, + timetickets, + disabled, + loadingCallback, + completedCallback, + refetch +}) { + const { t } = useTranslation(); + const [updateTimeTickets] = useMutation(UPDATE_TIME_TICKETS); - const [loading, setLoading] = useState(false); - const handleCommit = async () => { - setLoading(true); - try { - const result = await updateTimeTickets({ - variables: { - timeticketIds: timetickets.map((ticket) => ticket.id), - timeticket: { - commited_by: currentUser.email, - committed_at: day(), - }, - }, - update(cache) { - cache.modify({ - fields: { - timeTickets(existingtickets, {readField}) { - const modifiedIds = timetickets.map((ticket) => ticket.id); - return existingtickets.map((ticket) => { - if (modifiedIds.includes(readField("id", ticket))) { - return { - ...ticket, - commited_by: currentUser.email, - committed_at: day(), - }; - } - return ticket; - }); - }, - }, - }); - }, - }); - if (result.errors) { - notification.open({ - type: "error", - message: t("timetickets.errors.creating", { - message: JSON.stringify(result.errors), - }), + const [loading, setLoading] = useState(false); + const handleCommit = async () => { + setLoading(true); + try { + const result = await updateTimeTickets({ + variables: { + timeticketIds: timetickets.map((ticket) => ticket.id), + timeticket: { + commited_by: currentUser.email, + committed_at: day() + } + }, + update(cache) { + cache.modify({ + fields: { + timeTickets(existingtickets, { readField }) { + const modifiedIds = timetickets.map((ticket) => ticket.id); + return existingtickets.map((ticket) => { + if (modifiedIds.includes(readField("id", ticket))) { + return { + ...ticket, + commited_by: currentUser.email, + committed_at: day() + }; + } + return ticket; }); - } else { - notification.open({ - type: "success", - message: t("timetickets.successes.committed"), - }); - if (!!completedCallback) completedCallback([]); - if (!!loadingCallback) loadingCallback(false); + } } - } catch (error) { - } finally { - setLoading(false); + }); } - }; + }); + if (result.errors) { + notification.open({ + type: "error", + message: t("timetickets.errors.creating", { + message: JSON.stringify(result.errors) + }) + }); + } else { + notification.open({ + type: "success", + message: t("timetickets.successes.committed") + }); + if (!!completedCallback) completedCallback([]); + if (!!loadingCallback) loadingCallback(false); + } + } catch (error) { + } finally { + setLoading(false); + } + }; - return ( - - ); + return ( + + ); } export default connect(mapStateToProps, null)(TimeTicketsCommit); diff --git a/client/src/components/time-tickets-payroll-table/time-tickets-payroll-table.component.jsx b/client/src/components/time-tickets-payroll-table/time-tickets-payroll-table.component.jsx index dd8f1d5ce..4fb24f399 100644 --- a/client/src/components/time-tickets-payroll-table/time-tickets-payroll-table.component.jsx +++ b/client/src/components/time-tickets-payroll-table/time-tickets-payroll-table.component.jsx @@ -1,42 +1,40 @@ -import {Button} from "antd"; +import { Button } from "antd"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useLocation} from "react-router-dom"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; -import {useTranslation} from "react-i18next"; +import React, { useState } from "react"; +import { useLocation } from "react-router-dom"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; +import { useTranslation } from "react-i18next"; import dayjs from "../../utils/day"; const PayrollTemplate = TemplateList("special").exported_payroll; export default function TimeTicketsPayrollTable() { - const searchParams = queryString.parse(useLocation().search); - const {start, end} = searchParams; - const {t} = useTranslation(); - const [loading, setLoading] = useState(false); + const searchParams = queryString.parse(useLocation().search); + const { start, end } = searchParams; + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); - const handleClick = async () => { - setLoading(true); + const handleClick = async () => { + setLoading(true); - await GenerateDocument( - { - name: PayrollTemplate.key, - variables: { - start: start - ? start - : dayjs().startOf("week").subtract(7, "day").format("YYYY-MM-DD"), - end: end ? end : dayjs().endOf("week").format("YYYY-MM-DD"), - }, - }, - {}, - "x" - ); - - setLoading(false); - }; - - return ( - + await GenerateDocument( + { + name: PayrollTemplate.key, + variables: { + start: start ? start : dayjs().startOf("week").subtract(7, "day").format("YYYY-MM-DD"), + end: end ? end : dayjs().endOf("week").format("YYYY-MM-DD") + } + }, + {}, + "x" ); + + setLoading(false); + }; + + return ( + + ); } diff --git a/client/src/components/time-tickets-summary-employees/time-tickets-summary-employees.component.jsx b/client/src/components/time-tickets-summary-employees/time-tickets-summary-employees.component.jsx index 6c00e2de2..9b45ea36c 100644 --- a/client/src/components/time-tickets-summary-employees/time-tickets-summary-employees.component.jsx +++ b/client/src/components/time-tickets-summary-employees/time-tickets-summary-employees.component.jsx @@ -1,346 +1,306 @@ -import {Card, Col, Row, Table} from "antd"; +import { Card, Col, Row, Table } from "antd"; import _ from "lodash"; import dayjs from "../../utils/day"; -import React, {useMemo, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {onlyUnique} from "../../utils/arrayHelper"; -import {alphaSort} from "../../utils/sorters"; -import {TemplateList} from "../../utils/TemplateConstants"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { onlyUnique } from "../../utils/arrayHelper"; +import { alphaSort } from "../../utils/sorters"; +import { TemplateList } from "../../utils/TemplateConstants"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; import Dinero from "dinero.js"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function TimeTicketsSummaryEmployees({ - bodyshop, - loading, - timetickets, - startDate, - endDate, - }) { - //Group everything by employee - //Then sum the individual time TimeTicketsSummary. +export function TimeTicketsSummaryEmployees({ bodyshop, loading, timetickets, startDate, endDate }) { + //Group everything by employee + //Then sum the individual time TimeTicketsSummary. - //Calculate job based tickets. - const jobTicketsByEmployee = {}; - timetickets - .filter((i) => i.cost_center !== "timetickets.labels.shift") - .map((tt) => { - if (!!!jobTicketsByEmployee[tt.employeeid]) { - jobTicketsByEmployee[tt.employeeid] = []; - } - jobTicketsByEmployee[tt.employeeid].push(tt); - return null; - }); - const jobTickets = Object.keys(jobTicketsByEmployee).map(function (key) { - return { - jobKey: key, - employee: jobTicketsByEmployee[key][0].employee, - tickets: jobTicketsByEmployee[key], - }; + //Calculate job based tickets. + const jobTicketsByEmployee = {}; + timetickets + .filter((i) => i.cost_center !== "timetickets.labels.shift") + .map((tt) => { + if (!!!jobTicketsByEmployee[tt.employeeid]) { + jobTicketsByEmployee[tt.employeeid] = []; + } + jobTicketsByEmployee[tt.employeeid].push(tt); + return null; }); + const jobTickets = Object.keys(jobTicketsByEmployee).map(function (key) { + return { + jobKey: key, + employee: jobTicketsByEmployee[key][0].employee, + tickets: jobTicketsByEmployee[key] + }; + }); - //Calculate shift based tickets. - const shiftTicketsByEmployee = {}; - timetickets - .filter((i) => i.cost_center === "timetickets.labels.shift") - .map((tt) => { - if (!!!shiftTicketsByEmployee[tt.employeeid]) { - shiftTicketsByEmployee[tt.employeeid] = []; - } - shiftTicketsByEmployee[tt.employeeid].push(tt); - return null; - }); - const shiftTickets = Object.keys(shiftTicketsByEmployee).map(function (key) { - return { - employee: shiftTicketsByEmployee[key][0].employee, - tickets: shiftTicketsByEmployee[key], - }; + //Calculate shift based tickets. + const shiftTicketsByEmployee = {}; + timetickets + .filter((i) => i.cost_center === "timetickets.labels.shift") + .map((tt) => { + if (!!!shiftTicketsByEmployee[tt.employeeid]) { + shiftTicketsByEmployee[tt.employeeid] = []; + } + shiftTicketsByEmployee[tt.employeeid].push(tt); + return null; }); + const shiftTickets = Object.keys(shiftTicketsByEmployee).map(function (key) { + return { + employee: shiftTicketsByEmployee[key][0].employee, + tickets: shiftTicketsByEmployee[key] + }; + }); - return ( - - - - - - - - - ); + return ( + + + + + + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(TimeTicketsSummaryEmployees); +export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketsSummaryEmployees); -const JobRelatedTicketsTable = ({ - loading, - jobTickets, - startDate, - endDate, - }) => { - const Templates = TemplateList(); - const {t} = useTranslation(); - const [state, setState] = useState({ - sortedInfo: {}, - }); - const data = useMemo(() => { - return _.flatten( - jobTickets.map((item, idx) => { - const employeeCostCenters = item.tickets - .map((i) => i.cost_center) - .filter(onlyUnique); +const JobRelatedTicketsTable = ({ loading, jobTickets, startDate, endDate }) => { + const Templates = TemplateList(); + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {} + }); + const data = useMemo(() => { + return _.flatten( + jobTickets.map((item, idx) => { + const employeeCostCenters = item.tickets.map((i) => i.cost_center).filter(onlyUnique); - return employeeCostCenters.map((costCenter) => { - const actHrs = item.tickets - .filter((ticket) => ticket.cost_center === costCenter) - .reduce((acc, val) => acc + val.actualhrs, 0); + return employeeCostCenters.map((costCenter) => { + const actHrs = item.tickets + .filter((ticket) => ticket.cost_center === costCenter) + .reduce((acc, val) => acc + val.actualhrs, 0); - const prodHrs = item.tickets - .filter((ticket) => ticket.cost_center === costCenter) - .reduce((acc, val) => acc + val.productivehrs, 0); + const prodHrs = item.tickets + .filter((ticket) => ticket.cost_center === costCenter) + .reduce((acc, val) => acc + val.productivehrs, 0); - const clockHrs = item.tickets - .filter((ticket) => ticket.cost_center === costCenter) - .reduce((acc, val) => { - if (!!val.clockoff && !!val.clockon) - return ( - acc + - dayjs(val.clockoff).diff(dayjs(val.clockon), "hour", true) - ); - return acc; - }, 0); - - const pay = item.tickets - .filter((ticket) => ticket.cost_center === costCenter) - .reduce((acc, val) => { - return acc.add( - Dinero({amount: Math.round(val.rate * 100)}).multiply( - val.flat_rate ? val.productivehrs : val.actualhrs - ) - ); - }, Dinero()); - - return { - id: `${item.jobKey}${costCenter}`, - costCenter, - item, - actHrs: actHrs.toFixed(1), - prodHrs: prodHrs.toFixed(1), - clockHrs, - ...InstanceRenderManager({ imex: {}, rome: { pay } }), - }; - }); - }) - ); - }, [jobTickets]); - - const columns = [ - { - title: t("employees.labels.name"), - dataIndex: "empname", - key: "empname", - sorter: (a, b) => - alphaSort(a.item.employee.last_name, b.item.employee.last_name), - sortOrder: - state.sortedInfo.columnKey === "empname" && state.sortedInfo.order, - render: (text, record) => - `${record.item.employee.first_name} ${record.item.employee.last_name} ${ - record.costCenter ? `(${record.costCenter})` : "" - }`.trim(), - }, - { - title: t("timetickets.fields.actualhrs"), - dataIndex: "actHrs", - key: "actHrs", - sorter: (a, b) => a.actHrs - b.actHrs, - sortOrder: - state.sortedInfo.columnKey === "actHrs" && state.sortedInfo.order, - }, - { - title: t("timetickets.fields.productivehrs"), - dataIndex: "prodHrs", - key: "prodHrs", - sorter: (a, b) => a.prodHrs - b.prodHrs, - sortOrder: - state.sortedInfo.columnKey === "prodHrs" && state.sortedInfo.order, - }, - { - title: t("timetickets.fields.efficiency"), - dataIndex: "total", - key: "total", - sorter: (a, b) => - (a.actHrs === 0 || !a.actHrs ? 0 : (a.prodHrs / a.actHrs) * 100) - - (b.actHrs === 0 || !b.actHrs ? 0 : (b.prodHrs / b.actHrs) * 100), - sortOrder: - state.sortedInfo.columnKey === "total" && state.sortedInfo.order, - render: (text, record) => - record.actHrs === 0 || !record.actHrs - ? "∞" - : ((record.prodHrs / record.actHrs) * 100).toFixed(2), - }, - { - title: t("timetickets.fields.clockhours"), - dataIndex: "clockHrs", - key: "clockHrs", - sorter: (a, b) => a.clockHrs - b.clockHrs, - sortOrder: - state.sortedInfo.columnKey === "clockHrs" && state.sortedInfo.order, - render: (text, record) => record.clockHrs.toFixed(2), - }, - ...InstanceRenderManager({ imex: [], rome:[{ - title: "Pay", - dataIndex: "Pay", - key: "Pay", - sorter: (a, b) => a.clockHrs - b.clockHrs, - sortOrder: - state.sortedInfo.columnKey === "clockHrs" && state.sortedInfo.order, - render: (text, record) => record.pay.toFormat("$0.00"), - }]}) - , - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", - render: (text, record) => ( - - ), - }, - ]; - - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - - return ( - - - - ); -}; - -const ShiftRelatedTicketsTable = ({ - loading, - shiftTickets, - startDate, - endDate, - }) => { - const Templates = TemplateList(); - const {t} = useTranslation(); - const [state, setState] = useState({ - sortedInfo: {}, - }); - const data = useMemo(() => { - return shiftTickets.map((item) => { - const clockHrs = item.tickets.reduce((acc, val) => { - if (!!val.clockoff && !!val.clockon) - return ( - acc + dayjs(val.clockoff).diff(dayjs(val.clockon), "hour", true) - ); - return acc; + const clockHrs = item.tickets + .filter((ticket) => ticket.cost_center === costCenter) + .reduce((acc, val) => { + if (!!val.clockoff && !!val.clockon) + return acc + dayjs(val.clockoff).diff(dayjs(val.clockon), "hour", true); + return acc; }, 0); - return {id: item.employee.id, item, clockHrs}; + const pay = item.tickets + .filter((ticket) => ticket.cost_center === costCenter) + .reduce((acc, val) => { + return acc.add( + Dinero({ amount: Math.round(val.rate * 100) }).multiply( + val.flat_rate ? val.productivehrs : val.actualhrs + ) + ); + }, Dinero()); + + return { + id: `${item.jobKey}${costCenter}`, + costCenter, + item, + actHrs: actHrs.toFixed(1), + prodHrs: prodHrs.toFixed(1), + clockHrs, + ...InstanceRenderManager({ imex: {}, rome: { pay } }) + }; }); - }, [shiftTickets]); - - const columns = [ - { - title: t("employees.labels.name"), - dataIndex: "empname", - key: "empname", - sorter: (a, b) => alphaSort(a.empname, b.empname), - sortOrder: - state.sortedInfo.columnKey === "empname" && state.sortedInfo.order, - render: (text, record) => - `${record.item.employee.first_name} ${record.item.employee.last_name}`, - }, - - { - title: t("timetickets.fields.clockhours"), - dataIndex: "clockHrs", - key: "clockHrs", - sorter: (a, b) => a.clockHrs - b.clockHrs, - sortOrder: - state.sortedInfo.columnKey === "clockHrs" && state.sortedInfo.order, - render: (text, record) => record.clockHrs.toFixed(2), - }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", - render: (text, record) => ( - - ), - }, - ]; - - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - - return ( - -
    - + }) ); + }, [jobTickets]); + + const columns = [ + { + title: t("employees.labels.name"), + dataIndex: "empname", + key: "empname", + sorter: (a, b) => alphaSort(a.item.employee.last_name, b.item.employee.last_name), + sortOrder: state.sortedInfo.columnKey === "empname" && state.sortedInfo.order, + render: (text, record) => + `${record.item.employee.first_name} ${record.item.employee.last_name} ${ + record.costCenter ? `(${record.costCenter})` : "" + }`.trim() + }, + { + title: t("timetickets.fields.actualhrs"), + dataIndex: "actHrs", + key: "actHrs", + sorter: (a, b) => a.actHrs - b.actHrs, + sortOrder: state.sortedInfo.columnKey === "actHrs" && state.sortedInfo.order + }, + { + title: t("timetickets.fields.productivehrs"), + dataIndex: "prodHrs", + key: "prodHrs", + sorter: (a, b) => a.prodHrs - b.prodHrs, + sortOrder: state.sortedInfo.columnKey === "prodHrs" && state.sortedInfo.order + }, + { + title: t("timetickets.fields.efficiency"), + dataIndex: "total", + key: "total", + sorter: (a, b) => + (a.actHrs === 0 || !a.actHrs ? 0 : (a.prodHrs / a.actHrs) * 100) - + (b.actHrs === 0 || !b.actHrs ? 0 : (b.prodHrs / b.actHrs) * 100), + sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order, + render: (text, record) => + record.actHrs === 0 || !record.actHrs ? "∞" : ((record.prodHrs / record.actHrs) * 100).toFixed(2) + }, + { + title: t("timetickets.fields.clockhours"), + dataIndex: "clockHrs", + key: "clockHrs", + sorter: (a, b) => a.clockHrs - b.clockHrs, + sortOrder: state.sortedInfo.columnKey === "clockHrs" && state.sortedInfo.order, + render: (text, record) => record.clockHrs.toFixed(2) + }, + ...InstanceRenderManager({ + imex: [], + rome: [ + { + title: "Pay", + dataIndex: "Pay", + key: "Pay", + sorter: (a, b) => a.clockHrs - b.clockHrs, + sortOrder: state.sortedInfo.columnKey === "clockHrs" && state.sortedInfo.order, + render: (text, record) => record.pay.toFormat("$0.00") + } + ] + }), + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => ( + + ) + } + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + return ( + +
    + + ); +}; + +const ShiftRelatedTicketsTable = ({ loading, shiftTickets, startDate, endDate }) => { + const Templates = TemplateList(); + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {} + }); + const data = useMemo(() => { + return shiftTickets.map((item) => { + const clockHrs = item.tickets.reduce((acc, val) => { + if (!!val.clockoff && !!val.clockon) return acc + dayjs(val.clockoff).diff(dayjs(val.clockon), "hour", true); + return acc; + }, 0); + + return { id: item.employee.id, item, clockHrs }; + }); + }, [shiftTickets]); + + const columns = [ + { + title: t("employees.labels.name"), + dataIndex: "empname", + key: "empname", + sorter: (a, b) => alphaSort(a.empname, b.empname), + sortOrder: state.sortedInfo.columnKey === "empname" && state.sortedInfo.order, + render: (text, record) => `${record.item.employee.first_name} ${record.item.employee.last_name}` + }, + + { + title: t("timetickets.fields.clockhours"), + dataIndex: "clockHrs", + key: "clockHrs", + sorter: (a, b) => a.clockHrs - b.clockHrs, + sortOrder: state.sortedInfo.columnKey === "clockHrs" && state.sortedInfo.order, + render: (text, record) => record.clockHrs.toFixed(2) + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => ( + + ) + } + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + return ( + +
    + + ); }; diff --git a/client/src/components/tt-approvals-list/tt-approvals-list.component.jsx b/client/src/components/tt-approvals-list/tt-approvals-list.component.jsx index c58a8d5ea..755415824 100644 --- a/client/src/components/tt-approvals-list/tt-approvals-list.component.jsx +++ b/client/src/components/tt-approvals-list/tt-approvals-list.component.jsx @@ -1,238 +1,218 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Space, Table, Tag} from "antd"; +import { SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Space, Table, Tag } from "antd"; import Dinero from "dinero.js"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectAuthLevel, selectBodyshop,} from "../../redux/user/user.selectors"; -import {DateFormatter, DateTimeFormatter} from "../../utils/DateFormatter"; -import {onlyUnique} from "../../utils/arrayHelper"; -import {alphaSort, dateSort} from "../../utils/sorters"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors"; +import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter"; +import { onlyUnique } from "../../utils/arrayHelper"; +import { alphaSort, dateSort } from "../../utils/sorters"; import TtApproveButtonComponent from "../tt-approve-button/tt-approve-button.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - authLevel: selectAuthLevel, + bodyshop: selectBodyshop, + authLevel: selectAuthLevel }); const mapDispatchToProps = (dispatch) => ({ - setTimeTicketTaskContext: (context) => - dispatch(setModalContext({context: context, modal: "timeTicketTask"})), + setTimeTicketTaskContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicketTask" })) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(TtApprovalsListComponent); +export default connect(mapStateToProps, mapDispatchToProps)(TtApprovalsListComponent); export function TtApprovalsListComponent({ - bodyshop, - setTimeTicketTaskContext, - authLevel, - disabled, - loading, - tt_approval_queue, - total, - refetch, - techConsole, - jobId, - extra, - }) { - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); + bodyshop, + setTimeTicketTaskContext, + authLevel, + disabled, + loading, + tt_approval_queue, + total, + refetch, + techConsole, + jobId, + extra +}) { + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); - const {t} = useTranslation(); - const history = useNavigate(); - const search = queryString.parse(useLocation().search); - const {page} = search; - const [selectedTickets, setSelectedTickets] = useState([]); + const { t } = useTranslation(); + const history = useNavigate(); + const search = queryString.parse(useLocation().search); + const { page } = search; + const [selectedTickets, setSelectedTickets] = useState([]); - const columns = [ - { - title: t("timetickets.fields.date"), - dataIndex: "date", - key: "date", - sorter: (a, b) => dateSort(a.date, b.date), - sortOrder: - state.sortedInfo.columnKey === "date" && state.sortedInfo.order, - render: (text, record) => {record.date}, - }, - { - title: t("timetickets.fields.employee"), - dataIndex: "employeeid", - key: "employeeid", - sorter: (a, b) => alphaSort(a.employee.last_name, b.employee.last_name), - sortOrder: - state.sortedInfo.columnKey === "employee" && state.sortedInfo.order, - render: (text, record) => - `${record.employee.first_name} ${record.employee.last_name}`, - filters: - tt_approval_queue - .map((l) => l.employeeid) - .filter(onlyUnique) - .map((s) => { - return { - text: (() => { - const emp = bodyshop.employees.find((e) => e.id === s); + const columns = [ + { + title: t("timetickets.fields.date"), + dataIndex: "date", + key: "date", + sorter: (a, b) => dateSort(a.date, b.date), + sortOrder: state.sortedInfo.columnKey === "date" && state.sortedInfo.order, + render: (text, record) => {record.date} + }, + { + title: t("timetickets.fields.employee"), + dataIndex: "employeeid", + key: "employeeid", + sorter: (a, b) => alphaSort(a.employee.last_name, b.employee.last_name), + sortOrder: state.sortedInfo.columnKey === "employee" && state.sortedInfo.order, + render: (text, record) => `${record.employee.first_name} ${record.employee.last_name}`, + filters: + tt_approval_queue + .map((l) => l.employeeid) + .filter(onlyUnique) + .map((s) => { + return { + text: (() => { + const emp = bodyshop.employees.find((e) => e.id === s); - return `${emp?.first_name} ${emp?.last_name}`; - })(), // - value: [s], - }; - }) || [], - onFilter: (value, record) => value.includes(record.employeeid), - }, - { - title: t("timetickets.fields.cost_center"), - dataIndex: "cost_center", - key: "cost_center", - sorter: (a, b) => alphaSort(a.cost_center, b.cost_center), - render: (text, record) => - record.cost_center === "timetickets.labels.shift" - ? t(record.cost_center) - : record.cost_center, - sortOrder: - state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order, - filters: - tt_approval_queue - .map((l) => l.cost_center) - .filter(onlyUnique) - .map((s) => { - return { - text: s === "timetickets.labels.shift" ? t(s) : s, //|| "No Status*", - value: [s], - }; - }) || [], - onFilter: (value, record) => value.includes(record.cost_center), - }, - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => - alphaSort(a.job && a.job.ro_number, b.job && b.job.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - render: (text, record) => - record.job && ( - - - {record.job.ro_number || "N/A"} - {record.job.status} - - - ), - }, - { - title: t("timetickets.fields.productivehrs"), - dataIndex: "productivehrs", - key: "productivehrs", - sorter: (a, b) => a.productivehrs - b.productivehrs, - sortOrder: - state.sortedInfo.columnKey === "productivehrs" && - state.sortedInfo.order, - }, - { - title: t("timetickets.fields.actualhrs"), - dataIndex: "actualhrs", - key: "actualhrs", - sorter: (a, b) => a.actualhrs - b.actualhrs, - sortOrder: - state.sortedInfo.columnKey === "actualhrs" && state.sortedInfo.order, - }, - { - title: t("timetickets.fields.memo"), - dataIndex: "memo", - key: "memo", - sorter: (a, b) => alphaSort(a.memo, b.memo), - sortOrder: - state.sortedInfo.columnKey === "memo" && state.sortedInfo.order, - render: (text, record) => - record.clockon || record.clockoff ? t(record.memo) : record.memo, - }, - { - title: t("timetickets.fields.clockon"), - dataIndex: "clockon", - key: "clockon", + return `${emp?.first_name} ${emp?.last_name}`; + })(), // + value: [s] + }; + }) || [], + onFilter: (value, record) => value.includes(record.employeeid) + }, + { + title: t("timetickets.fields.cost_center"), + dataIndex: "cost_center", + key: "cost_center", + sorter: (a, b) => alphaSort(a.cost_center, b.cost_center), + render: (text, record) => + record.cost_center === "timetickets.labels.shift" ? t(record.cost_center) : record.cost_center, + sortOrder: state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order, + filters: + tt_approval_queue + .map((l) => l.cost_center) + .filter(onlyUnique) + .map((s) => { + return { + text: s === "timetickets.labels.shift" ? t(s) : s, //|| "No Status*", + value: [s] + }; + }) || [], + onFilter: (value, record) => value.includes(record.cost_center) + }, + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.job && a.job.ro_number, b.job && b.job.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => + record.job && ( + + + {record.job.ro_number || "N/A"} + {record.job.status} + + + ) + }, + { + title: t("timetickets.fields.productivehrs"), + dataIndex: "productivehrs", + key: "productivehrs", + sorter: (a, b) => a.productivehrs - b.productivehrs, + sortOrder: state.sortedInfo.columnKey === "productivehrs" && state.sortedInfo.order + }, + { + title: t("timetickets.fields.actualhrs"), + dataIndex: "actualhrs", + key: "actualhrs", + sorter: (a, b) => a.actualhrs - b.actualhrs, + sortOrder: state.sortedInfo.columnKey === "actualhrs" && state.sortedInfo.order + }, + { + title: t("timetickets.fields.memo"), + dataIndex: "memo", + key: "memo", + sorter: (a, b) => alphaSort(a.memo, b.memo), + sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order, + render: (text, record) => (record.clockon || record.clockoff ? t(record.memo) : record.memo) + }, + { + title: t("timetickets.fields.clockon"), + dataIndex: "clockon", + key: "clockon", - render: (text, record) => ( - {record.clockon} - ), - }, - { - title: "Pay", - dataIndex: "pay", - key: "pay", - render: (text, record) => - Dinero({amount: Math.round(record.rate * 100)}) - .multiply(record.flat_rate ? record.productivehrs : record.actualhrs) - .toFormat("$0.00"), - }, - ]; + render: (text, record) => {record.clockon} + }, + { + title: "Pay", + dataIndex: "pay", + key: "pay", + render: (text, record) => + Dinero({ amount: Math.round(record.rate * 100) }) + .multiply(record.flat_rate ? record.productivehrs : record.actualhrs) + .toFormat("$0.00") + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - search.page = pagination.current; - if (sorter && sorter.column && sorter.column.sortObject) { - search.searchObj = JSON.stringify(sorter.column.sortObject(sorter.order)); - } else { - delete search.searchObj; - search.sortcolumn = sorter.order ? sorter.columnKey : null; - search.sortorder = sorter.order; - } + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + search.page = pagination.current; + if (sorter && sorter.column && sorter.column.sortObject) { + search.searchObj = JSON.stringify(sorter.column.sortObject(sorter.order)); + } else { + delete search.searchObj; + search.sortcolumn = sorter.order ? sorter.columnKey : null; + search.sortorder = sorter.order; + } - search.sort = JSON.stringify({[sorter.columnKey]: sorter.order}); - history({search: queryString.stringify(search)}); - }; + search.sort = JSON.stringify({ [sorter.columnKey]: sorter.order }); + history({ search: queryString.stringify(search) }); + }; - return ( - - {extra} - - - - } - > -
    - setSelectedTickets(selectedRows.map((i) => i.id)), - onSelect: (record, selected, selectedRows, nativeEvent) => { - setSelectedTickets(selectedRows.map((i) => i.id)); - }, - selectedRowKeys: selectedTickets, - type: "checkbox", - }} - /> - - ); + return ( + + {extra} + + + + } + > +
    setSelectedTickets(selectedRows.map((i) => i.id)), + onSelect: (record, selected, selectedRows, nativeEvent) => { + setSelectedTickets(selectedRows.map((i) => i.id)); + }, + selectedRowKeys: selectedTickets, + type: "checkbox" + }} + /> + + ); } diff --git a/client/src/components/tt-approvals-list/tt-approvals-list.container.jsx b/client/src/components/tt-approvals-list/tt-approvals-list.container.jsx index 6e524f095..b6a7e31b1 100644 --- a/client/src/components/tt-approvals-list/tt-approvals-list.container.jsx +++ b/client/src/components/tt-approvals-list/tt-approvals-list.container.jsx @@ -1,66 +1,52 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import queryString from "query-string"; import React from "react"; -import {connect} from "react-redux"; -import {useLocation} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {QUERY_ALL_TT_APPROVALS_PAGINATED} from "../../graphql/tt-approvals.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; +import { connect } from "react-redux"; +import { useLocation } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { QUERY_ALL_TT_APPROVALS_PAGINATED } from "../../graphql/tt-approvals.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import AlertComponent from "../alert/alert.component"; import TtApprovalsListComponent from "./tt-approvals-list.component"; const mapStateToProps = createStructuredSelector({}); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function TimeTicketsContainer({ - bodyshop, - setBreadcrumbs, - setSelectedHeader, - }) { - const searchParams = queryString.parse(useLocation().search); - const {page, sortcolumn, sortorder, search, searchObj} = searchParams; +export function TimeTicketsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) { + const searchParams = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder, search, searchObj } = searchParams; - const {loading, error, data, refetch} = useQuery( - QUERY_ALL_TT_APPROVALS_PAGINATED, - { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - variables: { - search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, - order: [ - searchObj - ? JSON.parse(searchObj) - : { - [sortcolumn || "date"]: sortorder - ? sortorder === "descend" - ? "desc" - : "asc" - : "desc", - }, - ], - }, - } - ); + const { loading, error, data, refetch } = useQuery(QUERY_ALL_TT_APPROVALS_PAGINATED, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + search: search || "", + offset: page ? (page - 1) * 25 : 0, + limit: 25, + order: [ + searchObj + ? JSON.parse(searchObj) + : { + [sortcolumn || "date"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc" + } + ] + } + }); - if (error) return ; + if (error) return ; - return ( - - ); + return ( + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(TimeTicketsContainer); +export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketsContainer); diff --git a/client/src/components/tt-approve-button/tt-approve-button.component.jsx b/client/src/components/tt-approve-button/tt-approve-button.component.jsx index db7109bf9..8a31528d7 100644 --- a/client/src/components/tt-approve-button/tt-approve-button.component.jsx +++ b/client/src/components/tt-approve-button/tt-approve-button.component.jsx @@ -1,99 +1,96 @@ -import {useApolloClient} from "@apollo/client"; -import {Button, notification} from "antd"; +import { useApolloClient } from "@apollo/client"; +import { Button, notification } from "antd"; import _ from "lodash"; import day from "../../utils/day"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {INSERT_TIME_TICKET_AND_APPROVE} from "../../graphql/timetickets.queries"; -import {QUERY_TT_APPROVALS_BY_IDS} from "../../graphql/tt-approvals.queries"; -import {selectAuthLevel, selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; -import {HasRbacAccess} from "../rbac-wrapper/rbac-wrapper.component"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_TIME_TICKET_AND_APPROVE } from "../../graphql/timetickets.queries"; +import { QUERY_TT_APPROVALS_BY_IDS } from "../../graphql/tt-approvals.queries"; +import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - currentUser: selectCurrentUser, - authLevel: selectAuthLevel, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser, + authLevel: selectAuthLevel }); export function TtApproveButton({ - bodyshop, - currentUser, - selectedTickets, - disabled, - authLevel, - loadingCallback, - completedCallback, - refetch, - }) { - const {t} = useTranslation(); - const client = useApolloClient(); + bodyshop, + currentUser, + selectedTickets, + disabled, + authLevel, + loadingCallback, + completedCallback, + refetch +}) { + const { t } = useTranslation(); + const client = useApolloClient(); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(false); - const handleQbxml = async () => { - setLoading(true); - try { - const {data} = await client.query({ - query: QUERY_TT_APPROVALS_BY_IDS, - variables: {ids: selectedTickets}, - }); + const handleQbxml = async () => { + setLoading(true); + try { + const { data } = await client.query({ + query: QUERY_TT_APPROVALS_BY_IDS, + variables: { ids: selectedTickets } + }); - const insertResponse = await client.mutate({ - mutation: INSERT_TIME_TICKET_AND_APPROVE, - variables: { - timeTicketInput: data.tt_approval_queue.map((tta) => ({ - ..._.omit(tta, ["id", "__typename"]), - ttapprovalqueueid: tta.id, - })), - approvalIds: selectedTickets, - approvalUpdate: { - approved_at: day(), - approved_by: currentUser.email, - }, - }, - }); - if (insertResponse.errors) { - notification.open({ - type: "error", - message: t("timetickets.errors.creating", { - message: JSON.stringify(insertResponse.errors), - }), - }); - } else { - notification.open({ - type: "success", - message: t("timetickets.successes.created"), - }); - } - } catch (error) { - notification.open({ - type: "error", - message: t("timetickets.errors.creating", { - message: error.message, - }), - }); - } finally { - setLoading(false); + const insertResponse = await client.mutate({ + mutation: INSERT_TIME_TICKET_AND_APPROVE, + variables: { + timeTicketInput: data.tt_approval_queue.map((tta) => ({ + ..._.omit(tta, ["id", "__typename"]), + ttapprovalqueueid: tta.id + })), + approvalIds: selectedTickets, + approvalUpdate: { + approved_at: day(), + approved_by: currentUser.email + } } + }); + if (insertResponse.errors) { + notification.open({ + type: "error", + message: t("timetickets.errors.creating", { + message: JSON.stringify(insertResponse.errors) + }) + }); + } else { + notification.open({ + type: "success", + message: t("timetickets.successes.created") + }); + } + } catch (error) { + notification.open({ + type: "error", + message: t("timetickets.errors.creating", { + message: error.message + }) + }); + } finally { + setLoading(false); + } - // if (!!completedCallback) completedCallback([]); - // if (!!loadingCallback) loadingCallback(false); - }; + // if (!!completedCallback) completedCallback([]); + // if (!!loadingCallback) loadingCallback(false); + }; - return ( - - ); + return ( + + ); } export default connect(mapStateToProps, null)(TtApproveButton); diff --git a/client/src/components/update-alert/update-alert.component.jsx b/client/src/components/update-alert/update-alert.component.jsx index c17e1c10a..909512c23 100644 --- a/client/src/components/update-alert/update-alert.component.jsx +++ b/client/src/components/update-alert/update-alert.component.jsx @@ -1,16 +1,16 @@ -import { AlertOutlined } from '@ant-design/icons'; -import { Alert, Button, Col, Row, Space } from 'antd'; -import i18n from 'i18next'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { connect } from 'react-redux'; -import { createStructuredSelector } from 'reselect'; -import { selectUpdateAvailable } from '../../redux/application/application.selectors'; -import { useRegisterSW } from 'virtual:pwa-register/react'; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { AlertOutlined } from "@ant-design/icons"; +import { Alert, Button, Col, Row, Space } from "antd"; +import i18n from "i18next"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectUpdateAvailable } from "../../redux/application/application.selectors"; +import { useRegisterSW } from "virtual:pwa-register/react"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - updateAvailable: selectUpdateAvailable, + updateAvailable: selectUpdateAvailable }); const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) @@ -22,60 +22,59 @@ export function UpdateAlert({ updateAvailable }) { const { offlineReady: [ - offlineReady, //setOfflineReady + offlineReady //setOfflineReady ], needRefresh: [ - needRefresh, //setNeedRefresh + needRefresh //setNeedRefresh ], - updateServiceWorker, + updateServiceWorker } = useRegisterSW({ onRegistered(r) { // eslint-disable-next-line prefer-template - console.log('SW Registered: ' + r); + console.log("SW Registered: " + r); r && setInterval(() => { r.update(); }, intervalMS); }, onRegisterError(error) { - console.log('SW registration error', error); - }, + console.log("SW registration error", error); + } }); - if (import.meta.env.DEV) - console.log(`SW Status => Refresh? ${needRefresh} - offlineReady? ${offlineReady}`); + if (import.meta.env.DEV) console.log(`SW Status => Refresh? ${needRefresh} - offlineReady? ${offlineReady}`); if (!needRefresh) return null; return ( } description={ - {t('general.messages.newversionmessage', { + {t("general.messages.newversionmessage", { app: InstanceRenderManager({ - imex: '$t(titles.imexonline)', - rome: '$t(titles.romeonline)', - promanager: '$t(titles.promanager)', - }), + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) })} diff --git a/client/src/components/user-request-pw-reset/user-request-pw-reset.styles.scss b/client/src/components/user-request-pw-reset/user-request-pw-reset.styles.scss index b446ab9b0..0c449251a 100644 --- a/client/src/components/user-request-pw-reset/user-request-pw-reset.styles.scss +++ b/client/src/components/user-request-pw-reset/user-request-pw-reset.styles.scss @@ -28,4 +28,3 @@ left: 50%; transform: translate(-50%, 0); } - \ No newline at end of file diff --git a/client/src/components/user-request-pw-reset/user-request-reset-pw.component.jsx b/client/src/components/user-request-pw-reset/user-request-reset-pw.component.jsx index a7eb6847a..ce888ed64 100644 --- a/client/src/components/user-request-pw-reset/user-request-reset-pw.component.jsx +++ b/client/src/components/user-request-pw-reset/user-request-reset-pw.component.jsx @@ -1,92 +1,95 @@ -import {Button, Form, Input, Result, Typography} from "antd"; +import { Button, Form, Input, Result, Typography } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; import ImEXOnlineLogo from "../../assets/logo192.png"; -import {sendPasswordReset, sendPasswordResetAgain,} from "../../redux/user/user.actions"; -import {selectPasswordReset} from "../../redux/user/user.selectors"; +import { sendPasswordReset, sendPasswordResetAgain } from "../../redux/user/user.actions"; +import { selectPasswordReset } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import "./user-request-pw-reset.styles.scss"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - passwordReset: selectPasswordReset, + passwordReset: selectPasswordReset }); const mapDispatchToProps = (dispatch) => ({ - sendPasswordReset: (email) => dispatch(sendPasswordReset(email)), - sendPasswordResetAgain: (email) => dispatch(sendPasswordResetAgain(email)), + sendPasswordReset: (email) => dispatch(sendPasswordReset(email)), + sendPasswordResetAgain: (email) => dispatch(sendPasswordResetAgain(email)) }); -export function UserRequestResetPw({ - passwordReset, - sendPasswordReset, - sendPasswordResetAgain, - }) { - const handleFinish = (values) => { - try { - sendPasswordReset(values.email); - } catch (error) { - console.log(error); - } - }; - - const {t} = useTranslation(); - if (passwordReset.success) - return ( - sendPasswordResetAgain(passwordReset.email)} - loading={passwordReset.loading} - > - {t("general.labels.sendagain")} - , - ]} - /> - ); +export function UserRequestResetPw({ passwordReset, sendPasswordReset, sendPasswordResetAgain }) { + const handleFinish = (values) => { + try { + sendPasswordReset(values.email); + } catch (error) { + console.log(error); + } + }; + const { t } = useTranslation(); + if (passwordReset.success) return ( -
    -
    - {InstanceRenderManager({imex: - {InstanceRenderManager({imex: t("titles.imexonline"), rome: t("titles.romeonline"), promanager:t("titles.promanager")})} -
    - {t("titles.resetpassword")} - -
    - - - - {passwordReset.error ? ( - - ) : null} - - -
    + sendPasswordResetAgain(passwordReset.email)} + loading={passwordReset.loading} + > + {t("general.labels.sendagain")} + + ]} + /> ); + + return ( +
    +
    + {InstanceRenderManager({ + + {InstanceRenderManager({ + imex: t("titles.imexonline"), + rome: t("titles.romeonline"), + promanager: t("titles.promanager") + })} + +
    + {t("titles.resetpassword")} + +
    + + + + {passwordReset.error ? : null} + + +
    + ); } export default connect(mapStateToProps, mapDispatchToProps)(UserRequestResetPw); diff --git a/client/src/components/user-validate-pw-reset/user-validate-pw-reset.component.jsx b/client/src/components/user-validate-pw-reset/user-validate-pw-reset.component.jsx index 7f1d47534..4ea899d01 100644 --- a/client/src/components/user-validate-pw-reset/user-validate-pw-reset.component.jsx +++ b/client/src/components/user-validate-pw-reset/user-validate-pw-reset.component.jsx @@ -1,142 +1,136 @@ -import {LockOutlined} from "@ant-design/icons"; -import {Button, Form, Input, Result, Typography} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import { LockOutlined } from "@ant-design/icons"; +import { Button, Form, Input, Result, Typography } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import RomeLogo from "../../assets/RomeOnlineBlue.png"; import ImEXOnlineLogo from "../../assets/logo192.png"; -import {auth} from "../../firebase/firebase.utils"; -import {checkActionCode} from "@firebase/auth"; -import {validatePasswordResetStart} from "../../redux/user/user.actions"; -import {selectPasswordReset} from "../../redux/user/user.selectors"; +import { auth } from "../../firebase/firebase.utils"; +import { checkActionCode } from "@firebase/auth"; +import { validatePasswordResetStart } from "../../redux/user/user.actions"; +import { selectPasswordReset } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import "./user-validate-pw-reset.styles.scss"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - passwordReset: selectPasswordReset, + passwordReset: selectPasswordReset }); const mapDispatchToProps = (dispatch) => ({ - validatePasswordReset: (emailAndPin) => - dispatch(validatePasswordResetStart(emailAndPin)), + validatePasswordReset: (emailAndPin) => dispatch(validatePasswordResetStart(emailAndPin)) }); -export function UserValidatePwReset({ - passwordReset, - validatePasswordReset, - oobCode, - }) { - const {t} = useTranslation(); - const [codeValid, setCodeValid] = useState({loading: true}); - const handleFinish = (values) => { - validatePasswordReset({code: oobCode, password: values.password}); - }; +export function UserValidatePwReset({ passwordReset, validatePasswordReset, oobCode }) { + const { t } = useTranslation(); + const [codeValid, setCodeValid] = useState({ loading: true }); + const handleFinish = (values) => { + validatePasswordReset({ code: oobCode, password: values.password }); + }; - useEffect(() => { - async function checkCodeValid() { - try { - const codeValid = await checkActionCode(auth, oobCode); + useEffect(() => { + async function checkCodeValid() { + try { + const codeValid = await checkActionCode(auth, oobCode); - setCodeValid({loading: false, ...codeValid}); - } catch (error) { - setCodeValid({loading: false, ...error}); - } - } + setCodeValid({ loading: false, ...codeValid }); + } catch (error) { + setCodeValid({ loading: false, ...error }); + } + } - checkCodeValid(); - }, [oobCode]); + checkCodeValid(); + }, [oobCode]); - if (codeValid.loading) return ; - - if (codeValid.code) - return ( -
    - -
    - ); - - if (passwordReset.success) - return ( - - - , - ]} - /> - ); + if (codeValid.loading) return ; + if (codeValid.code) return ( -
    -
    - {InstanceRenderManager({imex:t("titles.imexonline"), - - {InstanceRenderManager({imex: t("titles.imexonline"), rome: t("titles.romeonline"), promanager:t("titles.promanager")})} -
    - - {t("titles.resetpasswordvalidate")} - - -
    - - } - type="password" - placeholder={t("general.labels.password")} - /> - - ({ - validator(rule, value) { - if (!value || getFieldValue("password") === value) { - return Promise.resolve(); - } - return Promise.reject(t("general.labels.passwordsdonotmatch")); - }, - }), - ]} - > - } - type="password" - placeholder={t("general.labels.password")} - /> - - {passwordReset.error ? ( - - ) : null} - - -
    +
    + +
    ); + + if (passwordReset.success) + return ( + + + + ]} + /> + ); + + return ( +
    +
    + {InstanceRenderManager({ + + + {InstanceRenderManager({ + imex: t("titles.imexonline"), + rome: t("titles.romeonline"), + promanager: t("titles.promanager") + })} + +
    + {t("titles.resetpasswordvalidate")} + +
    + + } type="password" placeholder={t("general.labels.password")} /> + + ({ + validator(rule, value) { + if (!value || getFieldValue("password") === value) { + return Promise.resolve(); + } + return Promise.reject(t("general.labels.passwordsdonotmatch")); + } + }) + ]} + > + } type="password" placeholder={t("general.labels.password")} /> + + {passwordReset.error ? : null} + + +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(UserValidatePwReset); +export default connect(mapStateToProps, mapDispatchToProps)(UserValidatePwReset); diff --git a/client/src/components/user-validate-pw-reset/user-validate-pw-reset.styles.scss b/client/src/components/user-validate-pw-reset/user-validate-pw-reset.styles.scss index 540b1e453..c86d6b2c5 100644 --- a/client/src/components/user-validate-pw-reset/user-validate-pw-reset.styles.scss +++ b/client/src/components/user-validate-pw-reset/user-validate-pw-reset.styles.scss @@ -28,4 +28,3 @@ left: 50%; transform: translate(-50%, 0); } - \ No newline at end of file diff --git a/client/src/components/vehicle-detail-form/vehicle-detail-form.component.jsx b/client/src/components/vehicle-detail-form/vehicle-detail-form.component.jsx index 4ebc5484b..c81def6a0 100644 --- a/client/src/components/vehicle-detail-form/vehicle-detail-form.component.jsx +++ b/client/src/components/vehicle-detail-form/vehicle-detail-form.component.jsx @@ -1,147 +1,141 @@ -import {Form, Input} from "antd"; +import { Form, Input } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; -export default function VehicleDetailFormComponent({form, loading}) { - const {t} = useTranslation(); +export default function VehicleDetailFormComponent({ form, loading }) { + const { t } = useTranslation(); - return ( -
    - + return ( +
    + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + - - - + + + - - - - - - + + + + + + - - - - - - - - - - -
    - ); + + + + + + + + + + +
    + ); } diff --git a/client/src/components/vehicle-detail-form/vehicle-detail-form.container.jsx b/client/src/components/vehicle-detail-form/vehicle-detail-form.container.jsx index 2b48e844d..08d9a256e 100644 --- a/client/src/components/vehicle-detail-form/vehicle-detail-form.container.jsx +++ b/client/src/components/vehicle-detail-form/vehicle-detail-form.container.jsx @@ -1,112 +1,102 @@ -import React, {useState} from "react"; -import {Button, Form, notification, Popconfirm} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; -import {useMutation} from "@apollo/client"; +import React, { useState } from "react"; +import { Button, Form, notification, Popconfirm } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; +import { useMutation } from "@apollo/client"; import VehicleDetailFormComponent from "./vehicle-detail-form.component"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import dayjs from "../../utils/day"; -import {DELETE_VEHICLE, UPDATE_VEHICLE} from "../../graphql/vehicles.queries"; -import {useNavigate} from "react-router-dom"; +import { DELETE_VEHICLE, UPDATE_VEHICLE } from "../../graphql/vehicles.queries"; +import { useNavigate } from "react-router-dom"; -function VehicleDetailFormContainer({vehicle, refetch}) { - const {t} = useTranslation(); - const [updateVehicle] = useMutation(UPDATE_VEHICLE); - const [deleteVehicle] = useMutation(DELETE_VEHICLE); - const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - const history = useNavigate(); +function VehicleDetailFormContainer({ vehicle, refetch }) { + const { t } = useTranslation(); + const [updateVehicle] = useMutation(UPDATE_VEHICLE); + const [deleteVehicle] = useMutation(DELETE_VEHICLE); + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const history = useNavigate(); - const handleDelete = async () => { - setLoading(true); - const result = await deleteVehicle({ - variables: {id: vehicle.id}, - }); - console.log(result); - if (result.errors) { - notification["error"]({ - message: t("vehicles.errors.deleting", { - error: JSON.stringify(result.errors), - }), - }); - setLoading(false); - } else { - notification["success"]({ - message: t("vehicles.successes.delete"), - }); - setLoading(false); - history(`/manage/vehicles`); - } - }; + const handleDelete = async () => { + setLoading(true); + const result = await deleteVehicle({ + variables: { id: vehicle.id } + }); + console.log(result); + if (result.errors) { + notification["error"]({ + message: t("vehicles.errors.deleting", { + error: JSON.stringify(result.errors) + }) + }); + setLoading(false); + } else { + notification["success"]({ + message: t("vehicles.successes.delete") + }); + setLoading(false); + history(`/manage/vehicles`); + } + }; - const handleFinish = async (values) => { - setLoading(true); - const result = await updateVehicle({ - variables: {vehId: vehicle.id, vehicle: values}, - }); + const handleFinish = async (values) => { + setLoading(true); + const result = await updateVehicle({ + variables: { vehId: vehicle.id, vehicle: values } + }); - if (!!result.errors) { - notification["error"]({ - message: t("vehicles.errors.saving", { - message: JSON.stringify(result.errors), - }), - }); - } + if (!!result.errors) { + notification["error"]({ + message: t("vehicles.errors.saving", { + message: JSON.stringify(result.errors) + }) + }); + } - notification["success"]({ - message: t("vehicles.successes.save"), - }); + notification["success"]({ + message: t("vehicles.successes.save") + }); - if (refetch) await refetch(); - form.resetFields(); - form.resetFields(); - setLoading(false); - }; + if (refetch) await refetch(); + form.resetFields(); + form.resetFields(); + setLoading(false); + }; - return ( - <> - - - , - , - ]} - /> -
    - - - - ); + return ( + <> + + + , + + ]} + /> +
    + + + + ); } export default VehicleDetailFormContainer; diff --git a/client/src/components/vehicle-detail-jobs/vehicle-detail-jobs.component.jsx b/client/src/components/vehicle-detail-jobs/vehicle-detail-jobs.component.jsx index 6fe616d09..e7a974565 100644 --- a/client/src/components/vehicle-detail-jobs/vehicle-detail-jobs.component.jsx +++ b/client/src/components/vehicle-detail-jobs/vehicle-detail-jobs.component.jsx @@ -1,140 +1,125 @@ -import {Card, Table} from "antd"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { Card, Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; import { alphaSort, statusSort } from "../../utils/sorters"; -import OwnerNameDisplay, { - OwnerNameDisplayFunction, -} from "../owner-name-display/owner-name-display.component"; +import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -export function VehicleDetailJobsComponent({vehicle, bodyshop}) { - const {t} = useTranslation(); - const [selectedJobs, setSelectedJobs] = useState([]); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); +export function VehicleDetailJobsComponent({ vehicle, bodyshop }) { + const { t } = useTranslation(); + const [selectedJobs, setSelectedJobs] = useState([]); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - ellipsis: true, - render: (text, record) => ( - - {record.ro_number || t("general.labels.na")} - - ), - sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - sorter: (a, b) => - alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, - render: (text, record) => ( - - - - ), - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", - sorter: (a, b) => - statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - filters: bodyshop.md_ro_statuses.statuses.map((status) => ({ - text: status, - value: status, - })), - onFilter: (value, record) => value.includes(record.status), - }, + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + ellipsis: true, + render: (text, record) => ( + {record.ro_number || t("general.labels.na")} + ), + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + render: (text, record) => ( + + + + ) + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), + sortOrder: state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + filters: bodyshop.md_ro_statuses.statuses.map((status) => ({ + text: status, + value: status + })), + onFilter: (value, record) => value.includes(record.status) + }, - { - title: t("jobs.fields.clm_total"), - dataIndex: "clm_total", - key: "clm_total", - render: (text, record) => ( - {record.clm_total} - ), - sorter: (a, b) => a.clm_total - b.clm_total, - sortOrder: - state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, - }, - ]; + { + title: t("jobs.fields.clm_total"), + dataIndex: "clm_total", + key: "clm_total", + render: (text, record) => {record.clm_total}, + sorter: (a, b) => a.clm_total - b.clm_total, + sortOrder: state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order + } + ]; - return ( - - } - > -
    { - setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []); - }, - onSelectAll: (selected, selectedRows, changeRows) => { - setSelectedJobs( - selectedRows - ? selectedRows - .filter((i) => - bodyshop.md_ro_statuses.active_statuses.includes(i.status) - ) - .map((i) => i.id) - : [] - ); - }, - selectedRowKeys: selectedJobs, - getCheckboxProps: (record) => ({ - disabled: bodyshop.md_ro_statuses.active_statuses - ? !bodyshop.md_ro_statuses.active_statuses.includes(record.status) - : true, - }), - }} - /> - - ); + return ( + + } + > +
    { + setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []); + }, + onSelectAll: (selected, selectedRows, changeRows) => { + setSelectedJobs( + selectedRows + ? selectedRows + .filter((i) => bodyshop.md_ro_statuses.active_statuses.includes(i.status)) + .map((i) => i.id) + : [] + ); + }, + selectedRowKeys: selectedJobs, + getCheckboxProps: (record) => ({ + disabled: bodyshop.md_ro_statuses.active_statuses + ? !bodyshop.md_ro_statuses.active_statuses.includes(record.status) + : true + }) + }} + /> + + ); } export default connect(mapStateToProps, null)(VehicleDetailJobsComponent); diff --git a/client/src/components/vehicle-detail-update-jobs/vehicle-detail-update-jobs.component.jsx b/client/src/components/vehicle-detail-update-jobs/vehicle-detail-update-jobs.component.jsx index 5fd4e5011..d2aa52d6e 100644 --- a/client/src/components/vehicle-detail-update-jobs/vehicle-detail-update-jobs.component.jsx +++ b/client/src/components/vehicle-detail-update-jobs/vehicle-detail-update-jobs.component.jsx @@ -1,47 +1,43 @@ import React from "react"; -import {Button, notification} from "antd"; -import {useTranslation} from "react-i18next"; -import {useMutation} from "@apollo/client"; -import {UPDATE_JOBS} from "../../graphql/jobs.queries"; -import {logImEXEvent} from "../../firebase/firebase.utils"; +import { Button, notification } from "antd"; +import { useTranslation } from "react-i18next"; +import { useMutation } from "@apollo/client"; +import { UPDATE_JOBS } from "../../graphql/jobs.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; -export default function VehicleDetailUpdateJobsComponent({ - vehicle, - selectedJobs, - disabled, - }) { - const {t} = useTranslation(); - const [updateJobs] = useMutation(UPDATE_JOBS); - const handlecClick = (e) => { - logImEXEvent("vehicle_update_jobs"); +export default function VehicleDetailUpdateJobsComponent({ vehicle, selectedJobs, disabled }) { + const { t } = useTranslation(); + const [updateJobs] = useMutation(UPDATE_JOBS); + const handlecClick = (e) => { + logImEXEvent("vehicle_update_jobs"); - updateJobs({ - variables: { - jobIds: selectedJobs, - fields: { - plate_no: vehicle["plate_no"], - plate_st: vehicle["plate_st"], - v_vin: vehicle["v_vin"], - v_model_yr: vehicle["v_model_yr"], - v_model_desc: vehicle["v_model_desc"], - v_make_desc: vehicle["v_make_desc"], - v_color: vehicle["v_color"], - }, - }, - }) - .then((response) => { - notification["success"]({message: t("jobs.successes.updated")}); - }) - .catch((error) => { - notification["error"]({ - message: t("jobs.errors.updating", {error: JSON.stringify(error)}), - }); - }); - }; + updateJobs({ + variables: { + jobIds: selectedJobs, + fields: { + plate_no: vehicle["plate_no"], + plate_st: vehicle["plate_st"], + v_vin: vehicle["v_vin"], + v_model_yr: vehicle["v_model_yr"], + v_model_desc: vehicle["v_model_desc"], + v_make_desc: vehicle["v_make_desc"], + v_color: vehicle["v_color"] + } + } + }) + .then((response) => { + notification["success"]({ message: t("jobs.successes.updated") }); + }) + .catch((error) => { + notification["error"]({ + message: t("jobs.errors.updating", { error: JSON.stringify(error) }) + }); + }); + }; - return ( - - ); + return ( + + ); } diff --git a/client/src/components/vehicle-search-select/vehicle-search-select.component.jsx b/client/src/components/vehicle-search-select/vehicle-search-select.component.jsx index fa3266100..3dc2d4555 100644 --- a/client/src/components/vehicle-search-select/vehicle-search-select.component.jsx +++ b/client/src/components/vehicle-search-select/vehicle-search-select.component.jsx @@ -1,95 +1,89 @@ -import {LoadingOutlined} from "@ant-design/icons"; -import {useLazyQuery} from "@apollo/client"; -import {Empty, Select} from "antd"; +import { LoadingOutlined } from "@ant-design/icons"; +import { useLazyQuery } from "@apollo/client"; +import { Empty, Select } from "antd"; import _ from "lodash"; -import React, {forwardRef, useEffect, useState} from "react"; +import React, { forwardRef, useEffect, useState } from "react"; import { - SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE, - SEARCH_VEHICLES_FOR_AUTOCOMPLETE, + SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE, + SEARCH_VEHICLES_FOR_AUTOCOMPLETE } from "../../graphql/vehicles.queries"; import AlertComponent from "../alert/alert.component"; -const {Option} = Select; +const { Option } = Select; -const VehicleSearchSelect = ({value, onChange, onBlur, disabled}, ref) => { - const [callSearch, {loading, error, data}] = useLazyQuery( - SEARCH_VEHICLES_FOR_AUTOCOMPLETE - ); +const VehicleSearchSelect = ({ value, onChange, onBlur, disabled }, ref) => { + const [callSearch, { loading, error, data }] = useLazyQuery(SEARCH_VEHICLES_FOR_AUTOCOMPLETE); - const [callIdSearch, {loading: idLoading, error: idError, data: idData}] = - useLazyQuery(SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE); + const [callIdSearch, { loading: idLoading, error: idError, data: idData }] = useLazyQuery( + SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE + ); - const executeSearch = (v) => { - if (v && v.variables?.search !== "" && v.variables.search.length >= 2) - callSearch(v); - }; - const debouncedExecuteSearch = _.debounce(executeSearch, 500); + const executeSearch = (v) => { + if (v && v.variables?.search !== "" && v.variables.search.length >= 2) callSearch(v); + }; + const debouncedExecuteSearch = _.debounce(executeSearch, 500); - const handleSearch = (value) => { - debouncedExecuteSearch({variables: {search: value}}); - }; + const handleSearch = (value) => { + debouncedExecuteSearch({ variables: { search: value } }); + }; - const [option, setOption] = useState(value); + const [option, setOption] = useState(value); - useEffect(() => { - if (value === option && value) { - callIdSearch({variables: {id: value}}); - } - }, [value, option, callIdSearch]); + useEffect(() => { + if (value === option && value) { + callIdSearch({ variables: { id: value } }); + } + }, [value, option, callIdSearch]); - // useEffect(() => { - // if (value !== option && onChange) { - // onChange(option); - // } - // }, [value, option, onChange]); + // useEffect(() => { + // if (value !== option && onChange) { + // onChange(option); + // } + // }, [value, option, onChange]); - const handleSelect = (value) => { - setOption(value); - if (value !== option && onChange) { - onChange(value); - } - }; + const handleSelect = (value) => { + setOption(value); + if (value !== option && onChange) { + onChange(value); + } + }; - const theOptions = [ - ...(idData && idData.vehicles_by_pk ? [idData.vehicles_by_pk] : []), - ...(data && data.search_vehicles ? data.search_vehicles : []), - ]; + const theOptions = [ + ...(idData && idData.vehicles_by_pk ? [idData.vehicles_by_pk] : []), + ...(data && data.search_vehicles ? data.search_vehicles : []) + ]; - return ( -
    - - {idLoading || loading ? : null} - {error ? : null} - {idError ? ( - - ) : null} -
    - ); + return ( +
    + + {idLoading || loading ? : null} + {error ? : null} + {idError ? : null} +
    + ); }; export default forwardRef(VehicleSearchSelect); diff --git a/client/src/components/vehicle-tag-popover/vehicle-tag-popover.component.jsx b/client/src/components/vehicle-tag-popover/vehicle-tag-popover.component.jsx index d045295ea..c0fbb5848 100644 --- a/client/src/components/vehicle-tag-popover/vehicle-tag-popover.component.jsx +++ b/client/src/components/vehicle-tag-popover/vehicle-tag-popover.component.jsx @@ -1,86 +1,80 @@ -import {Button, Col, Descriptions, Popover, Row, Tag} from "antd"; +import { Button, Col, Descriptions, Popover, Row, Tag } from "antd"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {Link} from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; -export default function VehicleTagPopoverComponent({job}) { - const {t} = useTranslation(); - if (!job.vehicle) return null; +export default function VehicleTagPopoverComponent({ job }) { + const { t } = useTranslation(); + if (!job.vehicle) return null; - const content = ( -
    - -
    - - - {`${job.v_model_yr || t("general.labels.na")} ${ - job.v_color || "" - } + const content = ( +
    + +
    + + + {`${job.v_model_yr || t("general.labels.na")} ${job.v_color || ""} ${job.v_make_desc || t("general.labels.na")} ${job.v_model_desc || t("general.labels.na")}`} - + - - {`${job.plate_no || t("general.labels.na")}`} - - - {`${job.plate_st || t("general.labels.na")}`} - - - - {`${job.v_vin || t("general.labels.na")}`} - - - - - - - - {`${job.vehicle.v_model_yr || t("general.labels.na")} + + {`${job.plate_no || t("general.labels.na")}`} + + + {`${job.plate_st || t("general.labels.na")}`} + + + {`${job.v_vin || t("general.labels.na")}`} + + + + + + + {`${job.vehicle.v_model_yr || t("general.labels.na")} ${job.vehicle.v_make_desc || t("general.labels.na")} ${job.vehicle.v_model_desc || t("general.labels.na")}`} - + - - {`${job.vehicle.plate_no || t("general.labels.na")}`} - - - {`${job.vehicle.plate_st || t("general.labels.na")}`} - - - {`${ - job.vehicle.v_vin || t("general.labels.na") - }`} - - - - - - - - - ); + + {`${job.vehicle.plate_no || t("general.labels.na")}`} + + + {`${job.vehicle.plate_st || t("general.labels.na")}`} + + + {`${job.vehicle.v_vin || t("general.labels.na")}`} + + + + + + + + + ); - return ( - - - {job.vehicleid ? ( - - {`${job.v_model_yr || ""} ${job.v_color || ""} + return ( + + + {job.vehicleid ? ( + + {`${job.v_model_yr || ""} ${job.v_color || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""} | ${job.plate_no || ""}`} - - ) : ( - + + ) : ( + {`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""} | ${job.plate_no || ""}`} - )} - - - ); + )} + + + ); } diff --git a/client/src/components/vehicle-vin-display/vehicle-vin-display.component.jsx b/client/src/components/vehicle-vin-display/vehicle-vin-display.component.jsx index d216e2a53..141ed899c 100644 --- a/client/src/components/vehicle-vin-display/vehicle-vin-display.component.jsx +++ b/client/src/components/vehicle-vin-display/vehicle-vin-display.component.jsx @@ -1,18 +1,18 @@ import React from "react"; -export default function VehicleVinDisplay({children}) { - if (!children) return null; +export default function VehicleVinDisplay({ children }) { + if (!children) return null; - if (typeof children !== "string" || children.length !== 17) return children; - const vin = children.trim(); + if (typeof children !== "string" || children.length !== 17) return children; + const vin = children.trim(); - const first = vin.substring(0, 9); - const second = vin.substring(9, 17); + const first = vin.substring(0, 9); + const second = vin.substring(9, 17); - return ( - <> - {first} - {second} - - ); + return ( + <> + {first} + {second} + + ); } diff --git a/client/src/components/vehicles-list/vehicles-list.component.jsx b/client/src/components/vehicles-list/vehicles-list.component.jsx index a5f8bf982..f2e4a90dd 100644 --- a/client/src/components/vehicles-list/vehicles-list.component.jsx +++ b/client/src/components/vehicles-list/vehicles-list.component.jsx @@ -1,128 +1,121 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Input, Space, Table, Typography} from "antd"; +import { SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Input, Space, Table, Typography } from "antd"; import queryString from "query-string"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {Link, useLocation, useNavigate} from "react-router-dom"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link, useLocation, useNavigate } from "react-router-dom"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; -import {pageLimit} from "../../utils/config"; -import { alphaSort } from '../../utils/sorters'; +import { pageLimit } from "../../utils/config"; +import { alphaSort } from "../../utils/sorters"; -export default function VehiclesListComponent({ - loading, - vehicles, - total, - refetch, - }) { - const search = queryString.parse(useLocation().search); - const { - page, - //sortcolumn, sortorder, - } = search; - const history = useNavigate(); +export default function VehiclesListComponent({ loading, vehicles, total, refetch }) { + const search = queryString.parse(useLocation().search); + const { + page + //sortcolumn, sortorder, + } = search; + const history = useNavigate(); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); - const {t} = useTranslation(); + const { t } = useTranslation(); - const columns = [ - { - title: t("vehicles.fields.v_vin"), - dataIndex: "v_vin", - key: "v_vin", - sorter: (a, b) => alphaSort(a.v_vin, b.v_vin), - sortOrder: state.sortedInfo.columnKey === "v_vin" && state.sortedInfo.order, - render: (text, record) => ( - - {record.v_vin || "N/A"} - - ), - }, - { - title: t("vehicles.fields.description"), - dataIndex: "description", - key: "description", - render: (text, record) => { - return ( - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - } ${record.v_color || ""}`} - ); - }, - }, - { - title: t("vehicles.fields.plate_no"), - dataIndex: "plate_no", - key: "plate_no", - sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), - sortOrder: state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, - render: (text, record) => { - return ( - {`${record.plate_st || ""} | ${record.plate_no || ""}`} - ); - }, - }, - ]; + const columns = [ + { + title: t("vehicles.fields.v_vin"), + dataIndex: "v_vin", + key: "v_vin", + sorter: (a, b) => alphaSort(a.v_vin, b.v_vin), + sortOrder: state.sortedInfo.columnKey === "v_vin" && state.sortedInfo.order, + render: (text, record) => ( + + {record.v_vin || "N/A"} + + ) + }, + { + title: t("vehicles.fields.description"), + dataIndex: "description", + key: "description", + render: (text, record) => { + return ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ + record.v_model_desc || "" + } ${record.v_color || ""}`} + ); + } + }, + { + title: t("vehicles.fields.plate_no"), + dataIndex: "plate_no", + key: "plate_no", + sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), + sortOrder: state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, + render: (text, record) => { + return {`${record.plate_st || ""} | ${record.plate_no || ""}`}; + } + } + ]; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - search.page = pagination.current; - search.sortcolumn = sorter.columnKey; - search.sortorder = sorter.order; - history({search: queryString.stringify(search)}); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + search.page = pagination.current; + search.sortcolumn = sorter.columnKey; + search.sortorder = sorter.order; + history({ search: queryString.stringify(search) }); + }; - return ( - - {search.search && ( - <> - - {t("general.labels.searchresults", {search: search.search})} - - - - )} - - { - search.search = value; - history({search: queryString.stringify(search)}); - }} - enterButton - /> - - } - > -
    + {search.search && ( + <> + + {t("general.labels.searchresults", { search: search.search })} + + + + )} + + { + search.search = value; + history({ search: queryString.stringify(search) }); + }} + enterButton + /> + + } + > +
    + + ); } diff --git a/client/src/components/vehicles-list/vehicles-list.container.jsx b/client/src/components/vehicles-list/vehicles-list.container.jsx index 4d25f369d..504270153 100644 --- a/client/src/components/vehicles-list/vehicles-list.container.jsx +++ b/client/src/components/vehicles-list/vehicles-list.container.jsx @@ -1,45 +1,38 @@ import React from "react"; import VehiclesListComponent from "./vehicles-list.component"; -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import AlertComponent from "../alert/alert.component"; -import {QUERY_ALL_VEHICLES_PAGINATED} from "../../graphql/vehicles.queries"; +import { QUERY_ALL_VEHICLES_PAGINATED } from "../../graphql/vehicles.queries"; import queryString from "query-string"; -import {useLocation} from "react-router-dom"; -import {pageLimit} from "../../utils/config"; +import { useLocation } from "react-router-dom"; +import { pageLimit } from "../../utils/config"; export default function VehiclesListContainer() { - const searchParams = queryString.parse(useLocation().search); - const {page, sortcolumn, sortorder, search} = searchParams; + const searchParams = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder, search } = searchParams; - const {loading, error, data, refetch} = useQuery( - QUERY_ALL_VEHICLES_PAGINATED, + const { loading, error, data, refetch } = useQuery(QUERY_ALL_VEHICLES_PAGINATED, { + variables: { + search: search || "", + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, + order: [ { - variables: { - search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, - order: [ - { - [sortcolumn || "created_at"]: sortorder - ? sortorder === "descend" - ? "desc" - : "asc" - : "desc", - }, - ], - }, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", + [sortcolumn || "created_at"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc" } - ); + ] + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - if (error) return ; - return ( - - ); + if (error) return ; + return ( + + ); } diff --git a/client/src/components/vendor-search-select/vendor-search-select.component.jsx b/client/src/components/vendor-search-select/vendor-search-select.component.jsx index dbc588137..8e9d6b5c2 100644 --- a/client/src/components/vendor-search-select/vendor-search-select.component.jsx +++ b/client/src/components/vendor-search-select/vendor-search-select.component.jsx @@ -1,16 +1,13 @@ -import { HeartOutlined } from '@ant-design/icons'; -import { Select, Space, Tag } from 'antd'; -import React, { forwardRef, useEffect, useState } from 'react'; -import PhoneNumberFormatter from '../../utils/PhoneFormatter'; +import { HeartOutlined } from "@ant-design/icons"; +import { Select, Space, Tag } from "antd"; +import React, { forwardRef, useEffect, useState } from "react"; +import PhoneNumberFormatter from "../../utils/PhoneFormatter"; const { Option } = Select; //To be used as a form element only. -const VendorSearchSelect = ( - { value, onChange, options, onSelect, disabled, preferredMake, showPhone }, - ref -) => { +const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, preferredMake, showPhone }, ref) => { const [option, setOption] = useState(value); useEffect(() => { @@ -21,10 +18,7 @@ const VendorSearchSelect = ( const favorites = preferredMake && options - ? options.filter( - (o) => - o.favorite.filter((f) => f.toLowerCase() === preferredMake.toLowerCase()).length > 0 - ) + ? options.filter((o) => o.favorite.filter((f) => f.toLowerCase() === preferredMake.toLowerCase()).length > 0) : []; return ( @@ -33,13 +27,13 @@ const VendorSearchSelect = ( showSearch value={option} style={{ - width: '100%', + width: "100%" }} labelRender={({ label, value, ...rest }) => { if (!value || !options) return label; const discount = options?.find((o) => o.id === value)?.discount; return ( -
    +
    {label}
    {discount && discount !== 0 ? {`${discount * 100}%`} : null} @@ -51,19 +45,17 @@ const VendorSearchSelect = ( optionFilterProp="name" onSelect={onSelect} disabled={disabled || false} - optionLabelProp={'name'} + optionLabelProp={"name"} > {favorites ? favorites.map((o) => ( @@ -72,14 +64,12 @@ const VendorSearchSelect = ( {options ? options.map((o) => (
    { + return { + onClick: (event) => { + handleOnRowClick(record); } - > -
    { - return { - onClick: (event) => { - handleOnRowClick(record); - }, - }; - }} - /> - - ); + }; + }} + /> + + ); } diff --git a/client/src/components/vendors-list/vendors-list.container.jsx b/client/src/components/vendors-list/vendors-list.container.jsx index b8e466b3b..60f7aa1ba 100644 --- a/client/src/components/vendors-list/vendors-list.container.jsx +++ b/client/src/components/vendors-list/vendors-list.container.jsx @@ -1,42 +1,42 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import queryString from "query-string"; import React from "react"; -import {useLocation, useNavigate} from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; import AlertComponent from "../../components/alert/alert.component"; -import {QUERY_ALL_VENDORS} from "../../graphql/vendors.queries"; +import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries"; import VendorsListComponent from "./vendors-list.component"; export default function VendorsListContainer() { - const {loading, error, data, refetch} = useQuery(QUERY_ALL_VENDORS, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); - const search = queryString.parse(useLocation().search); - const history = useNavigate(); + const { loading, error, data, refetch } = useQuery(QUERY_ALL_VENDORS, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const search = queryString.parse(useLocation().search); + const history = useNavigate(); - const handleNewVendor = () => { - search.selectedvendor = "new"; - history({search: queryString.stringify(search)}); - }; + const handleNewVendor = () => { + search.selectedvendor = "new"; + history({ search: queryString.stringify(search) }); + }; - const handleOnRowClick = (record) => { - if (record) { - search.selectedvendor = record.id; - history({search: queryString.stringify(search)}); - } else { - delete search.selectedvendor; - history({search: queryString.stringify(search)}); - } - }; + const handleOnRowClick = (record) => { + if (record) { + search.selectedvendor = record.id; + history({ search: queryString.stringify(search) }); + } else { + delete search.selectedvendor; + history({ search: queryString.stringify(search) }); + } + }; - if (error) return ; - return ( - - ); + if (error) return ; + return ( + + ); } diff --git a/client/src/components/vendors-phonebook-add/vendors-phonebook-add.component.jsx b/client/src/components/vendors-phonebook-add/vendors-phonebook-add.component.jsx index 71cc9c2b6..82ead8fff 100644 --- a/client/src/components/vendors-phonebook-add/vendors-phonebook-add.component.jsx +++ b/client/src/components/vendors-phonebook-add/vendors-phonebook-add.component.jsx @@ -1,83 +1,80 @@ -import {Button, notification} from "antd"; -import React, {useState} from "react"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {useTranslation} from "react-i18next"; -import {useMutation} from "@apollo/client"; -import {INSERT_NEW_PHONEBOOK} from "../../graphql/phonebook.queries"; +import { Button, notification } from "antd"; +import React, { useState } from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; +import { useMutation } from "@apollo/client"; +import { INSERT_NEW_PHONEBOOK } from "../../graphql/phonebook.queries"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(VendorsPhonebookAdd); +export default connect(mapStateToProps, mapDispatchToProps)(VendorsPhonebookAdd); -export function VendorsPhonebookAdd({form, bodyshop, disabled}) { - const [loading, setLoading] = useState(false); - const [insertPhonebook] = useMutation(INSERT_NEW_PHONEBOOK); - const {t} = useTranslation(); +export function VendorsPhonebookAdd({ form, bodyshop, disabled }) { + const [loading, setLoading] = useState(false); + const [insertPhonebook] = useMutation(INSERT_NEW_PHONEBOOK); + const { t } = useTranslation(); - const handleAdd = async () => { - setLoading(true); + const handleAdd = async () => { + setLoading(true); - const VendorValues = form.getFieldsValue([ - "name", - "email", - "phone", - "street1", - "street2", - "city", - "state", - "zip", - "country", - ]); + const VendorValues = form.getFieldsValue([ + "name", + "email", + "phone", + "street1", + "street2", + "city", + "state", + "zip", + "country" + ]); - const result = await insertPhonebook({ - variables: { - phonebook_entry: [ - { - //The phonebook obj, - bodyshopid: bodyshop.id, - company: VendorValues.name, - category: t("phonebook.labels.vendorcategory"), - address1: VendorValues.street1, - address2: VendorValues.street2, - city: VendorValues.city, - state: VendorValues.state, - zip: VendorValues.zip, - country: VendorValues.country, - phone1: VendorValues.phone, - email: VendorValues.email, - }, - ], - }, - }); + const result = await insertPhonebook({ + variables: { + phonebook_entry: [ + { + //The phonebook obj, + bodyshopid: bodyshop.id, + company: VendorValues.name, + category: t("phonebook.labels.vendorcategory"), + address1: VendorValues.street1, + address2: VendorValues.street2, + city: VendorValues.city, + state: VendorValues.state, + zip: VendorValues.zip, + country: VendorValues.country, + phone1: VendorValues.phone, + email: VendorValues.email + } + ] + } + }); - if (result.errors) { - notification.open({ - type: "error", - message: t("phonebook.errors.adding", { - error: JSON.stringify(result.errors), - }), - }); - } else { - notification.open({ - type: "success", - message: t("phonebook.successes.added"), - }); - } - setLoading(false); - }; + if (result.errors) { + notification.open({ + type: "error", + message: t("phonebook.errors.adding", { + error: JSON.stringify(result.errors) + }) + }); + } else { + notification.open({ + type: "success", + message: t("phonebook.successes.added") + }); + } + setLoading(false); + }; - return ( - - ); + return ( + + ); } diff --git a/client/src/firebase/firebase.utils.js b/client/src/firebase/firebase.utils.js index f03e0de1d..7e3b47434 100644 --- a/client/src/firebase/firebase.utils.js +++ b/client/src/firebase/firebase.utils.js @@ -1,9 +1,9 @@ -import {getAnalytics, logEvent} from "firebase/analytics"; -import {initializeApp} from "firebase/app"; -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 { getAnalytics, logEvent } from "firebase/analytics"; +import { initializeApp } from "firebase/app"; +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"; import { checkBeta } from "../utils/handleBeta"; @@ -16,42 +16,42 @@ export const analytics = getAnalytics(); //export default firebase; export const getCurrentUser = () => { - return new Promise((resolve, reject) => { - const unsubscribe = auth.onAuthStateChanged((userAuth) => { - unsubscribe(); - resolve(userAuth); - }, reject); - }); + return new Promise((resolve, reject) => { + const unsubscribe = auth.onAuthStateChanged((userAuth) => { + unsubscribe(); + resolve(userAuth); + }, reject); + }); }; export const updateCurrentUser = (userDetails) => { - return new Promise((resolve, reject) => { - const unsubscribe = auth.onAuthStateChanged((userAuth) => { - updateProfile(userAuth, userDetails).then((r) => { - unsubscribe(); - resolve(userAuth); - }); - }, reject); - }); + return new Promise((resolve, reject) => { + const unsubscribe = auth.onAuthStateChanged((userAuth) => { + updateProfile(userAuth, userDetails).then((r) => { + unsubscribe(); + resolve(userAuth); + }); + }, reject); + }); }; export const updateCurrentPassword = async (password) => { - const currentUser = await getCurrentUser(); + const currentUser = await getCurrentUser(); - return updatePassword(currentUser, password); + return updatePassword(currentUser, password); }; let messaging; try { - messaging = getMessaging(); + messaging = getMessaging(); } catch (error) { - console.log(error); + console.log(error); } -export {messaging}; +export { messaging }; export const requestForToken = () => { return getToken(messaging, { - vapidKey: import.meta.env.VITE_APP_FIREBASE_PUBLIC_VAPID_KEY, + vapidKey: import.meta.env.VITE_APP_FIREBASE_PUBLIC_VAPID_KEY }) .then((currentToken) => { if (currentToken) { @@ -59,9 +59,7 @@ export const requestForToken = () => { // Perform any other necessary action with the token } else { // Show permission request UI - console.log( - "No registration token available. Request permission to generate one." - ); + console.log("No registration token available. Request permission to generate one."); } }) .catch((err) => { @@ -70,40 +68,33 @@ export const requestForToken = () => { }; export const onMessageListener = () => - new Promise((resolve) => { - onMessage(messaging, (payload) => { - console.log("Inbound FCM Message", payload); - resolve(payload); - }); + new Promise((resolve) => { + onMessage(messaging, (payload) => { + console.log("Inbound FCM Message", payload); + resolve(payload); }); + }); export const logImEXEvent = (eventName, additionalParams, stateProp = null) => { - const state = stateProp || store.getState(); - const eventParams = { - shop: - (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || - null, - user: - (state.user && state.user.currentUser && state.user.currentUser.email) || - null, - ...additionalParams, - }; - axios.post("/ioevent", { - useremail: - (state.user && state.user.currentUser && state.user.currentUser.email) || - null, - bodyshopid: - (state.user && state.user.bodyshop && state.user.bodyshop.id) || null, - operationName: eventName, - variables: additionalParams, - dbevent: false, - env: checkBeta() ? "beta" : "master", - }); - // console.log( - // "%c[Analytics]", - // "background-color: green ;font-weight:bold;", - // eventName, - // eventParams - // ); - logEvent(analytics, eventName, eventParams); + const state = stateProp || store.getState(); + const eventParams = { + shop: (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || null, + user: (state.user && state.user.currentUser && state.user.currentUser.email) || null, + ...additionalParams + }; + axios.post("/ioevent", { + useremail: (state.user && state.user.currentUser && state.user.currentUser.email) || null, + bodyshopid: (state.user && state.user.bodyshop && state.user.bodyshop.id) || null, + operationName: eventName, + variables: additionalParams, + dbevent: false, + env: checkBeta() ? "beta" : "master" + }); + // console.log( + // "%c[Analytics]", + // "background-color: green ;font-weight:bold;", + // eventName, + // eventParams + // ); + logEvent(analytics, eventName, eventParams); }; diff --git a/client/src/graphql/accounting.queries.js b/client/src/graphql/accounting.queries.js index 9d081a390..930df6cf1 100644 --- a/client/src/graphql/accounting.queries.js +++ b/client/src/graphql/accounting.queries.js @@ -1,174 +1,146 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_JOBS_FOR_EXPORT = gql` - query QUERY_JOBS_FOR_EXPORT($invoicedStatus: String!) { - jobs( - where: { - date_exported: { _is_null: true } - status: { _eq: $invoicedStatus } - } - ) { - id - ro_number - ownr_fn - ownr_ln - ownr_co_nm - date_invoiced - date_exported - status - v_model_desc - v_make_desc - v_model_yr - v_color + query QUERY_JOBS_FOR_EXPORT($invoicedStatus: String!) { + jobs(where: { date_exported: { _is_null: true }, status: { _eq: $invoicedStatus } }) { + id + ro_number + ownr_fn + ownr_ln + ownr_co_nm + date_invoiced + date_exported + status + v_model_desc + v_make_desc + v_model_yr + v_color - clm_total - clm_no - ins_co_nm - exportlogs { - id - successful - } - } + clm_total + clm_no + ins_co_nm + exportlogs { + id + successful + } } + } `; export const QUERY_BILLS_FOR_EXPORT = gql` - query QUERY_BILLS_FOR_EXPORT { - bills(where: { exported: { _eq: false }, isinhouse: { _eq: false } }) { - id - exported - date - invoice_number - is_credit_memo - total - exportlogs { - id - successful - } - job { - id - ro_number - } - vendor { - name - id - } - } + query QUERY_BILLS_FOR_EXPORT { + bills(where: { exported: { _eq: false }, isinhouse: { _eq: false } }) { + id + exported + date + invoice_number + is_credit_memo + total + exportlogs { + id + successful + } + job { + id + ro_number + } + vendor { + name + id + } } + } `; export const QUERY_PAYMENTS_FOR_EXPORT = gql` - query QUERY_PAYMENTS_FOR_EXPORT { - payments( - where: { exportedat: { _is_null: true } } - order_by: { created_at: desc } - ) { - id - amount - job { - ro_number + query QUERY_PAYMENTS_FOR_EXPORT { + payments(where: { exportedat: { _is_null: true } }, order_by: { created_at: desc }) { + id + amount + job { + ro_number - id - ownr_fn - ownr_ln - ownr_co_nm - } - payer - memo - exportedat - stripeid - created_at - transactionid - paymentnum - date - exportlogs { - id - successful - } - } + id + ownr_fn + ownr_ln + ownr_co_nm + } + payer + memo + exportedat + stripeid + created_at + transactionid + paymentnum + date + exportlogs { + id + successful + } } + } `; export const INSERT_EXPORT_LOG = gql` - mutation INSERT_EXPORT_LOG($logs: [exportlog_insert_input!]!) { - insert_exportlog(objects: $logs) { - affected_rows - } + mutation INSERT_EXPORT_LOG($logs: [exportlog_insert_input!]!) { + insert_exportlog(objects: $logs) { + affected_rows } + } `; export const QUERY_PHONEBOOK_PAGINATED = gql` - query QUERY_PHONEBOOK_PAGINATED( - $search: String - $offset: Int - $limit: Int - $order: [phonebook_order_by!] - ) { - search_phonebook( - offset: $offset - limit: $limit - order_by: $order - args: { search: $search } - ) { - id - created_at - firstname - lastname - phone1 - phone2 - state - zip - fax - email - country - company - city - category - address2 - address1 - } - search_phonebook_aggregate(args: { search: $search }) { - aggregate { - count(distinct: true) - } - } + query QUERY_PHONEBOOK_PAGINATED($search: String, $offset: Int, $limit: Int, $order: [phonebook_order_by!]) { + search_phonebook(offset: $offset, limit: $limit, order_by: $order, args: { search: $search }) { + id + created_at + firstname + lastname + phone1 + phone2 + state + zip + fax + email + country + company + city + category + address2 + address1 } + search_phonebook_aggregate(args: { search: $search }) { + aggregate { + count(distinct: true) + } + } + } `; export const QUERY_EXPORT_LOG_PAGINATED = gql` - query QUERY_ALL_EXPORTLOG_PAGINATED( - $search: String - $offset: Int - $limit: Int - $order: [exportlog_order_by!] - ) { - search_exportlog( - offset: $offset - limit: $limit - order_by: $order - args: { search: $search } - ) { - id - job { - ro_number - id - } - payment { - id - paymentnum - } - bill { - invoice_number - id - } - successful - message - created_at - useremail - } - search_exportlog_aggregate(args: { search: $search }) { - aggregate { - count(distinct: true) - } - } + query QUERY_ALL_EXPORTLOG_PAGINATED($search: String, $offset: Int, $limit: Int, $order: [exportlog_order_by!]) { + search_exportlog(offset: $offset, limit: $limit, order_by: $order, args: { search: $search }) { + id + job { + ro_number + id + } + payment { + id + paymentnum + } + bill { + invoice_number + id + } + successful + message + created_at + useremail } + search_exportlog_aggregate(args: { search: $search }) { + aggregate { + count(distinct: true) + } + } + } `; diff --git a/client/src/graphql/allocations.queries.js b/client/src/graphql/allocations.queries.js index 488a6d8da..c04631f3d 100644 --- a/client/src/graphql/allocations.queries.js +++ b/client/src/graphql/allocations.queries.js @@ -1,21 +1,21 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const INSERT_ALLOCATION = gql` - mutation INSERT_ALLOCATION($alloc: [allocations_insert_input!]!) { - insert_allocations(objects: $alloc) { - returning { - id - } - } + mutation INSERT_ALLOCATION($alloc: [allocations_insert_input!]!) { + insert_allocations(objects: $alloc) { + returning { + id + } } + } `; export const DELETE_ALLOCATION = gql` - mutation DELETE_ALLOCATION($id: uuid!) { - delete_allocations(where: { id: { _eq: $id } }) { - returning { - id - } - } + mutation DELETE_ALLOCATION($id: uuid!) { + delete_allocations(where: { id: { _eq: $id } }) { + returning { + id + } } + } `; diff --git a/client/src/graphql/apollo-error-handling.js b/client/src/graphql/apollo-error-handling.js index 78c3efee7..5e6e88691 100644 --- a/client/src/graphql/apollo-error-handling.js +++ b/client/src/graphql/apollo-error-handling.js @@ -1,22 +1,17 @@ -import {onError} from "@apollo/client/link/error"; +import { onError } from "@apollo/client/link/error"; //https://stackoverflow.com/questions/57163454/refreshing-a-token-with-apollo-client-firebase-auth import * as Sentry from "@sentry/react"; -const errorLink = onError( - ({graphQLErrors, networkError, operation, forward}) => { - if (graphQLErrors) { - graphQLErrors.forEach(({message, locations, path}) => { - console.log( - `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}` - ); - Sentry.captureException({message, locations, path}); - }); - } - if (networkError) - console.log(`[Network error]: ${JSON.stringify(networkError)}`); - console.log(operation.getContext()); - return forward(operation); - } -); +const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => { + if (graphQLErrors) { + graphQLErrors.forEach(({ message, locations, path }) => { + console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`); + Sentry.captureException({ message, locations, path }); + }); + } + if (networkError) console.log(`[Network error]: ${JSON.stringify(networkError)}`); + console.log(operation.getContext()); + return forward(operation); +}); export default errorLink; diff --git a/client/src/graphql/appointments.queries.js b/client/src/graphql/appointments.queries.js index 17ee28c9c..29d653efc 100644 --- a/client/src/graphql/appointments.queries.js +++ b/client/src/graphql/appointments.queries.js @@ -1,439 +1,375 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql` - query QUERY_ALL_ACTIVE_APPOINTMENTS( - $start: timestamptz! - $end: timestamptz! - $startd: date! - $endd: date! + query QUERY_ALL_ACTIVE_APPOINTMENTS($start: timestamptz!, $end: timestamptz!, $startd: date!, $endd: date!) { + employee_vacation( + where: { + _or: [ + { start: { _gte: $startd } } + { end: { _lte: $endd } } + { _and: [{ start: { _lte: $startd } }, { end: { _gte: $endd } }] } + ] + } ) { - employee_vacation( - where: { - _or: [ - { start: { _gte: $startd } } - { end: { _lte: $endd } } - { _and: [{ start: { _lte: $startd } }, { end: { _gte: $endd } }] } - ] - } - ) { - id - start - end - employee { - id - last_name - first_name - } - } - appointments( - where: { - canceled: { _eq: false } - end: { _lte: $end } - start: { _gte: $start } - } - ) { - start - id - end - arrived - title - isintake - block - color - note - job { - alt_transport - ro_number - ownr_ln - ownr_co_nm - ownr_fn - ownr_ph1 - ownr_ph2 - ownr_ea - clm_total - id - clm_no - ins_co_nm - v_model_yr - v_make_desc - v_model_desc - est_ct_fn - est_ct_ln - labhrs: joblines_aggregate( - where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - larhrs: joblines_aggregate( - where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - } - } + id + start + end + employee { + id + last_name + first_name + } } + appointments(where: { canceled: { _eq: false }, end: { _lte: $end }, start: { _gte: $start } }) { + start + id + end + arrived + title + isintake + block + color + note + job { + alt_transport + ro_number + ownr_ln + ownr_co_nm + ownr_fn + ownr_ph1 + ownr_ph2 + ownr_ea + clm_total + id + clm_no + ins_co_nm + v_model_yr + v_make_desc + v_model_desc + est_ct_fn + est_ct_ln + labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + } + } + } `; export const INSERT_APPOINTMENT_BLOCK = gql` - mutation INSERT_APPOINTMENT_BLOCK($app: [appointments_insert_input!]!) { - insert_appointments(objects: $app) { - returning { - id - start - end - arrived - title - isintake - block - note - } - } + mutation INSERT_APPOINTMENT_BLOCK($app: [appointments_insert_input!]!) { + insert_appointments(objects: $app) { + returning { + id + start + end + arrived + title + isintake + block + note + } } + } `; export const INSERT_MANUAL_APPT = gql` - mutation INSERT_MANUAL_APPT($apt: appointments_insert_input!) { - insert_appointments_one(object: $apt) { - start - id - end - arrived - title - isintake - block - color - note - canceled - job { - id - } - } + mutation INSERT_MANUAL_APPT($apt: appointments_insert_input!) { + insert_appointments_one(object: $apt) { + start + id + end + arrived + title + isintake + block + color + note + canceled + job { + id + } } + } `; export const INSERT_APPOINTMENT = gql` - mutation INSERT_APPOINTMENT( - $app: [appointments_insert_input!]! - $jobId: uuid - $altTransport: String - ) { - insert_appointments(objects: $app) { - returning { - id - start - end - arrived - title - isintake - block - color - note - } - } - update_jobs( - where: { id: { _eq: $jobId } } - _set: { alt_transport: $altTransport } - ) { - returning { - id - alt_transport - } - } + mutation INSERT_APPOINTMENT($app: [appointments_insert_input!]!, $jobId: uuid, $altTransport: String) { + insert_appointments(objects: $app) { + returning { + id + start + end + arrived + title + isintake + block + color + note + } } + update_jobs(where: { id: { _eq: $jobId } }, _set: { alt_transport: $altTransport }) { + returning { + id + alt_transport + } + } + } `; export const QUERY_APPOINTMENT_BY_DATE = gql` - query QUERY_APPOINTMENT_BY_DATE( - $start: timestamptz - $end: timestamptz - $startd: date! - $endd: date! - ) { - employee_vacation( - where: { _or: [{ start: { _gte: $startd } }, { end: { _lte: $endd } }] } - ) { - id - start - end - employee { - id - last_name - first_name - } - } - appointments( - where: { start: { _lte: $end, _gte: $start }, canceled: { _eq: false } } - ) { - start - id - end - title - isintake - block - color - note - job { - alt_transport - ro_number - ownr_ln - ownr_fn - ownr_ph1 - ownr_ph2 - ownr_ea - clm_total - id - clm_no - vehicle { - id - v_model_yr - v_make_desc - v_model_desc - } - labhrs: joblines_aggregate( - where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - larhrs: joblines_aggregate( - where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - } - } + query QUERY_APPOINTMENT_BY_DATE($start: timestamptz, $end: timestamptz, $startd: date!, $endd: date!) { + employee_vacation(where: { _or: [{ start: { _gte: $startd } }, { end: { _lte: $endd } }] }) { + id + start + end + employee { + id + last_name + first_name + } } + appointments(where: { start: { _lte: $end, _gte: $start }, canceled: { _eq: false } }) { + start + id + end + title + isintake + block + color + note + job { + alt_transport + ro_number + ownr_ln + ownr_fn + ownr_ph1 + ownr_ph2 + ownr_ea + clm_total + id + clm_no + vehicle { + id + v_model_yr + v_make_desc + v_model_desc + } + labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + } + } + } `; export const UPDATE_APPOINTMENT = gql` - mutation UPDATE_APPOINTMENT($appid: uuid!, $app: appointments_set_input) { - update_appointments(where: { id: { _eq: $appid } }, _set: $app) { - returning { - id - start - id - end - arrived - title - isintake - block - color - note - } - } + mutation UPDATE_APPOINTMENT($appid: uuid!, $app: appointments_set_input) { + update_appointments(where: { id: { _eq: $appid } }, _set: $app) { + returning { + id + start + id + end + arrived + title + isintake + block + color + note + } } + } `; export const CANCEL_APPOINTMENT_BY_ID = gql` - mutation CANCEL_APPOINTMENT_BY_ID($appid: uuid!) { - update_appointments( - where: { id: { _eq: $appid } } - _set: { canceled: true } - ) { - returning { - id - canceled - } - } + mutation CANCEL_APPOINTMENT_BY_ID($appid: uuid!) { + update_appointments(where: { id: { _eq: $appid } }, _set: { canceled: true }) { + returning { + id + canceled + } } + } `; export const CANCEL_APPOINTMENTS_BY_JOB_ID = gql` - mutation CANCEL_APPOINTMENTS_BY_JOB_ID($jobid: uuid!, $job: jobs_set_input) { - update_appointments( - where: { _and: { jobid: { _eq: $jobid }, arrived: { _eq: false } } } - _set: { canceled: true } - ) { - returning { - id - canceled - } - } - update_jobs_by_pk(pk_columns: { id: $jobid }, _set: $job) { - date_scheduled - id - scheduled_in - scheduled_completion - status - lost_sale_reason - date_lost_sale - } + mutation CANCEL_APPOINTMENTS_BY_JOB_ID($jobid: uuid!, $job: jobs_set_input) { + update_appointments( + where: { _and: { jobid: { _eq: $jobid }, arrived: { _eq: false } } } + _set: { canceled: true } + ) { + returning { + id + canceled + } } + update_jobs_by_pk(pk_columns: { id: $jobid }, _set: $job) { + date_scheduled + id + scheduled_in + scheduled_completion + status + lost_sale_reason + date_lost_sale + } + } `; export const QUERY_APPOINTMENTS_BY_JOBID = gql` - query QUERY_APPOINTMENTS_BY_JOBID($jobid: uuid!) { - appointments(where: { jobid: { _eq: $jobid } }, order_by: { start: desc }) { - start - id - end - color - isintake - arrived - canceled - created_at - block - note - } + query QUERY_APPOINTMENTS_BY_JOBID($jobid: uuid!) { + appointments(where: { jobid: { _eq: $jobid } }, order_by: { start: desc }) { + start + id + end + color + isintake + arrived + canceled + created_at + block + note } + } `; export const QUERY_SCHEDULE_LOAD_DATA = gql` - query QUERY_SCHEDULE_LOAD_DATA($start: timestamptz!, $end: timestamptz!) { - prodJobs: jobs( - where: { inproduction: { _eq: true }, suspended: { _eq: false } } - ) { - id - actual_in - scheduled_in - actual_completion - scheduled_completion - inproduction - ro_number - labhrs: joblines_aggregate( - where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - larhrs: joblines_aggregate( - where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } + query QUERY_SCHEDULE_LOAD_DATA($start: timestamptz!, $end: timestamptz!) { + prodJobs: jobs(where: { inproduction: { _eq: true }, suspended: { _eq: false } }) { + id + actual_in + scheduled_in + actual_completion + scheduled_completion + inproduction + ro_number + labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) { + aggregate { + sum { + mod_lb_hrs + } } - compJobs: jobs( - where: { - _and: [ - { suspended: { _eq: false } } - { - _or: [ - { scheduled_completion: { _gte: $start, _lte: $end } } - { actual_completion: { _gte: $start, _lte: $end } } - ] - } - ] - } - ) { - id - status - ro_number - scheduled_completion - actual_completion - scheduled_in - ownr_fn - ownr_ln - ownr_co_nm - inproduction - labhrs: joblines_aggregate( - where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - larhrs: joblines_aggregate( - where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - } - arrJobs: jobs( - where: { - scheduled_in: { _gte: $start, _lte: $end } - suspended: { _eq: false } - } - ) { - id - scheduled_in - actual_in - scheduled_completion - ro_number - ownr_fn - ownr_ln - ownr_co_nm - alt_transport - actual_completion - inproduction - status - labhrs: joblines_aggregate( - where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - larhrs: joblines_aggregate( - where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } + } + larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) { + aggregate { + sum { + mod_lb_hrs + } } + } } + compJobs: jobs( + where: { + _and: [ + { suspended: { _eq: false } } + { + _or: [ + { scheduled_completion: { _gte: $start, _lte: $end } } + { actual_completion: { _gte: $start, _lte: $end } } + ] + } + ] + } + ) { + id + status + ro_number + scheduled_completion + actual_completion + scheduled_in + ownr_fn + ownr_ln + ownr_co_nm + inproduction + labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + } + arrJobs: jobs(where: { scheduled_in: { _gte: $start, _lte: $end }, suspended: { _eq: false } }) { + id + scheduled_in + actual_in + scheduled_completion + ro_number + ownr_fn + ownr_ln + ownr_co_nm + alt_transport + actual_completion + inproduction + status + labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + } + } `; export const MARK_APPOINTMENT_ARRIVED = gql` - mutation MARK_APPOINTMENT_ARRIVED($appointmentId: uuid!) { - update_appointments( - where: { id: { _eq: $appointmentId } } - _set: { arrived: true } - ) { - affected_rows - returning { - id - arrived - } - } + mutation MARK_APPOINTMENT_ARRIVED($appointmentId: uuid!) { + update_appointments(where: { id: { _eq: $appointmentId } }, _set: { arrived: true }) { + affected_rows + returning { + id + arrived + } } + } `; export const MARK_LATEST_APPOINTMENT_ARRIVED = gql` - mutation MARK_LATEST_APPOINTMENT_ARRIVED($jobId: uuid!) { - update_appointments( - where: { - jobid: { _eq: $jobId } - canceled: { _eq: false } - isintake: { _eq: true } - } - _set: { arrived: true } - ) { - affected_rows - returning { - id - arrived - } - } + mutation MARK_LATEST_APPOINTMENT_ARRIVED($jobId: uuid!) { + update_appointments( + where: { jobid: { _eq: $jobId }, canceled: { _eq: false }, isintake: { _eq: true } } + _set: { arrived: true } + ) { + affected_rows + returning { + id + arrived + } } + } `; diff --git a/client/src/graphql/associations.queries.js b/client/src/graphql/associations.queries.js index 29209dde2..8179448d4 100644 --- a/client/src/graphql/associations.queries.js +++ b/client/src/graphql/associations.queries.js @@ -1,70 +1,55 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_ALL_ASSOCIATIONS = gql` - query QUERY_ALL_ASSOCIATIONS($email: String) { - associations( - where: { useremail: { _eq: $email } } - order_by: { bodyshop: { shopname: asc } } - ) { - id - active - bodyshop { - shopname - } - } + query QUERY_ALL_ASSOCIATIONS($email: String) { + associations(where: { useremail: { _eq: $email } }, order_by: { bodyshop: { shopname: asc } }) { + id + active + bodyshop { + shopname + } } + } `; export const UPDATE_ASSOCIATION = gql` - mutation UPDATE_ASSOCIATION($assocId: uuid, $assocActive: Boolean) { - update_associations( - where: { id: { _eq: $assocId } } - _set: { active: $assocActive } - ) { - returning { - id - active - authlevel - default_prod_list_view - } - } + mutation UPDATE_ASSOCIATION($assocId: uuid, $assocActive: Boolean) { + update_associations(where: { id: { _eq: $assocId } }, _set: { active: $assocActive }) { + returning { + id + active + authlevel + default_prod_list_view + } } + } `; export const UPDATE_ACTIVE_ASSOCIATION = gql` - mutation UPDATE_ACTIVE_ASSOCIATION($newActiveAssocId: uuid) { - nweActive: update_associations( - where: { id: { _eq: $newActiveAssocId } } - _set: { active: true } - ) { - returning { - id - shopid - active - } - } - inactive: update_associations( - where: { id: { _neq: $newActiveAssocId } } - _set: { active: false } - ) { - returning { - id - shopid - active - } - } + mutation UPDATE_ACTIVE_ASSOCIATION($newActiveAssocId: uuid) { + nweActive: update_associations(where: { id: { _eq: $newActiveAssocId } }, _set: { active: true }) { + returning { + id + shopid + active + } } + inactive: update_associations(where: { id: { _neq: $newActiveAssocId } }, _set: { active: false }) { + returning { + id + shopid + active + } + } + } `; export const UPDATE_ACTIVE_PROD_LIST_VIEW = gql` - mutation UPDATE_ACTIVE_PROD_LIST_VIEW($assocId: uuid, $view: String) { - update_associations( - where: { id: { _eq: $assocId } } - _set: { default_prod_list_view: $view } - ) { - returning { - id - default_prod_list_view - } - } + mutation UPDATE_ACTIVE_PROD_LIST_VIEW($assocId: uuid, $view: String) { + update_associations(where: { id: { _eq: $assocId } }, _set: { default_prod_list_view: $view }) { + returning { + id + default_prod_list_view + } } + } `; diff --git a/client/src/graphql/audit_trail.queries.js b/client/src/graphql/audit_trail.queries.js index 2ea1846d2..745e29e31 100644 --- a/client/src/graphql/audit_trail.queries.js +++ b/client/src/graphql/audit_trail.queries.js @@ -1,47 +1,41 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_AUDIT_TRAIL = gql` - query QUERY_AUDIT_TRAIL($jobid: uuid!) { - audit_trail( - where: { jobid: { _eq: $jobid } } - order_by: { created: desc } - ) { - useremail - jobid - operation - id - created - bodyshopid - } - email_audit_trail( - where: { jobid: { _eq: $jobid } } - order_by: { created_at: desc } - ) { - cc - contents - created_at - id - jobid - noteid - subject - to - useremail - status - } + query QUERY_AUDIT_TRAIL($jobid: uuid!) { + audit_trail(where: { jobid: { _eq: $jobid } }, order_by: { created: desc }) { + useremail + jobid + operation + id + created + bodyshopid } + email_audit_trail(where: { jobid: { _eq: $jobid } }, order_by: { created_at: desc }) { + cc + contents + created_at + id + jobid + noteid + subject + to + useremail + status + } + } `; export const INSERT_AUDIT_TRAIL = gql` - mutation INSERT_AUDIT_TRAIL($auditObj: audit_trail_insert_input!) { - insert_audit_trail_one(object: $auditObj) { - id - jobid - billid - bodyshopid - created - operation - type - useremail - } + mutation INSERT_AUDIT_TRAIL($auditObj: audit_trail_insert_input!) { + insert_audit_trail_one(object: $auditObj) { + id + jobid + billid + bodyshopid + created + operation + type + useremail } + } `; diff --git a/client/src/graphql/available-jobs.queries.js b/client/src/graphql/available-jobs.queries.js index d30a2f006..69a5190ad 100644 --- a/client/src/graphql/available-jobs.queries.js +++ b/client/src/graphql/available-jobs.queries.js @@ -1,118 +1,112 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_AVAILABLE_JOBS = gql` - query QUERY_AVAILABLE_JOBS { - available_jobs(order_by: { updated_at: desc }) { - cieca_id - clm_amt - clm_no - created_at - id - issupplement - ownr_name - source_system - supplement_number - updated_at - uploaded_by - ins_co_nm - vehicle_info - job { - id - ro_number - status - } - } + query QUERY_AVAILABLE_JOBS { + available_jobs(order_by: { updated_at: desc }) { + cieca_id + clm_amt + clm_no + created_at + id + issupplement + ownr_name + source_system + supplement_number + updated_at + uploaded_by + ins_co_nm + vehicle_info + job { + id + ro_number + status + } } + } `; export const QUERY_AVAILABLE_NEW_JOBS = gql` - query QUERY_AVAILABLE_NEW_JOBS { - available_jobs( - where: { issupplement: { _eq: false } } - order_by: { updated_at: desc } - ) { - cieca_id - clm_amt - clm_no - created_at - id - issupplement - ownr_name - source_system - supplement_number - updated_at - uploaded_by - ins_co_nm - vehicle_info - } + query QUERY_AVAILABLE_NEW_JOBS { + available_jobs(where: { issupplement: { _eq: false } }, order_by: { updated_at: desc }) { + cieca_id + clm_amt + clm_no + created_at + id + issupplement + ownr_name + source_system + supplement_number + updated_at + uploaded_by + ins_co_nm + vehicle_info } + } `; export const QUERY_AVAILABLE_SUPPLEMENT_JOBS = gql` - query QUERY_AVAILABLE_SUPPLEMENT_JOBS { - available_jobs( - where: { issupplement: { _eq: true } } - order_by: { updated_at: desc } - ) { - cieca_id - clm_amt - clm_no - created_at - id - issupplement - ownr_name - source_system - supplement_number - updated_at - uploaded_by - ins_co_nm - vehicle_info - job { - id - ro_number - } - } + query QUERY_AVAILABLE_SUPPLEMENT_JOBS { + available_jobs(where: { issupplement: { _eq: true } }, order_by: { updated_at: desc }) { + cieca_id + clm_amt + clm_no + created_at + id + issupplement + ownr_name + source_system + supplement_number + updated_at + uploaded_by + ins_co_nm + vehicle_info + job { + id + ro_number + } } + } `; export const DELETE_AVAILABLE_JOB = gql` - mutation DELETE_AVAILABLE_JOB($id: uuid) { - delete_available_jobs(where: { id: { _eq: $id } }) { - affected_rows - } + mutation DELETE_AVAILABLE_JOB($id: uuid) { + delete_available_jobs(where: { id: { _eq: $id } }) { + affected_rows } + } `; export const DELETE_ALL_AVAILABLE_JOBS = gql` - mutation DELETE_ALL_AVAILABLE_JOBS { - delete_available_jobs(where: {}) { - affected_rows - } + mutation DELETE_ALL_AVAILABLE_JOBS { + delete_available_jobs(where: {}) { + affected_rows } + } `; export const DELETE_ALL_AVAILABLE_NEW_JOBS = gql` - mutation DELETE_ALL_AVAILABLE_NEW_JOBS { - delete_available_jobs(where: { issupplement: { _eq: false } }) { - affected_rows - } + mutation DELETE_ALL_AVAILABLE_NEW_JOBS { + delete_available_jobs(where: { issupplement: { _eq: false } }) { + affected_rows } + } `; export const DELETE_ALL_AVAILABLE_SUPPLEMENT_JOBS = gql` - mutation DELETE_ALL_AVAILABLE_NEW_JOBS { - delete_available_jobs(where: { issupplement: { _eq: true } }) { - affected_rows - } + mutation DELETE_ALL_AVAILABLE_NEW_JOBS { + delete_available_jobs(where: { issupplement: { _eq: true } }) { + affected_rows } + } `; export const QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK = gql` - query QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK($id: uuid!) { - available_jobs_by_pk(id: $id) { - id - issupplement - est_data - } + query QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK($id: uuid!) { + available_jobs_by_pk(id: $id) { + id + issupplement + est_data } + } `; diff --git a/client/src/graphql/bill-lines.queries.js b/client/src/graphql/bill-lines.queries.js index 5cbe33625..09e057558 100644 --- a/client/src/graphql/bill-lines.queries.js +++ b/client/src/graphql/bill-lines.queries.js @@ -1,31 +1,28 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const UPDATE_BILL_LINE = gql` - mutation UPDATE_BILL_LINE( - $billLineId: uuid! - $billLine: billlines_set_input! - ) { - update_billlines(where: { id: { _eq: $billLineId } }, _set: $billLine) { - returning { - id - } - } + mutation UPDATE_BILL_LINE($billLineId: uuid!, $billLine: billlines_set_input!) { + update_billlines(where: { id: { _eq: $billLineId } }, _set: $billLine) { + returning { + id + } } + } `; export const DELETE_BILL_LINE = gql` - mutation DELETE_BILL_LINE($id: uuid!) { - delete_billlines_by_pk(id: $id) { - id - } + mutation DELETE_BILL_LINE($id: uuid!) { + delete_billlines_by_pk(id: $id) { + id } + } `; export const INSERT_NEW_BILL_LINES = gql` - mutation INSERT_NEW_BILL_LINES($billLines: [billlines_insert_input!]!) { - insert_billlines(objects: $billLines) { - returning { - id - } - } + mutation INSERT_NEW_BILL_LINES($billLines: [billlines_insert_input!]!) { + insert_billlines(objects: $billLines) { + returning { + id + } } + } `; diff --git a/client/src/graphql/bills.queries.js b/client/src/graphql/bills.queries.js index 64e67ef42..355a123dd 100644 --- a/client/src/graphql/bills.queries.js +++ b/client/src/graphql/bills.queries.js @@ -1,250 +1,236 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const INSERT_NEW_BILL = gql` - mutation INSERT_NEW_BILL($bill: [bills_insert_input!]!) { - insert_bills(objects: $bill) { - returning { - id - invoice_number - } - } + mutation INSERT_NEW_BILL($bill: [bills_insert_input!]!) { + insert_bills(objects: $bill) { + returning { + id + invoice_number + } } + } `; export const DELETE_BILL = gql` - mutation DELETE_BILL($billId: uuid!) { - delete_bills_by_pk(id: $billId) { - id - } + mutation DELETE_BILL($billId: uuid!) { + delete_bills_by_pk(id: $billId) { + id } + } `; export const QUERY_ALL_BILLS_PAGINATED = gql` - query QUERY_ALL_BILLS_PAGINATED( - $offset: Int - $limit: Int - $order: [bills_order_by!]! - ) { - bills(offset: $offset, limit: $limit, order_by: $order) { - id - vendorid - vendor { - id - name - } - federal_tax_rate - local_tax_rate - state_tax_rate - is_credit_memo - total - invoice_number - date - isinhouse - exported - job { - id - ro_number - } - } - bills_aggregate { - aggregate { - count(distinct: true) - } - } + query QUERY_ALL_BILLS_PAGINATED($offset: Int, $limit: Int, $order: [bills_order_by!]!) { + bills(offset: $offset, limit: $limit, order_by: $order) { + id + vendorid + vendor { + id + name + } + federal_tax_rate + local_tax_rate + state_tax_rate + is_credit_memo + total + invoice_number + date + isinhouse + exported + job { + id + ro_number + } } + bills_aggregate { + aggregate { + count(distinct: true) + } + } + } `; export const QUERY_BILLS_BY_JOBID = gql` - query QUERY_PARTS_BILLS_BY_JOBID($jobid: uuid!) { - parts_orders( - where: { jobid: { _eq: $jobid } } - order_by: { order_date: desc } - ) { - id - vendor { - id - name - email - } - order_date - deliver_by - return - orderedby - parts_order_lines { - id - act_price - db_price - line_desc - oem_partno - status - line_remarks - quantity - job_line_id - part_type - cost - cm_received - jobline { - id - part_type - } - backordered_eta - backordered_on - } - order_number - comments - user_email - } - parts_dispatch(where: { jobid: { _eq: $jobid } }) { - id - dispatched_at - dispatched_by - employeeid - number - parts_dispatch_lines { - joblineid - id - quantity - accepted_at - jobline { - id - line_desc - } - } - } - bills(where: { jobid: { _eq: $jobid } }, order_by: { date: desc }) { - id - vendorid - vendor { - id - name - email - } - total - invoice_number - date - federal_tax_rate - state_tax_rate - local_tax_rate - is_credit_memo - isinhouse - exported - billlines { - actual_price - quantity - actual_cost - cost_center - id - joblineid - line_desc - applicable_taxes - deductedfromlbr - lbr_adjustment - jobline { - oem_partno - part_type - } - } + query QUERY_PARTS_BILLS_BY_JOBID($jobid: uuid!) { + parts_orders(where: { jobid: { _eq: $jobid } }, order_by: { order_date: desc }) { + id + vendor { + id + name + email + } + order_date + deliver_by + return + orderedby + parts_order_lines { + id + act_price + db_price + line_desc + oem_partno + status + line_remarks + quantity + job_line_id + part_type + cost + cm_received + jobline { + id + part_type } + backordered_eta + backordered_on + } + order_number + comments + user_email } + parts_dispatch(where: { jobid: { _eq: $jobid } }) { + id + dispatched_at + dispatched_by + employeeid + number + parts_dispatch_lines { + joblineid + id + quantity + accepted_at + jobline { + id + line_desc + } + } + } + bills(where: { jobid: { _eq: $jobid } }, order_by: { date: desc }) { + id + vendorid + vendor { + id + name + email + } + total + invoice_number + date + federal_tax_rate + state_tax_rate + local_tax_rate + is_credit_memo + isinhouse + exported + billlines { + actual_price + quantity + actual_cost + cost_center + id + joblineid + line_desc + applicable_taxes + deductedfromlbr + lbr_adjustment + jobline { + oem_partno + part_type + } + } + } + } `; export const QUERY_BILL_BY_PK = gql` - query QUERY_BILL_BY_PK($billid: uuid!) { - bills_by_pk(id: $billid) { - due_date - exported - exported_at - id - invoice_number - date - is_credit_memo - jobid - total - updated_at - vendorid - local_tax_rate - state_tax_rate - federal_tax_rate - isinhouse - inventories { - id - line_desc - } - vendor { - id - name - discount - } - billlines { - id - line_desc - actual_price - actual_cost - cost_center - quantity - joblineid - inventories { - id - } - jobline { - oem_partno - part_type - } - applicable_taxes - deductedfromlbr - lbr_adjustment - } - documents { - id - key - name - type - size - } + query QUERY_BILL_BY_PK($billid: uuid!) { + bills_by_pk(id: $billid) { + due_date + exported + exported_at + id + invoice_number + date + is_credit_memo + jobid + total + updated_at + vendorid + local_tax_rate + state_tax_rate + federal_tax_rate + isinhouse + inventories { + id + line_desc + } + vendor { + id + name + discount + } + billlines { + id + line_desc + actual_price + actual_cost + cost_center + quantity + joblineid + inventories { + id } + jobline { + oem_partno + part_type + } + applicable_taxes + deductedfromlbr + lbr_adjustment + } + documents { + id + key + name + type + size + } } + } `; export const UPDATE_BILL = gql` - mutation UPDATE_BILL($billId: uuid!, $bill: bills_set_input!) { - update_bills(where: { id: { _eq: $billId } }, _set: $bill) { - returning { - id - exported - exported_at - } - } + mutation UPDATE_BILL($billId: uuid!, $bill: bills_set_input!) { + update_bills(where: { id: { _eq: $billId } }, _set: $bill) { + returning { + id + exported + exported_at + } } + } `; export const UPDATE_BILLS = gql` - mutation UPDATE_BILLS($billIdList: [uuid!]!, $bill: bills_set_input!) { - update_bills(where: { id: { _in: $billIdList } }, _set: $bill) { - returning { - id - exported - exported_at - } - } + mutation UPDATE_BILLS($billIdList: [uuid!]!, $bill: bills_set_input!) { + update_bills(where: { id: { _in: $billIdList } }, _set: $bill) { + returning { + id + exported + exported_at + } } + } `; export const CHECK_BILL_INVOICE_NUMBER = gql` - query CHECK_BILL_INVOICE_NUMBER($invoice_number: String!, $vendorid: uuid!) { - bills_aggregate( - where: { - _and: { - invoice_number: { _ilike: $invoice_number } - vendorid: { _eq: $vendorid } - } - } - ) { - aggregate { - count - } - nodes { - id - } - } + query CHECK_BILL_INVOICE_NUMBER($invoice_number: String!, $vendorid: uuid!) { + bills_aggregate(where: { _and: { invoice_number: { _ilike: $invoice_number }, vendorid: { _eq: $vendorid } } }) { + aggregate { + count + } + nodes { + id + } } + } `; diff --git a/client/src/graphql/bodyshop.queries.js b/client/src/graphql/bodyshop.queries.js index a8efb8d0b..e1d5b93e1 100644 --- a/client/src/graphql/bodyshop.queries.js +++ b/client/src/graphql/bodyshop.queries.js @@ -1,359 +1,347 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const INTROSPECTION = gql` - query INTROSPECTION { - __schema { - types { - name - } - } + query INTROSPECTION { + __schema { + types { + name + } } + } `; export const QUERY_EULA = gql` - query QUERY_EULA($now: timestamptz!) { - eulas(where: {effective_date: {_lte: $now}, _or: [{end_date: {_is_null: true}}, {end_date: {_gt: $now}}]}) { - id - content + query QUERY_EULA($now: timestamptz!) { + eulas( + where: { effective_date: { _lte: $now }, _or: [{ end_date: { _is_null: true } }, { end_date: { _gt: $now } }] } + ) { + id + content - eula_acceptances { - id - date_accepted - } - } + eula_acceptances { + id + date_accepted + } } + } `; export const QUERY_BODYSHOP = gql` - query QUERY_BODYSHOP { - bodyshops(where: { associations: { active: { _eq: true } } }) { - associations { - id - authlevel - useremail - default_prod_list_view - user { - authid - email - dashboardlayout - validemail - employee { - id - } - } - } - address1 - address2 - city - country - created_at - email - phone - federal_tax_id + query QUERY_BODYSHOP { + bodyshops(where: { associations: { active: { _eq: true } } }) { + associations { + id + authlevel + useremail + default_prod_list_view + user { + authid + email + dashboardlayout + validemail + employee { id - insurance_vendor_id - logo_img_path - md_ro_statuses - md_order_statuses - md_functionality_toggles - shopname - state - state_tax_id - updated_at - zip_post - shoprates - region_config - md_responsibility_centers - messagingservicesid - template_header - textid - production_config - bill_tax_rates - inhousevendorid - accountingconfig - appt_length - stripe_acct_id - ssbuckets - scoreboard_target - md_referral_sources - md_messaging_presets - intakechecklist - speedprint - md_parts_locations - md_notes_presets - md_rbac - prodtargethrs - md_classes - md_ins_cos - md_categories - enforce_class - md_labor_rates - deliverchecklist - target_touchtime - appt_colors - appt_alt_transport - schedule_start_time - schedule_end_time - imexshopid - default_adjustment_rate - workingdays - use_fippa - md_payment_types - md_hour_split - sub_status - jobsizelimit - md_ccc_rates - enforce_referral - website - jc_hourly_rates - md_jobline_presets - cdk_dealerid - features - attach_pdf_to_email - tt_allow_post_to_invoiced - cdk_configuration - md_estimators - md_ded_notes - pbs_configuration - pbs_serialnumber - md_filehandlers - md_email_cc - timezone - ss_configuration - md_from_emails - last_name_first - md_parts_order_comment - bill_allow_post_to_closed - md_to_emails - uselocalmediaserver - localmediaserverhttp - localmediaservernetwork - localmediatoken - enforce_conversion_csr - md_lost_sale_reasons - md_parts_scan - enforce_conversion_category - tt_enforce_hours_for_tech_console - md_tasks_presets - use_paint_scale_data - employee_teams( - order_by: { name: asc } - where: { active: { _eq: true } } - ) { - id - name - employee_team_members { - id - employeeid - labor_rates - percentage - } - } - employees { - user_email - id - active - first_name - last_name - employee_number - rates - external_id - flat_rate - } + } } + } + address1 + address2 + city + country + created_at + email + phone + federal_tax_id + id + insurance_vendor_id + logo_img_path + md_ro_statuses + md_order_statuses + md_functionality_toggles + shopname + state + state_tax_id + updated_at + zip_post + shoprates + region_config + md_responsibility_centers + messagingservicesid + template_header + textid + production_config + bill_tax_rates + inhousevendorid + accountingconfig + appt_length + stripe_acct_id + ssbuckets + scoreboard_target + md_referral_sources + md_messaging_presets + intakechecklist + speedprint + md_parts_locations + md_notes_presets + md_rbac + prodtargethrs + md_classes + md_ins_cos + md_categories + enforce_class + md_labor_rates + deliverchecklist + target_touchtime + appt_colors + appt_alt_transport + schedule_start_time + schedule_end_time + imexshopid + default_adjustment_rate + workingdays + use_fippa + md_payment_types + md_hour_split + sub_status + jobsizelimit + md_ccc_rates + enforce_referral + website + jc_hourly_rates + md_jobline_presets + cdk_dealerid + features + attach_pdf_to_email + tt_allow_post_to_invoiced + cdk_configuration + md_estimators + md_ded_notes + pbs_configuration + pbs_serialnumber + md_filehandlers + md_email_cc + timezone + ss_configuration + md_from_emails + last_name_first + md_parts_order_comment + bill_allow_post_to_closed + md_to_emails + uselocalmediaserver + localmediaserverhttp + localmediaservernetwork + localmediatoken + enforce_conversion_csr + md_lost_sale_reasons + md_parts_scan + enforce_conversion_category + tt_enforce_hours_for_tech_console + md_tasks_presets + use_paint_scale_data + employee_teams(order_by: { name: asc }, where: { active: { _eq: true } }) { + id + name + employee_team_members { + id + employeeid + labor_rates + percentage + } + } + employees { + user_email + id + active + first_name + last_name + employee_number + rates + external_id + flat_rate + } } + } `; export const QUERY_SHOP_ID = gql` - query QUERY_SHOP_ID { - bodyshops(where: { associations: { active: { _eq: true } } }) { - id - } + query QUERY_SHOP_ID { + bodyshops(where: { associations: { active: { _eq: true } } }) { + id } + } `; export const UPDATE_SHOP = gql` - mutation UPDATE_SHOP($id: uuid, $shop: bodyshops_set_input!) { - update_bodyshops(where: { id: { _eq: $id } }, _set: $shop) { - returning { - address1 - address2 - city - country - created_at - email - phone - federal_tax_id - id - insurance_vendor_id - logo_img_path - md_ro_statuses - md_order_statuses - md_functionality_toggles - shopname - state - state_tax_id - updated_at - zip_post - shoprates - region_config - md_responsibility_centers - messagingservicesid - template_header - textid - production_config - bill_tax_rates - appt_length - stripe_acct_id - ssbuckets - scoreboard_target - md_referral_sources - md_messaging_presets - intakechecklist - speedprint - md_parts_locations - md_notes_presets - md_rbac - prodtargethrs - md_classes - md_ins_cos - md_categories - enforce_class - md_labor_rates - deliverchecklist - target_touchtime - appt_colors - appt_alt_transport - schedule_start_time - schedule_end_time - imexshopid - default_adjustment_rate - workingdays - use_fippa - md_payment_types - md_hour_split - sub_status - jobsizelimit - md_ccc_rates - enforce_referral - website - jc_hourly_rates - md_jobline_presets - cdk_dealerid - attach_pdf_to_email - tt_allow_post_to_invoiced - cdk_configuration - md_estimators - md_ded_notes - pbs_configuration - pbs_serialnumber - md_filehandlers - md_email_cc - timezone - ss_configuration - md_from_emails - last_name_first - md_parts_order_comment - bill_allow_post_to_closed - md_to_emails - uselocalmediaserver - localmediaserverhttp - localmediaservernetwork - localmediatoken - enforce_conversion_csr - md_lost_sale_reasons - md_parts_scan - enforce_conversion_category - tt_enforce_hours_for_tech_console - md_tasks_presets - employee_teams( - order_by: { name: asc } - where: { active: { _eq: true } } - ) { - id - name - employee_team_members { - id - employeeid - labor_rates - percentage - } - } - employees { - id - first_name - active - last_name - employee_number - rates - user_email - external_id - } - } + mutation UPDATE_SHOP($id: uuid, $shop: bodyshops_set_input!) { + update_bodyshops(where: { id: { _eq: $id } }, _set: $shop) { + returning { + address1 + address2 + city + country + created_at + email + phone + federal_tax_id + id + insurance_vendor_id + logo_img_path + md_ro_statuses + md_order_statuses + md_functionality_toggles + shopname + state + state_tax_id + updated_at + zip_post + shoprates + region_config + md_responsibility_centers + messagingservicesid + template_header + textid + production_config + bill_tax_rates + appt_length + stripe_acct_id + ssbuckets + scoreboard_target + md_referral_sources + md_messaging_presets + intakechecklist + speedprint + md_parts_locations + md_notes_presets + md_rbac + prodtargethrs + md_classes + md_ins_cos + md_categories + enforce_class + md_labor_rates + deliverchecklist + target_touchtime + appt_colors + appt_alt_transport + schedule_start_time + schedule_end_time + imexshopid + default_adjustment_rate + workingdays + use_fippa + md_payment_types + md_hour_split + sub_status + jobsizelimit + md_ccc_rates + enforce_referral + website + jc_hourly_rates + md_jobline_presets + cdk_dealerid + attach_pdf_to_email + tt_allow_post_to_invoiced + cdk_configuration + md_estimators + md_ded_notes + pbs_configuration + pbs_serialnumber + md_filehandlers + md_email_cc + timezone + ss_configuration + md_from_emails + last_name_first + md_parts_order_comment + bill_allow_post_to_closed + md_to_emails + uselocalmediaserver + localmediaserverhttp + localmediaservernetwork + localmediatoken + enforce_conversion_csr + md_lost_sale_reasons + md_parts_scan + enforce_conversion_category + tt_enforce_hours_for_tech_console + md_tasks_presets + employee_teams(order_by: { name: asc }, where: { active: { _eq: true } }) { + id + name + employee_team_members { + id + employeeid + labor_rates + percentage + } } + employees { + id + first_name + active + last_name + employee_number + rates + user_email + external_id + } + } } + } `; export const QUERY_INTAKE_CHECKLIST = gql` - query QUERY_INTAKE_CHECKLIST($shopId: uuid!, $jobId: uuid!) { - bodyshops_by_pk(id: $shopId) { - id - intakechecklist - } - jobs_by_pk(id: $jobId) { - id - ro_number - production_vars - scheduled_completion - scheduled_delivery - intakechecklist - status - owner { - allow_text_message - id - } - labhrs: joblines_aggregate( - where: { - _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] - } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - larhrs: joblines_aggregate( - where: { - _and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }] - } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - } + query QUERY_INTAKE_CHECKLIST($shopId: uuid!, $jobId: uuid!) { + bodyshops_by_pk(id: $shopId) { + id + intakechecklist } + jobs_by_pk(id: $jobId) { + id + ro_number + production_vars + scheduled_completion + scheduled_delivery + intakechecklist + status + owner { + allow_text_message + id + } + labhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + larhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }] }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + } + } `; export const QUERY_DELIVER_CHECKLIST = gql` - query QUERY_DELIVER_CHECKLIST($shopId: uuid!, $jobId: uuid!) { - bodyshops_by_pk(id: $shopId) { - id - deliverchecklist - } - jobs_by_pk(id: $jobId) { - id - ro_number - actual_completion - actual_delivery - } + query QUERY_DELIVER_CHECKLIST($shopId: uuid!, $jobId: uuid!) { + bodyshops_by_pk(id: $shopId) { + id + deliverchecklist } + jobs_by_pk(id: $jobId) { + id + ro_number + actual_completion + actual_delivery + } + } `; export const QUERY_STRIPE_ID = gql` - query QUERY_STRIPE_ID { - bodyshops(where: { associations: { active: { _eq: true } } }) { - stripe_acct_id - } + query QUERY_STRIPE_ID { + bodyshops(where: { associations: { active: { _eq: true } } }) { + stripe_acct_id } + } `; diff --git a/client/src/graphql/cccontracts.queries.js b/client/src/graphql/cccontracts.queries.js index 75cda9bf0..8e4800f23 100644 --- a/client/src/graphql/cccontracts.queries.js +++ b/client/src/graphql/cccontracts.queries.js @@ -1,233 +1,210 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const INSERT_NEW_CONTRACT = gql` - mutation INSERT_NEW_CONTRACT( - $contract: [cccontracts_insert_input!]! - $ccId: uuid! - $damage: String - $mileage: numeric - ) { - insert_cccontracts(objects: $contract) { - returning { - id - } - } - update_courtesycars_by_pk( - pk_columns: { id: $ccId } - _set: { - status: "courtesycars.status.out" - mileage: $mileage - damage: $damage - } - ) { - status - id - damage - mileage - } + mutation INSERT_NEW_CONTRACT( + $contract: [cccontracts_insert_input!]! + $ccId: uuid! + $damage: String + $mileage: numeric + ) { + insert_cccontracts(objects: $contract) { + returning { + id + } } + update_courtesycars_by_pk( + pk_columns: { id: $ccId } + _set: { status: "courtesycars.status.out", mileage: $mileage, damage: $damage } + ) { + status + id + damage + mileage + } + } `; export const UPDATE_CONTRACT = gql` - mutation UPDATE_CONTRACT( - $contractId: uuid! - $cccontract: cccontracts_set_input! - ) { - update_cccontracts(where: { id: { _eq: $contractId } }, _set: $cccontract) { - returning { - id - } - } + mutation UPDATE_CONTRACT($contractId: uuid!, $cccontract: cccontracts_set_input!) { + update_cccontracts(where: { id: { _eq: $contractId } }, _set: $cccontract) { + returning { + id + } } + } `; export const RETURN_CONTRACT = gql` - mutation RETURN_CONTRACT( - $contractId: uuid! - $cccontract: cccontracts_set_input! - $courtesycarid: uuid! - $courtesycar: courtesycars_set_input! - ) { - update_cccontracts(where: { id: { _eq: $contractId } }, _set: $cccontract) { - returning { - id - } - } - update_courtesycars( - where: { id: { _eq: $courtesycarid } } - _set: $courtesycar - ) { - returning { - id - } - } + mutation RETURN_CONTRACT( + $contractId: uuid! + $cccontract: cccontracts_set_input! + $courtesycarid: uuid! + $courtesycar: courtesycars_set_input! + ) { + update_cccontracts(where: { id: { _eq: $contractId } }, _set: $cccontract) { + returning { + id + } } + update_courtesycars(where: { id: { _eq: $courtesycarid } }, _set: $courtesycar) { + returning { + id + } + } + } `; export const QUERY_CONTRACT_BY_PK = gql` - query QUERY_CONTRACT_BY_PK($id: uuid!) { - cccontracts_by_pk(id: $id) { - actualreturn - agreementnumber - courtesycarid - driver_addr1 - driver_city - driver_addr2 - driver_dlexpiry - driver_dlnumber - driver_dlst - driver_dob - driver_fn - driver_ln - driver_ph1 - driver_state - driver_zip - id - jobid - dailyrate - actax - dailyfreekm - refuelcharge - excesskmrate - cleanupcharge - damagewaiver - federaltax - statetax - localtax - coverage - fuelin - fuelout - damage - job { - id + query QUERY_CONTRACT_BY_PK($id: uuid!) { + cccontracts_by_pk(id: $id) { + actualreturn + agreementnumber + courtesycarid + driver_addr1 + driver_city + driver_addr2 + driver_dlexpiry + driver_dlnumber + driver_dlst + driver_dob + driver_fn + driver_ln + driver_ph1 + driver_state + driver_zip + id + jobid + dailyrate + actax + dailyfreekm + refuelcharge + excesskmrate + cleanupcharge + damagewaiver + federaltax + statetax + localtax + coverage + fuelin + fuelout + damage + job { + id - ro_number - v_make_desc - v_model_desc - ownr_fn - ownr_ln - ownr_co_nm - clm_no - scheduled_completion - ownerid - vehicleid - owner { - ownr_fn - ownr_ln - id - ownr_co_nm - } - vehicle { - id - v_make_desc - v_model_desc - v_model_yr - v_vin - } - } - kmend - kmstart - scheduledreturn - start - status - updated_at - courtesycar { - id - fleetnumber - make - model - year - plate - } + ro_number + v_make_desc + v_model_desc + ownr_fn + ownr_ln + ownr_co_nm + clm_no + scheduled_completion + ownerid + vehicleid + owner { + ownr_fn + ownr_ln + id + ownr_co_nm } + vehicle { + id + v_make_desc + v_model_desc + v_model_yr + v_vin + } + } + kmend + kmstart + scheduledreturn + start + status + updated_at + courtesycar { + id + fleetnumber + make + model + year + plate + } } + } `; export const QUERY_ACTIVE_CONTRACTS_PAGINATED = gql` - query QUERY_ACTIVE_CONTRACTS_PAGINATED( - $search: String - $offset: Int - $limit: Int - $order: [cccontracts_order_by!]! - ) { - search_cccontracts( - args: { search: $search } - offset: $offset - limit: $limit - order_by: $order - ) { - agreementnumber - courtesycarid - driver_fn - driver_ln - driver_ph1 - id - jobid - job { - id - ro_number - ownr_fn - ownr_ln - ownr_co_nm - } - scheduledreturn - actualreturn - start - status - courtesycar { - id - fleetnumber - make - model - year - plate - } - } - search_cccontracts_aggregate(args: { search: $search }) { - aggregate { - count(distinct: true) - } - } + query QUERY_ACTIVE_CONTRACTS_PAGINATED($search: String, $offset: Int, $limit: Int, $order: [cccontracts_order_by!]!) { + search_cccontracts(args: { search: $search }, offset: $offset, limit: $limit, order_by: $order) { + agreementnumber + courtesycarid + driver_fn + driver_ln + driver_ph1 + id + jobid + job { + id + ro_number + ownr_fn + ownr_ln + ownr_co_nm + } + scheduledreturn + actualreturn + start + status + courtesycar { + id + fleetnumber + make + model + year + plate + } } + search_cccontracts_aggregate(args: { search: $search }) { + aggregate { + count(distinct: true) + } + } + } `; export const FIND_CONTRACT = gql` - query FIND_CONTRACT($plate: String, $time: timestamptz!) { - cccontracts( - where: { - _or: [ - { actualreturn: { _gte: $time } } - { actualreturn: { _is_null: true } } - ] - start: { _lte: $time } - courtesycar: { plate: { _eq: $plate } } - } - ) { - agreementnumber - courtesycarid - driver_fn - driver_ln - driver_ph1 - id - jobid - job { - id - ro_number - ownr_fn - ownr_ln - ownr_co_nm - } - scheduledreturn - actualreturn - start - status - courtesycar { - id - fleetnumber - make - model - year - plate - } - } + query FIND_CONTRACT($plate: String, $time: timestamptz!) { + cccontracts( + where: { + _or: [{ actualreturn: { _gte: $time } }, { actualreturn: { _is_null: true } }] + start: { _lte: $time } + courtesycar: { plate: { _eq: $plate } } + } + ) { + agreementnumber + courtesycarid + driver_fn + driver_ln + driver_ph1 + id + jobid + job { + id + ro_number + ownr_fn + ownr_ln + ownr_co_nm + } + scheduledreturn + actualreturn + start + status + courtesycar { + id + fleetnumber + make + model + year + plate + } } + } `; diff --git a/client/src/graphql/conversations.queries.js b/client/src/graphql/conversations.queries.js index 7031cac5f..f482cc325 100644 --- a/client/src/graphql/conversations.queries.js +++ b/client/src/graphql/conversations.queries.js @@ -1,134 +1,116 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const UNREAD_CONVERSATION_COUNT = gql` - query UNREAD_CONVERSATION_COUNT { - messages_aggregate( - where: { read: { _eq: false }, isoutbound: { _eq: false } } - ) { - aggregate { - count - } - } + query UNREAD_CONVERSATION_COUNT { + messages_aggregate(where: { read: { _eq: false }, isoutbound: { _eq: false } }) { + aggregate { + count + } } + } `; export const CONVERSATION_LIST_QUERY = gql` - query CONVERSATION_LIST_QUERY($offset: Int!) { - conversations( - order_by: { updated_at: desc } - limit: 50 - offset: $offset - where: { archived: { _eq: false } } - ) { - 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 - } - } + query CONVERSATION_LIST_QUERY($offset: Int!) { + conversations(order_by: { updated_at: desc }, limit: 50, offset: $offset, where: { archived: { _eq: false } }) { + 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 + } + } } + } `; export const CONVERSATION_SUBSCRIPTION_BY_PK = gql` - subscription CONVERSATION_SUBSCRIPTION_BY_PK($conversationId: uuid!) { - messages( - order_by: { created_at: asc_nulls_first } - where: { conversationid: { _eq: $conversationId } } - ) { - id - status - text - isoutbound - image - image_path - userid - created_at - read - } + subscription CONVERSATION_SUBSCRIPTION_BY_PK($conversationId: uuid!) { + messages(order_by: { created_at: asc_nulls_first }, where: { conversationid: { _eq: $conversationId } }) { + id + status + text + isoutbound + image + image_path + userid + created_at + read } + } `; export const GET_CONVERSATION_DETAILS = gql` - query GET_CONVERSATION_DETAILS($conversationId: uuid!) { - conversations_by_pk(id: $conversationId) { - id - phone_num - archived - label - job_conversations { - jobid - conversationid - job { - id - ownr_fn - ownr_ln - ownr_co_nm - ro_number - } - } + query GET_CONVERSATION_DETAILS($conversationId: uuid!) { + conversations_by_pk(id: $conversationId) { + id + phone_num + archived + label + job_conversations { + jobid + conversationid + job { + id + ownr_fn + ownr_ln + ownr_co_nm + ro_number } + } } + } `; export const CONVERSATION_ID_BY_PHONE = gql` - query CONVERSATION_ID_BY_PHONE($phone: String!) { - conversations(where: { phone_num: { _eq: $phone } }) { - id - job_conversations { - jobid - id - } - } + query CONVERSATION_ID_BY_PHONE($phone: String!) { + conversations(where: { phone_num: { _eq: $phone } }) { + id + job_conversations { + jobid + id + } } + } `; export const CREATE_CONVERSATION = gql` - mutation CREATE_CONVERSATION($conversation: [conversations_insert_input!]!) { - insert_conversations(objects: $conversation) { - returning { - id - } - } + mutation CREATE_CONVERSATION($conversation: [conversations_insert_input!]!) { + insert_conversations(objects: $conversation) { + returning { + id + } } + } `; export const TOGGLE_CONVERSATION_ARCHIVE = gql` - mutation TOGGLE_CONVERSATION_ARCHIVE($id: uuid!, $archived: Boolean) { - update_conversations_by_pk( - pk_columns: { id: $id } - _set: { archived: $archived } - ) { - archived - id - } + mutation TOGGLE_CONVERSATION_ARCHIVE($id: uuid!, $archived: Boolean) { + update_conversations_by_pk(pk_columns: { id: $id }, _set: { archived: $archived }) { + archived + id } + } `; export const UPDATE_CONVERSATION_LABEL = gql` - mutation UPDATE_CONVERSATION_LABEL($id: uuid!, $label: String) { - update_conversations_by_pk( - pk_columns: { id: $id } - _set: { label: $label } - ) { - label - id - } + mutation UPDATE_CONVERSATION_LABEL($id: uuid!, $label: String) { + update_conversations_by_pk(pk_columns: { id: $id }, _set: { label: $label }) { + label + id } + } `; diff --git a/client/src/graphql/courtesy-car.queries.js b/client/src/graphql/courtesy-car.queries.js index 24e7040e2..173cb4e19 100644 --- a/client/src/graphql/courtesy-car.queries.js +++ b/client/src/graphql/courtesy-car.queries.js @@ -1,174 +1,160 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const INSERT_NEW_COURTESY_CAR = gql` - mutation INSERT_NEW_COURTESY_CAR( - $courtesycar: [courtesycars_insert_input!]! - ) { - insert_courtesycars(objects: $courtesycar) { - returning { - id - } - } + mutation INSERT_NEW_COURTESY_CAR($courtesycar: [courtesycars_insert_input!]!) { + insert_courtesycars(objects: $courtesycar) { + returning { + id + } } + } `; export const QUERY_AVAILABLE_CC = gql` - query QUERY_AVAILABLE_CC($today: date) { - courtesycars( - where: { - _or: [ - { serviceenddate: { _is_null: true } } - { serviceenddate: { _gt: $today } } - ] - status: { _eq: "courtesycars.status.in" } - } - order_by: { fleetnumber: asc } - ) { - color - dailycost - damage - fleetnumber - fuel - id - insuranceexpires - make - mileage - model - notes - nextservicekm - nextservicedate - plate - readiness - status - year - } + query QUERY_AVAILABLE_CC($today: date) { + courtesycars( + where: { + _or: [{ serviceenddate: { _is_null: true } }, { serviceenddate: { _gt: $today } }] + status: { _eq: "courtesycars.status.in" } + } + order_by: { fleetnumber: asc } + ) { + color + dailycost + damage + fleetnumber + fuel + id + insuranceexpires + make + mileage + model + notes + nextservicekm + nextservicedate + plate + readiness + status + year } + } `; export const CHECK_CC_FLEET_NUMBER = gql` - query CHECK_VENDOR_NAME($name: String!) { - courtesycars_aggregate(where: { fleetnumber: { _ilike: $name } }) { - aggregate { - count - } - nodes { - id - } - } + query CHECK_VENDOR_NAME($name: String!) { + courtesycars_aggregate(where: { fleetnumber: { _ilike: $name } }) { + aggregate { + count + } + nodes { + id + } } + } `; export const QUERY_ALL_CC = gql` - query QUERY_ALL_CC { - courtesycars(order_by: { fleetnumber: asc }) { - color - created_at - dailycost - damage - fleetnumber - fuel - id - insuranceexpires - leaseenddate - make - mileage - model - nextservicedate - nextservicekm - notes - plate - purchasedate - readiness - registrationexpires - serviceenddate - servicestartdate - status - vin - year - cccontracts( - where: { status: { _eq: "contracts.status.out" } } - order_by: { contract_date: desc } - limit: 1 - ) { - id - scheduledreturn - job { - id - ownr_fn - ownr_ln - ownr_co_nm - ro_number - } - } + query QUERY_ALL_CC { + courtesycars(order_by: { fleetnumber: asc }) { + color + created_at + dailycost + damage + fleetnumber + fuel + id + insuranceexpires + leaseenddate + make + mileage + model + nextservicedate + nextservicekm + notes + plate + purchasedate + readiness + registrationexpires + serviceenddate + servicestartdate + status + vin + year + cccontracts(where: { status: { _eq: "contracts.status.out" } }, order_by: { contract_date: desc }, limit: 1) { + id + scheduledreturn + job { + id + ownr_fn + ownr_ln + ownr_co_nm + ro_number } + } } + } `; export const QUERY_CC_BY_PK = gql` - query QUERY_CC_BY_PK( - $id: uuid! - $offset: Int - $limit: Int - $order: [cccontracts_order_by!]! - ) { - courtesycars_by_pk(id: $id) { - bodyshopid - color - created_at - dailycost - damage - fleetnumber - fuel - id - insuranceexpires - leaseenddate - make - mileage - model - nextservicedate - nextservicekm - notes - plate - purchasedate - readiness - registrationexpires - serviceenddate - servicestartdate - status - vin - year - cccontracts_aggregate { - aggregate { - count(distinct: true) - } - } - cccontracts(offset: $offset, limit: $limit, order_by: $order) { - agreementnumber - driver_fn - driver_ln - id - kmstart - kmend - scheduledreturn - start - status - job { - id - ownr_ln - ownr_fn - ownr_co_nm - ro_number - } - } + query QUERY_CC_BY_PK($id: uuid!, $offset: Int, $limit: Int, $order: [cccontracts_order_by!]!) { + courtesycars_by_pk(id: $id) { + bodyshopid + color + created_at + dailycost + damage + fleetnumber + fuel + id + insuranceexpires + leaseenddate + make + mileage + model + nextservicedate + nextservicekm + notes + plate + purchasedate + readiness + registrationexpires + serviceenddate + servicestartdate + status + vin + year + cccontracts_aggregate { + aggregate { + count(distinct: true) } + } + cccontracts(offset: $offset, limit: $limit, order_by: $order) { + agreementnumber + driver_fn + driver_ln + id + kmstart + kmend + scheduledreturn + start + status + job { + id + ownr_ln + ownr_fn + ownr_co_nm + ro_number + } + } } + } `; export const UPDATE_CC = gql` - mutation UPDATE_CC($ccId: uuid!, $cc: courtesycars_set_input!) { - update_courtesycars(where: { id: { _eq: $ccId } }, _set: $cc) { - returning { - id - } - } + mutation UPDATE_CC($ccId: uuid!, $cc: courtesycars_set_input!) { + update_courtesycars(where: { id: { _eq: $ccId } }, _set: $cc) { + returning { + id + } } + } `; diff --git a/client/src/graphql/csi.queries.js b/client/src/graphql/csi.queries.js index 4f74e7be3..d2b62af43 100644 --- a/client/src/graphql/csi.queries.js +++ b/client/src/graphql/csi.queries.js @@ -1,95 +1,94 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_SURVEY = gql` - query QUERY_SURVEY($surveyId: uuid!) { - csi_by_pk(id: $surveyId) { - relateddata - valid - validuntil - id - csiquestion { - id - config - } - } + query QUERY_SURVEY($surveyId: uuid!) { + csi_by_pk(id: $surveyId) { + relateddata + valid + validuntil + id + csiquestion { + id + config + } } + } `; export const COMPLETE_SURVEY = gql` - mutation COMPLETE_SURVEY($surveyId: uuid!, $survey: csi_set_input) { - update_csi(where: { id: { _eq: $surveyId } }, _set: $survey) { - affected_rows - } + mutation COMPLETE_SURVEY($surveyId: uuid!, $survey: csi_set_input) { + update_csi(where: { id: { _eq: $surveyId } }, _set: $survey) { + affected_rows } + } `; export const GET_ALL_QUESTION_SETS = gql` - query GET_ALL_QUESTION_SETS { - csiquestions(order_by: { created_at: desc }) { - id - created_at - config - current - csis_aggregate { - aggregate { - count - } - } + query GET_ALL_QUESTION_SETS { + csiquestions(order_by: { created_at: desc }) { + id + created_at + config + current + csis_aggregate { + aggregate { + count } + } } + } `; export const GET_CURRENT_QUESTIONSET_ID = gql` - query GET_CURRENT_QUESTIONSET_ID { - csiquestions(where: { current: { _eq: true } }) { - id - } + query GET_CURRENT_QUESTIONSET_ID { + csiquestions(where: { current: { _eq: true } }) { + id } + } `; export const INSERT_CSI = gql` - mutation INSERT_CSI($csiInput: [csi_insert_input!]!) { - insert_csi(objects: $csiInput) { - returning { - id - } - } + mutation INSERT_CSI($csiInput: [csi_insert_input!]!) { + insert_csi(objects: $csiInput) { + returning { + id + } } + } `; export const QUERY_CSI_RESPONSE_PAGINATED = gql` - query QUERY_CSI_RESPONSE_PAGINATED{ - - csi(order_by: { completedon: desc_nulls_last }) { - id - completedon - job { - ownr_fn - ownr_ln - ownerid - ro_number - id - } - } - csi_aggregate { - aggregate { - count(distinct: true) - } - } + query QUERY_CSI_RESPONSE_PAGINATED { + csi(order_by: { completedon: desc_nulls_last }) { + id + completedon + job { + ownr_fn + ownr_ln + ownerid + ro_number + id + } } + csi_aggregate { + aggregate { + count(distinct: true) + } + } + } `; export const QUERY_CSI_RESPONSE_BY_PK = gql` - query QUERY_CSI_RESPONSE_BY_PK($id: uuid!) { - csi_by_pk(id: $id) { - completedon - relateddata - valid - validuntil - id - response - csiquestion { - id - config - } - } + query QUERY_CSI_RESPONSE_BY_PK($id: uuid!) { + csi_by_pk(id: $id) { + completedon + relateddata + valid + validuntil + id + response + csiquestion { + id + config + } } + } `; diff --git a/client/src/graphql/dms.queries.js b/client/src/graphql/dms.queries.js index 52decbfb0..eaa0a82a8 100644 --- a/client/src/graphql/dms.queries.js +++ b/client/src/graphql/dms.queries.js @@ -1,13 +1,13 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const SEARCH_DMS_VEHICLES = gql` - query SEARCH_DMS_VEHICLES($search: String) { - search_dms_vehicles(args: { search: $search }) { - id - make - makecode - model - modelcode - } + query SEARCH_DMS_VEHICLES($search: String) { + search_dms_vehicles(args: { search: $search }) { + id + make + makecode + model + modelcode } + } `; diff --git a/client/src/graphql/documents.queries.js b/client/src/graphql/documents.queries.js index a187e9529..69318ee4a 100644 --- a/client/src/graphql/documents.queries.js +++ b/client/src/graphql/documents.queries.js @@ -1,128 +1,125 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const GET_DOCUMENT_BY_PK = gql` - query GET_DOCUMENT_BY_PK($documentId: uuid!) { - documents_by_pk(id: $documentId) { - id - name - key - type - size - takenat - extension - jobid - } + query GET_DOCUMENT_BY_PK($documentId: uuid!) { + documents_by_pk(id: $documentId) { + id + name + key + type + size + takenat + extension + jobid } + } `; export const GET_DOCUMENTS_BY_JOB = gql` - query GET_DOCUMENTS_BY_JOB($jobId: uuid!) { - jobs_by_pk(id: $jobId) { - id - ro_number - } - documents_aggregate(where: { jobid: { _eq: $jobId } }) { - aggregate { - sum { - size - } - } - } - documents(order_by: { takenat: desc }, where: { jobid: { _eq: $jobId } }) { - id - name - key - type - size - takenat - extension - bill { - id - invoice_number - date - vendor { - id - name - } - } - } + query GET_DOCUMENTS_BY_JOB($jobId: uuid!) { + jobs_by_pk(id: $jobId) { + id + ro_number } + documents_aggregate(where: { jobid: { _eq: $jobId } }) { + aggregate { + sum { + size + } + } + } + documents(order_by: { takenat: desc }, where: { jobid: { _eq: $jobId } }) { + id + name + key + type + size + takenat + extension + bill { + id + invoice_number + date + vendor { + id + name + } + } + } + } `; export const GET_DOC_SIZE_BY_JOB = gql` - query GET_DOC_SIZE_BY_JOB($jobId: uuid!) { - documents_aggregate(where: { jobid: { _eq: $jobId } }) { - aggregate { - sum { - size - } - } + query GET_DOC_SIZE_BY_JOB($jobId: uuid!) { + documents_aggregate(where: { jobid: { _eq: $jobId } }) { + aggregate { + sum { + size } + } } + } `; export const INSERT_NEW_DOCUMENT = gql` - mutation INSERT_NEW_DOCUMENT($docInput: [documents_insert_input!]!) { - insert_documents(objects: $docInput) { - returning { - id - name - key - size - takenat - } - } + mutation INSERT_NEW_DOCUMENT($docInput: [documents_insert_input!]!) { + insert_documents(objects: $docInput) { + returning { + id + name + key + size + takenat + } } + } `; export const DELETE_DOCUMENT = gql` - mutation DELETE_DOCUMENT($id: uuid) { - delete_documents(where: { id: { _eq: $id } }) { - returning { - id - } - } + mutation DELETE_DOCUMENT($id: uuid) { + delete_documents(where: { id: { _eq: $id } }) { + returning { + id + } } + } `; export const DELETE_DOCUMENTS = gql` - mutation DELETE_DOCUMENTS($ids: [uuid!]!) { - delete_documents(where: { id: { _in: $ids } }) { - returning { - id - } - } + mutation DELETE_DOCUMENTS($ids: [uuid!]!) { + delete_documents(where: { id: { _in: $ids } }) { + returning { + id + } } + } `; export const QUERY_TEMPORARY_DOCS = gql` - query QUERY_TEMPORARY_DOCS { - documents( - where: { jobid: { _is_null: true } } - order_by: { takenat: desc } - ) { - id - name - key - type - extension - size - takenat - } + query QUERY_TEMPORARY_DOCS { + documents(where: { jobid: { _is_null: true } }, order_by: { takenat: desc }) { + id + name + key + type + extension + size + takenat } + } `; export const UPDATE_DOCUMENT = gql` - mutation UPDATE_DOCUMENT($id: uuid!, $document: documents_set_input!) { - update_documents_by_pk(pk_columns: { id: $id }, _set: $document) { - billid - bodyshopid - extension - id - jobid - name - type - key - size - takenat - } + mutation UPDATE_DOCUMENT($id: uuid!, $document: documents_set_input!) { + update_documents_by_pk(pk_columns: { id: $id }, _set: $document) { + billid + bodyshopid + extension + id + jobid + name + type + key + size + takenat } + } `; diff --git a/client/src/graphql/employee_teams.queries.js b/client/src/graphql/employee_teams.queries.js index 36cc0db8f..bfb5104de 100644 --- a/client/src/graphql/employee_teams.queries.js +++ b/client/src/graphql/employee_teams.queries.js @@ -1,96 +1,93 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_TEAMS = gql` - query QUERY_TEAMS { - employee_teams(order_by: { name: asc }) { - id - max_load - name - employee_team_members { - id - employeeid - labor_rates - percentage - } - } + query QUERY_TEAMS { + employee_teams(order_by: { name: asc }) { + id + max_load + name + employee_team_members { + id + employeeid + labor_rates + percentage + } } + } `; export const UPDATE_EMPLOYEE_TEAM = gql` - mutation UPDATE_EMPLOYEE_TEAM( - $employeeTeamId: uuid! - $employeeTeam: employee_teams_set_input - $teamMemberDeletes: [uuid!] - $teamMemberUpdates: [employee_team_members_updates!]! - $teamMemberInserts: [employee_team_members_insert_input!]! - ) { - update_employee_team_members_many(updates: $teamMemberUpdates) { - returning { - employeeid - id - labor_rates - percentage - } - } - delete_employee_team_members(where: { id: { _in: $teamMemberDeletes } }) { - affected_rows - } - insert_employee_team_members(objects: $teamMemberInserts) { - returning { - employeeid - id - labor_rates - percentage - } - } - update_employee_teams_by_pk( - pk_columns: { id: $employeeTeamId } - _set: $employeeTeam - ) { - active - id - max_load - name - employee_team_members { - employeeid - id - labor_rates - percentage - } - } + mutation UPDATE_EMPLOYEE_TEAM( + $employeeTeamId: uuid! + $employeeTeam: employee_teams_set_input + $teamMemberDeletes: [uuid!] + $teamMemberUpdates: [employee_team_members_updates!]! + $teamMemberInserts: [employee_team_members_insert_input!]! + ) { + update_employee_team_members_many(updates: $teamMemberUpdates) { + returning { + employeeid + id + labor_rates + percentage + } } + delete_employee_team_members(where: { id: { _in: $teamMemberDeletes } }) { + affected_rows + } + insert_employee_team_members(objects: $teamMemberInserts) { + returning { + employeeid + id + labor_rates + percentage + } + } + update_employee_teams_by_pk(pk_columns: { id: $employeeTeamId }, _set: $employeeTeam) { + active + id + max_load + name + employee_team_members { + employeeid + id + labor_rates + percentage + } + } + } `; export const INSERT_EMPLOYEE_TEAM = gql` - mutation INSERT_EMPLOYEE_TEAM($employeeTeam: employee_teams_insert_input!) { - insert_employee_teams_one(object: $employeeTeam) { - active - id - max_load - name - employee_team_members { - employeeid - id - labor_rates - percentage - } - } + mutation INSERT_EMPLOYEE_TEAM($employeeTeam: employee_teams_insert_input!) { + insert_employee_teams_one(object: $employeeTeam) { + active + id + max_load + name + employee_team_members { + employeeid + id + labor_rates + percentage + } } + } `; export const QUERY_EMPLOYEE_TEAM_BY_ID = gql` - query QUERY_EMPLOYEE_TEAM_BY_ID($id: uuid!) { - employee_teams_by_pk(id: $id) { - active - id - max_load - name - employee_team_members { - employeeid - id - labor_rates - percentage - } - } + query QUERY_EMPLOYEE_TEAM_BY_ID($id: uuid!) { + employee_teams_by_pk(id: $id) { + active + id + max_load + name + employee_team_members { + employeeid + id + labor_rates + percentage + } } + } `; diff --git a/client/src/graphql/employees.queries.js b/client/src/graphql/employees.queries.js index 00755e58c..2f558e838 100644 --- a/client/src/graphql/employees.queries.js +++ b/client/src/graphql/employees.queries.js @@ -1,146 +1,144 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_EMPLOYEES = gql` - query QUERY_EMPLOYEES { - employees(order_by: { employee_number: asc }) { - active - employee_number - first_name - flat_rate - id - last_name - } + query QUERY_EMPLOYEES { + employees(order_by: { employee_number: asc }) { + active + employee_number + first_name + flat_rate + id + last_name } + } `; export const QUERY_EMPLOYEE_BY_ID = gql` - query QUERY_EMPLOYEE_BY_ID($id: uuid!) { - employees_by_pk(id: $id) { - last_name - id - first_name - employee_number - active - termination_date - hire_date - flat_rate - rates - pin - user_email - external_id - employee_vacations(order_by: { start: desc }) { - id - start - end - } - } + query QUERY_EMPLOYEE_BY_ID($id: uuid!) { + employees_by_pk(id: $id) { + last_name + id + first_name + employee_number + active + termination_date + hire_date + flat_rate + rates + pin + user_email + external_id + employee_vacations(order_by: { start: desc }) { + id + start + end + } } + } `; export const CHECK_EMPLOYEE_NUMBER = gql` - query CHECK_EMPLOYEE_NUMBER($employeenumber: String!) { - employees_aggregate( - where: { employee_number: { _ilike: $employeenumber } } - ) { - aggregate { - count - } - nodes { - id - } - } + query CHECK_EMPLOYEE_NUMBER($employeenumber: String!) { + employees_aggregate(where: { employee_number: { _ilike: $employeenumber } }) { + aggregate { + count + } + nodes { + id + } } + } `; export const QUERY_ACTIVE_EMPLOYEES = gql` - query QUERY_ACTIVE_EMPLOYEES { - employees(where: { active: { _eq: true } }) { - last_name - id - first_name - employee_number - active - termination_date - hire_date - flat_rate - rates - pin - user_email - } + query QUERY_ACTIVE_EMPLOYEES { + employees(where: { active: { _eq: true } }) { + last_name + id + first_name + employee_number + active + termination_date + hire_date + flat_rate + rates + pin + user_email } + } `; export const INSERT_EMPLOYEES = gql` - mutation INSERT_EMPLOYEES($employees: [employees_insert_input!]!) { - insert_employees(objects: $employees) { - returning { - last_name - id - first_name - employee_number - active - termination_date - hire_date - flat_rate - rates - pin - user_email - } - } + mutation INSERT_EMPLOYEES($employees: [employees_insert_input!]!) { + insert_employees(objects: $employees) { + returning { + last_name + id + first_name + employee_number + active + termination_date + hire_date + flat_rate + rates + pin + user_email + } } + } `; export const UPDATE_EMPLOYEE = gql` - mutation UPDATE_EMPLOYEE($id: uuid!, $employee: employees_set_input) { - update_employees(where: { id: { _eq: $id } }, _set: $employee) { - returning { - last_name - id - first_name - employee_number - active - termination_date - hire_date - flat_rate - rates - pin - user_email - } - } + mutation UPDATE_EMPLOYEE($id: uuid!, $employee: employees_set_input) { + update_employees(where: { id: { _eq: $id } }, _set: $employee) { + returning { + last_name + id + first_name + employee_number + active + termination_date + hire_date + flat_rate + rates + pin + user_email + } } + } `; export const DELETE_EMPLOYEE = gql` - mutation DELETE_EMPLOYEE($id: uuid!) { - delete_employees(where: { id: { _eq: $id } }) { - returning { - id - } - } + mutation DELETE_EMPLOYEE($id: uuid!) { + delete_employees(where: { id: { _eq: $id } }) { + returning { + id + } } + } `; export const QUERY_USERS_BY_EMAIL = gql` - query QUERY_USERS_BY_EMAIL($email: String!) { - users(where: { email: { _ilike: $email } }) { - email - } + query QUERY_USERS_BY_EMAIL($email: String!) { + users(where: { email: { _ilike: $email } }) { + email } + } `; export const INSERT_VACATION = gql` - mutation INSERT_VACATION($vacation: employee_vacation_insert_input!) { - insert_employee_vacation_one(object: $vacation) { - id - start - end - } + mutation INSERT_VACATION($vacation: employee_vacation_insert_input!) { + insert_employee_vacation_one(object: $vacation) { + id + start + end } + } `; export const DELETE_VACATION = gql` - mutation DELETE_VACATION($id: uuid!) { - delete_employee_vacation_by_pk(id: $id) { - id - } + mutation DELETE_VACATION($id: uuid!) { + delete_employee_vacation_by_pk(id: $id) { + id } + } `; diff --git a/client/src/graphql/inventory.queries.js b/client/src/graphql/inventory.queries.js index b849ab837..28f3412b5 100644 --- a/client/src/graphql/inventory.queries.js +++ b/client/src/graphql/inventory.queries.js @@ -1,181 +1,160 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const INSERT_INVENTORY_AND_CREDIT = gql` - mutation INSERT_INVENTORY_AND_CREDIT( - $inv: inventory_insert_input! - $cm: bills_insert_input! - $pol: parts_orders_insert_input! - $joblineId: uuid! - $joblineStatus: String - ) { - insert_inventory_one(object: $inv) { - id - } - insert_bills_one(object: $cm) { - id - } - insert_parts_orders_one(object: $pol) { - id - } - update_joblines_by_pk( - pk_columns: { id: $joblineId } - _set: { status: $joblineStatus } - ) { - id - status - } + mutation INSERT_INVENTORY_AND_CREDIT( + $inv: inventory_insert_input! + $cm: bills_insert_input! + $pol: parts_orders_insert_input! + $joblineId: uuid! + $joblineStatus: String + ) { + insert_inventory_one(object: $inv) { + id } + insert_bills_one(object: $cm) { + id + } + insert_parts_orders_one(object: $pol) { + id + } + update_joblines_by_pk(pk_columns: { id: $joblineId }, _set: { status: $joblineStatus }) { + id + status + } + } `; export const UPDATE_INVENTORY_LINES = gql` - mutation UPDATE_INVENTORY_LINES( - $InventoryIds: [uuid!]! - $consumedbybillid: uuid! - ) { - update_inventory( - where: { id: { _in: $InventoryIds } } - _set: { consumedbybillid: $consumedbybillid } - ) { - affected_rows - } + mutation UPDATE_INVENTORY_LINES($InventoryIds: [uuid!]!, $consumedbybillid: uuid!) { + update_inventory(where: { id: { _in: $InventoryIds } }, _set: { consumedbybillid: $consumedbybillid }) { + affected_rows } + } `; export const QUERY_OUTSTANDING_INVENTORY = gql` - query QUERY_OUTSTANDING_INVENTORY { - inventory( - where: { consumedbybillid: { _is_null: true } } - order_by: { line_desc: asc } - ) { - id - actual_cost - actual_price - quantity - billlineid - line_desc - comment - manualinvoicenumber - manualvendor - consumedbybillid - billline { - bill { - invoice_number - vendor { - name - } - } - } + query QUERY_OUTSTANDING_INVENTORY { + inventory(where: { consumedbybillid: { _is_null: true } }, order_by: { line_desc: asc }) { + id + actual_cost + actual_price + quantity + billlineid + line_desc + comment + manualinvoicenumber + manualvendor + consumedbybillid + billline { + bill { + invoice_number + vendor { + name + } } + } } + } `; export const QUERY_INVENTORY_PAGINATED = gql` - query QUERY_INVENTORY_PAGINATED( - $search: String - $offset: Int - $limit: Int - $order: [inventory_order_by!] - $consumedIsNull: Boolean + query QUERY_INVENTORY_PAGINATED( + $search: String + $offset: Int + $limit: Int + $order: [inventory_order_by!] + $consumedIsNull: Boolean + ) { + search_inventory( + args: { search: $search } + offset: $offset + limit: $limit + order_by: $order + where: { consumedbybillid: { _is_null: $consumedIsNull } } ) { - search_inventory( - args: { search: $search } - offset: $offset - limit: $limit - order_by: $order - where: { consumedbybillid: { _is_null: $consumedIsNull } } - ) { + id + line_desc + actual_price + actual_cost + comment + manualinvoicenumber + manualvendor + consumedbybillid + bill { + id + invoice_number + job { + ro_number + id + } + } + billline { + id + bill { + id + invoice_number + job { id - line_desc - actual_price - actual_cost - comment - manualinvoicenumber - manualvendor - consumedbybillid - bill { - id - invoice_number - job { - ro_number - id - } - } - billline { - id - bill { - id - invoice_number - job { - id - v_make_desc - v_model_desc - v_model_yr - } - vendor { - id - name - } - } - } - } - search_inventory_aggregate( - args: { search: $search } - where: { consumedbybillid: { _is_null: $consumedIsNull } } - ) { - aggregate { - count(distinct: true) - } + v_make_desc + v_model_desc + v_model_yr + } + vendor { + id + name + } } + } } + search_inventory_aggregate(args: { search: $search }, where: { consumedbybillid: { _is_null: $consumedIsNull } }) { + aggregate { + count(distinct: true) + } + } + } `; export const DELETE_INVENTORY_LINE = gql` - mutation DELETE_INVENTORY_LINE($lineId: uuid!) { - delete_inventory_by_pk(id: $lineId) { - id - } + mutation DELETE_INVENTORY_LINE($lineId: uuid!) { + delete_inventory_by_pk(id: $lineId) { + id } + } `; export const INSERT_INVENTORY_LINE = gql` - mutation INSERT_INVENTORY_LINE($inventoryItem: inventory_insert_input!) { - insert_inventory_one(object: $inventoryItem) { - id - line_desc - consumedbybillid - billlineid - actual_price - actual_cost - comment - manualinvoicenumber - manualvendor - bill { - invoice_number - } - } + mutation INSERT_INVENTORY_LINE($inventoryItem: inventory_insert_input!) { + insert_inventory_one(object: $inventoryItem) { + id + line_desc + consumedbybillid + billlineid + actual_price + actual_cost + comment + manualinvoicenumber + manualvendor + bill { + invoice_number + } } + } `; export const UPDATE_INVENTORY_LINE = gql` - mutation UPDATE_INVENTORY_LINE( - $inventoryId: uuid! - $inventoryItem: inventory_set_input! - ) { - update_inventory_by_pk( - pk_columns: { id: $inventoryId } - _set: $inventoryItem - ) { - id - line_desc - consumedbybillid - billlineid - actual_price - actual_cost - comment - manualinvoicenumber - manualvendor - bill { - invoice_number - } - } + mutation UPDATE_INVENTORY_LINE($inventoryId: uuid!, $inventoryItem: inventory_set_input!) { + update_inventory_by_pk(pk_columns: { id: $inventoryId }, _set: $inventoryItem) { + id + line_desc + consumedbybillid + billlineid + actual_price + actual_cost + comment + manualinvoicenumber + manualvendor + bill { + invoice_number + } } + } `; diff --git a/client/src/graphql/job-conversations.queries.js b/client/src/graphql/job-conversations.queries.js index 0a3fe6f3e..ab8e6fdf3 100644 --- a/client/src/graphql/job-conversations.queries.js +++ b/client/src/graphql/job-conversations.queries.js @@ -1,44 +1,37 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const INSERT_CONVERSATION_TAG = gql` - mutation INSERT_CONVERSATION_TAG($conversationId: uuid!, $jobId: uuid!) { - insert_job_conversations( - objects: { conversationid: $conversationId, jobid: $jobId } - on_conflict: { constraint: job_conversations_pkey, update_columns: jobid } - ) { - returning { - jobid - conversationid - id - conversation { - id - job_conversations { - id - jobid - conversationid - job { - ownr_fn - ownr_ln - ownr_co_nm - } - } - } + mutation INSERT_CONVERSATION_TAG($conversationId: uuid!, $jobId: uuid!) { + insert_job_conversations( + objects: { conversationid: $conversationId, jobid: $jobId } + on_conflict: { constraint: job_conversations_pkey, update_columns: jobid } + ) { + returning { + jobid + conversationid + id + conversation { + id + job_conversations { + id + jobid + conversationid + job { + ownr_fn + ownr_ln + ownr_co_nm } + } } + } } + } `; export const REMOVE_CONVERSATION_TAG = gql` - mutation REMOVE_CONVERSATION_TAG($conversationId: uuid!, $jobId: uuid!) { - delete_job_conversations( - where: { - _and: { - jobid: { _eq: $jobId } - conversationid: { _eq: $conversationId } - } - } - ) { - affected_rows - } + mutation REMOVE_CONVERSATION_TAG($conversationId: uuid!, $jobId: uuid!) { + delete_job_conversations(where: { _and: { jobid: { _eq: $jobId }, conversationid: { _eq: $conversationId } } }) { + affected_rows } + } `; diff --git a/client/src/graphql/jobs-lines.queries.js b/client/src/graphql/jobs-lines.queries.js index 28b4e7030..bb7b7cf20 100644 --- a/client/src/graphql/jobs-lines.queries.js +++ b/client/src/graphql/jobs-lines.queries.js @@ -1,291 +1,273 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const GET_ALL_JOBLINES_BY_PK = gql` - query GET_ALL_JOBLINES_BY_PK($id: uuid!) { - joblines(where: { jobid: { _eq: $id } }, order_by: { line_no: asc }) { - id - line_no - unq_seq - line_ind - line_desc - part_type - oem_partno - db_price - act_price - part_qty - mod_lbr_ty - db_hrs - mod_lb_hrs - lbr_op - lbr_amt - op_code_desc - status - notes - location - tax_part - manual_line - } + query GET_ALL_JOBLINES_BY_PK($id: uuid!) { + joblines(where: { jobid: { _eq: $id } }, order_by: { line_no: asc }) { + id + line_no + unq_seq + line_ind + line_desc + part_type + oem_partno + db_price + act_price + part_qty + mod_lbr_ty + db_hrs + mod_lb_hrs + lbr_op + lbr_amt + op_code_desc + status + notes + location + tax_part + manual_line } + } `; export const GET_LINE_TICKET_BY_PK = gql` - query GET_LINE_TICKET_BY_PK($id: uuid!) { - jobs_by_pk(id: $id) { - id - lbr_adjustments - converted - status - } - joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) { - id - line_desc - part_type - oem_partno - db_price - act_price - part_qty - mod_lbr_ty - db_hrs - mod_lb_hrs - lbr_op - lbr_amt - op_code_desc - convertedtolbr - convertedtolbr_data - } - timetickets(where: { jobid: { _eq: $id } }) { - actualhrs - ciecacode - cost_center - created_by - date - id - jobid - employeeid - memo - flat_rate - clockon - clockoff - rate - committed_at - commited_by - task_name - employee { - id - first_name - last_name - employee_number - } - productivehrs - } + query GET_LINE_TICKET_BY_PK($id: uuid!) { + jobs_by_pk(id: $id) { + id + lbr_adjustments + converted + status } + joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) { + id + line_desc + part_type + oem_partno + db_price + act_price + part_qty + mod_lbr_ty + db_hrs + mod_lb_hrs + lbr_op + lbr_amt + op_code_desc + convertedtolbr + convertedtolbr_data + } + timetickets(where: { jobid: { _eq: $id } }) { + actualhrs + ciecacode + cost_center + created_by + date + id + jobid + employeeid + memo + flat_rate + clockon + clockoff + rate + committed_at + commited_by + task_name + employee { + id + first_name + last_name + employee_number + } + productivehrs + } + } `; export const GET_JOB_INFO_DRAW_CALCULATIONS = gql` - query GET_JOB_INFO_DRAW_CALCULATIONS($id: uuid!) { - jobs_by_pk(id: $id) { - id - lbr_adjustments - converted - rate_lab - rate_lad - rate_laa - rate_la1 - rate_la2 - rate_la3 - rate_la4 - rate_lau - rate_lar - rate_lag - rate_laf - rate_lam - } - timetickets(where: { jobid: { _eq: $id } }) { - actualhrs - ciecacode - cost_center - date - id - jobid - employeeid - memo - flat_rate - clockon - clockoff - rate - employee { - id - first_name - last_name - employee_number - } - productivehrs - } - joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) { - id - line_desc - part_type - oem_partno - db_price - act_price - part_qty - mod_lbr_ty - db_hrs - mod_lb_hrs - lbr_op - lbr_amt - op_code_desc - convertedtolbr - convertedtolbr_data - } + query GET_JOB_INFO_DRAW_CALCULATIONS($id: uuid!) { + jobs_by_pk(id: $id) { + id + lbr_adjustments + converted + rate_lab + rate_lad + rate_laa + rate_la1 + rate_la2 + rate_la3 + rate_la4 + rate_lau + rate_lar + rate_lag + rate_laf + rate_lam } + timetickets(where: { jobid: { _eq: $id } }) { + actualhrs + ciecacode + cost_center + date + id + jobid + employeeid + memo + flat_rate + clockon + clockoff + rate + employee { + id + first_name + last_name + employee_number + } + productivehrs + } + joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) { + id + line_desc + part_type + oem_partno + db_price + act_price + part_qty + mod_lbr_ty + db_hrs + mod_lb_hrs + lbr_op + lbr_amt + op_code_desc + convertedtolbr + convertedtolbr_data + } + } `; export const UPDATE_JOB_LINE_STATUS = gql` - mutation UPDATE_JOB_LINE_STATUS( - $ids: [uuid!]! - $status: String! - $location: String - ) { - update_joblines( - where: { id: { _in: $ids } } - _set: { status: $status, location: $location } - ) { - affected_rows - returning { - id - status - location - } - } + mutation UPDATE_JOB_LINE_STATUS($ids: [uuid!]!, $status: String!, $location: String) { + update_joblines(where: { id: { _in: $ids } }, _set: { status: $status, location: $location }) { + affected_rows + returning { + id + status + location + } } + } `; export const INSERT_NEW_JOB_LINE = gql` - mutation INSERT_NEW_JOB_LINE($lineInput: [joblines_insert_input!]!) { - insert_joblines(objects: $lineInput) { - returning { - id - } - } + mutation INSERT_NEW_JOB_LINE($lineInput: [joblines_insert_input!]!) { + insert_joblines(objects: $lineInput) { + returning { + id + } } + } `; export const RECEIVE_PARTS_LINE = gql` - mutation RECEIVE_PARTS_LINE( - $lineId: uuid! - $line: joblines_set_input! - $orderLineId: uuid! - $orderLine: parts_order_lines_set_input! - ) { - update_joblines(where: { id: { _eq: $lineId } }, _set: $line) { - returning { - id - notes - mod_lbr_ty - part_qty - db_price - act_price - line_desc - oem_partno - notes - location - status - removed - } - } - update_parts_order_lines_by_pk( - pk_columns: { id: $orderLineId } - _set: $orderLine - ) { - id - line_desc - backordered_on - backordered_eta - status - } + mutation RECEIVE_PARTS_LINE( + $lineId: uuid! + $line: joblines_set_input! + $orderLineId: uuid! + $orderLine: parts_order_lines_set_input! + ) { + update_joblines(where: { id: { _eq: $lineId } }, _set: $line) { + returning { + id + notes + mod_lbr_ty + part_qty + db_price + act_price + line_desc + oem_partno + notes + location + status + removed + } } + update_parts_order_lines_by_pk(pk_columns: { id: $orderLineId }, _set: $orderLine) { + id + line_desc + backordered_on + backordered_eta + status + } + } `; export const UPDATE_JOB_LINE_SUBLET = gql` - mutation UPDATE_JOB_LINE_SUBLET( - $lineId: uuid! - $line: joblines_set_input! - $now: timestamptz! - $jobId: uuid! - ) { - update_jobs_by_pk(pk_columns: { id: $jobId }, _set: { updated_at: $now }) { - id - updated_at - } - update_joblines(where: { id: { _eq: $lineId } }, _set: $line) { - returning { - id - sublet_completed - sublet_ignored - } - } + mutation UPDATE_JOB_LINE_SUBLET($lineId: uuid!, $line: joblines_set_input!, $now: timestamptz!, $jobId: uuid!) { + update_jobs_by_pk(pk_columns: { id: $jobId }, _set: { updated_at: $now }) { + id + updated_at } + update_joblines(where: { id: { _eq: $lineId } }, _set: $line) { + returning { + id + sublet_completed + sublet_ignored + } + } + } `; export const UPDATE_JOB_LINE = gql` - mutation UPDATE_JOB_LINE($lineId: uuid!, $line: joblines_set_input!) { - update_joblines(where: { id: { _eq: $lineId } }, _set: $line) { - returning { - id - notes - mod_lbr_ty - part_qty - db_price - act_price - line_desc - line_no - oem_partno - notes - location - status - removed - convertedtolbr - convertedtolbr_data - assigned_team - } - } + mutation UPDATE_JOB_LINE($lineId: uuid!, $line: joblines_set_input!) { + update_joblines(where: { id: { _eq: $lineId } }, _set: $line) { + returning { + id + notes + mod_lbr_ty + part_qty + db_price + act_price + line_desc + line_no + oem_partno + notes + location + status + removed + convertedtolbr + convertedtolbr_data + assigned_team + } } + } `; export const GET_JOB_LINES_TO_ENTER_BILL = gql` - query GET_JOB_LINES_TO_ENTER_BILL($id: uuid!) { - joblines( - where: { jobid: { _eq: $id } } - order_by: { act_price: desc_nulls_last } - ) { - removed - id - line_desc - part_type - oem_partno - alt_partno - db_price - act_price - part_qty - mod_lbr_ty - db_hrs - mod_lb_hrs - lbr_op - lbr_amt - op_code_desc - alt_partno - assigned_team - } - jobs_by_pk(id: $id) { - id - status - ious { - id - ro_number - } - v_make_desc - } + query GET_JOB_LINES_TO_ENTER_BILL($id: uuid!) { + joblines(where: { jobid: { _eq: $id } }, order_by: { act_price: desc_nulls_last }) { + removed + id + line_desc + part_type + oem_partno + alt_partno + db_price + act_price + part_qty + mod_lbr_ty + db_hrs + mod_lb_hrs + lbr_op + lbr_amt + op_code_desc + alt_partno + assigned_team } + jobs_by_pk(id: $id) { + id + status + ious { + id + ro_number + } + v_make_desc + } + } `; // oem_partno: { // _neq: ""; @@ -295,14 +277,12 @@ export const GET_JOB_LINES_TO_ENTER_BILL = gql` // } export const generateJobLinesUpdatesForInvoicing = (joblines) => { - const updates = joblines.reduce((acc, jl, idx) => { - return ( - acc + - `a${idx}:update_joblines(where: {id: {_eq: "${ - jl.id - }"}}, _set: {profitcenter_labor: "${ - jl.profitcenter_labor || "" - }", profitcenter_part: "${jl.profitcenter_part || ""}"}) { + const updates = joblines.reduce((acc, jl, idx) => { + return ( + acc + + `a${idx}:update_joblines(where: {id: {_eq: "${jl.id}"}}, _set: {profitcenter_labor: "${ + jl.profitcenter_labor || "" + }", profitcenter_part: "${jl.profitcenter_part || ""}"}) { returning { line_desc profitcenter_part @@ -310,10 +290,10 @@ export const generateJobLinesUpdatesForInvoicing = (joblines) => { id } }` - ); - }, ""); + ); + }, ""); - return gql` + return gql` mutation UPDATE_JOBLINES_FOR_INVOICING{ ${updates} } @@ -321,51 +301,43 @@ export const generateJobLinesUpdatesForInvoicing = (joblines) => { }; export const DELETE_JOB_LINE_BY_PK = gql` - mutation DELETE_JOB_LINE_BY_PK($joblineId: uuid!) { - update_joblines_by_pk( - pk_columns: { id: $joblineId } - _set: { removed: true } - ) { - removed - id - } + mutation DELETE_JOB_LINE_BY_PK($joblineId: uuid!) { + update_joblines_by_pk(pk_columns: { id: $joblineId }, _set: { removed: true }) { + removed + id } + } `; export const UPDATE_JOB_LINES_IOU = gql` - mutation UPDATE_JOB_LINES_IOU($ids: [uuid!]!) { - update_joblines(where: { id: { _in: $ids } }, _set: { ioucreated: true }) { - returning { - ioucreated - id - } - } + mutation UPDATE_JOB_LINES_IOU($ids: [uuid!]!) { + update_joblines(where: { id: { _in: $ids } }, _set: { ioucreated: true }) { + returning { + ioucreated + id + } } + } `; export const UPDATE_LINE_PPC = gql` - mutation UPDATE_LINE_PPC($id: uuid!, $jobline: joblines_set_input) { - update_joblines_by_pk(pk_columns: { id: $id }, _set: $jobline) { - jobid - id - act_price_before_ppc - act_price - } + mutation UPDATE_LINE_PPC($id: uuid!, $jobline: joblines_set_input) { + update_joblines_by_pk(pk_columns: { id: $id }, _set: $jobline) { + jobid + id + act_price_before_ppc + act_price } + } `; export const UPDATE_LINE_BULK_ASSIGN = gql` - mutation UPDATE_LINE_BULK_ASSIGN( - $ids: [uuid!]! - $jobline: joblines_set_input - ) { - update_joblines_many( - updates: { _set: $jobline, where: { id: { _in: $ids } } } - ) { - returning { - id - assigned_team - } - } + mutation UPDATE_LINE_BULK_ASSIGN($ids: [uuid!]!, $jobline: joblines_set_input) { + update_joblines_many(updates: { _set: $jobline, where: { id: { _in: $ids } } }) { + returning { + id + assigned_team + } } + } `; diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 174f6ad04..1dd4ea175 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -1,1206 +1,1156 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql` - query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED( - $offset: Int - $limit: Int - $order: [jobs_order_by!] - $statuses: [String!]! - $isConverted: Boolean + query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED( + $offset: Int + $limit: Int + $order: [jobs_order_by!] + $statuses: [String!]! + $isConverted: Boolean + ) { + jobs( + offset: $offset + limit: $limit + where: { status: { _in: $statuses }, converted: { _eq: $isConverted } } + order_by: $order ) { - jobs( - offset: $offset - limit: $limit - where: { status: { _in: $statuses }, converted: { _eq: $isConverted } } - order_by: $order - ) { - iouparent - ownr_fn - ownr_ln - ownr_co_nm - ownr_ph1 - ownr_ph2 - ownr_ea - ownerid - comment - plate_no - plate_st - v_vin - v_model_yr - v_model_desc - v_make_desc - v_color - vehicleid - actual_completion - actual_delivery - actual_in - production_vars - id - ins_co_nm - clm_no - po_number - clm_total - owner_owing - ro_number - scheduled_completion - scheduled_in - scheduled_delivery - status - updated_at - ded_amt - suspended - est_ct_fn - est_ct_ln - } - jobs_aggregate(where: { status: { _in: $statuses } }) { - aggregate { - count(distinct: true) - } - } + iouparent + ownr_fn + ownr_ln + ownr_co_nm + ownr_ph1 + ownr_ph2 + ownr_ea + ownerid + comment + plate_no + plate_st + v_vin + v_model_yr + v_model_desc + v_make_desc + v_color + vehicleid + actual_completion + actual_delivery + actual_in + production_vars + id + ins_co_nm + clm_no + po_number + clm_total + owner_owing + ro_number + scheduled_completion + scheduled_in + scheduled_delivery + status + updated_at + ded_amt + suspended + est_ct_fn + est_ct_ln } + jobs_aggregate(where: { status: { _in: $statuses } }) { + aggregate { + count(distinct: true) + } + } + } `; export const QUERY_ALL_ACTIVE_JOBS = gql` - query QUERY_ALL_ACTIVE_JOBS($statuses: [String!]!, $isConverted: Boolean) { - jobs( - where: { status: { _in: $statuses }, converted: { _eq: $isConverted } } - order_by: { created_at: desc } - ) { - iouparent - ownr_fn - ownr_ln - ownr_co_nm - ownr_ph1 - ownr_ph2 - ownr_ea - ownerid - comment - plate_no - plate_st - v_vin - v_model_yr - v_model_desc - v_make_desc - v_color - vehicleid - actual_completion - actual_delivery - actual_in - production_vars - id - ins_co_nm - clm_no - po_number - clm_total - owner_owing - ro_number - scheduled_completion - scheduled_in - scheduled_delivery - status - updated_at - ded_amt - suspended - est_ct_fn - est_ct_ln - } + query QUERY_ALL_ACTIVE_JOBS($statuses: [String!]!, $isConverted: Boolean) { + jobs(where: { status: { _in: $statuses }, converted: { _eq: $isConverted } }, order_by: { created_at: desc }) { + iouparent + ownr_fn + ownr_ln + ownr_co_nm + ownr_ph1 + ownr_ph2 + ownr_ea + ownerid + comment + plate_no + plate_st + v_vin + v_model_yr + v_model_desc + v_make_desc + v_color + vehicleid + actual_completion + actual_delivery + actual_in + production_vars + id + ins_co_nm + clm_no + po_number + clm_total + owner_owing + ro_number + scheduled_completion + scheduled_in + scheduled_delivery + status + updated_at + ded_amt + suspended + est_ct_fn + est_ct_ln } + } `; export const QUERY_PARTS_QUEUE = gql` - query QUERY_PARTS_QUEUE( - $statuses: [String!]! - , $offset: Int - , $limit: Int) { - - jobs_aggregate(where: { _and: [{ status: { _in: $statuses } }] }) { - aggregate { - count(distinct: true) - } - } - jobs( - where: { - _and: [{ status: { _in: $statuses }, converted: { _eq: true } }] - } - offset: $offset - limit: $limit - order_by: { ro_number: desc } - ) { - ownr_fn - ownr_ln - ownr_co_nm - ownr_ph1 - ownr_ph2 - ownr_ea - plate_no - plate_st - v_vin - v_model_yr - v_model_desc - v_make_desc - v_color - vehicleid - scheduled_in - scheduled_completion - id - ins_co_nm - clm_no - ro_number - status - updated_at - vehicleid - ownerid - queued_for_parts - comment - joblines_status { - count - part_type - status - } - } + query QUERY_PARTS_QUEUE($statuses: [String!]!, $offset: Int, $limit: Int) { + jobs_aggregate(where: { _and: [{ status: { _in: $statuses } }] }) { + aggregate { + count(distinct: true) + } } + jobs( + where: { _and: [{ status: { _in: $statuses }, converted: { _eq: true } }] } + offset: $offset + limit: $limit + order_by: { ro_number: desc } + ) { + ownr_fn + ownr_ln + ownr_co_nm + ownr_ph1 + ownr_ph2 + ownr_ea + plate_no + plate_st + v_vin + v_model_yr + v_model_desc + v_make_desc + v_color + vehicleid + scheduled_in + scheduled_completion + id + ins_co_nm + clm_no + ro_number + status + updated_at + vehicleid + ownerid + queued_for_parts + comment + joblines_status { + count + part_type + status + } + } + } `; export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql` - subscription SUBSCRIPTION_JOBS_IN_PRODUCTION { - jobs(where: { inproduction: { _eq: true } }) { - id - updated_at - } + subscription SUBSCRIPTION_JOBS_IN_PRODUCTION { + jobs(where: { inproduction: { _eq: true } }) { + id + updated_at } + } `; export const QUERY_EXACT_JOB_IN_PRODUCTION = gql` - query QUERY_EXACT_JOB_IN_PRODUCTION($id: uuid!) { - jobs(where: { id: { _eq: $id } }) { - id - iouparent - status - ro_number - comment - ownr_fn - ownr_ln - category - ownr_co_nm - v_model_yr - v_model_desc - clm_no - v_make_desc - v_color - plate_no - actual_in - scheduled_completion - scheduled_delivery - date_last_contacted - date_next_contact - ins_co_nm - clm_total - ownr_ph1 - ownr_ph2 - special_coverage_policy - owner_owing - production_vars - kanbanparent - alt_transport - employee_body - employee_refinish - employee_prep - employee_csr - date_repairstarted - joblines_status { - part_type - status - count - } - labhrs: joblines_aggregate( - where: { - _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] - } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - larhrs: joblines_aggregate( - where: { - _and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }] - } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - subletLines: joblines( - where: { - _and: { part_type: { _in: ["PAS", "PASL"] }, removed: { _eq: false } } - } - order_by: { line_no: asc } - ) { - id - line_desc - sublet_ignored - sublet_completed - jobid - } + query QUERY_EXACT_JOB_IN_PRODUCTION($id: uuid!) { + jobs(where: { id: { _eq: $id } }) { + id + iouparent + status + ro_number + comment + ownr_fn + ownr_ln + category + ownr_co_nm + v_model_yr + v_model_desc + clm_no + v_make_desc + v_color + plate_no + actual_in + scheduled_completion + scheduled_delivery + date_last_contacted + date_next_contact + ins_co_nm + clm_total + ownr_ph1 + ownr_ph2 + special_coverage_policy + owner_owing + production_vars + kanbanparent + alt_transport + employee_body + employee_refinish + employee_prep + employee_csr + date_repairstarted + joblines_status { + part_type + status + count + } + labhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] }) { + aggregate { + sum { + mod_lb_hrs + } } + } + larhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }] }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + subletLines: joblines( + where: { _and: { part_type: { _in: ["PAS", "PASL"] }, removed: { _eq: false } } } + order_by: { line_no: asc } + ) { + id + line_desc + sublet_ignored + sublet_completed + jobid + } } + } `; export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql` - query QUERY_EXACT_JOBS_IN_PRODUCTION($ids: [uuid!]!) { - jobs(where: { id: { _in: $ids } }) { - id - iouparent - status - ro_number - comment - ownr_fn - category - ownr_ln - ownr_co_nm - v_model_yr - v_model_desc - clm_no - v_make_desc - v_color - plate_no - actual_in - scheduled_completion - scheduled_delivery - date_last_contacted - date_next_contact - ins_co_nm - clm_total - ownr_ph1 - ownr_ph2 - special_coverage_policy - owner_owing - production_vars - kanbanparent - alt_transport - employee_body - employee_refinish - employee_prep - employee_csr - date_repairstarted - joblines_status { - part_type - status - count - } - labhrs: joblines_aggregate( - where: { - _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] - } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - larhrs: joblines_aggregate( - where: { - _and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }] - } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - subletLines: joblines( - where: { - _and: { part_type: { _in: ["PAS", "PASL"] }, removed: { _eq: false } } - } - order_by: { line_no: asc } - ) { - id - line_desc - sublet_ignored - sublet_completed - jobid - } + query QUERY_EXACT_JOBS_IN_PRODUCTION($ids: [uuid!]!) { + jobs(where: { id: { _in: $ids } }) { + id + iouparent + status + ro_number + comment + ownr_fn + category + ownr_ln + ownr_co_nm + v_model_yr + v_model_desc + clm_no + v_make_desc + v_color + plate_no + actual_in + scheduled_completion + scheduled_delivery + date_last_contacted + date_next_contact + ins_co_nm + clm_total + ownr_ph1 + ownr_ph2 + special_coverage_policy + owner_owing + production_vars + kanbanparent + alt_transport + employee_body + employee_refinish + employee_prep + employee_csr + date_repairstarted + joblines_status { + part_type + status + count + } + labhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] }) { + aggregate { + sum { + mod_lb_hrs + } } + } + larhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }] }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + subletLines: joblines( + where: { _and: { part_type: { _in: ["PAS", "PASL"] }, removed: { _eq: false } } } + order_by: { line_no: asc } + ) { + id + line_desc + sublet_ignored + sublet_completed + jobid + } } + } `; export const QUERY_JOBS_IN_PRODUCTION = gql` - query QUERY_JOBS_IN_PRODUCTION { - jobs(where: { inproduction: { _eq: true } }) { - id - updated_at - comment - status - category - iouparent - ro_number - ownerid - ownr_fn - ownr_ln - ownr_co_nm - v_model_yr - v_model_desc - clm_no - v_make_desc - v_color - vehicleid - plate_no - actual_in - scheduled_completion - scheduled_delivery - date_last_contacted - date_next_contact - ins_co_nm - clm_total - ownr_ph1 - ownr_ph2 - special_coverage_policy - owner_owing - production_vars - kanbanparent - alt_transport - employee_body - employee_refinish - employee_prep - employee_csr - est_ct_fn - est_ct_ln - suspended - date_repairstarted - joblines_status { - part_type - status - count - } - labhrs: joblines_aggregate( - where: { - _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] - } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - larhrs: joblines_aggregate( - where: { - _and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }] - } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - subletLines: joblines( - where: { - _and: { part_type: { _in: ["PAS", "PASL"] }, removed: { _eq: false } } - } - order_by: { line_no: asc } - ) { - id - line_desc - sublet_ignored - sublet_completed - jobid - } + query QUERY_JOBS_IN_PRODUCTION { + jobs(where: { inproduction: { _eq: true } }) { + id + updated_at + comment + status + category + iouparent + ro_number + ownerid + ownr_fn + ownr_ln + ownr_co_nm + v_model_yr + v_model_desc + clm_no + v_make_desc + v_color + vehicleid + plate_no + actual_in + scheduled_completion + scheduled_delivery + date_last_contacted + date_next_contact + ins_co_nm + clm_total + ownr_ph1 + ownr_ph2 + special_coverage_policy + owner_owing + production_vars + kanbanparent + alt_transport + employee_body + employee_refinish + employee_prep + employee_csr + est_ct_fn + est_ct_ln + suspended + date_repairstarted + joblines_status { + part_type + status + count + } + labhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] }) { + aggregate { + sum { + mod_lb_hrs + } } + } + larhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }] }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + subletLines: joblines( + where: { _and: { part_type: { _in: ["PAS", "PASL"] }, removed: { _eq: false } } } + order_by: { line_no: asc } + ) { + id + line_desc + sublet_ignored + sublet_completed + jobid + } } + } `; export const QUERY_LBR_HRS_BY_PK = gql` - query QUERY_LBR_HRS_BY_PK($id: uuid!) { - jobs_by_pk(id: $id) { - id - ro_number - labhrs: joblines_aggregate( - where: { - _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] - } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } - larhrs: joblines_aggregate( - where: { - _and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }] - } - ) { - aggregate { - sum { - mod_lb_hrs - } - } - } + query QUERY_LBR_HRS_BY_PK($id: uuid!) { + jobs_by_pk(id: $id) { + id + ro_number + labhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] }) { + aggregate { + sum { + mod_lb_hrs + } } + } + larhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }] }) { + aggregate { + sum { + mod_lb_hrs + } + } + } } + } `; export const QUERY_JOB_COSTING_DETAILS = gql` - query QUERY_JOB_COSTING_DETAILS($id: uuid!) { - jobs_by_pk(id: $id) { - ro_number - clm_total - id - ded_amt - ded_status - depreciation_taxes - other_amount_payable - towing_payable - storage_payable - adjustment_bottom_line - federal_tax_rate - state_tax_rate - local_tax_rate - tax_tow_rt - tax_str_rt - tax_paint_mat_rt - tax_sub_rt - tax_lbr_rt - tax_levies_rt - parts_tax_rates - job_totals - labor_rate_desc - rate_la1 - rate_la2 - rate_la3 - rate_la4 - rate_laa - rate_lab - rate_lad - rate_lae - rate_laf - rate_lag - rate_lam - rate_lar - rate_las - rate_lau - rate_ma2s - rate_ma2t - rate_ma3s - rate_mabl - rate_macs - rate_mahw - rate_mapa - rate_mash - rate_matd - actual_in - status - ca_bc_pvrt - ca_customer_gst - joblines(where: { removed: { _eq: false } }) { - id - unq_seq - line_ind - tax_part - line_desc - prt_dsmk_p - prt_dsmk_m - part_type - oem_partno - db_price - act_price - part_qty - mod_lbr_ty - db_hrs - mod_lb_hrs - lbr_op - lbr_amt - op_code_desc - } - bills { - id - federal_tax_rate - local_tax_rate - state_tax_rate - is_credit_memo - billlines { - actual_cost - cost_center - id - quantity - } - } - timetickets { - id - rate - cost_center - actualhrs - productivehrs - flat_rate - } + query QUERY_JOB_COSTING_DETAILS($id: uuid!) { + jobs_by_pk(id: $id) { + ro_number + clm_total + id + ded_amt + ded_status + depreciation_taxes + other_amount_payable + towing_payable + storage_payable + adjustment_bottom_line + federal_tax_rate + state_tax_rate + local_tax_rate + tax_tow_rt + tax_str_rt + tax_paint_mat_rt + tax_sub_rt + tax_lbr_rt + tax_levies_rt + parts_tax_rates + job_totals + labor_rate_desc + rate_la1 + rate_la2 + rate_la3 + rate_la4 + rate_laa + rate_lab + rate_lad + rate_lae + rate_laf + rate_lag + rate_lam + rate_lar + rate_las + rate_lau + rate_ma2s + rate_ma2t + rate_ma3s + rate_mabl + rate_macs + rate_mahw + rate_mapa + rate_mash + rate_matd + actual_in + status + ca_bc_pvrt + ca_customer_gst + joblines(where: { removed: { _eq: false } }) { + id + unq_seq + line_ind + tax_part + line_desc + prt_dsmk_p + prt_dsmk_m + part_type + oem_partno + db_price + act_price + part_qty + mod_lbr_ty + db_hrs + mod_lb_hrs + lbr_op + lbr_amt + op_code_desc + } + bills { + id + federal_tax_rate + local_tax_rate + state_tax_rate + is_credit_memo + billlines { + actual_cost + cost_center + id + quantity } + } + timetickets { + id + rate + cost_center + actualhrs + productivehrs + flat_rate + } } + } `; export const GET_JOB_BY_PK = gql` - query GET_JOB_BY_PK($id: uuid!) { - jobs_by_pk(id: $id) { - actual_completion - actual_delivery - actual_in - adjustment_bottom_line - alt_transport - area_of_damage - auto_add_ats - available_jobs { - id - } - ca_bc_pvrt - ca_customer_gst - ca_gst_registrant - category - cccontracts { - agreementnumber - courtesycar { - fleetnumber - id - make - model - plate - year - } - id - start - status - scheduledreturn - } - cieca_pfl - cieca_pfo - cieca_pft - cieca_ttl - class - clm_no - clm_total - comment - converted - csiinvites { - completedon - id - } - date_estimated - date_exported - date_invoiced - date_last_contacted - date_lost_sale - date_next_contact - date_open - date_rentalresp - date_repairstarted - date_scheduled - date_towin - date_void - ded_amt - ded_note - ded_status - deliverchecklist - depreciation_taxes - driveable - employee_body - employee_body_rel { - id - first_name - last_name - } - employee_csr - employee_csr_rel { - id - first_name - last_name - } - employee_prep - employee_prep_rel { - id - first_name - last_name - } - employee_refinish - employee_refinish_rel { - id - first_name - last_name - } - est_co_nm - est_ct_fn - est_ct_ln - est_ea - est_ph1 - federal_tax_rate - id - inproduction - ins_addr1 - ins_city - ins_co_id - ins_co_nm - ins_ct_fn - ins_ct_ln - ins_ea - ins_ph1 - intakechecklist - invoice_final_note - iouparent - job_totals - joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) { - act_price - act_price_before_ppc - ah_detail_line - alt_partm - alt_partno - assigned_team - billlines(limit: 1, order_by: { bill: { date: desc } }) { - actual_cost - actual_price - bill { - id - invoice_number - vendor { - id - name - } - } - id - joblineid - quantity - } - convertedtolbr - critical - db_hrs - db_price - db_ref - id - ioucreated - lbr_amt - lbr_op - line_desc - line_ind - line_no - line_ref - location - manual_line - mod_lb_hrs - mod_lbr_ty - notes - oem_partno - op_code_desc - parts_dispatch_lines(limit: 1, order_by: { accepted_at: desc }) { - accepted_at - id - parts_dispatch { - employeeid - id - } - } - part_qty - part_type - prt_dsmk_m - prt_dsmk_p - status - tax_part - unq_seq - } - kmin - kmout - labor_rate_desc - lbr_adjustments - local_tax_rate - loss_date - loss_desc - loss_of_use - lost_sale_reason - materials - other_amount_payable - owner { - id - note - ownr_addr1 - ownr_addr2 - ownr_city - ownr_co_nm - ownr_ctry - ownr_ea - ownr_fn - ownr_ln - ownr_ph1 - ownr_ph2 - ownr_st - ownr_zip - tax_number - } - ownerid - owner_owing - ownr_addr1 - ownr_addr2 - ownr_city - ownr_co_nm - ownr_ctry - ownr_ea - ownr_fn - ownr_ln - ownr_ph1 - ownr_ph2 - ownr_st - ownr_zip - parts_tax_rates - payments { - amount - created_at - date - exportedat - id - jobid - memo - payer - paymentnum - transactionid - type - } - plate_no - plate_st - po_number - policy_no - production_vars - rate_ats - rate_la1 - rate_la2 - rate_la3 - rate_la4 - rate_laa - rate_lab - rate_lad - rate_lae - rate_laf - rate_lag - rate_lam - rate_lar - rate_las - rate_lau - rate_ma2s - rate_ma2t - rate_ma3s - rate_mabl - rate_macs - rate_mahw - rate_mapa - rate_mash - rate_matd - referral_source - referral_source_extra - regie_number - remove_from_ar - ro_number - scheduled_completion - scheduled_delivery - scheduled_in - selling_dealer - selling_dealer_contact - servicing_dealer - servicing_dealer_contact - special_coverage_policy - state_tax_rate - status - storage_payable - suspended - 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 - unit_number - updated_at - v_color - v_make_desc - v_model_yr - v_model_desc - v_vin - vehicle { - id - jobs { - clm_no - id - ro_number - status - } - notes - plate_no - plate_st - v_color - v_make_desc - v_model_yr - v_model_desc - v_paint_codes - v_vin - } - vehicleid - voided + query GET_JOB_BY_PK($id: uuid!) { + jobs_by_pk(id: $id) { + actual_completion + actual_delivery + actual_in + adjustment_bottom_line + alt_transport + area_of_damage + auto_add_ats + available_jobs { + id + } + ca_bc_pvrt + ca_customer_gst + ca_gst_registrant + category + cccontracts { + agreementnumber + courtesycar { + fleetnumber + id + make + model + plate + year } + id + start + status + scheduledreturn + } + cieca_pfl + cieca_pfo + cieca_pft + cieca_ttl + class + clm_no + clm_total + comment + converted + csiinvites { + completedon + id + } + date_estimated + date_exported + date_invoiced + date_last_contacted + date_lost_sale + date_next_contact + date_open + date_rentalresp + date_repairstarted + date_scheduled + date_towin + date_void + ded_amt + ded_note + ded_status + deliverchecklist + depreciation_taxes + driveable + employee_body + employee_body_rel { + id + first_name + last_name + } + employee_csr + employee_csr_rel { + id + first_name + last_name + } + employee_prep + employee_prep_rel { + id + first_name + last_name + } + employee_refinish + employee_refinish_rel { + id + first_name + last_name + } + est_co_nm + est_ct_fn + est_ct_ln + est_ea + est_ph1 + federal_tax_rate + id + inproduction + ins_addr1 + ins_city + ins_co_id + ins_co_nm + ins_ct_fn + ins_ct_ln + ins_ea + ins_ph1 + intakechecklist + invoice_final_note + iouparent + job_totals + joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) { + act_price + act_price_before_ppc + ah_detail_line + alt_partm + alt_partno + assigned_team + billlines(limit: 1, order_by: { bill: { date: desc } }) { + actual_cost + actual_price + bill { + id + invoice_number + vendor { + id + name + } + } + id + joblineid + quantity + } + convertedtolbr + critical + db_hrs + db_price + db_ref + id + ioucreated + lbr_amt + lbr_op + line_desc + line_ind + line_no + line_ref + location + manual_line + mod_lb_hrs + mod_lbr_ty + notes + oem_partno + op_code_desc + parts_dispatch_lines(limit: 1, order_by: { accepted_at: desc }) { + accepted_at + id + parts_dispatch { + employeeid + id + } + } + part_qty + part_type + prt_dsmk_m + prt_dsmk_p + status + tax_part + unq_seq + } + kmin + kmout + labor_rate_desc + lbr_adjustments + local_tax_rate + loss_date + loss_desc + loss_of_use + lost_sale_reason + materials + other_amount_payable + owner { + id + note + ownr_addr1 + ownr_addr2 + ownr_city + ownr_co_nm + ownr_ctry + ownr_ea + ownr_fn + ownr_ln + ownr_ph1 + ownr_ph2 + ownr_st + ownr_zip + tax_number + } + ownerid + owner_owing + ownr_addr1 + ownr_addr2 + ownr_city + ownr_co_nm + ownr_ctry + ownr_ea + ownr_fn + ownr_ln + ownr_ph1 + ownr_ph2 + ownr_st + ownr_zip + parts_tax_rates + payments { + amount + created_at + date + exportedat + id + jobid + memo + payer + paymentnum + transactionid + type + } + plate_no + plate_st + po_number + policy_no + production_vars + rate_ats + rate_la1 + rate_la2 + rate_la3 + rate_la4 + rate_laa + rate_lab + rate_lad + rate_lae + rate_laf + rate_lag + rate_lam + rate_lar + rate_las + rate_lau + rate_ma2s + rate_ma2t + rate_ma3s + rate_mabl + rate_macs + rate_mahw + rate_mapa + rate_mash + rate_matd + referral_source + referral_source_extra + regie_number + remove_from_ar + ro_number + scheduled_completion + scheduled_delivery + scheduled_in + selling_dealer + selling_dealer_contact + servicing_dealer + servicing_dealer_contact + special_coverage_policy + state_tax_rate + status + storage_payable + suspended + 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 + unit_number + updated_at + v_color + v_make_desc + v_model_yr + v_model_desc + v_vin + vehicle { + id + jobs { + clm_no + id + ro_number + status + } + notes + plate_no + plate_st + v_color + v_make_desc + v_model_yr + v_model_desc + v_paint_codes + v_vin + } + vehicleid + voided } + } `; export const GET_JOB_RECONCILIATION_BY_PK = gql` - query GET_JOB_RECONCILIATION_BY_PK($id: uuid!) { - bills(where: { jobid: { _eq: $id } }) { - id - vendorid - vendor { - id - name - } - total - invoice_number - date - federal_tax_rate - state_tax_rate - local_tax_rate - is_credit_memo - isinhouse - exported - billlines(where: { deductedfromlbr: { _eq: false } }) { - actual_price - quantity - actual_cost - cost_center - id - joblineid - line_desc - applicable_taxes - deductedfromlbr - jobline { - id - removed - } - } - } - jobs_by_pk(id: $id) { - id - joblines(order_by: { line_no: asc }) { - id - removed - line_no - unq_seq - line_ind - line_desc - part_type - oem_partno - db_price - act_price - part_qty - mod_lbr_ty - db_hrs - mod_lb_hrs - lbr_op - lbr_amt - op_code_desc - status - notes - location - tax_part - db_ref - manual_line - misc_amt - } + query GET_JOB_RECONCILIATION_BY_PK($id: uuid!) { + bills(where: { jobid: { _eq: $id } }) { + id + vendorid + vendor { + id + name + } + total + invoice_number + date + federal_tax_rate + state_tax_rate + local_tax_rate + is_credit_memo + isinhouse + exported + billlines(where: { deductedfromlbr: { _eq: false } }) { + actual_price + quantity + actual_cost + cost_center + id + joblineid + line_desc + applicable_taxes + deductedfromlbr + jobline { + id + removed } + } } + jobs_by_pk(id: $id) { + id + joblines(order_by: { line_no: asc }) { + id + removed + line_no + unq_seq + line_ind + line_desc + part_type + oem_partno + db_price + act_price + part_qty + mod_lbr_ty + db_hrs + mod_lb_hrs + lbr_op + lbr_amt + op_code_desc + status + notes + location + tax_part + db_ref + manual_line + misc_amt + } + } + } `; export const QUERY_JOB_CARD_DETAILS = gql` - query QUERY_JOB_CARD_DETAILS($id: uuid!) { - jobs_by_pk(id: $id) { - ownr_fn - employee_body - employee_refinish - alt_transport - inproduction - production_vars - ownr_ln - ownr_co_nm - ownr_ph1 - ownr_ph2 - comment - ownr_ea - ca_gst_registrant - owner_owing - special_coverage_policy - suspended - lbr_adjustments - available_jobs { - id - } - joblines_status { - part_type - count - status - } + query QUERY_JOB_CARD_DETAILS($id: uuid!) { + jobs_by_pk(id: $id) { + ownr_fn + employee_body + employee_refinish + alt_transport + inproduction + production_vars + ownr_ln + ownr_co_nm + ownr_ph1 + ownr_ph2 + comment + ownr_ea + ca_gst_registrant + owner_owing + special_coverage_policy + suspended + lbr_adjustments + available_jobs { + id + } + joblines_status { + part_type + count + status + } - joblines( - where: { - removed: { _eq: false } - part_type: { _is_null: false, _nin: ["PAE", "PAS", "PASL"] } - } - order_by: { line_no: asc } - ) { - id - alt_partm - line_no - unq_seq - line_ind - line_desc - line_ref - part_type - part_qty - mod_lbr_ty - db_hrs - mod_lb_hrs - lbr_op - lbr_amt - op_code_desc - status - notes - location - tax_part - db_ref - manual_line - prt_dsmk_p - prt_dsmk_m - ioucreated - convertedtolbr - critical - parts_dispatch_lines { - id - accepted_at - quantity - parts_dispatch { - id - employeeid - dispatched_at - dispatched_by - number - } - } - } - owner { - id - allow_text_message - preferred_contact - tax_number - } - vehicleid - v_model_yr - v_make_desc - v_model_desc - v_color - v_vin - plate_st - plate_no - vehicle { - id - v_model_yr - v_make_desc - v_model_desc - v_color - plate_no - notes - jobs { - id - clm_no - ro_number - } - } - actual_completion - actual_delivery - actual_in - scheduled_in - po_number + joblines( + where: { removed: { _eq: false }, part_type: { _is_null: false, _nin: ["PAE", "PAS", "PASL"] } } + order_by: { line_no: asc } + ) { + id + alt_partm + line_no + unq_seq + line_ind + line_desc + line_ref + part_type + part_qty + mod_lbr_ty + db_hrs + mod_lb_hrs + lbr_op + lbr_amt + op_code_desc + status + notes + location + tax_part + db_ref + manual_line + prt_dsmk_p + prt_dsmk_m + ioucreated + convertedtolbr + critical + parts_dispatch_lines { + id + accepted_at + quantity + parts_dispatch { id - ins_co_nm - ins_ct_fn - ins_ct_ln - ins_ph1 - ins_ea - est_co_nm - est_ph1 - est_ea - est_ct_fn - est_ct_ln - clm_no - status - job_totals - area_of_damage - ro_number - scheduled_completion - scheduled_in - scheduled_delivery - date_invoiced - date_last_contacted - date_next_contact - date_open - date_exported - date_repairstarted - date_scheduled - date_estimated - employee_body_rel { - id - first_name - last_name - } - employee_refinish_rel { - id - first_name - last_name - } - employee_prep_rel { - id - first_name - last_name - } - employee_csr_rel { - id - first_name - last_name - } - notes { - id - text - critical - private - created_at - } - updated_at - clm_total - ded_amt - voided - cccontracts { - id - agreementnumber - status - start - scheduledreturn - courtesycar { - id - make - model - year - plate - fleetnumber - } - } - documents(limit: 3, order_by: { created_at: desc }) { - id - key - type - } + employeeid + dispatched_at + dispatched_by + number + } } + } + owner { + id + allow_text_message + preferred_contact + tax_number + } + vehicleid + v_model_yr + v_make_desc + v_model_desc + v_color + v_vin + plate_st + plate_no + vehicle { + id + v_model_yr + v_make_desc + v_model_desc + v_color + plate_no + notes + jobs { + id + clm_no + ro_number + } + } + actual_completion + actual_delivery + actual_in + scheduled_in + po_number + id + ins_co_nm + ins_ct_fn + ins_ct_ln + ins_ph1 + ins_ea + est_co_nm + est_ph1 + est_ea + est_ct_fn + est_ct_ln + clm_no + status + job_totals + area_of_damage + ro_number + scheduled_completion + scheduled_in + scheduled_delivery + date_invoiced + date_last_contacted + date_next_contact + date_open + date_exported + date_repairstarted + date_scheduled + date_estimated + employee_body_rel { + id + first_name + last_name + } + employee_refinish_rel { + id + first_name + last_name + } + employee_prep_rel { + id + first_name + last_name + } + employee_csr_rel { + id + first_name + last_name + } + notes { + id + text + critical + private + created_at + } + updated_at + clm_total + ded_amt + voided + cccontracts { + id + agreementnumber + status + start + scheduledreturn + courtesycar { + id + make + model + year + plate + fleetnumber + } + } + documents(limit: 3, order_by: { created_at: desc }) { + id + key + type + } } + } `; export const QUERY_TECH_JOB_DETAILS = gql` - query QUERY_TECH_JOB_DETAILS($id: uuid!) { - jobs_by_pk(id: $id) { - ownr_fn - ownr_ln - v_model_yr - v_make_desc - v_model_desc - v_color - plate_no - actual_completion - actual_delivery - actual_in - id - ins_co_nm - clm_no - status - job_totals - area_of_damage - ro_number - scheduled_completion - scheduled_in - scheduled_delivery - date_invoiced - date_last_contacted - date_next_contact - date_open - date_exported - voided - date_scheduled - date_estimated - employee_body - employee_refinish - employee_prep - joblines(where: { removed: { _eq: false } }) { - id - unq_seq - line_ind - tax_part - line_desc - prt_dsmk_p - prt_dsmk_m - part_type - oem_partno - db_price - act_price - part_qty - mod_lbr_ty - db_hrs - mod_lb_hrs - lbr_op - lbr_amt - op_code_desc - } - notes { - id - text - critical - private - created_at - } - updated_at - documents(order_by: { created_at: desc }) { - id - key - size - type - } - } + query QUERY_TECH_JOB_DETAILS($id: uuid!) { + jobs_by_pk(id: $id) { + ownr_fn + ownr_ln + v_model_yr + v_make_desc + v_model_desc + v_color + plate_no + actual_completion + actual_delivery + actual_in + id + ins_co_nm + clm_no + status + job_totals + area_of_damage + ro_number + scheduled_completion + scheduled_in + scheduled_delivery + date_invoiced + date_last_contacted + date_next_contact + date_open + date_exported + voided + date_scheduled + date_estimated + employee_body + employee_refinish + employee_prep + joblines(where: { removed: { _eq: false } }) { + id + unq_seq + line_ind + tax_part + line_desc + prt_dsmk_p + prt_dsmk_m + part_type + oem_partno + db_price + act_price + part_qty + mod_lbr_ty + db_hrs + mod_lb_hrs + lbr_op + lbr_amt + op_code_desc + } + notes { + id + text + critical + private + created_at + } + updated_at + documents(order_by: { created_at: desc }) { + id + key + size + type + } } + } `; export const UPDATE_JOB = gql` - mutation UPDATE_JOB($jobId: uuid!, $job: jobs_set_input!) { - update_jobs(where: { id: { _eq: $jobId } }, _set: $job) { - returning { - id - comment - date_exported - status - alt_transport - ro_number - production_vars - lbr_adjustments - suspended - queued_for_parts - scheduled_completion - actual_in - date_repairstarted - date_void - date_lost_sale - } - } + mutation UPDATE_JOB($jobId: uuid!, $job: jobs_set_input!) { + update_jobs(where: { id: { _eq: $jobId } }, _set: $job) { + returning { + id + comment + date_exported + status + alt_transport + ro_number + production_vars + lbr_adjustments + suspended + queued_for_parts + scheduled_completion + actual_in + date_repairstarted + date_void + date_lost_sale + } } + } `; export const JOB_PRODUCTION_TOGGLE = gql` mutation UPDATE_JOB($jobId: uuid!, $job: jobs_set_input!) { @@ -1219,875 +1169,859 @@ export const JOB_PRODUCTION_TOGGLE = gql` `; export const UPDATE_JOB_ASSIGNMENTS = gql` - mutation UPDATE_JOB_ASSIGNMENTS($jobId: uuid!, $job: jobs_set_input!) { - update_jobs(where: { id: { _eq: $jobId } }, _set: $job) { - returning { - id - employee_body_rel { - id - first_name - last_name - } - employee_refinish_rel { - id - first_name - last_name - } - employee_prep_rel { - id - first_name - last_name - } - employee_csr_rel { - id - first_name - last_name - } - employee_csr - employee_body - employee_prep - } + mutation UPDATE_JOB_ASSIGNMENTS($jobId: uuid!, $job: jobs_set_input!) { + update_jobs(where: { id: { _eq: $jobId } }, _set: $job) { + returning { + id + employee_body_rel { + id + first_name + last_name } + employee_refinish_rel { + id + first_name + last_name + } + employee_prep_rel { + id + first_name + last_name + } + employee_csr_rel { + id + first_name + last_name + } + employee_csr + employee_body + employee_prep + } } + } `; export const VOID_JOB = gql` - mutation VOID_JOB( - $jobId: uuid! - $job: jobs_set_input! - $note: [notes_insert_input!]! - ) { - update_jobs_by_pk(_set: $job, pk_columns: { id: $jobId }) { - id - date_exported - date_void - status - alt_transport - ro_number - production_vars - lbr_adjustments - } - insert_notes(objects: $note) { - affected_rows - } - update_appointments( - where: { jobid: { _eq: $jobId } } - _set: { canceled: true } - ) { - returning { - id - canceled - } - } + mutation VOID_JOB($jobId: uuid!, $job: jobs_set_input!, $note: [notes_insert_input!]!) { + update_jobs_by_pk(_set: $job, pk_columns: { id: $jobId }) { + id + date_exported + date_void + status + alt_transport + ro_number + production_vars + lbr_adjustments } + insert_notes(objects: $note) { + affected_rows + } + update_appointments(where: { jobid: { _eq: $jobId } }, _set: { canceled: true }) { + returning { + id + canceled + } + } + } `; export const UPDATE_JOBS = gql` - mutation UPDATE_JOBS($jobIds: [uuid!]!, $fields: jobs_set_input!) { - update_jobs(where: { id: { _in: $jobIds } }, _set: $fields) { - returning { - id - date_exported - status - } - } + mutation UPDATE_JOBS($jobIds: [uuid!]!, $fields: jobs_set_input!) { + update_jobs(where: { id: { _in: $jobIds } }, _set: $fields) { + returning { + id + date_exported + status + } } + } `; export const CONVERT_JOB_TO_RO = gql` - mutation CONVERT_JOB_TO_RO($jobId: uuid!, $job: jobs_set_input!) { - update_jobs(where: { id: { _eq: $jobId } }, _set: $job) { - returning { - id - ro_number - converted - class - ins_co_nm - referral_source - referral_source_extra - employee_csr - employee_csr_rel { - id - } - } + mutation CONVERT_JOB_TO_RO($jobId: uuid!, $job: jobs_set_input!) { + update_jobs(where: { id: { _eq: $jobId } }, _set: $job) { + returning { + id + ro_number + converted + class + ins_co_nm + referral_source + referral_source_extra + employee_csr + employee_csr_rel { + id } + } } + } `; export const INSERT_NEW_JOB = gql` - mutation INSERT_JOB($job: [jobs_insert_input!]!) { - insert_jobs(objects: $job) { - returning { - id - } - } + mutation INSERT_JOB($job: [jobs_insert_input!]!) { + insert_jobs(objects: $job) { + returning { + id + } } + } `; export const GET_JOB_INFO_FOR_STRIPE = gql` - query GET_JOB_INFO_FOR_STRIPE($jobid: uuid!) { - jobs_by_pk(id: $jobid) { - id - ro_number - ownr_fn - ownr_ln - ownr_co_nm - ownr_ph1 - ownr_ph2 - ownr_ea - } + query GET_JOB_INFO_FOR_STRIPE($jobid: uuid!) { + jobs_by_pk(id: $jobid) { + id + ro_number + ownr_fn + ownr_ln + ownr_co_nm + ownr_ph1 + ownr_ph2 + ownr_ea } + } `; export const UPDATE_JOB_STATUS = gql` - mutation UPDATE_JOB_STATUS($jobId: uuid!, $status: String!) { - update_jobs(where: { id: { _eq: $jobId } }, _set: { status: $status }) { - returning { - id - status - inproduction - } - } + mutation UPDATE_JOB_STATUS($jobId: uuid!, $status: String!) { + update_jobs(where: { id: { _eq: $jobId } }, _set: { status: $status }) { + returning { + id + status + inproduction + } } + } `; export const ACTIVE_JOBS_FOR_AUTOCOMPLETE = gql` - query ACTIVE_JOBS_FOR_AUTOCOMPLETE($statuses: [String!]!) { - jobs(where: { status: { _in: $statuses } }) { - id - ownr_co_nm - ownr_fn - ownr_ln - ro_number - vehicleid - v_make_desc - v_model_desc - v_model_yr - } + query ACTIVE_JOBS_FOR_AUTOCOMPLETE($statuses: [String!]!) { + jobs(where: { status: { _in: $statuses } }) { + id + ownr_co_nm + ownr_fn + ownr_ln + ro_number + vehicleid + v_make_desc + v_model_desc + v_model_yr } + } `; export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql` - query SEARCH_JOBS_FOR_AUTOCOMPLETE( - $search: String - $isConverted: Boolean - $notExported: Boolean - $notInvoiced: Boolean - ) { - search_jobs( - args: { search: $search } - limit: 25 - where: { - _and: { - converted: { _eq: $isConverted } - date_exported: { _is_null: $notExported } - date_invoiced: { _is_null: $notInvoiced } - } - } - ) { - id - ownr_co_nm - ownr_fn - ownr_ln - ro_number - clm_no - vehicleid - v_make_desc - v_model_desc - v_model_yr - status + query SEARCH_JOBS_FOR_AUTOCOMPLETE( + $search: String + $isConverted: Boolean + $notExported: Boolean + $notInvoiced: Boolean + ) { + search_jobs( + args: { search: $search } + limit: 25 + where: { + _and: { + converted: { _eq: $isConverted } + date_exported: { _is_null: $notExported } + date_invoiced: { _is_null: $notInvoiced } } + } + ) { + id + ownr_co_nm + ownr_fn + ownr_ln + ro_number + clm_no + vehicleid + v_make_desc + v_model_desc + v_model_yr + status } + } `; export const SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE = gql` - query SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE($id: uuid!) { - jobs_by_pk(id: $id) { - id - ownr_co_nm - ownr_fn - ownr_ln - ro_number - clm_no - vehicleid - v_make_desc - v_model_desc - v_model_yr - status - } + query SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE($id: uuid!) { + jobs_by_pk(id: $id) { + id + ownr_co_nm + ownr_fn + ownr_ln + ro_number + clm_no + vehicleid + v_make_desc + v_model_desc + v_model_yr + status } + } `; export const SEARCH_FOR_JOBS = gql` - query SEARCH_FOR_JOBS($search: String!) { - search_jobs(args: { search: $search }, limit: 25) { - id - ro_number - ownr_co_nm - ownr_fn - ownr_ln - } + query SEARCH_FOR_JOBS($search: String!) { + search_jobs(args: { search: $search }, limit: 25) { + id + ro_number + ownr_co_nm + ownr_fn + ownr_ln } + } `; export const QUERY_JOB_FOR_DUPE = gql` - query QUERY_JOB_FOR_DUPE($id: uuid!) { - jobs_by_pk(id: $id) { - id - agt_addr1 - agt_addr2 - agt_city - agt_co_id - agt_co_nm - agt_ct_fn - agt_ct_ln - agt_ct_ph - agt_ct_phx - agt_ctry - agt_ea - agt_faxx - agt_fax - agt_lic_no - agt_ph1 - agt_ph1x - agt_ph2 - agt_zip - agt_st - agt_ph2x - area_of_damage - cat_no - cieca_stl - cieca_ttl - clm_addr1 - clm_addr2 - clm_city - clm_ct_fn - clm_ct_ln - clm_ct_ph - clm_ct_phx - clm_ctry - clm_ea - clm_fax - clm_faxx - clm_ofc_id - clm_ofc_nm - clm_ph1 - clm_ph1x - clm_ph2 - clm_ph2x - clm_st - clm_title - clm_total - clm_zip - cust_pr - est_addr1 - est_addr2 - est_city - est_co_nm - est_ct_fn - est_ct_ln - est_ctry - est_ea - est_ph1 - est_st - est_zip - federal_tax_rate - ins_addr1 - ins_addr2 - ins_city - ins_co_id - ins_co_nm - ins_ct_fn - ins_ct_ln - ins_ct_ph - ins_ct_phx - ins_ctry - ins_ea - ins_fax - ins_faxx - ins_memo - ins_ph1 - ins_ph1x - ins_ph2 - ins_ph2x - ins_st - ins_title - ins_zip - insd_addr1 - insd_addr2 - insd_city - insd_co_nm - insd_ctry - insd_ea - insd_fax - insd_faxx - insd_fn - insd_ln - insd_ph1 - insd_ph1x - insd_ph2 - insd_ph2x - insd_st - insd_title - insd_zip - labor_rate_desc - labor_rate_id - local_tax_rate - other_amount_payable - owner_owing - ownerid - ownr_addr1 - ownr_addr2 - ownr_city - ownr_co_nm - ownr_ctry - ownr_ea - ownr_fax - ownr_faxx - ownr_fn - ownr_ln - ownr_ph1 - ownr_ph1x - ownr_ph2 - ownr_ph2x - ownr_st - ownr_title - ownr_zip - parts_tax_rates - plate_no - plate_st - po_number - rate_la1 - rate_la2 - rate_la3 - rate_la4 - rate_laa - rate_lab - rate_lad - rate_lae - rate_lag - rate_laf - rate_lam - rate_lar - rate_las - rate_lau - rate_ma2s - rate_ma2t - rate_ma3s - rate_mabl - rate_macs - rate_mahw - rate_mapa - rate_mash - rate_matd - regie_number - selling_dealer - selling_dealer_contact - servicing_dealer - servicing_dealer_contact - shopid - state_tax_rate - tax_lbr_rt - tax_levies_rt - tax_paint_mat_rt - tax_predis - tax_prethr - tax_pstthr - tax_str_rt - tax_sub_rt - tax_thramt - tax_tow_rt - unit_number - v_color - v_make_desc - v_model_desc - v_model_yr - v_vin - vehicleid - joblines(where: { removed: { _eq: false } }) { - act_price - alt_co_id - alt_overrd - alt_part_i - alt_partm - bett_amt - alt_partno - bett_pctg - bett_tax - bett_type - cert_part - db_hrs - db_price - db_ref - est_seq - glass_flag - id - lbr_amt - lbr_hrs_j - lbr_inc - lbr_op - lbr_op_j - lbr_tax - lbr_typ_j - line_desc - line_ind - line_ref - misc_amt - misc_sublt - misc_tax - mod_lb_hrs - mod_lbr_ty - oem_partno - op_code_desc - paint_stg - paint_tone - part_qty - part_type - price_inc - price_j - prt_dsmk_m - prt_dsmk_p - status - tax_part - unq_seq - manual_line - notes - line_no - tran_code - } - driveable - towin - adj_g_disc - adj_strdis - adj_towdis - ca_gst_registrant - special_coverage_policy - tax_registration_number - tax_shop_mat_rt - } + query QUERY_JOB_FOR_DUPE($id: uuid!) { + jobs_by_pk(id: $id) { + id + agt_addr1 + agt_addr2 + agt_city + agt_co_id + agt_co_nm + agt_ct_fn + agt_ct_ln + agt_ct_ph + agt_ct_phx + agt_ctry + agt_ea + agt_faxx + agt_fax + agt_lic_no + agt_ph1 + agt_ph1x + agt_ph2 + agt_zip + agt_st + agt_ph2x + area_of_damage + cat_no + cieca_stl + cieca_ttl + clm_addr1 + clm_addr2 + clm_city + clm_ct_fn + clm_ct_ln + clm_ct_ph + clm_ct_phx + clm_ctry + clm_ea + clm_fax + clm_faxx + clm_ofc_id + clm_ofc_nm + clm_ph1 + clm_ph1x + clm_ph2 + clm_ph2x + clm_st + clm_title + clm_total + clm_zip + cust_pr + est_addr1 + est_addr2 + est_city + est_co_nm + est_ct_fn + est_ct_ln + est_ctry + est_ea + est_ph1 + est_st + est_zip + federal_tax_rate + ins_addr1 + ins_addr2 + ins_city + ins_co_id + ins_co_nm + ins_ct_fn + ins_ct_ln + ins_ct_ph + ins_ct_phx + ins_ctry + ins_ea + ins_fax + ins_faxx + ins_memo + ins_ph1 + ins_ph1x + ins_ph2 + ins_ph2x + ins_st + ins_title + ins_zip + insd_addr1 + insd_addr2 + insd_city + insd_co_nm + insd_ctry + insd_ea + insd_fax + insd_faxx + insd_fn + insd_ln + insd_ph1 + insd_ph1x + insd_ph2 + insd_ph2x + insd_st + insd_title + insd_zip + labor_rate_desc + labor_rate_id + local_tax_rate + other_amount_payable + owner_owing + ownerid + ownr_addr1 + ownr_addr2 + ownr_city + ownr_co_nm + ownr_ctry + ownr_ea + ownr_fax + ownr_faxx + ownr_fn + ownr_ln + ownr_ph1 + ownr_ph1x + ownr_ph2 + ownr_ph2x + ownr_st + ownr_title + ownr_zip + parts_tax_rates + plate_no + plate_st + po_number + rate_la1 + rate_la2 + rate_la3 + rate_la4 + rate_laa + rate_lab + rate_lad + rate_lae + rate_lag + rate_laf + rate_lam + rate_lar + rate_las + rate_lau + rate_ma2s + rate_ma2t + rate_ma3s + rate_mabl + rate_macs + rate_mahw + rate_mapa + rate_mash + rate_matd + regie_number + selling_dealer + selling_dealer_contact + servicing_dealer + servicing_dealer_contact + shopid + state_tax_rate + tax_lbr_rt + tax_levies_rt + tax_paint_mat_rt + tax_predis + tax_prethr + tax_pstthr + tax_str_rt + tax_sub_rt + tax_thramt + tax_tow_rt + unit_number + v_color + v_make_desc + v_model_desc + v_model_yr + v_vin + vehicleid + joblines(where: { removed: { _eq: false } }) { + act_price + alt_co_id + alt_overrd + alt_part_i + alt_partm + bett_amt + alt_partno + bett_pctg + bett_tax + bett_type + cert_part + db_hrs + db_price + db_ref + est_seq + glass_flag + id + lbr_amt + lbr_hrs_j + lbr_inc + lbr_op + lbr_op_j + lbr_tax + lbr_typ_j + line_desc + line_ind + line_ref + misc_amt + misc_sublt + misc_tax + mod_lb_hrs + mod_lbr_ty + oem_partno + op_code_desc + paint_stg + paint_tone + part_qty + part_type + price_inc + price_j + prt_dsmk_m + prt_dsmk_p + status + tax_part + unq_seq + manual_line + notes + line_no + tran_code + } + driveable + towin + adj_g_disc + adj_strdis + adj_towdis + ca_gst_registrant + special_coverage_policy + tax_registration_number + tax_shop_mat_rt } + } `; export const QUERY_ALL_JOB_FIELDS = gql` - query QUERY_ALL_JOB_FIELDS($id: uuid!) { - jobs_by_pk(id: $id) { - id - adj_g_disc - adj_strdis - adj_towdis - adjustment_bottom_line - agt_addr1 - agt_addr2 - agt_city - agt_co_id - agt_co_nm - agt_ct_fn - agt_ct_ln - agt_ct_ph - agt_ct_phx - agt_ctry - agt_ea - agt_faxx - agt_fax - agt_lic_no - agt_ph1 - agt_ph1x - agt_ph2 - agt_zip - agt_st - agt_ph2x - area_of_damage - cat_no - cieca_stl - cieca_ttl - clm_addr1 - clm_addr2 - clm_city - clm_ct_fn - clm_ct_ln - clm_ct_ph - clm_ct_phx - clm_ctry - clm_ea - clm_fax - clm_faxx - clm_ofc_id - clm_ofc_nm - clm_ph1 - clm_ph1x - clm_ph2 - clm_ph2x - clm_st - clm_title - clm_total - clm_zip - cust_pr - ded_amt - ded_status - depreciation_taxes - est_addr1 - est_addr2 - est_city - est_co_nm - est_ct_fn - est_ct_ln - est_ctry - est_ea - est_ph1 - est_st - est_zip - federal_tax_rate - g_bett_amt - ins_addr1 - ins_addr2 - ins_city - ins_co_id - ins_co_nm - ins_ct_fn - ins_ct_ln - ins_ct_ph - ins_ct_phx - ins_ctry - ins_ea - ins_fax - ins_faxx - ins_memo - ins_ph1 - ins_ph1x - ins_ph2 - ins_ph2x - ins_st - ins_title - ins_zip - insd_addr1 - insd_addr2 - insd_city - insd_co_nm - insd_ctry - insd_ea - insd_fax - insd_faxx - insd_fn - insd_ln - insd_ph1 - insd_ph1x - insd_ph2 - insd_ph2x - insd_st - insd_title - insd_zip - job_totals - labor_rate_desc - labor_rate_id - local_tax_rate - other_amount_payable - owner_owing - ownerid - ownr_addr1 - ownr_addr2 - ownr_city - ownr_co_nm - ownr_ctry - ownr_ea - ownr_fax - ownr_faxx - ownr_fn - ownr_ln - ownr_ph1 - ownr_ph1x - ownr_ph2 - ownr_ph2x - ownr_st - ownr_title - ownr_zip - parts_tax_rates - pay_amt - pay_chknm - pay_type - payee_nms - plate_no - plate_st - po_number - policy_no - rate_la1 - rate_la2 - rate_la3 - rate_la4 - rate_laa - rate_lab - rate_lad - rate_lae - rate_lag - rate_laf - rate_lam - rate_lar - rate_las - rate_lau - rate_ma2s - rate_ma2t - rate_ma3s - rate_mabl - rate_macs - rate_mahw - rate_mapa - rate_mash - rate_matd - referral_source - referral_source_extra - regie_number - selling_dealer - selling_dealer_contact - servicing_dealer - servicing_dealer_contact - shopid - special_coverage_policy - state_tax_rate - storage_payable - tax_lbr_rt - tax_levies_rt - tax_paint_mat_rt - tax_predis - tax_prethr - tax_pstthr - tax_str_rt - tax_sub_rt - tax_thramt - tax_tow_rt - theft_ind - tlos_ind - towing_payable - unit_number - v_color - v_make_desc - v_model_desc - v_model_yr - v_vin - vehicleid - joblines(where: { removed: { _eq: false } }) { - act_price - alt_co_id - alt_overrd - alt_part_i - alt_partm - bett_amt - alt_partno - bett_pctg - bett_tax - bett_type - cert_part - db_hrs - db_price - db_ref - est_seq - glass_flag - id - lbr_amt - lbr_hrs_j - lbr_inc - lbr_op - lbr_op_j - lbr_tax - lbr_typ_j - line_desc - line_ind - line_ref - misc_amt - misc_sublt - misc_tax - mod_lb_hrs - mod_lbr_ty - oem_partno - op_code_desc - paint_stg - paint_tone - part_qty - part_type - price_inc - price_j - prt_dsmk_m - prt_dsmk_p - status - tax_part - unq_seq - manual_line - } - employee_body - employee_refinish - employee_prep - driveable - towin - } + query QUERY_ALL_JOB_FIELDS($id: uuid!) { + jobs_by_pk(id: $id) { + id + adj_g_disc + adj_strdis + adj_towdis + adjustment_bottom_line + agt_addr1 + agt_addr2 + agt_city + agt_co_id + agt_co_nm + agt_ct_fn + agt_ct_ln + agt_ct_ph + agt_ct_phx + agt_ctry + agt_ea + agt_faxx + agt_fax + agt_lic_no + agt_ph1 + agt_ph1x + agt_ph2 + agt_zip + agt_st + agt_ph2x + area_of_damage + cat_no + cieca_stl + cieca_ttl + clm_addr1 + clm_addr2 + clm_city + clm_ct_fn + clm_ct_ln + clm_ct_ph + clm_ct_phx + clm_ctry + clm_ea + clm_fax + clm_faxx + clm_ofc_id + clm_ofc_nm + clm_ph1 + clm_ph1x + clm_ph2 + clm_ph2x + clm_st + clm_title + clm_total + clm_zip + cust_pr + ded_amt + ded_status + depreciation_taxes + est_addr1 + est_addr2 + est_city + est_co_nm + est_ct_fn + est_ct_ln + est_ctry + est_ea + est_ph1 + est_st + est_zip + federal_tax_rate + g_bett_amt + ins_addr1 + ins_addr2 + ins_city + ins_co_id + ins_co_nm + ins_ct_fn + ins_ct_ln + ins_ct_ph + ins_ct_phx + ins_ctry + ins_ea + ins_fax + ins_faxx + ins_memo + ins_ph1 + ins_ph1x + ins_ph2 + ins_ph2x + ins_st + ins_title + ins_zip + insd_addr1 + insd_addr2 + insd_city + insd_co_nm + insd_ctry + insd_ea + insd_fax + insd_faxx + insd_fn + insd_ln + insd_ph1 + insd_ph1x + insd_ph2 + insd_ph2x + insd_st + insd_title + insd_zip + job_totals + labor_rate_desc + labor_rate_id + local_tax_rate + other_amount_payable + owner_owing + ownerid + ownr_addr1 + ownr_addr2 + ownr_city + ownr_co_nm + ownr_ctry + ownr_ea + ownr_fax + ownr_faxx + ownr_fn + ownr_ln + ownr_ph1 + ownr_ph1x + ownr_ph2 + ownr_ph2x + ownr_st + ownr_title + ownr_zip + parts_tax_rates + pay_amt + pay_chknm + pay_type + payee_nms + plate_no + plate_st + po_number + policy_no + rate_la1 + rate_la2 + rate_la3 + rate_la4 + rate_laa + rate_lab + rate_lad + rate_lae + rate_lag + rate_laf + rate_lam + rate_lar + rate_las + rate_lau + rate_ma2s + rate_ma2t + rate_ma3s + rate_mabl + rate_macs + rate_mahw + rate_mapa + rate_mash + rate_matd + referral_source + referral_source_extra + regie_number + selling_dealer + selling_dealer_contact + servicing_dealer + servicing_dealer_contact + shopid + special_coverage_policy + state_tax_rate + storage_payable + tax_lbr_rt + tax_levies_rt + tax_paint_mat_rt + tax_predis + tax_prethr + tax_pstthr + tax_str_rt + tax_sub_rt + tax_thramt + tax_tow_rt + theft_ind + tlos_ind + towing_payable + unit_number + v_color + v_make_desc + v_model_desc + v_model_yr + v_vin + vehicleid + joblines(where: { removed: { _eq: false } }) { + act_price + alt_co_id + alt_overrd + alt_part_i + alt_partm + bett_amt + alt_partno + bett_pctg + bett_tax + bett_type + cert_part + db_hrs + db_price + db_ref + est_seq + glass_flag + id + lbr_amt + lbr_hrs_j + lbr_inc + lbr_op + lbr_op_j + lbr_tax + lbr_typ_j + line_desc + line_ind + line_ref + misc_amt + misc_sublt + misc_tax + mod_lb_hrs + mod_lbr_ty + oem_partno + op_code_desc + paint_stg + paint_tone + part_qty + part_type + price_inc + price_j + prt_dsmk_m + prt_dsmk_p + status + tax_part + unq_seq + manual_line + } + employee_body + employee_refinish + employee_prep + driveable + towin } + } `; export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql` - query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED( - $offset: Int - $limit: Int - $order: [jobs_order_by!] - $statusList: [String!] - ) { - jobs( - offset: $offset - limit: $limit - order_by: $order - where: { status: { _in: $statusList } } - ) { - comment - ownr_fn - ownr_ln - ownr_co_nm - ownr_ph1 - ownr_ph2 - plate_no - plate_st - v_vin - v_model_yr - v_model_desc - v_make_desc - v_color - vehicleid - id - ins_co_nm - clm_no - clm_total - owner_owing - ro_number - po_number - status - updated_at - ded_amt - } - jobs_aggregate(where: { status: { _in: $statusList } }) { - aggregate { - count(distinct: true) - } - } + query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED( + $offset: Int + $limit: Int + $order: [jobs_order_by!] + $statusList: [String!] + ) { + jobs(offset: $offset, limit: $limit, order_by: $order, where: { status: { _in: $statusList } }) { + comment + ownr_fn + ownr_ln + ownr_co_nm + ownr_ph1 + ownr_ph2 + plate_no + plate_st + v_vin + v_model_yr + v_model_desc + v_make_desc + v_color + vehicleid + id + ins_co_nm + clm_no + clm_total + owner_owing + ro_number + po_number + status + updated_at + ded_amt } + jobs_aggregate(where: { status: { _in: $statusList } }) { + aggregate { + count(distinct: true) + } + } + } `; export const QUERY_JOB_CLOSE_DETAILS = gql` - query QUERY_JOB_CLOSE_DETAILS($id: uuid!) { - jobs_by_pk(id: $id) { - ro_number - invoice_allocation - invoice_final_note - ins_co_id - dms_allocation - id - inproduction - ded_amt - ded_status - depreciation_taxes - other_amount_payable - towing_payable - storage_payable - adjustment_bottom_line - federal_tax_rate - state_tax_rate - local_tax_rate - tax_tow_rt - tax_str_rt - tax_paint_mat_rt - tax_sub_rt - tax_lbr_rt - tax_levies_rt - parts_tax_rates - job_totals - rate_la1 - rate_la2 - rate_la3 - rate_la4 - rate_laa - rate_lab - rate_lad - rate_lae - rate_laf - rate_lag - rate_lam - rate_lar - rate_las - rate_lau - rate_ma2s - rate_ma2t - rate_ma3s - rate_mabl - rate_macs - rate_mahw - rate_mapa - rate_mash - rate_matd - status - date_exported - date_invoiced - voided - scheduled_completion - actual_completion - scheduled_delivery - actual_delivery - scheduled_in - date_invoiced - actual_in - kmin - kmout - qb_multiple_payers - lbr_adjustments - joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) { - id - removed - tax_part - line_desc - prt_dsmk_p - prt_dsmk_m - part_type - oem_partno - db_price - act_price - part_qty - mod_lbr_ty - db_hrs - mod_lb_hrs - lbr_op - lbr_amt - op_code_desc - profitcenter_labor - profitcenter_part - prt_dsmk_p - convertedtolbr - convertedtolbr_data - } - } + query QUERY_JOB_CLOSE_DETAILS($id: uuid!) { + jobs_by_pk(id: $id) { + ro_number + invoice_allocation + invoice_final_note + ins_co_id + dms_allocation + id + inproduction + ded_amt + ded_status + depreciation_taxes + other_amount_payable + towing_payable + storage_payable + adjustment_bottom_line + federal_tax_rate + state_tax_rate + local_tax_rate + tax_tow_rt + tax_str_rt + tax_paint_mat_rt + tax_sub_rt + tax_lbr_rt + tax_levies_rt + parts_tax_rates + job_totals + rate_la1 + rate_la2 + rate_la3 + rate_la4 + rate_laa + rate_lab + rate_lad + rate_lae + rate_laf + rate_lag + rate_lam + rate_lar + rate_las + rate_lau + rate_ma2s + rate_ma2t + rate_ma3s + rate_mabl + rate_macs + rate_mahw + rate_mapa + rate_mash + rate_matd + status + date_exported + date_invoiced + voided + scheduled_completion + actual_completion + scheduled_delivery + actual_delivery + scheduled_in + date_invoiced + actual_in + kmin + kmout + qb_multiple_payers + lbr_adjustments + joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) { + id + removed + tax_part + line_desc + prt_dsmk_p + prt_dsmk_m + part_type + oem_partno + db_price + act_price + part_qty + mod_lbr_ty + db_hrs + mod_lb_hrs + lbr_op + lbr_amt + op_code_desc + profitcenter_labor + profitcenter_part + prt_dsmk_p + convertedtolbr + convertedtolbr_data + } } + } `; export const generate_UPDATE_JOB_KANBAN = ( - oldChildId, - oldChildNewParent, - movedId, - movedNewParent, - movedNewStatus, - newChildId, - newChildParent + oldChildId, + oldChildNewParent, + movedId, + movedNewParent, + movedNewStatus, + newChildId, + newChildParent ) => { - // console.log("oldChildId", oldChildId, "oldChildNewParent", oldChildNewParent); - // console.log("Moved", movedId, movedNewParent, movedNewStatus); - // console.log("new", newChildId, newChildParent); + // console.log("oldChildId", oldChildId, "oldChildNewParent", oldChildNewParent); + // console.log("Moved", movedId, movedNewParent, movedNewStatus); + // console.log("new", newChildId, newChildParent); - const oldChildQuery = ` + const oldChildQuery = ` updateOldChild: update_jobs(where: { id: { _eq: "${oldChildId}" } }, - _set: {kanbanparent: ${ - oldChildNewParent ? `"${oldChildNewParent}"` : null - }}) { + _set: {kanbanparent: ${oldChildNewParent ? `"${oldChildNewParent}"` : null}}) { returning { id kanbanparent } }`; - const movedQuery = ` + const movedQuery = ` updateMovedChild: update_jobs(where: { id: { _eq: "${movedId}" } }, - _set: {kanbanparent: ${ - movedNewParent ? `"${movedNewParent}"` : null - } , status: "${movedNewStatus}"}) { + _set: {kanbanparent: ${movedNewParent ? `"${movedNewParent}"` : null} , status: "${movedNewStatus}"}) { returning { id status @@ -2095,7 +2029,7 @@ export const generate_UPDATE_JOB_KANBAN = ( } }`; - const newChildQuery = ` + const newChildQuery = ` updateNewChild: update_jobs(where: { id: { _eq: "${newChildId}" } }, _set: {kanbanparent: ${newChildParent ? `"${newChildParent}"` : null}}) { returning { @@ -2104,7 +2038,7 @@ export const generate_UPDATE_JOB_KANBAN = ( } }`; - return gql` + return gql` mutation UPDATE_JOB_KANBAN { ${oldChildId ? oldChildQuery : ""} ${movedId ? movedQuery : ""} @@ -2114,506 +2048,445 @@ export const generate_UPDATE_JOB_KANBAN = ( }; export const QUERY_JOB_LBR_ADJUSTMENTS = gql` - query QUERY_JOB_LBR_ADJUSTMENTS($id: uuid!) { - jobs_by_pk(id: $id) { - id - lbr_adjustments - } + query QUERY_JOB_LBR_ADJUSTMENTS($id: uuid!) { + jobs_by_pk(id: $id) { + id + lbr_adjustments } + } `; export const DELETE_JOB = gql` - mutation DELETE_JOB($id: uuid!) { - delete_jobs_by_pk(id: $id) { - id - } + mutation DELETE_JOB($id: uuid!) { + delete_jobs_by_pk(id: $id) { + id } + } `; export const GET_JOB_FOR_CC_CONTRACT = gql` - query GET_JOB_FOR_CC_CONTRACT($id: uuid!) { - jobs_by_pk(id: $id) { - id - ownr_fn - ownr_ln - ownr_addr1 - ownr_st - ownr_city - ownr_zip - ownr_ph1 - ownr_ph2 - } + query GET_JOB_FOR_CC_CONTRACT($id: uuid!) { + jobs_by_pk(id: $id) { + id + ownr_fn + ownr_ln + ownr_addr1 + ownr_st + ownr_city + ownr_zip + ownr_ph1 + ownr_ph2 } + } `; export const QUERY_JOB_CHECKLISTS = gql` - query QUERY_JOB_CHECKLISTS($id: uuid!) { - jobs_by_pk(id: $id) { - id - deliverchecklist - intakechecklist - ro_number - scheduled_completion - actual_completion - scheduled_delivery - actual_delivery - production_vars - owner { - id - allow_text_message - } - bodyshop { - id - intakechecklist - deliverchecklist - } - } + query QUERY_JOB_CHECKLISTS($id: uuid!) { + jobs_by_pk(id: $id) { + id + deliverchecklist + intakechecklist + ro_number + scheduled_completion + actual_completion + scheduled_delivery + actual_delivery + production_vars + owner { + id + allow_text_message + } + bodyshop { + id + intakechecklist + deliverchecklist + } } + } `; export const FIND_JOBS_BY_CLAIM = gql` - query FIND_JOBS_BY_CLAIM($claimNumbers: String!) { - jobs(where: { clm_no: { _similar: $claimNumbers } }) { - id - clm_no - ro_number - actual_completion - ownr_fn - ownr_ln - ownr_co_nm - } + query FIND_JOBS_BY_CLAIM($claimNumbers: String!) { + jobs(where: { clm_no: { _similar: $claimNumbers } }) { + id + clm_no + ro_number + actual_completion + ownr_fn + ownr_ln + ownr_co_nm } + } `; export const QUERY_JOB_EXPORT_DMS = gql` - query QUERY_JOB_EXPORT_DMS($id: uuid!) { - jobs_by_pk(id: $id) { - id - ro_number - po_number - clm_no - job_totals - ded_amt - ded_status - ownr_fn - ownr_ln - ownr_co_nm - ins_co_nm - kmin - kmout - v_make_desc - v_model_yr - v_model_desc - area_of_damage - date_exported - } + query QUERY_JOB_EXPORT_DMS($id: uuid!) { + jobs_by_pk(id: $id) { + id + ro_number + po_number + clm_no + job_totals + ded_amt + ded_status + ownr_fn + ownr_ln + ownr_co_nm + ins_co_nm + kmin + kmout + v_make_desc + v_model_yr + v_model_desc + area_of_damage + date_exported } + } `; export const QUERY_RELATED_ROS = gql` - query QUERY_RELATED_ROS($jobid: uuid!) { - relatedjobs( - where: { - _or: [{ childjob: { _eq: $jobid } }, { parentjob: { _eq: $jobid } }] - } - ) { - parentjob - id - parentjob_rel { - id - ro_number - } - childjob - childjob_rel { - id - ro_number - } - } + query QUERY_RELATED_ROS($jobid: uuid!) { + relatedjobs(where: { _or: [{ childjob: { _eq: $jobid } }, { parentjob: { _eq: $jobid } }] }) { + parentjob + id + parentjob_rel { + id + ro_number + } + childjob + childjob_rel { + id + ro_number + } } + } `; export const INSERT_RELATED_ROS = gql` - mutation INSERT_RELATED_ROS($relationship: relatedjobs_insert_input!) { - insert_relatedjobs_one(object: $relationship) { - parentjob - id - parentjob_rel { - id - ro_number - } - childjob - childjob_rel { - id - ro_number - } - } + mutation INSERT_RELATED_ROS($relationship: relatedjobs_insert_input!) { + insert_relatedjobs_one(object: $relationship) { + parentjob + id + parentjob_rel { + id + ro_number + } + childjob + childjob_rel { + id + ro_number + } } + } `; export const DELETE_RELATED_RO = gql` - mutation DELETE_RELATED_RO($relationshipid: uuid!) { - delete_relatedjobs_by_pk(id: $relationshipid) { - id - } + mutation DELETE_RELATED_RO($relationshipid: uuid!) { + delete_relatedjobs_by_pk(id: $relationshipid) { + id } + } `; export const GET_JOB_LINE_ORDERS = gql` - query GET_JOB_LINE_ORDERS($joblineid: uuid!) { - billlines(where: { joblineid: { _eq: $joblineid } }) { - actual_cost - actual_price - billid - quantity - bill { - id - invoice_number - date - vendorid - vendor { - id - name - } - } - } - parts_dispatch_lines(where: { joblineid: { _eq: $joblineid } }) { - id - accepted_at - quantity - parts_dispatch { - id - employeeid - dispatched_at - dispatched_by - number - } - } - parts_order_lines(where: { job_line_id: { _eq: $joblineid } }) { - id - act_price - parts_order { - id - order_date - order_number - orderedby - return - comments - vendor { - id - name - } - } + query GET_JOB_LINE_ORDERS($joblineid: uuid!) { + billlines(where: { joblineid: { _eq: $joblineid } }) { + actual_cost + actual_price + billid + quantity + bill { + id + invoice_number + date + vendorid + vendor { + id + name } + } } + parts_dispatch_lines(where: { joblineid: { _eq: $joblineid } }) { + id + accepted_at + quantity + parts_dispatch { + id + employeeid + dispatched_at + dispatched_by + number + } + } + parts_order_lines(where: { job_line_id: { _eq: $joblineid } }) { + id + act_price + parts_order { + id + order_date + order_number + orderedby + return + comments + vendor { + id + name + } + } + } + } `; export const UPDATE_REMOVE_FROM_AR = gql` - mutation UPDATE_REMOVE_FROM_AR($jobId: uuid!, $remove_from_ar: Boolean!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { remove_from_ar: $remove_from_ar } - ) { - id - remove_from_ar - } + mutation UPDATE_REMOVE_FROM_AR($jobId: uuid!, $remove_from_ar: Boolean!) { + update_jobs_by_pk(pk_columns: { id: $jobId }, _set: { remove_from_ar: $remove_from_ar }) { + id + remove_from_ar } + } `; export const UNVOID_JOB = gql` - mutation UNVOID_JOB( - $jobId: uuid! - $default_imported: String! - $currentUserEmail: String! - $text: String! - ) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { voided: false, status: $default_imported, date_void: null } - ) { - id - date_void - voided - status - } - insert_notes( - objects: { - jobid: $jobId - audit: true - created_by: $currentUserEmail - text: $text - } - ) { - returning { - id - } - } + mutation UNVOID_JOB($jobId: uuid!, $default_imported: String!, $currentUserEmail: String!, $text: String!) { + update_jobs_by_pk(pk_columns: { id: $jobId }, _set: { voided: false, status: $default_imported, date_void: null }) { + id + date_void + voided + status } + insert_notes(objects: { jobid: $jobId, audit: true, created_by: $currentUserEmail, text: $text }) { + returning { + id + } + } + } `; export const DELETE_INTAKE_CHECKLIST = gql` - mutation DELETE_INTAKE($jobId: uuid!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { intakechecklist: null } - ) { - id - intakechecklist - } + mutation DELETE_INTAKE($jobId: uuid!) { + update_jobs_by_pk(pk_columns: { id: $jobId }, _set: { intakechecklist: null }) { + id + intakechecklist } + } `; export const DELETE_DELIVERY_CHECKLIST = gql` - mutation DELETE_DELIVERY($jobId: uuid!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { deliverchecklist: null } - ) { - id - deliverchecklist - } + mutation DELETE_DELIVERY($jobId: uuid!) { + update_jobs_by_pk(pk_columns: { id: $jobId }, _set: { deliverchecklist: null }) { + id + deliverchecklist } + } `; export const MARK_JOB_FOR_REEXPORT = gql` - mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!, $default_invoiced: String!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { date_exported: null, status: $default_invoiced } - ) { - id - date_exported - status - date_invoiced - } + mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!, $default_invoiced: String!) { + update_jobs_by_pk(pk_columns: { id: $jobId }, _set: { date_exported: null, status: $default_invoiced }) { + id + date_exported + status + date_invoiced } + } `; export const MARK_JOB_AS_EXPORTED = gql` - mutation MARK_JOB_AS_EXPORTED( - $jobId: uuid! - $date_exported: timestamptz! - $default_exported: String! - ) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { date_exported: $date_exported, status: $default_exported } - ) { - id - date_exported - date_invoiced - status - } + mutation MARK_JOB_AS_EXPORTED($jobId: uuid!, $date_exported: timestamptz!, $default_exported: String!) { + update_jobs_by_pk(pk_columns: { id: $jobId }, _set: { date_exported: $date_exported, status: $default_exported }) { + id + date_exported + date_invoiced + status } + } `; export const MARK_JOB_AS_UNINVOICED = gql` - mutation MARK_JOB_AS_UNINVOICED($jobId: uuid!, $default_delivered: String!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { - date_exported: null - date_invoiced: null - status: $default_delivered - } - ) { - id - date_exported - date_invoiced - status - } + mutation MARK_JOB_AS_UNINVOICED($jobId: uuid!, $default_delivered: String!) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { date_exported: null, date_invoiced: null, status: $default_delivered } + ) { + id + date_exported + date_invoiced + status } + } `; export const QUERY_COMPLETED_TASKS = gql` - query QUERY_COMPLETED_TASKS($jobid: uuid!) { - jobs_by_pk(id: $jobid) { - id - completed_tasks - } + query QUERY_COMPLETED_TASKS($jobid: uuid!) { + jobs_by_pk(id: $jobid) { + id + completed_tasks } + } `; export const QUERY_JOBS_TECH_ASIGNED_TO_BY_TEAM = gql` - query QUERY_JOBS_TECH_ASIGNED_TO_BY_TEAM($teamIds: [uuid!]!) { - jobs( - where: { - inproduction: { _eq: true } - joblines: { assigned_team: { _in: $teamIds } } - } - ) { - id - v_make_desc - v_model_desc - v_color - v_vin - plate_no - plate_st - clm_no - ownr_fn - ownr_ln - ownr_co_nm - status - ro_number - } + query QUERY_JOBS_TECH_ASIGNED_TO_BY_TEAM($teamIds: [uuid!]!) { + jobs(where: { inproduction: { _eq: true }, joblines: { assigned_team: { _in: $teamIds } } }) { + id + v_make_desc + v_model_desc + v_color + v_vin + plate_no + plate_st + clm_no + ownr_fn + ownr_ln + ownr_co_nm + status + ro_number } + } `; export const QUERY_PARTS_QUEUE_CARD_DETAILS = gql` - query QUERY_JOB_CARD_DETAILS($id: uuid!) { - jobs_by_pk(id: $id) { - actual_completion - actual_delivery - actual_in - alt_transport - available_jobs { - id - } - area_of_damage - ca_gst_registrant - cccontracts { - agreementnumber - courtesycar { - id - make - model - year - plate - fleetnumber - } - id - scheduledreturn - start - status - } - clm_no - clm_total - comment - date_estimated - date_exported - date_invoiced - date_last_contacted - date_next_contact - date_open - date_repairstarted - date_scheduled - ded_amt - employee_body - employee_body_rel { - id - first_name - last_name - } - employee_csr - employee_csr_rel { - id - first_name - last_name - } - employee_prep - employee_prep_rel { - id - first_name - last_name - } - employee_refinish - employee_refinish_rel { - id - first_name - last_name - } - est_co_nm - est_ct_fn - est_ct_ln - est_ea - est_ph1 - id - ins_co_nm - ins_ct_fn - ins_ct_ln - ins_ea - ins_ph1 - inproduction - job_totals - joblines( - order_by: { line_no: asc } - where: { - part_type: { - _in: [ - "PAN" - "PAC" - "PAR" - "PAL" - "PAA" - "PAM" - "PAP" - "PAG" - ] - } - removed: { _eq: false } - } - ) { - act_price - alt_partno - db_ref - id - line_desc - line_no - location - mod_lbr_ty - mod_lb_hrs - oem_partno - part_qty - part_type - prt_dsmk_m - status - } - lbr_adjustments - ownr_co_nm - ownr_ea - ownr_fn - ownr_ln - ownr_ph1 - ownr_ph2 - owner { - id - allow_text_message - preferred_contact - tax_number - } - owner_owing - plate_no - plate_st - po_number - production_vars - ro_number - scheduled_completion - scheduled_delivery - scheduled_in - special_coverage_policy - status - suspended - updated_at - vehicle { - id - jobs { - id - clm_no - ro_number - } - notes - plate_no - v_color - v_make_desc - v_model_desc - v_model_yr - } - vehicleid - v_color - v_make_desc - v_model_desc - v_model_yr - v_vin - voided + query QUERY_JOB_CARD_DETAILS($id: uuid!) { + jobs_by_pk(id: $id) { + actual_completion + actual_delivery + actual_in + alt_transport + available_jobs { + id + } + area_of_damage + ca_gst_registrant + cccontracts { + agreementnumber + courtesycar { + id + make + model + year + plate + fleetnumber } + id + scheduledreturn + start + status + } + clm_no + clm_total + comment + date_estimated + date_exported + date_invoiced + date_last_contacted + date_next_contact + date_open + date_repairstarted + date_scheduled + ded_amt + employee_body + employee_body_rel { + id + first_name + last_name + } + employee_csr + employee_csr_rel { + id + first_name + last_name + } + employee_prep + employee_prep_rel { + id + first_name + last_name + } + employee_refinish + employee_refinish_rel { + id + first_name + last_name + } + est_co_nm + est_ct_fn + est_ct_ln + est_ea + est_ph1 + id + ins_co_nm + ins_ct_fn + ins_ct_ln + ins_ea + ins_ph1 + inproduction + job_totals + joblines( + order_by: { line_no: asc } + where: { part_type: { _in: ["PAN", "PAC", "PAR", "PAL", "PAA", "PAM", "PAP", "PAG"] }, removed: { _eq: false } } + ) { + act_price + alt_partno + db_ref + id + line_desc + line_no + location + mod_lbr_ty + mod_lb_hrs + oem_partno + part_qty + part_type + prt_dsmk_m + status + } + lbr_adjustments + ownr_co_nm + ownr_ea + ownr_fn + ownr_ln + ownr_ph1 + ownr_ph2 + owner { + id + allow_text_message + preferred_contact + tax_number + } + owner_owing + plate_no + plate_st + po_number + production_vars + ro_number + scheduled_completion + scheduled_delivery + scheduled_in + special_coverage_policy + status + suspended + updated_at + vehicle { + id + jobs { + id + clm_no + ro_number + } + notes + plate_no + v_color + v_make_desc + v_model_desc + v_model_yr + } + vehicleid + v_color + v_make_desc + v_model_desc + v_model_yr + v_vin + voided } + } `; diff --git a/client/src/graphql/messages.queries.js b/client/src/graphql/messages.queries.js index 747d05d7a..202be9979 100644 --- a/client/src/graphql/messages.queries.js +++ b/client/src/graphql/messages.queries.js @@ -1,16 +1,13 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const MARK_MESSAGES_AS_READ_BY_CONVERSATION = gql` - mutation MARK_MESSAGES_AS_READ_BY_CONVERSATION($conversationId: uuid) { - update_messages( - where: { conversationid: { _eq: $conversationId } } - _set: { read: true } - ) { - returning { - id - read - isoutbound - } - } + mutation MARK_MESSAGES_AS_READ_BY_CONVERSATION($conversationId: uuid) { + update_messages(where: { conversationid: { _eq: $conversationId } }, _set: { read: true }) { + returning { + id + read + isoutbound + } } + } `; diff --git a/client/src/graphql/metadata.queries.js b/client/src/graphql/metadata.queries.js index 54357ce0c..0121a598e 100644 --- a/client/src/graphql/metadata.queries.js +++ b/client/src/graphql/metadata.queries.js @@ -1,22 +1,22 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const GET_LANDING_NAV_ITEMS = gql` - query nav_items { - masterdata_by_pk(key: "LANDING_NAV_ITEMS") { - value - } + query nav_items { + masterdata_by_pk(key: "LANDING_NAV_ITEMS") { + value } + } `; export const GET_NAV_ITEMS = gql` - query nav_items { - masterdata_by_pk(key: "NAV_ITEMS") { - value - } + query nav_items { + masterdata_by_pk(key: "NAV_ITEMS") { + value } + } `; export const GET_SELECTED_NAV_ITEM = gql` - query selected_nav_item { - selectedNavItem @client - } + query selected_nav_item { + selectedNavItem @client + } `; diff --git a/client/src/graphql/notes.queries.js b/client/src/graphql/notes.queries.js index 658e5796e..791d48b08 100644 --- a/client/src/graphql/notes.queries.js +++ b/client/src/graphql/notes.queries.js @@ -1,79 +1,79 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const INSERT_NEW_NOTE = gql` - mutation INSERT_NEW_NOTE($noteInput: [notes_insert_input!]!) { - insert_notes(objects: $noteInput) { - returning { - created_at - created_by - critical - id - jobid - private - text - updated_at - audit - type - } - } + mutation INSERT_NEW_NOTE($noteInput: [notes_insert_input!]!) { + insert_notes(objects: $noteInput) { + returning { + created_at + created_by + critical + id + jobid + private + text + updated_at + audit + type + } } + } `; export const QUERY_NOTES_BY_JOB_PK = gql` - query QUERY_NOTES_BY_JOB_PK($id: uuid!) { - jobs_by_pk(id: $id) { - id - ro_number - vehicle { - jobs { - id - ro_number - status - clm_no - } - } - notes { - created_at - created_by - critical - id - jobid - private - text - updated_at - audit - type - } + query QUERY_NOTES_BY_JOB_PK($id: uuid!) { + jobs_by_pk(id: $id) { + id + ro_number + vehicle { + jobs { + id + ro_number + status + clm_no } + } + notes { + created_at + created_by + critical + id + jobid + private + text + updated_at + audit + type + } } + } `; export const UPDATE_NOTE = gql` - mutation UPDATE_NOTE($noteId: uuid!, $note: notes_set_input!) { - update_notes(where: { id: { _eq: $noteId } }, _set: $note) { - returning { - id - created_at - created_by - critical - id - jobid - private - text - updated_at - audit - type - } - } + mutation UPDATE_NOTE($noteId: uuid!, $note: notes_set_input!) { + update_notes(where: { id: { _eq: $noteId } }, _set: $note) { + returning { + id + created_at + created_by + critical + id + jobid + private + text + updated_at + audit + type + } } + } `; export const DELETE_NOTE = gql` - mutation DELETE_NOTE($noteId: uuid!) { - delete_notes(where: { id: { _eq: $noteId } }) { - returning { - id - } - } + mutation DELETE_NOTE($noteId: uuid!) { + delete_notes(where: { id: { _eq: $noteId } }) { + returning { + id + } } + } `; diff --git a/client/src/graphql/owners.queries.js b/client/src/graphql/owners.queries.js index 743f6c7ed..35f28a70c 100644 --- a/client/src/graphql/owners.queries.js +++ b/client/src/graphql/owners.queries.js @@ -1,189 +1,175 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_SEARCH_OWNER_BY_IDX = gql` - query QUERY_SEARCH_OWNER_BY_IDX($search: String!) { - search_owners(args: { search: $search }, limit: 100) { - ownr_fn - ownr_ln - ownr_co_nm - ownr_ph1 - ownr_ph2 - ownr_addr1 - ownr_addr2 - ownr_city - ownr_ctry - ownr_ea - ownr_st - ownr_zip - id - note - } + query QUERY_SEARCH_OWNER_BY_IDX($search: String!) { + search_owners(args: { search: $search }, limit: 100) { + ownr_fn + ownr_ln + ownr_co_nm + ownr_ph1 + ownr_ph2 + ownr_addr1 + ownr_addr2 + ownr_city + ownr_ctry + ownr_ea + ownr_st + ownr_zip + id + note } + } `; export const SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE = gql` - query SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE($id: uuid!) { - owners_by_pk(id: $id) { - id - ownr_fn - ownr_ln - ownr_co_nm - ownr_addr1 - } + query SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE($id: uuid!) { + owners_by_pk(id: $id) { + id + ownr_fn + ownr_ln + ownr_co_nm + ownr_addr1 } + } `; export const SEARCH_OWNERS_FOR_AUTOCOMPLETE = gql` - query SEARCH_OWNERS_FOR_AUTOCOMPLETE($search: String) { - search_owners( - args: { search: $search } - limit: 25 - order_by: { ownr_ln: desc_nulls_last } - ) { - id - ownr_fn - ownr_ln - ownr_co_nm - ownr_addr1 - } + query SEARCH_OWNERS_FOR_AUTOCOMPLETE($search: String) { + search_owners(args: { search: $search }, limit: 25, order_by: { ownr_ln: desc_nulls_last }) { + id + ownr_fn + ownr_ln + ownr_co_nm + ownr_addr1 } + } `; export const QUERY_OWNER_BY_ID = gql` - query QUERY_OWNER_BY_ID($id: uuid!) { - owners_by_pk(id: $id) { - id - allow_text_message - ownr_addr1 - ownr_addr2 - ownr_co_nm - ownr_city - ownr_ctry - ownr_ea - ownr_fn - ownr_ph1 - ownr_ln - ownr_ph2 - ownr_st - ownr_title - ownr_zip - preferred_contact - note - tax_number - jobs(order_by: { date_open: desc }) { - id - ro_number - clm_no - status - clm_total - v_model_yr - v_model_desc - v_make_desc - vehicleid - } - } + query QUERY_OWNER_BY_ID($id: uuid!) { + owners_by_pk(id: $id) { + id + allow_text_message + ownr_addr1 + ownr_addr2 + ownr_co_nm + ownr_city + ownr_ctry + ownr_ea + ownr_fn + ownr_ph1 + ownr_ln + ownr_ph2 + ownr_st + ownr_title + ownr_zip + preferred_contact + note + tax_number + jobs(order_by: { date_open: desc }) { + id + ro_number + clm_no + status + clm_total + v_model_yr + v_model_desc + v_make_desc + vehicleid + } } + } `; export const UPDATE_OWNER = gql` - mutation UPDATE_OWNER($ownerId: uuid!, $owner: owners_set_input!) { - update_owners(where: { id: { _eq: $ownerId } }, _set: $owner) { - returning { - id - } - } + mutation UPDATE_OWNER($ownerId: uuid!, $owner: owners_set_input!) { + update_owners(where: { id: { _eq: $ownerId } }, _set: $owner) { + returning { + id + } } + } `; export const DELETE_OWNER = gql` - mutation DELETE_OWNER($id: uuid!) { - delete_owners_by_pk(id: $id) { - id - } + mutation DELETE_OWNER($id: uuid!) { + delete_owners_by_pk(id: $id) { + id } + } `; export const QUERY_ALL_OWNERS = gql` - query QUERY_ALL_OWNERS { - owners { - id - allow_text_message - created_at - ownr_addr1 - ownr_addr2 - ownr_co_nm - ownr_city - ownr_ctry - ownr_ea - ownr_fn - ownr_ph1 - ownr_ln - ownr_ph2 - ownr_st - ownr_title - ownr_zip - preferred_contact - updated_at - } + query QUERY_ALL_OWNERS { + owners { + id + allow_text_message + created_at + ownr_addr1 + ownr_addr2 + ownr_co_nm + ownr_city + ownr_ctry + ownr_ea + ownr_fn + ownr_ph1 + ownr_ln + ownr_ph2 + ownr_st + ownr_title + ownr_zip + preferred_contact + updated_at } + } `; export const QUERY_ALL_OWNERS_PAGINATED = gql` - query QUERY_ALL_OWNERS_PAGINATED( - $search: String - $offset: Int - $limit: Int - $order: [owners_order_by!]! - ) { - search_owners( - args: { search: $search } - offset: $offset - limit: $limit - order_by: $order - ) { - id - allow_text_message - created_at - ownr_addr1 - ownr_addr2 - ownr_co_nm - ownr_city - ownr_ctry - ownr_ea - ownr_fn - ownr_ph1 - ownr_ln - ownr_ph2 - ownr_st - ownr_title - ownr_zip - preferred_contact - updated_at - } - search_owners_aggregate(args: { search: $search }) { - aggregate { - count(distinct: true) - } - } + query QUERY_ALL_OWNERS_PAGINATED($search: String, $offset: Int, $limit: Int, $order: [owners_order_by!]!) { + search_owners(args: { search: $search }, offset: $offset, limit: $limit, order_by: $order) { + id + allow_text_message + created_at + ownr_addr1 + ownr_addr2 + ownr_co_nm + ownr_city + ownr_ctry + ownr_ea + ownr_fn + ownr_ph1 + ownr_ln + ownr_ph2 + ownr_st + ownr_title + ownr_zip + preferred_contact + updated_at } + search_owners_aggregate(args: { search: $search }) { + aggregate { + count(distinct: true) + } + } + } `; export const QUERY_OWNER_FOR_JOB_CREATION = gql` - query QUERY_OWNER_FOR_JOB_CREATION($id: uuid!) { - owners_by_pk(id: $id) { - id - ownr_addr1 - ownr_addr2 - ownr_co_nm - ownr_city - ownr_ctry - ownr_ea - ownr_fn - ownr_ph1 - ownr_ln - ownr_ph2 - ownr_st - ownr_title - ownr_zip - } + query QUERY_OWNER_FOR_JOB_CREATION($id: uuid!) { + owners_by_pk(id: $id) { + id + ownr_addr1 + ownr_addr2 + ownr_co_nm + ownr_city + ownr_ctry + ownr_ea + ownr_fn + ownr_ph1 + ownr_ln + ownr_ph2 + ownr_st + ownr_title + ownr_zip } + } `; diff --git a/client/src/graphql/parts-dispatch.queries.js b/client/src/graphql/parts-dispatch.queries.js index a1d292718..b6e9c2738 100644 --- a/client/src/graphql/parts-dispatch.queries.js +++ b/client/src/graphql/parts-dispatch.queries.js @@ -1,79 +1,66 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const INSERT_PARTS_DISPATCH = gql` - mutation INSERT_PARTS_DISPATCH($partsDispatch: parts_dispatch_insert_input!) { - insert_parts_dispatch_one(object: $partsDispatch) { - id - jobid - number - employeeid - parts_dispatch_lines { - id - joblineid - quantity - } - } + mutation INSERT_PARTS_DISPATCH($partsDispatch: parts_dispatch_insert_input!) { + insert_parts_dispatch_one(object: $partsDispatch) { + id + jobid + number + employeeid + parts_dispatch_lines { + id + joblineid + quantity + } } + } `; export const GET_UNACCEPTED_PARTS_DISPATCH = gql` - query GET_UNACCEPTED_PARTS_DISPATCH( - $techId: uuid! - $offset: Int - $limit: Int + query GET_UNACCEPTED_PARTS_DISPATCH($techId: uuid!, $offset: Int, $limit: Int) { + parts_dispatch_aggregate( + where: { employeeid: { _eq: $techId }, parts_dispatch_lines: { accepted_at: { _is_null: true } } } ) { - parts_dispatch_aggregate( - where: { - employeeid: { _eq: $techId } - parts_dispatch_lines: { accepted_at: { _is_null: true } } - } - ) { - aggregate { - count(distinct: true) - } - } - parts_dispatch( - offset: $offset - limit: $limit - where: { - employeeid: { _eq: $techId } - parts_dispatch_lines: { accepted_at: { _is_null: true } } - } - ) { - id - job { - id - ro_number - status - v_make_desc - v_model_desc - v_model_yr - v_color - } - dispatched_at - dispatched_by - parts_dispatch_lines { - id - accepted_at - jobline { - line_desc - id - } - quantity - joblineid - } - } + aggregate { + count(distinct: true) + } } + parts_dispatch( + offset: $offset + limit: $limit + where: { employeeid: { _eq: $techId }, parts_dispatch_lines: { accepted_at: { _is_null: true } } } + ) { + id + job { + id + ro_number + status + v_make_desc + v_model_desc + v_model_yr + v_color + } + dispatched_at + dispatched_by + parts_dispatch_lines { + id + accepted_at + jobline { + line_desc + id + } + quantity + joblineid + } + } + } `; export const UPDATE_PARTS_DISPATCH_LINE = gql` - mutation UPDATE_PARTS_DISPATCH_LINE( - $id: uuid! - $line: parts_dispatch_lines_set_input! - ) { - update_parts_dispatch_lines_by_pk(pk_columns: { id: $id }, _set: $line) { - accepted_at - id - } + mutation UPDATE_PARTS_DISPATCH_LINE($id: uuid!, $line: parts_dispatch_lines_set_input!) { + update_parts_dispatch_lines_by_pk(pk_columns: { id: $id }, _set: $line) { + accepted_at + id } + } `; diff --git a/client/src/graphql/parts-orders.queries.js b/client/src/graphql/parts-orders.queries.js index 4921d381e..2fa4aa777 100644 --- a/client/src/graphql/parts-orders.queries.js +++ b/client/src/graphql/parts-orders.queries.js @@ -1,398 +1,374 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const INSERT_NEW_PARTS_ORDERS = gql` - mutation INSERT_NEW_PARTS_ORDERS($po: [parts_orders_insert_input!]!) { - insert_parts_orders(objects: $po) { - returning { - id - order_number - } - } + mutation INSERT_NEW_PARTS_ORDERS($po: [parts_orders_insert_input!]!) { + insert_parts_orders(objects: $po) { + returning { + id + order_number + } } + } `; export const QUERY_PARTS_ORDER_OEC = gql` - query QUERY_PARTS_ORDER_OEC($id: uuid!) { - parts_orders_by_pk(id: $id) { - parts_order_lines { - jobline { - tran_code - act_price - db_ref - db_price - db_hrs - glass_flag - id - lbr_amt - lbr_hrs_j - lbr_inc - lbr_op - lbr_op_j - lbr_tax - lbr_typ_j - line_desc - line_ind - line_no - line_ref - location - misc_amt - misc_sublt - misc_tax - mod_lb_hrs - mod_lbr_ty - oem_partno - op_code_desc - paint_stg - paint_tone - part_qty - part_type - price_inc - price_j - prt_dsmk_m - prt_dsmk_p - tax_part - unq_seq - alt_co_id - alt_overrd - alt_part_i - alt_partm - alt_partno - bett_amt - bett_pctg - bett_tax - bett_type - cert_part - est_seq - } - act_price - id - db_price - line_desc - quantity - part_type - } - job { - bodyshop { - shopname - bill_tax_rates - } - ro_number - clm_no - asgn_no - asgn_date - state_tax_rate - area_of_damage - asgn_no - asgn_type - ciecaid - clm_addr1 - clm_city - clm_addr2 - clm_ct_fn - clm_ct_ln - clm_ct_ph - clm_ct_phx - clm_ctry - clm_ea - clm_fax - clm_faxx - clm_ofc_id - clm_ofc_nm - clm_ph1 - clm_ph1x - clm_ph2 - clm_ph2x - clm_st - clm_title - clm_total - clm_zip - ded_amt - est_addr1 - est_addr2 - est_city - est_co_nm - est_ct_fn - est_ctry - est_ct_ln - est_ea - est_ph1 - est_st - est_zip - g_bett_amt - id - ins_addr1 - ins_city - ins_addr2 - ins_co_id - ins_co_nm - ins_ct_fn - ins_ct_ln - ins_ct_ph - ins_ct_phx - ins_ctry - ins_ea - ins_fax - ins_faxx - ins_memo - ins_ph1 - ins_ph1x - ins_ph2 - ins_ph2x - ins_st - ins_title - ins_zip - insd_addr1 - insd_addr2 - insd_city - insd_co_nm - insd_ctry - insd_ea - insd_fax - insd_faxx - insd_fn - insd_ln - insd_ph1 - insd_ph1x - insd_ph2 - insd_ph2x - insd_st - insd_title - insd_zip - job_totals - loss_cat - loss_date - loss_desc - loss_of_use - loss_type - ownr_addr1 - ownr_addr2 - ownr_city - ownr_co_nm - ownr_ctry - ownr_ea - ownr_fax - ownr_faxx - ownr_ph1 - ownr_fn - ownr_ln - ownr_ph1x - ownr_ph2 - ownr_ph2x - ownr_st - ownr_title - ownr_zip - parts_tax_rates - pay_amt - pay_date - pay_type - pay_chknm - payee_nms - plate_no - plate_st - po_number - policy_no - tax_lbr_rt - tax_levies_rt - tax_paint_mat_rt - tax_predis - tax_prethr - tax_pstthr - tax_registration_number - tax_str_rt - tax_shop_mat_rt - tax_sub_rt - tax_thramt - tax_tow_rt - theft_ind - tlos_ind - towin - v_color - v_make_desc - v_model_desc - v_model_yr - v_vin - vehicle { - v_bstyle - v_type - v_trimcode - v_tone - v_stage - v_prod_dt - v_options - v_paint_codes - v_model_yr - v_model_desc - v_mldgcode - v_makecode - v_make_desc - v_engine - v_cond - v_color - trim_color - shopid - plate_no - plate_st - db_v_code - v_vin - } - agt_zip - agt_st - agt_ph2x - agt_ph2 - agt_ph1x - agt_ph1 - agt_lic_no - agt_faxx - agt_fax - agt_ea - agt_ctry - agt_ct_phx - agt_ct_ph - agt_ct_ln - agt_ct_fn - agt_co_nm - agt_co_id - agt_city - agt_addr1 - agt_addr2 - adj_g_disc - rate_matd - rate_mash - rate_mapa - rate_mahw - rate_macs - rate_mabl - rate_ma3s - rate_ma2t - rate_ma2s - rate_lau - rate_las - rate_lar - rate_lam - rate_lag - rate_laf - rate_lae - rate_lad - rate_lab - rate_laa - rate_la4 - rate_la3 - rate_la2 - rate_la1 - } + query QUERY_PARTS_ORDER_OEC($id: uuid!) { + parts_orders_by_pk(id: $id) { + parts_order_lines { + jobline { + tran_code + act_price + db_ref + db_price + db_hrs + glass_flag + id + lbr_amt + lbr_hrs_j + lbr_inc + lbr_op + lbr_op_j + lbr_tax + lbr_typ_j + line_desc + line_ind + line_no + line_ref + location + misc_amt + misc_sublt + misc_tax + mod_lb_hrs + mod_lbr_ty + oem_partno + op_code_desc + paint_stg + paint_tone + part_qty + part_type + price_inc + price_j + prt_dsmk_m + prt_dsmk_p + tax_part + unq_seq + alt_co_id + alt_overrd + alt_part_i + alt_partm + alt_partno + bett_amt + bett_pctg + bett_tax + bett_type + cert_part + est_seq } + act_price + id + db_price + line_desc + quantity + part_type + } + job { + bodyshop { + shopname + bill_tax_rates + } + ro_number + clm_no + asgn_no + asgn_date + state_tax_rate + area_of_damage + asgn_no + asgn_type + ciecaid + clm_addr1 + clm_city + clm_addr2 + clm_ct_fn + clm_ct_ln + clm_ct_ph + clm_ct_phx + clm_ctry + clm_ea + clm_fax + clm_faxx + clm_ofc_id + clm_ofc_nm + clm_ph1 + clm_ph1x + clm_ph2 + clm_ph2x + clm_st + clm_title + clm_total + clm_zip + ded_amt + est_addr1 + est_addr2 + est_city + est_co_nm + est_ct_fn + est_ctry + est_ct_ln + est_ea + est_ph1 + est_st + est_zip + g_bett_amt + id + ins_addr1 + ins_city + ins_addr2 + ins_co_id + ins_co_nm + ins_ct_fn + ins_ct_ln + ins_ct_ph + ins_ct_phx + ins_ctry + ins_ea + ins_fax + ins_faxx + ins_memo + ins_ph1 + ins_ph1x + ins_ph2 + ins_ph2x + ins_st + ins_title + ins_zip + insd_addr1 + insd_addr2 + insd_city + insd_co_nm + insd_ctry + insd_ea + insd_fax + insd_faxx + insd_fn + insd_ln + insd_ph1 + insd_ph1x + insd_ph2 + insd_ph2x + insd_st + insd_title + insd_zip + job_totals + loss_cat + loss_date + loss_desc + loss_of_use + loss_type + ownr_addr1 + ownr_addr2 + ownr_city + ownr_co_nm + ownr_ctry + ownr_ea + ownr_fax + ownr_faxx + ownr_ph1 + ownr_fn + ownr_ln + ownr_ph1x + ownr_ph2 + ownr_ph2x + ownr_st + ownr_title + ownr_zip + parts_tax_rates + pay_amt + pay_date + pay_type + pay_chknm + payee_nms + plate_no + plate_st + po_number + policy_no + tax_lbr_rt + tax_levies_rt + tax_paint_mat_rt + tax_predis + tax_prethr + tax_pstthr + tax_registration_number + tax_str_rt + tax_shop_mat_rt + tax_sub_rt + tax_thramt + tax_tow_rt + theft_ind + tlos_ind + towin + v_color + v_make_desc + v_model_desc + v_model_yr + v_vin + vehicle { + v_bstyle + v_type + v_trimcode + v_tone + v_stage + v_prod_dt + v_options + v_paint_codes + v_model_yr + v_model_desc + v_mldgcode + v_makecode + v_make_desc + v_engine + v_cond + v_color + trim_color + shopid + plate_no + plate_st + db_v_code + v_vin + } + agt_zip + agt_st + agt_ph2x + agt_ph2 + agt_ph1x + agt_ph1 + agt_lic_no + agt_faxx + agt_fax + agt_ea + agt_ctry + agt_ct_phx + agt_ct_ph + agt_ct_ln + agt_ct_fn + agt_co_nm + agt_co_id + agt_city + agt_addr1 + agt_addr2 + adj_g_disc + rate_matd + rate_mash + rate_mapa + rate_mahw + rate_macs + rate_mabl + rate_ma3s + rate_ma2t + rate_ma2s + rate_lau + rate_las + rate_lar + rate_lam + rate_lag + rate_laf + rate_lae + rate_lad + rate_lab + rate_laa + rate_la4 + rate_la3 + rate_la2 + rate_la1 + } } + } `; export const DELETE_PARTS_ORDER = gql` - mutation DELETE_PARTS_ORDER($partsOrderId: uuid!) { - delete_parts_orders_by_pk(id: $partsOrderId) { - id - } + mutation DELETE_PARTS_ORDER($partsOrderId: uuid!) { + delete_parts_orders_by_pk(id: $partsOrderId) { + id } + } `; export const DELETE_PARTS_ORDER_LINE = gql` - mutation DELETE_PARTS_ORDER_LINE($partsOrderLineId: uuid!) { - delete_parts_order_lines_by_pk(id: $partsOrderLineId) { - id - } + mutation DELETE_PARTS_ORDER_LINE($partsOrderLineId: uuid!) { + delete_parts_order_lines_by_pk(id: $partsOrderLineId) { + id } + } `; export const MUTATION_UPDATE_PO_CM_REECEIVED = gql` - mutation MUTATION_UPDATE_PO_CM_REECEIVED( - $partsLineId: uuid! - $partsOrder: parts_order_lines_set_input - ) { - update_parts_order_lines( - where: { id: { _eq: $partsLineId } } - _set: $partsOrder - ) { - returning { - id - cm_received - } - } + mutation MUTATION_UPDATE_PO_CM_REECEIVED($partsLineId: uuid!, $partsOrder: parts_order_lines_set_input) { + update_parts_order_lines(where: { id: { _eq: $partsLineId } }, _set: $partsOrder) { + returning { + id + cm_received + } } + } `; export const MUTATION_UPDATE_BO_ETA = gql` - mutation MUTATION_UPDATE_BO_ETA( - $partsLineId: uuid! - $partsOrder: parts_order_lines_set_input - ) { - update_parts_order_lines( - where: { id: { _eq: $partsLineId } } - _set: $partsOrder - ) { - returning { - status - backordered_eta - id - } - } + mutation MUTATION_UPDATE_BO_ETA($partsLineId: uuid!, $partsOrder: parts_order_lines_set_input) { + update_parts_order_lines(where: { id: { _eq: $partsLineId } }, _set: $partsOrder) { + returning { + status + backordered_eta + id + } } + } `; export const MUTATION_BACKORDER_PART_LINE = gql` - mutation MUTATION_BACKORDER_PART_LINE( - $jobLineId: uuid! - $partsLineId: uuid! - $status: String! - $partsOrder: parts_order_lines_set_input - ) { - update_parts_order_lines( - where: { id: { _eq: $partsLineId } } - _set: $partsOrder - ) { - returning { - status - backordered_on - backordered_eta - id - } - } - update_joblines( - where: { id: { _eq: $jobLineId } } - _set: { status: $status } - ) { - returning { - status - id - } - } + mutation MUTATION_BACKORDER_PART_LINE( + $jobLineId: uuid! + $partsLineId: uuid! + $status: String! + $partsOrder: parts_order_lines_set_input + ) { + update_parts_order_lines(where: { id: { _eq: $partsLineId } }, _set: $partsOrder) { + returning { + status + backordered_on + backordered_eta + id + } } + update_joblines(where: { id: { _eq: $jobLineId } }, _set: { status: $status }) { + returning { + status + id + } + } + } `; export const QUERY_UNRECEIVED_LINES = gql` - query QUERY_UNRECEIVED_LINES($jobId: uuid!, $vendorId: uuid!) { - parts_order_lines( - where: { - parts_order: { jobid: { _eq: $jobId }, vendorid: { _eq: $vendorId } } - cm_received: { _neq: true } - } - ) { - cm_received - id - line_desc - quantity - act_price - cost - oem_partno - } + query QUERY_UNRECEIVED_LINES($jobId: uuid!, $vendorId: uuid!) { + parts_order_lines( + where: { parts_order: { jobid: { _eq: $jobId }, vendorid: { _eq: $vendorId } }, cm_received: { _neq: true } } + ) { + cm_received + id + line_desc + quantity + act_price + cost + oem_partno } + } `; export const MUTATION_MARK_RETURN_RECEIVED = gql` - mutation MUTATION_MARK_RETURN_RECEIVED($partsLineIds: [uuid!]!) { - update_parts_order_lines( - where: { id: { _in: $partsLineIds } } - _set: { cm_received: true } - ) { - returning { - id - cm_received - } - } + mutation MUTATION_MARK_RETURN_RECEIVED($partsLineIds: [uuid!]!) { + update_parts_order_lines(where: { id: { _in: $partsLineIds } }, _set: { cm_received: true }) { + returning { + id + cm_received + } } + } `; diff --git a/client/src/graphql/payment_response.queries.js b/client/src/graphql/payment_response.queries.js index 3dfd2f100..aa5f5f363 100644 --- a/client/src/graphql/payment_response.queries.js +++ b/client/src/graphql/payment_response.queries.js @@ -1,53 +1,51 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const INSERT_PAYMENT_RESPONSE = gql` - mutation INSERT_PAYMENT_RESPONSE( - $paymentResponse: [payment_response_insert_input!]! - ) { - insert_payment_response(objects: $paymentResponse) { - returning { - id - } - } + mutation INSERT_PAYMENT_RESPONSE($paymentResponse: [payment_response_insert_input!]!) { + insert_payment_response(objects: $paymentResponse) { + returning { + id + } } + } `; export const QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID = gql` - query QUERY_PAYMENT_RESPONSE_BY_PK($paymentid: uuid!) { - payment_response(where: { paymentid: { _eq: $paymentid } }) { - id - jobid - bodyshopid - paymentid - amount - declinereason - ext_paymentid - successful - response - } + query QUERY_PAYMENT_RESPONSE_BY_PK($paymentid: uuid!) { + payment_response(where: { paymentid: { _eq: $paymentid } }) { + id + jobid + bodyshopid + paymentid + amount + declinereason + ext_paymentid + successful + response } + } `; export const QUERY_RO_AND_OWNER_BY_JOB_PKS = gql` - query QUERY_RO_AND_OWNER_BY_JOB_PKS($jobids: [uuid!]!) { - jobs(where: { id: { _in: $jobids } }) { - ro_number - ownr_fn - ownr_ln - ownr_ea - ownr_zip - } + query QUERY_RO_AND_OWNER_BY_JOB_PKS($jobids: [uuid!]!) { + jobs(where: { id: { _in: $jobids } }) { + ro_number + ownr_fn + ownr_ln + ownr_ea + ownr_zip } + } `; export const GET_REFUNDABLE_AMOUNT_BY_JOBID = gql` - query GET_REFUNDABLE_AMOUNT_BY_JOBID($jobid: uuid!) { - payment_response_aggregate(where: { jobid: { _eq: $jobid } }) { - aggregate { - sum { - amount - } - } + query GET_REFUNDABLE_AMOUNT_BY_JOBID($jobid: uuid!) { + payment_response_aggregate(where: { jobid: { _eq: $jobid } }) { + aggregate { + sum { + amount } + } } + } `; diff --git a/client/src/graphql/payments.queries.js b/client/src/graphql/payments.queries.js index d49e21cc1..5e87e8231 100644 --- a/client/src/graphql/payments.queries.js +++ b/client/src/graphql/payments.queries.js @@ -1,182 +1,175 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const INSERT_NEW_PAYMENT = gql` - mutation INSERT_NEW_PAYMENT($paymentInput: [payments_insert_input!]!) { - insert_payments(objects: $paymentInput) { - returning { - id - jobid - amount - payer - created_at - transactionid - memo - date - type - exportedat - } - } + mutation INSERT_NEW_PAYMENT($paymentInput: [payments_insert_input!]!) { + insert_payments(objects: $paymentInput) { + returning { + id + jobid + amount + payer + created_at + transactionid + memo + date + type + exportedat + } } + } `; export const QUERY_ALL_PAYMENTS_PAGINATED = gql` - query QUERY_ALL_PAYMENTS_PAGINATED( - $offset: Int - $limit: Int - $order: [payments_order_by!]! - ) { - payments(offset: $offset, limit: $limit, order_by: $order) { - id - amount - created_at - date - exportedat - jobid - job { - id - ownerid - ownr_co_nm - ownr_fn - ownr_ln - owner { - id - ownr_co_nm - ownr_fn - ownr_ln - } - ro_number - } - memo - payer - paymentnum - stripeid - transactionid - type - } - payments_aggregate { - aggregate { - count(distinct: true) - } + query QUERY_ALL_PAYMENTS_PAGINATED($offset: Int, $limit: Int, $order: [payments_order_by!]!) { + payments(offset: $offset, limit: $limit, order_by: $order) { + id + amount + created_at + date + exportedat + jobid + job { + id + ownerid + ownr_co_nm + ownr_fn + ownr_ln + owner { + id + ownr_co_nm + ownr_fn + ownr_ln } + ro_number + } + memo + payer + paymentnum + stripeid + transactionid + type } + payments_aggregate { + aggregate { + count(distinct: true) + } + } + } `; export const UPDATE_PAYMENT = gql` - mutation UPDATE_PAYMENT($paymentId: uuid!, $payment: payments_set_input!) { - update_payments(where: { id: { _eq: $paymentId } }, _set: $payment) { - returning { - id - amount - created_at - date - exportedat - jobid - job { - id - ownerid - ownr_co_nm - ownr_fn - ownr_ln - owner { - id - ownr_co_nm - ownr_fn - ownr_ln - } - ro_number - } - memo - payer - paymentnum - stripeid - transactionid - type - } + mutation UPDATE_PAYMENT($paymentId: uuid!, $payment: payments_set_input!) { + update_payments(where: { id: { _eq: $paymentId } }, _set: $payment) { + returning { + id + amount + created_at + date + exportedat + jobid + job { + id + ownerid + ownr_co_nm + ownr_fn + ownr_ln + owner { + id + ownr_co_nm + ownr_fn + ownr_ln + } + ro_number } + memo + payer + paymentnum + stripeid + transactionid + type + } } + } `; export const UPDATE_PAYMENTS = gql` - mutation UPDATE_PAYMENTS( - $paymentIdList: [uuid!]! - $payment: payments_set_input! - ) { - update_payments(where: { id: { _in: $paymentIdList } }, _set: $payment) { - returning { - id - amount - created_at - date - exportedat - jobid - job { - id - ownerid - ownr_co_nm - ownr_fn - ownr_ln - owner { - id - ownr_co_nm - ownr_fn - ownr_ln - } - ro_number - } - memo - payer - paymentnum - stripeid - transactionid - type - } + mutation UPDATE_PAYMENTS($paymentIdList: [uuid!]!, $payment: payments_set_input!) { + update_payments(where: { id: { _in: $paymentIdList } }, _set: $payment) { + returning { + id + amount + created_at + date + exportedat + jobid + job { + id + ownerid + ownr_co_nm + ownr_fn + ownr_ln + owner { + id + ownr_co_nm + ownr_fn + ownr_ln + } + ro_number } + memo + payer + paymentnum + stripeid + transactionid + type + } } + } `; export const QUERY_JOB_PAYMENT_TOTALS = gql` - query QUERY_JOB_PAYMENT_TOTALS($id: uuid!) { - jobs_by_pk(id: $id) { - id - job_totals - payments { - id - amount - date - } - } + query QUERY_JOB_PAYMENT_TOTALS($id: uuid!) { + jobs_by_pk(id: $id) { + id + job_totals + payments { + id + amount + date + } } + } `; export const QUERY_PAYMENT_BY_ID = gql` - query QUERY_PAYMENT_BY_ID($paymentId: uuid!) { - payments_by_pk(id: $paymentId) { - id - amount - created_at - exportedat - date - jobid - job { - id - ownerid - ownr_co_nm - ownr_fn - ownr_ln - owner { - id - ownr_co_nm - ownr_fn - ownr_ln - } - ro_number - } - memo - payer - paymentnum - stripeid - transactionid - type + query QUERY_PAYMENT_BY_ID($paymentId: uuid!) { + payments_by_pk(id: $paymentId) { + id + amount + created_at + exportedat + date + jobid + job { + id + ownerid + ownr_co_nm + ownr_fn + ownr_ln + owner { + id + ownr_co_nm + ownr_fn + ownr_ln } + ro_number + } + memo + payer + paymentnum + stripeid + transactionid + type } + } `; diff --git a/client/src/graphql/phonebook.queries.js b/client/src/graphql/phonebook.queries.js index 8245730fb..b60228264 100644 --- a/client/src/graphql/phonebook.queries.js +++ b/client/src/graphql/phonebook.queries.js @@ -1,92 +1,82 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_PHONEBOOK_PAGINATED = gql` - query QUERY_PHONEBOOK_PAGINATED( - $search: String - $offset: Int - $limit: Int - $order: [phonebook_order_by!] - ) { - search_phonebook( - offset: $offset - limit: $limit - order_by: $order - args: { search: $search } - ) { - id - created_at - firstname - lastname - phone1 - phone2 - state - zip - fax - email - country - company - city - category - address2 - address1 - } - search_phonebook_aggregate(args: { search: $search }) { - aggregate { - count(distinct: true) - } - } + query QUERY_PHONEBOOK_PAGINATED($search: String, $offset: Int, $limit: Int, $order: [phonebook_order_by!]) { + search_phonebook(offset: $offset, limit: $limit, order_by: $order, args: { search: $search }) { + id + created_at + firstname + lastname + phone1 + phone2 + state + zip + fax + email + country + company + city + category + address2 + address1 } + search_phonebook_aggregate(args: { search: $search }) { + aggregate { + count(distinct: true) + } + } + } `; export const QUERY_PHONEBOOK_BY_ID = gql` - query QUERY_PHONEBOOK_BY_ID($id: uuid!) { - phonebook_by_pk(id: $id) { - id - created_at - firstname - lastname - phone1 - phone2 - state - zip - fax - email - country - company - city - category - address2 - address1 - } + query QUERY_PHONEBOOK_BY_ID($id: uuid!) { + phonebook_by_pk(id: $id) { + id + created_at + firstname + lastname + phone1 + phone2 + state + zip + fax + email + country + company + city + category + address2 + address1 } + } `; export const UPDATE_PHONEBOOK = gql` - mutation UPDATE_VENDOR($id: uuid!, $phonebook: phonebook_set_input!) { - update_phonebook(where: { id: { _eq: $id } }, _set: $phonebook) { - returning { - id - } - } + mutation UPDATE_VENDOR($id: uuid!, $phonebook: phonebook_set_input!) { + update_phonebook(where: { id: { _eq: $id } }, _set: $phonebook) { + returning { + id + } } + } `; export const INSERT_NEW_PHONEBOOK = gql` - mutation INSERT_NEW_PHONEBOOK($phonebook_entry: [phonebook_insert_input!]!) { - insert_phonebook(objects: $phonebook_entry) { - returning { - id - } - } + mutation INSERT_NEW_PHONEBOOK($phonebook_entry: [phonebook_insert_input!]!) { + insert_phonebook(objects: $phonebook_entry) { + returning { + id + } } + } `; export const DELETE_PHONEBOOK = gql` - mutation DELETE_PHONEBOOK($id: uuid!) { - delete_phonebook(where: { id: { _eq: $id } }) { - returning { - id - } - } + mutation DELETE_PHONEBOOK($id: uuid!) { + delete_phonebook(where: { id: { _eq: $id } }) { + returning { + id + } } + } `; diff --git a/client/src/graphql/schema.js b/client/src/graphql/schema.js index 57305a08a..02ab07838 100644 --- a/client/src/graphql/schema.js +++ b/client/src/graphql/schema.js @@ -1,4 +1,4 @@ -import {buildSchema} from "graphql"; +import { buildSchema } from "graphql"; export default buildSchema(` schema { diff --git a/client/src/graphql/scoreboard.queries.js b/client/src/graphql/scoreboard.queries.js index f63be7bb4..931f2df85 100644 --- a/client/src/graphql/scoreboard.queries.js +++ b/client/src/graphql/scoreboard.queries.js @@ -1,129 +1,113 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_SCOREBOARD = gql` - query QUERY_SCOREBOARD($start: date!, $end: date!) { - scoreboard( - where: { _and: { date: { _gte: $start, _lte: $end } } } - order_by: { date: asc } - ) { - id - painthrs - bodyhrs - date - job { - id - ro_number - ownr_fn - ownr_ln - ownr_co_nm - v_make_desc - v_model_desc - v_model_yr - job_totals - } - } + query QUERY_SCOREBOARD($start: date!, $end: date!) { + scoreboard(where: { _and: { date: { _gte: $start, _lte: $end } } }, order_by: { date: asc }) { + id + painthrs + bodyhrs + date + job { + id + ro_number + ownr_fn + ownr_ln + ownr_co_nm + v_make_desc + v_model_desc + v_model_yr + job_totals + } } + } `; export const DELETE_SCOREBOARD_ENTRY = gql` - mutation DELETE_SCOREBOARD_ENTRY($sbId: uuid!) { - delete_scoreboard_by_pk(id: $sbId) { - id - } + mutation DELETE_SCOREBOARD_ENTRY($sbId: uuid!) { + delete_scoreboard_by_pk(id: $sbId) { + id } + } `; export const INSERT_SCOREBOARD_ENTRY = gql` - mutation INSERT_SCOREBOARD_ENTRY($sbInput: [scoreboard_insert_input!]!) { - insert_scoreboard(objects: $sbInput) { - returning { - id - date - bodyhrs - painthrs - } - } + mutation INSERT_SCOREBOARD_ENTRY($sbInput: [scoreboard_insert_input!]!) { + insert_scoreboard(objects: $sbInput) { + returning { + id + date + bodyhrs + painthrs + } } + } `; export const UPDATE_SCOREBOARD_ENTRY = gql` - mutation UPDATE_SCOREBOARD_ENTRY( - $sbId: uuid! - $sbInput: scoreboard_set_input! - ) { - update_scoreboard_by_pk(_set: $sbInput, pk_columns: { id: $sbId }) { - id - date - bodyhrs - painthrs - } + mutation UPDATE_SCOREBOARD_ENTRY($sbId: uuid!, $sbInput: scoreboard_set_input!) { + update_scoreboard_by_pk(_set: $sbInput, pk_columns: { id: $sbId }) { + id + date + bodyhrs + painthrs } + } `; export const QUERY_SCOREBOARD_ENTRY = gql` - query QUERY_SCOREBOARD_ENTRY($jobid: uuid!) { - scoreboard(where: { jobid: { _eq: $jobid } }) { - bodyhrs - date - id - painthrs - } + query QUERY_SCOREBOARD_ENTRY($jobid: uuid!) { + scoreboard(where: { jobid: { _eq: $jobid } }) { + bodyhrs + date + id + painthrs } + } `; export const GET_BLOCKED_DAYS = gql` - query GET_BLOCKED_DAYS($start: timestamptz, $end: timestamptz) { - appointments( - where: { - _and: [ - { block: { _eq: true } } - { canceled: { _eq: false } } - { start: { _gte: $start } } - { end: { _lte: $end } } - ] - } - ) { - id - block - start - end - } + query GET_BLOCKED_DAYS($start: timestamptz, $end: timestamptz) { + appointments( + where: { + _and: [ + { block: { _eq: true } } + { canceled: { _eq: false } } + { start: { _gte: $start } } + { end: { _lte: $end } } + ] + } + ) { + id + block + start + end } + } `; export const QUERY_SCOREBOARD_PAGINATED = gql` - query QUERY_SCOREBOARD_PAGINATED( - $search: String - $offset: Int - $limit: Int - $order: [scoreboard_order_by!] - ) { - scoreboard( - where: { job: { ro_number: { _ilike: $search } } } - offset: $offset - limit: $limit - order_by: $order - ) { - id - jobid - job { - id - ro_number - invoice_date - v_make_desc - v_model_desc - v_model_yr - ownr_fn - ownr_ln - ownr_co_nm - } - date - bodyhrs - painthrs - } - scoreboard_aggregate(where: { job: { ro_number: { _ilike: $search } } }) { - aggregate { - count(distinct: true) - } - } + query QUERY_SCOREBOARD_PAGINATED($search: String, $offset: Int, $limit: Int, $order: [scoreboard_order_by!]) { + scoreboard(where: { job: { ro_number: { _ilike: $search } } }, offset: $offset, limit: $limit, order_by: $order) { + id + jobid + job { + id + ro_number + invoice_date + v_make_desc + v_model_desc + v_model_yr + ownr_fn + ownr_ln + ownr_co_nm + } + date + bodyhrs + painthrs } + scoreboard_aggregate(where: { job: { ro_number: { _ilike: $search } } }) { + aggregate { + count(distinct: true) + } + } + } `; diff --git a/client/src/graphql/search.queries.js b/client/src/graphql/search.queries.js index cfa6f3341..1c1943e30 100644 --- a/client/src/graphql/search.queries.js +++ b/client/src/graphql/search.queries.js @@ -1,63 +1,63 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const GLOBAL_SEARCH_QUERY = gql` - query GLOBAL_SEARCH_QUERY($search: String) { - search_jobs(args: { search: $search }, limit: 25) { - id - ro_number - status - clm_no - v_model_yr - v_model_desc - v_make_desc - v_color - ownr_fn - ownr_ln - ownr_co_nm - } - search_owners(args: { search: $search }, limit: 25) { - id - ownr_fn - ownr_ln - ownr_co_nm - ownr_ph1 - ownr_ph2 - } - search_vehicles(args: { search: $search }, limit: 25) { - id - v_model_yr - v_model_desc - v_make_desc - v_color - v_vin - plate_no - } - search_payments(args: { search: $search }, limit: 25) { - id - amount - paymentnum - job { - ro_number - id - } - memo - transactionid - } - search_bills(args: { search: $search }, limit: 25) { - id - date - invoice_number - vendor { - id - name - } - } - search_phonebook(args: { search: $search }, limit: 25) { - id - firstname - lastname - company - phone1 - } + query GLOBAL_SEARCH_QUERY($search: String) { + search_jobs(args: { search: $search }, limit: 25) { + id + ro_number + status + clm_no + v_model_yr + v_model_desc + v_make_desc + v_color + ownr_fn + ownr_ln + ownr_co_nm } + search_owners(args: { search: $search }, limit: 25) { + id + ownr_fn + ownr_ln + ownr_co_nm + ownr_ph1 + ownr_ph2 + } + search_vehicles(args: { search: $search }, limit: 25) { + id + v_model_yr + v_model_desc + v_make_desc + v_color + v_vin + plate_no + } + search_payments(args: { search: $search }, limit: 25) { + id + amount + paymentnum + job { + ro_number + id + } + memo + transactionid + } + search_bills(args: { search: $search }, limit: 25) { + id + date + invoice_number + vendor { + id + name + } + } + search_phonebook(args: { search: $search }, limit: 25) { + id + firstname + lastname + company + phone1 + } + } `; diff --git a/client/src/graphql/templates.queries.js b/client/src/graphql/templates.queries.js index a8c63858e..fc3a41c05 100644 --- a/client/src/graphql/templates.queries.js +++ b/client/src/graphql/templates.queries.js @@ -1,76 +1,73 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_TEMPLATES_BY_NAME = gql` - query QUERY_TEMPLATES_BY_NAME($name: String!) { - templates(where: { name: { _eq: $name } }) { - id - name - query - html - bodyshopid - } + query QUERY_TEMPLATES_BY_NAME($name: String!) { + templates(where: { name: { _eq: $name } }) { + id + name + query + html + bodyshopid } + } `; export const QUERY_TEMPLATES_BY_NAME_FOR_DUPE = gql` - query QUERY_TEMPLATES_BY_NAME_FOR_DUPE($name: String!) { - templates(where: { name: { _eq: $name } }) { - id - name - query - html - bodyshopid - jsontemplate - } + query QUERY_TEMPLATES_BY_NAME_FOR_DUPE($name: String!) { + templates(where: { name: { _eq: $name } }) { + id + name + query + html + bodyshopid + jsontemplate } + } `; export const QUERY_CUSTOM_TEMPLATES = gql` - query QUERY_CUSTOM_TEMPLATES { - templates(where: { bodyshopid: { _is_null: false } }) { - id - name - } + query QUERY_CUSTOM_TEMPLATES { + templates(where: { bodyshopid: { _is_null: false } }) { + id + name } + } `; export const QUERY_TEMPLATE_BY_PK = gql` - query QUERY_TEMPLATE_BY_PK($templateId: uuid!) { - templates_by_pk(id: $templateId) { - id - name - query - html - jsontemplate - } + query QUERY_TEMPLATE_BY_PK($templateId: uuid!) { + templates_by_pk(id: $templateId) { + id + name + query + html + jsontemplate } + } `; export const UPDATE_TEMPLATE = gql` - mutation UPDATE_TEMPLATE( - $templateId: uuid! - $template: templates_set_input! - ) { - update_templates(where: { id: { _eq: $templateId } }, _set: $template) { - returning { - id - } - } + mutation UPDATE_TEMPLATE($templateId: uuid!, $template: templates_set_input!) { + update_templates(where: { id: { _eq: $templateId } }, _set: $template) { + returning { + id + } } + } `; export const INSERT_TEMPLATE = gql` - mutation INSERT_TEMPLATE($template: templates_insert_input!) { - insert_templates(objects: [$template]) { - returning { - id - } - } + mutation INSERT_TEMPLATE($template: templates_insert_input!) { + insert_templates(objects: [$template]) { + returning { + id + } } + } `; export const DELETE_TEMPLATE = gql` - mutation DELETE_TEMPLATE($templateId: uuid!) { - delete_templates(where: { id: { _eq: $templateId } }) { - affected_rows - } + mutation DELETE_TEMPLATE($templateId: uuid!) { + delete_templates(where: { id: { _eq: $templateId } }) { + affected_rows } + } `; diff --git a/client/src/graphql/timetickets.queries.js b/client/src/graphql/timetickets.queries.js index a6dff0c52..f56976e2b 100644 --- a/client/src/graphql/timetickets.queries.js +++ b/client/src/graphql/timetickets.queries.js @@ -1,442 +1,406 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_TICKETS_BY_JOBID = gql` - query QUERY_TICKETS_BY_JOBID($jobid: uuid!) { - timetickets( - where: { jobid: { _eq: $jobid } } - order_by: { date: desc_nulls_first } - ) { - actualhrs - cost_center - ciecacode - rate - productivehrs - id - memo - jobid - flat_rate - commited_by - committed_at - employee { - employee_number - first_name - last_name - id - } - } + query QUERY_TICKETS_BY_JOBID($jobid: uuid!) { + timetickets(where: { jobid: { _eq: $jobid } }, order_by: { date: desc_nulls_first }) { + actualhrs + cost_center + ciecacode + rate + productivehrs + id + memo + jobid + flat_rate + commited_by + committed_at + employee { + employee_number + first_name + last_name + id + } } + } `; export const QUERY_TIME_TICKETS_IN_RANGE = gql` - query QUERY_TIME_TICKETS_IN_RANGE($start: date!, $end: date!) { - timetickets( - where: { date: { _gte: $start, _lte: $end } } - order_by: { date: desc_nulls_first } - ) { - actualhrs - ciecacode - clockoff - clockon - cost_center - created_at - created_by - date - id - rate - productivehrs - memo - jobid - flat_rate - commited_by - committed_at - task_name - job { - id - ro_number - } - employeeid - employee { - id - employee_number - first_name - last_name - } - } + query QUERY_TIME_TICKETS_IN_RANGE($start: date!, $end: date!) { + timetickets(where: { date: { _gte: $start, _lte: $end } }, order_by: { date: desc_nulls_first }) { + actualhrs + ciecacode + clockoff + clockon + cost_center + created_at + created_by + date + id + rate + productivehrs + memo + jobid + flat_rate + commited_by + committed_at + task_name + job { + id + ro_number + } + employeeid + employee { + id + employee_number + first_name + last_name + } } + } `; export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql` - query QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE( - $employeeid: uuid! - $start: date! - $end: date! - $fixedStart: date! - $fixedEnd: date! + query QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE( + $employeeid: uuid! + $start: date! + $end: date! + $fixedStart: date! + $fixedEnd: date! + ) { + timetickets( + where: { date: { _gte: $start, _lte: $end }, employeeid: { _eq: $employeeid } } + order_by: { date: desc_nulls_first } ) { - timetickets( - where: { - date: { _gte: $start, _lte: $end } - employeeid: { _eq: $employeeid } - } - order_by: { date: desc_nulls_first } - ) { - actualhrs - ciecacode - clockoff - clockon - cost_center - created_at - created_by - date - id - rate - productivehrs - memo - jobid - commited_by - committed_at - flat_rate - job { - id - ro_number - } - employeeid - employee { - id - employee_number - first_name - last_name - } - } - fixedperiod: timetickets( - where: { - date: { _gte: $fixedStart, _lte: $fixedEnd } - employeeid: { _eq: $employeeid } - } - order_by: { date: desc_nulls_first } - ) { - actualhrs - ciecacode - clockoff - clockon - cost_center - created_at - created_by - date - id - rate - productivehrs - memo - jobid - flat_rate - commited_by - committed_at - job { - id - ro_number - } - employeeid - employee { - id - employee_number - first_name - last_name - } - } + actualhrs + ciecacode + clockoff + clockon + cost_center + created_at + created_by + date + id + rate + productivehrs + memo + jobid + commited_by + committed_at + flat_rate + job { + id + ro_number + } + employeeid + employee { + id + employee_number + first_name + last_name + } } + fixedperiod: timetickets( + where: { date: { _gte: $fixedStart, _lte: $fixedEnd }, employeeid: { _eq: $employeeid } } + order_by: { date: desc_nulls_first } + ) { + actualhrs + ciecacode + clockoff + clockon + cost_center + created_at + created_by + date + id + rate + productivehrs + memo + jobid + flat_rate + commited_by + committed_at + job { + id + ro_number + } + employeeid + employee { + id + employee_number + first_name + last_name + } + } + } `; export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql` - query QUERY_TIME_TICKETS_IN_RANGE_SB( - $start: date! - $end: date! - $fixedStart: date! - $fixedEnd: date! - $jobStart: timestamptz! - $jobEnd: timestamptz! + query QUERY_TIME_TICKETS_IN_RANGE_SB( + $start: date! + $end: date! + $fixedStart: date! + $fixedEnd: date! + $jobStart: timestamptz! + $jobEnd: timestamptz! + ) { + timetickets( + where: { date: { _gte: $start, _lte: $end }, cost_center: { _neq: "timetickets.labels.shift" } } + order_by: { date: desc_nulls_first } ) { - timetickets( - where: { - date: { _gte: $start, _lte: $end } - cost_center: { _neq: "timetickets.labels.shift" } - } - order_by: { date: desc_nulls_first } - ) { - actualhrs - ciecacode - clockoff - clockon - cost_center - created_at - created_by - date - id - rate - actualhrs - productivehrs - memo - jobid - committed_at - commited_by - flat_rate - commited_by - committed_at - job { - id - ro_number - } - employeeid - employee { - id - employee_number - first_name - last_name - } - } - fixedperiod: timetickets( - where: { - date: { _gte: $fixedStart, _lte: $fixedEnd } - cost_center: { _neq: "timetickets.labels.shift" } - } - order_by: { date: desc_nulls_first } - ) { - actualhrs - ciecacode - clockoff - clockon - cost_center - created_at - created_by - date - id - rate - productivehrs - memo - jobid - flat_rate - job { - id - ro_number - } - employeeid - employee { - id - employee_number - first_name - last_name - } - } - jobs( - where: { - date_invoiced: { _is_null: true } - ro_number: { _is_null: false } - voided: { _eq: false } - _or: [ - { actual_completion: { _gte: $jobStart, _lte: $jobEnd } } - { actual_delivery: { _gte: $jobStart, _lte: $jobEnd } } - ] - } - ) { - id - joblines(order_by: { line_no: asc }, where: { removed: { _eq: false } }) { - convertedtolbr - convertedtolbr_data - mod_lb_hrs - mod_lbr_ty - } - } + actualhrs + ciecacode + clockoff + clockon + cost_center + created_at + created_by + date + id + rate + actualhrs + productivehrs + memo + jobid + committed_at + commited_by + flat_rate + commited_by + committed_at + job { + id + ro_number + } + employeeid + employee { + id + employee_number + first_name + last_name + } } + fixedperiod: timetickets( + where: { date: { _gte: $fixedStart, _lte: $fixedEnd }, cost_center: { _neq: "timetickets.labels.shift" } } + order_by: { date: desc_nulls_first } + ) { + actualhrs + ciecacode + clockoff + clockon + cost_center + created_at + created_by + date + id + rate + productivehrs + memo + jobid + flat_rate + job { + id + ro_number + } + employeeid + employee { + id + employee_number + first_name + last_name + } + } + jobs( + where: { + date_invoiced: { _is_null: true } + ro_number: { _is_null: false } + voided: { _eq: false } + _or: [ + { actual_completion: { _gte: $jobStart, _lte: $jobEnd } } + { actual_delivery: { _gte: $jobStart, _lte: $jobEnd } } + ] + } + ) { + id + joblines(order_by: { line_no: asc }, where: { removed: { _eq: false } }) { + convertedtolbr + convertedtolbr_data + mod_lb_hrs + mod_lbr_ty + } + } + } `; export const INSERT_NEW_TIME_TICKET = gql` - mutation INSERT_NEW_TIME_TICKET( - $timeTicketInput: [timetickets_insert_input!]! - ) { - insert_timetickets(objects: $timeTicketInput) { - returning { - id - created_by - clockon - clockoff - employeeid - productivehrs - actualhrs - ciecacode - date - memo - flat_rate - commited_by - committed_at - } - } + mutation INSERT_NEW_TIME_TICKET($timeTicketInput: [timetickets_insert_input!]!) { + insert_timetickets(objects: $timeTicketInput) { + returning { + id + created_by + clockon + clockoff + employeeid + productivehrs + actualhrs + ciecacode + date + memo + flat_rate + commited_by + committed_at + } } + } `; export const INSERT_TIME_TICKET_AND_APPROVE = gql` - mutation INSERT_TIME_TICKET_AND_APPROVE( - $timeTicketInput: [timetickets_insert_input!]! - $approvalIds: [uuid!]! - $approvalUpdate: tt_approval_queue_set_input - ) { - insert_timetickets(objects: $timeTicketInput) { - returning { - id - clockon - clockoff - employeeid - productivehrs - actualhrs - ciecacode - date - memo - flat_rate - commited_by - committed_at - } - } - update_tt_approval_queue( - where: { id: { _in: $approvalIds } } - _set: $approvalUpdate - ) { - returning { - id - approved_at - approved_at - - } - } + mutation INSERT_TIME_TICKET_AND_APPROVE( + $timeTicketInput: [timetickets_insert_input!]! + $approvalIds: [uuid!]! + $approvalUpdate: tt_approval_queue_set_input + ) { + insert_timetickets(objects: $timeTicketInput) { + returning { + id + clockon + clockoff + employeeid + productivehrs + actualhrs + ciecacode + date + memo + flat_rate + commited_by + committed_at + } } + update_tt_approval_queue(where: { id: { _in: $approvalIds } }, _set: $approvalUpdate) { + returning { + id + approved_at + approved_at + } + } + } `; export const UPDATE_TIME_TICKET = gql` - mutation UPDATE_TIME_TICKET( - $timeticketId: uuid! - $timeticket: timetickets_set_input! - ) { - update_timetickets( - where: { id: { _eq: $timeticketId } } - _set: $timeticket - ) { - returning { - id - clockon - clockoff - employeeid - productivehrs - actualhrs - ciecacode - created_at - updated_at - jobid - date - flat_rate - memo - committed_at - commited_by - } - } + mutation UPDATE_TIME_TICKET($timeticketId: uuid!, $timeticket: timetickets_set_input!) { + update_timetickets(where: { id: { _eq: $timeticketId } }, _set: $timeticket) { + returning { + id + clockon + clockoff + employeeid + productivehrs + actualhrs + ciecacode + created_at + updated_at + jobid + date + flat_rate + memo + committed_at + commited_by + } } + } `; export const UPDATE_TIME_TICKETS = gql` - mutation UPDATE_TIME_TICKETS( - $timeticketIds: [uuid!]! - $timeticket: timetickets_set_input! - ) { - update_timetickets( - where: { id: { _in: $timeticketIds } } - _set: $timeticket - ) { - returning { - id - clockon - clockoff - employeeid - productivehrs - actualhrs - ciecacode - created_at - updated_at - jobid - date - flat_rate - memo - committed_at - commited_by - } - } + mutation UPDATE_TIME_TICKETS($timeticketIds: [uuid!]!, $timeticket: timetickets_set_input!) { + update_timetickets(where: { id: { _in: $timeticketIds } }, _set: $timeticket) { + returning { + id + clockon + clockoff + employeeid + productivehrs + actualhrs + ciecacode + created_at + updated_at + jobid + date + flat_rate + memo + committed_at + commited_by + } } + } `; export const QUERY_ACTIVE_TIME_TICKETS = gql` - query QUERY_ACTIVE_TIME_TICKETS($employeeId: uuid) { - timetickets( - order_by: { date: desc_nulls_first } - where: { - _and: { - clockoff: { _is_null: true } - employeeid: { _eq: $employeeId } - clockon: { _is_null: false } - jobid: { _is_null: false } - } - } - ) { - id - clockon - memo - cost_center - flat_rate - jobid - commited_by - committed_at - job { - id - ownr_fn - ownr_ln - ownr_co_nm - v_model_desc - v_make_desc - v_model_yr - ro_number - } + query QUERY_ACTIVE_TIME_TICKETS($employeeId: uuid) { + timetickets( + order_by: { date: desc_nulls_first } + where: { + _and: { + clockoff: { _is_null: true } + employeeid: { _eq: $employeeId } + clockon: { _is_null: false } + jobid: { _is_null: false } } + } + ) { + id + clockon + memo + cost_center + flat_rate + jobid + commited_by + committed_at + job { + id + ownr_fn + ownr_ln + ownr_co_nm + v_model_desc + v_make_desc + v_model_yr + ro_number + } } + } `; export const QUERY_ACTIVE_SHIFT_TIME_TICKETS = gql` - query QUERY_ACTIVE_SHIFT_TIME_TICKETS($employeeId: uuid) { - timetickets( - where: { - _and: { - clockoff: { _is_null: true } - employeeid: { _eq: $employeeId } - clockon: { _is_null: false } - jobid: { _is_null: true } - } - } - ) { - id - clockon - memo - jobid - job { - id - ownr_fn - ownr_ln - ownr_co_nm - v_model_desc - v_make_desc - v_model_yr - ro_number - } + query QUERY_ACTIVE_SHIFT_TIME_TICKETS($employeeId: uuid) { + timetickets( + where: { + _and: { + clockoff: { _is_null: true } + employeeid: { _eq: $employeeId } + clockon: { _is_null: false } + jobid: { _is_null: true } } + } + ) { + id + clockon + memo + jobid + job { + id + ownr_fn + ownr_ln + ownr_co_nm + v_model_desc + v_make_desc + v_model_yr + ro_number + } } + } `; export const DELETE_TIME_TICKET = gql` - mutation DELETE_TIME_TICKET($id: uuid!) { - delete_timetickets_by_pk(id: $id) { - id - } + mutation DELETE_TIME_TICKET($id: uuid!) { + delete_timetickets_by_pk(id: $id) { + id } + } `; diff --git a/client/src/graphql/tt-approvals.queries.js b/client/src/graphql/tt-approvals.queries.js index 79e0f0e64..4301debbb 100644 --- a/client/src/graphql/tt-approvals.queries.js +++ b/client/src/graphql/tt-approvals.queries.js @@ -1,98 +1,85 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_ALL_TT_APPROVALS_PAGINATED = gql` - query QUERY_ALL_TT_APPROVALS_PAGINATED( - $offset: Int - $limit: Int - $order: [tt_approval_queue_order_by!]! - ) { - tt_approval_queue( - offset: $offset - limit: $limit - order_by: $order - where: { approved_at: { _is_null: true } } - ) { - id - jobid - bodyshopid - employeeid - employee { - first_name - last_name - id - } - job { - ro_number - status - id - } - employeeid - actualhrs - productivehrs - ciecacode - cost_center - date - rate - } - tt_approval_queue_aggregate { - aggregate { - count(distinct: true) - } - } + query QUERY_ALL_TT_APPROVALS_PAGINATED($offset: Int, $limit: Int, $order: [tt_approval_queue_order_by!]!) { + tt_approval_queue(offset: $offset, limit: $limit, order_by: $order, where: { approved_at: { _is_null: true } }) { + id + jobid + bodyshopid + employeeid + employee { + first_name + last_name + id + } + job { + ro_number + status + id + } + employeeid + actualhrs + productivehrs + ciecacode + cost_center + date + rate } + tt_approval_queue_aggregate { + aggregate { + count(distinct: true) + } + } + } `; export const INSERT_NEW_TT_APPROVALS = gql` - mutation INSERT_NEW_TT_APPROVALS( - $timeTicketInput: [tt_approval_queue_insert_input!]! - ) { - insert_tt_approval_queue(objects: $timeTicketInput) { - returning { - id - employeeid - productivehrs - actualhrs - ciecacode - date - memo - flat_rate - } - } + mutation INSERT_NEW_TT_APPROVALS($timeTicketInput: [tt_approval_queue_insert_input!]!) { + insert_tt_approval_queue(objects: $timeTicketInput) { + returning { + id + employeeid + productivehrs + actualhrs + ciecacode + date + memo + flat_rate + } } + } `; export const QUERY_TT_APPROVALS_BY_IDS = gql` - query QUERY_TT_APPROVALS_BY_IDS($ids: [uuid!]!) { - tt_approval_queue(where: { id: { _in: $ids } }) { - id - productivehrs - actualhrs - rate - memo - jobid - flat_rate - employeeid - date - ciecacode - bodyshopid - cost_center - } + query QUERY_TT_APPROVALS_BY_IDS($ids: [uuid!]!) { + tt_approval_queue(where: { id: { _in: $ids } }) { + id + productivehrs + actualhrs + rate + memo + jobid + flat_rate + employeeid + date + ciecacode + bodyshopid + cost_center } + } `; export const UPDATE_TT_BY_APPROVAL = gql` - mutation UPDATE_TT_BY_APPROVAL( - $ttApprovalUpdates: [tt_approval_queue_updates!]! - ) { - update_tt_approval_queue_many(updates: $ttApprovalUpdates) { - returning { - id - approved_at - approved_by - timeticket { - id - } - } + mutation UPDATE_TT_BY_APPROVAL($ttApprovalUpdates: [tt_approval_queue_updates!]!) { + update_tt_approval_queue_many(updates: $ttApprovalUpdates) { + returning { + id + approved_at + approved_by + timeticket { + id } + } } + } `; diff --git a/client/src/graphql/user.queries.js b/client/src/graphql/user.queries.js index 273603e79..bd9b9b1cb 100644 --- a/client/src/graphql/user.queries.js +++ b/client/src/graphql/user.queries.js @@ -1,101 +1,87 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_SHOP_ASSOCIATIONS = gql` - query QUERY_SHOP_ASSOCIATIONS($shopid: uuid!) { - associations(where: { shopid: { _eq: $shopid } }) { - id - authlevel - shopid - user { - email - } - } + query QUERY_SHOP_ASSOCIATIONS($shopid: uuid!) { + associations(where: { shopid: { _eq: $shopid } }) { + id + authlevel + shopid + user { + email + } } + } `; export const UPDATE_ASSOCIATION = gql` - mutation UPDATE_ASSOCIATION( - $assocId: uuid! - $assoc: associations_set_input! - ) { - update_associations(where: { id: { _eq: $assocId } }, _set: $assoc) { - returning { - id - authlevel - shopid - user { - email - } - } + mutation UPDATE_ASSOCIATION($assocId: uuid!, $assoc: associations_set_input!) { + update_associations(where: { id: { _eq: $assocId } }, _set: $assoc) { + returning { + id + authlevel + shopid + user { + email } + } } + } `; export const INSERT_EULA_ACCEPTANCE = gql` - mutation INSERT_EULA_ACCEPTANCE($eulaAcceptance:eula_acceptances_insert_input!) { - insert_eula_acceptances_one(object: $eulaAcceptance){ - id - } + mutation INSERT_EULA_ACCEPTANCE($eulaAcceptance: eula_acceptances_insert_input!) { + insert_eula_acceptances_one(object: $eulaAcceptance) { + id } + } `; export const UPSERT_USER = gql` - mutation UPSERT_USER($authEmail: String!, $authToken: String!) { - insert_users( - objects: [{ email: $authEmail, authid: $authToken }] - on_conflict: { constraint: users_pkey, update_columns: [authid] } - ) { - returning { - authid - } - } + mutation UPSERT_USER($authEmail: String!, $authToken: String!) { + insert_users( + objects: [{ email: $authEmail, authid: $authToken }] + on_conflict: { constraint: users_pkey, update_columns: [authid] } + ) { + returning { + authid + } } + } `; export const UPDATE_DASHBOARD_LAYOUT = gql` - mutation UPDATE_DASHBOARD_LAYOUT($email: String!, $layout: jsonb!) { - update_users_by_pk( - pk_columns: { email: $email } - _set: { dashboardlayout: $layout } - ) { - email - dashboardlayout - } + mutation UPDATE_DASHBOARD_LAYOUT($email: String!, $layout: jsonb!) { + update_users_by_pk(pk_columns: { email: $email }, _set: { dashboardlayout: $layout }) { + email + dashboardlayout } + } `; export const UPDATE_FCM_TOKEN = gql` - mutation UPDATE_FCM_TOKEN($authEmail: String!, $token: jsonb!) { - update_users( - where: { email: { _eq: $authEmail } } - _append: { fcmtokens: $token } - ) { - affected_rows - returning { - fcmtokens - } - } + mutation UPDATE_FCM_TOKEN($authEmail: String!, $token: jsonb!) { + update_users(where: { email: { _eq: $authEmail } }, _append: { fcmtokens: $token }) { + affected_rows + returning { + fcmtokens + } } + } `; export const QUERY_KANBAN_SETTINGS = gql` - query QUERY_KANBAN_SETTINGS($email: String!) { - associations( - where: { _and: { useremail: { _eq: $email }, active: { _eq: true } } } - ) { - id - kanban_settings - } + query QUERY_KANBAN_SETTINGS($email: String!) { + associations(where: { _and: { useremail: { _eq: $email }, active: { _eq: true } } }) { + id + kanban_settings } + } `; export const UPDATE_KANBAN_SETTINGS = gql` - mutation UPDATE_KANBAN_SETTINGS($id: uuid!, $ks: jsonb) { - update_associations_by_pk( - pk_columns: { id: $id } - _set: { kanban_settings: $ks } - ) { - id - kanban_settings - } + mutation UPDATE_KANBAN_SETTINGS($id: uuid!, $ks: jsonb) { + update_associations_by_pk(pk_columns: { id: $id }, _set: { kanban_settings: $ks }) { + id + kanban_settings } + } `; diff --git a/client/src/graphql/vehicles.queries.js b/client/src/graphql/vehicles.queries.js index 32d970655..3fa81ca54 100644 --- a/client/src/graphql/vehicles.queries.js +++ b/client/src/graphql/vehicles.queries.js @@ -1,4 +1,4 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_VEHICLE_BY_ID = gql` query QUERY_VEHICLE_BY_ID($id: uuid!) { @@ -46,151 +46,141 @@ export const QUERY_VEHICLE_BY_ID = gql` `; export const UPDATE_VEHICLE = gql` - mutation UPDATE_VEHICLE($vehId: uuid!, $vehicle: vehicles_set_input!) { - update_vehicles(where: { id: { _eq: $vehId } }, _set: $vehicle) { - returning { - id - } - } + mutation UPDATE_VEHICLE($vehId: uuid!, $vehicle: vehicles_set_input!) { + update_vehicles(where: { id: { _eq: $vehId } }, _set: $vehicle) { + returning { + id + } } + } `; export const DELETE_VEHICLE = gql` - mutation DELETE_VEHICLE($id: uuid!) { - delete_vehicles_by_pk(id: $id) { - id - } + mutation DELETE_VEHICLE($id: uuid!) { + delete_vehicles_by_pk(id: $id) { + id } + } `; export const QUERY_ALL_VEHICLES = gql` - query QUERY_ALL_VEHICLES { - vehicles { - id - plate_no - plate_st - v_vin - v_model_yr - v_model_desc - v_make_desc - v_color - v_bstyle - updated_at - } + query QUERY_ALL_VEHICLES { + vehicles { + id + plate_no + plate_st + v_vin + v_model_yr + v_model_desc + v_make_desc + v_color + v_bstyle + updated_at } + } `; export const QUERY_ALL_VEHICLES_PAGINATED = gql` - query QUERY_ALL_VEHICLES( - $search: String - $offset: Int - $limit: Int - $order: [vehicles_order_by!]! - ) { - search_vehicles( - args: { search: $search } - offset: $offset - limit: $limit - order_by: $order - ) { - id - plate_no - plate_st - v_vin - v_model_yr - v_model_desc - v_make_desc - v_color - v_bstyle - updated_at - } - search_vehicles_aggregate(args: { search: $search }) { - aggregate { - count(distinct: true) - } - } + query QUERY_ALL_VEHICLES($search: String, $offset: Int, $limit: Int, $order: [vehicles_order_by!]!) { + search_vehicles(args: { search: $search }, offset: $offset, limit: $limit, order_by: $order) { + id + plate_no + plate_st + v_vin + v_model_yr + v_model_desc + v_make_desc + v_color + v_bstyle + updated_at } + search_vehicles_aggregate(args: { search: $search }) { + aggregate { + count(distinct: true) + } + } + } `; export const SEARCH_VEHICLE_BY_VIN = gql` - query SEARCH_VEHICLE_BY_VIN($vin: String!) { - vehicles(where: { v_vin: { _ilike: $vin } }) { - id - plate_no - plate_st - v_vin - v_model_yr - v_model_desc - v_make_desc - v_color - v_bstyle - updated_at - v_type - v_trimcode - v_tone - v_stage - v_prod_dt - v_paint_codes - v_options - v_mldgcode - v_makecode - v_engine - v_cond - trim_color - db_v_code - } + query SEARCH_VEHICLE_BY_VIN($vin: String!) { + vehicles(where: { v_vin: { _ilike: $vin } }) { + id + plate_no + plate_st + v_vin + v_model_yr + v_model_desc + v_make_desc + v_color + v_bstyle + updated_at + v_type + v_trimcode + v_tone + v_stage + v_prod_dt + v_paint_codes + v_options + v_mldgcode + v_makecode + v_engine + v_cond + trim_color + db_v_code } + } `; export const SEARCH_VEHICLES = gql` - query SEARCH_VEHICLES($search: String!) { - search_vehicles(args: { search: $search }) { - id - plate_no - plate_st - v_vin - v_model_yr - v_model_desc - v_make_desc - v_color - v_bstyle - updated_at - v_type - v_trimcode - v_tone - v_stage - v_prod_dt - v_paint_codes - v_options - v_mldgcode - v_makecode - v_engine - v_cond - trim_color - db_v_code - } + query SEARCH_VEHICLES($search: String!) { + search_vehicles(args: { search: $search }) { + id + plate_no + plate_st + v_vin + v_model_yr + v_model_desc + v_make_desc + v_color + v_bstyle + updated_at + v_type + v_trimcode + v_tone + v_stage + v_prod_dt + v_paint_codes + v_options + v_mldgcode + v_makecode + v_engine + v_cond + trim_color + db_v_code } + } `; export const SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE = gql` - query SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE($id: uuid!) { - vehicles_by_pk(id: $id) { - id - v_vin - v_model_yr - v_make_desc - v_model_desc - } + query SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE($id: uuid!) { + vehicles_by_pk(id: $id) { + id + v_vin + v_model_yr + v_make_desc + v_model_desc } + } `; export const SEARCH_VEHICLES_FOR_AUTOCOMPLETE = gql` - query SEARCH_VEHICLES_FOR_AUTOCOMPLETE($search: String) { - search_vehicles(args: { search: $search }, limit: 25) { - id - v_vin - v_model_yr - v_make_desc - v_model_desc - } + query SEARCH_VEHICLES_FOR_AUTOCOMPLETE($search: String) { + search_vehicles(args: { search: $search }, limit: 25) { + id + v_vin + v_model_yr + v_make_desc + v_model_desc } + } `; diff --git a/client/src/graphql/vendors.queries.js b/client/src/graphql/vendors.queries.js index 752f1dc84..ce7c08507 100644 --- a/client/src/graphql/vendors.queries.js +++ b/client/src/graphql/vendors.queries.js @@ -1,129 +1,129 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; export const QUERY_VENDOR_BY_ID = gql` - query QUERY_VENDOR_BY_ID($id: uuid!) { - vendors_by_pk(id: $id) { - zip - street2 - state - name - id - favorite - email - due_date - discount - country - cost_center - city - street1 - active - phone - dmsid - } + query QUERY_VENDOR_BY_ID($id: uuid!) { + vendors_by_pk(id: $id) { + zip + street2 + state + name + id + favorite + email + due_date + discount + country + cost_center + city + street1 + active + phone + dmsid } + } `; export const CHECK_VENDOR_NAME = gql` - query CHECK_VENDOR_NAME($name: String!) { - vendors_aggregate(where: { name: { _ilike: $name } }) { - aggregate { - count - } - nodes { - id - } - } + query CHECK_VENDOR_NAME($name: String!) { + vendors_aggregate(where: { name: { _ilike: $name } }) { + aggregate { + count + } + nodes { + id + } } + } `; export const UPDATE_VENDOR = gql` - mutation UPDATE_VENDOR($id: uuid!, $vendor: vendors_set_input!) { - update_vendors(where: { id: { _eq: $id } }, _set: $vendor) { - returning { - id - } - } + mutation UPDATE_VENDOR($id: uuid!, $vendor: vendors_set_input!) { + update_vendors(where: { id: { _eq: $id } }, _set: $vendor) { + returning { + id + } } + } `; export const QUERY_ALL_VENDORS = gql` - query QUERY_ALL_VENDORS { - vendors(order_by: { name: asc }) { - name - id - cost_center - city - phone - active - } + query QUERY_ALL_VENDORS { + vendors(order_by: { name: asc }) { + name + id + cost_center + city + phone + active } + } `; export const INSERT_NEW_VENDOR = gql` - mutation INSERT_NEW_VENDOR($vendorInput: [vendors_insert_input!]!) { - insert_vendors(objects: $vendorInput) { - returning { - id - } - } + mutation INSERT_NEW_VENDOR($vendorInput: [vendors_insert_input!]!) { + insert_vendors(objects: $vendorInput) { + returning { + id + } } + } `; export const DELETE_VENDOR = gql` - mutation DELETE_VENDOR($id: uuid!) { - delete_vendors(where: { id: { _eq: $id } }) { - returning { - id - } - } + mutation DELETE_VENDOR($id: uuid!) { + delete_vendors(where: { id: { _eq: $id } }) { + returning { + id + } } + } `; export const QUERY_ALL_VENDORS_FOR_ORDER = gql` - query QUERY_ALL_VENDORS_FOR_ORDER($jobId: uuid) { - vendors(order_by: { name: asc }, where: { active: { _eq: true } }) { - name - cost_center - id - favorite - discount - email - active - phone - } - jobs(where: { id: { _eq: $jobId } }) { - v_make_desc - } + query QUERY_ALL_VENDORS_FOR_ORDER($jobId: uuid) { + vendors(order_by: { name: asc }, where: { active: { _eq: true } }) { + name + cost_center + id + favorite + discount + email + active + phone } + jobs(where: { id: { _eq: $jobId } }) { + v_make_desc + } + } `; export const SEARCH_VENDOR_AUTOCOMPLETE = gql` - query SEARCH_VENDOR_AUTOCOMPLETE { - vendors(order_by: { name: asc }, where: { active: { _eq: true } }) { - name - discount - id - cost_center - active - favorite - } + query SEARCH_VENDOR_AUTOCOMPLETE { + vendors(order_by: { name: asc }, where: { active: { _eq: true } }) { + name + discount + id + cost_center + active + favorite } + } `; export const SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR = gql` - query SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR { - vendors(order_by: { name: asc }, where: { active: { _eq: true } }) { - name - discount - id - cost_center - street1 - street2 - zip - country - city - email - state - active - } + query SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR { + vendors(order_by: { name: asc }, where: { active: { _eq: true } }) { + name + discount + id + cost_center + street1 + street2 + zip + country + city + email + state + active } + } `; diff --git a/client/src/index.css b/client/src/index.css index 985a39026..6a66989ff 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -1,14 +1,12 @@ body { - margin: 0; - height: 100%; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", - "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + margin: 0; + height: 100%; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", + "Droid Sans", "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", - monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } diff --git a/client/src/index.jsx b/client/src/index.jsx index b63cc5434..9678b6b69 100644 --- a/client/src/index.jsx +++ b/client/src/index.jsx @@ -3,12 +3,7 @@ import Dinero from "dinero.js"; import React from "react"; import ReactDOM from "react-dom/client"; import { Provider } from "react-redux"; -import { - createBrowserRouter, - createRoutesFromElements, - Route, - RouterProvider, -} from "react-router-dom"; +import { createBrowserRouter, createRoutesFromElements, Route, RouterProvider } from "react-router-dom"; import { PersistGate } from "redux-persist/integration/react"; import AppContainer from "./App/App.container"; import LoadingSpinner from "./components/loading-spinner/loading-spinner.component"; @@ -19,9 +14,9 @@ import "./translations/i18n"; import "./utils/CleanAxios"; import { ConfigProvider } from "antd"; import InstanceRenderManager from "./utils/instanceRenderMgr"; -import { registerSW } from 'virtual:pwa-register' +import { registerSW } from "virtual:pwa-register"; -registerSW({ immediate: true }) +registerSW({ immediate: true }); //import { BrowserTracing } from "@sentry/tracing"; //import "antd/dist/antd.css"; // import "antd/dist/antd.less"; @@ -31,68 +26,63 @@ registerSW({ immediate: true }) Dinero.globalRoundingMode = "HALF_EVEN"; if (import.meta.env.PROD) { - Sentry.init({ - dsn: InstanceRenderManager({ - imex: "https://fd7e89369b6b4bdc9c6c4c9f22fa4ee4@o492140.ingest.sentry.io/5651027", - rome: "https://a6acc91c073e414196014b8484627a61@o492140.ingest.sentry.io/4504561071161344", - promanager: "", //TODO:AIO Add in the sentry tracker for proman. - }), + Sentry.init({ + dsn: InstanceRenderManager({ + imex: "https://fd7e89369b6b4bdc9c6c4c9f22fa4ee4@o492140.ingest.sentry.io/5651027", + rome: "https://a6acc91c073e414196014b8484627a61@o492140.ingest.sentry.io/4504561071161344", + promanager: "" //TODO:AIO Add in the sentry tracker for proman. + }), - ignoreErrors: [ - "ResizeObserver loop", - "Module specifier, 'fs' does not start", - "Module specifier, 'zlib' does not start with", - ], - integrations: [ - Sentry.replayIntegration({ - maskAllText: false, - blockAllMedia: true, - }), - new Sentry.BrowserTracing({}), - ], - tracePropagationTargets: [ - "api.imex.online", - "api.test.imex.online", - "db.imex.online", - "api.romeonline.io", - "api.test.romeonline.io", - "db.romeonline.io", - ], - tracesSampleRate: 1.0, - replaysOnErrorSampleRate: 1.0, - environment: import.meta.env.MODE, - }); + ignoreErrors: [ + "ResizeObserver loop", + "Module specifier, 'fs' does not start", + "Module specifier, 'zlib' does not start with" + ], + integrations: [ + Sentry.replayIntegration({ + maskAllText: false, + blockAllMedia: true + }), + new Sentry.BrowserTracing({}) + ], + tracePropagationTargets: [ + "api.imex.online", + "api.test.imex.online", + "db.imex.online", + "api.romeonline.io", + "api.test.romeonline.io", + "db.romeonline.io" + ], + tracesSampleRate: 1.0, + replaysOnErrorSampleRate: 1.0, + environment: import.meta.env.MODE + }); } -const router = createBrowserRouter( - createRoutesFromElements(} />) -); +const router = createBrowserRouter(createRoutesFromElements(} />)); - -if (import.meta.env.DEV ) { - let styles = 'font-weight: bold; font-size: 50px;color: red; 6px 6px 0 rgb(226,91,14) , 9px 9px 0 rgb(245,221,8) , 12px 12px 0 rgb(5,148,68) ' - console.log('%c %s', styles, `VER: ${import.meta.env.VITE_APP_INSTANCE}`) +if (import.meta.env.DEV) { + let styles = + "font-weight: bold; font-size: 50px;color: red; 6px 6px 0 rgb(226,91,14) , 9px 9px 0 rgb(245,221,8) , 12px 12px 0 rgb(5,148,68) "; + console.log("%c %s", styles, `VER: ${import.meta.env.VITE_APP_INSTANCE}`); } function App() { - return ( - } - persistor={persistor} - > - - - - - ); + return ( + } persistor={persistor}> + + + + + ); } // Used for ANTD Component Tokens // https://ant.design/docs/react/migrate-less-variables ReactDOM.createRoot(document.getElementById("root")).render( - - - + + + ); reportWebVitals(); diff --git a/client/src/pages/accounting-payables/accounting-payables.container.jsx b/client/src/pages/accounting-payables/accounting-payables.container.jsx index 69e18f8a9..1055b21af 100644 --- a/client/src/pages/accounting-payables/accounting-payables.container.jsx +++ b/client/src/pages/accounting-payables/accounting-payables.container.jsx @@ -1,88 +1,71 @@ -import {useQuery} from "@apollo/client"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; +import { useQuery } from "@apollo/client"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; import AccountingPayablesTable from "../../components/accounting-payables-table/accounting-payables-table.component"; import AlertComponent from "../../components/alert/alert.component"; -import {checkPartnerStatus} from "../../components/partner-ping/partner-ping.component"; +import { checkPartnerStatus } from "../../components/partner-ping/partner-ping.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {QUERY_BILLS_FOR_EXPORT} from "../../graphql/accounting.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectPartnerVersion} from "../../redux/application/application.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { QUERY_BILLS_FOR_EXPORT } from "../../graphql/accounting.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectPartnerVersion } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; - +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - partnerVersion: selectPartnerVersion, + bodyshop: selectBodyshop, + partnerVersion: selectPartnerVersion }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function AccountingPayablesContainer({ - bodyshop, - setBreadcrumbs, - setSelectedHeader, - partnerVersion, - }) { - const {t} = useTranslation(); +export function AccountingPayablesContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, partnerVersion }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.accounting-payables",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})}); - setSelectedHeader("payables"); - setBreadcrumbs([ - { - link: "/manage/accounting/payables", - label: t("titles.bc.accounting-payables"), - }, - ]); - checkPartnerStatus(bodyshop, true); - }, [t, setBreadcrumbs, setSelectedHeader, bodyshop]); - - const {loading, error, data, refetch} = useQuery(QUERY_BILLS_FOR_EXPORT, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", + useEffect(() => { + document.title = t("titles.accounting-payables", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) }); + setSelectedHeader("payables"); + setBreadcrumbs([ + { + link: "/manage/accounting/payables", + label: t("titles.bc.accounting-payables") + } + ]); + checkPartnerStatus(bodyshop, true); + }, [t, setBreadcrumbs, setSelectedHeader, bodyshop]); - if (error) return ; + const { loading, error, data, refetch } = useQuery(QUERY_BILLS_FOR_EXPORT, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const noPath = - !partnerVersion?.qbpath && - !( - bodyshop && - (bodyshop.cdk_dealerid || - bodyshop.pbs_serialnumber || - bodyshop.accountingconfig.qbo) - ); + if (error) return ; - return ( -
    - - - {noPath && ( - - )} - - - -
    - ); + const noPath = + !partnerVersion?.qbpath && + !(bodyshop && (bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || bodyshop.accountingconfig.qbo)); + + return ( +
    + + + {noPath && } + + + +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(AccountingPayablesContainer); +export default connect(mapStateToProps, mapDispatchToProps)(AccountingPayablesContainer); diff --git a/client/src/pages/accounting-payments/accounting-payments.container.jsx b/client/src/pages/accounting-payments/accounting-payments.container.jsx index 2c5544fab..77c6157fa 100644 --- a/client/src/pages/accounting-payments/accounting-payments.container.jsx +++ b/client/src/pages/accounting-payments/accounting-payments.container.jsx @@ -1,88 +1,69 @@ -import {useQuery} from "@apollo/client"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; +import { useQuery } from "@apollo/client"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; import AccountingPaymentsTable from "../../components/accounting-payments-table/accounting-payments-table.component"; import AlertComponent from "../../components/alert/alert.component"; -import {QUERY_PAYMENTS_FOR_EXPORT} from "../../graphql/accounting.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { QUERY_PAYMENTS_FOR_EXPORT } from "../../graphql/accounting.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {checkPartnerStatus} from "../../components/partner-ping/partner-ping.component"; -import {selectPartnerVersion} from "../../redux/application/application.selectors"; +import { checkPartnerStatus } from "../../components/partner-ping/partner-ping.component"; +import { selectPartnerVersion } from "../../redux/application/application.selectors"; import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - partnerVersion: selectPartnerVersion, + bodyshop: selectBodyshop, + partnerVersion: selectPartnerVersion }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function AccountingPaymentsContainer({ - bodyshop, - setBreadcrumbs, - setSelectedHeader, - partnerVersion, - }) { - const {t} = useTranslation(); +export function AccountingPaymentsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, partnerVersion }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.accounting-payments", {app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})}); - setSelectedHeader("payments"); - setBreadcrumbs([ - { - link: "/manage/accounting/payments", - label: t("titles.bc.accounting-payments"), - }, - ]); - checkPartnerStatus(bodyshop, true); - }, [t, setBreadcrumbs, setSelectedHeader, bodyshop]); + useEffect(() => { + document.title = t("titles.accounting-payments", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("payments"); + setBreadcrumbs([ + { + link: "/manage/accounting/payments", + label: t("titles.bc.accounting-payments") + } + ]); + checkPartnerStatus(bodyshop, true); + }, [t, setBreadcrumbs, setSelectedHeader, bodyshop]); - const {loading, error, data, refetch} = useQuery( - QUERY_PAYMENTS_FOR_EXPORT, - { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - } - ); + const { loading, error, data, refetch } = useQuery(QUERY_PAYMENTS_FOR_EXPORT, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - if (error) return ; - const noPath = - !partnerVersion?.qbpath && - !( - bodyshop && - (bodyshop.cdk_dealerid || - bodyshop.pbs_serialnumber || - bodyshop.accountingconfig.qbo) - ); - return ( -
    - - - {noPath && ( - - )} - - - -
    - ); + if (error) return ; + const noPath = + !partnerVersion?.qbpath && + !(bodyshop && (bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || bodyshop.accountingconfig.qbo)); + return ( +
    + + + {noPath && } + + + +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(AccountingPaymentsContainer); +export default connect(mapStateToProps, mapDispatchToProps)(AccountingPaymentsContainer); diff --git a/client/src/pages/accounting-qbo/accounting-qbo.page.jsx b/client/src/pages/accounting-qbo/accounting-qbo.page.jsx index 92a834fef..136d41b11 100644 --- a/client/src/pages/accounting-qbo/accounting-qbo.page.jsx +++ b/client/src/pages/accounting-qbo/accounting-qbo.page.jsx @@ -1,46 +1,39 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; import QboAuthorizeComponent from "../../components/qbo-authorize/qbo-authorize.component"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function AccountingReceivablesContainer({ - bodyshop, - setBreadcrumbs, - setSelectedHeader, - }) { - const {t} = useTranslation(); +export function AccountingReceivablesContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.accounting-qbo"); - setSelectedHeader("qbo"); - setBreadcrumbs([ - { - link: "/manage/accounting/qbo", - label: t("titles.bc.accounting-qbo"), - }, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.accounting-qbo"); + setSelectedHeader("qbo"); + setBreadcrumbs([ + { + link: "/manage/accounting/qbo", + label: t("titles.bc.accounting-qbo") + } + ]); + }, [t, setBreadcrumbs, setSelectedHeader]); - return ( -
    - -
    - ); + return ( +
    + +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(AccountingReceivablesContainer); +export default connect(mapStateToProps, mapDispatchToProps)(AccountingReceivablesContainer); diff --git a/client/src/pages/accounting-receivables/accounting-receivables.container.jsx b/client/src/pages/accounting-receivables/accounting-receivables.container.jsx index 2187d2f54..e1485a3a9 100644 --- a/client/src/pages/accounting-receivables/accounting-receivables.container.jsx +++ b/client/src/pages/accounting-receivables/accounting-receivables.container.jsx @@ -1,91 +1,74 @@ -import {useQuery} from "@apollo/client"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import AccountingReceivablesTable - from "../../components/accounting-receivables-table/accounting-receivables-table.component"; +import { useQuery } from "@apollo/client"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import AccountingReceivablesTable from "../../components/accounting-receivables-table/accounting-receivables-table.component"; import AlertComponent from "../../components/alert/alert.component"; -import {QUERY_JOBS_FOR_EXPORT} from "../../graphql/accounting.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { QUERY_JOBS_FOR_EXPORT } from "../../graphql/accounting.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {checkPartnerStatus} from "../../components/partner-ping/partner-ping.component"; -import {selectPartnerVersion} from "../../redux/application/application.selectors"; +import { checkPartnerStatus } from "../../components/partner-ping/partner-ping.component"; +import { selectPartnerVersion } from "../../redux/application/application.selectors"; import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - partnerVersion: selectPartnerVersion, + bodyshop: selectBodyshop, + partnerVersion: selectPartnerVersion }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function AccountingReceivablesContainer({ - bodyshop, - setBreadcrumbs, - setSelectedHeader, - partnerVersion, - }) { - const {t} = useTranslation(); +export function AccountingReceivablesContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, partnerVersion }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.accounting-receivables",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})}); - setSelectedHeader("receivables"); - setBreadcrumbs([ - { - link: "/manage/accounting/receivables", - label: t("titles.bc.accounting-receivables"), - }, - ]); - checkPartnerStatus(bodyshop, true); - }, [t, setBreadcrumbs, setSelectedHeader, bodyshop]); - - const {loading, error, data, refetch} = useQuery(QUERY_JOBS_FOR_EXPORT, { - variables: { - invoicedStatus: bodyshop.md_ro_statuses.default_invoiced || "Invoiced*", - }, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", + useEffect(() => { + document.title = t("titles.accounting-receivables", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) }); + setSelectedHeader("receivables"); + setBreadcrumbs([ + { + link: "/manage/accounting/receivables", + label: t("titles.bc.accounting-receivables") + } + ]); + checkPartnerStatus(bodyshop, true); + }, [t, setBreadcrumbs, setSelectedHeader, bodyshop]); - if (error) return ; + const { loading, error, data, refetch } = useQuery(QUERY_JOBS_FOR_EXPORT, { + variables: { + invoicedStatus: bodyshop.md_ro_statuses.default_invoiced || "Invoiced*" + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const noPath = - !partnerVersion?.qbpath && - !( - bodyshop && - (bodyshop.cdk_dealerid || - bodyshop.pbs_serialnumber || - bodyshop.accountingconfig.qbo) - ); + if (error) return ; - return ( -
    - - - {noPath && ( - - )} - - - -
    - ); + const noPath = + !partnerVersion?.qbpath && + !(bodyshop && (bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || bodyshop.accountingconfig.qbo)); + + return ( +
    + + + {noPath && } + + + +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(AccountingReceivablesContainer); +export default connect(mapStateToProps, mapDispatchToProps)(AccountingReceivablesContainer); diff --git a/client/src/pages/bills/bills.page.component.jsx b/client/src/pages/bills/bills.page.component.jsx index 9ab2bb185..eba2096d6 100644 --- a/client/src/pages/bills/bills.page.component.jsx +++ b/client/src/pages/bills/bills.page.component.jsx @@ -1,318 +1,291 @@ -import {EditFilled, SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Checkbox, Input, Space, Table, Typography} from "antd"; +import { EditFilled, SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd"; import axios from "axios"; import queryString from "query-string"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, useLocation, useNavigate} from "react-router-dom"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useLocation, useNavigate } from "react-router-dom"; import BillDeleteButton from "../../components/bill-delete-button/bill-delete-button.component"; import PartsOrderModalContainer from "../../components/parts-order-modal/parts-order-modal.container"; import PrintWrapperComponent from "../../components/print-wrapper/print-wrapper.component"; -import {setModalContext} from "../../redux/modals/modals.actions"; +import { setModalContext } from "../../redux/modals/modals.actions"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {DateFormatter} from "../../utils/DateFormatter"; -import {TemplateList} from "../../utils/TemplateConstants"; -import {alphaSort, dateSort} from "../../utils/sorters"; -import {pageLimit} from "../../utils/config"; +import { DateFormatter } from "../../utils/DateFormatter"; +import { TemplateList } from "../../utils/TemplateConstants"; +import { alphaSort, dateSort } from "../../utils/sorters"; +import { pageLimit } from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ - setPartsOrderContext: (context) => - dispatch(setModalContext({context: context, modal: "partsOrder"})), - setBillEnterContext: (context) => - dispatch(setModalContext({context: context, modal: "billEnter"})), + setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })), + setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })) }); -export function BillsListPage({ - loading, - data, - refetch, - total, - setPartsOrderContext, - setBillEnterContext, - }) { - const search = queryString.parse(useLocation().search); - const [openSearchResults, setOpenSearchResults] = useState([]); - const [searchLoading, setSearchLoading] = useState(false); - const {page} = search; - const history = useNavigate(); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); - const Templates = TemplateList("bill"); - const {t} = useTranslation(); - const columns = [ - { - title: t("bills.fields.vendorname"), - dataIndex: "vendorname", - key: "vendorname", - // sortObject: (direction) => { - // return { - // vendor: { - // name: direction - // ? direction === "descend" - // ? "desc" - // : "asc" - // : "desc", - // }, - // }; - // }, - // sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name), - // sortOrder: - // state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order, - render: (text, record) => {record.vendor.name}, - }, - { - title: t("bills.fields.invoice_number"), - dataIndex: "invoice_number", - key: "invoice_number", - sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), - sortOrder: - state.sortedInfo.columnKey === "invoice_number" && - state.sortedInfo.order, - }, - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - // sortObject: (direction) => { - // return { - // job: { - // ro_number: direction - // ? direction === "descend" - // ? "desc" - // : "asc" - // : "desc", - // }, - // }; - // }, - // sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), - // sortOrder: - // state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - render: (text, record) => - record.job && ( - - {record.job.ro_number} - - ), - }, - { - title: t("bills.fields.date"), - dataIndex: "date", - key: "date", - sorter: (a, b) => dateSort(a.date, b.date), - sortOrder: - state.sortedInfo.columnKey === "date" && state.sortedInfo.order, - render: (text, record) => {record.date}, - }, - { - title: t("bills.fields.total"), - dataIndex: "total", - key: "total", - sorter: (a, b) => a.total - b.total, - sortOrder: - state.sortedInfo.columnKey === "total" && state.sortedInfo.order, - render: (text, record) => ( - {record.total} - ), - }, - { - title: t("bills.fields.is_credit_memo"), - dataIndex: "is_credit_memo", - key: "is_credit_memo", - sorter: (a, b) => a.is_credit_memo - b.is_credit_memo, - sortOrder: - state.sortedInfo.columnKey === "is_credit_memo" && - state.sortedInfo.order, - render: (text, record) => ( - - ), - }, - { - title: t("bills.fields.exported"), - dataIndex: "exported", - key: "exported", - sorter: (a, b) => a.exported - b.exported, - sortOrder: - state.sortedInfo.columnKey === "exported" && state.sortedInfo.order, - render: (text, record) => , - }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", - render: (text, record) => ( - - - - - { - // - } - { - //Filter out the state and set it again. - setOpenSearchResults((currentResults) => - currentResults.filter((bill) => bill.id !== deletedBillid) - ); - }} - /> - {record.isinhouse && ( - - )} - - ), - }, - ]; - - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - search.page = pagination.current; - if (sorter && sorter.column && sorter.column.sortObject) { - search.searchObj = JSON.stringify(sorter.column.sortObject(sorter.order)); - } else { - delete search.searchObj; - search.sortcolumn = sorter.order ? sorter.columnKey : null; - search.sortorder = sorter.order; - } - search.sort = JSON.stringify({[sorter.columnKey]: sorter.order}); - history({search: queryString.stringify(search)}); - }; - - useEffect(() => { - if (search.search && search.search.trim() !== "") { - searchBills(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - async function searchBills(value) { - try { - setSearchLoading(true); - const searchData = await axios.post("/search", { - search: value || search.search, - index: "bills", - }); - setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source)); - } catch (error) { - console.log("Error while fetching search results", error); - } finally { - setSearchLoading(false); - } - } - - return ( - - {search.search && ( - <> - - {t("general.labels.searchresults", {search: search.search})} - - - - )} - - - - { - search.search = value; - history({search: queryString.stringify(search)}); - searchBills(value); - }} - loading={loading || searchLoading} - enterButton - /> - - } - > - - -
    { + // return { + // vendor: { + // name: direction + // ? direction === "descend" + // ? "desc" + // : "asc" + // : "desc", + // }, + // }; + // }, + // sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name), + // sortOrder: + // state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order, + render: (text, record) => {record.vendor.name} + }, + { + title: t("bills.fields.invoice_number"), + dataIndex: "invoice_number", + key: "invoice_number", + sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), + sortOrder: state.sortedInfo.columnKey === "invoice_number" && state.sortedInfo.order + }, + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + // sortObject: (direction) => { + // return { + // job: { + // ro_number: direction + // ? direction === "descend" + // ? "desc" + // : "asc" + // : "desc", + // }, + // }; + // }, + // sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), + // sortOrder: + // state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => record.job && {record.job.ro_number} + }, + { + title: t("bills.fields.date"), + dataIndex: "date", + key: "date", + sorter: (a, b) => dateSort(a.date, b.date), + sortOrder: state.sortedInfo.columnKey === "date" && state.sortedInfo.order, + render: (text, record) => {record.date} + }, + { + title: t("bills.fields.total"), + dataIndex: "total", + key: "total", + sorter: (a, b) => a.total - b.total, + sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order, + render: (text, record) => {record.total} + }, + { + title: t("bills.fields.is_credit_memo"), + dataIndex: "is_credit_memo", + key: "is_credit_memo", + sorter: (a, b) => a.is_credit_memo - b.is_credit_memo, + sortOrder: state.sortedInfo.columnKey === "is_credit_memo" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: t("bills.fields.exported"), + dataIndex: "exported", + key: "exported", + sorter: (a, b) => a.exported - b.exported, + sortOrder: state.sortedInfo.columnKey === "exported" && state.sortedInfo.order, + render: (text, record) => + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => ( + + + + + { + // + } + { + //Filter out the state and set it again. + setOpenSearchResults((currentResults) => currentResults.filter((bill) => bill.id !== deletedBillid)); + }} + /> + {record.isinhouse && ( + - - ); + )} + + ) + } + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + search.page = pagination.current; + if (sorter && sorter.column && sorter.column.sortObject) { + search.searchObj = JSON.stringify(sorter.column.sortObject(sorter.order)); + } else { + delete search.searchObj; + search.sortcolumn = sorter.order ? sorter.columnKey : null; + search.sortorder = sorter.order; + } + search.sort = JSON.stringify({ [sorter.columnKey]: sorter.order }); + history({ search: queryString.stringify(search) }); + }; + + useEffect(() => { + if (search.search && search.search.trim() !== "") { + searchBills(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + async function searchBills(value) { + try { + setSearchLoading(true); + const searchData = await axios.post("/search", { + search: value || search.search, + index: "bills" + }); + setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source)); + } catch (error) { + console.log("Error while fetching search results", error); + } finally { + setSearchLoading(false); + } + } + + return ( + + {search.search && ( + <> + + {t("general.labels.searchresults", { search: search.search })} + + + + )} + + + + { + search.search = value; + history({ search: queryString.stringify(search) }); + searchBills(value); + }} + loading={loading || searchLoading} + enterButton + /> + + } + > + + +
    + + ); } export default connect(null, mapDispatchToProps)(BillsListPage); diff --git a/client/src/pages/bills/bills.page.container.jsx b/client/src/pages/bills/bills.page.container.jsx index 0d8cba856..bf2dde4d5 100644 --- a/client/src/pages/bills/bills.page.container.jsx +++ b/client/src/pages/bills/bills.page.container.jsx @@ -1,77 +1,74 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import queryString from "query-string"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation} from "react-router-dom"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation } from "react-router-dom"; import AlertComponent from "../../components/alert/alert.component"; import BillDetailEditContainer from "../../components/bill-detail-edit/bill-detail-edit.container"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {QUERY_ALL_BILLS_PAGINATED} from "../../graphql/bills.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; +import { QUERY_ALL_BILLS_PAGINATED } from "../../graphql/bills.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import BillsPageComponent from "./bills.page.component"; -import {pageLimit} from "../../utils/config"; +import { pageLimit } from "../../utils/config"; import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function BillsPageContainer({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); - const searchParams = queryString.parse(useLocation().search); - const {page, sortcolumn, sortorder, searchObj} = searchParams; +export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); + const searchParams = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder, searchObj } = searchParams; - useEffect(() => { - document.title = t("titles.bills-list",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})}); - setSelectedHeader("bills"); - setBreadcrumbs([ - {link: "/manage/bills", label: t("titles.bc.bills-list")}, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.bills-list", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("bills"); + setBreadcrumbs([{ link: "/manage/bills", label: t("titles.bc.bills-list") }]); + }, [t, setBreadcrumbs, setSelectedHeader]); - const {loading, error, data, refetch} = useQuery( - QUERY_ALL_BILLS_PAGINATED, - { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - variables: { - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, - order: [ - searchObj - ? JSON.parse(searchObj) - : { - [sortcolumn || "date"]: sortorder - ? sortorder === "descend" - ? "desc" - : "asc" - : "desc", - }, - ], - }, - } - ); + const { loading, error, data, refetch } = useQuery(QUERY_ALL_BILLS_PAGINATED, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, + order: [ + searchObj + ? JSON.parse(searchObj) + : { + [sortcolumn || "date"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc" + } + ] + } + }); - if (error) return ; - return ( - - -
    - + if (error) return ; + return ( + + +
    + - -
    -
    -
    - ); + +
    +
    +
    + ); } export default connect(null, mapDispatchToProps)(BillsPageContainer); diff --git a/client/src/pages/contract-create/contract-create.page.component.jsx b/client/src/pages/contract-create/contract-create.page.component.jsx index 5b0675fd2..f4aa406f3 100644 --- a/client/src/pages/contract-create/contract-create.page.component.jsx +++ b/client/src/pages/contract-create/contract-create.page.component.jsx @@ -1,70 +1,58 @@ -import {Button, Col, Form, Row, Space, Switch} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import { Button, Col, Form, Row, Space, Switch } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; import React from "react"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import ContractCarsContainer from "../../components/contract-cars/contract-cars.container"; import ContractFormComponent from "../../components/contract-form/contract-form.component"; import ContractJobsContainer from "../../components/contract-jobs/contract-jobs.container"; -export default function ContractCreatePageComponent({ - form, - selectedJobState, - selectedCarState, - loading, - }) { - const {t} = useTranslation(); +export default function ContractCreatePageComponent({ form, selectedJobState, selectedCarState, loading }) { + const { t } = useTranslation(); - const CreateButton = ( - - {selectedJobState[0] && selectedCarState[0] && ( - - - - )} - - - ); + const CreateButton = ( + + {selectedJobState[0] && selectedCarState[0] && ( + + + + )} + + + ); - return ( -
    - -
    - - - - - - -
    - -
    - - - - - ); + return ( +
    + +
    + + + + + + +
    + +
    + + + + + ); } diff --git a/client/src/pages/contract-create/contract-create.page.container.jsx b/client/src/pages/contract-create/contract-create.page.container.jsx index 6b33e8fd0..7487780ab 100644 --- a/client/src/pages/contract-create/contract-create.page.container.jsx +++ b/client/src/pages/contract-create/contract-create.page.container.jsx @@ -1,149 +1,139 @@ -import {useMutation} from "@apollo/client"; -import {Form, notification} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import { useMutation } from "@apollo/client"; +import { Form, notification } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {INSERT_NEW_CONTRACT} from "../../graphql/cccontracts.queries"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { INSERT_NEW_CONTRACT } from "../../graphql/cccontracts.queries"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import ContractCreatePageComponent from "./contract-create.page.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function ContractCreatePageContainer({ - bodyshop, - setBreadcrumbs, - setSelectedHeader, - }) { - const [form] = Form.useForm(); - const {t} = useTranslation(); +export function ContractCreatePageContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) { + const [form] = Form.useForm(); + const { t } = useTranslation(); - const history = useNavigate(); - const location = useLocation(); - const [loading, setLoading] = useState(false); - const selectedCarState = useState(null); - const selectedJobState = useState( - (location.state && location.state.jobId) || null - ); + const history = useNavigate(); + const location = useLocation(); + const [loading, setLoading] = useState(false); + const selectedCarState = useState(null); + const selectedJobState = useState((location.state && location.state.jobId) || null); - const [insertContract] = useMutation(INSERT_NEW_CONTRACT); - const [intakeJob] = useMutation(UPDATE_JOB); + const [insertContract] = useMutation(INSERT_NEW_CONTRACT); + const [intakeJob] = useMutation(UPDATE_JOB); - const handleFinish = async ({addtoproduction, ...values}) => { - if (!!selectedCarState[0] && !!selectedJobState[0]) { - setLoading(true); - const result = await insertContract({ - variables: { - ccId: selectedCarState[0].id, - damage: values.damage, - mileage: values.kmstart, - contract: { - ...values, - status: "contracts.status.out", - courtesycarid: selectedCarState[0].id, - jobid: selectedJobState[0], - }, - }, - }); - - if (!result.errors) { - //Update the courtesy car to have the damage. - notification["success"]({ - message: t("contracts.successes.saved"), - }); - - //Intake the job if required - if (addtoproduction) { - const result2 = await intakeJob({ - variables: { - jobId: selectedJobState[0], - job: { - actual_in: new Date(), - inproduction: true, - status: bodyshop.md_ro_statuses.default_arrived, - }, - }, - }); - if (result2.errors) { - notification["error"]({ - message: t("jobs.errors.saving", { - error: JSON.stringify(!result2.errors), - }), - }); - return; - } - } - - form.resetFields(); - form.resetFields(); - history( - `/manage/courtesycars/contracts/${result.data.insert_cccontracts.returning[0].id}` - ); - } else { - notification["error"]({ - message: t("contracts.errors.saving", { - error: JSON.stringify(!result.errors), - }), - }); - } - } else { - notification["error"]({ - message: t("contracts.errors.selectjobandcar"), - }); + const handleFinish = async ({ addtoproduction, ...values }) => { + if (!!selectedCarState[0] && !!selectedJobState[0]) { + setLoading(true); + const result = await insertContract({ + variables: { + ccId: selectedCarState[0].id, + damage: values.damage, + mileage: values.kmstart, + contract: { + ...values, + status: "contracts.status.out", + courtesycarid: selectedCarState[0].id, + jobid: selectedJobState[0] + } } - setLoading(false); - }; + }); - useEffect(() => { - document.title = t("titles.contracts-create",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})}); - setSelectedHeader("newcontract"); - setBreadcrumbs([ - {link: "/manage/courtesycars", label: t("titles.bc.courtesycars")}, - { - link: "/manage/courtesycars/contracts", - label: t("titles.bc.contracts"), - }, - { - link: "/manage/courtesycars/contracts/new", - label: t("titles.bc.contracts-create"), - }, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + if (!result.errors) { + //Update the courtesy car to have the damage. + notification["success"]({ + message: t("contracts.successes.saved") + }); - return ( - - -
    - - -
    -
    - ); + //Intake the job if required + if (addtoproduction) { + const result2 = await intakeJob({ + variables: { + jobId: selectedJobState[0], + job: { + actual_in: new Date(), + inproduction: true, + status: bodyshop.md_ro_statuses.default_arrived + } + } + }); + if (result2.errors) { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(!result2.errors) + }) + }); + return; + } + } + + form.resetFields(); + form.resetFields(); + history(`/manage/courtesycars/contracts/${result.data.insert_cccontracts.returning[0].id}`); + } else { + notification["error"]({ + message: t("contracts.errors.saving", { + error: JSON.stringify(!result.errors) + }) + }); + } + } else { + notification["error"]({ + message: t("contracts.errors.selectjobandcar") + }); + } + setLoading(false); + }; + + useEffect(() => { + document.title = t("titles.contracts-create", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("newcontract"); + setBreadcrumbs([ + { link: "/manage/courtesycars", label: t("titles.bc.courtesycars") }, + { + link: "/manage/courtesycars/contracts", + label: t("titles.bc.contracts") + }, + { + link: "/manage/courtesycars/contracts/new", + label: t("titles.bc.contracts-create") + } + ]); + }, [t, setBreadcrumbs, setSelectedHeader]); + + return ( + + +
    + + +
    +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ContractCreatePageContainer); +export default connect(mapStateToProps, mapDispatchToProps)(ContractCreatePageContainer); diff --git a/client/src/pages/contract-detail/contract-detail.page.component.jsx b/client/src/pages/contract-detail/contract-detail.page.component.jsx index 27b3ceb71..e170548ea 100644 --- a/client/src/pages/contract-detail/contract-detail.page.component.jsx +++ b/client/src/pages/contract-detail/contract-detail.page.component.jsx @@ -1,126 +1,115 @@ -import {Button, Col, Dropdown, Form, Row, Space, Typography,} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import { Button, Col, Dropdown, Form, Row, Space, Typography } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import ContractConvertToRo from "../../components/contract-convert-to-ro/contract-convert-to-ro.component"; -import ContractCourtesyCarBlock - from "../../components/contract-courtesy-car-block/contract-courtesy-car-block.component"; +import ContractCourtesyCarBlock from "../../components/contract-courtesy-car-block/contract-courtesy-car-block.component"; import ContractFormComponent from "../../components/contract-form/contract-form.component"; import ContractJobBlock from "../../components/contract-job-block/contract-job-block.component"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {GenerateDocument} from "../../utils/RenderTemplate"; -import {TemplateList} from "../../utils/TemplateConstants"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; const mapDispatchToProps = (dispatch) => ({ - setCourtesyCarReturnModalContext: (context) => - dispatch(setModalContext({context: context, modal: "courtesyCarReturn"})), + setCourtesyCarReturnModalContext: (context) => + dispatch(setModalContext({ context: context, modal: "courtesyCarReturn" })) }); export function ContractDetailPage({ - contract, - job, - courtesyCar, - setCourtesyCarReturnModalContext, - refetch, - form, - saveLoading - }) { - const {t} = useTranslation(); - return ( -
    - - - - - {() => { - const menu = { - onClick: (e) => { - GenerateDocument( - { - name: TemplateList("courtesycarcontract")[e.key].key, - variables: {id: contract.id}, - }, - {}, - "p" - ); - }, - items: [ - { - key: "courtesy_car_contract", - label: t("contracts.actions.printcontract"), - }, - { - key: "courtesy_car_terms", - label: t("printcenter.courtesycarcontract.courtesy_car_terms"), - }, - { - key: "courtesy_car_impound", - label: t("printcenter.courtesycarcontract.courtesy_car_impound"), - }, - ] - }; + contract, + job, + courtesyCar, + setCourtesyCarReturnModalContext, + refetch, + form, + saveLoading +}) { + const { t } = useTranslation(); + return ( +
    + + + + + {() => { + const menu = { + onClick: (e) => { + GenerateDocument( + { + name: TemplateList("courtesycarcontract")[e.key].key, + variables: { id: contract.id } + }, + {}, + "p" + ); + }, + items: [ + { + key: "courtesy_car_contract", + label: t("contracts.actions.printcontract") + }, + { + key: "courtesy_car_terms", + label: t("printcenter.courtesycarcontract.courtesy_car_terms") + }, + { + key: "courtesy_car_impound", + label: t("printcenter.courtesycarcontract.courtesy_car_impound") + } + ] + }; - return ( - - - - - - + return ( + + + + + + - - - ); - }} - - } - /> - -
    - - - - - - - - - - - ); + + + ); + }} + + } + /> + + + + + + + + + + + + + ); } export default connect(null, mapDispatchToProps)(ContractDetailPage); diff --git a/client/src/pages/contract-detail/contract-detail.page.container.jsx b/client/src/pages/contract-detail/contract-detail.page.container.jsx index 2ce8f3d3f..4b6bd8d84 100644 --- a/client/src/pages/contract-detail/contract-detail.page.container.jsx +++ b/client/src/pages/contract-detail/contract-detail.page.container.jsx @@ -1,170 +1,150 @@ -import {useMutation, useQuery} from "@apollo/client"; -import {Form, notification} from "antd"; +import { useMutation, useQuery } from "@apollo/client"; +import { Form, notification } from "antd"; import dayjs from "../../utils/day"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useParams} from "react-router-dom"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useParams } from "react-router-dom"; import AlertComponent from "../../components/alert/alert.component"; -import CourtesyCarReturnModalContainer - from "../../components/courtesy-car-return-modal/courtesy-car-return-modal.container"; +import CourtesyCarReturnModalContainer from "../../components/courtesy-car-return-modal/courtesy-car-return-modal.container"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {QUERY_CONTRACT_BY_PK, UPDATE_CONTRACT,} from "../../graphql/cccontracts.queries"; -import {addRecentItem, setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {CreateRecentItem} from "../../utils/create-recent-item"; +import { QUERY_CONTRACT_BY_PK, UPDATE_CONTRACT } from "../../graphql/cccontracts.queries"; +import { addRecentItem, setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { CreateRecentItem } from "../../utils/create-recent-item"; import ContractDetailPageComponent from "./contract-detail.page.component"; import NotFound from "../../components/not-found/not-found.component"; import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - addRecentItem: (item) => dispatch(addRecentItem(item)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + addRecentItem: (item) => dispatch(addRecentItem(item)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function ContractDetailPageContainer({ - setBreadcrumbs, - addRecentItem, - setSelectedHeader, - }) { - const {t} = useTranslation(); - const [updateContract] = useMutation(UPDATE_CONTRACT); - const [saveLoading, setsaveLoading] = useState(false); - const [form] = Form.useForm(); - const {contractId} = useParams(); +export function ContractDetailPageContainer({ setBreadcrumbs, addRecentItem, setSelectedHeader }) { + const { t } = useTranslation(); + const [updateContract] = useMutation(UPDATE_CONTRACT); + const [saveLoading, setsaveLoading] = useState(false); + const [form] = Form.useForm(); + const { contractId } = useParams(); - const {loading, error, data, refetch} = useQuery(QUERY_CONTRACT_BY_PK, { - variables: {id: contractId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const { loading, error, data, refetch } = useQuery(QUERY_CONTRACT_BY_PK, { + variables: { id: contractId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - useEffect(() => { - setSelectedHeader("contracts"); - document.title = loading - ? InstanceRenderManager({imex: t("titles.imexonline"), rome: t("titles.romeonline"), promanager:t("titles.promanager")}) - : error - ? InstanceRenderManager({imex: t("titles.imexonline"), rome: t("titles.romeonline"), promanager:t("titles.promanager")}) - : t("titles.contracts-detail", { - id: - (data && - data.cccontracts_by_pk && - data.cccontracts_by_pk.agreementnumber) || - "", - }); + useEffect(() => { + setSelectedHeader("contracts"); + document.title = loading + ? InstanceRenderManager({ + imex: t("titles.imexonline"), + rome: t("titles.romeonline"), + promanager: t("titles.promanager") + }) + : error + ? InstanceRenderManager({ + imex: t("titles.imexonline"), + rome: t("titles.romeonline"), + promanager: t("titles.promanager") + }) + : t("titles.contracts-detail", { + id: (data && data.cccontracts_by_pk && data.cccontracts_by_pk.agreementnumber) || "" + }); - setBreadcrumbs([ - {link: "/manage/courtesycars", label: t("titles.bc.courtesycars")}, - { - link: "/manage/courtesycars/contracts", - label: t("titles.bc.contracts"), - }, - { - link: "/manage/courtesycars/contracts/new", - label: t("titles.bc.contracts-detail", { - number: - (data && - data.cccontracts_by_pk && - data.cccontracts_by_pk.agreementnumber) || - "", - }), - }, - ]); - - if (data && data.cccontracts_by_pk) - addRecentItem( - CreateRecentItem( - contractId, - "contract", - data.cccontracts_by_pk.agreementnumber, - `/manage/courtesycars/contracts/${contractId}` - ) - ); - }, [ - t, - data, - error, - loading, - setBreadcrumbs, - addRecentItem, - contractId, - setSelectedHeader, + setBreadcrumbs([ + { link: "/manage/courtesycars", label: t("titles.bc.courtesycars") }, + { + link: "/manage/courtesycars/contracts", + label: t("titles.bc.contracts") + }, + { + link: "/manage/courtesycars/contracts/new", + label: t("titles.bc.contracts-detail", { + number: (data && data.cccontracts_by_pk && data.cccontracts_by_pk.agreementnumber) || "" + }) + } ]); - const handleFinish = async (values) => { - setsaveLoading(true); - const result = await updateContract({ - variables: {cccontract: {...values}, contractId: contractId}, - }); - if (!!result.errors) { - notification["error"]({ - message: t("contracts.errors.saving", { - message: JSON.stringify(result.errors), - }), - }); - return; - } - notification["success"]({message: t("contracts.successes.saved")}); - if (refetch) await refetch(); - setsaveLoading(false); + if (data && data.cccontracts_by_pk) + addRecentItem( + CreateRecentItem( + contractId, + "contract", + data.cccontracts_by_pk.agreementnumber, + `/manage/courtesycars/contracts/${contractId}` + ) + ); + }, [t, data, error, loading, setBreadcrumbs, addRecentItem, contractId, setSelectedHeader]); - form.resetFields(); - form.resetFields(); - }; + const handleFinish = async (values) => { + setsaveLoading(true); + const result = await updateContract({ + variables: { cccontract: { ...values }, contractId: contractId } + }); + if (!!result.errors) { + notification["error"]({ + message: t("contracts.errors.saving", { + message: JSON.stringify(result.errors) + }) + }); + return; + } + notification["success"]({ message: t("contracts.successes.saved") }); + if (refetch) await refetch(); + setsaveLoading(false); - useEffect(() => { - if (data && data.cccontracts_by_pk) form.resetFields(); - }, [data, form]); + form.resetFields(); + form.resetFields(); + }; - if (error) return ; - if (loading) return ; + useEffect(() => { + if (data && data.cccontracts_by_pk) form.resetFields(); + }, [data, form]); - if (!!!data.cccontracts_by_pk) return ; + if (error) return ; + if (loading) return ; - return ( - - -
    - -
    - - -
    -
    -
    - ); + if (!!!data.cccontracts_by_pk) return ; + + return ( + + +
    + +
    + + +
    +
    +
    + ); } export default connect(null, mapDispatchToProps)(ContractDetailPageContainer); diff --git a/client/src/pages/contracts/contracts.page.component.jsx b/client/src/pages/contracts/contracts.page.component.jsx index 049489aa0..6149dcfb9 100644 --- a/client/src/pages/contracts/contracts.page.component.jsx +++ b/client/src/pages/contracts/contracts.page.component.jsx @@ -1,18 +1,6 @@ import React from "react"; import ContractsList from "../../components/contracts-list/contracts-list.component"; -export default function ContractsPageComponent({ - loading, - data, - refetch, - total, - }) { - return ( - - ); +export default function ContractsPageComponent({ loading, data, refetch, total }) { + return ; } diff --git a/client/src/pages/contracts/contracts.page.container.jsx b/client/src/pages/contracts/contracts.page.container.jsx index a6300841e..4653f654d 100644 --- a/client/src/pages/contracts/contracts.page.container.jsx +++ b/client/src/pages/contracts/contracts.page.container.jsx @@ -1,74 +1,73 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import queryString from "query-string"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation} from "react-router-dom"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation } from "react-router-dom"; import AlertComponent from "../../components/alert/alert.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {QUERY_ACTIVE_CONTRACTS_PAGINATED} from "../../graphql/cccontracts.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; +import { QUERY_ACTIVE_CONTRACTS_PAGINATED } from "../../graphql/cccontracts.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import ContractsPageComponent from "./contracts.page.component"; -import {pageLimit} from "../../utils/config"; +import { pageLimit } from "../../utils/config"; import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function ContractsPageContainer({setBreadcrumbs, setSelectedHeader}) { - const searchParams = queryString.parse(useLocation().search); - const {search, page, sortcolumn, sortorder} = searchParams; +export function ContractsPageContainer({ setBreadcrumbs, setSelectedHeader }) { + const searchParams = queryString.parse(useLocation().search); + const { search, page, sortcolumn, sortorder } = searchParams; - const {loading, error, data, refetch} = useQuery( - QUERY_ACTIVE_CONTRACTS_PAGINATED, + const { loading, error, data, refetch } = useQuery(QUERY_ACTIVE_CONTRACTS_PAGINATED, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + search: search || "", + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, + order: [ { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - variables: { - search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, - order: [ - { - [sortcolumn || "start"]: sortorder - ? sortorder === "descend" - ? "desc" - : "asc" - : "desc", - }, - ], - }, + [sortcolumn || "start"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc" } - ); - const {t} = useTranslation(); - useEffect(() => { - document.title = t("titles.contracts", {app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})}); - setSelectedHeader("contracts"); - setBreadcrumbs([ - {link: "/manage/courtesycars", label: t("titles.bc.courtesycars")}, - { - link: "/manage/courtesycars/contracts", - label: t("titles.bc.contracts"), - }, - ]); - }, [setBreadcrumbs, t, setSelectedHeader]); + ] + } + }); + const { t } = useTranslation(); + useEffect(() => { + document.title = t("titles.contracts", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("contracts"); + setBreadcrumbs([ + { link: "/manage/courtesycars", label: t("titles.bc.courtesycars") }, + { + link: "/manage/courtesycars/contracts", + label: t("titles.bc.contracts") + } + ]); + }, [setBreadcrumbs, t, setSelectedHeader]); - if (error) return ; - return ( - - - - - - ); + if (error) return ; + return ( + + + + + + ); } export default connect(null, mapDispatchToProps)(ContractsPageContainer); diff --git a/client/src/pages/courtesy-car-create/courtesy-car-create.page.container.jsx b/client/src/pages/courtesy-car-create/courtesy-car-create.page.container.jsx index ab659bec4..4ed2ef05a 100644 --- a/client/src/pages/courtesy-car-create/courtesy-car-create.page.container.jsx +++ b/client/src/pages/courtesy-car-create/courtesy-car-create.page.container.jsx @@ -1,85 +1,82 @@ -import {useMutation} from "@apollo/client"; -import {Form, notification} from "antd"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import { useMutation } from "@apollo/client"; +import { Form, notification } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import CourtesyCarFormComponent from "../../components/courtesy-car-form/courtesy-car-form.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {INSERT_NEW_COURTESY_CAR} from "../../graphql/courtesy-car.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { INSERT_NEW_COURTESY_CAR } from "../../graphql/courtesy-car.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function CourtesyCarCreateContainer({ - bodyshop, - setBreadcrumbs, - setSelectedHeader, - }) { - const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - const [insertCourtesyCar] = useMutation(INSERT_NEW_COURTESY_CAR); - const {t} = useTranslation(); - const history = useNavigate(); +export function CourtesyCarCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) { + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [insertCourtesyCar] = useMutation(INSERT_NEW_COURTESY_CAR); + const { t } = useTranslation(); + const history = useNavigate(); - const handleFinish = async (values) => { - setLoading(true); - const result = await insertCourtesyCar({ - variables: {courtesycar: {...values, bodyshopid: bodyshop.id}}, - }); + const handleFinish = async (values) => { + setLoading(true); + const result = await insertCourtesyCar({ + variables: { courtesycar: { ...values, bodyshopid: bodyshop.id } } + }); - if (!!result.errors) { - notification["error"]({ - message: t("courtesycars.errors.saving", { - message: JSON.stringify(result.errors), - }), - }); - setLoading(false); - } else { - setLoading(false); - form.resetFields(); - form.resetFields(); - notification["success"]({message: t("courtesycars.successes.saved")}); - history( - `/manage/courtesycars/${result.data.insert_courtesycars.returning[0].id}` - ); - } - }; + if (!!result.errors) { + notification["error"]({ + message: t("courtesycars.errors.saving", { + message: JSON.stringify(result.errors) + }) + }); + setLoading(false); + } else { + setLoading(false); + form.resetFields(); + form.resetFields(); + notification["success"]({ message: t("courtesycars.successes.saved") }); + history(`/manage/courtesycars/${result.data.insert_courtesycars.returning[0].id}`); + } + }; - useEffect(() => { - setSelectedHeader("courtesycarsall"); - document.title = t("titles.courtesycars-create",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})}); - setBreadcrumbs([ - {link: "/manage/courtesycars", label: t("titles.bc.courtesycars")}, - { - link: "/manage/courtesycars/new", - label: t("titles.bc.courtesycars-new"), - }, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + setSelectedHeader("courtesycarsall"); + document.title = t("titles.courtesycars-create", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setBreadcrumbs([ + { link: "/manage/courtesycars", label: t("titles.bc.courtesycars") }, + { + link: "/manage/courtesycars/new", + label: t("titles.bc.courtesycars-new") + } + ]); + }, [t, setBreadcrumbs, setSelectedHeader]); - return ( - - -
    - - -
    -
    - ); + return ( + + +
    + + +
    +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(CourtesyCarCreateContainer); +export default connect(mapStateToProps, mapDispatchToProps)(CourtesyCarCreateContainer); diff --git a/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.component.jsx b/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.component.jsx index 10b071b9e..0c6fbe609 100644 --- a/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.component.jsx +++ b/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.component.jsx @@ -1,27 +1,18 @@ import React from "react"; import CourtesyCarCreateFormComponent from "../../components/courtesy-car-form/courtesy-car-form.component"; -import CourtesyCarContractListComponent - from "../../components/courtesy-car-contract-list/courtesy-car-contract-list.component"; -import {Col, Divider, Row} from "antd"; +import CourtesyCarContractListComponent from "../../components/courtesy-car-contract-list/courtesy-car-contract-list.component"; +import { Col, Divider, Row } from "antd"; -export default function CourtesyCarDetailPageComponent({ - contracts, - form, - saveLoading, - totalContracts, - }) { - return ( - -
    - - - - - - - - ); +export default function CourtesyCarDetailPageComponent({ contracts, form, saveLoading, totalContracts }) { + return ( + + + + + + + + + + ); } diff --git a/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx b/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx index 194f9b448..8fc7efe6b 100644 --- a/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx +++ b/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx @@ -1,201 +1,175 @@ -import {useMutation, useQuery} from "@apollo/client"; -import {Form, notification} from "antd"; +import { useMutation, useQuery } from "@apollo/client"; +import { Form, notification } from "antd"; import dayjs from "../../utils/day"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation, useParams} from "react-router-dom"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation, useParams } from "react-router-dom"; import AlertComponent from "../../components/alert/alert.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {QUERY_CC_BY_PK, UPDATE_CC} from "../../graphql/courtesy-car.queries"; -import {addRecentItem, setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {CreateRecentItem} from "../../utils/create-recent-item"; +import { QUERY_CC_BY_PK, UPDATE_CC } from "../../graphql/courtesy-car.queries"; +import { addRecentItem, setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { CreateRecentItem } from "../../utils/create-recent-item"; import UndefinedToNull from "./../../utils/undefinedtonull"; import CourtesyCarDetailPageComponent from "./courtesy-car-detail.page.component"; import NotFound from "../../components/not-found/not-found.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import queryString from "query-string"; -import {pageLimit} from "../../utils/config"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { pageLimit } from "../../utils/config"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - addRecentItem: (item) => dispatch(addRecentItem(item)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + addRecentItem: (item) => dispatch(addRecentItem(item)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function CourtesyCarDetailPageContainer({ - setBreadcrumbs, - addRecentItem, - setSelectedHeader, - }) { - const searchParams = queryString.parse(useLocation().search); - const {page, sortcolumn, sortorder} = searchParams; +export function CourtesyCarDetailPageContainer({ setBreadcrumbs, addRecentItem, setSelectedHeader }) { + const searchParams = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder } = searchParams; - const {t} = useTranslation(); - const [updateCourtesyCar] = useMutation(UPDATE_CC); - const [form] = Form.useForm(); - const {ccId} = useParams(); - const [saveLoading, setSaveLoading] = useState(false); - const {loading, error, data} = useQuery(QUERY_CC_BY_PK, { - variables: { - id: ccId, - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, - order: [ - { - [sortcolumn || "start"]: sortorder - ? sortorder === "descend" - ? "desc" - : "asc" - : "desc", - }, - ], - }, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const { t } = useTranslation(); + const [updateCourtesyCar] = useMutation(UPDATE_CC); + const [form] = Form.useForm(); + const { ccId } = useParams(); + const [saveLoading, setSaveLoading] = useState(false); + const { loading, error, data } = useQuery(QUERY_CC_BY_PK, { + variables: { + id: ccId, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, + order: [ + { + [sortcolumn || "start"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc" + } + ] + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - useEffect(() => { - setSelectedHeader("courtesycarsall"); + useEffect(() => { + setSelectedHeader("courtesycarsall"); - document.title = loading - ? InstanceRenderManager({imex: t("titles.imexonline"), rome: t("titles.romeonline"), promanager:t("titles.promanager")}) - : error - ? InstanceRenderManager({imex: t("titles.imexonline"), rome: t("titles.romeonline"), promanager:t("titles.promanager")}) - : t("titles.courtesycars-detail", { - app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'}), - id: - (data && - data.courtesycars_by_pk && - data.courtesycars_by_pk.fleet_number) || - "", - }); - setBreadcrumbs([ - {link: "/manage/courtesycars", label: t("titles.bc.courtesycars")}, - { - link: `/manage/courtesycars/${ - (data && data.courtesycars_by_pk && data.courtesycars_by_pk.id) || "" - }`, - label: t("titles.bc.courtesycars-detail", { - number: - (data && - data.courtesycars_by_pk && - data.courtesycars_by_pk.fleetnumber) || - "", - }), - }, - ]); - - if (data && data.courtesycars_by_pk) - addRecentItem( - CreateRecentItem( - ccId, - "courtesycar", - data.courtesycars_by_pk.fleet_number || data.courtesycars_by_pk.vin, - `/manage/courtesycars/${ccId}` - ) - ); - }, [ - t, - data, - error, - loading, - setBreadcrumbs, - ccId, - addRecentItem, - setSelectedHeader, + document.title = loading + ? InstanceRenderManager({ + imex: t("titles.imexonline"), + rome: t("titles.romeonline"), + promanager: t("titles.promanager") + }) + : error + ? InstanceRenderManager({ + imex: t("titles.imexonline"), + rome: t("titles.romeonline"), + promanager: t("titles.promanager") + }) + : t("titles.courtesycars-detail", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }), + id: (data && data.courtesycars_by_pk && data.courtesycars_by_pk.fleet_number) || "" + }); + setBreadcrumbs([ + { link: "/manage/courtesycars", label: t("titles.bc.courtesycars") }, + { + link: `/manage/courtesycars/${(data && data.courtesycars_by_pk && data.courtesycars_by_pk.id) || ""}`, + label: t("titles.bc.courtesycars-detail", { + number: (data && data.courtesycars_by_pk && data.courtesycars_by_pk.fleetnumber) || "" + }) + } ]); - const handleFinish = async (values) => { - setSaveLoading(true); + if (data && data.courtesycars_by_pk) + addRecentItem( + CreateRecentItem( + ccId, + "courtesycar", + data.courtesycars_by_pk.fleet_number || data.courtesycars_by_pk.vin, + `/manage/courtesycars/${ccId}` + ) + ); + }, [t, data, error, loading, setBreadcrumbs, ccId, addRecentItem, setSelectedHeader]); - const result = await updateCourtesyCar({ - variables: { - cc: {...UndefinedToNull(values, ["readiness"])}, - ccId: ccId, - }, - refetchQueries: ["QUERY_CC_BY_PK"], - awaitRefetchQueries: true, - }); + const handleFinish = async (values) => { + setSaveLoading(true); - if (!!result.errors) { - notification["error"]({ - message: t("courtesycars.errors.saving", {error: error}), - }); + const result = await updateCourtesyCar({ + variables: { + cc: { ...UndefinedToNull(values, ["readiness"]) }, + ccId: ccId + }, + refetchQueries: ["QUERY_CC_BY_PK"], + awaitRefetchQueries: true + }); + + if (!!result.errors) { + notification["error"]({ + message: t("courtesycars.errors.saving", { error: error }) + }); + } + + notification["success"]({ + message: t("courtesycars.successes.saved") + }); + + setSaveLoading(false); + }; + + useEffect(() => { + if (data && data.courtesycars_by_pk) { + form.resetFields(); + form.resetFields(); + } + }, [data, form]); + + if (loading) return ; + if (error) return ; + + if (!!!data.courtesycars_by_pk) return ; + + return ( + +
    { - if (data && data.courtesycars_by_pk) { - form.resetFields(); - form.resetFields(); - } - }, [data, form]); - - if (loading) return ; - if (error) return ; - - if (!!!data.courtesycars_by_pk) return ; - - return ( - - - - - - ); + > + + +
    + ); } -export default connect( - null, - mapDispatchToProps -)(CourtesyCarDetailPageContainer); +export default connect(null, mapDispatchToProps)(CourtesyCarDetailPageContainer); diff --git a/client/src/pages/courtesy-cars/courtesy-cars.page.component.jsx b/client/src/pages/courtesy-cars/courtesy-cars.page.component.jsx index fd9978b32..d840ccc41 100644 --- a/client/src/pages/courtesy-cars/courtesy-cars.page.component.jsx +++ b/client/src/pages/courtesy-cars/courtesy-cars.page.component.jsx @@ -1,12 +1,6 @@ import React from "react"; import CourtesyCarsListComponent from "../../components/courtesy-cars-list/courtesy-cars-list.component"; -export default function CourtesyCarsPageComponent({loading, data, refetch}) { - return ( - - ); +export default function CourtesyCarsPageComponent({ loading, data, refetch }) { + return ; } diff --git a/client/src/pages/courtesy-cars/courtesy-cars.page.container.jsx b/client/src/pages/courtesy-cars/courtesy-cars.page.container.jsx index df41fc465..607242570 100644 --- a/client/src/pages/courtesy-cars/courtesy-cars.page.container.jsx +++ b/client/src/pages/courtesy-cars/courtesy-cars.page.container.jsx @@ -1,49 +1,46 @@ -import {useQuery} from "@apollo/client"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import { useQuery } from "@apollo/client"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import AlertComponent from "../../components/alert/alert.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {QUERY_ALL_CC} from "../../graphql/courtesy-car.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; +import { QUERY_ALL_CC } from "../../graphql/courtesy-car.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import CourtesyCarsPageComponent from "./courtesy-cars.page.component"; import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function CourtesyCarsPageContainer({ - setBreadcrumbs, - setSelectedHeader, - }) { - const {loading, error, data, refetch} = useQuery(QUERY_ALL_CC, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", +export function CourtesyCarsPageContainer({ setBreadcrumbs, setSelectedHeader }) { + const { loading, error, data, refetch } = useQuery(QUERY_ALL_CC, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const { t } = useTranslation(); + useEffect(() => { + document.title = t("titles.courtesycars", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) }); - const {t} = useTranslation(); - useEffect(() => { - document.title = t("titles.courtesycars",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})}); - setSelectedHeader("courtesycarsall"); - setBreadcrumbs([ - {link: "/manage/courtesycars", label: t("titles.bc.courtesycars")}, - ]); - }, [setBreadcrumbs, t, setSelectedHeader]); + setSelectedHeader("courtesycarsall"); + setBreadcrumbs([{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") }]); + }, [setBreadcrumbs, t, setSelectedHeader]); - if (error) return ; - return ( - - - - - - ); + if (error) return ; + return ( + + + + + + ); } export default connect(null, mapDispatchToProps)(CourtesyCarsPageContainer); diff --git a/client/src/pages/csi/csi.container.page.jsx b/client/src/pages/csi/csi.container.page.jsx index 5896f1c6d..7e4857d1b 100644 --- a/client/src/pages/csi/csi.container.page.jsx +++ b/client/src/pages/csi/csi.container.page.jsx @@ -1,255 +1,243 @@ //import {useMutation, useQuery } from "@apollo/client"; -import {Button, Form, Layout, Result, Typography} from "antd"; +import { Button, Form, Layout, Result, Typography } from "antd"; import axios from "axios"; -import React, {useCallback, useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useParams} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import React, { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useParams } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import AlertComponent from "../../components/alert/alert.component"; import ConfigFormComponents from "../../components/config-form-components/config-form-components.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; -import {selectCurrentUser} from "../../redux/user/user.selectors"; -import {DateTimeFormat} from "./../../utils/DateFormatter"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { selectCurrentUser } from "../../redux/user/user.selectors"; +import { DateTimeFormat } from "./../../utils/DateFormatter"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({}); export default connect(mapStateToProps, mapDispatchToProps)(CsiContainerPage); -export function CsiContainerPage({currentUser}) { - const {surveyId} = useParams(); - const [form] = Form.useForm(); - const [axiosResponse, setAxiosResponse] = useState(null); - const [submitting, setSubmitting] = useState({ - loading: false, - submitted: false, - }); +export function CsiContainerPage({ currentUser }) { + const { surveyId } = useParams(); + const [form] = Form.useForm(); + const [axiosResponse, setAxiosResponse] = useState(null); + const [submitting, setSubmitting] = useState({ + loading: false, + submitted: false + }); + const { t } = useTranslation(); + const getAxiosData = useCallback(async () => { + try { + try { + window.$crisp.push(["do", "chat:hide"]); + } catch { + console.log("Unable to attach to crisp instance. "); + } + setSubmitting((prevSubmitting) => ({ ...prevSubmitting, loading: true })); - const {t} = useTranslation(); - - - const getAxiosData = useCallback(async () => { - try { - try { - window.$crisp.push(["do", "chat:hide"]); - } catch { - console.log("Unable to attach to crisp instance. "); - } - setSubmitting((prevSubmitting) => ({...prevSubmitting, loading: true})); - - const response = await axios.post("/csi/lookup", { - surveyId - }); - setSubmitting((prevSubmitting) => ({ - ...prevSubmitting, - loading: false, - })); - setAxiosResponse(response.data); - } catch (error) { - console.error(`Something went wrong...: ${error.message}`); - console.dir({ - stack: error?.stack, - message: - error?.message, - }); - } - }, [setAxiosResponse, surveyId]); - - useEffect(() => { - getAxiosData().catch((err) => - console.error( - `Something went wrong fetching axios data: ${err.message || ""}` - ) - ); - }, [getAxiosData]); - - // Return if authorized - if (currentUser && currentUser.authorized) { - return ( - - - - ); + const response = await axios.post("/csi/lookup", { + surveyId + }); + setSubmitting((prevSubmitting) => ({ + ...prevSubmitting, + loading: false + })); + setAxiosResponse(response.data); + } catch (error) { + console.error(`Something went wrong...: ${error.message}`); + console.dir({ + stack: error?.stack, + message: error?.message + }); } + }, [setAxiosResponse, surveyId]); - if (submitting.loading) return ; + useEffect(() => { + getAxiosData().catch((err) => console.error(`Something went wrong fetching axios data: ${err.message || ""}`)); + }, [getAxiosData]); - const handleFinish = async (values) => { - try { - setSubmitting({...submitting, loading: true, submitting: true}); - const result = await axios.post("/csi/submit", {surveyId, values}); - console.log("result", result); - if (!!!result.errors && result.data.update_csi.affected_rows > 0) { - setSubmitting({...submitting, loading: false, submitted: true}); - } - } catch (error) { - console.error(`Something went wrong...: ${error.message}`); - console.dir({ - stack: error?.stack, - message: error?.message, - }); - } - }; + // Return if authorized + if (currentUser && currentUser.authorized) { + return ( + + + + ); + } - if (!axiosResponse || axiosResponse.csi_by_pk === null) { - // Do something here , this is where you would return a loading box or something - return ( - <> - - -
    - - -
    - - {t("csi.labels.copyright")}{" "} - {t("csi.fields.surveyid", {surveyId: surveyId})} - -
    - - ); - } else { + if (submitting.loading) return ; + + const handleFinish = async (values) => { + try { + setSubmitting({ ...submitting, loading: true, submitting: true }); + const result = await axios.post("/csi/submit", { surveyId, values }); + console.log("result", result); + if (!!!result.errors && result.data.update_csi.affected_rows > 0) { + setSubmitting({ ...submitting, loading: false, submitted: true }); + } + } catch (error) { + console.error(`Something went wrong...: ${error.message}`); + console.dir({ + stack: error?.stack, + message: error?.message + }); + } + }; + + if (!axiosResponse || axiosResponse.csi_by_pk === null) { + // Do something here , this is where you would return a loading box or something + return ( + <> + + +
    + + +
    + + {t("csi.labels.copyright")} {t("csi.fields.surveyid", { surveyId: surveyId })} + +
    + + ); + } else { const { - relateddata: {bodyshop, job}, - csiquestion: {config: csiquestions}, + relateddata: { bodyshop, job }, + csiquestion: { config: csiquestions } } = axiosResponse.csi_by_pk; return ( - - -
    -
    - {bodyshop.logo_img_path && bodyshop.logo_img_path.src ? ( - {bodyshop.shopname.concat("Logo")}) : null} -
    - - {bodyshop.shopname || ""} - - - {`${bodyshop.address1 || ""}${bodyshop.address2 ? ", " : ""}${ - bodyshop.address2 || "" - }`.trim()} - - - {`${bodyshop.city || ""}${ - bodyshop.city && bodyshop.state ? ", " : "" - }${bodyshop.state || ""} ${bodyshop.zip_post || ""}`.trim()} - -
    -
    - {t("csi.labels.title")} - {t("csi.labels.greeting", { - name: job.ownr_co_nm || job.ownr_fn || "", - })} - - {t("csi.labels.intro", { - shopname: - bodyshop.shopname || "" - })} - -
    - - {submitting.error ? ( - + +
    +
    + {bodyshop.logo_img_path && bodyshop.logo_img_path.src ? ( + {bodyshop.shopname.concat("Logo")} ) : null} +
    + + {bodyshop.shopname || ""} + + + {`${bodyshop.address1 || ""}${bodyshop.address2 ? ", " : ""}${bodyshop.address2 || ""}`.trim()} + + + {`${bodyshop.city || ""}${ + bodyshop.city && bodyshop.state ? ", " : "" + }${bodyshop.state || ""} ${bodyshop.zip_post || ""}`.trim()} + +
    +
    + {t("csi.labels.title")} + + {t("csi.labels.greeting", { + name: job.ownr_co_nm || job.ownr_fn || "" + })} + + + {t("csi.labels.intro", { + shopname: bodyshop.shopname || "" + })} + +
    - {submitting.submitted ? ( - - - - ) : ( - -
    - {axiosResponse.csi_by_pk.valid ? ( - <> - - - ) : ( - <> - - - {t("csi.successes.submittedsub")} - - - )} - -
    + {submitting.error ? : null} - )} - - {t("csi.labels.copyright")}{" "} - {t("csi.fields.surveyid", {surveyId: surveyId})} - -
    + {submitting.submitted ? ( + + + + ) : ( + +
    + {axiosResponse.csi_by_pk.valid ? ( + <> + + + + ) : ( + <> + + + {t("csi.successes.submittedsub")} + + + )} + +
    + )} + + {t("csi.labels.copyright")} {t("csi.fields.surveyid", { surveyId: surveyId })} + +
    ); - } + } } diff --git a/client/src/pages/dashboard/dashboard.container.jsx b/client/src/pages/dashboard/dashboard.container.jsx index 58cfc2c05..6094d678d 100644 --- a/client/src/pages/dashboard/dashboard.container.jsx +++ b/client/src/pages/dashboard/dashboard.container.jsx @@ -1,39 +1,44 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import DashboardGridComponent from "../../components/dashboard-grid/dashboard-grid.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function ExportsLogPageContainer({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); +export function ExportsLogPageContainer({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.dashboard", {app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} - ); - setSelectedHeader("dashboard"); - setBreadcrumbs([ - { - link: "/manage/accounting/exportlogs", - label: t("titles.bc.dashboard"), - }, - ]); - }, [setBreadcrumbs, t, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.dashboard", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("dashboard"); + setBreadcrumbs([ + { + link: "/manage/accounting/exportlogs", + label: t("titles.bc.dashboard") + } + ]); + }, [setBreadcrumbs, t, setSelectedHeader]); - return ( - - - - - - ); + return ( + + + + + + ); } export default connect(null, mapDispatchToProps)(ExportsLogPageContainer); diff --git a/client/src/pages/disclaimer/disclaimer.page.jsx b/client/src/pages/disclaimer/disclaimer.page.jsx index c56fc6728..c6315e3d4 100644 --- a/client/src/pages/disclaimer/disclaimer.page.jsx +++ b/client/src/pages/disclaimer/disclaimer.page.jsx @@ -5,22 +5,20 @@ import { useTranslation } from "react-i18next"; export default function AboutPage() { const { t } = useTranslation(); - return ( -
    - {`${InstanceRenderMgr({ - imex: t("titles.imexonline"), - rome: t("titles.romeonline"), - promanager: t("titles.promanager"), - })}Rome Online V.${import.meta.env.MODE}-${ - import.meta.env.VITE_APP_GIT_SHA - }`} - Third Party Notices - - Application - - - API - -
    - ); + return ( +
    + {`${InstanceRenderMgr({ + imex: t("titles.imexonline"), + rome: t("titles.romeonline"), + promanager: t("titles.promanager") + })}Rome Online V.${import.meta.env.MODE}-${import.meta.env.VITE_APP_GIT_SHA}`} + Third Party Notices + + Application + + + API + +
    + ); } diff --git a/client/src/pages/dms-payables/dms-payables.container.jsx b/client/src/pages/dms-payables/dms-payables.container.jsx index 398b85a46..d00feddf3 100644 --- a/client/src/pages/dms-payables/dms-payables.container.jsx +++ b/client/src/pages/dms-payables/dms-payables.container.jsx @@ -1,162 +1,162 @@ -import {Button, Card, Col, notification, Row, Select, Space} from "antd"; -import React, {useEffect, useRef, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import { Button, Card, Col, notification, Row, Select, Space } from "antd"; +import React, { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import SocketIO from "socket.io-client"; -import DmsAllocationsSummaryApComponent - from "../../components/dms-allocations-summary-ap/dms-allocations-summary-ap.component"; +import DmsAllocationsSummaryApComponent from "../../components/dms-allocations-summary-ap/dms-allocations-summary-ap.component"; import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component"; -import {auth} from "../../firebase/firebase.utils"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { auth } from "../../firebase/firebase.utils"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer); export const socket = SocketIO( - import.meta.env.PROD - ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL - : window.location.origin, + import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : window.location.origin, { path: "/ws", withCredentials: true, auth: async (callback) => { const token = auth.currentUser && (await auth.currentUser.getIdToken()); callback({ token }); - }, + } } ); -export function DmsContainer({bodyshop, setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); - const [logLevel, setLogLevel] = useState("DEBUG"); - const history = useNavigate(); - const [logs, setLogs] = useState([]); +export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); + const [logLevel, setLogLevel] = useState("DEBUG"); + const history = useNavigate(); + const [logs, setLogs] = useState([]); - const {state} = useLocation(); + const { state } = useLocation(); - const logsRef = useRef(null); + const logsRef = useRef(null); - useEffect(() => { - document.title = t("titles.dms", {app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("dms"); - setBreadcrumbs([ - { - link: "/manage/accounting/payables", - label: t("titles.bc.accounting-payables"), - }, - { - link: "/manage/dms", - label: t("titles.bc.dms"), - }, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.dms", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("dms"); + setBreadcrumbs([ + { + link: "/manage/accounting/payables", + label: t("titles.bc.accounting-payables") + }, + { + link: "/manage/dms", + label: t("titles.bc.dms") + } + ]); + }, [t, setBreadcrumbs, setSelectedHeader]); - useEffect(() => { - socket.on("connect", () => socket.emit("set-log-level", logLevel)); - socket.on("reconnect", () => { - setLogs((logs) => { - return [ - ...logs, - { - timestamp: new Date(), - level: "WARNING", - message: "Reconnected to CDK Export Service", - }, - ]; - }); - }); + useEffect(() => { + socket.on("connect", () => socket.emit("set-log-level", logLevel)); + socket.on("reconnect", () => { + setLogs((logs) => { + return [ + ...logs, + { + timestamp: new Date(), + level: "WARNING", + message: "Reconnected to CDK Export Service" + } + ]; + }); + }); - socket.on("log-event", (payload) => { - setLogs((logs) => { - return [...logs, payload]; - }); - }); + socket.on("log-event", (payload) => { + setLogs((logs) => { + return [...logs, payload]; + }); + }); - socket.on("ap-export-complete", (payload) => { - notification.open({ - type: "success", - message: t("jobs.labels.dms.apexported"), - }); - }); + socket.on("ap-export-complete", (payload) => { + notification.open({ + type: "success", + message: t("jobs.labels.dms.apexported") + }); + }); - if (socket.disconnected) socket.connect(); - return () => { - socket.removeAllListeners(); - socket.disconnect(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + if (socket.disconnected) socket.connect(); + return () => { + socket.removeAllListeners(); + socket.disconnect(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - if (!state?.billids) { - history(`/manage/accounting/payables`); - } + if (!state?.billids) { + history(`/manage/accounting/payables`); + } - return ( - -
    - - + return ( + + + + - -
    - - - -
    +
    + + + + - - } - > - - -
    - - - ); + socket.disconnect(); + socket.connect(); + }} + > + Reconnect + + + } + > + + + + + + ); } export const determineDmsType = (bodyshop) => { - if (bodyshop.cdk_dealerid) return "cdk"; - else { - return "pbs"; - } + if (bodyshop.cdk_dealerid) return "cdk"; + else { + return "pbs"; + } }; diff --git a/client/src/pages/dms/dms.container.jsx b/client/src/pages/dms/dms.container.jsx index fe0335658..504089fd8 100644 --- a/client/src/pages/dms/dms.container.jsx +++ b/client/src/pages/dms/dms.container.jsx @@ -1,11 +1,11 @@ -import {useQuery} from "@apollo/client"; -import {Button, Card, Col, notification, Result, Row, Select, Space,} from "antd"; +import { useQuery } from "@apollo/client"; +import { Button, Card, Col, notification, Result, Row, Select, Space } from "antd"; import queryString from "query-string"; -import React, {useEffect, useRef, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import SocketIO from "socket.io-client"; import AlertComponent from "../../components/alert/alert.component"; import DmsAllocationsSummary from "../../components/dms-allocations-summary/dms-allocations-summary.component"; @@ -13,213 +13,197 @@ import DmsCustomerSelector from "../../components/dms-customer-selector/dms-cust import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component"; import DmsPostForm from "../../components/dms-post-form/dms-post-form.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; -import {OwnerNameDisplayFunction} from "../../components/owner-name-display/owner-name-display.component"; -import {auth} from "../../firebase/firebase.utils"; -import {QUERY_JOB_EXPORT_DMS} from "../../graphql/jobs.queries"; -import { - insertAuditTrail, - setBreadcrumbs, - setSelectedHeader, -} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component"; +import { auth } from "../../firebase/firebase.utils"; +import { QUERY_JOB_EXPORT_DMS } from "../../graphql/jobs.queries"; +import { insertAuditTrail, setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), - insertAuditTrail: ({ jobid, operation, type }) => - dispatch(insertAuditTrail({ jobid, operation, type })), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer); export const socket = SocketIO( - import.meta.env.PROD - ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL - : "http://localhost:4000", // for dev testing, + import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "http://localhost:4000", // for dev testing, { path: "/ws", withCredentials: true, auth: async (callback) => { const token = auth.currentUser && (await auth.currentUser.getIdToken()); callback({ token }); - }, + } } ); -export function DmsContainer({ - bodyshop, - setBreadcrumbs, - setSelectedHeader, - insertAuditTrail, - }) { - const {t} = useTranslation(); - const [logLevel, setLogLevel] = useState("DEBUG"); - const history = useNavigate(); - const [logs, setLogs] = useState([]); - const search = queryString.parse(useLocation().search); - const {jobId} = search; +export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, insertAuditTrail }) { + const { t } = useTranslation(); + const [logLevel, setLogLevel] = useState("DEBUG"); + const history = useNavigate(); + const [logs, setLogs] = useState([]); + const search = queryString.parse(useLocation().search); + const { jobId } = search; - const {loading, error, data} = useQuery(QUERY_JOB_EXPORT_DMS, { - variables: {id: jobId}, - skip: !jobId, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", + const { loading, error, data } = useQuery(QUERY_JOB_EXPORT_DMS, { + variables: { id: jobId }, + skip: !jobId, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const logsRef = useRef(null); + + useEffect(() => { + document.title = t("titles.dms", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) }); - const logsRef = useRef(null); + setSelectedHeader("dms"); + setBreadcrumbs([ + { + link: "/manage/accounting/receivables", + label: t("titles.bc.accounting-receivables") + }, + { + link: "/manage/dms", + label: t("titles.bc.dms") + } + ]); + }, [t, setBreadcrumbs, setSelectedHeader]); - useEffect(() => { - document.title = t("titles.dms",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("dms"); - setBreadcrumbs([ - { - link: "/manage/accounting/receivables", - label: t("titles.bc.accounting-receivables"), - }, - { - link: "/manage/dms", - label: t("titles.bc.dms"), - }, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + socket.on("connect", () => socket.emit("set-log-level", logLevel)); + socket.on("reconnect", () => { + setLogs((logs) => { + return [ + ...logs, + { + timestamp: new Date(), + level: "WARNING", + message: "Reconnected to CDK Export Service" + } + ]; + }); + }); + socket.on("connect_error", (err) => { + console.log(`connect_error due to ${err}`, err); + notification.error({ message: err.message }); + }); + socket.on("log-event", (payload) => { + setLogs((logs) => { + return [...logs, payload]; + }); + }); + socket.on("export-success", (payload) => { + notification.success({ + message: t("jobs.successes.exported") + }); + insertAuditTrail({ + jobid: payload, + operation: AuditTrailMapping.jobexported(), + type: "jobexported" + }); + history("/manage/accounting/receivables"); + }); - useEffect(() => { - socket.on("connect", () => socket.emit("set-log-level", logLevel)); - socket.on("reconnect", () => { - setLogs((logs) => { - return [ - ...logs, - { - timestamp: new Date(), - level: "WARNING", - message: "Reconnected to CDK Export Service", - }, - ]; - }); - }); - socket.on("connect_error", (err) => { - console.log(`connect_error due to ${err}`, err); - notification.error({message: err.message}); - }); - socket.on("log-event", (payload) => { - setLogs((logs) => { - return [...logs, payload]; - }); - }); - socket.on("export-success", (payload) => { - notification.success({ - message: t("jobs.successes.exported"), - }); - insertAuditTrail({ - jobid: payload, - operation: AuditTrailMapping.jobexported(), - type: "jobexported",}); - history("/manage/accounting/receivables"); - }); + if (socket.disconnected) socket.connect(); + return () => { + socket.removeAllListeners(); + socket.disconnect(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - if (socket.disconnected) socket.connect(); - return () => { - socket.removeAllListeners(); - socket.disconnect(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + if (loading) return ; + if (error) return ; - if (loading) return ; - if (error) return ; + if (!jobId || !(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) || !(data && data.jobs_by_pk)) + return ; - if ( - !jobId || - !(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) || - !(data && data.jobs_by_pk) - ) - return ; + if (data.jobs_by_pk && data.jobs_by_pk.date_exported) + return ; - if (data.jobs_by_pk && data.jobs_by_pk.date_exported) - return ; - - return ( -
    - -
    - + return ( +
    + +
    + {`${ - data && data.jobs_by_pk && data.jobs_by_pk.ro_number + data && data.jobs_by_pk && data.jobs_by_pk.ro_number }`} - {` | ${OwnerNameDisplayFunction(data.jobs_by_pk)} | ${ - data.jobs_by_pk.v_model_yr || "" - } ${data.jobs_by_pk.v_make_desc || ""} ${ - data.jobs_by_pk.v_model_desc || "" - }`} + {` | ${OwnerNameDisplayFunction(data.jobs_by_pk)} | ${ + data.jobs_by_pk.v_model_yr || "" + } ${data.jobs_by_pk.v_make_desc || ""} ${data.jobs_by_pk.v_model_desc || ""}`} - } - socket={socket} - jobId={jobId} - /> - - - - + } + socket={socket} + jobId={jobId} + /> + + + + - + - -
    - - - - - - } - > - - -
    - - - - ); + +
    + + + + + + } + > + + +
    + + + + ); } export const determineDmsType = (bodyshop) => { - if (bodyshop.cdk_dealerid) return "cdk"; - else { - return "pbs"; - } + if (bodyshop.cdk_dealerid) return "cdk"; + else { + return "pbs"; + } }; diff --git a/client/src/pages/export-logs/export-logs.page.component.jsx b/client/src/pages/export-logs/export-logs.page.component.jsx index 66fca6317..fce432d56 100644 --- a/client/src/pages/export-logs/export-logs.page.component.jsx +++ b/client/src/pages/export-logs/export-logs.page.component.jsx @@ -1,168 +1,145 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {useQuery} from "@apollo/client"; -import {Button, Card, Checkbox, Input, Space, Table, Typography} from "antd"; +import { SyncOutlined } from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd"; import _ from "lodash"; import queryString from "query-string"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import AlertComponent from "../../components/alert/alert.component"; -import {QUERY_EXPORT_LOG_PAGINATED} from "../../graphql/accounting.queries"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {DateTimeFormatter} from "../../utils/DateFormatter"; +import { QUERY_EXPORT_LOG_PAGINATED } from "../../graphql/accounting.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; import { pageLimit } from "../../utils/config"; import { alphaSort, dateSort } from "./../../utils/sorters"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); -export function ExportLogsPageComponent({bodyshop}) { - const searchParams = queryString.parse(useLocation().search); - const {page, sortcolumn, sortorder, search} = searchParams; - const history = useNavigate(); +export function ExportLogsPageComponent({ bodyshop }) { + const searchParams = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder, search } = searchParams; + const history = useNavigate(); - const {loading, error, data, refetch} = useQuery( - QUERY_EXPORT_LOG_PAGINATED, + const { loading, error, data, refetch } = useQuery(QUERY_EXPORT_LOG_PAGINATED, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + search: search || "", + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, + order: [ { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - variables: { - search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, - order: [ - { - ...(sortcolumn === "ro_number" - ? { - job: { - [sortcolumn|| "created_at"]: sortorder - ? sortorder === "descend" - ? "desc" - : "asc" - : "desc",}, + ...(sortcolumn === "ro_number" + ? { + job: { + [sortcolumn || "created_at"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc" } - : sortcolumn === "invoice_number" + } + : sortcolumn === "invoice_number" ? { bill: { - [sortcolumn || "created_at"]: sortorder - ? sortorder === "descend" - ? "desc" - : "asc" - : "desc", - }, + [sortcolumn || "created_at"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc" + } } : sortcolumn === "paymentnum" - ? { - payment: { - [sortcolumn || "created_at"]: sortorder - ? sortorder === "descend" - ? "desc" - : "asc" - : "desc", - }, - } - : { - [sortcolumn || "created_at"]: sortorder - ? sortorder === "descend" - ? "desc" - : "asc" - : "desc", - }), - }, - ], - }, + ? { + payment: { + [sortcolumn || "created_at"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc" + } + } + : { + [sortcolumn || "created_at"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc" + }) } - ); + ] + } + }); - const {t} = useTranslation(); + const { t } = useTranslation(); - if (error) return ; + if (error) return ; - const handleTableChange = (pagination, filters, sorter) => { - searchParams.page = pagination.current; - searchParams.sortcolumn = sorter.columnKey; - searchParams.sortorder = sorter.order; - if (filters.status) { - searchParams.statusFilters = JSON.stringify( - _.flattenDeep(filters.status) - ); - } else { - delete searchParams.statusFilters; - } - history({search: queryString.stringify(searchParams)}); - }; + const handleTableChange = (pagination, filters, sorter) => { + searchParams.page = pagination.current; + searchParams.sortcolumn = sorter.columnKey; + searchParams.sortorder = sorter.order; + if (filters.status) { + searchParams.statusFilters = JSON.stringify(_.flattenDeep(filters.status)); + } else { + delete searchParams.statusFilters; + } + history({ search: queryString.stringify(searchParams) }); + }; - const columns = [ - { - title: t("general.labels.created_at"), - dataIndex: "created_at", - key: "created_at", - sorter: (a, b) => dateSort(a.created_at, b.created_at), - sortOrder: sortcolumn === "created_at" && sortorder,render: (text, record) => ( - {record.created_at} - ), - }, - { - title: t("employees.fields.user_email"), - dataIndex: "useremail", - key: "useremail", - }, - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", -sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + const columns = [ + { + title: t("general.labels.created_at"), + dataIndex: "created_at", + key: "created_at", + sorter: (a, b) => dateSort(a.created_at, b.created_at), + sortOrder: sortcolumn === "created_at" && sortorder, + render: (text, record) => {record.created_at} + }, + { + title: t("employees.fields.user_email"), + dataIndex: "useremail", + key: "useremail" + }, + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), sortOrder: sortcolumn === "ro_number" && sortorder, - render: (text, record) => - record.job && ( - - {(record.job && record.job.ro_number) || t("general.labels.na")} - - ), - }, - { - title: t("bills.fields.invoice_number"), - dataIndex: "invoice_number", - key: "invoice_number", - sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), - sortOrder: sortcolumn === "invoice_number" && sortorder,render: (text, record) => - record.bill && ( - - {record.bill && record.bill.invoice_number} - - ), - }, - { - title: t("payments.fields.paymentnum"), - dataIndex: "paymentnum", - key: "paymentnum",sorter: (a, b) => alphaSort(a.paymentnum, b.paymentnum), + render: (text, record) => + record.job && ( + + {(record.job && record.job.ro_number) || t("general.labels.na")} + + ) + }, + { + title: t("bills.fields.invoice_number"), + dataIndex: "invoice_number", + key: "invoice_number", + sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), + sortOrder: sortcolumn === "invoice_number" && sortorder, + render: (text, record) => + record.bill && ( + + {record.bill && record.bill.invoice_number} + + ) + }, + { + title: t("payments.fields.paymentnum"), + dataIndex: "paymentnum", + key: "paymentnum", + sorter: (a, b) => alphaSort(a.paymentnum, b.paymentnum), sortOrder: sortcolumn === "paymentnum" && sortorder, - render: (text, record) => - record.payment && ( - Number(a.successful) - Number(b.successful), + render: (text, record) => + record.payment && ( + + {record.payment && record.payment.paymentnum} + + ) + }, + { + title: t("general.labels.successful"), + dataIndex: "successful", + key: "successful", + sorter: (a, b) => Number(a.successful) - Number(b.successful), sortOrder: sortcolumn === "successful" && sortorder, filters: [ { text: "True", value: true }, - { text: "False", value: false }, + { text: "False", value: false } ], onFilter: (value, record) => record.successful === value, - render: (text, record) => , + render: (text, record) => }, { title: t("general.labels.message"), @@ -177,61 +154,61 @@ sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), ))} - ), - }, + ) + } ]; - return ( - - {searchParams.search && ( - <> - - {t("general.labels.searchresults", { - search: searchParams.search, - })} - - - - )} - - { - searchParams.search = value; - history({search: queryString.stringify(searchParams)}); - }} - /> - - } - > -
    + {searchParams.search && ( + <> + + {t("general.labels.searchresults", { + search: searchParams.search + })} + + + + )} + + { + searchParams.search = value; + history({ search: queryString.stringify(searchParams) }); + }} + /> + + } + > +
    + + ); } export default connect(mapStateToProps, null)(ExportLogsPageComponent); diff --git a/client/src/pages/export-logs/export-logs.page.container.jsx b/client/src/pages/export-logs/export-logs.page.container.jsx index 436b4f45b..885577b95 100644 --- a/client/src/pages/export-logs/export-logs.page.container.jsx +++ b/client/src/pages/export-logs/export-logs.page.container.jsx @@ -1,38 +1,44 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import ExportLogsPage from "./export-logs.page.component"; import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function ExportsLogPageContainer({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); +export function ExportsLogPageContainer({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.export-logs",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("export-logs"); - setBreadcrumbs([ - { - link: "/manage/accounting/exportlogs", - label: t("titles.bc.export-logs"), - }, - ]); - }, [setBreadcrumbs, t, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.export-logs", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("export-logs"); + setBreadcrumbs([ + { + link: "/manage/accounting/exportlogs", + label: t("titles.bc.export-logs") + } + ]); + }, [setBreadcrumbs, t, setSelectedHeader]); - return ( - - - - - - ); + return ( + + + + + + ); } export default connect(null, mapDispatchToProps)(ExportsLogPageContainer); diff --git a/client/src/pages/help/help.page.jsx b/client/src/pages/help/help.page.jsx index 78df4e288..ef9e269e8 100644 --- a/client/src/pages/help/help.page.jsx +++ b/client/src/pages/help/help.page.jsx @@ -2,9 +2,9 @@ import React from "react"; import HelpRescue from "../../components/help-rescue/help-rescue.component"; export default function HelpPage() { - return ( -
    - -
    - ); + return ( +
    + +
    + ); } diff --git a/client/src/pages/inventory/inventory.page.jsx b/client/src/pages/inventory/inventory.page.jsx index 8ad5ee04e..a6a1f128c 100644 --- a/client/src/pages/inventory/inventory.page.jsx +++ b/client/src/pages/inventory/inventory.page.jsx @@ -1,32 +1,38 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import InventoryList from "../../components/inventory-list/inventory-list.container"; import InventoryUpsertModalContainer from "../../components/inventory-upsert-modal/inventory-upsert-modal.container"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function InventoryPage({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); +export function InventoryPage({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.inventory",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("inventory"); - setBreadcrumbs([{link: "/manage/jobs", label: t("titles.bc.inventory")}]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.inventory", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("inventory"); + setBreadcrumbs([{ link: "/manage/jobs", label: t("titles.bc.inventory") }]); + }, [t, setBreadcrumbs, setSelectedHeader]); - return ( - - - - - ); + return ( + + + + + ); } export default connect(null, mapDispatchToProps)(InventoryPage); diff --git a/client/src/pages/jobs-admin/jobs-admin.page.jsx b/client/src/pages/jobs-admin/jobs-admin.page.jsx index 905de9173..03d726efd 100644 --- a/client/src/pages/jobs-admin/jobs-admin.page.jsx +++ b/client/src/pages/jobs-admin/jobs-admin.page.jsx @@ -1,9 +1,9 @@ -import {useQuery} from "@apollo/client"; -import {Card, Col, Result, Row, Space, Typography} from "antd"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useParams} from "react-router-dom"; +import { useQuery } from "@apollo/client"; +import { Card, Col, Result, Row, Space, Typography } from "antd"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useParams } from "react-router-dom"; import AlertComponent from "../../components/alert/alert.component"; import JobCalculateTotals from "../../components/job-calculate-totals/job-calculate-totals.component"; import ScoreboardAddButton from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component"; @@ -12,127 +12,121 @@ import JobsAdminClass from "../../components/jobs-admin-class/jobs-admin-class.c import JobsAdminDatesChange from "../../components/jobs-admin-dates/jobs-admin-dates.component"; import JobsAdminDeleteIntake from "../../components/jobs-admin-delete-intake/jobs-admin-delete-intake.component"; import JobsAdminMarkReexport from "../../components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component"; -import JobAdminOwnerReassociate - from "../../components/jobs-admin-owner-reassociate/jobs-admin-owner-reassociate.component"; +import JobAdminOwnerReassociate from "../../components/jobs-admin-owner-reassociate/jobs-admin-owner-reassociate.component"; import JobsAdminUnvoid from "../../components/jobs-admin-unvoid/jobs-admin-unvoid.component"; -import JobAdminVehicleReassociate - from "../../components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component"; +import JobAdminVehicleReassociate from "../../components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component"; import JobsAdminRemoveAR from "../../components/jobs-admin-remove-ar/jobs-admin-remove-ar.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import NotFound from "../../components/not-found/not-found.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {GET_JOB_BY_PK} from "../../graphql/jobs.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { GET_JOB_BY_PK } from "../../graphql/jobs.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); const colSpan = { - sm: {span: 24}, - md: {span: 12}, - lg: {span: 8}, - xl: {span: 6}, + sm: { span: 24 }, + md: { span: 12 }, + lg: { span: 8 }, + xl: { span: 6 } }; const cardStyle = { - height: "100%", + height: "100%" }; -export function JobsCloseContainer({setBreadcrumbs, setSelectedHeader}) { - const {jobId} = useParams(); - const {loading, error, data} = useQuery(GET_JOB_BY_PK, { - variables: {id: jobId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", +export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) { + const { jobId } = useParams(); + const { loading, error, data } = useQuery(GET_JOB_BY_PK, { + variables: { id: jobId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const { t } = useTranslation(); + useEffect(() => { + setSelectedHeader("activejobs"); + document.title = t("titles.jobs-admin", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }), + ro_number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null }); - const {t} = useTranslation(); - useEffect(() => { - setSelectedHeader("activejobs"); - document.title = t("titles.jobs-admin", { - app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'}), - ro_number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null, - }); - setBreadcrumbs([ - { - link: `/manage/jobs/`, - label: t("titles.bc.jobs"), - }, - { - link: `/manage/jobs/${jobId}/`, - label: t("titles.bc.jobs-detail", { - number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null, - }), - }, - { - link: `/manage/jobs/${jobId}/admin`, - label: t("titles.bc.jobs-admin"), - }, - ]); - }, [setBreadcrumbs, t, jobId, data, setSelectedHeader]); + setBreadcrumbs([ + { + link: `/manage/jobs/`, + label: t("titles.bc.jobs") + }, + { + link: `/manage/jobs/${jobId}/`, + label: t("titles.bc.jobs-detail", { + number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null + }) + }, + { + link: `/manage/jobs/${jobId}/admin`, + label: t("titles.bc.jobs-admin") + } + ]); + }, [setBreadcrumbs, t, jobId, data, setSelectedHeader]); - if (loading) return ; - if (error) return ; - if (!!!data.jobs_by_pk) return ; - if (!data.jobs_by_pk.job_totals) - return ( - } - /> - ); + if (loading) return ; + if (error) return ; + if (!!!data.jobs_by_pk) return ; + if (!data.jobs_by_pk.job_totals) + return } />; - return ( - - - {t("jobs.labels.adminwarning")} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + return ( + + + {t("jobs.labels.adminwarning")} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - ); + + + + + + + + ); } export default connect(null, mapDispatchToProps)(JobsCloseContainer); diff --git a/client/src/pages/jobs-all/jobs-all.container.jsx b/client/src/pages/jobs-all/jobs-all.container.jsx index 0cee5e40c..88adc7921 100644 --- a/client/src/pages/jobs-all/jobs-all.container.jsx +++ b/client/src/pages/jobs-all/jobs-all.container.jsx @@ -1,75 +1,72 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import queryString from "query-string"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import AlertComponent from "../../components/alert/alert.component"; import JobsListPaginated from "../../components/jobs-list-paginated/jobs-list-paginated.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED} from "../../graphql/jobs.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {pageLimit} from "../../utils/config"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED } from "../../graphql/jobs.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { pageLimit } from "../../utils/config"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - //bodyshop: selectBodyshop, + //bodyshop: selectBodyshop, }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function AllJobs({setBreadcrumbs, setSelectedHeader}) { - const searchParams = queryString.parse(useLocation().search); - const {page, sortcolumn, sortorder, statusFilters} = searchParams; +export function AllJobs({ setBreadcrumbs, setSelectedHeader }) { + const searchParams = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder, statusFilters } = searchParams; - const {loading, error, data, refetch} = useQuery( - QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED, + const { loading, error, data, refetch } = useQuery(QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, + ...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}), + order: [ { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - variables: { - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, - ...(statusFilters ? {statusList: JSON.parse(statusFilters)} : {}), - order: [ - { - [sortcolumn || "ro_number"]: - sortorder && sortorder !== "false" - ? (sortorder === "descend" - ? "desc" - : "asc") - : "desc", - }, - ], - }, + [sortcolumn || "ro_number"]: + sortorder && sortorder !== "false" ? (sortorder === "descend" ? "desc" : "asc") : "desc" } - ); - const {t} = useTranslation(); + ] + } + }); + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.jobs-all", {app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("alljobs"); - setBreadcrumbs([ - {link: "/manage/jobs/all", label: t("titles.bc.jobs-all")}, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.jobs-all", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("alljobs"); + setBreadcrumbs([{ link: "/manage/jobs/all", label: t("titles.bc.jobs-all") }]); + }, [t, setBreadcrumbs, setSelectedHeader]); - if (error) return ; - return ( - - - - ); + if (error) return ; + return ( + + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(AllJobs); diff --git a/client/src/pages/jobs-available/jobs-available.page.container.jsx b/client/src/pages/jobs-available/jobs-available.page.container.jsx index 6ba22f9c5..ebe58cd8d 100644 --- a/client/src/pages/jobs-available/jobs-available.page.container.jsx +++ b/client/src/pages/jobs-available/jobs-available.page.container.jsx @@ -1,66 +1,69 @@ -import {Button} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import { Button } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import AlertComponent from "../../components/alert/alert.component"; import JobsAvailableTableContainer from "../../components/jobs-available-table/jobs-available-table.container"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectPartnerVersion} from "../../redux/application/application.selectors"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectPartnerVersion } from "../../redux/application/application.selectors"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - partnerVersion: selectPartnerVersion, + partnerVersion: selectPartnerVersion }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function JobsAvailablePageContainer({ - partnerVersion, - setBreadcrumbs, - setSelectedHeader, - }) { - const {t} = useTranslation(); +export function JobsAvailablePageContainer({ partnerVersion, setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.jobsavailable", {app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("availablejobs"); - setBreadcrumbs([ - {link: "/manage/available", label: t("titles.bc.availablejobs")}, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.jobsavailable", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("availablejobs"); + setBreadcrumbs([{ link: "/manage/available", label: t("titles.bc.availablejobs") }]); + }, [t, setBreadcrumbs, setSelectedHeader]); - return ( - -
    - - - - } - /> - {!partnerVersion && ( - - )} - -
    -
    - ); + return ( + +
    + + + + } + /> + {!partnerVersion && ( + + )} + +
    +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsAvailablePageContainer); +export default connect(mapStateToProps, mapDispatchToProps)(JobsAvailablePageContainer); diff --git a/client/src/pages/jobs-checklist-view/jobs-checklist-view.page.jsx b/client/src/pages/jobs-checklist-view/jobs-checklist-view.page.jsx index bd310ce6c..f25bace38 100644 --- a/client/src/pages/jobs-checklist-view/jobs-checklist-view.page.jsx +++ b/client/src/pages/jobs-checklist-view/jobs-checklist-view.page.jsx @@ -1,126 +1,112 @@ -import {useQuery} from "@apollo/client"; -import {Col, Row, Typography} from "antd"; +import { useQuery } from "@apollo/client"; +import { Col, Row, Typography } from "antd"; import dayjs from "../../utils/day"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useParams} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useParams } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import AlertComponent from "../../components/alert/alert.component"; -import JobChecklistForm - from "../../components/job-checklist/components/job-checklist-form/job-checklist-form.component"; +import JobChecklistForm from "../../components/job-checklist/components/job-checklist-form/job-checklist-form.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {QUERY_JOB_CHECKLISTS} from "../../graphql/jobs.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { QUERY_JOB_CHECKLISTS } from "../../graphql/jobs.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function JobsChecklistViewContainer({ - bodyshop, - setBreadcrumbs, - setSelectedHeader, - }) { - const {t} = useTranslation(); - const {jobId} = useParams(); - const {loading, error, data} = useQuery(QUERY_JOB_CHECKLISTS, { - variables: {id: jobId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", +export function JobsChecklistViewContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); + const { jobId } = useParams(); + const { loading, error, data } = useQuery(QUERY_JOB_CHECKLISTS, { + variables: { id: jobId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + + useEffect(() => { + document.title = t("titles.jobs-checklist", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) }); + setSelectedHeader("activejobs"); + setBreadcrumbs([ + { link: "/manage/jobs", label: t("titles.bc.jobs") }, + { + link: `/manage/jobs/${jobId}`, + label: t("titles.bc.jobs-detail", { + number: (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || "" + }) + }, + { + link: `/manage/jobs/${jobId}/checklist`, + label: t("titles.bc.jobs-checklist") + } + ]); + }, [t, setBreadcrumbs, jobId, data, setSelectedHeader]); - useEffect(() => { - document.title = t("titles.jobs-checklist",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("activejobs"); - setBreadcrumbs([ - {link: "/manage/jobs", label: t("titles.bc.jobs")}, - { - link: `/manage/jobs/${jobId}`, - label: t("titles.bc.jobs-detail", { - number: (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || "", - }), - }, - { - link: `/manage/jobs/${jobId}/checklist`, - label: t("titles.bc.jobs-checklist"), - }, - ]); - }, [t, setBreadcrumbs, jobId, data, setSelectedHeader]); + if (loading) return ; + if (error) return ; - if (loading) return ; - if (error) return ; + //The Form is the actual config to use. - //The Form is the actual config to use. + const CompletedBy = ({ checklist }) => ( +
    +
    + {t("jobs.labels.checklistcompletedby", { + by: checklist.completed_by, + at: dayjs(checklist.completed_at).format("MM/DD/YYYY @ h:mm a") + })} +
    +
    + ); - const CompletedBy = ({checklist}) => ( -
    -
    - {t("jobs.labels.checklistcompletedby", { - by: checklist.completed_by, - at: dayjs(checklist.completed_at).format("MM/DD/YYYY @ h:mm a"), - })} -
    -
    - ); - - return ( - - -
    - - {t("jobs.labels.intakechecklist")} - - {data.jobs_by_pk.intakechecklist && - data.jobs_by_pk.intakechecklist.form && ( - <> - - - - )} - - - - {t("jobs.labels.deliverchecklist")} - - {data.jobs_by_pk.deliverchecklist && - data.jobs_by_pk.deliverchecklist.form && ( - <> - - - - )} - - - - ); + return ( + + + + {t("jobs.labels.intakechecklist")} + {data.jobs_by_pk.intakechecklist && data.jobs_by_pk.intakechecklist.form && ( + <> + + + + )} + + + {t("jobs.labels.deliverchecklist")} + {data.jobs_by_pk.deliverchecklist && data.jobs_by_pk.deliverchecklist.form && ( + <> + + + + )} + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsChecklistViewContainer); +export default connect(mapStateToProps, mapDispatchToProps)(JobsChecklistViewContainer); diff --git a/client/src/pages/jobs-close/jobs-close.component.jsx b/client/src/pages/jobs-close/jobs-close.component.jsx index e01138250..0554f6a6e 100644 --- a/client/src/pages/jobs-close/jobs-close.component.jsx +++ b/client/src/pages/jobs-close/jobs-close.component.jsx @@ -1,33 +1,33 @@ -import {DeleteFilled} from "@ant-design/icons"; -import {useApolloClient, useMutation} from "@apollo/client"; +import { DeleteFilled } from "@ant-design/icons"; +import { useApolloClient, useMutation } from "@apollo/client"; import { - Alert, - Button, - Col, - Divider, - Form, - Input, - InputNumber, - notification, - Popconfirm, - Row, - Select, - Space, - Statistic, - Switch, - Typography, + Alert, + Button, + Col, + Divider, + Form, + Input, + InputNumber, + notification, + Popconfirm, + Row, + Select, + Space, + Statistic, + Switch, + Typography } from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import { PageHeader } from "@ant-design/pro-layout"; -import React, {useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; // import { useNavigate } from 'react-router-dom'; -import {useSplitTreatments} from "@splitsoftware/splitio-react"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; import Dinero from "dinero.js"; import dayjs from "../../utils/day"; -import {Link} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import DateTimePicker from "../../components/form-date-time-picker/form-date-time-picker.component"; import FormsFieldChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component"; import CurrencyInput from "../../components/form-items-formatted/currency-form-item.component"; @@ -35,510 +35,450 @@ import JobsScoreboardAdd from "../../components/job-scoreboard-add-button/job-sc import JobsCloseAutoAllocate from "../../components/jobs-close-auto-allocate/jobs-close-auto-allocate.component"; import JobsCloseLines from "../../components/jobs-close-lines/jobs-close-lines.component"; import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component"; -import {generateJobLinesUpdatesForInvoicing} from "../../graphql/jobs-lines.queries"; -import {UPDATE_JOB} from "../../graphql/jobs.queries"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries"; +import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - jobRO: selectJobReadOnly, + bodyshop: selectBodyshop, + jobRO: selectJobReadOnly }); const mapDispatchToProps = (dispatch) => ({ - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export function JobsCloseComponent({job, bodyshop, jobRO, insertAuditTrail}) { - const {t} = useTranslation(); - const [form] = Form.useForm(); - const client = useApolloClient(); - // const history = useNavigate(); - const [closeJob] = useMutation(UPDATE_JOB); - const [loading, setLoading] = useState(false); +export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) { + const { t } = useTranslation(); + const [form] = Form.useForm(); + const client = useApolloClient(); + // const history = useNavigate(); + const [closeJob] = useMutation(UPDATE_JOB); + const [loading, setLoading] = useState(false); - const {treatments: {Qb_Multi_Ar, ClosingPeriod}} = useSplitTreatments({ - attributes: {}, - names: ["Qb_Multi_Ar", "ClosingPeriod"], - splitKey: bodyshop && bodyshop.imexshopid, + const { + treatments: { Qb_Multi_Ar, ClosingPeriod } + } = useSplitTreatments({ + attributes: {}, + names: ["Qb_Multi_Ar", "ClosingPeriod"], + splitKey: bodyshop && bodyshop.imexshopid + }); + + const handleFinish = async ({ removefromproduction, ...values }) => { + setLoading(true); + const result = await client.mutate({ + mutation: generateJobLinesUpdatesForInvoicing(values.joblines) + }); + if (result.errors) { + return; // Abandon the rest of the close. + } + + const closeResult = await closeJob({ + variables: { + jobId: job.id, + job: { + status: bodyshop.md_ro_statuses.default_invoiced || "", + date_invoiced: values.date_invoiced, + actual_in: values.actual_in, + actual_completion: values.actual_completion, + actual_delivery: values.actual_delivery, + kmin: values.kmin, + kmout: values.kmout, + dms_allocation: values.dms_allocation, + ...(removefromproduction ? { inproduction: false } : {}), + ...(values.qb_multiple_payers ? { qb_multiple_payers: values.qb_multiple_payers } : {}) + } + }, + refetchQueries: ["QUERY_JOB_CLOSE_DETAILS"], + awaitRefetchQueries: true }); - const handleFinish = async ({removefromproduction, ...values}) => { - setLoading(true); - const result = await client.mutate({ - mutation: generateJobLinesUpdatesForInvoicing(values.joblines), - }); - if (result.errors) { - return; // Abandon the rest of the close. - } + if (!result.errors) { + // notification["success"]({ message: t("jobs.successes.save") }); + // form.resetFields(); + } else { + notification["error"]({ + message: t("job.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + return; // Abandon the rest of the close. + } - const closeResult = await closeJob({ - variables: { - jobId: job.id, - job: { - status: bodyshop.md_ro_statuses.default_invoiced || "", - date_invoiced: values.date_invoiced, - actual_in: values.actual_in, - actual_completion: values.actual_completion, - actual_delivery: values.actual_delivery, - kmin: values.kmin, - kmout: values.kmout, - dms_allocation: values.dms_allocation, - ...(removefromproduction ? {inproduction: false} : {}), - ...(values.qb_multiple_payers - ? {qb_multiple_payers: values.qb_multiple_payers} - : {}), - }, - }, - refetchQueries: ["QUERY_JOB_CLOSE_DETAILS"], - awaitRefetchQueries: true, - }); + if (!closeResult.errors) { + setLoading(false); - if (!result.errors) { - // notification["success"]({ message: t("jobs.successes.save") }); - // form.resetFields(); - } else { - notification["error"]({ - message: t("job.errors.saving", { - error: JSON.stringify(result.errors), - }), - }); - return; // Abandon the rest of the close. - } + notification["success"]({ + message: t("jobs.successes.closed") + }); + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.jobinvoiced(), + type: "jobinvoiced" + }); + // history(`/manage/jobs/${job.id}`); + } else { + setLoading(false); + notification["error"]({ + message: t("job.errors.closing", { + error: JSON.stringify(closeResult.errors) + }) + }); + } + form.resetFields(); + form.resetFields(); - if (!closeResult.errors) { - setLoading(false); + setLoading(false); + }; - notification["success"]({ - message: t("jobs.successes.closed"), - }); - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.jobinvoiced(), - type: "jobinvoiced",}); - // history(`/manage/jobs/${job.id}`); - } else { - setLoading(false); - notification["error"]({ - message: t("job.errors.closing", { - error: JSON.stringify(closeResult.errors), - }), - }); - } - form.resetFields(); - form.resetFields(); + return ( +
    +
    + + - setLoading(false); - }; + form.submit()} + disabled={jobRO} + okText={t("general.labels.yes")} + cancelText={t("general.labels.no")} + title={t("jobs.labels.closeconfirm")} + > + + + {(bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid) && ( + + + + )} + + + } + /> - return ( -
    - - - - - form.submit()} - disabled={jobRO} - okText={t("general.labels.yes")} - cancelText={t("general.labels.no")} - title={t("jobs.labels.closeconfirm")} - > - - - {(bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid) && ( - - - - )} - - + + + {!job.actual_in && job.scheduled_in && } + {!job.actual_completion && job.scheduled_completion && ( + + )} + {!job.actual_delivery && job.scheduled_delivery && ( + + )} + + + + + + + {() => { + return ( + + + + ); + }} + + + + + ({ + validator(_, value) { + if (!bodyshop.cdk_dealerid) return Promise.resolve(); + if (!value || dayjs(value).isSameOrAfter(dayjs(), "day")) { + return Promise.resolve(); + } + return Promise.reject(new Error(t("jobs.labels.dms.invoicedatefuture"))); + } + }), + ({ getFieldValue }) => ({ + validator(_, value) { + if (ClosingPeriod.treatment === "on" && bodyshop.accountingconfig.ClosingPeriod) { + if ( + dayjs(value).isSameOrAfter(dayjs(bodyshop.accountingconfig.ClosingPeriod[0]).startOf("day")) && + dayjs(value).isSameOrBefore(dayjs(bodyshop.accountingconfig.ClosingPeriod[1]).endOf("day")) + ) { + return Promise.resolve(); + } else { + return Promise.reject(new Error(t("jobs.labels.closingperiod"))); + } + } else { + return Promise.resolve(); + } + } + }) + ]} + > + + + {!jobRO && job.inproduction && ( + + + + )} + {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( + + + + )} + {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( + ({ + validator(_, value) { + if (!value || getFieldValue("kmin") <= value) { + return Promise.resolve(); } - /> - - - {!job.actual_in && job.scheduled_in && ( - - )} - {!job.actual_completion && job.scheduled_completion && ( - - )} - {!job.actual_delivery && job.scheduled_delivery && ( - - )} - - - - - - - {() => { - return ( - - - + return Promise.reject(new Error(t("jobs.labels.dms.kmoutnotgreaterthankmin"))); + } + }) + ]} + > + + + )} + {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( + + + + )} + + + {Qb_Multi_Ar.treatment === "on" && ( + <> + {t("jobs.labels.multipayers")} + +
    + ({ + validator(_, value) { + let totalAllocated = Dinero(); + + const payers = form.getFieldValue("qb_multiple_payers"); + payers && + payers.forEach((payer) => { + totalAllocated = totalAllocated.add( + Dinero({ + amount: Math.round((payer?.amount || 0) * 100) + }) ); - }} - - - - - ({ - validator(_, value) { - if (!bodyshop.cdk_dealerid) return Promise.resolve(); - if (!value || dayjs(value).isSameOrAfter(dayjs(), "day")) { - return Promise.resolve(); - } - return Promise.reject( - new Error(t("jobs.labels.dms.invoicedatefuture")) - ); - }, - }), - ({getFieldValue}) => ({ - validator(_, value) { - if ( - ClosingPeriod.treatment === "on" && - bodyshop.accountingconfig.ClosingPeriod - ) { - if ( - dayjs(value).isSameOrAfter( - dayjs( - bodyshop.accountingconfig.ClosingPeriod[0] - ).startOf("day") - ) && - dayjs(value).isSameOrBefore( - dayjs( - bodyshop.accountingconfig.ClosingPeriod[1] - ).endOf("day") - ) - ) { - return Promise.resolve(); - } else { - return Promise.reject( - new Error(t("jobs.labels.closingperiod")) - ); - } - } else { - return Promise.resolve(); - } - }, - }), - ]} - > - - - {!jobRO && job.inproduction && ( - - - - )} - {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( - - - - )} - {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( - ({ - validator(_, value) { - if (!value || getFieldValue("kmin") <= value) { - return Promise.resolve(); - } - - return Promise.reject( - new Error(t("jobs.labels.dms.kmoutnotgreaterthankmin")) - ); - }, - }), - ]} - > - - - )} - {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( - - - - )} - - - {Qb_Multi_Ar.treatment === "on" && ( - <>{t("jobs.labels.multipayers")} - - - = 0 + ? Promise.resolve() + : Promise.reject(new Error(t("jobs.labels.additionalpayeroverallocation"))); + } + }) + ]} + > + {(fields, { add, remove }) => { + return ( +
    + {fields.map((field, index) => ( + + + ({ - validator(_, value) { - let totalAllocated = Dinero(); - - const payers = form.getFieldValue("qb_multiple_payers"); - payers && - payers.forEach((payer) => { - totalAllocated = totalAllocated.add( - Dinero({ - amount: Math.round((payer?.amount || 0) * 100), - }) - ); - }); - const discrep = job.job_totals - ? Dinero(job.job_totals.totals.total_repairs).subtract( - totalAllocated - ) - : Dinero(); - return discrep.getAmount() >= 0 - ? Promise.resolve() - : Promise.reject( - new Error( - t("jobs.labels.additionalpayeroverallocation") - ) - ); - }, - }), + { + required: true + } ]} - > - {(fields, {add, remove}) => { - return ( -
    - {fields.map((field, index) => ( - - - - - + > + + - - - + + + - { - if(!jobRO){ - remove(field.name); - } - }} - /> - - - ))} - - - -
    - ); + { + if (!jobRO) { + remove(field.name); + } }} - - -
    - - {() => { - //Perform Calculation to determine discrepancy. - let totalAllocated = Dinero(); + /> + + + ))} + + + + + ); + }} + + + + + {() => { + //Perform Calculation to determine discrepancy. + let totalAllocated = Dinero(); - const payers = form.getFieldValue("qb_multiple_payers"); - payers && - payers.forEach((payer) => { - totalAllocated = totalAllocated.add( - Dinero({ - amount: Math.round((payer?.amount || 0) * 100), - }) - ); - }); - const discrep = job.job_totals - ? Dinero(job.job_totals.totals.total_repairs).subtract( - totalAllocated - ) - : Dinero(); - return ( - - - - - - - - = - 0 ? "green" : "red", - }} - value={discrep.toFormat()} - /> - - ); - }} - - - - - )} - - - - - ); + const payers = form.getFieldValue("qb_multiple_payers"); + payers && + payers.forEach((payer) => { + totalAllocated = totalAllocated.add( + Dinero({ + amount: Math.round((payer?.amount || 0) * 100) + }) + ); + }); + const discrep = job.job_totals + ? Dinero(job.job_totals.totals.total_repairs).subtract(totalAllocated) + : Dinero(); + return ( + + + + + - + + = + 0 ? "green" : "red" + }} + value={discrep.toFormat()} + /> + + ); + }} + + + + + )} + + + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseComponent); diff --git a/client/src/pages/jobs-close/jobs-close.container.jsx b/client/src/pages/jobs-close/jobs-close.container.jsx index 03923f93e..ad47ae676 100644 --- a/client/src/pages/jobs-close/jobs-close.container.jsx +++ b/client/src/pages/jobs-close/jobs-close.container.jsx @@ -1,87 +1,82 @@ -import {useQuery} from "@apollo/client"; -import {Result} from "antd"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useParams} from "react-router-dom"; +import { useQuery } from "@apollo/client"; +import { Result } from "antd"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useParams } from "react-router-dom"; import AlertComponent from "../../components/alert/alert.component"; import JobCalculateTotals from "../../components/job-calculate-totals/job-calculate-totals.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import NotFound from "../../components/not-found/not-found.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {QUERY_JOB_CLOSE_DETAILS} from "../../graphql/jobs.queries"; -import {setBreadcrumbs, setJobReadOnly, setSelectedHeader,} from "../../redux/application/application.actions"; +import { QUERY_JOB_CLOSE_DETAILS } from "../../graphql/jobs.queries"; +import { setBreadcrumbs, setJobReadOnly, setSelectedHeader } from "../../redux/application/application.actions"; import IsJobReadOnly from "../../utils/jobReadOnly"; import JobsCloseComponent from "./jobs-close.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), - setJobReadOnly: (bool) => dispatch(setJobReadOnly(bool)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setJobReadOnly: (bool) => dispatch(setJobReadOnly(bool)) }); -export function JobsCloseContainer({ - setBreadcrumbs, - setSelectedHeader, - setJobReadOnly, - }) { - const {jobId} = useParams(); - const {loading, error, data} = useQuery(QUERY_JOB_CLOSE_DETAILS, { - variables: {id: jobId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", +export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader, setJobReadOnly }) { + const { jobId } = useParams(); + const { loading, error, data } = useQuery(QUERY_JOB_CLOSE_DETAILS, { + variables: { id: jobId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const { t } = useTranslation(); + useEffect(() => { + if (data && data.jobs_by_pk) { + setJobReadOnly(IsJobReadOnly(data.jobs_by_pk)); + } + }, [data, setJobReadOnly]); + useEffect(() => { + setSelectedHeader("activejobs"); + document.title = t("titles.jobs-close", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }), + number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null }); - const {t} = useTranslation(); - useEffect(() => { - if (data && data.jobs_by_pk) { - setJobReadOnly(IsJobReadOnly(data.jobs_by_pk)); - } - }, [data, setJobReadOnly]); - useEffect(() => { - setSelectedHeader("activejobs"); - document.title = t("titles.jobs-close", { - app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'}), - number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null, - }); - setBreadcrumbs([ - { - link: `/manage/jobs/${jobId}/`, - label: t("titles.bc.jobs"), - }, - { - link: `/manage/jobs/${jobId}/`, - label: t("titles.bc.jobs-detail", { - number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null, - }), - }, - { - link: `/manage/jobs/${jobId}/close`, - label: t("titles.bc.jobs-close"), - }, - ]); - }, [setBreadcrumbs, t, jobId, data, setSelectedHeader]); + setBreadcrumbs([ + { + link: `/manage/jobs/${jobId}/`, + label: t("titles.bc.jobs") + }, + { + link: `/manage/jobs/${jobId}/`, + label: t("titles.bc.jobs-detail", { + number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null + }) + }, + { + link: `/manage/jobs/${jobId}/close`, + label: t("titles.bc.jobs-close") + } + ]); + }, [setBreadcrumbs, t, jobId, data, setSelectedHeader]); - if (loading) return ; - if (error) return ; - if (!!!data.jobs_by_pk) return ; + if (loading) return ; + if (error) return ; + if (!!!data.jobs_by_pk) return ; - if (!data.jobs_by_pk.job_totals) - return ( - } - /> - ); + if (!data.jobs_by_pk.job_totals) + return } />; - return ( - -
    - -
    -
    - ); + return ( + +
    + +
    +
    + ); } export default connect(null, mapDispatchToProps)(JobsCloseContainer); diff --git a/client/src/pages/jobs-create/jobs-create.component.jsx b/client/src/pages/jobs-create/jobs-create.component.jsx index 9f827ddc8..ef1787d2b 100644 --- a/client/src/pages/jobs-create/jobs-create.component.jsx +++ b/client/src/pages/jobs-create/jobs-create.component.jsx @@ -1,168 +1,167 @@ -import {Button, Result, Space, Steps} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import { Button, Result, Space, Steps } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; -import React, {useContext, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {Link} from "react-router-dom"; +import React, { useContext, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; import AlertComponent from "../../components/alert/alert.component"; import JobsCreateJobsInfo from "../../components/jobs-create-jobs-info/jobs-create-jobs-info.component"; import JobsCreateOwnerInfoContainer from "../../components/jobs-create-owner-info/jobs-create-owner-info.container"; -import JobsCreateVehicleInfoContainer - from "../../components/jobs-create-vehicle-info/jobs-create-vehicle-info.container"; +import JobsCreateVehicleInfoContainer from "../../components/jobs-create-vehicle-info/jobs-create-vehicle-info.container"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import FormsFieldChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component"; -export default function JobsCreateComponent({form}) { - const [pageIndex, setPageIndex] = useState(0); - const [errorMessage, setErrorMessage] = useState(null); - const [state] = useContext(JobCreateContext); +export default function JobsCreateComponent({ form }) { + const [pageIndex, setPageIndex] = useState(0); + const [errorMessage, setErrorMessage] = useState(null); + const [state] = useContext(JobCreateContext); - const {t} = useTranslation(); - const steps = [ - { - title: t("jobs.labels.create.vehicleinfo"), - content: , - validation: - !!state.vehicle.new || - !!state.vehicle.selectedid || - !!state.vehicle.none, - error: t("vehicles.errors.selectexistingornew"), - }, - { - title: t("jobs.labels.create.ownerinfo"), - content: , - validation: !!state.owner.new || !!state.owner.selectedid, - error: t("owners.errors.selectexistingornew"), - }, - { - title: t("jobs.labels.create.jobinfo"), - content: , - }, - ]; + const { t } = useTranslation(); + const steps = [ + { + title: t("jobs.labels.create.vehicleinfo"), + content: , + validation: !!state.vehicle.new || !!state.vehicle.selectedid || !!state.vehicle.none, + error: t("vehicles.errors.selectexistingornew") + }, + { + title: t("jobs.labels.create.ownerinfo"), + content: , + validation: !!state.owner.new || !!state.owner.selectedid, + error: t("owners.errors.selectexistingornew") + }, + { + title: t("jobs.labels.create.jobinfo"), + content: + } + ]; - const next = () => { - setPageIndex(pageIndex + 1); - console.log("NExt") - }; - const prev = () => { - setPageIndex(pageIndex - 1); - }; - const {Step} = Steps; - - const ProgressButtons = ({top}) => { - return ( - - {pageIndex > 0 && } - {pageIndex < steps.length - 1 && ( - - )} - {pageIndex === steps.length - 1 && ( - - )} - - } - > - {top && ( - - {steps.map((item, idx) => ( - { setPageIndex(idx); - // form - // .validateFields() - // .then((r) => { - // if (steps[pageIndex].validation) { - // setErrorMessage(null); - // setPageIndex(idx); - // } else { - // setErrorMessage(steps[pageIndex].error); - // } - // }) - // .catch((error) => console.log("error", error)); - }} - /> - ))} - - )} - - ); - }; + const next = () => { + setPageIndex(pageIndex + 1); + console.log("NExt"); + }; + const prev = () => { + setPageIndex(pageIndex - 1); + }; + const { Step } = Steps; + const ProgressButtons = ({ top }) => { return ( -
    - {state.created ? ( -
    - - - , - - - , - ]} - /> -
    - ) : ( -
    - - - {errorMessage ? ( -
    - -
    - ) : null} - - {steps.map((item, idx) => ( -
    - {item.content} -
    - ))} - -
    + + {pageIndex > 0 && } + {pageIndex < steps.length - 1 && ( + )} -
    + {pageIndex === steps.length - 1 && ( + + )} + + } + > + {top && ( + + {steps.map((item, idx) => ( + { + setPageIndex(idx); + // form + // .validateFields() + // .then((r) => { + // if (steps[pageIndex].validation) { + // setErrorMessage(null); + // setPageIndex(idx); + // } else { + // setErrorMessage(steps[pageIndex].error); + // } + // }) + // .catch((error) => console.log("error", error)); + }} + /> + ))} + + )} + ); + }; + + return ( +
    + {state.created ? ( +
    + + + , + + + + ]} + /> +
    + ) : ( +
    + + + {errorMessage ? ( +
    + +
    + ) : null} + + {steps.map((item, idx) => ( +
    + {item.content} +
    + ))} + +
    + )} +
    + ); } diff --git a/client/src/pages/jobs-create/jobs-create.container.jsx b/client/src/pages/jobs-create/jobs-create.container.jsx index 1b1af899e..d919bcb46 100644 --- a/client/src/pages/jobs-create/jobs-create.container.jsx +++ b/client/src/pages/jobs-create/jobs-create.container.jsx @@ -1,289 +1,287 @@ -import {useLazyQuery, useMutation} from "@apollo/client"; -import {Form, notification} from "antd"; +import { useLazyQuery, useMutation } from "@apollo/client"; +import { Form, notification } from "antd"; import _ from "lodash"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {INSERT_NEW_JOB} from "../../graphql/jobs.queries"; -import {QUERY_OWNER_FOR_JOB_CREATION} from "../../graphql/owners.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { INSERT_NEW_JOB } from "../../graphql/jobs.queries"; +import { QUERY_OWNER_FOR_JOB_CREATION } from "../../graphql/owners.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import JobsCreateComponent from "./jobs-create.component"; import JobCreateContext from "./jobs-create.context"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -function JobsCreateContainer({bodyshop, setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); - const contextState = useState({ - vehicle: { - new: false, - search: "", - selectedid: null, - vehicleObj: null, - none: false, - }, - owner: {new: false, search: "", selectedid: null}, - job: null, - created: false, - error: null, - newJobId: null, - newJobEstNum: null, +function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); + const contextState = useState({ + vehicle: { + new: false, + search: "", + selectedid: null, + vehicleObj: null, + none: false + }, + owner: { new: false, search: "", selectedid: null }, + job: null, + created: false, + error: null, + newJobId: null, + newJobEstNum: null + }); + const [form] = Form.useForm(); + const [state, setState] = contextState; + const [insertJob] = useMutation(INSERT_NEW_JOB); + const [loadOwner, RemoteOwnerData] = useLazyQuery(QUERY_OWNER_FOR_JOB_CREATION); + + useEffect(() => { + if (!!state.owner.selectedid) { + loadOwner({ + variables: { id: state.owner.selectedid } + }); + } + }, [state.owner.selectedid, loadOwner]); + + useEffect(() => { + document.title = t("titles.jobs-create", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) }); - const [form] = Form.useForm(); - const [state, setState] = contextState; - const [insertJob] = useMutation(INSERT_NEW_JOB); - const [loadOwner, RemoteOwnerData] = useLazyQuery( - QUERY_OWNER_FOR_JOB_CREATION + setSelectedHeader("newjob"); + setBreadcrumbs([ + { link: "/manage/available", label: t("titles.bc.availablejobs") }, + { + link: "/manage/jobs/new", + label: t("titles.bc.jobs-new") + } + ]); + }, [t, setBreadcrumbs, setSelectedHeader]); + + const runInsertJob = (job) => { + insertJob({ variables: { job: job } }) + .then((resp) => { + setState({ + ...state, + created: true, + error: null, + newJobId: resp.data.insert_jobs.returning[0].id + }); + }) + .catch((error) => { + notification["error"]({ + message: t("jobs.errors.creating", { error: error }) + }); + setState({ ...state, error: error }); + }); + }; + + const handleFinish = (values) => { + let job = Object.assign( + {}, + values, + { date_open: new Date() }, + { date_estimated: new Date() }, + { + vehicle: state.vehicle.selectedid || state.vehicle.none ? null : values.vehicle, + vehicleid: state.vehicle.selectedid || null + }, + { + owner: state.owner.selectedid ? null : values.owner, + ownerid: state.owner.selectedid || null + }, + { + status: bodyshop.md_ro_statuses.default_imported || "Open*", + shopid: bodyshop.id + } ); - useEffect(() => { - if (!!state.owner.selectedid) { - loadOwner({ - variables: {id: state.owner.selectedid}, - }); - } - }, [state.owner.selectedid, loadOwner]); + let ownerData; + if (!!!job.ownerid) { + ownerData = job.owner.data; + ownerData.shopid = bodyshop.id; + delete ownerData.allow_text_message; + delete ownerData.preferred_contact; + delete job.ownerid; + } else { + ownerData = _.cloneDeep(RemoteOwnerData.data.owners_by_pk); + delete ownerData.id; + delete ownerData.__typename; + } + if (!state.vehicle.none) { + if (!!!job.vehicleid) { + delete job.vehicleid; + job.vehicle.data.shopid = bodyshop.id; + job.plate_no = job.vehicle.data.plate_no; + job.plate_st = job.vehicle.data.plate_st; + job.v_vin = job.vehicle.data.v_vin; + job.v_model_yr = job.vehicle.data.v_model_yr; + job.v_model_desc = job.vehicle.data.v_model_desc; + job.v_make_desc = job.vehicle.data.v_make_desc; + job.v_color = job.vehicle.data.v_color; + } else { + job.plate_no = state.vehicle.vehicleObj.plate_no; + job.plate_st = state.vehicle.vehicleObj.plate_st; + job.v_vin = state.vehicle.vehicleObj.v_vin; + job.v_model_yr = state.vehicle.vehicleObj.v_model_yr; + job.v_model_desc = state.vehicle.vehicleObj.v_model_desc; + job.v_make_desc = state.vehicle.vehicleObj.v_make_desc; + job.v_color = state.vehicle.vehicleObj.v_color; + } + } - useEffect(() => { - document.title = t("titles.jobs-create",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("newjob"); - setBreadcrumbs([ - {link: "/manage/available", label: t("titles.bc.availablejobs")}, - { - link: "/manage/jobs/new", - label: t("titles.bc.jobs-new"), - }, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + job = { ...job, ...ownerData }; - const runInsertJob = (job) => { - insertJob({variables: {job: job}}) - .then((resp) => { - setState({ - ...state, - created: true, - error: null, - newJobId: resp.data.insert_jobs.returning[0].id, - }); + if (job.owner === null) delete job.owner; + if (job.vehicle === null) delete job.vehicle; + + runInsertJob(job); + }; + + return ( + + +
    { - notification["error"]({ - message: t("jobs.errors.creating", {error: error}), - }); - setState({...state, error: error}); - }); - }; - - const handleFinish = (values) => { - let job = Object.assign( - {}, - values, - {date_open: new Date()}, - {date_estimated: new Date()}, - { - vehicle: - state.vehicle.selectedid || state.vehicle.none - ? null - : values.vehicle, - vehicleid: state.vehicle.selectedid || null, - }, - { - owner: state.owner.selectedid ? null : values.owner, - ownerid: state.owner.selectedid || null, - }, - { - status: bodyshop.md_ro_statuses.default_imported || "Open*", - shopid: bodyshop.id, - } - ); - - let ownerData; - if (!!!job.ownerid) { - ownerData = job.owner.data; - ownerData.shopid = bodyshop.id; - delete ownerData.allow_text_message; - delete ownerData.preferred_contact; - delete job.ownerid; - } else { - ownerData = _.cloneDeep(RemoteOwnerData.data.owners_by_pk); - delete ownerData.id; - delete ownerData.__typename; - } - if (!state.vehicle.none) { - if (!!!job.vehicleid) { - delete job.vehicleid; - job.vehicle.data.shopid = bodyshop.id; - job.plate_no = job.vehicle.data.plate_no; - job.plate_st = job.vehicle.data.plate_st; - job.v_vin = job.vehicle.data.v_vin; - job.v_model_yr = job.vehicle.data.v_model_yr; - job.v_model_desc = job.vehicle.data.v_model_desc; - job.v_make_desc = job.vehicle.data.v_make_desc; - job.v_color = job.vehicle.data.v_color; - } else { - job.plate_no = state.vehicle.vehicleObj.plate_no; - job.plate_st = state.vehicle.vehicleObj.plate_st; - job.v_vin = state.vehicle.vehicleObj.v_vin; - job.v_model_yr = state.vehicle.vehicleObj.v_model_yr; - job.v_model_desc = state.vehicle.vehicleObj.v_model_desc; - job.v_make_desc = state.vehicle.vehicleObj.v_make_desc; - job.v_color = state.vehicle.vehicleObj.v_color; - } - } - - job = {...job, ...ownerData}; - - if (job.owner === null) delete job.owner; - if (job.vehicle === null) delete job.vehicle; - - runInsertJob(job); - }; - - return ( - - - - - - - - ); + }} + > + + +
    +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsCreateContainer); +export default connect(mapStateToProps, mapDispatchToProps)(JobsCreateContainer); diff --git a/client/src/pages/jobs-deliver/jobs-delivery.page.container.jsx b/client/src/pages/jobs-deliver/jobs-delivery.page.container.jsx index 9631ba38a..1535936e9 100644 --- a/client/src/pages/jobs-deliver/jobs-delivery.page.container.jsx +++ b/client/src/pages/jobs-deliver/jobs-delivery.page.container.jsx @@ -1,80 +1,75 @@ -import {useQuery} from "@apollo/client"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useParams} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import { useQuery } from "@apollo/client"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useParams } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import AlertComponent from "../../components/alert/alert.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {QUERY_DELIVER_CHECKLIST} from "../../graphql/bodyshop.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { QUERY_DELIVER_CHECKLIST } from "../../graphql/bodyshop.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import JobchecklistComponent from "../../components/job-checklist/job-checklist.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function JobsDeliverContainer({ - bodyshop, - setBreadcrumbs, - setSelectedHeader, - }) { - const {t} = useTranslation(); - const {jobId} = useParams(); - const {loading, error, data} = useQuery(QUERY_DELIVER_CHECKLIST, { - variables: {shopId: bodyshop.id, jobId: jobId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", +export function JobsDeliverContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); + const { jobId } = useParams(); + const { loading, error, data } = useQuery(QUERY_DELIVER_CHECKLIST, { + variables: { shopId: bodyshop.id, jobId: jobId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + + useEffect(() => { + document.title = t("titles.jobs-deliver", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) }); + setSelectedHeader("activejobs"); + setBreadcrumbs([ + { link: "/manage/jobs", label: t("titles.bc.jobs") }, + { + link: `/manage/jobs/${jobId}`, + label: t("titles.bc.jobs-detail", { + number: (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || "" + }) + }, + { + link: `/manage/jobs/${jobId}/deliver`, + label: t("titles.bc.jobs-deliver") + } + ]); + }, [t, setBreadcrumbs, jobId, data, setSelectedHeader]); - useEffect(() => { - document.title = t("titles.jobs-deliver",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("activejobs"); - setBreadcrumbs([ - {link: "/manage/jobs", label: t("titles.bc.jobs")}, - { - link: `/manage/jobs/${jobId}`, - label: t("titles.bc.jobs-detail", { - number: (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || "", - }), - }, - { - link: `/manage/jobs/${jobId}/deliver`, - label: t("titles.bc.jobs-deliver"), - }, - ]); - }, [t, setBreadcrumbs, jobId, data, setSelectedHeader]); - - if (loading) return ; - if (error) return ; - if (data && !!!data.bodyshops_by_pk.deliverchecklist) - return ( - - ); - return ( - -
    - -
    -
    - ); + if (loading) return ; + if (error) return ; + if (data && !!!data.bodyshops_by_pk.deliverchecklist) + return ; + return ( + +
    + +
    +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsDeliverContainer); +export default connect(mapStateToProps, mapDispatchToProps)(JobsDeliverContainer); diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx index 1027001f4..b6d827c1f 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx @@ -1,25 +1,25 @@ import Icon, { - BarsOutlined, - CalendarFilled, - DollarCircleOutlined, - FileImageFilled, - HistoryOutlined, - PrinterFilled, - SyncOutlined, - ToolFilled, + BarsOutlined, + CalendarFilled, + DollarCircleOutlined, + FileImageFilled, + HistoryOutlined, + PrinterFilled, + SyncOutlined, + ToolFilled } from "@ant-design/icons"; -import {Button, Divider, Form, notification, Space, Tabs,} from "antd"; -import {PageHeader} from "@ant-design/pro-layout"; +import { Button, Divider, Form, notification, Space, Tabs } from "antd"; +import { PageHeader } from "@ant-design/pro-layout"; import Axios from "axios"; import dayjs from "../../utils/day"; import queryString from "query-string"; -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {FaHardHat, FaRegStickyNote, FaShieldAlt} from "react-icons/fa"; -import {connect} from "react-redux"; -import {useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { FaHardHat, FaRegStickyNote, FaShieldAlt } from "react-icons/fa"; +import { connect } from "react-redux"; +import { useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component"; import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component"; import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container"; @@ -38,370 +38,362 @@ import JobsDetailPliContainer from "../../components/jobs-detail-pli/jobs-detail import JobsDetailRates from "../../components/jobs-detail-rates/jobs-detail-rates.component"; import JobsDetailTotals from "../../components/jobs-detail-totals/jobs-detail-totals.component"; import JobsDocumentsGalleryContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container"; -import JobsDocumentsLocalGallery - from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container"; +import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container"; import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container"; import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container"; import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container"; -import {insertAuditTrail} from "../../redux/application/application.actions"; -import {selectJobReadOnly} from "../../redux/application/application.selectors"; -import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import UndefinedToNull from "../../utils/undefinedtonull"; import _ from "lodash"; import JobProfileDataWarning from "../../components/job-profile-data-warning/job-profile-data-warning.component"; -import {DateTimeFormat} from "../../utils/DateFormatter"; +import { DateTimeFormat } from "../../utils/DateFormatter"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - jobRO: selectJobReadOnly, + bodyshop: selectBodyshop, + jobRO: selectJobReadOnly }); const mapDispatchToProps = (dispatch) => ({ - setPrintCenterContext: (context) => - dispatch(setModalContext({context: context, modal: "printCenter"})), - insertAuditTrail: ({jobid, operation, type}) => - dispatch(insertAuditTrail({jobid, operation, type })), + setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" })), + insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); export function JobsDetailPage({ - bodyshop, - setPrintCenterContext, - jobRO, - job, - mutationUpdateJob, - handleSubmit, - insertAuditTrail, - refetch, - }) { - const {t} = useTranslation(); - const [form] = Form.useForm(); - const history = useNavigate(); - const [loading, setLoading] = useState(false); - const search = queryString.parse(useLocation().search); - const formItemLayout = { - layout: "vertical", - }; + bodyshop, + setPrintCenterContext, + jobRO, + job, + mutationUpdateJob, + handleSubmit, + insertAuditTrail, + refetch +}) { + const { t } = useTranslation(); + const [form] = Form.useForm(); + const history = useNavigate(); + const [loading, setLoading] = useState(false); + const search = queryString.parse(useLocation().search); + const formItemLayout = { + layout: "vertical" + }; - useEffect(() => { - //form.setFieldsValue(transormJobToForm(job)); - form.resetFields(); - }, [form, job]); + useEffect(() => { + //form.setFieldsValue(transormJobToForm(job)); + form.resetFields(); + }, [form, job]); - //useKeyboardSaveShortcut(form.submit); + //useKeyboardSaveShortcut(form.submit); - const handleFinish = async (values) => { - setLoading(true); + const handleFinish = async (values) => { + setLoading(true); - const result = await mutationUpdateJob({ - variables: { - jobId: job.id, - job: { - ...UndefinedToNull(values, [ - "alt_transport", - "category", - "referral_source", - ]), - // The union and spread is required to keep values coming in from the estimating system that aren't displayed. - parts_tax_rates: _.union( - Object.keys(job.parts_tax_rates), - Object.keys(values.parts_tax_rates || {}) - ).reduce((acc, val) => { - acc[val] = { - ...job.parts_tax_rates[val], - ...values.parts_tax_rates?.[val], //TODO:AIO Verify that these still save for Rome Online with this null coalescing. - }; - return acc; - }, {}), - materials: _.union( - Object.keys(job.materials), - Object.keys(values.materials|| {}) - ).reduce((acc, val) => { - acc[val] = { - ...job.materials[val], - ...values.materials?.[val], - }; - return acc; - }, {}), - cieca_pfl: _.union( - Object.keys(job.cieca_pfl), - Object.keys(values.cieca_pfl|| {}) - ).reduce((acc, val) => { - acc[val] = { - ...job.cieca_pfl[val], - ...values.cieca_pfl?.[val], - }; - return acc; - }, {}), - cieca_pfo: {...job.cieca_pfo, ...values.cieca_pfo}, - }, + const result = await mutationUpdateJob({ + variables: { + jobId: job.id, + job: { + ...UndefinedToNull(values, ["alt_transport", "category", "referral_source"]), + // The union and spread is required to keep values coming in from the estimating system that aren't displayed. + parts_tax_rates: _.union(Object.keys(job.parts_tax_rates), Object.keys(values.parts_tax_rates || {})).reduce( + (acc, val) => { + acc[val] = { + ...job.parts_tax_rates[val], + ...values.parts_tax_rates?.[val] //TODO:AIO Verify that these still save for Rome Online with this null coalescing. + }; + return acc; }, - }); - try { - const newTotals = await Axios.post("/job/totalsssu", { - id: job.id, - }); - - if (newTotals.status !== 200 || result.errors) { - notification["error"]({ - message: t("jobs.errors.totalscalc"), - }); - } else { - notification["success"]({ - message: t("jobs.successes.savetitle"), - }); - const changedAuditFields = form.getFieldsValue( - [ - "scheduled_in", - "actual_in", - "scheduled_completion", - "actual_completion", - "scheduled_delivery", - "actual_delivery", - "date_invoiced", - "ins_co_nm", - "ded_amt", - "ded_status", - "date_exported", - "special_coverage_policy", - "ca_gst_registrant", - "ca_bc_pvrt", - "scheduled_in", - "rate_la1", - "rate_la2", - "rate_la3", - "rate_la4", - "rate_laa", - "rate_lab", - "rate_lad", - "rate_lae", - "rate_laf", - "rate_lag", - "rate_lam", - "rate_lar", - "rate_las", - "rate_lau", - "rate_ma2s", - "rate_ma2t", - "rate_ma3s", - "rate_mabl", - "rate_macs", - "rate_mapa", - "rate_mahw", - "rate_mash", - "rate_matd", - ], - (meta) => meta && meta.touched - ); - - Object.keys(changedAuditFields).forEach((key) => { - insertAuditTrail({ - jobid: job.id, - operation: AuditTrailMapping.jobfieldchange( - key, - changedAuditFields[key] instanceof dayjs - ? DateTimeFormat(changedAuditFields[key]) - : changedAuditFields[key] - ), - type: "jobfieldchange",}); - }); - - await refetch(); - form.setFieldsValue(transformJobToForm(job)); - form.resetFields(); - } - } catch (error) { - notification["error"]({ - message: t("jobs.errors.totalscalc"), - }); - } finally { - setLoading(false); + {} + ), + materials: _.union(Object.keys(job.materials), Object.keys(values.materials || {})).reduce((acc, val) => { + acc[val] = { + ...job.materials[val], + ...values.materials?.[val] + }; + return acc; + }, {}), + cieca_pfl: _.union(Object.keys(job.cieca_pfl), Object.keys(values.cieca_pfl || {})).reduce((acc, val) => { + acc[val] = { + ...job.cieca_pfl[val], + ...values.cieca_pfl?.[val] + }; + return acc; + }, {}), + cieca_pfo: { ...job.cieca_pfo, ...values.cieca_pfo } } - }; + } + }); + try { + const newTotals = await Axios.post("/job/totalsssu", { + id: job.id + }); - const menuExtra = ( - - - - - - - - - - ); + if (newTotals.status !== 200 || result.errors) { + notification["error"]({ + message: t("jobs.errors.totalscalc") + }); + } else { + notification["success"]({ + message: t("jobs.successes.savetitle") + }); + const changedAuditFields = form.getFieldsValue( + [ + "scheduled_in", + "actual_in", + "scheduled_completion", + "actual_completion", + "scheduled_delivery", + "actual_delivery", + "date_invoiced", + "ins_co_nm", + "ded_amt", + "ded_status", + "date_exported", + "special_coverage_policy", + "ca_gst_registrant", + "ca_bc_pvrt", + "scheduled_in", + "rate_la1", + "rate_la2", + "rate_la3", + "rate_la4", + "rate_laa", + "rate_lab", + "rate_lad", + "rate_lae", + "rate_laf", + "rate_lag", + "rate_lam", + "rate_lar", + "rate_las", + "rate_lau", + "rate_ma2s", + "rate_ma2t", + "rate_ma3s", + "rate_mabl", + "rate_macs", + "rate_mapa", + "rate_mahw", + "rate_mash", + "rate_matd" + ], + (meta) => meta && meta.touched + ); - return ( -
    - - - - -
    - window.history.back()} - title={job.ro_number || t("general.labels.na")} - extra={menuExtra} - /> - - - - - history({search: `?tab=${key}`})} - tabBarStyle={{fontWeight: "bold", borderBottom: "10px"}} - items={[ - { - key: "general", - icon: , - label: t("menus.jobsdetail.general"), - forceRender: true, - children: , - }, - { - key: "repairdata", - icon: , - label: t("menus.jobsdetail.repairdata"), - forceRender: true, - children: ( - - ), - }, - { - key: "rates", - icon: , - label: t("menus.jobsdetail.rates"), - forceRender: true, - children: , - }, - { - key: "totals", - icon: , - label: t("menus.jobsdetail.totals"), - children: , - }, - { - key: "partssublet", - icon: , - label: HasFeatureAccess({featureName: "bills", bodyshop}) ? t("menus.jobsdetail.partssublet") : t("menus.jobsdetail.parts"), - children: , - }, - ...InstanceRenderManager({ imex: true, rome: true, promanager: HasFeatureAccess({ featureName: 'timetickets', bodyshop }) }) ? [ { - key: "labor", - icon: , - label: t("menus.jobsdetail.labor"), - children: , - },]: [], - { - key: 'lifecycle', - icon: , - label: t("menus.jobsdetail.lifecycle"), - children: - }, - { - key: "dates", - icon: , - label: t("menus.jobsdetail.dates"), - forceRender: true, - children: , - }, - ...InstanceRenderManager({ imex: true, rome: true, promanager: HasFeatureAccess({ featureName: 'media', bodyshop }) }) ? [ { - key: "documents", - icon: , - label: t("jobs.labels.documents"), - children: bodyshop.uselocalmediaserver ? ( - - ) : ( - - ), - },]:[], - { - key: "notes", - icon: , - label: t("jobs.labels.notes"), - children: , - }, - { - key: "audit", - icon: , - label: t("jobs.labels.audit"), - children: , - }, - ]} - /> - -
    - ); + Object.keys(changedAuditFields).forEach((key) => { + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.jobfieldchange( + key, + changedAuditFields[key] instanceof dayjs + ? DateTimeFormat(changedAuditFields[key]) + : changedAuditFields[key] + ), + type: "jobfieldchange" + }); + }); + + await refetch(); + form.setFieldsValue(transformJobToForm(job)); + form.resetFields(); + } + } catch (error) { + notification["error"]({ + message: t("jobs.errors.totalscalc") + }); + } finally { + setLoading(false); + } + }; + + const menuExtra = ( + + + + + + + + + + ); + + return ( +
    + + + + +
    + window.history.back()} + title={job.ro_number || t("general.labels.na")} + extra={menuExtra} + /> + + + + + history({ search: `?tab=${key}` })} + tabBarStyle={{ fontWeight: "bold", borderBottom: "10px" }} + items={[ + { + key: "general", + icon: , + label: t("menus.jobsdetail.general"), + forceRender: true, + children: + }, + { + key: "repairdata", + icon: , + label: t("menus.jobsdetail.repairdata"), + forceRender: true, + children: + }, + { + key: "rates", + icon: , + label: t("menus.jobsdetail.rates"), + forceRender: true, + children: + }, + { + key: "totals", + icon: , + label: t("menus.jobsdetail.totals"), + children: + }, + { + key: "partssublet", + icon: , + label: HasFeatureAccess({ featureName: "bills", bodyshop }) + ? t("menus.jobsdetail.partssublet") + : t("menus.jobsdetail.parts"), + children: + }, + ...(InstanceRenderManager({ + imex: true, + rome: true, + promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop }) + }) + ? [ + { + key: "labor", + icon: , + label: t("menus.jobsdetail.labor"), + children: + } + ] + : []), + { + key: "lifecycle", + icon: , + label: t("menus.jobsdetail.lifecycle"), + children: + }, + { + key: "dates", + icon: , + label: t("menus.jobsdetail.dates"), + forceRender: true, + children: + }, + ...(InstanceRenderManager({ + imex: true, + rome: true, + promanager: HasFeatureAccess({ featureName: "media", bodyshop }) + }) + ? [ + { + key: "documents", + icon: , + label: t("jobs.labels.documents"), + children: bodyshop.uselocalmediaserver ? ( + + ) : ( + + ) + } + ] + : []), + { + key: "notes", + icon: , + label: t("jobs.labels.notes"), + children: + }, + { + key: "audit", + icon: , + label: t("jobs.labels.audit"), + children: + } + ]} + /> + +
    + ); } export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage); const transformJobToForm = (job) => { - const transformedJob = {...job}; + const transformedJob = { ...job }; - transformedJob.parts_tax_rates = Object.keys(transformedJob.parts_tax_rates).reduce((acc, parttype) => { - acc[parttype] = Object.keys(transformedJob.parts_tax_rates[parttype]).reduce((innerAcc, key) => { - if (key.includes("tx_in")) { - innerAcc[key] = transformedJob.parts_tax_rates[parttype][key] === "Y" || transformedJob.parts_tax_rates[parttype][key] === true; - } else { - innerAcc[key] = transformedJob.parts_tax_rates[parttype][key]; - } - return innerAcc; - }, {}); - return acc; + transformedJob.parts_tax_rates = Object.keys(transformedJob.parts_tax_rates).reduce((acc, parttype) => { + acc[parttype] = Object.keys(transformedJob.parts_tax_rates[parttype]).reduce((innerAcc, key) => { + if (key.includes("tx_in")) { + innerAcc[key] = + transformedJob.parts_tax_rates[parttype][key] === "Y" || + transformedJob.parts_tax_rates[parttype][key] === true; + } else { + innerAcc[key] = transformedJob.parts_tax_rates[parttype][key]; + } + return innerAcc; }, {}); + return acc; + }, {}); - transformedJob.loss_date = transformedJob.loss_date ? dayjs(transformedJob.loss_date) : null; - transformedJob.date_estimated = transformedJob.date_estimated ? dayjs(transformedJob.date_estimated) : null; + transformedJob.loss_date = transformedJob.loss_date ? dayjs(transformedJob.loss_date) : null; + transformedJob.date_estimated = transformedJob.date_estimated ? dayjs(transformedJob.date_estimated) : null; - return transformedJob; + return transformedJob; }; diff --git a/client/src/pages/jobs-detail/jobs-detail.page.container.jsx b/client/src/pages/jobs-detail/jobs-detail.page.container.jsx index 4accbf719..5ac3cfc74 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.container.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.container.jsx @@ -1,123 +1,107 @@ -import {useMutation, useQuery} from "@apollo/client"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useParams} from 'react-router-dom'; -import {createStructuredSelector} from "reselect"; +import { useMutation, useQuery } from "@apollo/client"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useParams } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import AlertComponent from "../../components/alert/alert.component"; import SpinComponent from "../../components/loading-spinner/loading-spinner.component"; import NotFound from "../../components/not-found/not-found.component"; -import {OwnerNameDisplayFunction} from "../../components/owner-name-display/owner-name-display.component"; +import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {GET_JOB_BY_PK, UPDATE_JOB} from "../../graphql/jobs.queries"; +import { GET_JOB_BY_PK, UPDATE_JOB } from "../../graphql/jobs.queries"; import { - addRecentItem, - setBreadcrumbs, - setJobReadOnly, - setSelectedHeader, + addRecentItem, + setBreadcrumbs, + setJobReadOnly, + setSelectedHeader } from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {CreateRecentItem} from "../../utils/create-recent-item"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { CreateRecentItem } from "../../utils/create-recent-item"; import IsJobReadOnly from "../../utils/jobReadOnly"; import JobsDetailPage from "./jobs-detail.page.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - addRecentItem: (item) => dispatch(addRecentItem(item)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), - setJobReadOnly: (bool) => dispatch(setJobReadOnly(bool)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + addRecentItem: (item) => dispatch(addRecentItem(item)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setJobReadOnly: (bool) => dispatch(setJobReadOnly(bool)) }); -function JobsDetailPageContainer({ - setBreadcrumbs, - addRecentItem, - setSelectedHeader, - setJobReadOnly, - }) { - const {jobId} = useParams(); - const {t} = useTranslation(); +function JobsDetailPageContainer({ setBreadcrumbs, addRecentItem, setSelectedHeader, setJobReadOnly }) { + const { jobId } = useParams(); + const { t } = useTranslation(); - const {loading, error, data, refetch} = useQuery(GET_JOB_BY_PK, { - variables: {id: jobId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); - const [mutationUpdateJob] = useMutation(UPDATE_JOB); + const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, { + variables: { id: jobId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const [mutationUpdateJob] = useMutation(UPDATE_JOB); - useEffect(() => { - setSelectedHeader("activejobs"); - document.title = loading - ? InstanceRenderManager({imex: t("titles.imexonline"), rome: t("titles.romeonline"), promanager:t("titles.promanager")}) - : error - ? InstanceRenderManager({imex: t("titles.imexonline"), rome: t("titles.romeonline"), promanager:t("titles.promanager")}) - : t("titles.jobsdetail", { - app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'}), - ro_number: - (data.jobs_by_pk && data.jobs_by_pk.ro_number) || - t("general.labels.na"), - }); - setBreadcrumbs([ - {link: "/manage/jobs", label: t("titles.bc.jobs")}, - { - link: `/manage/jobs/${jobId}`, - label: t("titles.bc.jobs-detail", { - number: - (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || - t("general.labels.na"), - }), - }, - ]); - - if (data && data.jobs_by_pk) { - setJobReadOnly(IsJobReadOnly(data.jobs_by_pk)); - - addRecentItem( - CreateRecentItem( - jobId, - "job", - - `${ - data.jobs_by_pk.ro_number || t("general.labels.na") - } | ${OwnerNameDisplayFunction(data.jobs_by_pk)}`, - `/manage/jobs/${jobId}` - ) - ); - } - }, [ - loading, - data, - t, - error, - setBreadcrumbs, - jobId, - addRecentItem, - setSelectedHeader, - setJobReadOnly, + useEffect(() => { + setSelectedHeader("activejobs"); + document.title = loading + ? InstanceRenderManager({ + imex: t("titles.imexonline"), + rome: t("titles.romeonline"), + promanager: t("titles.promanager") + }) + : error + ? InstanceRenderManager({ + imex: t("titles.imexonline"), + rome: t("titles.romeonline"), + promanager: t("titles.promanager") + }) + : t("titles.jobsdetail", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }), + ro_number: (data.jobs_by_pk && data.jobs_by_pk.ro_number) || t("general.labels.na") + }); + setBreadcrumbs([ + { link: "/manage/jobs", label: t("titles.bc.jobs") }, + { + link: `/manage/jobs/${jobId}`, + label: t("titles.bc.jobs-detail", { + number: (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || t("general.labels.na") + }) + } ]); - if (loading) return ; - if (error) return ; - if (!!!data.jobs_by_pk) return ; + if (data && data.jobs_by_pk) { + setJobReadOnly(IsJobReadOnly(data.jobs_by_pk)); - return data.jobs_by_pk ? ( - - - - ) : ( - - ); + addRecentItem( + CreateRecentItem( + jobId, + "job", + + `${data.jobs_by_pk.ro_number || t("general.labels.na")} | ${OwnerNameDisplayFunction(data.jobs_by_pk)}`, + `/manage/jobs/${jobId}` + ) + ); + } + }, [loading, data, t, error, setBreadcrumbs, jobId, addRecentItem, setSelectedHeader, setJobReadOnly]); + + if (loading) return ; + if (error) return ; + if (!!!data.jobs_by_pk) return ; + + return data.jobs_by_pk ? ( + + + + ) : ( + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsDetailPageContainer); +export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPageContainer); diff --git a/client/src/pages/jobs-intake/jobs-intake.page.container.jsx b/client/src/pages/jobs-intake/jobs-intake.page.container.jsx index 7a3a38844..a0a5bae84 100644 --- a/client/src/pages/jobs-intake/jobs-intake.page.container.jsx +++ b/client/src/pages/jobs-intake/jobs-intake.page.container.jsx @@ -1,94 +1,84 @@ -import {useQuery} from "@apollo/client"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useParams} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import { useQuery } from "@apollo/client"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useParams } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import AlertComponent from "../../components/alert/alert.component"; import JobChecklist from "../../components/job-checklist/job-checklist.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; -import {QUERY_INTAKE_CHECKLIST} from "../../graphql/bodyshop.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { QUERY_INTAKE_CHECKLIST } from "../../graphql/bodyshop.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {Result} from "antd"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { Result } from "antd"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function JobsIntakeContainer({ - bodyshop, - setBreadcrumbs, - setSelectedHeader, - }) { - const {t} = useTranslation(); - const {jobId} = useParams(); +export function JobsIntakeContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); + const { jobId } = useParams(); - const {loading, error, data} = useQuery(QUERY_INTAKE_CHECKLIST, { - variables: {shopId: bodyshop.id, jobId: jobId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", + const { loading, error, data } = useQuery(QUERY_INTAKE_CHECKLIST, { + variables: { shopId: bodyshop.id, jobId: jobId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + + useEffect(() => { + document.title = t("titles.jobs-intake", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) }); + setSelectedHeader("activejobs"); + setBreadcrumbs([ + { link: "/manage/jobs", label: t("titles.bc.jobs") }, + { + link: `/manage/jobs/${jobId}`, + label: t("titles.bc.jobs-detail", { + number: data && ((data.jobs_by_pk && data.jobs_by_pk.ro_number) || t("general.labels.na")) + }) + }, + { + link: `/manage/jobs/${jobId}/intake`, + label: t("titles.bc.jobs-intake") + } + ]); + }, [t, setBreadcrumbs, jobId, data, setSelectedHeader]); - useEffect(() => { - document.title = t("titles.jobs-intake",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("activejobs"); - setBreadcrumbs([ - {link: "/manage/jobs", label: t("titles.bc.jobs")}, - { - link: `/manage/jobs/${jobId}`, - label: t("titles.bc.jobs-detail", { - number: - data && - ((data.jobs_by_pk && data.jobs_by_pk.ro_number) || - t("general.labels.na")), - }), - }, - { - link: `/manage/jobs/${jobId}/intake`, - label: t("titles.bc.jobs-intake"), - }, - ]); - }, [t, setBreadcrumbs, jobId, data, setSelectedHeader]); + if (loading) return ; + if (error) return ; - if (loading) return ; - if (error) return ; + if (data && !!!data.bodyshops_by_pk.intakechecklist) + return ; - if (data && !!!data.bodyshops_by_pk.intakechecklist) - return ( - - ); - - return ( - -
    - {!!data.jobs_by_pk.intakechecklist || - !bodyshop.md_ro_statuses.pre_production_statuses.includes( - data.jobs_by_pk.status - ) ? ( - - ) : ( - - )} -
    -
    - ); + return ( + +
    + {!!data.jobs_by_pk.intakechecklist || + !bodyshop.md_ro_statuses.pre_production_statuses.includes(data.jobs_by_pk.status) ? ( + + ) : ( + + )} +
    +
    + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(JobsIntakeContainer); +export default connect(mapStateToProps, mapDispatchToProps)(JobsIntakeContainer); diff --git a/client/src/pages/jobs-ready/jobs-ready.page.jsx b/client/src/pages/jobs-ready/jobs-ready.page.jsx index 30d4d64d7..d1b1a58f9 100644 --- a/client/src/pages/jobs-ready/jobs-ready.page.jsx +++ b/client/src/pages/jobs-ready/jobs-ready.page.jsx @@ -1,34 +1,38 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import JobDetailCards from "../../components/job-detail-cards/job-detail-cards.component"; import JobsReadyList from "../../components/jobs-ready-list/jobs-ready-list.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function JobsReadyPage({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); +export function JobsReadyPage({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.readyjobs",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("readyjobs"); - setBreadcrumbs([ - {link: "/manage/jobs", label: t("titles.bc.jobs-ready")}, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.readyjobs", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("readyjobs"); + setBreadcrumbs([{ link: "/manage/jobs", label: t("titles.bc.jobs-ready") }]); + }, [t, setBreadcrumbs, setSelectedHeader]); - return ( - - - - - ); + return ( + + + + + ); } export default connect(null, mapDispatchToProps)(JobsReadyPage); diff --git a/client/src/pages/jobs/jobs.page.jsx b/client/src/pages/jobs/jobs.page.jsx index 289091a53..f330bb2f0 100644 --- a/client/src/pages/jobs/jobs.page.jsx +++ b/client/src/pages/jobs/jobs.page.jsx @@ -1,34 +1,38 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import JobDetailCards from "../../components/job-detail-cards/job-detail-cards.component"; import JobsList from "../../components/jobs-list/jobs-list.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function JobsPage({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); +export function JobsPage({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.jobs",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("activejobs"); - setBreadcrumbs([ - {link: "/manage/jobs", label: t("titles.bc.jobs-active")}, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.jobs", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("activejobs"); + setBreadcrumbs([{ link: "/manage/jobs", label: t("titles.bc.jobs-active") }]); + }, [t, setBreadcrumbs, setSelectedHeader]); - return ( - - - - - ); + return ( + + + + + ); } export default connect(null, mapDispatchToProps)(JobsPage); diff --git a/client/src/pages/landing/landing.page.jsx b/client/src/pages/landing/landing.page.jsx index a0c283a4a..081418fdf 100644 --- a/client/src/pages/landing/landing.page.jsx +++ b/client/src/pages/landing/landing.page.jsx @@ -1,21 +1,21 @@ -import React, { useEffect } from 'react'; -import { connect } from 'react-redux'; -import { useNavigate } from 'react-router-dom'; -import { createStructuredSelector } from 'reselect'; -import { selectCurrentUser } from '../../redux/user/user.selectors'; -import { Spin } from 'antd'; +import React, { useEffect } from "react"; +import { connect } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { selectCurrentUser } from "../../redux/user/user.selectors"; +import { Spin } from "antd"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, + currentUser: selectCurrentUser }); export default connect(mapStateToProps, null)(LandingPage); export function LandingPage({ currentUser }) { const navigate = useNavigate(); - console.log('Main'); + console.log("Main"); useEffect(() => { - navigate('/manage/jobs'); + navigate("/manage/jobs"); }, [currentUser, navigate]); return ; } diff --git a/client/src/pages/manage-root/manage-root.page.component.jsx b/client/src/pages/manage-root/manage-root.page.component.jsx index 57d1b4853..c53ddc15f 100644 --- a/client/src/pages/manage-root/manage-root.page.component.jsx +++ b/client/src/pages/manage-root/manage-root.page.component.jsx @@ -1,20 +1,19 @@ -import React, {useEffect} from "react"; +import React, { useEffect } from "react"; //import DashboardGridComponent from "../../components/dashboard-grid/dashboard-grid.component"; -import {useNavigate} from "react-router-dom"; +import { useNavigate } from "react-router-dom"; export default function ManageRootPageComponent() { - //const client = useApolloClient(); - const navigate = useNavigate(); + //const client = useApolloClient(); + const navigate = useNavigate(); - useEffect(() => { - navigate('/manage/jobs'); - }, [navigate]); - - return
    ; - // return ( - //
    - // - //
    - // ); + useEffect(() => { + navigate("/manage/jobs"); + }, [navigate]); + return
    ; + // return ( + //
    + // + //
    + // ); } diff --git a/client/src/pages/manage-root/manage-root.page.container.jsx b/client/src/pages/manage-root/manage-root.page.container.jsx index 193a16864..1763dcb6b 100644 --- a/client/src/pages/manage-root/manage-root.page.container.jsx +++ b/client/src/pages/manage-root/manage-root.page.container.jsx @@ -1,31 +1,34 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {setBreadcrumbs} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { setBreadcrumbs } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import ManageRootPageComponent from "./manage-root.page.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)) }); -export function ManageRootPageContainer({setBreadcrumbs, bodyshop}) { - const {t} = useTranslation(); - useEffect(() => { - document.title = t("titles.manageroot",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setBreadcrumbs([]); - }, [t, setBreadcrumbs]); +export function ManageRootPageContainer({ setBreadcrumbs, bodyshop }) { + const { t } = useTranslation(); + useEffect(() => { + document.title = t("titles.manageroot", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setBreadcrumbs([]); + }, [t, setBreadcrumbs]); - return ; + return ; } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ManageRootPageContainer); +export default connect(mapStateToProps, mapDispatchToProps)(ManageRootPageContainer); diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index 9663d8132..eb1f04ff3 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -12,7 +12,7 @@ import ErrorBoundary from "../../components/error-boundary/error-boundary.compon //import FooterComponent from "../../components/footer/footer.component"; //Component Imports import * as Sentry from "@sentry/react"; -import Joyride from 'react-joyride'; +import Joyride from "react-joyride"; import TestComponent from "../../components/_test/test.page"; import HeaderContainer from "../../components/header/header.container"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; @@ -20,7 +20,7 @@ import PartnerPingComponent from "../../components/partner-ping/partner-ping.com import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container"; import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component"; import { requestForToken } from "../../firebase/firebase.utils"; -import { selectBodyshop, selectInstanceConflict, } from "../../redux/user/user.selectors"; +import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors"; import UpdateAlert from "../../components/update-alert/update-alert.component"; import { setJoyRideFinished } from "../../redux/application/application.actions.js"; @@ -31,538 +31,614 @@ import "./manage.page.styles.scss"; const JobsPage = lazy(() => import("../jobs/jobs.page")); const CardPaymentModalContainer = lazy(() => - import("../../components/card-payment-modal/card-payment-modal.container.") + import("../../components/card-payment-modal/card-payment-modal.container.") ); -const JobsDetailPage = lazy(() => - import("../jobs-detail/jobs-detail.page.container") -); +const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container")); const InventoryListPage = lazy(() => import("../inventory/inventory.page")); const ProfilePage = lazy(() => import("../profile/profile.container.page")); -const JobsAvailablePage = lazy(() => - import("../jobs-available/jobs-available.page.container") -); -const ScheduleContainer = lazy(() => - import("../schedule/schedule.page.container") -); -const VehiclesContainer = lazy(() => - import("../vehicles/vehicles.page.container") -); -const VehiclesDetailContainer = lazy(() => - import("../vehicles-detail/vehicles-detail.page.container") -); +const JobsAvailablePage = lazy(() => import("../jobs-available/jobs-available.page.container")); +const ScheduleContainer = lazy(() => import("../schedule/schedule.page.container")); +const VehiclesContainer = lazy(() => import("../vehicles/vehicles.page.container")); +const VehiclesDetailContainer = lazy(() => import("../vehicles-detail/vehicles-detail.page.container")); const OwnersContainer = lazy(() => import("../owners/owners.page.container")); -const OwnersDetailContainer = lazy(() => - import("../owners-detail/owners-detail.page.container") -); +const OwnersDetailContainer = lazy(() => import("../owners-detail/owners-detail.page.container")); const ShopPage = lazy(() => import("../shop/shop.page.component")); -const ShopVendorPageContainer = lazy(() => - import("../shop-vendor/shop-vendor.page.container") -); -const EmailOverlayContainer = lazy(() => - import("../../components/email-overlay/email-overlay.container.jsx") -); -const JobsCreateContainerPage = lazy(() => - import("../jobs-create/jobs-create.container") -); -const CourtesyCarCreateContainer = lazy(() => - import("../courtesy-car-create/courtesy-car-create.page.container") -); -const CourtesyCarDetailContainer = lazy(() => - import("../courtesy-car-detail/courtesy-car-detail.page.container") -); -const CourtesyCarsPage = lazy(() => - import("../courtesy-cars/courtesy-cars.page.container") -); -const ContractCreatePage = lazy(() => - import("../contract-create/contract-create.page.container") -); -const ContractDetailPage = lazy(() => - import("../contract-detail/contract-detail.page.container") -); -const ContractsList = lazy(() => - import("../contracts/contracts.page.container") -); +const ShopVendorPageContainer = lazy(() => import("../shop-vendor/shop-vendor.page.container")); +const EmailOverlayContainer = lazy(() => import("../../components/email-overlay/email-overlay.container.jsx")); +const JobsCreateContainerPage = lazy(() => import("../jobs-create/jobs-create.container")); +const CourtesyCarCreateContainer = lazy(() => import("../courtesy-car-create/courtesy-car-create.page.container")); +const CourtesyCarDetailContainer = lazy(() => import("../courtesy-car-detail/courtesy-car-detail.page.container")); +const CourtesyCarsPage = lazy(() => import("../courtesy-cars/courtesy-cars.page.container")); +const ContractCreatePage = lazy(() => import("../contract-create/contract-create.page.container")); +const ContractDetailPage = lazy(() => import("../contract-detail/contract-detail.page.container")); +const ContractsList = lazy(() => import("../contracts/contracts.page.container")); const BillsListPage = lazy(() => import("../bills/bills.page.container")); -const JobCostingModal = lazy(() => - import("../../components/job-costing-modal/job-costing-modal.container") -); -const ReportCenterModal = lazy(() => - import("../../components/report-center-modal/report-center-modal.container") -); -const BillEnterModalContainer = lazy(() => - import("../../components/bill-enter-modal/bill-enter-modal.container") -); -const TimeTicketModalContainer = lazy(() => - import("../../components/time-ticket-modal/time-ticket-modal.container") -); +const JobCostingModal = lazy(() => import("../../components/job-costing-modal/job-costing-modal.container")); +const ReportCenterModal = lazy(() => import("../../components/report-center-modal/report-center-modal.container")); +const BillEnterModalContainer = lazy(() => import("../../components/bill-enter-modal/bill-enter-modal.container")); +const TimeTicketModalContainer = lazy(() => import("../../components/time-ticket-modal/time-ticket-modal.container")); const TimeTicketModalTask = lazy(() => - import( - "../../components/time-ticket-task-modal/time-ticket-task-modal.container" - ) -); -const PaymentModalContainer = lazy(() => - import("../../components/payment-modal/payment-modal.container") -); -const ProductionListPage = lazy(() => - import("../production-list/production-list.container") -); -const ProductionBoardPage = lazy(() => - import("../production-board/production-board.container") + import("../../components/time-ticket-task-modal/time-ticket-task-modal.container") ); +const PaymentModalContainer = lazy(() => import("../../components/payment-modal/payment-modal.container")); +const ProductionListPage = lazy(() => import("../production-list/production-list.container")); +const ProductionBoardPage = lazy(() => import("../production-board/production-board.container")); // const ShopTemplates = lazy(() => // import("../shop-templates/shop-templates.container") // ); -const JobIntake = lazy(() => - import("../jobs-intake/jobs-intake.page.container") -); -const JobChecklistView = lazy(() => - import("../jobs-checklist-view/jobs-checklist-view.page") -); -const JobDeliver = lazy(() => - import("../jobs-deliver/jobs-delivery.page.container") -); -const AccountingQboCallback = lazy(() => - import("../accounting-qbo/accounting-qbo.page") -); -const AccountingReceivables = lazy(() => - import("../accounting-receivables/accounting-receivables.container") -); -const AccountingPayables = lazy(() => - import("../accounting-payables/accounting-payables.container") -); -const AccountingPayments = lazy(() => - import("../accounting-payments/accounting-payments.container") -); +const JobIntake = lazy(() => import("../jobs-intake/jobs-intake.page.container")); +const JobChecklistView = lazy(() => import("../jobs-checklist-view/jobs-checklist-view.page")); +const JobDeliver = lazy(() => import("../jobs-deliver/jobs-delivery.page.container")); +const AccountingQboCallback = lazy(() => import("../accounting-qbo/accounting-qbo.page")); +const AccountingReceivables = lazy(() => import("../accounting-receivables/accounting-receivables.container")); +const AccountingPayables = lazy(() => import("../accounting-payables/accounting-payables.container")); +const AccountingPayments = lazy(() => import("../accounting-payments/accounting-payments.container")); const AllJobs = lazy(() => import("../jobs-all/jobs-all.container")); const ReadyJobs = lazy(() => import("../jobs-ready/jobs-ready.page")); const JobsClose = lazy(() => import("../jobs-close/jobs-close.container")); const JobsAdmin = lazy(() => import("../jobs-admin/jobs-admin.page")); -const TempDocs = lazy(() => - import("../temporary-docs/temporary-docs.container") -); +const TempDocs = lazy(() => import("../temporary-docs/temporary-docs.container")); -const ShopCsiPageContainer = lazy(() => - import("../shop-csi/shop-csi.container.page") -); -const PaymentsAll = lazy(() => - import("../payments-all/payments-all.container.page") -); +const ShopCsiPageContainer = lazy(() => import("../shop-csi/shop-csi.container.page")); +const PaymentsAll = lazy(() => import("../payments-all/payments-all.container.page")); const ShiftClock = lazy(() => import("../shift-clock/shift-clock.page")); -const Scoreboard = lazy(() => - import("../scoreboard/scoreboard.page.container") -); -const TimeTicketsAll = lazy(() => - import("../time-tickets/time-tickets.container") -); +const Scoreboard = lazy(() => import("../scoreboard/scoreboard.page.container")); +const TimeTicketsAll = lazy(() => import("../time-tickets/time-tickets.container")); const Help = lazy(() => import("../help/help.page")); -const PartsQueue = lazy(() => - import("../parts-queue/parts-queue.page.container") -); -const ExportLogs = lazy(() => - import("../export-logs/export-logs.page.container") -); +const PartsQueue = lazy(() => import("../parts-queue/parts-queue.page.container")); +const ExportLogs = lazy(() => import("../export-logs/export-logs.page.container")); const Phonebook = lazy(() => import("../phonebook/phonebook.page.container")); -const EmailTest = lazy(() => - import("../../components/email-test/email-test-component") -); +const EmailTest = lazy(() => import("../../components/email-test/email-test-component")); const Dashboard = lazy(() => import("../dashboard/dashboard.container")); const Dms = lazy(() => import("../dms/dms.container")); -const DmsPayables = lazy(() => - import("../dms-payables/dms-payables.container") -); -const ManageRootPage = lazy(() => - import("../manage-root/manage-root.page.container") -); -const TtApprovals = lazy(() => - import("../tt-approvals/tt-approvals.page.container") -); - -const {Content, Footer} = Layout; - +const DmsPayables = lazy(() => import("../dms-payables/dms-payables.container")); +const ManageRootPage = lazy(() => import("../manage-root/manage-root.page.container")); +const TtApprovals = lazy(() => import("../tt-approvals/tt-approvals.page.container")); +const { Content, Footer } = Layout; const mapStateToProps = createStructuredSelector({ - conflict: selectInstanceConflict, - bodyshop: selectBodyshop, - enableJoyRide: selectEnableJoyRide, - joyRideSteps: selectJoyRideSteps + conflict: selectInstanceConflict, + bodyshop: selectBodyshop, + enableJoyRide: selectEnableJoyRide, + joyRideSteps: selectJoyRideSteps }); - const mapDispatchToProps = (dispatch) => ({ - setJoyRideFinished: (steps) => dispatch(setJoyRideFinished(steps)), - + setJoyRideFinished: (steps) => dispatch(setJoyRideFinished(steps)) }); -export function Manage({conflict, bodyshop,enableJoyRide,joyRideSteps,setJoyRideFinished}) { - const {t} = useTranslation(); - const [chatVisible] = useState(false); -const [tours, setTours] = useState([]) +export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished }) { + const { t } = useTranslation(); + const [chatVisible] = useState(false); + const [tours, setTours] = useState([]); + useEffect(() => { + const widgetId = InstanceRenderManager({ + imex: "IABVNO4scRKY11XBQkNr", + rome: "mQdqARMzkZRUVugJ6TdS" + }); + window.noticeable.render("widget", widgetId); + requestForToken().catch((error) => { + console.error(`Unable to request for token.`, error); + }); + }, []); - useEffect(() => { - const widgetId = InstanceRenderManager({ imex:"IABVNO4scRKY11XBQkNr" ,rome: "mQdqARMzkZRUVugJ6TdS"}) ; - window.noticeable.render("widget", widgetId); - requestForToken().catch((error) => { - console.error(`Unable to request for token.`, error) - }); - }, []); + useEffect(() => { + document.title = InstanceRenderManager({ + imex: t("titles.imexonline"), + rome: t("titles.romeonline"), + promanager: t("titles.promanager") + }); + }, [t]); + const AppRouteTable = ( + + } + This + > + - useEffect(() => { - document.title = InstanceRenderManager({imex: t("titles.imexonline"), rome: t("titles.romeonline"), promanager:t("titles.promanager")}); - }, [t]); - const AppRouteTable = ( - } This - > - + - + + + + + + + + + + } /> + } /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + { + // } + // /> + } + }> + + + } + /> + }> + + + } + /> - - - - - - - - - - }/> - }/> - }> - - - }/> - }> - - } - /> - }> - - } - /> - }> - - } - /> - }> - - } - /> - }> - - } - /> - }> - - }/> - }> - - }/> - }> - - } - /> - }> - - }/> - }> - - }/> - }> - - } - /> - }> - - } - /> - }> - - } - /> - }> - - } - /> - }> - - } - /> - }> - - } - /> - }> - - } - /> - }> - - }/> - }> - - } - /> - }> - - } - /> - }> - - } - /> - }> - - } - /> - }> - - }/> - }> - - }/> - }> - - } - /> - }> - - } - /> - }> - - } - /> - }> - - }/> - { - // } - // /> - } - }> - - } - /> - }> - - } - /> + }> + + + } + /> - }> - - } - /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> - }> - - } - /> - }> - - } - /> - }> - - } - /> - }> - - } - /> - }> - - }/> - }> - - }/> - }> - - }/> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + } /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + + + ); - }> - - }/> - }> - - }/> - }> - - }/> - }> - - } - /> - }> - - }/> - }/> - }> - - }/> - }> - - }/> - }> - - }/> - - - ); + let PageContent; - let PageContent; + if (conflict) PageContent = ; + else if (bodyshop && bodyshop.sub_status !== "active") PageContent = ; + else PageContent = AppRouteTable; - if (conflict) PageContent = ; - else if (bodyshop && bodyshop.sub_status !== "active") - PageContent = ; - else PageContent = AppRouteTable; + return ( + <> + + + + + + { + if (props.action === "reset") { + setJoyRideFinished(); + } + }} + /> + + } showDialog> + {PageContent} + - return ( - <> - - - - - - { - if (props.action === 'reset') { - setJoyRideFinished(); - } - }} - /> - - } showDialog> - {PageContent} - - - - -
    -
    -
    -
    - {`${InstanceRenderManager({ - imex: t('titles.imexonline'), - rome: t('titles.romeonline'), - promanager: t('titles.promanager'), - })} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`} -
    -
    + + +
    +
    +
    +
    + {`${InstanceRenderManager({ + imex: t("titles.imexonline"), + rome: t("titles.romeonline"), + promanager: t("titles.promanager") + })} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
    - - Disclaimer & Notices - +
    - {InstanceRenderManager({ - promanager: ( - - - - - {tours.map((tour) => ( - window.productFruits.api.tours.tryStartTour(tour.id)} - > - {tour.name} - - ))} - - - - ), - })} -
    - - - ); + + Disclaimer & Notices + +
    + {InstanceRenderManager({ + promanager: ( + + + + + {tours.map((tour) => ( + window.productFruits.api.tours.tryStartTour(tour.id)}> + {tour.name} + + ))} + + + + ) + })} +
    +
    + + ); } export default connect(mapStateToProps, mapDispatchToProps)(Manage); diff --git a/client/src/pages/manage/manage.page.container.jsx b/client/src/pages/manage/manage.page.container.jsx index 4de204d47..5b56bc49a 100644 --- a/client/src/pages/manage/manage.page.container.jsx +++ b/client/src/pages/manage/manage.page.container.jsx @@ -1,36 +1,35 @@ -import {useQuery} from "@apollo/client"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import { useQuery } from "@apollo/client"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import AlertComponent from "../../components/alert/alert.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; -import {QUERY_BODYSHOP} from "../../graphql/bodyshop.queries"; -import {setBodyshop} from "../../redux/user/user.actions"; +import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries"; +import { setBodyshop } from "../../redux/user/user.actions"; import ManagePage from "./manage.page.component"; const mapDispatchToProps = (dispatch) => ({ - setBodyshop: (bs) => dispatch(setBodyshop(bs)), + setBodyshop: (bs) => dispatch(setBodyshop(bs)) }); -function ManagePageContainer({setBodyshop}) { - const {loading, error, data} = useQuery(QUERY_BODYSHOP, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); +function ManagePageContainer({ setBodyshop }) { + const { loading, error, data } = useQuery(QUERY_BODYSHOP, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - const {t} = useTranslation(); + const { t } = useTranslation(); - useEffect(() => { - if (data) { - setBodyshop(data.bodyshops[0] || {notfound: true}); - } - }, [data, setBodyshop]); + useEffect(() => { + if (data) { + setBodyshop(data.bodyshops[0] || { notfound: true }); + } + }, [data, setBodyshop]); - if (loading) - return ; - if (error) return ; + if (loading) return ; + if (error) return ; - return ; + return ; } export default connect(null, mapDispatchToProps)(ManagePageContainer); diff --git a/client/src/pages/owners-detail/owners-detail.page.component.jsx b/client/src/pages/owners-detail/owners-detail.page.component.jsx index 3522e5657..63d3d09e8 100644 --- a/client/src/pages/owners-detail/owners-detail.page.component.jsx +++ b/client/src/pages/owners-detail/owners-detail.page.component.jsx @@ -1,18 +1,18 @@ -import {Col, Divider, Row} from "antd"; +import { Col, Divider, Row } from "antd"; import React from "react"; import OwnerDetailForm from "../../components/owner-detail-form/owner-detail-form.container"; import OwnerDetailJobsComponent from "../../components/owner-detail-jobs/owner-detail-jobs.component"; -export default function OwnersDetailComponent({owner, refetch}) { - return ( - -
    - - - - - - - - ); +export default function OwnersDetailComponent({ owner, refetch }) { + return ( + + + + + + + + + + ); } diff --git a/client/src/pages/owners-detail/owners-detail.page.container.jsx b/client/src/pages/owners-detail/owners-detail.page.container.jsx index 9a1c2ed3e..95904c01a 100644 --- a/client/src/pages/owners-detail/owners-detail.page.container.jsx +++ b/client/src/pages/owners-detail/owners-detail.page.container.jsx @@ -1,75 +1,70 @@ -import {useQuery} from "@apollo/client"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useParams} from 'react-router-dom'; +import { useQuery } from "@apollo/client"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useParams } from "react-router-dom"; import AlertComponent from "../../components/alert/alert.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {QUERY_OWNER_BY_ID} from "../../graphql/owners.queries"; -import {addRecentItem, setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {CreateRecentItem} from "../../utils/create-recent-item"; +import { QUERY_OWNER_BY_ID } from "../../graphql/owners.queries"; +import { addRecentItem, setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { CreateRecentItem } from "../../utils/create-recent-item"; import OwnersDetailComponent from "./owners-detail.page.component"; import NotFound from "../../components/not-found/not-found.component"; -import {OwnerNameDisplayFunction} from "../../components/owner-name-display/owner-name-display.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - addRecentItem: (item) => dispatch(addRecentItem(item)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + addRecentItem: (item) => dispatch(addRecentItem(item)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function OwnersDetailContainer({ - setBreadcrumbs, - addRecentItem, - setSelectedHeader, - }) { - const {ownerId} = useParams(); - const {t} = useTranslation(); +export function OwnersDetailContainer({ setBreadcrumbs, addRecentItem, setSelectedHeader }) { + const { ownerId } = useParams(); + const { t } = useTranslation(); - const {loading, data, error, refetch} = useQuery(QUERY_OWNER_BY_ID, { - variables: {id: ownerId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", + const { loading, data, error, refetch } = useQuery(QUERY_OWNER_BY_ID, { + variables: { id: ownerId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + + useEffect(() => { + document.title = t("titles.owners-detail", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }), + name: data ? OwnerNameDisplayFunction(data.owners_by_pk) : "" }); + setSelectedHeader("owners"); + setBreadcrumbs([ + { link: "/manage/owners", label: t("titles.bc.owners") }, + { + link: `/manage/owners/${ownerId}`, + label: t("titles.bc.owner-detail", { + name: data ? OwnerNameDisplayFunction(data.owners_by_pk) : "" + }) + } + ]); - useEffect(() => { - document.title = t("titles.owners-detail", { - app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'}), - name: data ? OwnerNameDisplayFunction(data.owners_by_pk) : "", - }); - setSelectedHeader("owners"); - setBreadcrumbs([ - {link: "/manage/owners", label: t("titles.bc.owners")}, - { - link: `/manage/owners/${ownerId}`, - label: t("titles.bc.owner-detail", { - name: data ? OwnerNameDisplayFunction(data.owners_by_pk) : "", - }), - }, - ]); + if (data && data.owners_by_pk) + addRecentItem( + CreateRecentItem(ownerId, "owner", OwnerNameDisplayFunction(data.owners_by_pk), `/manage/owners/${ownerId}`) + ); + }, [setBreadcrumbs, t, data, ownerId, addRecentItem, setSelectedHeader]); - if (data && data.owners_by_pk) - addRecentItem( - CreateRecentItem( - ownerId, - "owner", - OwnerNameDisplayFunction(data.owners_by_pk), - `/manage/owners/${ownerId}` - ) - ); - }, [setBreadcrumbs, t, data, ownerId, addRecentItem, setSelectedHeader]); + if (loading) return ; + if (error) return ; + if (!!!data.owners_by_pk) return ; - if (loading) return ; - if (error) return ; - if (!!!data.owners_by_pk) return ; - - return ( - - - - ); + return ( + + + + ); } export default connect(null, mapDispatchToProps)(OwnersDetailContainer); diff --git a/client/src/pages/owners/owners.page.component.jsx b/client/src/pages/owners/owners.page.component.jsx index 60927893e..2a975c373 100644 --- a/client/src/pages/owners/owners.page.component.jsx +++ b/client/src/pages/owners/owners.page.component.jsx @@ -2,5 +2,5 @@ import React from "react"; import OwnersListContainer from "../../components/owners-list/owners-list.container"; export default function OwnersPageComponent() { - return ; + return ; } diff --git a/client/src/pages/owners/owners.page.container.jsx b/client/src/pages/owners/owners.page.container.jsx index e796e0cb8..0729e2d84 100644 --- a/client/src/pages/owners/owners.page.container.jsx +++ b/client/src/pages/owners/owners.page.container.jsx @@ -1,29 +1,35 @@ -import React, {useEffect} from "react"; +import React, { useEffect } from "react"; import OwnersPageComponent from "./owners.page.component"; -import {useTranslation} from "react-i18next"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {connect} from "react-redux"; +import { useTranslation } from "react-i18next"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { connect } from "react-redux"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function OwnersPageContainer({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); - useEffect(() => { - document.title = t("titles.owners",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("owners"); - setBreadcrumbs([{link: "/manage/owners", label: t("titles.bc.owners")}]); - }, [t, setBreadcrumbs, setSelectedHeader]); +export function OwnersPageContainer({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); + useEffect(() => { + document.title = t("titles.owners", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("owners"); + setBreadcrumbs([{ link: "/manage/owners", label: t("titles.bc.owners") }]); + }, [t, setBreadcrumbs, setSelectedHeader]); - return ( - - - - ); + return ( + + + + ); } export default connect(null, mapDispatchToProps)(OwnersPageContainer); diff --git a/client/src/pages/parts-queue/parts-queue.page.container.jsx b/client/src/pages/parts-queue/parts-queue.page.container.jsx index 1ec99b606..91572e276 100644 --- a/client/src/pages/parts-queue/parts-queue.page.container.jsx +++ b/client/src/pages/parts-queue/parts-queue.page.container.jsx @@ -1,34 +1,38 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import PartsQueueDetailCard from "../../components/parts-queue-card/parts-queue-card.component"; import PartsQueueList from "../../components/parts-queue-list/parts-queue.list.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function PartsQueuePageContainer({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); +export function PartsQueuePageContainer({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.parts-queue",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("parts-queue"); - setBreadcrumbs([ - {link: "/manage/partsqueue", label: t("titles.bc.parts-queue")}, - ]); - }, [setBreadcrumbs, t, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.parts-queue", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("parts-queue"); + setBreadcrumbs([{ link: "/manage/partsqueue", label: t("titles.bc.parts-queue") }]); + }, [setBreadcrumbs, t, setSelectedHeader]); - return ( - - - - - ); + return ( + + + + + ); } export default connect(null, mapDispatchToProps)(PartsQueuePageContainer); diff --git a/client/src/pages/payments-all/payments-all.container.page.jsx b/client/src/pages/payments-all/payments-all.container.page.jsx index 61c3875d6..9e63b8dfd 100644 --- a/client/src/pages/payments-all/payments-all.container.page.jsx +++ b/client/src/pages/payments-all/payments-all.container.page.jsx @@ -1,79 +1,76 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import queryString from "query-string"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import AlertComponent from "../../components/alert/alert.component"; import PaymentsListPaginated from "../../components/payments-list-paginated/payment-list-paginated.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {QUERY_ALL_PAYMENTS_PAGINATED} from "../../graphql/payments.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {pageLimit} from "../../utils/config"; +import { QUERY_ALL_PAYMENTS_PAGINATED } from "../../graphql/payments.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { pageLimit } from "../../utils/config"; import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function AllJobs({bodyshop, setBreadcrumbs, setSelectedHeader}) { - const searchParams = queryString.parse(useLocation().search); - const {page, sortcolumn, sortorder, searchObj} = searchParams; +export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) { + const searchParams = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder, searchObj } = searchParams; - const {loading, error, data, refetch} = useQuery( - QUERY_ALL_PAYMENTS_PAGINATED, - { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - variables: { - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, - order: [ - searchObj - ? JSON.parse(searchObj) - : { - [sortcolumn || "date"]: sortorder - ? sortorder === "descend" - ? "desc" - : "asc" - : "desc", - }, - ], - }, - } - ); - const {t} = useTranslation(); + const { loading, error, data, refetch } = useQuery(QUERY_ALL_PAYMENTS_PAGINATED, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, + order: [ + searchObj + ? JSON.parse(searchObj) + : { + [sortcolumn || "date"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc" + } + ] + } + }); + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.payments-all",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("allpayments"); - setBreadcrumbs([ - {link: "/manage/payments", label: t("titles.bc.payments-all")}, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.payments-all", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("allpayments"); + setBreadcrumbs([{ link: "/manage/payments", label: t("titles.bc.payments-all") }]); + }, [t, setBreadcrumbs, setSelectedHeader]); - if (error) return ; - return ( - - - - - - ); + if (error) return ; + return ( + + + + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(AllJobs); diff --git a/client/src/pages/phonebook/phonebook.page.component.jsx b/client/src/pages/phonebook/phonebook.page.component.jsx index af61bfca0..c52283e1a 100644 --- a/client/src/pages/phonebook/phonebook.page.component.jsx +++ b/client/src/pages/phonebook/phonebook.page.component.jsx @@ -1,216 +1,207 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {useQuery} from "@apollo/client"; -import {Button, Card, Input, Space, Table, Typography} from "antd"; +import { SyncOutlined } from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import { Button, Card, Input, Space, Table, Typography } from "antd"; import _ from "lodash"; import queryString from "query-string"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import AlertComponent from "../../components/alert/alert.component"; -import {QUERY_PHONEBOOK_PAGINATED} from "../../graphql/phonebook.queries"; -import {selectAuthLevel, selectBodyshop,} from "../../redux/user/user.selectors"; +import { QUERY_PHONEBOOK_PAGINATED } from "../../graphql/phonebook.queries"; +import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors"; import ChatOpenButton from "../../components/chat-open-button/chat-open-button.component"; -import {alphaSort} from "../../utils/sorters"; -import {HasRbacAccess} from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {pageLimit} from "../../utils/config"; +import { alphaSort } from "../../utils/sorters"; +import { HasRbacAccess } from "../../components/rbac-wrapper/rbac-wrapper.component"; +import { pageLimit } from "../../utils/config"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - authLevel: selectAuthLevel, + bodyshop: selectBodyshop, + authLevel: selectAuthLevel }); -export function PhonebookPageComponent({bodyshop, authLevel}) { - const searchParams = queryString.parse(useLocation().search); - const {page, sortcolumn, sortorder, search, phonebookentry} = searchParams; - const history = useNavigate(); +export function PhonebookPageComponent({ bodyshop, authLevel }) { + const searchParams = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder, search, phonebookentry } = searchParams; + const history = useNavigate(); - const {loading, error, data, refetch} = useQuery( - QUERY_PHONEBOOK_PAGINATED, + const { loading, error, data, refetch } = useQuery(QUERY_PHONEBOOK_PAGINATED, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + search: search || "", + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, + order: [ { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - variables: { - search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, - order: [ - { - [sortcolumn || "lastname"]: sortorder - ? sortorder === "descend" - ? "desc" - : "asc" - : "asc", - }, - ], - }, + [sortcolumn || "lastname"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "asc" } - ); + ] + } + }); - const {t} = useTranslation(); + const { t } = useTranslation(); - if (error) return ; + if (error) return ; - const handleTableChange = (pagination, filters, sorter) => { - searchParams.page = pagination.current; - searchParams.sortcolumn = sorter.columnKey; - searchParams.sortorder = sorter.order; - if (filters.status) { - searchParams.statusFilters = JSON.stringify( - _.flattenDeep(filters.status) - ); - } else { - delete searchParams.statusFilters; - } - history({search: queryString.stringify(searchParams)}); - }; + const handleTableChange = (pagination, filters, sorter) => { + searchParams.page = pagination.current; + searchParams.sortcolumn = sorter.columnKey; + searchParams.sortorder = sorter.order; + if (filters.status) { + searchParams.statusFilters = JSON.stringify(_.flattenDeep(filters.status)); + } else { + delete searchParams.statusFilters; + } + history({ search: queryString.stringify(searchParams) }); + }; - const columns = [ - { - title: t("phonebook.fields.firstname"), - dataIndex: "firstname", - key: "firstname", - sorter: (a, b) => alphaSort(a.firstname, b.firstname), - sortOrder: sortcolumn === "firstname" && sortorder, - }, - { - title: t("phonebook.fields.lastname"), - dataIndex: "lastname", - key: "lastname", - sorter: (a, b) => alphaSort(a.lastname, b.lastname), - sortOrder: sortcolumn === "lastname" && sortorder, - }, - { - title: t("phonebook.fields.company"), - dataIndex: "company", - key: "company", - sorter: (a, b) => alphaSort(a.company, b.company), - sortOrder: sortcolumn === "company" && sortorder, - }, - { - title: t("phonebook.fields.category"), - dataIndex: "category", - key: "category", - sorter: (a, b) => alphaSort(a.category, b.category), - sortOrder: sortcolumn === "category" && sortorder, - }, - { - title: t("phonebook.fields.email"), - dataIndex: "email", - key: "email", - }, - { - title: t("phonebook.fields.phone1"), - dataIndex: "phone1", - key: "phone1", - render: (text, record) => , - }, - { - title: t("phonebook.fields.phone2"), - dataIndex: "phone2", - key: "phone2", - render: (text, record) => , - }, - { - title: t("phonebook.fields.address1"), - dataIndex: "address1", - key: "address1", - }, - { - title: t("phonebook.fields.city"), - dataIndex: "city", - key: "city", - }, - ]; + const columns = [ + { + title: t("phonebook.fields.firstname"), + dataIndex: "firstname", + key: "firstname", + sorter: (a, b) => alphaSort(a.firstname, b.firstname), + sortOrder: sortcolumn === "firstname" && sortorder + }, + { + title: t("phonebook.fields.lastname"), + dataIndex: "lastname", + key: "lastname", + sorter: (a, b) => alphaSort(a.lastname, b.lastname), + sortOrder: sortcolumn === "lastname" && sortorder + }, + { + title: t("phonebook.fields.company"), + dataIndex: "company", + key: "company", + sorter: (a, b) => alphaSort(a.company, b.company), + sortOrder: sortcolumn === "company" && sortorder + }, + { + title: t("phonebook.fields.category"), + dataIndex: "category", + key: "category", + sorter: (a, b) => alphaSort(a.category, b.category), + sortOrder: sortcolumn === "category" && sortorder + }, + { + title: t("phonebook.fields.email"), + dataIndex: "email", + key: "email" + }, + { + title: t("phonebook.fields.phone1"), + dataIndex: "phone1", + key: "phone1", + render: (text, record) => + }, + { + title: t("phonebook.fields.phone2"), + dataIndex: "phone2", + key: "phone2", + render: (text, record) => + }, + { + title: t("phonebook.fields.address1"), + dataIndex: "address1", + key: "address1" + }, + { + title: t("phonebook.fields.city"), + dataIndex: "city", + key: "city" + } + ]; - const handleNewPhonebook = () => { - searchParams.phonebookentry = "new"; - history({search: queryString.stringify(searchParams)}); - }; + const handleNewPhonebook = () => { + searchParams.phonebookentry = "new"; + history({ search: queryString.stringify(searchParams) }); + }; - const handleOnRowClick = (record) => { - if (record) { - searchParams.phonebookentry = record.id; - history({search: queryString.stringify(searchParams)}); - } else { - delete searchParams.phonebookentry; - history({search: queryString.stringify(searchParams)}); - } - }; - const hasNoAccess = !HasRbacAccess({ - bodyshop, - authLevel, - action: "phonebook:edit", - }); + const handleOnRowClick = (record) => { + if (record) { + searchParams.phonebookentry = record.id; + history({ search: queryString.stringify(searchParams) }); + } else { + delete searchParams.phonebookentry; + history({ search: queryString.stringify(searchParams) }); + } + }; + const hasNoAccess = !HasRbacAccess({ + bodyshop, + authLevel, + action: "phonebook:edit" + }); - return ( - - {searchParams.search && ( - <> - - {t("general.labels.searchresults", { - search: searchParams.search, - })} - - - - )} - - - { - searchParams.search = value; - searchParams.page = 1; - history({search: queryString.stringify(searchParams)}); - }} - /> - + return ( + + {searchParams.search && ( + <> + + {t("general.labels.searchresults", { + search: searchParams.search + })} + + + + )} + + + { + searchParams.search = value; + searchParams.page = 1; + history({ search: queryString.stringify(searchParams) }); + }} + /> + + } + > +
    { + return { + onClick: (event) => { + handleOnRowClick(record); } - > -
    { - return { - onClick: (event) => { - handleOnRowClick(record); - }, - }; - }} - /> - - ); + }; + }} + /> + + ); } export default connect(mapStateToProps, null)(PhonebookPageComponent); diff --git a/client/src/pages/phonebook/phonebook.page.container.jsx b/client/src/pages/phonebook/phonebook.page.container.jsx index 71d5a4594..fe407cec3 100644 --- a/client/src/pages/phonebook/phonebook.page.container.jsx +++ b/client/src/pages/phonebook/phonebook.page.container.jsx @@ -1,70 +1,74 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import PhonebookPage from "./phonebook.page.component"; -import {Drawer, Grid} from "antd"; -import {useLocation, useNavigate} from "react-router-dom"; +import { Drawer, Grid } from "antd"; +import { useLocation, useNavigate } from "react-router-dom"; import PhonebookFormContainer from "../../components/phonebook-form/phonebook-form.container"; import queryString from "query-string"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function PhonebookContainer({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); +export function PhonebookContainer({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.phonebook", {app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("phonebook"); - setBreadcrumbs([ - { - link: "/manage/phonebook", - label: t("titles.bc.phonebook"), - }, - ]); - }, [setBreadcrumbs, t, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.phonebook", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("phonebook"); + setBreadcrumbs([ + { + link: "/manage/phonebook", + label: t("titles.bc.phonebook") + } + ]); + }, [setBreadcrumbs, t, setSelectedHeader]); - const search = queryString.parse(useLocation().search); - const {phonebookentry} = search; + const search = queryString.parse(useLocation().search); + const { phonebookentry } = search; - const navigate = useNavigate(); + const navigate = useNavigate(); - const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) - .filter((screen) => !!screen[1]) - .slice(-1)[0]; + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) + .filter((screen) => !!screen[1]) + .slice(-1)[0]; - const bpoints = { - xs: "100%", - sm: "100%", - md: "100%", - lg: "50%", - xl: "50%", - xxl: "45%", - }; - const drawerPercentage = selectedBreakpoint - ? bpoints[selectedBreakpoint[0]] - : "100%"; + const bpoints = { + xs: "100%", + sm: "100%", + md: "100%", + lg: "50%", + xl: "50%", + xxl: "45%" + }; + const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%"; - return ( - ( - - { - delete search.phonebookentry; - navigate({search: queryString.stringify(search)}); - }} - open={phonebookentry} - > - - - ) - ); + return ( + + + { + delete search.phonebookentry; + navigate({ search: queryString.stringify(search) }); + }} + open={phonebookentry} + > + + + + ); } export default connect(null, mapDispatchToProps)(PhonebookContainer); diff --git a/client/src/pages/production-board/production-board.component.jsx b/client/src/pages/production-board/production-board.component.jsx index 02a1d3a7b..9b69da414 100644 --- a/client/src/pages/production-board/production-board.component.jsx +++ b/client/src/pages/production-board/production-board.component.jsx @@ -2,5 +2,5 @@ import React from "react"; import ProductionBoardKanbanContainer from "../../components/production-board-kanban/production-board-kanban.container"; export default function ProductionBoardComponent() { - return ; + return ; } diff --git a/client/src/pages/production-board/production-board.container.jsx b/client/src/pages/production-board/production-board.container.jsx index a91c26dcb..f646d16c1 100644 --- a/client/src/pages/production-board/production-board.container.jsx +++ b/client/src/pages/production-board/production-board.container.jsx @@ -1,51 +1,50 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import ProductionBoardComponent from "./production-board.component"; import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function ProductionBoardContainer({ - setBreadcrumbs, - bodyshop, - setSelectedHeader, - }) { - const {t} = useTranslation(); +export function ProductionBoardContainer({ setBreadcrumbs, bodyshop, setSelectedHeader }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.productionboard",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("productionboard"); - setBreadcrumbs([ - { - link: "/manage/production/board", - label: t("titles.bc.productionboard"), - }, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.productionboard", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("productionboard"); + setBreadcrumbs([ + { + link: "/manage/production/board", + label: t("titles.bc.productionboard") + } + ]); + }, [t, setBreadcrumbs, setSelectedHeader]); - return ( - - - - - - ); + return ( + + + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ProductionBoardContainer); +export default connect(mapStateToProps, mapDispatchToProps)(ProductionBoardContainer); diff --git a/client/src/pages/production-list/production-list.component.jsx b/client/src/pages/production-list/production-list.component.jsx index dd5b29a86..116d6cb20 100644 --- a/client/src/pages/production-list/production-list.component.jsx +++ b/client/src/pages/production-list/production-list.component.jsx @@ -2,5 +2,5 @@ import React from "react"; import ProductionListTable from "../../components/production-list-table/production-list-table.container"; export default function ProductionListComponent() { - return ; + return ; } diff --git a/client/src/pages/production-list/production-list.container.jsx b/client/src/pages/production-list/production-list.container.jsx index 312fd63b2..9c7cc4892 100644 --- a/client/src/pages/production-list/production-list.container.jsx +++ b/client/src/pages/production-list/production-list.container.jsx @@ -1,36 +1,40 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import ProductionListComponent from "./production-list.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); export function ProductionListContainer({ - setBreadcrumbs, + setBreadcrumbs, - setSelectedHeader, - }) { - const {t} = useTranslation(); + setSelectedHeader +}) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.productionlist",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("productionlist"); - setBreadcrumbs([ - {link: "/manage/production/list", label: t("titles.bc.productionlist")}, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.productionlist", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("productionlist"); + setBreadcrumbs([{ link: "/manage/production/list", label: t("titles.bc.productionlist") }]); + }, [t, setBreadcrumbs, setSelectedHeader]); - return ( - - - - ); + return ( + + + + ); } export default connect(null, mapDispatchToProps)(ProductionListContainer); diff --git a/client/src/pages/profile/profile.container.page.jsx b/client/src/pages/profile/profile.container.page.jsx index a28c11e2a..3ab67ec65 100644 --- a/client/src/pages/profile/profile.container.page.jsx +++ b/client/src/pages/profile/profile.container.page.jsx @@ -1,26 +1,30 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import ProfilePage from "./profile.page"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function ProfileContainerPage({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); - useEffect(() => { - setSelectedHeader("profile"); - setBreadcrumbs([ - {link: "/manage/profile", label: t("titles.bc.profile")}, - ]); - document.title = t("titles.profile",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - }, [t, setBreadcrumbs, setSelectedHeader]); +export function ProfileContainerPage({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); + useEffect(() => { + setSelectedHeader("profile"); + setBreadcrumbs([{ link: "/manage/profile", label: t("titles.bc.profile") }]); + document.title = t("titles.profile", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + }, [t, setBreadcrumbs, setSelectedHeader]); - return ; + return ; } export default connect(null, mapDispatchToProps)(ProfileContainerPage); diff --git a/client/src/pages/profile/profile.page.jsx b/client/src/pages/profile/profile.page.jsx index 478d1cfe4..3d897eda8 100644 --- a/client/src/pages/profile/profile.page.jsx +++ b/client/src/pages/profile/profile.page.jsx @@ -1,15 +1,15 @@ -import {Row} from "antd"; +import { Row } from "antd"; import React from "react"; import ProfileMyComponent from "../../components/profile-my/profile-my.component"; import ProfileShopsContainer from "../../components/profile-shops/profile-shops.container"; export default function ProfilePage() { - return ( -
    - - - - -
    - ); + return ( +
    + + + + +
    + ); } diff --git a/client/src/pages/reset-password/reset-password.component.jsx b/client/src/pages/reset-password/reset-password.component.jsx index 4e088c429..74e07d2a5 100644 --- a/client/src/pages/reset-password/reset-password.component.jsx +++ b/client/src/pages/reset-password/reset-password.component.jsx @@ -1,14 +1,13 @@ import React from "react"; -import {useLocation} from "react-router-dom"; +import { useLocation } from "react-router-dom"; import UserRequestResetPw from "../../components/user-request-pw-reset/user-request-reset-pw.component"; import UserValidatePwReset from "../../components/user-validate-pw-reset/user-validate-pw-reset.component"; import queryString from "query-string"; export default function ResetPassword() { - const searchParams = queryString.parse(useLocation().search); - const {mode, oobCode} = searchParams; + const searchParams = queryString.parse(useLocation().search); + const { mode, oobCode } = searchParams; - if (mode === "resetPassword") - return ; - return ; -} \ No newline at end of file + if (mode === "resetPassword") return ; + return ; +} diff --git a/client/src/pages/schedule/schedule.page.component.jsx b/client/src/pages/schedule/schedule.page.component.jsx index 7be62df2f..3be49895f 100644 --- a/client/src/pages/schedule/schedule.page.component.jsx +++ b/client/src/pages/schedule/schedule.page.component.jsx @@ -2,5 +2,5 @@ import React from "react"; import ScheduleCalendarContainer from "../../components/schedule-calendar/schedule-calendar.container"; export default function SchedulePageComponent() { - return ; + return ; } diff --git a/client/src/pages/schedule/schedule.page.container.jsx b/client/src/pages/schedule/schedule.page.container.jsx index dfbf390b2..9ee7ff34c 100644 --- a/client/src/pages/schedule/schedule.page.container.jsx +++ b/client/src/pages/schedule/schedule.page.container.jsx @@ -1,32 +1,36 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import SchedulePageComponent from "./schedule.page.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function SchedulePageContainer({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); +export function SchedulePageContainer({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.schedule",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("schedule"); - setBreadcrumbs([ - {link: "/manage/schedule", label: t("titles.bc.schedule")}, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.schedule", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("schedule"); + setBreadcrumbs([{ link: "/manage/schedule", label: t("titles.bc.schedule") }]); + }, [t, setBreadcrumbs, setSelectedHeader]); - return ( - - - - ); + return ( + + + + ); } export default connect(null, mapDispatchToProps)(SchedulePageContainer); diff --git a/client/src/pages/scoreboard/scoreboard.page.container.jsx b/client/src/pages/scoreboard/scoreboard.page.container.jsx index 92138057b..693a1b977 100644 --- a/client/src/pages/scoreboard/scoreboard.page.container.jsx +++ b/client/src/pages/scoreboard/scoreboard.page.container.jsx @@ -1,34 +1,34 @@ -import Icon, {FieldTimeOutlined} from "@ant-design/icons"; -import {Tabs} from "antd"; +import Icon, { FieldTimeOutlined } from "@ant-design/icons"; +import { Tabs } from "antd"; import queryString from "query-string"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {FaShieldAlt} from "react-icons/fa"; -import {connect} from "react-redux"; -import {useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { FaShieldAlt } from "react-icons/fa"; +import { connect } from "react-redux"; +import { useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import ScoreboardDisplay from "../../components/scoreboard-display/scoreboard-display.component"; import ScoreboardTimeTicketsStats from "../../components/scoreboard-timetickets-stats/scoreboard-timetickets.component"; import ScoreboardTimeTickets from "../../components/scoreboard-timetickets/scoreboard-timetickets.component"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; /** * Mapping state to props */ const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); /** * Mapping dispatch to props */ const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); /** @@ -37,74 +37,77 @@ const mapDispatchToProps = (dispatch) => ({ * @param {Function} props.setBreadcrumbs - Function to set breadcrumbs. * @param {Function} props.setSelectedHeader - Function to set selected header. */ -export function ScoreboardContainer({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); - const searchParams = queryString.parse(useLocation().search); - const {tab} = searchParams; - const history = useNavigate(); +export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); + const searchParams = queryString.parse(useLocation().search); + const { tab } = searchParams; + const history = useNavigate(); - /** - * useEffect hook to set document title, selected header and breadcrumbs - */ - useEffect(() => { - document.title = t("titles.scoreboard",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("scoreboard"); - setBreadcrumbs([ + /** + * useEffect hook to set document title, selected header and breadcrumbs + */ + useEffect(() => { + document.title = t("titles.scoreboard", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("scoreboard"); + setBreadcrumbs([ + { + link: "/manage/scoreboard", + label: t("titles.bc.scoreboard") + } + ]); + }, [t, setBreadcrumbs, setSelectedHeader]); + + /** + * Render the component + */ + return ( + + + { + searchParams.tab = key; + history({ + search: queryString.stringify(searchParams) + }); + }} + items={[ { - link: "/manage/scoreboard", - label: t("titles.bc.scoreboard"), + key: "sb", + icon: , + label: t("scoreboard.labels.jobs"), + forceRender: true, + children: }, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); - - /** - * Render the component - */ - return ( - - - { - searchParams.tab = key; - history({ - search: queryString.stringify(searchParams), - }); - }} - items={[ - { - key: "sb", - icon: , - label: t("scoreboard.labels.jobs"), - forceRender: true, - children: , - }, - { - key: "tickets", - icon: , - label: t("scoreboard.labels.timeticketsemployee"), - forceRender: true, - children: , - }, - { - key: "ticketsstats", - icon: , - label: t("scoreboard.labels.allemployeetimetickets"), - forceRender: true, - children: , - }, - ]} - /> - - - ); + { + key: "tickets", + icon: , + label: t("scoreboard.labels.timeticketsemployee"), + forceRender: true, + children: + }, + { + key: "ticketsstats", + icon: , + label: t("scoreboard.labels.allemployeetimetickets"), + forceRender: true, + children: + } + ]} + /> + + + ); } /** * Connecting the component to Redux store */ -export default connect( - mapStateToProps, - mapDispatchToProps -)(ScoreboardContainer); +export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardContainer); diff --git a/client/src/pages/shift-clock/shift-clock.page.jsx b/client/src/pages/shift-clock/shift-clock.page.jsx index 5641ec0b5..64b86f2e1 100644 --- a/client/src/pages/shift-clock/shift-clock.page.jsx +++ b/client/src/pages/shift-clock/shift-clock.page.jsx @@ -1,7 +1,7 @@ -import React from 'react'; -import RbacWrapper from '../../components/rbac-wrapper/rbac-wrapper.component'; -import TimeTicketShift from '../../components/time-ticket-shift/time-ticket-shift.container'; -import FeatureWrapperComponent from '../../components/feature-wrapper/feature-wrapper.component'; +import React from "react"; +import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; +import TimeTicketShift from "../../components/time-ticket-shift/time-ticket-shift.container"; +import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; export default function ShiftClock() { return ( diff --git a/client/src/pages/shop-csi/shop-csi.container.page.jsx b/client/src/pages/shop-csi/shop-csi.container.page.jsx index b8763b78f..4adcaf6cd 100644 --- a/client/src/pages/shop-csi/shop-csi.container.page.jsx +++ b/client/src/pages/shop-csi/shop-csi.container.page.jsx @@ -1,74 +1,72 @@ -import {useQuery} from "@apollo/client"; -import {Col, Row} from "antd"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; +import { useQuery } from "@apollo/client"; +import { Col, Row } from "antd"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; import AlertComponent from "../../components/alert/alert.component"; import CsiResponseFormContainer from "../../components/csi-response-form/csi-response-form.container"; -import CsiResponseListPaginated - from "../../components/csi-response-list-paginated/csi-response-list-paginated.component"; +import CsiResponseListPaginated from "../../components/csi-response-list-paginated/csi-response-list-paginated.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {QUERY_CSI_RESPONSE_PAGINATED} from "../../graphql/csi.queries"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { QUERY_CSI_RESPONSE_PAGINATED } from "../../graphql/csi.queries"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function ShopCsiContainer({ - bodyshop, - setBreadcrumbs, - setSelectedHeader, - }) { - const {t} = useTranslation(); +export function ShopCsiContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); - const {loading, error, data, refetch} = useQuery( - QUERY_CSI_RESPONSE_PAGINATED, - { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - } - ); + const { loading, error, data, refetch } = useQuery(QUERY_CSI_RESPONSE_PAGINATED, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); - useEffect(() => { - document.title = t("titles.shop-csi",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("shop-csi"); - setBreadcrumbs([ - { - link: "/manage/shop", - label: t("titles.bc.shop", {shopname: bodyshop.shopname}), - }, - {link: "/manage/shop/csi", label: t("titles.bc.shop-csi")}, - ]); - }, [t, setBreadcrumbs, bodyshop.shopname, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.shop-csi", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("shop-csi"); + setBreadcrumbs([ + { + link: "/manage/shop", + label: t("titles.bc.shop", { shopname: bodyshop.shopname }) + }, + { link: "/manage/shop/csi", label: t("titles.bc.shop-csi") } + ]); + }, [t, setBreadcrumbs, bodyshop.shopname, setSelectedHeader]); - if (error) return ; + if (error) return ; - return ( - - -
    - - - - - - - - ); + return ( + + + + + + + + + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(ShopCsiContainer); diff --git a/client/src/pages/shop-vendor/shop-vendor.page.component.jsx b/client/src/pages/shop-vendor/shop-vendor.page.component.jsx index 97098713b..a4d5f3186 100644 --- a/client/src/pages/shop-vendor/shop-vendor.page.component.jsx +++ b/client/src/pages/shop-vendor/shop-vendor.page.component.jsx @@ -1,43 +1,41 @@ -import {Drawer, Grid} from "antd"; +import { Drawer, Grid } from "antd"; import React from "react"; -import {useNavigate, useSearchParams} from "react-router-dom"; +import { useNavigate, useSearchParams } from "react-router-dom"; import VendorsFormContainer from "../../components/vendors-form/vendors-form.container"; import VendorsListContainer from "../../components/vendors-list/vendors-list.container"; export default function ShopVendorPageComponent() { - const [searchParams] = useSearchParams(); - const {selectedvendor} = Object.fromEntries(searchParams); - const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const { selectedvendor } = Object.fromEntries(searchParams); + const navigate = useNavigate(); - const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) - .filter((screen) => !!screen[1]) - .slice(-1)[0]; + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) + .filter((screen) => !!screen[1]) + .slice(-1)[0]; - const bpoints = { - xs: "100%", - sm: "100%", - md: "100%", - lg: "50%", - xl: "50%", - xxl: "45%", - }; - const drawerPercentage = selectedBreakpoint - ? bpoints[selectedBreakpoint[0]] - : "100%"; + const bpoints = { + xs: "100%", + sm: "100%", + md: "100%", + lg: "50%", + xl: "50%", + xxl: "45%" + }; + const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%"; - return ( - (
    - - { - searchParams.delete("selectedvendor"); - navigate({search: searchParams.toString()}); - }} - open={selectedvendor} - > - - -
    ) - ); -} \ No newline at end of file + return ( +
    + + { + searchParams.delete("selectedvendor"); + navigate({ search: searchParams.toString() }); + }} + open={selectedvendor} + > + + +
    + ); +} diff --git a/client/src/pages/shop-vendor/shop-vendor.page.container.jsx b/client/src/pages/shop-vendor/shop-vendor.page.container.jsx index 3729f0ca4..5f41d3438 100644 --- a/client/src/pages/shop-vendor/shop-vendor.page.container.jsx +++ b/client/src/pages/shop-vendor/shop-vendor.page.container.jsx @@ -1,48 +1,47 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import ShopVendorPageComponent from "./shop-vendor.page.component"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function ShopVendorPageContainer({ - bodyshop, - setBreadcrumbs, - setSelectedHeader, - }) { - const {t} = useTranslation(); - useEffect(() => { - document.title = t("titles.shop_vendors", {app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("shop-vendors"); - setBreadcrumbs([ - { - link: "/manage/shop", - label: t("titles.bc.shop", {shopname: bodyshop.shopname}), - }, - {link: "/manage/shop/vendors", label: t("titles.bc.shop-vendors")}, - ]); - }, [t, bodyshop.shopname, setBreadcrumbs, setSelectedHeader]); +export function ShopVendorPageContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); + useEffect(() => { + document.title = t("titles.shop_vendors", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("shop-vendors"); + setBreadcrumbs([ + { + link: "/manage/shop", + label: t("titles.bc.shop", { shopname: bodyshop.shopname }) + }, + { link: "/manage/shop/vendors", label: t("titles.bc.shop-vendors") } + ]); + }, [t, bodyshop.shopname, setBreadcrumbs, setSelectedHeader]); - return ( - - - - ); + return ( + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(ShopVendorPageContainer); +export default connect(mapStateToProps, mapDispatchToProps)(ShopVendorPageContainer); diff --git a/client/src/pages/shop/shop.page.component.jsx b/client/src/pages/shop/shop.page.component.jsx index 13efc59fc..19991f62f 100644 --- a/client/src/pages/shop/shop.page.component.jsx +++ b/client/src/pages/shop/shop.page.component.jsx @@ -1,93 +1,95 @@ -import {Tabs} from "antd"; -import React, {useEffect} from "react"; -import {useLocation, useNavigate} from "react-router-dom"; +import { Tabs } from "antd"; +import React, { useEffect } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; import queryString from "query-string"; -import {useTranslation} from "react-i18next"; +import { useTranslation } from "react-i18next"; import ShopEmployeesContainer from "../../components/shop-employees/shop-employees.container"; import ShopInfoContainer from "../../components/shop-info/shop-info.container"; import ShopCsiConfig from "../../components/shop-csi-config/shop-csi-config.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import ShopInfoUsersComponent from "../../components/shop-users/shop-users.component"; import ShopTeamsContainer from "../../components/shop-teams/shop-teams.container"; import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)) }); -export function ShopPage({bodyshop, setSelectedHeader, setBreadcrumbs}) { - const {t} = useTranslation(); - const history = useNavigate(); - const search = queryString.parse(useLocation().search); +export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) { + const { t } = useTranslation(); + const history = useNavigate(); + const search = queryString.parse(useLocation().search); - useEffect(() => { - document.title = t("titles.shop",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("shop"); - setBreadcrumbs([ - { - link: "/manage/shop", - label: t("titles.bc.shop", {shopname: bodyshop.shopname}), - }, - ]); - }, [t, setSelectedHeader, setBreadcrumbs, bodyshop.shopname]); - - useEffect(() => { - if (!search.tab) history({search: "?tab=info"}); - }, [history, search]); - - const items = [ - { - key: "info", - label: t("bodyshop.labels.shopinfo"), - children: , - }, - { - key: "employees", - label: t("bodyshop.labels.employees"), - children: , - }]; - - if (bodyshop.md_tasks_presets.enable_tasks) { - items.push({ - key: "teams", - label: t("bodyshop.labels.employee_teams"), - children: - }); - } - - items.push({ - key: "licensing", - label: t("bodyshop.labels.licensing"), - children: , - }, - ); - - if(HasFeatureAccess({featureName:"csi", bodyshop})){ - items.push({ - key: "csiq", - label: t("bodyshop.labels.csiq"), - children: , + useEffect(() => { + document.title = t("titles.shop", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" }) - } - return ( - - history({search: `?tab=${key}`})} - items={items} - /> - - ); + }); + setSelectedHeader("shop"); + setBreadcrumbs([ + { + link: "/manage/shop", + label: t("titles.bc.shop", { shopname: bodyshop.shopname }) + } + ]); + }, [t, setSelectedHeader, setBreadcrumbs, bodyshop.shopname]); + + useEffect(() => { + if (!search.tab) history({ search: "?tab=info" }); + }, [history, search]); + + const items = [ + { + key: "info", + label: t("bodyshop.labels.shopinfo"), + children: + }, + { + key: "employees", + label: t("bodyshop.labels.employees"), + children: + } + ]; + + if (bodyshop.md_tasks_presets.enable_tasks) { + items.push({ + key: "teams", + label: t("bodyshop.labels.employee_teams"), + children: + }); + } + + items.push({ + key: "licensing", + label: t("bodyshop.labels.licensing"), + children: + }); + + if (HasFeatureAccess({ featureName: "csi", bodyshop })) { + items.push({ + key: "csiq", + label: t("bodyshop.labels.csiq"), + children: + }); + } + return ( + + history({ search: `?tab=${key}` })} items={items} /> + + ); } export default connect(mapStateToProps, mapDispatchToProps)(ShopPage); diff --git a/client/src/pages/sign-in/sign-in.page.jsx b/client/src/pages/sign-in/sign-in.page.jsx index 26257fb9e..973b853d6 100644 --- a/client/src/pages/sign-in/sign-in.page.jsx +++ b/client/src/pages/sign-in/sign-in.page.jsx @@ -2,9 +2,9 @@ import React from "react"; import SignIn from "../../components/sign-in-form/sign-in-form.component"; export default function SignInPage() { - return ( -
    - -
    - ); + return ( +
    + +
    + ); } diff --git a/client/src/pages/tech-assigned-prod-jobs/tech-assigned-prod-jobs.component.jsx b/client/src/pages/tech-assigned-prod-jobs/tech-assigned-prod-jobs.component.jsx index 89400e25c..26c01afb8 100644 --- a/client/src/pages/tech-assigned-prod-jobs/tech-assigned-prod-jobs.component.jsx +++ b/client/src/pages/tech-assigned-prod-jobs/tech-assigned-prod-jobs.component.jsx @@ -1,261 +1,220 @@ -import {useQuery} from "@apollo/client"; -import React, {useState} from "react"; -import {Button, Card, Input, Space, Table} from "antd"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {QUERY_JOBS_TECH_ASIGNED_TO_BY_TEAM} from "../../graphql/jobs.queries"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {alphaSort} from "../../utils/sorters"; -import {SyncOutlined} from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import React, { useState } from "react"; +import { Button, Card, Input, Space, Table } from "antd"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { QUERY_JOBS_TECH_ASIGNED_TO_BY_TEAM } from "../../graphql/jobs.queries"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { alphaSort } from "../../utils/sorters"; +import { SyncOutlined } from "@ant-design/icons"; import queryString from "query-string"; -import {useTranslation} from "react-i18next"; -import {useLocation, useNavigate} from "react-router-dom"; -import {onlyUnique} from "../../utils/arrayHelper"; +import { useTranslation } from "react-i18next"; +import { useLocation, useNavigate } from "react-router-dom"; +import { onlyUnique } from "../../utils/arrayHelper"; import AlertComponent from "../../components/alert/alert.component"; import OwnerNameDisplay from "../../components/owner-name-display/owner-name-display.component"; -import {setModalContext} from "../../redux/modals/modals.actions"; +import { setModalContext } from "../../redux/modals/modals.actions"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - technician: selectTechnician, - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + technician: selectTechnician, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setTimeTicketTaskContext: (context) => - dispatch(setModalContext({context: context, modal: "timeTicketTask"})), + setTimeTicketTaskContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicketTask" })) }); -export function TechAssignedProdJobs({ - setTimeTicketTaskContext, - technician, - bodyshop, - }) { - const {loading, error, data, refetch} = useQuery( - QUERY_JOBS_TECH_ASIGNED_TO_BY_TEAM, - { - variables: { - teamIds: bodyshop.employee_teams - .filter((et) => - et.employee_team_members.find( - (etm) => etm.employeeid === technician.id - ) - ) - .map((et) => et.id), - }, - } - ); +export function TechAssignedProdJobs({ setTimeTicketTaskContext, technician, bodyshop }) { + const { loading, error, data, refetch } = useQuery(QUERY_JOBS_TECH_ASIGNED_TO_BY_TEAM, { + variables: { + teamIds: bodyshop.employee_teams + .filter((et) => et.employee_team_members.find((etm) => etm.employeeid === technician.id)) + .map((et) => et.id) + } + }); - const searchParams = queryString.parse(useLocation().search); - const {selected} = searchParams; + const searchParams = queryString.parse(useLocation().search); + const { selected } = searchParams; - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); - const {t} = useTranslation(); - const history = useNavigate(); - const [searchText, setSearchText] = useState(""); - if (error) return ; + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" } + }); + const { t } = useTranslation(); + const history = useNavigate(); + const [searchText, setSearchText] = useState(""); + if (error) return ; - const jobs = data - ? searchText === "" - ? data.jobs - : data.jobs.filter( - (j) => - (j.ro_number || "") - .toString() - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_fn || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_ln || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_co_nm || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.plate_no || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_model_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_make_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) - ) - : []; - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + const jobs = data + ? searchText === "" + ? data.jobs + : data.jobs.filter( + (j) => + (j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.plate_no || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase()) + ) + : []; + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - render: (text, record) => record.ro_number || t("general.labels.na"), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, - ellipsis: true, - render: (text, record) => ( - - + render: (text, record) => record.ro_number || t("general.labels.na") + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + ellipsis: true, + render: (text, record) => ( + + - ), - }, - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", - sorter: (a, b) => alphaSort(a.status, b.status), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - filters: - (jobs && - jobs - .map((j) => j.status) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Status*", - value: [s], - }; - })) || - [], - onFilter: (value, record) => value.includes(record.status), - render: (text, record) => { - return record.status || t("general.labels.na"); - }, - }, + ) + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => alphaSort(a.status, b.status), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + filters: + (jobs && + jobs + .map((j) => j.status) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Status*", + value: [s] + }; + })) || + [], + onFilter: (value, record) => value.includes(record.status), + render: (text, record) => { + return record.status || t("general.labels.na"); + } + }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - ellipsis: true, - render: (text, record) => ( - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - ), - }, - { - title: t("vehicles.fields.plate_no"), - dataIndex: "plate_no", - key: "plate_no", - sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), - sortOrder: - state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, - render: (text, record) => { - return record.plate_no ? record.plate_no : ""; - }, - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - ellipsis: true, - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - render: (text, record) => { - return record.clm_no ? ( - {record.clm_no} - ) : ( - t("general.labels.unknown") - ); - }, - }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", - render: (text, record) => ( - - ), - }, - ]; - const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - }; - - const handleOnRowClick = (record) => { - if (record) { - if (record.id) { - history({ - search: queryString.stringify({ - ...searchParams, - selected: record.id, - }), - }); - } - } - }; - return ( - - - { - setSearchText(e.target.value); - }} - value={searchText} - enterButton - /> - - } + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + render: (text, record) => ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + ) + }, + { + title: t("vehicles.fields.plate_no"), + dataIndex: "plate_no", + key: "plate_no", + sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), + sortOrder: state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, + render: (text, record) => { + return record.plate_no ? record.plate_no : ""; + } + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + ellipsis: true, + sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), + sortOrder: state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, + render: (text, record) => { + return record.clm_no ? {record.clm_no} : t("general.labels.unknown"); + } + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => ( +
    { - handleOnRowClick(record); - }, - selectedRowKeys: [selected], - type: "radio", - }} - onChange={handleTableChange} - // onRow={(record, rowIndex) => { - // return { - // onClick: (event) => { - // handleOnRowClick(record); - // }, - // }; - // }} - /> - - ); + {t("timetickets.actions.claimtasks")} + + ) + } + ]; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + const handleOnRowClick = (record) => { + if (record) { + if (record.id) { + history({ + search: queryString.stringify({ + ...searchParams, + selected: record.id + }) + }); + } + } + }; + return ( + + + { + setSearchText(e.target.value); + }} + value={searchText} + enterButton + /> + + } + > +
    { + handleOnRowClick(record); + }, + selectedRowKeys: [selected], + type: "radio" + }} + onChange={handleTableChange} + // onRow={(record, rowIndex) => { + // return { + // onClick: (event) => { + // handleOnRowClick(record); + // }, + // }; + // }} + /> + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(TechAssignedProdJobs); +export default connect(mapStateToProps, mapDispatchToProps)(TechAssignedProdJobs); diff --git a/client/src/pages/tech-dispatched-parts/tech-dispatched-parts.page.jsx b/client/src/pages/tech-dispatched-parts/tech-dispatched-parts.page.jsx index fa17f6acd..ebf54a096 100644 --- a/client/src/pages/tech-dispatched-parts/tech-dispatched-parts.page.jsx +++ b/client/src/pages/tech-dispatched-parts/tech-dispatched-parts.page.jsx @@ -1,146 +1,135 @@ -import {MinusCircleTwoTone, PlusCircleTwoTone, SyncOutlined,} from "@ant-design/icons"; -import {useQuery} from "@apollo/client"; -import {Button, Card, Space, Table} from "antd"; +import { MinusCircleTwoTone, PlusCircleTwoTone, SyncOutlined } from "@ant-design/icons"; +import { useQuery } from "@apollo/client"; +import { Button, Card, Space, Table } from "antd"; import queryString from "query-string"; import React from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {useLocation, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import AlertComponent from "../../components/alert/alert.component"; import PartsDispatchExpander from "../../components/parts-dispatch-expander/parts-dispatch-expander.component"; -import {GET_UNACCEPTED_PARTS_DISPATCH} from "../../graphql/parts-dispatch.queries"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {alphaSort} from "../../utils/sorters"; +import { GET_UNACCEPTED_PARTS_DISPATCH } from "../../graphql/parts-dispatch.queries"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { alphaSort } from "../../utils/sorters"; import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - technician: selectTechnician, - bodyshop: selectBodyshop, + //currentUser: selectCurrentUser + technician: selectTechnician, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({}); -export function TechDispatchedParts({technician, bodyshop}) { - const searchParams = queryString.parse(useLocation().search); - const {page} = searchParams; +export function TechDispatchedParts({ technician, bodyshop }) { + const searchParams = queryString.parse(useLocation().search); + const { page } = searchParams; - const {treatments: {Enhanced_Payroll}} = useSplitTreatments({ - attributes: {}, - names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid, + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid }); - const {loading, error, data, refetch} = useQuery( - GET_UNACCEPTED_PARTS_DISPATCH, - { - variables: { - techId: technician.id, - offset: page ? (page - 1) * 25 : 0, - limit: 25, - }, - } - ); + const { loading, error, data, refetch } = useQuery(GET_UNACCEPTED_PARTS_DISPATCH, { + variables: { + techId: technician.id, + offset: page ? (page - 1) * 25 : 0, + limit: 25 + } + }); - const {t} = useTranslation(); - const history = useNavigate(); + const { t } = useTranslation(); + const history = useNavigate(); - if (error) return ; + if (error) return ; - const parts_dispatch = data?.parts_dispatch; + const parts_dispatch = data?.parts_dispatch; - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "job.ro_number", - key: "ro_number", - sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "job.ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), - render: (text, record) => record.job.ro_number || t("general.labels.na"), - }, - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", - sorter: (a, b) => alphaSort(a.status, b.status), - render: (text, record) => { - return record.job.status || t("general.labels.na"); - }, - }, + render: (text, record) => record.job.ro_number || t("general.labels.na") + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => alphaSort(a.status, b.status), + render: (text, record) => { + return record.job.status || t("general.labels.na"); + } + }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - ellipsis: true, - render: (text, record) => ( - {`${record.job.v_model_yr || ""} ${ - record.job.v_make_desc || "" - } ${record.job.v_model_desc || ""}`} - ), - }, - ...Enhanced_Payroll.treatment=== 'on' ? [ { + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + render: (text, record) => ( + {`${record.job.v_model_yr || ""} ${record.job.v_make_desc || ""} ${record.job.v_model_desc || ""}`} + ) + }, + ...(Enhanced_Payroll.treatment === "on" + ? [ + { title: t("general.labels.actions"), dataIndex: "actions", key: "actions", - render: (text, record) => ( - - ), - },] : [] + render: (text, record) => + } + ] + : []) + ]; + const handleTableChange = (pagination, filters, sorter) => { + searchParams.page = pagination.current; + history({ search: queryString.stringify(searchParams) }); + }; - - ]; - const handleTableChange = (pagination, filters, sorter) => { - searchParams.page = pagination.current; - history({search: queryString.stringify(searchParams)}); - }; - - return ( - - - - } - > -
    ( - - ), - rowExpandable: (record) => true, - //expandRowByClick: true, - expandIcon: ({expanded, onExpand, record}) => - expanded ? ( - onExpand(record, e)}/> - ) : ( - onExpand(record, e)}/> - ), - }} - /> - - ); + return ( + + + + } + > +
    , + rowExpandable: (record) => true, + //expandRowByClick: true, + expandIcon: ({ expanded, onExpand, record }) => + expanded ? ( + onExpand(record, e)} /> + ) : ( + onExpand(record, e)} /> + ) + }} + /> + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(TechDispatchedParts); +export default connect(mapStateToProps, mapDispatchToProps)(TechDispatchedParts); diff --git a/client/src/pages/tech-job-clock/tech-job-clock.component.jsx b/client/src/pages/tech-job-clock/tech-job-clock.component.jsx index 765713209..f9c120be3 100644 --- a/client/src/pages/tech-job-clock/tech-job-clock.component.jsx +++ b/client/src/pages/tech-job-clock/tech-job-clock.component.jsx @@ -1,24 +1,30 @@ -import {Divider} from "antd"; -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; +import { Divider } from "antd"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import TechClockInFormContainer from "../../components/tech-job-clock-in-form/tech-job-clock-in-form.container"; import TechClockedInList from "../../components/tech-job-clocked-in-list/tech-job-clocked-in-list.component"; import TechJobStatistics from "../../components/tech-job-statistics/tech-job-statistics.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; export default function TechClockComponent() { - const {t} = useTranslation(); + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.techjobclock", {app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - }, [t]); + useEffect(() => { + document.title = t("titles.techjobclock", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + }, [t]); - return ( -
    - - - - -
    - ); + return ( +
    + + + + +
    + ); } diff --git a/client/src/pages/tech-lookup/tech-lookup.container.jsx b/client/src/pages/tech-lookup/tech-lookup.container.jsx index a00c6becb..b92a11d3f 100644 --- a/client/src/pages/tech-lookup/tech-lookup.container.jsx +++ b/client/src/pages/tech-lookup/tech-lookup.container.jsx @@ -1,21 +1,27 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component"; import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; export default function TechLookupContainer() { - const {t} = useTranslation(); + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.techjoblookup", {app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - }, [t]); + useEffect(() => { + document.title = t("titles.techjoblookup", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + }, [t]); - return ( -
    - - - -
    - ); + return ( +
    + + + +
    + ); } diff --git a/client/src/pages/tech-shift-clock/tech-shift-clock.component.jsx b/client/src/pages/tech-shift-clock/tech-shift-clock.component.jsx index 67491dda6..bdc9f8b32 100644 --- a/client/src/pages/tech-shift-clock/tech-shift-clock.component.jsx +++ b/client/src/pages/tech-shift-clock/tech-shift-clock.component.jsx @@ -1,18 +1,24 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import TimeTicketShift from "../../components/time-ticket-shift/time-ticket-shift.container"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; export default function TechShiftClock() { - const {t} = useTranslation(); + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.techshiftclock",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - }, [t]); + useEffect(() => { + document.title = t("titles.techshiftclock", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + }, [t]); - return ( -
    - -
    - ); + return ( +
    + +
    + ); } diff --git a/client/src/pages/tech/tech.page.component.jsx b/client/src/pages/tech/tech.page.component.jsx index 6366082fb..9f6b11cf7 100644 --- a/client/src/pages/tech/tech.page.component.jsx +++ b/client/src/pages/tech/tech.page.component.jsx @@ -1,9 +1,9 @@ -import {FloatButton, Layout} from "antd"; -import React, {lazy, Suspense, useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Route, Routes, useNavigate} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import { FloatButton, Layout } from "antd"; +import React, { lazy, Suspense, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Route, Routes, useNavigate } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import ErrorBoundary from "../../components/error-boundary/error-boundary.component"; import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component"; @@ -12,108 +12,99 @@ import TechHeader from "../../components/tech-header/tech-header.component"; import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component"; import TechSider from "../../components/tech-sider/tech-sider.component"; import UpdateAlert from "../../components/update-alert/update-alert.component"; -import {selectTechnician} from "../../redux/tech/tech.selectors"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; import "./tech.page.styles.scss"; -const TimeTicketModalContainer = lazy(() => - import("../../components/time-ticket-modal/time-ticket-modal.container") -); -const EmailOverlayContainer = lazy(() => - import("../../components/email-overlay/email-overlay.container.jsx") -); -const PrintCenterModalContainer = lazy(() => - import("../../components/print-center-modal/print-center-modal.container") -); -const TechLogin = lazy(() => - import("../../components/tech-login/tech-login.component") +const TimeTicketModalContainer = lazy(() => import("../../components/time-ticket-modal/time-ticket-modal.container")); +const EmailOverlayContainer = lazy(() => import("../../components/email-overlay/email-overlay.container.jsx")); +const PrintCenterModalContainer = lazy( + () => import("../../components/print-center-modal/print-center-modal.container") ); +const TechLogin = lazy(() => import("../../components/tech-login/tech-login.component")); const TechLookup = lazy(() => import("../tech-lookup/tech-lookup.container")); -const ProductionListPage = lazy(() => - import("../production-list/production-list.container") -); -const ProductionBoardPage = lazy(() => - import("../production-board/production-board.container") -); -const TechJobClock = lazy(() => - import("../tech-job-clock/tech-job-clock.component") -); -const TechShiftClock = lazy(() => - import("../tech-shift-clock/tech-shift-clock.component") -); -const TimeTicketModalTask = lazy(() => - import( - "../../components/time-ticket-task-modal/time-ticket-task-modal.container" - ) -); -const TechAssignedProdJobs = lazy(() => - import("../tech-assigned-prod-jobs/tech-assigned-prod-jobs.component") -); -const TechDispatchedParts = lazy(() => - import("../tech-dispatched-parts/tech-dispatched-parts.page") +const ProductionListPage = lazy(() => import("../production-list/production-list.container")); +const ProductionBoardPage = lazy(() => import("../production-board/production-board.container")); +const TechJobClock = lazy(() => import("../tech-job-clock/tech-job-clock.component")); +const TechShiftClock = lazy(() => import("../tech-shift-clock/tech-shift-clock.component")); +const TimeTicketModalTask = lazy( + () => import("../../components/time-ticket-task-modal/time-ticket-task-modal.container") ); +const TechAssignedProdJobs = lazy(() => import("../tech-assigned-prod-jobs/tech-assigned-prod-jobs.component")); +const TechDispatchedParts = lazy(() => import("../tech-dispatched-parts/tech-dispatched-parts.page")); -const {Content} = Layout; +const { Content } = Layout; const mapStateToProps = createStructuredSelector({ - technician: selectTechnician, + technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function TechPage({technician}) { - const {t} = useTranslation(); - const navigate = useNavigate(); +export function TechPage({ technician }) { + const { t } = useTranslation(); + const navigate = useNavigate(); - useEffect(() => { - document.title = InstanceRenderManager({imex: t("titles.imexonline"), rome: t("titles.romeonline"), promanager:t("titles.promanager")}); - }, [t]); + useEffect(() => { + document.title = InstanceRenderManager({ + imex: t("titles.imexonline"), + rome: t("titles.romeonline"), + promanager: t("titles.promanager") + }); + }, [t]); - useEffect(() => { - if (!technician) { - navigate(`/tech/login`); - } - }, [technician, navigate]); + useEffect(() => { + if (!technician) { + navigate(`/tech/login`); + } + }, [technician, navigate]); - return ( - - - - - - - - - - } - > - - - - - - - }/> - }/> - }/> - }/> - }/> - }/> - }/> - }/> - - - - - - - - - - ); + return ( + + + + + + + + + + } + > + + + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + + + + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(TechPage); diff --git a/client/src/pages/tech/tech.page.container.jsx b/client/src/pages/tech/tech.page.container.jsx index b5a526440..70c0e638b 100644 --- a/client/src/pages/tech/tech.page.container.jsx +++ b/client/src/pages/tech/tech.page.container.jsx @@ -1,39 +1,37 @@ -import {useQuery} from "@apollo/client"; -import React, {useEffect} from "react"; -import {connect} from "react-redux"; +import { useQuery } from "@apollo/client"; +import React, { useEffect } from "react"; +import { connect } from "react-redux"; import AlertComponent from "../../components/alert/alert.component"; -import {QUERY_BODYSHOP} from "../../graphql/bodyshop.queries"; -import {setBodyshop} from "../../redux/user/user.actions"; +import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries"; +import { setBodyshop } from "../../redux/user/user.actions"; import TechPage from "./tech.page.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; -import {useTranslation} from "react-i18next"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {createStructuredSelector} from "reselect"; +import { useTranslation } from "react-i18next"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { createStructuredSelector } from "reselect"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBodyshop: (bs) => dispatch(setBodyshop(bs)), + setBodyshop: (bs) => dispatch(setBodyshop(bs)) }); -export function TechPageContainer({bodyshop, setBodyshop}) { - const {loading, error, data} = useQuery(QUERY_BODYSHOP, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); - const {t} = useTranslation(); +export function TechPageContainer({ bodyshop, setBodyshop }) { + const { loading, error, data } = useQuery(QUERY_BODYSHOP, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + const { t } = useTranslation(); - useEffect(() => { - if (data) setBodyshop(data.bodyshops[0]); - }, [data, setBodyshop]); + useEffect(() => { + if (data) setBodyshop(data.bodyshops[0]); + }, [data, setBodyshop]); - - if (loading || !bodyshop) - return ; - if (error) return ; - return ; + if (loading || !bodyshop) return ; + if (error) return ; + return ; } export default connect(mapStateToProps, mapDispatchToProps)(TechPageContainer); diff --git a/client/src/pages/temporary-docs/temporary-docs.component.jsx b/client/src/pages/temporary-docs/temporary-docs.component.jsx index d9a9f1053..092011e65 100644 --- a/client/src/pages/temporary-docs/temporary-docs.component.jsx +++ b/client/src/pages/temporary-docs/temporary-docs.component.jsx @@ -1,47 +1,43 @@ -import {useQuery} from "@apollo/client"; +import { useQuery } from "@apollo/client"; import React from "react"; import AlertComponent from "../../components/alert/alert.component"; import JobsDocumentsComponent from "../../components/jobs-documents-gallery/jobs-documents-gallery.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; -import {QUERY_TEMPORARY_DOCS} from "../../graphql/documents.queries"; +import { QUERY_TEMPORARY_DOCS } from "../../graphql/documents.queries"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import JobsDocumentsLocalGallery - from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(TemporaryDocsComponent); +export default connect(mapStateToProps, mapDispatchToProps)(TemporaryDocsComponent); -export function TemporaryDocsComponent({bodyshop}) { - const {loading, error, data, refetch} = useQuery(QUERY_TEMPORARY_DOCS, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - skip: bodyshop.uselocalmediaserver, - }); +export function TemporaryDocsComponent({ bodyshop }) { + const { loading, error, data, refetch } = useQuery(QUERY_TEMPORARY_DOCS, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + skip: bodyshop.uselocalmediaserver + }); - if (loading) return ; - if (error) return ; + if (loading) return ; + if (error) return ; - if (bodyshop.uselocalmediaserver) { - return ; - } - return ( - - ); + if (bodyshop.uselocalmediaserver) { + return ; + } + return ( + + ); } diff --git a/client/src/pages/temporary-docs/temporary-docs.container.jsx b/client/src/pages/temporary-docs/temporary-docs.container.jsx index 652107cf7..bb8785838 100644 --- a/client/src/pages/temporary-docs/temporary-docs.container.jsx +++ b/client/src/pages/temporary-docs/temporary-docs.container.jsx @@ -1,47 +1,50 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import TemporaryDocsComponent from "./temporary-docs.component"; import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function TempDocumentsContainer({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); +export function TempDocumentsContainer({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.temporarydocs",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("temporarydocs"); - setBreadcrumbs([ - { - link: "/manage/temporarydocs", - label: t("titles.bc.temporarydocs"), - }, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.temporarydocs", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("temporarydocs"); + setBreadcrumbs([ + { + link: "/manage/temporarydocs", + label: t("titles.bc.temporarydocs") + } + ]); + }, [t, setBreadcrumbs, setSelectedHeader]); - return ( - - - - - - ); + return ( + + + + + + ); } -export default connect( - mapStateToProps, - mapDispatchToProps -)(TempDocumentsContainer); +export default connect(mapStateToProps, mapDispatchToProps)(TempDocumentsContainer); diff --git a/client/src/pages/time-tickets/time-tickets.container.jsx b/client/src/pages/time-tickets/time-tickets.container.jsx index 6c41a2657..766bd62e0 100644 --- a/client/src/pages/time-tickets/time-tickets.container.jsx +++ b/client/src/pages/time-tickets/time-tickets.container.jsx @@ -1,73 +1,73 @@ -import { useQuery } from '@apollo/client'; -import { Col, Row, Space } from 'antd'; -import dayjs from '../../utils/day'; -import React, { useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; -import { connect } from 'react-redux'; -import { useSearchParams } from 'react-router-dom'; -import { createStructuredSelector } from 'reselect'; -import AlertComponent from '../../components/alert/alert.component'; -import RbacWrapper from '../../components/rbac-wrapper/rbac-wrapper.component'; -import TimeTicketsDatesSelector from '../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component'; -import TimeTicketList from '../../components/time-ticket-list/time-ticket-list.component'; -import TimeTicketsPayrollTable from '../../components/time-tickets-payroll-table/time-tickets-payroll-table.component'; -import TimeTicketsSummaryEmployees from '../../components/time-tickets-summary-employees/time-tickets-summary-employees.component'; -import { QUERY_TIME_TICKETS_IN_RANGE } from '../../graphql/timetickets.queries'; -import TimeTicketsAttendanceTable from '../../components/time-tickets-attendance-table/time-tickets-attendance-table.component'; -import { setBreadcrumbs, setSelectedHeader } from '../../redux/application/application.actions'; -import TimeTicketsCommit from '../../components/time-tickets-commit/time-tickets-commit.component'; -import FeatureWrapperComponent from '../../components/feature-wrapper/feature-wrapper.component'; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; -import { useSplitTreatments } from '@splitsoftware/splitio-react'; -import { selectBodyshop } from '../../redux/user/user.selectors'; +import { useQuery } from "@apollo/client"; +import { Col, Row, Space } from "antd"; +import dayjs from "../../utils/day"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useSearchParams } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import AlertComponent from "../../components/alert/alert.component"; +import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; +import TimeTicketsDatesSelector from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component"; +import TimeTicketList from "../../components/time-ticket-list/time-ticket-list.component"; +import TimeTicketsPayrollTable from "../../components/time-tickets-payroll-table/time-tickets-payroll-table.component"; +import TimeTicketsSummaryEmployees from "../../components/time-tickets-summary-employees/time-tickets-summary-employees.component"; +import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries"; +import TimeTicketsAttendanceTable from "../../components/time-tickets-attendance-table/time-tickets-attendance-table.component"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import TimeTicketsCommit from "../../components/time-tickets-commit/time-tickets-commit.component"; +import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop:selectBodyshop + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); export function TimeTicketsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) { const { - treatments: { Enhanced_Payroll }, + treatments: { Enhanced_Payroll } } = useSplitTreatments({ attributes: {}, - names: ['Enhanced_Payroll'], - splitKey: bodyshop.imexshopid, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid }); const { t } = useTranslation(); const [searchParams] = useSearchParams(); const { start, end } = Object.fromEntries(searchParams); - const startDate = start ? dayjs(start) : dayjs().startOf('week').subtract(7, 'day'); - const endDate = end ? dayjs(end) : dayjs().endOf('week'); + const startDate = start ? dayjs(start) : dayjs().startOf("week").subtract(7, "day"); + const endDate = end ? dayjs(end) : dayjs().endOf("week"); const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE, { variables: { start: startDate, - end: endDate, + end: endDate }, - fetchPolicy: 'network-only', - nextFetchPolicy: 'network-only', + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" }); useEffect(() => { - document.title = t('titles.timetickets', { + document.title = t("titles.timetickets", { app: InstanceRenderManager({ - imex: '$t(titles.imexonline)', - rome: '$t(titles.romeonline)', - promanager: '$t(titles.promanager)', - }), + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) }); - setSelectedHeader('timetickets'); + setSelectedHeader("timetickets"); setBreadcrumbs([ { - link: '/manage/timetickets', - label: t('titles.bc.timetickets'), - }, + link: "/manage/timetickets", + label: t("titles.bc.timetickets") + } ]); }, [t, setBreadcrumbs, setSelectedHeader]); @@ -85,7 +85,7 @@ export function TimeTicketsContainer({ bodyshop, setBreadcrumbs, setSelectedHead - {Enhanced_Payroll.treatment === 'on' && ( + {Enhanced_Payroll.treatment === "on" && ( )} diff --git a/client/src/pages/tt-approvals/tt-approvals.page.container.jsx b/client/src/pages/tt-approvals/tt-approvals.page.container.jsx index 989816720..6f30c0727 100644 --- a/client/src/pages/tt-approvals/tt-approvals.page.container.jsx +++ b/client/src/pages/tt-approvals/tt-approvals.page.container.jsx @@ -1,44 +1,50 @@ -import React, {useEffect} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import TtApprovalsList from "../../components/tt-approvals-list/tt-approvals-list.container"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function TtApprovalsPage({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); +export function TtApprovalsPage({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.ttapprovals",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("ttapprovals"); - setBreadcrumbs([ - { - link: "/manage/ttapprovals", - label: t("titles.bc.ttapprovals"), - }, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.ttapprovals", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("ttapprovals"); + setBreadcrumbs([ + { + link: "/manage/ttapprovals", + label: t("titles.bc.ttapprovals") + } + ]); + }, [t, setBreadcrumbs, setSelectedHeader]); - return ( - - - - - - ); + return ( + + + + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(TtApprovalsPage); diff --git a/client/src/pages/vehicles-detail/vehicles-detail.page.component.jsx b/client/src/pages/vehicles-detail/vehicles-detail.page.component.jsx index 38fce6c88..6f8489f62 100644 --- a/client/src/pages/vehicles-detail/vehicles-detail.page.component.jsx +++ b/client/src/pages/vehicles-detail/vehicles-detail.page.component.jsx @@ -1,20 +1,20 @@ -import {Col, Divider, Row} from "antd"; +import { Col, Divider, Row } from "antd"; import React from "react"; import VehicleDetailFormContainer from "../../components/vehicle-detail-form/vehicle-detail-form.container"; import VehicleDetailJobsComponent from "../../components/vehicle-detail-jobs/vehicle-detail-jobs.component"; -export default function VehicleDetailComponent({vehicle, refetch}) { - return ( -
    - -
    - - - - - - - - - ); +export default function VehicleDetailComponent({ vehicle, refetch }) { + return ( +
    + +
    + + + + + + + + + ); } diff --git a/client/src/pages/vehicles-detail/vehicles-detail.page.container.jsx b/client/src/pages/vehicles-detail/vehicles-detail.page.container.jsx index 4df727606..ac3be0137 100644 --- a/client/src/pages/vehicles-detail/vehicles-detail.page.container.jsx +++ b/client/src/pages/vehicles-detail/vehicles-detail.page.container.jsx @@ -1,92 +1,81 @@ -import React, {useEffect} from "react"; +import React, { useEffect } from "react"; import VehicleDetailComponent from "./vehicles-detail.page.component"; -import {useQuery} from "@apollo/client"; -import {useParams} from 'react-router-dom'; -import {QUERY_VEHICLE_BY_ID} from "../../graphql/vehicles.queries"; +import { useQuery } from "@apollo/client"; +import { useParams } from "react-router-dom"; +import { QUERY_VEHICLE_BY_ID } from "../../graphql/vehicles.queries"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import AlertComponent from "../../components/alert/alert.component"; -import {useTranslation} from "react-i18next"; -import {addRecentItem, setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; -import {connect} from "react-redux"; -import {CreateRecentItem} from "../../utils/create-recent-item"; +import { useTranslation } from "react-i18next"; +import { addRecentItem, setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; +import { connect } from "react-redux"; +import { CreateRecentItem } from "../../utils/create-recent-item"; import NotFound from "../../components/not-found/not-found.component"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - addRecentItem: (item) => dispatch(addRecentItem(item)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + addRecentItem: (item) => dispatch(addRecentItem(item)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function VehicleDetailContainer({ - setBreadcrumbs, - addRecentItem, - setSelectedHeader, - }) { - const {vehId} = useParams(); - const {t} = useTranslation(); +export function VehicleDetailContainer({ setBreadcrumbs, addRecentItem, setSelectedHeader }) { + const { vehId } = useParams(); + const { t } = useTranslation(); - const {loading, data, error, refetch} = useQuery(QUERY_VEHICLE_BY_ID, { - variables: {id: vehId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", + const { loading, data, error, refetch } = useQuery(QUERY_VEHICLE_BY_ID, { + variables: { id: vehId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" + }); + + useEffect(() => { + document.title = t("titles.vehicledetail", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }), + vehicle: + data && data.vehicles_by_pk + ? `${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""} ${ + (data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || "" + } ${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) || ""}` + : "" }); + setSelectedHeader("vehicles"); - useEffect(() => { - document.title = t("titles.vehicledetail", { - app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'}), - vehicle: - data && data.vehicles_by_pk - ? `${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""} ${ - (data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || "" - } ${ - (data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) || "" - }` - : "", - }); - setSelectedHeader("vehicles"); + setBreadcrumbs([ + { link: "/manage/vehicles", label: t("titles.bc.vehicles") }, + { + link: `/manage/vehicles/${vehId}`, + label: t("titles.bc.vehicle-details", { + vehicle: + data && data.vehicles_by_pk + ? `${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""} ${ + (data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || "" + } ${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) || ""}` + : "" + }) + } + ]); - setBreadcrumbs([ - {link: "/manage/vehicles", label: t("titles.bc.vehicles")}, - { - link: `/manage/vehicles/${vehId}`, - label: t("titles.bc.vehicle-details", { - vehicle: - data && data.vehicles_by_pk - ? `${ - (data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || "" - } ${ - (data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || "" - } ${ - (data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) || - "" - }` - : "", - }), - }, - ]); + if (data && data.vehicles_by_pk) + addRecentItem( + CreateRecentItem( + vehId, + "vehicle", + `${data.vehicles_by_pk.v_vin || "N/A"} | ${ + data.vehicles_by_pk.v_model_yr || "" + } ${data.vehicles_by_pk.v_make_desc || ""} ${data.vehicles_by_pk.v_model_desc || ""}`.trim(), + `/manage/vehicles/${vehId}` + ) + ); + }, [t, data, setBreadcrumbs, vehId, addRecentItem, setSelectedHeader]); - if (data && data.vehicles_by_pk) - addRecentItem( - CreateRecentItem( - vehId, - "vehicle", - `${data.vehicles_by_pk.v_vin || "N/A"} | ${ - data.vehicles_by_pk.v_model_yr || "" - } ${data.vehicles_by_pk.v_make_desc || ""} ${ - data.vehicles_by_pk.v_model_desc || "" - }`.trim(), - `/manage/vehicles/${vehId}` - ) - ); - }, [t, data, setBreadcrumbs, vehId, addRecentItem, setSelectedHeader]); - - if (loading) return ; - if (error) return ; - if (!data.vehicles_by_pk) return ; - return ( - - ); + if (loading) return ; + if (error) return ; + if (!data.vehicles_by_pk) return ; + return ; } export default connect(null, mapDispatchToProps)(VehicleDetailContainer); diff --git a/client/src/pages/vehicles/vehicles.page.component.jsx b/client/src/pages/vehicles/vehicles.page.component.jsx index c1fc6ceac..706a37a13 100644 --- a/client/src/pages/vehicles/vehicles.page.component.jsx +++ b/client/src/pages/vehicles/vehicles.page.component.jsx @@ -2,5 +2,5 @@ import React from "react"; import VehiclesListContainer from "../../components/vehicles-list/vehicles-list.container"; export default function VehiclesPageComponent() { - return ; + return ; } diff --git a/client/src/pages/vehicles/vehicles.page.container.jsx b/client/src/pages/vehicles/vehicles.page.container.jsx index 8c56c7030..96a0af2b7 100644 --- a/client/src/pages/vehicles/vehicles.page.container.jsx +++ b/client/src/pages/vehicles/vehicles.page.container.jsx @@ -1,28 +1,32 @@ -import React, {useEffect} from "react"; +import React, { useEffect } from "react"; import VehiclesPageComponent from "./vehicles.page.component"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import InstanceRenderManager from '../../utils/instanceRenderMgr'; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; -import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; +import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; const mapDispatchToProps = (dispatch) => ({ - setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), - setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)) }); -export function VehiclesPageContainer({setBreadcrumbs, setSelectedHeader}) { - const {t} = useTranslation(); +export function VehiclesPageContainer({ setBreadcrumbs, setSelectedHeader }) { + const { t } = useTranslation(); - useEffect(() => { - document.title = t("titles.vehicles",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} ); - setSelectedHeader("vehicles"); - setBreadcrumbs([ - {link: "/manage/vehicles", label: t("titles.bc.vehicles")}, - ]); - }, [t, setBreadcrumbs, setSelectedHeader]); + useEffect(() => { + document.title = t("titles.vehicles", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }); + setSelectedHeader("vehicles"); + setBreadcrumbs([{ link: "/manage/vehicles", label: t("titles.bc.vehicles") }]); + }, [t, setBreadcrumbs, setSelectedHeader]); - return ; + return ; } export default connect(null, mapDispatchToProps)(VehiclesPageContainer); diff --git a/client/src/redux/application/application.actions.js b/client/src/redux/application/application.actions.js index 37f1a294a..89826ea30 100644 --- a/client/src/redux/application/application.actions.js +++ b/client/src/redux/application/application.actions.js @@ -1,77 +1,77 @@ import ApplicationActionTypes from "./application.types"; export const startLoading = () => ({ - type: ApplicationActionTypes.START_LOADING, + type: ApplicationActionTypes.START_LOADING }); export const endLoading = (options) => ({ - type: ApplicationActionTypes.END_LOADING, - payload: options, + type: ApplicationActionTypes.END_LOADING, + payload: options }); export const setBreadcrumbs = (breadcrumbs) => ({ - type: ApplicationActionTypes.SET_BREAD_CRUMBS, - payload: breadcrumbs, + type: ApplicationActionTypes.SET_BREAD_CRUMBS, + payload: breadcrumbs }); export const calculateScheduleLoad = (rangeEnd) => ({ - type: ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD, - payload: rangeEnd, + type: ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD, + payload: rangeEnd }); export const scheduleLoadSuccess = (load) => ({ - type: ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_SUCCESS, - payload: load, + type: ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_SUCCESS, + payload: load }); export const scheduleLoadFailure = (error) => ({ - type: ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_FAILURE, - payload: error, + type: ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_FAILURE, + payload: error }); export const addRecentItem = (item) => ({ - type: ApplicationActionTypes.ADD_RECENT_ITEM, - payload: item, + type: ApplicationActionTypes.ADD_RECENT_ITEM, + payload: item }); export const setSelectedHeader = (key) => ({ - type: ApplicationActionTypes.SET_SELECTED_HEADER, - payload: key, + type: ApplicationActionTypes.SET_SELECTED_HEADER, + payload: key }); export const setJobReadOnly = (bool) => ({ - type: ApplicationActionTypes.SET_JOB_READONLY, - payload: bool, + type: ApplicationActionTypes.SET_JOB_READONLY, + payload: bool }); export const setPartnerVersion = (version) => ({ - type: ApplicationActionTypes.SET_PARTNER_VERSION, - payload: version, + type: ApplicationActionTypes.SET_PARTNER_VERSION, + payload: version }); export const setOnline = (isOnline) => ({ - type: ApplicationActionTypes.SET_ONLINE_STATUS, - payload: isOnline, + type: ApplicationActionTypes.SET_ONLINE_STATUS, + payload: isOnline }); -export const insertAuditTrail = ({jobid, billid, operation, type}) => ({ - type: ApplicationActionTypes.INSERT_AUDIT_TRAIL, - payload: {jobid, billid, operation, type }, +export const insertAuditTrail = ({ jobid, billid, operation, type }) => ({ + type: ApplicationActionTypes.INSERT_AUDIT_TRAIL, + payload: { jobid, billid, operation, type } }); export const setProblemJobs = (problemJobs) => ({ - type: ApplicationActionTypes.SET_PROBLEM_JOBS, - payload: problemJobs, + type: ApplicationActionTypes.SET_PROBLEM_JOBS, + payload: problemJobs }); export const setUpdateAvailable = (isUpdateAvailable) => ({ - type: ApplicationActionTypes.SET_UPDATE_AVAILABLE, - payload: isUpdateAvailable, + type: ApplicationActionTypes.SET_UPDATE_AVAILABLE, + payload: isUpdateAvailable }); export const setJoyRideSteps = (steps) => ({ - type: ApplicationActionTypes.SET_JOYRIDE_STEPS, - payload: steps, + type: ApplicationActionTypes.SET_JOYRIDE_STEPS, + payload: steps }); export const setJoyRideFinished = () => ({ - type: ApplicationActionTypes.SET_JOYRIDE_FINISHED, - //payload: isUpdateAvailable, + type: ApplicationActionTypes.SET_JOYRIDE_FINISHED + //payload: isUpdateAvailable, }); diff --git a/client/src/redux/application/application.reducer.js b/client/src/redux/application/application.reducer.js index 79fb829e2..62090cc1f 100644 --- a/client/src/redux/application/application.reducer.js +++ b/client/src/redux/application/application.reducer.js @@ -1,118 +1,114 @@ import ApplicationActionTypes from "./application.types"; const INITIAL_STATE = { - loading: false, - online: true, - updateAvailable: false, - breadcrumbs: [], - recentItems: [], - selectedHeader: "home", - problemJobs: [], - scheduleLoad: { - load: {}, - calculating: false, - error: null, - }, - jobReadOnly: false, - partnerVersion: null, - enableJoyRide: false, - joyRideSteps: [] + loading: false, + online: true, + updateAvailable: false, + breadcrumbs: [], + recentItems: [], + selectedHeader: "home", + problemJobs: [], + scheduleLoad: { + load: {}, + calculating: false, + error: null + }, + jobReadOnly: false, + partnerVersion: null, + enableJoyRide: false, + joyRideSteps: [] }; const applicationReducer = (state = INITIAL_STATE, action) => { - switch (action.type) { - case ApplicationActionTypes.SET_UPDATE_AVAILABLE: - return { - ...state, - updateAvailable: action.payload, - }; - case ApplicationActionTypes.SET_SELECTED_HEADER: - return { - ...state, - selectedHeader: action.payload, - }; - case ApplicationActionTypes.SET_ONLINE_STATUS: - return { - ...state, - online: action.payload, - }; - case ApplicationActionTypes.ADD_RECENT_ITEM: - return { - ...state, - recentItems: updateRecentItemsArray(state, action.payload), - }; - case ApplicationActionTypes.SET_BREAD_CRUMBS: - return { - ...state, - breadcrumbs: action.payload, - }; - case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD: - return { - ...state, - problemJobs: [], - scheduleLoad: {...state.scheduleLoad, calculating: true, error: null}, - }; - case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_SUCCESS: - return { - ...state, - scheduleLoad: { - ...state.scheduleLoad, - load: action.payload, - calculating: false, - }, - }; - case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_FAILURE: - return { - ...state, - scheduleLoad: { - ...state.scheduleLoad, - calculating: false, - error: action.payload, - }, - }; - case ApplicationActionTypes.START_LOADING: - return { - ...state, - loading: true, - }; - case ApplicationActionTypes.END_LOADING: - return { - ...state, - loading: false, - }; + switch (action.type) { + case ApplicationActionTypes.SET_UPDATE_AVAILABLE: + return { + ...state, + updateAvailable: action.payload + }; + case ApplicationActionTypes.SET_SELECTED_HEADER: + return { + ...state, + selectedHeader: action.payload + }; + case ApplicationActionTypes.SET_ONLINE_STATUS: + return { + ...state, + online: action.payload + }; + case ApplicationActionTypes.ADD_RECENT_ITEM: + return { + ...state, + recentItems: updateRecentItemsArray(state, action.payload) + }; + case ApplicationActionTypes.SET_BREAD_CRUMBS: + return { + ...state, + breadcrumbs: action.payload + }; + case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD: + return { + ...state, + problemJobs: [], + scheduleLoad: { ...state.scheduleLoad, calculating: true, error: null } + }; + case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_SUCCESS: + return { + ...state, + scheduleLoad: { + ...state.scheduleLoad, + load: action.payload, + calculating: false + } + }; + case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_FAILURE: + return { + ...state, + scheduleLoad: { + ...state.scheduleLoad, + calculating: false, + error: action.payload + } + }; + case ApplicationActionTypes.START_LOADING: + return { + ...state, + loading: true + }; + case ApplicationActionTypes.END_LOADING: + return { + ...state, + loading: false + }; - case ApplicationActionTypes.SET_JOB_READONLY: - return {...state, jobReadOnly: action.payload}; + case ApplicationActionTypes.SET_JOB_READONLY: + return { ...state, jobReadOnly: action.payload }; - case ApplicationActionTypes.SET_PARTNER_VERSION: - return {...state, partnerVersion: action.payload}; - case ApplicationActionTypes.SET_PROBLEM_JOBS: { - return {...state, problemJobs: action.payload}; - } - case ApplicationActionTypes.SET_JOYRIDE_STEPS: { - return {...state, enableJoyRide:true, joyRideSteps: action.payload} - } - case ApplicationActionTypes.SET_JOYRIDE_FINISHED: { - return {...state, enableJoyRide:false, joyRideSteps: []} - } - default: - return state; + case ApplicationActionTypes.SET_PARTNER_VERSION: + return { ...state, partnerVersion: action.payload }; + case ApplicationActionTypes.SET_PROBLEM_JOBS: { + return { ...state, problemJobs: action.payload }; } + case ApplicationActionTypes.SET_JOYRIDE_STEPS: { + return { ...state, enableJoyRide: true, joyRideSteps: action.payload }; + } + case ApplicationActionTypes.SET_JOYRIDE_FINISHED: { + return { ...state, enableJoyRide: false, joyRideSteps: [] }; + } + default: + return state; + } }; export default applicationReducer; const updateRecentItemsArray = (state, newItem) => { - //Check to see if the new item is in the list. - const matchingIndex = state.recentItems.findIndex((i) => i.id === newItem.id); + //Check to see if the new item is in the list. + const matchingIndex = state.recentItems.findIndex((i) => i.id === newItem.id); - if (matchingIndex >= 0) { - return [ - newItem, - ...state.recentItems.slice(0, matchingIndex), - ...state.recentItems.slice(matchingIndex + 1, 9), - ]; - } else { - return [newItem, ...state.recentItems.slice(0, 9)]; - } + if (matchingIndex >= 0) { + return [newItem, ...state.recentItems.slice(0, matchingIndex), ...state.recentItems.slice(matchingIndex + 1, 9)]; + } else { + return [newItem, ...state.recentItems.slice(0, 9)]; + } }; diff --git a/client/src/redux/application/application.sagas.js b/client/src/redux/application/application.sagas.js index 4ff53cd2d..3aca6fa5d 100644 --- a/client/src/redux/application/application.sagas.js +++ b/client/src/redux/application/application.sagas.js @@ -1,303 +1,263 @@ import dayjs from "../../utils/day"; -import {all, call, put, select, takeLatest} from "redux-saga/effects"; -import {QUERY_SCHEDULE_LOAD_DATA} from "../../graphql/appointments.queries"; -import {INSERT_AUDIT_TRAIL} from "../../graphql/audit_trail.queries"; +import { all, call, put, select, takeLatest } from "redux-saga/effects"; +import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries"; +import { INSERT_AUDIT_TRAIL } from "../../graphql/audit_trail.queries"; import client from "../../utils/GraphQLClient"; -import {CalculateLoad, CheckJobBucket} from "../../utils/SSSUtils"; -import {scheduleLoadFailure, scheduleLoadSuccess, setProblemJobs,} from "./application.actions"; +import { CalculateLoad, CheckJobBucket } from "../../utils/SSSUtils"; +import { scheduleLoadFailure, scheduleLoadSuccess, setProblemJobs } from "./application.actions"; import ApplicationActionTypes from "./application.types"; export function* onCalculateScheduleLoad() { - yield takeLatest( - ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD, - calculateScheduleLoad - ); + yield takeLatest(ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD, calculateScheduleLoad); } -export function* calculateScheduleLoad({payload: end}) { - //REMINDER: dayjs.js is not immutable. Today WILL change when adjusted. - const today = dayjs().startOf("day"); - const state = yield select(); - const buckets = state.user.bodyshop.ssbuckets; +export function* calculateScheduleLoad({ payload: end }) { + //REMINDER: dayjs.js is not immutable. Today WILL change when adjusted. + const today = dayjs().startOf("day"); + const state = yield select(); + const buckets = state.user.bodyshop.ssbuckets; - try { - const result = yield client.query({ - query: QUERY_SCHEDULE_LOAD_DATA, - variables: { - start: today, - end: end, - }, + try { + const result = yield client.query({ + query: QUERY_SCHEDULE_LOAD_DATA, + variables: { + start: today, + end: end + } + }); + const { prodJobs, arrJobs, compJobs } = result.data; + + const load = { + productionTotal: {}, + productionHours: 0 + }; + let problemJobs = []; + //Set the current load. + buckets.forEach((bucket) => { + load.productionTotal[bucket.id] = { count: 0, label: bucket.label }; + }); + + prodJobs.forEach((item) => { + //Add all of the jobs currently in production to the buckets so that we have a starting point. + if (!item.actual_completion && dayjs(item.scheduled_completion).isBefore(dayjs().startOf("day"))) { + problemJobs.push({ + ...item, + code: "Job was scheduled to go, but it has not been completed. Update the scheduled completion date to correct projections" }); - const {prodJobs, arrJobs, compJobs} = result.data; + } - const load = { - productionTotal: {}, - productionHours: 0, - }; - let problemJobs = []; - //Set the current load. - buckets.forEach((bucket) => { - load.productionTotal[bucket.id] = {count: 0, label: bucket.label}; + if (item.actual_completion && dayjs(item.actual_completion).isBefore(dayjs().startOf("day"))) { + problemJobs.push({ + ...item, + code: "Job is already marked as completed, but it is still in production. This job should be removed from production" }); - - prodJobs.forEach((item) => { - //Add all of the jobs currently in production to the buckets so that we have a starting point. - if ( - !item.actual_completion && - dayjs(item.scheduled_completion).isBefore(dayjs().startOf("day")) - ) { - problemJobs.push({ - ...item, - code: "Job was scheduled to go, but it has not been completed. Update the scheduled completion date to correct projections", - }); - } - - if ( - item.actual_completion && - dayjs(item.actual_completion).isBefore(dayjs().startOf("day")) - ) { - problemJobs.push({ - ...item, - code: "Job is already marked as completed, but it is still in production. This job should be removed from production", - }); - } - if (!(item.actual_completion || item.scheduled_completion)) { - problemJobs.push({ - ...item, - code: "Job does not have a scheduled or actual completion date. Update the scheduled or actual completion dates to correct projections", - }); - } - - const bucketId = CheckJobBucket(buckets, item); - load.productionHours = - load.productionHours + - item.labhrs.aggregate.sum.mod_lb_hrs + - item.larhrs.aggregate.sum.mod_lb_hrs; - if (bucketId) { - load.productionTotal[bucketId].count = - load.productionTotal[bucketId].count + 1; - } else { - console.log("Uh oh, this job doesn't fit in a bucket!", item); - } + } + if (!(item.actual_completion || item.scheduled_completion)) { + problemJobs.push({ + ...item, + code: "Job does not have a scheduled or actual completion date. Update the scheduled or actual completion dates to correct projections" }); + } - arrJobs.forEach((item) => { - if (!item.scheduled_in) { - console.log("JOB HAS NO SCHEDULED IN DATE.", item); - problemJobs.push({ - ...item, - code: "Job has no scheduled in date", - }); - } - if (!item.actual_completion && item.actual_in && !item.inproduction) { - problemJobs.push({ - ...item, - code: "Job has an actual in date, but no actual completion date and is not marked as in production", - }); - } - if (item.actual_in && dayjs(item.actual_in).isAfter(dayjs())) { - problemJobs.push({ - ...item, - code: "Job has an actual in date set in the future", - }); - } - if ( - item.actual_completion && - dayjs(item.actual_completion).isAfter(dayjs()) - ) { - problemJobs.push({ - ...item, - code: "Job has an actual completion date set in the future", - }); - } - if (item.actual_completion && item.inproduction) { - problemJobs.push({ - ...item, - code: "Job has an actual completion date but it is still marked in production", - }); - } + const bucketId = CheckJobBucket(buckets, item); + load.productionHours = + load.productionHours + item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs; + if (bucketId) { + load.productionTotal[bucketId].count = load.productionTotal[bucketId].count + 1; + } else { + console.log("Uh oh, this job doesn't fit in a bucket!", item); + } + }); - const itemDate = dayjs(item.actual_in || item.scheduled_in).format( - "YYYY-MM-DD" - ); - - const AddJobForSchedulingCalc = !item.inproduction; - - if (!!load[itemDate]) { - load[itemDate].allHoursIn = - (load[itemDate].allHoursIn || 0) + - item.labhrs.aggregate.sum.mod_lb_hrs + - item.larhrs.aggregate.sum.mod_lb_hrs; - - //If the job hasn't already arrived, add it to the jobs in list. - // Make sure it also hasn't already been completed, or isn't an in and out job. - //This prevents the duplicate counting. - load[itemDate].allJobsIn.push(item); - if (AddJobForSchedulingCalc) { - load[itemDate].jobsIn.push(item); - load[itemDate].hoursIn = - (load[itemDate].hoursIn || 0) + - item.labhrs.aggregate.sum.mod_lb_hrs + - item.larhrs.aggregate.sum.mod_lb_hrs; - } - } else { - load[itemDate] = { - allJobsIn: [item], - jobsIn: AddJobForSchedulingCalc ? [item] : [], //Same as above, only add it if it isn't already in production. - jobsOut: [], - allJobsOut: [], - allHoursIn: - item.labhrs.aggregate.sum.mod_lb_hrs + - item.larhrs.aggregate.sum.mod_lb_hrs, - hoursIn: AddJobForSchedulingCalc - ? item.labhrs.aggregate.sum.mod_lb_hrs + - item.larhrs.aggregate.sum.mod_lb_hrs - : 0, - }; - } + arrJobs.forEach((item) => { + if (!item.scheduled_in) { + console.log("JOB HAS NO SCHEDULED IN DATE.", item); + problemJobs.push({ + ...item, + code: "Job has no scheduled in date" }); - - compJobs.forEach((item) => { - if (!(item.actual_completion || item.scheduled_completion)) - console.warn("JOB HAS NO COMPLETION DATE.", item); - - const inProdJobs = prodJobs.find((p) => p.id === item.id); - const inArrJobs = arrJobs.find((p) => p.id === item.id); - - const AddJobForSchedulingCalc = inProdJobs || inArrJobs; - - const itemDate = dayjs( - item.actual_completion || item.scheduled_completion - ).format("YYYY-MM-DD"); - //Skip it, it's already completed. - - if (!!load[itemDate]) { - load[itemDate].allHoursOut = - (load[itemDate].allHoursOut || 0) + - item.labhrs.aggregate.sum.mod_lb_hrs + - item.larhrs.aggregate.sum.mod_lb_hrs; - //Add only the jobs that are still in production to get rid of. - //If it's not in production, we'd subtract unnecessarily. - load[itemDate].allJobsOut.push(item); - - if (AddJobForSchedulingCalc) { - load[itemDate].jobsOut.push(item); - load[itemDate].hoursOut = - (load[itemDate].hoursOut || 0) + - item.labhrs.aggregate.sum.mod_lb_hrs + - item.larhrs.aggregate.sum.mod_lb_hrs; - } - } else { - load[itemDate] = { - allJobsOut: [item], - jobsOut: AddJobForSchedulingCalc ? [item] : [], //Same as above. - hoursOut: AddJobForSchedulingCalc - ? item.labhrs.aggregate.sum.mod_lb_hrs + - item.larhrs.aggregate.sum.mod_lb_hrs - : 0, - allHoursOut: - item.labhrs.aggregate.sum.mod_lb_hrs + - item.larhrs.aggregate.sum.mod_lb_hrs, - }; - } + } + if (!item.actual_completion && item.actual_in && !item.inproduction) { + problemJobs.push({ + ...item, + code: "Job has an actual in date, but no actual completion date and is not marked as in production" }); + } + if (item.actual_in && dayjs(item.actual_in).isAfter(dayjs())) { + problemJobs.push({ + ...item, + code: "Job has an actual in date set in the future" + }); + } + if (item.actual_completion && dayjs(item.actual_completion).isAfter(dayjs())) { + problemJobs.push({ + ...item, + code: "Job has an actual completion date set in the future" + }); + } + if (item.actual_completion && item.inproduction) { + problemJobs.push({ + ...item, + code: "Job has an actual completion date but it is still marked in production" + }); + } - //Propagate the expected load to each day. + const itemDate = dayjs(item.actual_in || item.scheduled_in).format("YYYY-MM-DD"); - const range = Math.round(dayjs.duration(end.diff(today)).asDays()) + 1; - for (var day = 0; day < range; day++) { - const current = dayjs(today).add(day, "day").format("YYYY-MM-DD"); - const prev = dayjs(today) - .add(day - 1, "day") - .format("YYYY-MM-DD"); - if (!!!load[current]) { - load[current] = {}; - } + const AddJobForSchedulingCalc = !item.inproduction; - if (day === 0) { - //Starting on day 1. The load is current. - load[current].expectedLoad = CalculateLoad( - load.productionTotal, - buckets, - load[current].jobsIn || [], - load[current].jobsOut || [] - ); - load[current].expectedJobCount = - prodJobs.length + - (load[current].jobsIn || []).length - - (load[current].jobsOut || []).length; - load[current].expectedHours = - load.productionHours + - (load[current].hoursIn || 0) - - (load[current].hoursOut || 0); - } else { - load[current].expectedLoad = CalculateLoad( - load[prev].expectedLoad, - buckets, - load[current].jobsIn || [], - load[current].jobsOut || [] - ); - load[current].expectedJobCount = - load[prev].expectedJobCount + - (load[current].jobsIn || []).length - - (load[current].jobsOut || []).length; - load[current].expectedHours = - load[prev].expectedHours + - (load[current].hoursIn || 0) - - (load[current].hoursOut || 0); - } + if (!!load[itemDate]) { + load[itemDate].allHoursIn = + (load[itemDate].allHoursIn || 0) + + item.labhrs.aggregate.sum.mod_lb_hrs + + item.larhrs.aggregate.sum.mod_lb_hrs; + + //If the job hasn't already arrived, add it to the jobs in list. + // Make sure it also hasn't already been completed, or isn't an in and out job. + //This prevents the duplicate counting. + load[itemDate].allJobsIn.push(item); + if (AddJobForSchedulingCalc) { + load[itemDate].jobsIn.push(item); + load[itemDate].hoursIn = + (load[itemDate].hoursIn || 0) + item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs; } + } else { + load[itemDate] = { + allJobsIn: [item], + jobsIn: AddJobForSchedulingCalc ? [item] : [], //Same as above, only add it if it isn't already in production. + jobsOut: [], + allJobsOut: [], + allHoursIn: item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs, + hoursIn: AddJobForSchedulingCalc + ? item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs + : 0 + }; + } + }); - yield put(setProblemJobs(problemJobs)); - yield put(scheduleLoadSuccess(load)); - } catch (error) { - yield put(scheduleLoadFailure(error)); + compJobs.forEach((item) => { + if (!(item.actual_completion || item.scheduled_completion)) console.warn("JOB HAS NO COMPLETION DATE.", item); + + const inProdJobs = prodJobs.find((p) => p.id === item.id); + const inArrJobs = arrJobs.find((p) => p.id === item.id); + + const AddJobForSchedulingCalc = inProdJobs || inArrJobs; + + const itemDate = dayjs(item.actual_completion || item.scheduled_completion).format("YYYY-MM-DD"); + //Skip it, it's already completed. + + if (!!load[itemDate]) { + load[itemDate].allHoursOut = + (load[itemDate].allHoursOut || 0) + + item.labhrs.aggregate.sum.mod_lb_hrs + + item.larhrs.aggregate.sum.mod_lb_hrs; + //Add only the jobs that are still in production to get rid of. + //If it's not in production, we'd subtract unnecessarily. + load[itemDate].allJobsOut.push(item); + + if (AddJobForSchedulingCalc) { + load[itemDate].jobsOut.push(item); + load[itemDate].hoursOut = + (load[itemDate].hoursOut || 0) + + item.labhrs.aggregate.sum.mod_lb_hrs + + item.larhrs.aggregate.sum.mod_lb_hrs; + } + } else { + load[itemDate] = { + allJobsOut: [item], + jobsOut: AddJobForSchedulingCalc ? [item] : [], //Same as above. + hoursOut: AddJobForSchedulingCalc + ? item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs + : 0, + allHoursOut: item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs + }; + } + }); + + //Propagate the expected load to each day. + + const range = Math.round(dayjs.duration(end.diff(today)).asDays()) + 1; + for (var day = 0; day < range; day++) { + const current = dayjs(today).add(day, "day").format("YYYY-MM-DD"); + const prev = dayjs(today) + .add(day - 1, "day") + .format("YYYY-MM-DD"); + if (!!!load[current]) { + load[current] = {}; + } + + if (day === 0) { + //Starting on day 1. The load is current. + load[current].expectedLoad = CalculateLoad( + load.productionTotal, + buckets, + load[current].jobsIn || [], + load[current].jobsOut || [] + ); + load[current].expectedJobCount = + prodJobs.length + (load[current].jobsIn || []).length - (load[current].jobsOut || []).length; + load[current].expectedHours = + load.productionHours + (load[current].hoursIn || 0) - (load[current].hoursOut || 0); + } else { + load[current].expectedLoad = CalculateLoad( + load[prev].expectedLoad, + buckets, + load[current].jobsIn || [], + load[current].jobsOut || [] + ); + load[current].expectedJobCount = + load[prev].expectedJobCount + (load[current].jobsIn || []).length - (load[current].jobsOut || []).length; + load[current].expectedHours = + load[prev].expectedHours + (load[current].hoursIn || 0) - (load[current].hoursOut || 0); + } } + + yield put(setProblemJobs(problemJobs)); + yield put(scheduleLoadSuccess(load)); + } catch (error) { + yield put(scheduleLoadFailure(error)); + } } export function* onInsertAuditTrail() { - yield takeLatest( - ApplicationActionTypes.INSERT_AUDIT_TRAIL, - insertAuditTrailSaga - ); + yield takeLatest(ApplicationActionTypes.INSERT_AUDIT_TRAIL, insertAuditTrailSaga); } -export function* insertAuditTrailSaga({ - payload: {jobid, billid, operation, type}, - }) { - const state = yield select(); - const bodyshop = state.user.bodyshop; - const currentUser = state.user.currentUser; +export function* insertAuditTrailSaga({ payload: { jobid, billid, operation, type } }) { + const state = yield select(); + const bodyshop = state.user.bodyshop; + const currentUser = state.user.currentUser; - const variables = { - auditObj: { - bodyshopid: bodyshop.id, - jobid, - billid, - operation, - type,useremail: currentUser.email, - }, - }; - yield client.mutate({ - mutation: INSERT_AUDIT_TRAIL, - variables, - update(cache, { data }) { - cache.modify({ - fields: { - audit_trail(existingAuditTrail, { readField }) { - const newAuditTrail = cache.writeQuery({ - data: data, - query: INSERT_AUDIT_TRAIL, - variables, - }); - return [...existingAuditTrail, newAuditTrail]; - }, - }, + const variables = { + auditObj: { + bodyshopid: bodyshop.id, + jobid, + billid, + operation, + type, + useremail: currentUser.email + } + }; + yield client.mutate({ + mutation: INSERT_AUDIT_TRAIL, + variables, + update(cache, { data }) { + cache.modify({ + fields: { + audit_trail(existingAuditTrail, { readField }) { + const newAuditTrail = cache.writeQuery({ + data: data, + query: INSERT_AUDIT_TRAIL, + variables }); - }, - }); + return [...existingAuditTrail, newAuditTrail]; + } + } + }); + } + }); } export function* applicationSagas() { - yield all([call(onCalculateScheduleLoad), call(onInsertAuditTrail)]); + yield all([call(onCalculateScheduleLoad), call(onInsertAuditTrail)]); } diff --git a/client/src/redux/application/application.selectors.js b/client/src/redux/application/application.selectors.js index 9bf486a1e..f5c1e0770 100644 --- a/client/src/redux/application/application.selectors.js +++ b/client/src/redux/application/application.selectors.js @@ -1,62 +1,26 @@ -import {createSelector} from "reselect"; +import { createSelector } from "reselect"; const selectApplication = (state) => state.application; -export const selectLoading = createSelector( - [selectApplication], - (application) => application.loading -); +export const selectLoading = createSelector([selectApplication], (application) => application.loading); -export const selectBreadcrumbs = createSelector( - [selectApplication], - (application) => application.breadcrumbs -); +export const selectBreadcrumbs = createSelector([selectApplication], (application) => application.breadcrumbs); -export const selectRecentItems = createSelector( - [selectApplication], - (application) => application.recentItems -); +export const selectRecentItems = createSelector([selectApplication], (application) => application.recentItems); -export const selectScheduleLoad = createSelector( - [selectApplication], - (application) => application.scheduleLoad.load -); -export const selectPartnerVersion = createSelector( - [selectApplication], - (application) => application.partnerVersion -); +export const selectScheduleLoad = createSelector([selectApplication], (application) => application.scheduleLoad.load); +export const selectPartnerVersion = createSelector([selectApplication], (application) => application.partnerVersion); export const selectScheduleLoadCalculating = createSelector( - [selectApplication], - (application) => application.scheduleLoad.calculating + [selectApplication], + (application) => application.scheduleLoad.calculating ); -export const selectSelectedHeader = createSelector( - [selectApplication], - (application) => application.selectedHeader -); +export const selectSelectedHeader = createSelector([selectApplication], (application) => application.selectedHeader); -export const selectJobReadOnly = createSelector( - [selectApplication], - (application) => application.jobReadOnly -); -export const selectOnline = createSelector( - [selectApplication], - (application) => application.online -); -export const selectProblemJobs = createSelector( - [selectApplication], - (application) => application.problemJobs -); -export const selectUpdateAvailable = createSelector( - [selectApplication], - (application) => application.updateAvailable -); -export const selectEnableJoyRide= createSelector( - [selectApplication], - (application) => application.enableJoyRide -); -export const selectJoyRideSteps= createSelector( - [selectApplication], - (application) => application.joyRideSteps -); +export const selectJobReadOnly = createSelector([selectApplication], (application) => application.jobReadOnly); +export const selectOnline = createSelector([selectApplication], (application) => application.online); +export const selectProblemJobs = createSelector([selectApplication], (application) => application.problemJobs); +export const selectUpdateAvailable = createSelector([selectApplication], (application) => application.updateAvailable); +export const selectEnableJoyRide = createSelector([selectApplication], (application) => application.enableJoyRide); +export const selectJoyRideSteps = createSelector([selectApplication], (application) => application.joyRideSteps); diff --git a/client/src/redux/application/application.types.js b/client/src/redux/application/application.types.js index d5e977734..3d8e0763b 100644 --- a/client/src/redux/application/application.types.js +++ b/client/src/redux/application/application.types.js @@ -1,19 +1,19 @@ const ApplicationActionTypes = { - START_LOADING: "START_LOADING", - END_LOADING: "END_LOADING", - SET_BREAD_CRUMBS: "SET_BREAD_CRUMBS", - CALCULATE_SCHEDULE_LOAD: "CALCULATE_SCHEDULE_LOAD", - CALCULATE_SCHEDULE_LOAD_SUCCESS: "CALCULATE_SCHEDULE_LOAD_SUCCESS", - CALCULATE_SCHEDULE_LOAD_FAILURE: "CALCULATE_SCHEDULE_LOAD_FAILURE", - ADD_RECENT_ITEM: "ADD_RECENT_ITEM", - SET_SELECTED_HEADER: "SET_SELECTED_HEADER", - SET_JOB_READONLY: "SET_JOB_READONLY", - SET_PARTNER_VERSION: "SET_PARTNER_VERSION", - SET_ONLINE_STATUS: "SET_ONLINE_STATUS", - INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL", - SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS", - SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE", - SET_JOYRIDE_STEPS: "SET_JOYRIDE_STEPS", - SET_JOYRIDE_FINISHED:"SET_JOYRIDE_FINISHED" + START_LOADING: "START_LOADING", + END_LOADING: "END_LOADING", + SET_BREAD_CRUMBS: "SET_BREAD_CRUMBS", + CALCULATE_SCHEDULE_LOAD: "CALCULATE_SCHEDULE_LOAD", + CALCULATE_SCHEDULE_LOAD_SUCCESS: "CALCULATE_SCHEDULE_LOAD_SUCCESS", + CALCULATE_SCHEDULE_LOAD_FAILURE: "CALCULATE_SCHEDULE_LOAD_FAILURE", + ADD_RECENT_ITEM: "ADD_RECENT_ITEM", + SET_SELECTED_HEADER: "SET_SELECTED_HEADER", + SET_JOB_READONLY: "SET_JOB_READONLY", + SET_PARTNER_VERSION: "SET_PARTNER_VERSION", + SET_ONLINE_STATUS: "SET_ONLINE_STATUS", + INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL", + SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS", + SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE", + SET_JOYRIDE_STEPS: "SET_JOYRIDE_STEPS", + SET_JOYRIDE_FINISHED: "SET_JOYRIDE_FINISHED" }; export default ApplicationActionTypes; diff --git a/client/src/redux/email/email.actions.js b/client/src/redux/email/email.actions.js index 47a235a4b..174763bf6 100644 --- a/client/src/redux/email/email.actions.js +++ b/client/src/redux/email/email.actions.js @@ -1,24 +1,24 @@ import EmailActionTypes from "./email.types"; export const toggleEmailOverlayVisible = () => ({ - type: EmailActionTypes.TOGGLE_EMAIL_OVERLAY_VISIBLE + type: EmailActionTypes.TOGGLE_EMAIL_OVERLAY_VISIBLE }); -export const setEmailOptions = options => ({ - type: EmailActionTypes.SET_EMAIL_OPTIONS, - payload: options +export const setEmailOptions = (options) => ({ + type: EmailActionTypes.SET_EMAIL_OPTIONS, + payload: options }); -export const sendEmail = email => ({ - type: EmailActionTypes.SEND_EMAIL, - payload: email +export const sendEmail = (email) => ({ + type: EmailActionTypes.SEND_EMAIL, + payload: email }); -export const sendEmailSuccess = options => ({ - type: EmailActionTypes.SEND_EMAIL_SUCCESS +export const sendEmailSuccess = (options) => ({ + type: EmailActionTypes.SEND_EMAIL_SUCCESS }); -export const sendEmailFailure = error => ({ - type: EmailActionTypes.SEND_EMAIL_FAILURE, - payload: error +export const sendEmailFailure = (error) => ({ + type: EmailActionTypes.SEND_EMAIL_FAILURE, + payload: error }); diff --git a/client/src/redux/email/email.reducer.js b/client/src/redux/email/email.reducer.js index 422edd839..bd9485d97 100644 --- a/client/src/redux/email/email.reducer.js +++ b/client/src/redux/email/email.reducer.js @@ -2,41 +2,41 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr"; import EmailActionTypes from "./email.types"; const INITIAL_STATE = { - emailConfig: { - messageOptions: { - from: { - name: "ShopName", - address: InstanceRenderManager({ - imex: "noreply@iemx.online", - rome: "noreply@romeonline.io", - }), - }, - to: null, - replyTo: null, - }, - template: { name: null, variables: {} }, + emailConfig: { + messageOptions: { + from: { + name: "ShopName", + address: InstanceRenderManager({ + imex: "noreply@iemx.online", + rome: "noreply@romeonline.io" + }) + }, + to: null, + replyTo: null }, + template: { name: null, variables: {} } + }, - open: false, - error: null, + open: false, + error: null }; const emailReducer = (state = INITIAL_STATE, action) => { - switch (action.type) { - case EmailActionTypes.TOGGLE_EMAIL_OVERLAY_VISIBLE: - return { - ...state, - open: !state.open, - }; - case EmailActionTypes.SET_EMAIL_OPTIONS: - return { - ...state, - emailConfig: { ...action.payload }, - open: true, - }; - default: - return state; - } + switch (action.type) { + case EmailActionTypes.TOGGLE_EMAIL_OVERLAY_VISIBLE: + return { + ...state, + open: !state.open + }; + case EmailActionTypes.SET_EMAIL_OPTIONS: + return { + ...state, + emailConfig: { ...action.payload }, + open: true + }; + default: + return state; + } }; export default emailReducer; diff --git a/client/src/redux/email/email.sagas.js b/client/src/redux/email/email.sagas.js index 4f54ad297..a9b1a250a 100644 --- a/client/src/redux/email/email.sagas.js +++ b/client/src/redux/email/email.sagas.js @@ -1,4 +1,4 @@ -import {all} from "redux-saga/effects"; +import { all } from "redux-saga/effects"; // import { sendEmailFailure, sendEmailSuccess } from "./email.actions"; // import { renderTemplate } from "../application/application.actions"; // import EmailActionTypes from "./email.types"; @@ -57,10 +57,10 @@ import {all} from "redux-saga/effects"; // } export function* emailSagas() { - yield all([ - // call(onSendEmail), - // call(onSendEmailFailure), - // call(onSendEmailSuccess) - //call(onSetEmailOptions), - ]); + yield all([ + // call(onSendEmail), + // call(onSendEmailFailure), + // call(onSendEmailSuccess) + //call(onSetEmailOptions), + ]); } diff --git a/client/src/redux/email/email.selectors.js b/client/src/redux/email/email.selectors.js index 7445a74df..37b539d7b 100644 --- a/client/src/redux/email/email.selectors.js +++ b/client/src/redux/email/email.selectors.js @@ -1,13 +1,7 @@ -import {createSelector} from "reselect"; +import { createSelector } from "reselect"; const selectEmail = (state) => state.email; -export const selectEmailVisible = createSelector( - [selectEmail], - (email) => email.open -); +export const selectEmailVisible = createSelector([selectEmail], (email) => email.open); -export const selectEmailConfig = createSelector( - [selectEmail], - (email) => email.emailConfig -); +export const selectEmailConfig = createSelector([selectEmail], (email) => email.emailConfig); diff --git a/client/src/redux/email/email.types.js b/client/src/redux/email/email.types.js index 5b897db9b..b7e1ef876 100644 --- a/client/src/redux/email/email.types.js +++ b/client/src/redux/email/email.types.js @@ -1,8 +1,8 @@ const EmailActionTypes = { - TOGGLE_EMAIL_OVERLAY_VISIBLE: "TOGGLE_EMAIL_OVERLAY_VISIBLE", - SET_EMAIL_OPTIONS: "SET_EMAIL_OPTIONS", - SEND_EMAIL: "SEND_EMAIL", - SEND_EMAIL_SUCCESS: "SEND_EMAIL_SUCCESS", - SEND_EMAIL_FAILURE: "SEND_EMAIL_FAILURE" + TOGGLE_EMAIL_OVERLAY_VISIBLE: "TOGGLE_EMAIL_OVERLAY_VISIBLE", + SET_EMAIL_OPTIONS: "SET_EMAIL_OPTIONS", + SEND_EMAIL: "SEND_EMAIL", + SEND_EMAIL_SUCCESS: "SEND_EMAIL_SUCCESS", + SEND_EMAIL_FAILURE: "SEND_EMAIL_FAILURE" }; export default EmailActionTypes; diff --git a/client/src/redux/media/media.actions.js b/client/src/redux/media/media.actions.js index 0787a34e2..9357ff6e2 100644 --- a/client/src/redux/media/media.actions.js +++ b/client/src/redux/media/media.actions.js @@ -1,43 +1,43 @@ import MediaActionTypes from "./media.types"; export const getJobMedia = (jobid) => ({ - type: MediaActionTypes.GET_MEDIA_FOR_JOB, - payload: jobid, + type: MediaActionTypes.GET_MEDIA_FOR_JOB, + payload: jobid }); -export const getBillMedia = ({jobid, invoice_number}) => { - return { - type: MediaActionTypes.GET_MEDIA_FOR_BILL, - payload: {jobid, invoice_number}, - }; +export const getBillMedia = ({ jobid, invoice_number }) => { + return { + type: MediaActionTypes.GET_MEDIA_FOR_BILL, + payload: { jobid, invoice_number } + }; }; -export const setJobMedia = ({jobid, media}) => ({ - type: MediaActionTypes.SET_MEDIA_FOR_JOB, - payload: {jobid, media}, +export const setJobMedia = ({ jobid, media }) => ({ + type: MediaActionTypes.SET_MEDIA_FOR_JOB, + payload: { jobid, media } }); -export const addMediaForJob = ({jobid, media}) => ({ - type: MediaActionTypes.ADD_MEDIA_FOR_JOB, - payload: {jobid, media}, +export const addMediaForJob = ({ jobid, media }) => ({ + type: MediaActionTypes.ADD_MEDIA_FOR_JOB, + payload: { jobid, media } }); -export const getJobMediaError = ({error, message}) => ({ - type: MediaActionTypes.GET_MEDIA_FOR_JOB_ERROR, - payload: {error, message}, +export const getJobMediaError = ({ error, message }) => ({ + type: MediaActionTypes.GET_MEDIA_FOR_JOB_ERROR, + payload: { error, message } }); -export const toggleMediaSelected = ({jobid, filename}) => ({ - type: MediaActionTypes.TOGGLE_MEDIA_SELECTED, - payload: {jobid, filename}, +export const toggleMediaSelected = ({ jobid, filename }) => ({ + type: MediaActionTypes.TOGGLE_MEDIA_SELECTED, + payload: { jobid, filename } }); -export const deselectAllMediaForJob = ({jobid}) => ({ - type: MediaActionTypes.DESELECT_ALL_MEDIA_FOR_JOB, - payload: {jobid}, +export const deselectAllMediaForJob = ({ jobid }) => ({ + type: MediaActionTypes.DESELECT_ALL_MEDIA_FOR_JOB, + payload: { jobid } }); -export const selectAllmediaForJob = ({jobid}) => ({ - type: MediaActionTypes.SELECT_ALL_MEDIA_FOR_JOB, - payload: {jobid}, +export const selectAllmediaForJob = ({ jobid }) => ({ + type: MediaActionTypes.SELECT_ALL_MEDIA_FOR_JOB, + payload: { jobid } }); diff --git a/client/src/redux/media/media.reducer.js b/client/src/redux/media/media.reducer.js index 5a69d8e29..ace8cac46 100644 --- a/client/src/redux/media/media.reducer.js +++ b/client/src/redux/media/media.reducer.js @@ -1,51 +1,51 @@ import MediaActionTypes from "./media.types"; -const INITIAL_STATE = {error: null}; +const INITIAL_STATE = { error: null }; const mediaReducer = (state = INITIAL_STATE, action) => { - switch (action.type) { - case MediaActionTypes.SET_MEDIA_FOR_JOB: - return {...state, [action.payload.jobid]: action.payload.media}; - case MediaActionTypes.GET_MEDIA_FOR_JOB_ERROR: - return {...state, error: action.payload}; - case MediaActionTypes.ADD_MEDIA_FOR_JOB: - return { - ...state, - [action.payload.jobid]: [ - ...(state[action.payload.jobid] ? state[action.payload.jobid] : []), - ...(action.payload.media || []), - ], - }; - case MediaActionTypes.TOGGLE_MEDIA_SELECTED: - return { - ...state, - [action.payload.jobid]: state[action.payload.jobid].map((p) => { - if (p.filename === action.payload.filename) { - p.isSelected = !p.isSelected; - } - return p; - }), - }; - case MediaActionTypes.SELECT_ALL_MEDIA_FOR_JOB: - return { - ...state, - [action.payload.jobid]: state[action.payload.jobid].map((p) => { - p.isSelected = true; + switch (action.type) { + case MediaActionTypes.SET_MEDIA_FOR_JOB: + return { ...state, [action.payload.jobid]: action.payload.media }; + case MediaActionTypes.GET_MEDIA_FOR_JOB_ERROR: + return { ...state, error: action.payload }; + case MediaActionTypes.ADD_MEDIA_FOR_JOB: + return { + ...state, + [action.payload.jobid]: [ + ...(state[action.payload.jobid] ? state[action.payload.jobid] : []), + ...(action.payload.media || []) + ] + }; + case MediaActionTypes.TOGGLE_MEDIA_SELECTED: + return { + ...state, + [action.payload.jobid]: state[action.payload.jobid].map((p) => { + if (p.filename === action.payload.filename) { + p.isSelected = !p.isSelected; + } + return p; + }) + }; + case MediaActionTypes.SELECT_ALL_MEDIA_FOR_JOB: + return { + ...state, + [action.payload.jobid]: state[action.payload.jobid].map((p) => { + p.isSelected = true; - return p; - }), - }; - case MediaActionTypes.DESELECT_ALL_MEDIA_FOR_JOB: - return { - ...state, - [action.payload.jobid]: state[action.payload.jobid].map((p) => { - p.isSelected = false; - return p; - }), - }; - default: - return state; - } + return p; + }) + }; + case MediaActionTypes.DESELECT_ALL_MEDIA_FOR_JOB: + return { + ...state, + [action.payload.jobid]: state[action.payload.jobid].map((p) => { + p.isSelected = false; + return p; + }) + }; + default: + return state; + } }; export default mediaReducer; diff --git a/client/src/redux/media/media.sagas.js b/client/src/redux/media/media.sagas.js index 25a97fa85..a85109776 100644 --- a/client/src/redux/media/media.sagas.js +++ b/client/src/redux/media/media.sagas.js @@ -1,123 +1,113 @@ -import {all, call, put, select, takeLatest} from "redux-saga/effects"; -import {getJobMediaError, setJobMedia} from "./media.actions"; +import { all, call, put, select, takeLatest } from "redux-saga/effects"; +import { getJobMediaError, setJobMedia } from "./media.actions"; import MediaActionTypes from "./media.types"; import cleanAxios from "../../utils/CleanAxios"; import normalizeUrl from "normalize-url"; export function* onSetJobMedia() { - yield takeLatest(MediaActionTypes.GET_MEDIA_FOR_JOB, getJobMedia); + yield takeLatest(MediaActionTypes.GET_MEDIA_FOR_JOB, getJobMedia); } -export function* getJobMedia({payload: jobid}) { - try { - const bodyshop = yield select((state) => state.user.bodyshop); - const localmediaserverhttp = bodyshop.localmediaserverhttp.trim(); +export function* getJobMedia({ payload: jobid }) { + try { + const bodyshop = yield select((state) => state.user.bodyshop); + const localmediaserverhttp = bodyshop.localmediaserverhttp.trim(); - if (localmediaserverhttp && localmediaserverhttp !== "") { - const imagesFetch = yield cleanAxios.post( - `${localmediaserverhttp}/jobs/list`, - { - jobid, - }, - {headers: {ims_token: bodyshop.localmediatoken}} - ); - const documentsFetch = yield cleanAxios.post( - `${localmediaserverhttp}/bills/list`, - { - jobid, - }, - {headers: {ims_token: bodyshop.localmediatoken}} - ); + if (localmediaserverhttp && localmediaserverhttp !== "") { + const imagesFetch = yield cleanAxios.post( + `${localmediaserverhttp}/jobs/list`, + { + jobid + }, + { headers: { ims_token: bodyshop.localmediatoken } } + ); + const documentsFetch = yield cleanAxios.post( + `${localmediaserverhttp}/bills/list`, + { + jobid + }, + { headers: { ims_token: bodyshop.localmediatoken } } + ); - yield put( - setJobMedia({ - jobid, - media: [ - ...imagesFetch.data.map((d, idx) => { - return { - ...d, - src: normalizeUrl(`${localmediaserverhttp}/${d.src}`), - thumbnail: normalizeUrl( - `${localmediaserverhttp}/${d.thumbnail}` - ), - ...(d.optimized && { - optimized: normalizeUrl( - `${localmediaserverhttp}/${d.optimized}` - ), - }), - isSelected: false, - key: idx, - }; - }), - ...documentsFetch.data.map((d, idx) => { - return { - ...d, - src: normalizeUrl(`${localmediaserverhttp}/${d.src}`), - thumbnail: normalizeUrl( - `${localmediaserverhttp}/${d.thumbnail}` - ), + yield put( + setJobMedia({ + jobid, + media: [ + ...imagesFetch.data.map((d, idx) => { + return { + ...d, + src: normalizeUrl(`${localmediaserverhttp}/${d.src}`), + thumbnail: normalizeUrl(`${localmediaserverhttp}/${d.thumbnail}`), + ...(d.optimized && { + optimized: normalizeUrl(`${localmediaserverhttp}/${d.optimized}`) + }), + isSelected: false, + key: idx + }; + }), + ...documentsFetch.data.map((d, idx) => { + return { + ...d, + src: normalizeUrl(`${localmediaserverhttp}/${d.src}`), + thumbnail: normalizeUrl(`${localmediaserverhttp}/${d.thumbnail}`), - isSelected: false, - key: idx, - }; - }), - ], - }) - ); - } - } catch (error) { - yield put(getJobMediaError(error)); + isSelected: false, + key: idx + }; + }) + ] + }) + ); } + } catch (error) { + yield put(getJobMediaError(error)); + } } export function* onSetBillMedia() { - yield takeLatest(MediaActionTypes.GET_MEDIA_FOR_BILL, getBillMedia); + yield takeLatest(MediaActionTypes.GET_MEDIA_FOR_BILL, getBillMedia); } -export function* getBillMedia({payload: {jobid, invoice_number}}) { - try { - const bodyshop = yield select((state) => state.user.bodyshop); - const localmediaserverhttp = bodyshop.localmediaserverhttp.trim(); +export function* getBillMedia({ payload: { jobid, invoice_number } }) { + try { + const bodyshop = yield select((state) => state.user.bodyshop); + const localmediaserverhttp = bodyshop.localmediaserverhttp.trim(); - if (localmediaserverhttp && localmediaserverhttp !== "") { - const documentsFetch = yield cleanAxios.post( - `${localmediaserverhttp}/bills/list`, - { - jobid, - invoice_number, - }, - {headers: {ims_token: bodyshop.localmediatoken}} - ); + if (localmediaserverhttp && localmediaserverhttp !== "") { + const documentsFetch = yield cleanAxios.post( + `${localmediaserverhttp}/bills/list`, + { + jobid, + invoice_number + }, + { headers: { ims_token: bodyshop.localmediatoken } } + ); - yield put( - setJobMedia({ - jobid, - media: [ - ...documentsFetch.data.map((d, idx) => { - return { - ...d, - src: normalizeUrl(`${localmediaserverhttp}/${d.src}`), - thumbnail: normalizeUrl( - `${localmediaserverhttp}/${d.thumbnail}` - ), - ...(d.optimized && { - optimized: normalizeUrl( - `${localmediaserverhttp}/${d.optimized}` - ), - }), - isSelected: false, - key: idx, - }; - }), - ], - }) - ); - } - } catch (error) { - yield put(getJobMediaError(error)); + yield put( + setJobMedia({ + jobid, + media: [ + ...documentsFetch.data.map((d, idx) => { + return { + ...d, + src: normalizeUrl(`${localmediaserverhttp}/${d.src}`), + thumbnail: normalizeUrl(`${localmediaserverhttp}/${d.thumbnail}`), + ...(d.optimized && { + optimized: normalizeUrl(`${localmediaserverhttp}/${d.optimized}`) + }), + isSelected: false, + key: idx + }; + }) + ] + }) + ); } + } catch (error) { + yield put(getJobMediaError(error)); + } } export function* mediaSagas() { - yield all([call(onSetJobMedia), call(onSetBillMedia)]); + yield all([call(onSetJobMedia), call(onSetBillMedia)]); } diff --git a/client/src/redux/media/media.selectors.js b/client/src/redux/media/media.selectors.js index 7cffb3c9e..e5c930e5c 100644 --- a/client/src/redux/media/media.selectors.js +++ b/client/src/redux/media/media.selectors.js @@ -1,4 +1,4 @@ -import {createSelector} from "reselect"; +import { createSelector } from "reselect"; const selectMedia = (state) => state.media; diff --git a/client/src/redux/media/media.types.js b/client/src/redux/media/media.types.js index 80aee516b..fff465c78 100644 --- a/client/src/redux/media/media.types.js +++ b/client/src/redux/media/media.types.js @@ -1,11 +1,11 @@ const MediaActionTypes = { - SET_MEDIA_FOR_JOB: "SET_MEDIA_FOR_JOB", - GET_MEDIA_FOR_JOB: "GET_MEDIA_FOR_JOB", - GET_MEDIA_FOR_JOB_ERROR: "GET_MEDIA_FOR_JOB_ERROR", - ADD_MEDIA_FOR_JOB: "ADD_MEDIA_FOR_JOB", - TOGGLE_MEDIA_SELECTED: "TOGGLE_MEDIA_SELECTED", - GET_MEDIA_FOR_BILL: "GET_MEDIA_FOR_BILL", - SELECT_ALL_MEDIA_FOR_JOB: "SELECT_ALL_MEDIA_FOR_JOB", - DESELECT_ALL_MEDIA_FOR_JOB: "DESELECT_ALL_MEDIA_FOR_JOB", + SET_MEDIA_FOR_JOB: "SET_MEDIA_FOR_JOB", + GET_MEDIA_FOR_JOB: "GET_MEDIA_FOR_JOB", + GET_MEDIA_FOR_JOB_ERROR: "GET_MEDIA_FOR_JOB_ERROR", + ADD_MEDIA_FOR_JOB: "ADD_MEDIA_FOR_JOB", + TOGGLE_MEDIA_SELECTED: "TOGGLE_MEDIA_SELECTED", + GET_MEDIA_FOR_BILL: "GET_MEDIA_FOR_BILL", + SELECT_ALL_MEDIA_FOR_JOB: "SELECT_ALL_MEDIA_FOR_JOB", + DESELECT_ALL_MEDIA_FOR_JOB: "DESELECT_ALL_MEDIA_FOR_JOB" }; export default MediaActionTypes; diff --git a/client/src/redux/messaging/messaging.actions.js b/client/src/redux/messaging/messaging.actions.js index 6d8b2a773..194773d90 100644 --- a/client/src/redux/messaging/messaging.actions.js +++ b/client/src/redux/messaging/messaging.actions.js @@ -1,35 +1,35 @@ import MessagingActionTypes from "./messaging.types"; export const toggleChatVisible = () => ({ - type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE, - //payload: user + type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE + //payload: user }); export const sendMessage = (message) => ({ - type: MessagingActionTypes.SEND_MESSAGE, - payload: message, + type: MessagingActionTypes.SEND_MESSAGE, + payload: message }); export const sendMessageSuccess = (message) => ({ - type: MessagingActionTypes.SEND_MESSAGE_SUCCESS, - payload: message, + type: MessagingActionTypes.SEND_MESSAGE_SUCCESS, + payload: message }); export const sendMessageFailure = (error) => ({ - type: MessagingActionTypes.SEND_MESSAGE_FAILURE, - payload: error, + type: MessagingActionTypes.SEND_MESSAGE_FAILURE, + payload: error }); export const setSelectedConversation = (conversationId) => ({ - type: MessagingActionTypes.SET_SELECTED_CONVERSATION, - payload: conversationId, + type: MessagingActionTypes.SET_SELECTED_CONVERSATION, + payload: conversationId }); export const openChatByPhone = (phoneNumber) => ({ - type: MessagingActionTypes.OPEN_CHAT_BY_PHONE, - payload: phoneNumber, + type: MessagingActionTypes.OPEN_CHAT_BY_PHONE, + payload: phoneNumber }); export const setMessage = (message) => ({ - type: MessagingActionTypes.SET_MESSAGE, - payload: message, -}); \ No newline at end of file + type: MessagingActionTypes.SET_MESSAGE, + payload: message +}); diff --git a/client/src/redux/messaging/messaging.reducer.js b/client/src/redux/messaging/messaging.reducer.js index a4f39ec37..38116aaf2 100644 --- a/client/src/redux/messaging/messaging.reducer.js +++ b/client/src/redux/messaging/messaging.reducer.js @@ -1,55 +1,55 @@ import MessagingActionTypes from "./messaging.types"; const INITIAL_STATE = { - open: false, - selectedConversationId: null, - isSending: false, - error: null, - message: null, - searchingForConversation: false, + open: false, + selectedConversationId: null, + isSending: false, + error: null, + message: null, + searchingForConversation: false }; const messagingReducer = (state = INITIAL_STATE, action) => { - switch (action.type) { - case MessagingActionTypes.SET_MESSAGE: - return {...state, message: action.payload}; - case MessagingActionTypes.TOGGLE_CHAT_VISIBLE: - return { - ...state, - open: !state.open, - }; - case MessagingActionTypes.OPEN_CHAT_BY_PHONE: - return { - ...state, - searchingForConversation: true, - }; - case MessagingActionTypes.SET_SELECTED_CONVERSATION: - return { - ...state, - open: true, - searchingForConversation: false, - selectedConversationId: action.payload, - }; - case MessagingActionTypes.SEND_MESSAGE: - return { - ...state, - error: null, - isSending: true, - }; - case MessagingActionTypes.SEND_MESSAGE_SUCCESS: - return { - ...state, - message: "", - isSending: false, - }; - case MessagingActionTypes.SEND_MESSAGE_FAILURE: - return { - ...state, - error: action.payload, - }; - default: - return state; - } + switch (action.type) { + case MessagingActionTypes.SET_MESSAGE: + return { ...state, message: action.payload }; + case MessagingActionTypes.TOGGLE_CHAT_VISIBLE: + return { + ...state, + open: !state.open + }; + case MessagingActionTypes.OPEN_CHAT_BY_PHONE: + return { + ...state, + searchingForConversation: true + }; + case MessagingActionTypes.SET_SELECTED_CONVERSATION: + return { + ...state, + open: true, + searchingForConversation: false, + selectedConversationId: action.payload + }; + case MessagingActionTypes.SEND_MESSAGE: + return { + ...state, + error: null, + isSending: true + }; + case MessagingActionTypes.SEND_MESSAGE_SUCCESS: + return { + ...state, + message: "", + isSending: false + }; + case MessagingActionTypes.SEND_MESSAGE_FAILURE: + return { + ...state, + error: action.payload + }; + default: + return state; + } }; export default messagingReducer; diff --git a/client/src/redux/messaging/messaging.sagas.js b/client/src/redux/messaging/messaging.sagas.js index 69e4f2679..16a1fd274 100644 --- a/client/src/redux/messaging/messaging.sagas.js +++ b/client/src/redux/messaging/messaging.sagas.js @@ -1,109 +1,102 @@ 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 {INSERT_CONVERSATION_TAG} from "../../graphql/job-conversations.queries"; +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 { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries"; import client from "../../utils/GraphQLClient"; -import {selectBodyshop} from "../user/user.selectors"; -import {sendMessageFailure, sendMessageSuccess, setSelectedConversation,} from "./messaging.actions"; +import { selectBodyshop } from "../user/user.selectors"; +import { sendMessageFailure, sendMessageSuccess, setSelectedConversation } from "./messaging.actions"; import MessagingActionTypes from "./messaging.types"; export function* onToggleChatVisible() { - yield takeLatest(MessagingActionTypes.TOGGLE_CHAT_VISIBLE, toggleChatLogging); + yield takeLatest(MessagingActionTypes.TOGGLE_CHAT_VISIBLE, toggleChatLogging); } export function* toggleChatLogging() { - try { - yield logImEXEvent("messaging_toggle_popup"); - } catch (error) { - console.log("Error in sendMessage saga.", error); - } + try { + yield logImEXEvent("messaging_toggle_popup"); + } catch (error) { + console.log("Error in sendMessage saga.", error); + } } export function* onOpenChatByPhone() { - yield takeLatest(MessagingActionTypes.OPEN_CHAT_BY_PHONE, openChatByPhone); + yield takeLatest(MessagingActionTypes.OPEN_CHAT_BY_PHONE, openChatByPhone); } -export function* openChatByPhone({payload}) { - logImEXEvent("messaging_open_by_phone"); - const {phone_num, jobid} = payload; +export function* openChatByPhone({ payload }) { + logImEXEvent("messaging_open_by_phone"); + const { 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}, - }); + 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 } + }); - if (conversations.length === 0) { - const { - data: { - insert_conversations: {returning: newConversationsId}, - }, - } = yield client.mutate({ - mutation: CREATE_CONVERSATION, - variables: { - conversation: [ - { - phone_num: p.number, - bodyshopid: bodyshop.id, - job_conversations: jobid ? {data: {jobid: jobid}} : null, - }, - ], - }, - }); - 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) - ) - yield client.mutate({ - mutation: INSERT_CONVERSATION_TAG, - variables: { - conversationId: conversations[0].id, - jobId: jobid, - }, - }); - } else { - console.log("ERROR: Multiple conversations found. "); - yield put(setSelectedConversation(null)); + if (conversations.length === 0) { + const { + data: { + insert_conversations: { returning: newConversationsId } } - } catch (error) { - console.log("Error in sendMessage saga.", error); + } = yield client.mutate({ + mutation: CREATE_CONVERSATION, + variables: { + conversation: [ + { + phone_num: p.number, + bodyshopid: bodyshop.id, + job_conversations: jobid ? { data: { jobid: jobid } } : null + } + ] + } + }); + 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)) + yield client.mutate({ + mutation: INSERT_CONVERSATION_TAG, + variables: { + conversationId: conversations[0].id, + jobId: jobid + } + }); + } else { + console.log("ERROR: Multiple conversations found. "); + yield put(setSelectedConversation(null)); } + } catch (error) { + console.log("Error in sendMessage saga.", error); + } } export function* onSendMessage() { - yield takeLatest(MessagingActionTypes.SEND_MESSAGE, sendMessage); + yield takeLatest(MessagingActionTypes.SEND_MESSAGE, sendMessage); } -export function* sendMessage({payload}) { - try { - const response = yield call(axios.post, "/sms/send", payload); - if (response.status === 200) { - yield put(sendMessageSuccess(payload)); - } else { - yield put(sendMessageFailure(response.data)); - } - } catch (error) { - console.log("Error in sendMessage saga.", error); - yield put(sendMessageFailure(error)); +export function* sendMessage({ payload }) { + try { + const response = yield call(axios.post, "/sms/send", payload); + if (response.status === 200) { + yield put(sendMessageSuccess(payload)); + } else { + yield put(sendMessageFailure(response.data)); } + } catch (error) { + console.log("Error in sendMessage saga.", error); + yield put(sendMessageFailure(error)); + } } export function* messagingSagas() { - yield all([ - call(onSendMessage), - call(onOpenChatByPhone), - call(onToggleChatVisible), - ]); + yield all([call(onSendMessage), call(onOpenChatByPhone), call(onToggleChatVisible)]); } diff --git a/client/src/redux/messaging/messaging.selectors.js b/client/src/redux/messaging/messaging.selectors.js index 22b2e6004..982d82ecf 100644 --- a/client/src/redux/messaging/messaging.selectors.js +++ b/client/src/redux/messaging/messaging.selectors.js @@ -1,33 +1,21 @@ -import {createSelector} from "reselect"; +import { createSelector } from "reselect"; const selectMessaging = (state) => state.messaging; -export const selectChatVisible = createSelector( - [selectMessaging], - (messaging) => messaging.open -); +export const selectChatVisible = createSelector([selectMessaging], (messaging) => messaging.open); -export const selectIsSending = createSelector( - [selectMessaging], - (messaging) => messaging.isSending -); +export const selectIsSending = createSelector([selectMessaging], (messaging) => messaging.isSending); -export const selectError = createSelector( - [selectMessaging], - (messaging) => messaging.error -); +export const selectError = createSelector([selectMessaging], (messaging) => messaging.error); export const selectSelectedConversation = createSelector( - [selectMessaging], - (messaging) => messaging.selectedConversationId + [selectMessaging], + (messaging) => messaging.selectedConversationId ); -export const selectMessage = createSelector( - [selectMessaging], - (messaging) => messaging.message -); +export const selectMessage = createSelector([selectMessaging], (messaging) => messaging.message); export const searchingForConversation = createSelector( - [selectMessaging], - (messaging) => messaging.searchingForConversation + [selectMessaging], + (messaging) => messaging.searchingForConversation ); diff --git a/client/src/redux/messaging/messaging.types.js b/client/src/redux/messaging/messaging.types.js index 29876ecf2..6cca88dff 100644 --- a/client/src/redux/messaging/messaging.types.js +++ b/client/src/redux/messaging/messaging.types.js @@ -1,10 +1,10 @@ const MessagingActionTypes = { - TOGGLE_CHAT_VISIBLE: "TOGGLE_CHAT_VISIBLE", - SEND_MESSAGE: "SEND_MESSAGE", - SEND_MESSAGE_SUCCESS: "SEND_MESSAGE_SUCCESS", - SEND_MESSAGE_FAILURE: "SEND_MESSAGE_FAILURE", - SET_SELECTED_CONVERSATION: "SET_SELECTED_CONVERSATION", - OPEN_CHAT_BY_PHONE: "OPEN_CHAT_BY_PHONE", - SET_MESSAGE: "SET_MESSAGE", + TOGGLE_CHAT_VISIBLE: "TOGGLE_CHAT_VISIBLE", + SEND_MESSAGE: "SEND_MESSAGE", + SEND_MESSAGE_SUCCESS: "SEND_MESSAGE_SUCCESS", + SEND_MESSAGE_FAILURE: "SEND_MESSAGE_FAILURE", + SET_SELECTED_CONVERSATION: "SET_SELECTED_CONVERSATION", + OPEN_CHAT_BY_PHONE: "OPEN_CHAT_BY_PHONE", + SET_MESSAGE: "SET_MESSAGE" }; export default MessagingActionTypes; diff --git a/client/src/redux/modals/modals.actions.js b/client/src/redux/modals/modals.actions.js index 761edf107..241e0bb55 100644 --- a/client/src/redux/modals/modals.actions.js +++ b/client/src/redux/modals/modals.actions.js @@ -1,12 +1,12 @@ import ModalsActionTypes from "./modals.types"; -export const toggleModalVisible = modalName => ({ - type: ModalsActionTypes.TOGGLE_MODAL_VISIBLE, - payload: modalName +export const toggleModalVisible = (modalName) => ({ + type: ModalsActionTypes.TOGGLE_MODAL_VISIBLE, + payload: modalName }); //Modal Context: {context (context object), modal(name of modal)} -export const setModalContext = modalContext => ({ - type: ModalsActionTypes.SET_MODAL_CONTEXT, - payload: modalContext +export const setModalContext = (modalContext) => ({ + type: ModalsActionTypes.SET_MODAL_CONTEXT, + payload: modalContext }); diff --git a/client/src/redux/modals/modals.reducer.js b/client/src/redux/modals/modals.reducer.js index 5cd7e5763..2265649df 100644 --- a/client/src/redux/modals/modals.reducer.js +++ b/client/src/redux/modals/modals.reducer.js @@ -1,56 +1,56 @@ import ModalsActionTypes from "./modals.types"; const baseModal = { - open: false, - context: {}, - actions: { - refetch: null, - }, + open: false, + context: {}, + actions: { + refetch: null + } }; const INITIAL_STATE = { - jobLineEdit: {...baseModal}, - billEnter: {...baseModal}, - courtesyCarReturn: {...baseModal}, - noteUpsert: {...baseModal}, - schedule: {...baseModal}, - partsOrder: {...baseModal}, - timeTicket: {...baseModal}, - timeTicketTask: {...baseModal}, - printCenter: {...baseModal}, - reconciliation: {...baseModal}, - payment: {...baseModal}, - jobCosting: {...baseModal}, - reportCenter: {...baseModal}, - partsReceive: {...baseModal}, - contractFinder: {...baseModal}, - inventoryUpsert: {...baseModal}, - ca_bc_eftTableConvert: {...baseModal}, - cardPayment: {...baseModal}, + jobLineEdit: { ...baseModal }, + billEnter: { ...baseModal }, + courtesyCarReturn: { ...baseModal }, + noteUpsert: { ...baseModal }, + schedule: { ...baseModal }, + partsOrder: { ...baseModal }, + timeTicket: { ...baseModal }, + timeTicketTask: { ...baseModal }, + printCenter: { ...baseModal }, + reconciliation: { ...baseModal }, + payment: { ...baseModal }, + jobCosting: { ...baseModal }, + reportCenter: { ...baseModal }, + partsReceive: { ...baseModal }, + contractFinder: { ...baseModal }, + inventoryUpsert: { ...baseModal }, + ca_bc_eftTableConvert: { ...baseModal }, + cardPayment: { ...baseModal } }; const modalsReducer = (state = INITIAL_STATE, action) => { - switch (action.type) { - case ModalsActionTypes.TOGGLE_MODAL_VISIBLE: - return { - ...state, - [action.payload]: { - ...state[action.payload], - open: !state[action.payload].open, - }, - }; - case ModalsActionTypes.SET_MODAL_CONTEXT: - return { - ...state, - [action.payload.modal]: { - ...state[action.payload.modal], - ...action.payload.context, - open: true, - }, - }; - default: - return state; - } + switch (action.type) { + case ModalsActionTypes.TOGGLE_MODAL_VISIBLE: + return { + ...state, + [action.payload]: { + ...state[action.payload], + open: !state[action.payload].open + } + }; + case ModalsActionTypes.SET_MODAL_CONTEXT: + return { + ...state, + [action.payload.modal]: { + ...state[action.payload.modal], + ...action.payload.context, + open: true + } + }; + default: + return state; + } }; export default modalsReducer; diff --git a/client/src/redux/modals/modals.sagas.js b/client/src/redux/modals/modals.sagas.js index 2e59be473..3cd4f8c6b 100644 --- a/client/src/redux/modals/modals.sagas.js +++ b/client/src/redux/modals/modals.sagas.js @@ -1,7 +1,7 @@ -import {all} from "redux-saga/effects"; +import { all } from "redux-saga/effects"; export function* modalsSagas() { - yield all([ - //call(onSendEmail), - ]); + yield all([ + //call(onSendEmail), + ]); } diff --git a/client/src/redux/modals/modals.selectors.js b/client/src/redux/modals/modals.selectors.js index a2f7d5ade..2d1010547 100644 --- a/client/src/redux/modals/modals.selectors.js +++ b/client/src/redux/modals/modals.selectors.js @@ -1,90 +1,36 @@ -import {createSelector} from "reselect"; +import { createSelector } from "reselect"; const selectModals = (state) => state.modals; -export const selectJobLineEditModal = createSelector( - [selectModals], - (modals) => modals.jobLineEdit -); +export const selectJobLineEditModal = createSelector([selectModals], (modals) => modals.jobLineEdit); -export const selectBillEnterModal = createSelector( - [selectModals], - (modals) => modals.billEnter -); +export const selectBillEnterModal = createSelector([selectModals], (modals) => modals.billEnter); -export const selectCourtesyCarReturn = createSelector( - [selectModals], - (modals) => modals.courtesyCarReturn -); +export const selectCourtesyCarReturn = createSelector([selectModals], (modals) => modals.courtesyCarReturn); -export const selectNoteUpsert = createSelector( - [selectModals], - (modals) => modals.noteUpsert -); +export const selectNoteUpsert = createSelector([selectModals], (modals) => modals.noteUpsert); -export const selectSchedule = createSelector( - [selectModals], - (modals) => modals.schedule -); +export const selectSchedule = createSelector([selectModals], (modals) => modals.schedule); -export const selectPartsOrder = createSelector( - [selectModals], - (modals) => modals.partsOrder -); +export const selectPartsOrder = createSelector([selectModals], (modals) => modals.partsOrder); -export const selectTimeTicket = createSelector( - [selectModals], - (modals) => modals.timeTicket -); -export const selectTimeTicketTasks = createSelector( - [selectModals], - (modals) => modals.timeTicketTask -); +export const selectTimeTicket = createSelector([selectModals], (modals) => modals.timeTicket); +export const selectTimeTicketTasks = createSelector([selectModals], (modals) => modals.timeTicketTask); -export const selectPrintCenter = createSelector( - [selectModals], - (modals) => modals.printCenter -); +export const selectPrintCenter = createSelector([selectModals], (modals) => modals.printCenter); -export const selectReconciliation = createSelector( - [selectModals], - (modals) => modals.reconciliation -); -export const selectPayment = createSelector( - [selectModals], - (modals) => modals.payment -); +export const selectReconciliation = createSelector([selectModals], (modals) => modals.reconciliation); +export const selectPayment = createSelector([selectModals], (modals) => modals.payment); -export const selectJobCosting = createSelector( - [selectModals], - (modals) => modals.jobCosting -); +export const selectJobCosting = createSelector([selectModals], (modals) => modals.jobCosting); -export const selectReportCenter = createSelector( - [selectModals], - (modals) => modals.reportCenter -); +export const selectReportCenter = createSelector([selectModals], (modals) => modals.reportCenter); -export const selectPartsReceive = createSelector( - [selectModals], - (modals) => modals.partsReceive -); +export const selectPartsReceive = createSelector([selectModals], (modals) => modals.partsReceive); -export const selectContractFinder = createSelector( - [selectModals], - (modals) => modals.contractFinder -); -export const selectInventoryUpsert = createSelector( - [selectModals], - (modals) => modals.inventoryUpsert -); +export const selectContractFinder = createSelector([selectModals], (modals) => modals.contractFinder); +export const selectInventoryUpsert = createSelector([selectModals], (modals) => modals.inventoryUpsert); -export const selectCaBcEtfTableConvert = createSelector( - [selectModals], - (modals) => modals.ca_bc_eftTableConvert -); +export const selectCaBcEtfTableConvert = createSelector([selectModals], (modals) => modals.ca_bc_eftTableConvert); -export const selectCardPayment = createSelector( - [selectModals], - (modals) => modals.cardPayment -); +export const selectCardPayment = createSelector([selectModals], (modals) => modals.cardPayment); diff --git a/client/src/redux/modals/modals.types.js b/client/src/redux/modals/modals.types.js index 911f4c581..ebe65e822 100644 --- a/client/src/redux/modals/modals.types.js +++ b/client/src/redux/modals/modals.types.js @@ -1,5 +1,5 @@ const ModalActionTypes = { - TOGGLE_MODAL_VISIBLE: "TOGGLE_MODAL_VISIBLE", - SET_MODAL_CONTEXT: "SET_MODAL_CONTEXT" + TOGGLE_MODAL_VISIBLE: "TOGGLE_MODAL_VISIBLE", + SET_MODAL_CONTEXT: "SET_MODAL_CONTEXT" }; export default ModalActionTypes; diff --git a/client/src/redux/root.reducer.js b/client/src/redux/root.reducer.js index 843de3edf..48113382b 100644 --- a/client/src/redux/root.reducer.js +++ b/client/src/redux/root.reducer.js @@ -1,7 +1,7 @@ -import {combineReducers} from "redux"; -import {persistReducer} from "redux-persist"; +import { combineReducers } from "redux"; +import { persistReducer } from "redux-persist"; import storage from "redux-persist/lib/storage"; -import {withReduxStateSync} from "redux-state-sync"; +import { withReduxStateSync } from "redux-state-sync"; import applicationReducer from "./application/application.reducer"; import emailReducer from "./email/email.reducer"; import mediaReducer from "./media/media.reducer"; @@ -18,23 +18,23 @@ import userReducer from "./user/user.reducer"; // }; const applicationPersistConfig = { - key: "v", - storage: storage, - whitelist: ["recentItems"], + key: "v", + storage: storage, + whitelist: ["recentItems"] }; const rootReducer = combineReducers({ - user: userReducer, - messaging: messagingReducer, - email: emailReducer, - modals: modalsReducer, - application: persistReducer(applicationPersistConfig, applicationReducer), - tech: techReducer, - media: mediaReducer, + user: userReducer, + messaging: messagingReducer, + email: emailReducer, + modals: modalsReducer, + application: persistReducer(applicationPersistConfig, applicationReducer), + tech: techReducer, + media: mediaReducer }); export default withReduxStateSync( - // persistReducer(persistConfig, - rootReducer - //) + // persistReducer(persistConfig, + rootReducer + //) ); diff --git a/client/src/redux/root.saga.js b/client/src/redux/root.saga.js index 0d0e66ca9..a64c6fbdd 100644 --- a/client/src/redux/root.saga.js +++ b/client/src/redux/root.saga.js @@ -1,21 +1,21 @@ -import {all, call} from "redux-saga/effects"; +import { all, call } from "redux-saga/effects"; -import {userSagas} from "./user/user.sagas"; -import {messagingSagas} from "./messaging/messaging.sagas"; -import {emailSagas} from "./email/email.sagas"; -import {modalsSagas} from "./modals/modals.sagas"; -import {applicationSagas} from "./application/application.sagas"; -import {techSagas} from "./tech/tech.sagas"; -import {mediaSagas} from "./media/media.sagas"; +import { userSagas } from "./user/user.sagas"; +import { messagingSagas } from "./messaging/messaging.sagas"; +import { emailSagas } from "./email/email.sagas"; +import { modalsSagas } from "./modals/modals.sagas"; +import { applicationSagas } from "./application/application.sagas"; +import { techSagas } from "./tech/tech.sagas"; +import { mediaSagas } from "./media/media.sagas"; export default function* rootSaga() { - yield all([ - call(userSagas), - call(messagingSagas), - call(emailSagas), - call(modalsSagas), - call(applicationSagas), - call(techSagas), - call(mediaSagas), - ]); + yield all([ + call(userSagas), + call(messagingSagas), + call(emailSagas), + call(modalsSagas), + call(applicationSagas), + call(techSagas), + call(mediaSagas) + ]); } diff --git a/client/src/redux/store.js b/client/src/redux/store.js index 3363609db..2e7ab1a0d 100644 --- a/client/src/redux/store.js +++ b/client/src/redux/store.js @@ -1,28 +1,25 @@ -import {configureStore} from '@reduxjs/toolkit'; -import {persistStore,} from "redux-persist"; -import {createLogger} from "redux-logger"; +import { configureStore } from "@reduxjs/toolkit"; +import { persistStore } from "redux-persist"; +import { createLogger } from "redux-logger"; import createSagaMiddleware from "redux-saga"; -import {createStateSyncMiddleware, initMessageListener,} from "redux-state-sync"; +import { createStateSyncMiddleware, initMessageListener } from "redux-state-sync"; import rootReducer from "./root.reducer"; import rootSaga from "./root.saga"; import * as Sentry from "@sentry/react"; const sentryReduxEnhancer = Sentry.createReduxEnhancer({ - // Optionally pass options + // Optionally pass options }); const sagaMiddleWare = createSagaMiddleware(); const reduxSyncConfig = { - whitelist: [ - "ADD_RECENT_ITEM", //"SET_SHOP_DETAILS" - ], + whitelist: [ + "ADD_RECENT_ITEM" //"SET_SHOP_DETAILS" + ] }; -const middlewares = [ - sagaMiddleWare, - createStateSyncMiddleware(reduxSyncConfig), -]; +const middlewares = [sagaMiddleWare, createStateSyncMiddleware(reduxSyncConfig)]; if (import.meta.env.DEV) { middlewares.push(createLogger({ collapsed: true, diff: true })); @@ -30,9 +27,10 @@ if (import.meta.env.DEV) { export const store = configureStore({ reducer: rootReducer, - middleware: (getDefaultMiddleware) => getDefaultMiddleware({ - serializableCheck: false, - }).concat(middlewares), + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: false + }).concat(middlewares), // middleware: middlewares, devTools: import.meta.env.DEV, enhancers: (getDefaultEnhancers) => getDefaultEnhancers().concat(sentryReduxEnhancer) @@ -42,9 +40,9 @@ sagaMiddleWare.run(rootSaga); initMessageListener(store); export const persistor = persistStore(store); -const e = {store, persistStore}; +const e = { store, persistStore }; export default e; if (window.Cypress) { - window.store = store; + window.store = store; } diff --git a/client/src/redux/tech/tech.actions.js b/client/src/redux/tech/tech.actions.js index 140dbfa23..c0500d035 100644 --- a/client/src/redux/tech/tech.actions.js +++ b/client/src/redux/tech/tech.actions.js @@ -1,20 +1,20 @@ import TechActionTypes from "./tech.types"; -export const techLoginStart = ({employeeid, pin}) => ({ - type: TechActionTypes.TECH_LOGIN_START, - payload: {employeeid, pin}, +export const techLoginStart = ({ employeeid, pin }) => ({ + type: TechActionTypes.TECH_LOGIN_START, + payload: { employeeid, pin } }); export const techLoginSuccess = (tech) => ({ - type: TechActionTypes.TECH_LOGIN_SUCCESS, - payload: tech, + type: TechActionTypes.TECH_LOGIN_SUCCESS, + payload: tech }); export const techLoginFailure = (error) => ({ - type: TechActionTypes.TECH_LOGIN_FAILURE, - payload: error, + type: TechActionTypes.TECH_LOGIN_FAILURE, + payload: error }); export const techLogout = () => ({ - type: TechActionTypes.TECH_LOGOUT, + type: TechActionTypes.TECH_LOGOUT }); diff --git a/client/src/redux/tech/tech.reducer.js b/client/src/redux/tech/tech.reducer.js index 07d14100c..d3d672be7 100644 --- a/client/src/redux/tech/tech.reducer.js +++ b/client/src/redux/tech/tech.reducer.js @@ -1,46 +1,46 @@ import TechActionTypes from "./tech.types"; const INITIAL_STATE = { - technician: null, - // technician: { - // employee_number: "101", - // first_name: "***HARDCODED", - // last_name: "IN REDUCER***", - // }, - loginLoading: false, - loginError: null, + technician: null, + // technician: { + // employee_number: "101", + // first_name: "***HARDCODED", + // last_name: "IN REDUCER***", + // }, + loginLoading: false, + loginError: null }; const applicationReducer = (state = INITIAL_STATE, action) => { - switch (action.type) { - case TechActionTypes.TECH_LOGOUT: - return { - ...state, - technician: null, - loginError: null, - }; - case TechActionTypes.TECH_LOGIN_START: - return { - ...state, - loginLoading: true, - }; - case TechActionTypes.TECH_LOGIN_SUCCESS: - return { - ...state, - technician: action.payload, - loginLoading: false, - loginError: false, - }; - case TechActionTypes.TECH_LOGIN_FAILURE: - return { - ...state, - loginError: action.payload, - loginLoading: false, - }; + switch (action.type) { + case TechActionTypes.TECH_LOGOUT: + return { + ...state, + technician: null, + loginError: null + }; + case TechActionTypes.TECH_LOGIN_START: + return { + ...state, + loginLoading: true + }; + case TechActionTypes.TECH_LOGIN_SUCCESS: + return { + ...state, + technician: action.payload, + loginLoading: false, + loginError: false + }; + case TechActionTypes.TECH_LOGIN_FAILURE: + return { + ...state, + loginError: action.payload, + loginLoading: false + }; - default: - return state; - } + default: + return state; + } }; export default applicationReducer; diff --git a/client/src/redux/tech/tech.sagas.js b/client/src/redux/tech/tech.sagas.js index 21daa84fc..90fda0fc1 100644 --- a/client/src/redux/tech/tech.sagas.js +++ b/client/src/redux/tech/tech.sagas.js @@ -1,37 +1,37 @@ import axios from "axios"; -import {all, call, put, select, takeLatest} from "redux-saga/effects"; -import {logImEXEvent} from "../../firebase/firebase.utils"; -import {selectBodyshop} from "../user/user.selectors"; -import {techLoginFailure, techLoginSuccess} from "./tech.actions"; +import { all, call, put, select, takeLatest } from "redux-saga/effects"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { selectBodyshop } from "../user/user.selectors"; +import { techLoginFailure, techLoginSuccess } from "./tech.actions"; import TechActionTypes from "./tech.types"; export function* onSignInStart() { - yield takeLatest(TechActionTypes.TECH_LOGIN_START, signInStart); + yield takeLatest(TechActionTypes.TECH_LOGIN_START, signInStart); } -export function* signInStart({payload: {employeeid, pin}}) { - try { - logImEXEvent("redux_tech_sign_in"); +export function* signInStart({ payload: { employeeid, pin } }) { + try { + logImEXEvent("redux_tech_sign_in"); - const bodyshop = yield select(selectBodyshop); - const response = yield call(axios.post, "/tech/login", { - shopid: bodyshop.id, - employeeid: employeeid, - pin: pin, - }); + const bodyshop = yield select(selectBodyshop); + const response = yield call(axios.post, "/tech/login", { + shopid: bodyshop.id, + employeeid: employeeid, + pin: pin + }); - const {valid, technician, error} = response.data; + const { valid, technician, error } = response.data; - if (valid) { - yield put(techLoginSuccess(technician)); - } else { - yield put(techLoginFailure(error)); - } - } catch (error) { - yield put(techLoginFailure(error)); + if (valid) { + yield put(techLoginSuccess(technician)); + } else { + yield put(techLoginFailure(error)); } + } catch (error) { + yield put(techLoginFailure(error)); + } } export function* techSagas() { - yield all([call(onSignInStart)]); + yield all([call(onSignInStart)]); } diff --git a/client/src/redux/tech/tech.selectors.js b/client/src/redux/tech/tech.selectors.js index 1803acbbf..ff0a61b7f 100644 --- a/client/src/redux/tech/tech.selectors.js +++ b/client/src/redux/tech/tech.selectors.js @@ -1,16 +1,7 @@ -import {createSelector} from "reselect"; +import { createSelector } from "reselect"; const selectTechReducer = (state) => state.tech; -export const selectTechnician = createSelector( - [selectTechReducer], - (application) => application.technician -); -export const selectLoginError = createSelector( - [selectTechReducer], - (application) => application.loginError -); -export const selectLoginLoading = createSelector( - [selectTechReducer], - (application) => application.loginLoading -); +export const selectTechnician = createSelector([selectTechReducer], (application) => application.technician); +export const selectLoginError = createSelector([selectTechReducer], (application) => application.loginError); +export const selectLoginLoading = createSelector([selectTechReducer], (application) => application.loginLoading); diff --git a/client/src/redux/tech/tech.types.js b/client/src/redux/tech/tech.types.js index f632b0cb3..a0ff16b2e 100644 --- a/client/src/redux/tech/tech.types.js +++ b/client/src/redux/tech/tech.types.js @@ -1,7 +1,7 @@ const TechActionTypes = { - TECH_LOGIN_START: "TECH_LOGIN_START", - TECH_LOGIN_SUCCESS: "TECH_LOGIN_SUCCESS", - TECH_LOGIN_FAILURE: "TECH_LOGIN_FAILURE", - TECH_LOGOUT: "TECH_LOGOUT", + TECH_LOGIN_START: "TECH_LOGIN_START", + TECH_LOGIN_SUCCESS: "TECH_LOGIN_SUCCESS", + TECH_LOGIN_FAILURE: "TECH_LOGIN_FAILURE", + TECH_LOGOUT: "TECH_LOGOUT" }; export default TechActionTypes; diff --git a/client/src/redux/user/user.actions.js b/client/src/redux/user/user.actions.js index 1289699f7..f5393b6e9 100644 --- a/client/src/redux/user/user.actions.js +++ b/client/src/redux/user/user.actions.js @@ -1,121 +1,120 @@ import UserActionTypes from "./user.types"; export const signInSuccess = (user) => ({ - type: UserActionTypes.SIGN_IN_SUCCESS, - payload: user, + type: UserActionTypes.SIGN_IN_SUCCESS, + payload: user }); export const signInFailure = (errorMsg) => ({ - type: UserActionTypes.SIGN_IN_FAILURE, - payload: errorMsg, + type: UserActionTypes.SIGN_IN_FAILURE, + payload: errorMsg }); export const emailSignInStart = (emailAndPassword) => ({ - type: UserActionTypes.EMAIL_SIGN_IN_START, - payload: emailAndPassword, + type: UserActionTypes.EMAIL_SIGN_IN_START, + payload: emailAndPassword }); export const checkUserSession = () => ({ - type: UserActionTypes.CHECK_USER_SESSION, + type: UserActionTypes.CHECK_USER_SESSION }); export const signOutStart = () => ({ - type: UserActionTypes.SIGN_OUT_START, + type: UserActionTypes.SIGN_OUT_START }); export const signOutSuccess = () => ({ - type: UserActionTypes.SIGN_OUT_SUCCESS, + type: UserActionTypes.SIGN_OUT_SUCCESS }); export const signOutFailure = (error) => ({ - type: UserActionTypes.SIGN_OUT_FAILURE, - payload: error, + type: UserActionTypes.SIGN_OUT_FAILURE, + payload: error }); export const unauthorizedUser = () => ({ - type: UserActionTypes.UNAUTHORIZED_USER, + type: UserActionTypes.UNAUTHORIZED_USER }); export const setUserLanguage = (language) => ({ - type: UserActionTypes.SET_USER_LANGUAGE, - payload: language, + type: UserActionTypes.SET_USER_LANGUAGE, + payload: language }); export const updateUserDetails = (userDetails) => ({ - type: UserActionTypes.UPDATE_USER_DETAILS, - payload: userDetails, + type: UserActionTypes.UPDATE_USER_DETAILS, + payload: userDetails }); export const updateUserDetailsSuccess = (userDetails) => ({ - type: UserActionTypes.UPDATE_USER_DETAILS_SUCCESS, - payload: userDetails, + type: UserActionTypes.UPDATE_USER_DETAILS_SUCCESS, + payload: userDetails }); export const setBodyshop = (bodyshop) => ({ - type: UserActionTypes.SET_SHOP_DETAILS, - payload: bodyshop, + type: UserActionTypes.SET_SHOP_DETAILS, + payload: bodyshop }); export const setInstanceId = (userInfo) => ({ - type: UserActionTypes.SET_INSTANCE_ID, - payload: userInfo, + type: UserActionTypes.SET_INSTANCE_ID, + payload: userInfo }); export const checkInstanceId = (uid) => ({ - type: UserActionTypes.CHECK_INSTANCE_ID, - payload: uid, + type: UserActionTypes.CHECK_INSTANCE_ID, + payload: uid }); export const setInstanceConflict = () => ({ - type: UserActionTypes.SET_INSTANCE_CONFLICT, + type: UserActionTypes.SET_INSTANCE_CONFLICT }); export const setLocalFingerprint = (fingerprint) => ({ - type: UserActionTypes.SET_LOCAL_FINGERPRINT, - payload: fingerprint, + type: UserActionTypes.SET_LOCAL_FINGERPRINT, + payload: fingerprint }); export const sendPasswordReset = (email) => ({ - type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START, - payload: email, + type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START, + payload: email }); export const sendPasswordResetAgain = (email) => ({ - type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN, - payload: email, + type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN, + payload: email }); export const sendPasswordResetFailure = (error) => ({ - type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_FAILURE, - payload: error, + type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_FAILURE, + payload: error }); export const sendPasswordResetSuccess = () => ({ - type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_SUCCESS, + type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_SUCCESS }); export const validatePasswordResetStart = (emailAndPin) => ({ - type: UserActionTypes.VALIDATE_PASSWORD_RESET_START, - payload: emailAndPin, + type: UserActionTypes.VALIDATE_PASSWORD_RESET_START, + payload: emailAndPin }); export const validatePasswordResetSuccess = () => ({ - type: UserActionTypes.VALIDATE_PASSWORD_RESET_SUCCESS, + type: UserActionTypes.VALIDATE_PASSWORD_RESET_SUCCESS }); export const validatePasswordResetFailure = (error) => ({ - type: UserActionTypes.VALIDATE_PASSWORD_RESET_FAILURE, - payload: error, + type: UserActionTypes.VALIDATE_PASSWORD_RESET_FAILURE, + payload: error }); export const setAuthlevel = (authlevel) => ({ - type: UserActionTypes.SET_AUTH_LEVEL, - payload: authlevel, + type: UserActionTypes.SET_AUTH_LEVEL, + payload: authlevel }); export const setCurrentEula = (eula) => ({ - type: UserActionTypes.SET_CURRENT_EULA, - payload: eula, + type: UserActionTypes.SET_CURRENT_EULA, + payload: eula }); export const acceptEula = () => ({ - type: UserActionTypes.EULA_ACCEPTED, + type: UserActionTypes.EULA_ACCEPTED }); - diff --git a/client/src/redux/user/user.reducer.js b/client/src/redux/user/user.reducer.js index aa860d612..c117bd1c9 100644 --- a/client/src/redux/user/user.reducer.js +++ b/client/src/redux/user/user.reducer.js @@ -1,129 +1,129 @@ import UserActionTypes from "./user.types"; const INITIAL_STATE = { - currentUser: { - authorized: null, - eulaIsAccepted: false, - //language: "en-US" - }, - bodyshop: null, - loginLoading: false, - fingerprint: null, + currentUser: { + authorized: null, + eulaIsAccepted: false + //language: "en-US" + }, + bodyshop: null, + loginLoading: false, + fingerprint: null, + error: null, + conflict: false, + passwordreset: { + email: null, error: null, - conflict: false, - passwordreset: { - email: null, - error: null, - success: false, - loading: false, - }, - authLevel: 0, - currentEula: null, + success: false, + loading: false + }, + authLevel: 0, + currentEula: null }; const userReducer = (state = INITIAL_STATE, action) => { - switch (action.type) { - case UserActionTypes.SET_LOCAL_FINGERPRINT: - return {...state, fingerprint: action.payload}; - case UserActionTypes.SET_INSTANCE_ID: - return {...state, conflict: false}; - case UserActionTypes.SET_INSTANCE_CONFLICT: - return {...state, conflict: true}; - case UserActionTypes.EMAIL_SIGN_IN_START: - return {...state, loginLoading: true}; - case UserActionTypes.VALIDATE_PASSWORD_RESET_START: - case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START: - return { - ...state, - passwordreset: { - email: action.payload, - error: null, - success: false, - loading: true, - }, - }; - case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN: - return { - ...state, - passwordreset: { - email: action.payload, - error: null, - success: true, - loading: true, - }, - }; - case UserActionTypes.VALIDATE_PASSWORD_RESET_FAILURE: - case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_FAILURE: - return {...state, passwordreset: {error: action.payload}}; - case UserActionTypes.VALIDATE_PASSWORD_RESET_SUCCESS: - case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_SUCCESS: - return { - ...state, - passwordreset: { - ...state.passwordreset, - success: true, - loading: false, - }, - }; - case UserActionTypes.EULA_ACCEPTED: - return { - ...state, - currentUser: {...state.currentUser, eulaIsAccepted: true}, - currentEula: null, - }; - case UserActionTypes.SIGN_IN_SUCCESS: - const {currentEula, ...currentUser} = action.payload - return { - ...state, - loginLoading: false, - currentUser: currentUser, - currentEula, - error: null, - }; - case UserActionTypes.SIGN_OUT_SUCCESS: - return { - ...state, - currentUser: {authorized: false}, - error: null, - }; - case UserActionTypes.UNAUTHORIZED_USER: - return { - ...state, - error: null, - currentUser: {authorized: false}, - }; - case UserActionTypes.SET_USER_LANGUAGE: - return { - ...state, - language: action.payload, - }; - case UserActionTypes.UPDATE_USER_DETAILS_SUCCESS: - return { - ...state, - currentUser: { - ...state.currentUser, - ...action.payload, //Spread current user details in. - }, - }; + switch (action.type) { + case UserActionTypes.SET_LOCAL_FINGERPRINT: + return { ...state, fingerprint: action.payload }; + case UserActionTypes.SET_INSTANCE_ID: + return { ...state, conflict: false }; + case UserActionTypes.SET_INSTANCE_CONFLICT: + return { ...state, conflict: true }; + case UserActionTypes.EMAIL_SIGN_IN_START: + return { ...state, loginLoading: true }; + case UserActionTypes.VALIDATE_PASSWORD_RESET_START: + case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START: + return { + ...state, + passwordreset: { + email: action.payload, + error: null, + success: false, + loading: true + } + }; + case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN: + return { + ...state, + passwordreset: { + email: action.payload, + error: null, + success: true, + loading: true + } + }; + case UserActionTypes.VALIDATE_PASSWORD_RESET_FAILURE: + case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_FAILURE: + return { ...state, passwordreset: { error: action.payload } }; + case UserActionTypes.VALIDATE_PASSWORD_RESET_SUCCESS: + case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_SUCCESS: + return { + ...state, + passwordreset: { + ...state.passwordreset, + success: true, + loading: false + } + }; + case UserActionTypes.EULA_ACCEPTED: + return { + ...state, + currentUser: { ...state.currentUser, eulaIsAccepted: true }, + currentEula: null + }; + case UserActionTypes.SIGN_IN_SUCCESS: + const { currentEula, ...currentUser } = action.payload; + return { + ...state, + loginLoading: false, + currentUser: currentUser, + currentEula, + error: null + }; + case UserActionTypes.SIGN_OUT_SUCCESS: + return { + ...state, + currentUser: { authorized: false }, + error: null + }; + case UserActionTypes.UNAUTHORIZED_USER: + return { + ...state, + error: null, + currentUser: { authorized: false } + }; + case UserActionTypes.SET_USER_LANGUAGE: + return { + ...state, + language: action.payload + }; + case UserActionTypes.UPDATE_USER_DETAILS_SUCCESS: + return { + ...state, + currentUser: { + ...state.currentUser, + ...action.payload //Spread current user details in. + } + }; - case UserActionTypes.SET_SHOP_DETAILS: - return { - ...state, - bodyshop: action.payload, - }; - case UserActionTypes.SIGN_IN_FAILURE: - case UserActionTypes.SIGN_OUT_FAILURE: - case UserActionTypes.EMAIL_SIGN_UP_FAILURE: - return { - ...state, - loginLoading: false, - error: action.payload, - }; - case UserActionTypes.SET_AUTH_LEVEL: - return {...state, authLevel: action.payload}; - default: - return state; - } + case UserActionTypes.SET_SHOP_DETAILS: + return { + ...state, + bodyshop: action.payload + }; + case UserActionTypes.SIGN_IN_FAILURE: + case UserActionTypes.SIGN_OUT_FAILURE: + case UserActionTypes.EMAIL_SIGN_UP_FAILURE: + return { + ...state, + loginLoading: false, + error: action.payload + }; + case UserActionTypes.SET_AUTH_LEVEL: + return { ...state, authLevel: action.payload }; + default: + return state; + } }; export default userReducer; diff --git a/client/src/redux/user/user.sagas.js b/client/src/redux/user/user.sagas.js index 6d6921d0d..0c5aae651 100644 --- a/client/src/redux/user/user.sagas.js +++ b/client/src/redux/user/user.sagas.js @@ -1,50 +1,50 @@ import FingerprintJS from "@fingerprintjs/fingerprintjs"; import * as Sentry from "@sentry/browser"; -import {notification} from "antd"; +import { notification } from "antd"; import axios from "axios"; -import {setUserId, setUserProperties} from "firebase/analytics"; +import { setUserId, setUserProperties } from "firebase/analytics"; import { - checkActionCode, - confirmPasswordReset, - sendPasswordResetEmail, - signInWithEmailAndPassword, - signOut, + checkActionCode, + confirmPasswordReset, + sendPasswordResetEmail, + signInWithEmailAndPassword, + signOut } from "firebase/auth"; -import {doc, getDoc, setDoc} from "firebase/firestore"; -import {getToken} from "firebase/messaging"; +import { doc, getDoc, setDoc } from "firebase/firestore"; +import { getToken } from "firebase/messaging"; import i18next from "i18next"; import LogRocket from "logrocket"; -import {all, call, delay, put, select, takeLatest} from "redux-saga/effects"; -import {factory} from "../../App/App.container"; +import { all, call, delay, put, select, takeLatest } from "redux-saga/effects"; +import { factory } from "../../App/App.container"; import { - analytics, - auth, - firestore, - getCurrentUser, - logImEXEvent, - messaging, - updateCurrentUser, + analytics, + auth, + firestore, + getCurrentUser, + logImEXEvent, + messaging, + updateCurrentUser } from "../../firebase/firebase.utils"; import { - checkInstanceId, - sendPasswordResetFailure, - sendPasswordResetSuccess, - setAuthlevel, - setInstanceConflict, - setInstanceId, - setLocalFingerprint, - signInFailure, - signInSuccess, - signOutFailure, - signOutSuccess, - unauthorizedUser, - updateUserDetailsSuccess, - validatePasswordResetFailure, - validatePasswordResetSuccess, + checkInstanceId, + sendPasswordResetFailure, + sendPasswordResetSuccess, + setAuthlevel, + setInstanceConflict, + setInstanceId, + setLocalFingerprint, + signInFailure, + signInSuccess, + signOutFailure, + signOutSuccess, + unauthorizedUser, + updateUserDetailsSuccess, + validatePasswordResetFailure, + validatePasswordResetSuccess } from "./user.actions"; import UserActionTypes from "./user.types"; import client from "../../utils/GraphQLClient"; -import {QUERY_EULA} from "../../graphql/bodyshop.queries"; +import { QUERY_EULA } from "../../graphql/bodyshop.queries"; import day from "../../utils/day"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; import { Userpilot } from "userpilot"; @@ -52,136 +52,135 @@ import { Userpilot } from "userpilot"; const fpPromise = FingerprintJS.load(); export function* onEmailSignInStart() { - yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail); + yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail); } -export function* signInWithEmail({payload: {email, password}}) { - try { - logImEXEvent("redux_sign_in_attempt", {user: email}); +export function* signInWithEmail({ payload: { email, password } }) { + try { + logImEXEvent("redux_sign_in_attempt", { user: email }); - const {user} = yield signInWithEmailAndPassword(auth, email, password); + const { user } = yield signInWithEmailAndPassword(auth, email, password); - yield put( - signInSuccess({ - uid: user.uid, - email: user.email, - displayName: user.displayName, - photoURL: user.photoURL, - authorized: true, - }) - ); - } catch (error) { - yield put(signInFailure(error)); - logImEXEvent("redux_sign_in_failure", {user: email, error}); - } + yield put( + signInSuccess({ + uid: user.uid, + email: user.email, + displayName: user.displayName, + photoURL: user.photoURL, + authorized: true + }) + ); + } catch (error) { + yield put(signInFailure(error)); + logImEXEvent("redux_sign_in_failure", { user: email, error }); + } } export function* onCheckUserSession() { - yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated); + yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated); } - export function* isUserAuthenticated() { - try { - logImEXEvent("redux_auth_check"); + try { + logImEXEvent("redux_auth_check"); - const user = yield getCurrentUser(); - if (!user) { - yield put(unauthorizedUser()); - return; - } - - LogRocket.identify(user.email); - - const eulaQuery = yield client.query({ - query: QUERY_EULA, - variables: { - now: day() - }, - }); - - const eulaIsAccepted = eulaQuery.data.eulas.length > 0 && eulaQuery.data.eulas[0].eula_acceptances.length > 0; - - yield put( - signInSuccess({ - uid: user.uid, - email: user.email, - displayName: user.displayName, - photoURL: user.photoURL, - authorized: true, - eulaIsAccepted, - currentEula: eulaIsAccepted ? null : eulaQuery.data.eulas[0], - }) - ); - } catch (error) { - yield put(signInFailure(error)); + const user = yield getCurrentUser(); + if (!user) { + yield put(unauthorizedUser()); + return; } + + LogRocket.identify(user.email); + + const eulaQuery = yield client.query({ + query: QUERY_EULA, + variables: { + now: day() + } + }); + + const eulaIsAccepted = eulaQuery.data.eulas.length > 0 && eulaQuery.data.eulas[0].eula_acceptances.length > 0; + + yield put( + signInSuccess({ + uid: user.uid, + email: user.email, + displayName: user.displayName, + photoURL: user.photoURL, + authorized: true, + eulaIsAccepted, + currentEula: eulaIsAccepted ? null : eulaQuery.data.eulas[0] + }) + ); + } catch (error) { + yield put(signInFailure(error)); + } } export function* onSignOutStart() { - yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart); + yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart); } export function* signOutStart() { + try { + logImEXEvent("redux_sign_out"); + + const state = yield select(); + + //unsub from topic. + try { - logImEXEvent("redux_sign_out"); - - const state = yield select(); - - //unsub from topic. - - try { - const fcm_tokens = yield getToken(messaging); - yield call(axios.post, "/notifications/unsubscribe", { - fcm_tokens, - imexshopid: state.user.bodyshop.imexshopid, - type: "messaging", - }); - } catch (error) { - console.log("No FCM token. Skipping unsubscribe."); - } - - yield signOut(auth); - yield put(signOutSuccess()); - localStorage.removeItem("token"); + const fcm_tokens = yield getToken(messaging); + yield call(axios.post, "/notifications/unsubscribe", { + fcm_tokens, + imexshopid: state.user.bodyshop.imexshopid, + type: "messaging" + }); } catch (error) { - yield put(signOutFailure(error.message)); + console.log("No FCM token. Skipping unsubscribe."); } + + yield signOut(auth); + yield put(signOutSuccess()); + localStorage.removeItem("token"); + } catch (error) { + yield put(signOutFailure(error.message)); + } } export function* onUpdateUserDetails() { - yield takeLatest(UserActionTypes.UPDATE_USER_DETAILS, updateUserDetails); + yield takeLatest(UserActionTypes.UPDATE_USER_DETAILS, updateUserDetails); } export function* updateUserDetails(userDetails) { - try { - const updatedDetails = yield updateCurrentUser(userDetails.payload); + try { + const updatedDetails = yield updateCurrentUser(userDetails.payload); - yield put(updateUserDetailsSuccess(updatedDetails)); - notification.open({ - type: "success", - message: i18next.t("profile.successes.updated"), - }); - } catch (error) { - //yield put(signOutFailure(error.message)); - } + yield put(updateUserDetailsSuccess(updatedDetails)); + notification.open({ + type: "success", + message: i18next.t("profile.successes.updated") + }); + } catch (error) { + //yield put(signOutFailure(error.message)); + } } export function* onSetInstanceId() { - yield takeLatest(UserActionTypes.SET_INSTANCE_ID, setInstanceIdSaga); + yield takeLatest(UserActionTypes.SET_INSTANCE_ID, setInstanceIdSaga); } -export function* setInstanceIdSaga({payload: uid}) { - try { - const userInstanceRef = doc(firestore, `userInstance/${uid}`); +export function* setInstanceIdSaga({ payload: uid }) { + try { + const userInstanceRef = doc(firestore, `userInstance/${uid}`); - // Get the visitor identifier when you need it. - const fp = yield fpPromise; - const result = yield fp.get(); - yield setDoc(userInstanceRef, { - timestamp: new Date(), - fingerprint: result.visitorId, - }); + // Get the visitor identifier when you need it. + const fp = yield fpPromise; + const result = yield fp.get(); + yield setDoc(userInstanceRef, { + timestamp: new Date(), + fingerprint: result.visitorId + }); yield put(setLocalFingerprint(result.visitorId)); yield delay(5 * 60 * 1000); @@ -192,178 +191,161 @@ export function* setInstanceIdSaga({payload: uid}) { } export function* onCheckInstanceId() { - yield takeLatest(UserActionTypes.CHECK_INSTANCE_ID, checkInstanceIdSaga); + yield takeLatest(UserActionTypes.CHECK_INSTANCE_ID, checkInstanceIdSaga); } -export function* checkInstanceIdSaga({payload: uid}) { - try { - const snapshot = yield getDoc(doc(firestore, `userInstance/${uid}`)); - let fingerprint = yield select((state) => state.user.fingerprint); - yield put(setInstanceConflict()); - if (snapshot.data().fingerprint === fingerprint) { - yield delay(5 * 60 * 1000); - yield put(checkInstanceId(uid)); - } else { - console.log("ERROR: Fingerprints do not match. Conflict detected."); - logImEXEvent("instance_confict"); - yield put(setInstanceConflict()); - } - } catch (error) { - console.log("error", error); +export function* checkInstanceIdSaga({ payload: uid }) { + try { + const snapshot = yield getDoc(doc(firestore, `userInstance/${uid}`)); + let fingerprint = yield select((state) => state.user.fingerprint); + yield put(setInstanceConflict()); + if (snapshot.data().fingerprint === fingerprint) { + yield delay(5 * 60 * 1000); + yield put(checkInstanceId(uid)); + } else { + console.log("ERROR: Fingerprints do not match. Conflict detected."); + logImEXEvent("instance_confict"); + yield put(setInstanceConflict()); } + } catch (error) { + console.log("error", error); + } } export function* onSignInSuccess() { - yield takeLatest(UserActionTypes.SIGN_IN_SUCCESS, signInSuccessSaga); + yield takeLatest(UserActionTypes.SIGN_IN_SUCCESS, signInSuccessSaga); } -export function* signInSuccessSaga({payload}) { - LogRocket.identify(payload.email); +export function* signInSuccessSaga({ payload }) { + LogRocket.identify(payload.email); + + try { + InstanceRenderManager({ + executeFunction: true, + args: [], + imex: () => { + window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]); + window.$crisp.push(["set", "session:segments", [["user"]]]); + }, + promanager: () => { + Userpilot.identify(payload.email, { + email: payload.email + }); + console.log("*** Userpilot identified."); + } + }); + } catch (error) { + console.log("Error updating Crisp settings.", error); + } + + try { + Sentry.setUser({ + email: payload.email, + username: payload.displayName || payload.email + }); + } catch (error) { + console.log("Error setting Sentry user.", error); + } + + setUserId(analytics, payload.email); + setUserProperties(analytics, payload); + yield logImEXEvent("redux_sign_in_success"); +} + +export function* onSendPasswordResetStart() { + yield takeLatest(UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START, sendPasswordResetEmailSaga); + yield takeLatest(UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN, sendPasswordResetEmailSaga); +} + +export function* sendPasswordResetEmailSaga({ payload }) { + try { + yield sendPasswordResetEmail(auth, payload, { + url: InstanceRenderManager({ + imex: "https://imex.online/passwordreset", + rome: "https://romeonline.io/passwordreset", + promanager: "https:promanager.web-est.com/passwordreset" + }) + }); + + yield put(sendPasswordResetSuccess()); + } catch (error) { + yield put(sendPasswordResetFailure(error.message)); + } +} + +export function* onValidatePasswordResetStart() { + yield takeLatest(UserActionTypes.VALIDATE_PASSWORD_RESET_START, validatePasswordResetStart); +} + +export function* validatePasswordResetStart({ payload: { password, code } }) { + try { + checkActionCode(auth, code); + yield confirmPasswordReset(auth, code, password); + yield put(validatePasswordResetSuccess()); + } catch (error) { + yield put(validatePasswordResetFailure(error.message)); + } +} + +export function* onSetShopDetails() { + yield takeLatest(UserActionTypes.SET_SHOP_DETAILS, SetAuthLevelFromShopDetails); +} + +export function* SetAuthLevelFromShopDetails({ payload }) { + try { + const userEmail = yield select((state) => state.user.currentUser.email); + try { + //console.log("Setting shop timezone."); + // dayjs.tz.setDefault(payload.timezone); + } catch (error) { + console.log(error); + } + + factory.client(payload.imexshopid); + + const authRecord = payload.associations.filter((a) => a.useremail.toLowerCase() === userEmail.toLowerCase()); + + yield put(setAuthlevel(authRecord[0] ? authRecord[0].authlevel : 0)); + yield put( + updateUserDetailsSuccess(authRecord[0] ? { validemail: authRecord[0].user.validemail } : { validemail: false }) + ); + + if (payload.features.singleDeviceOnly) { + const user = yield select((state) => state.user.currentUser); + + if (!(user.email.includes("@imex.") || user.email.includes("@rome."))) yield put(setInstanceId(user.uid)); + } try { InstanceRenderManager({ executeFunction: true, - args:[], + args: [], imex: () => { - window.$crisp.push(['set', 'user:nickname', [payload.displayName || payload.email]]); - window.$crisp.push(['set', 'session:segments', [['user']]]); - }, - promanager: () =>{ - Userpilot.identify( - payload.email, - { - email: payload.email, - } - ); - console.log("*** Userpilot identified.") + window.$crisp.push(["set", "user:company", [payload.shopname]]); + if (authRecord[0] && authRecord[0].user.validemail) { + window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]); + } } - }); } catch (error) { - console.log('Error updating Crisp settings.', error); - } - - try { - Sentry.setUser({ - email: payload.email, - username: payload.displayName || payload.email, - }); - } catch (error) { - console.log('Error setting Sentry user.', error); - } - - setUserId(analytics, payload.email); - setUserProperties(analytics, payload); - yield logImEXEvent("redux_sign_in_success"); -} - -export function* onSendPasswordResetStart() { - yield takeLatest( - UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START, - sendPasswordResetEmailSaga - ); - yield takeLatest( - UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN, - sendPasswordResetEmailSaga - ); -} - -export function* sendPasswordResetEmailSaga({payload}) { - try { - yield sendPasswordResetEmail(auth, payload, { - url: - InstanceRenderManager({imex: "https://imex.online/passwordreset", rome:"https://romeonline.io/passwordreset", promanager:"https:promanager.web-est.com/passwordreset"}) - , - }); - - yield put(sendPasswordResetSuccess()); - } catch (error) { - yield put(sendPasswordResetFailure(error.message)); - } -} - -export function* onValidatePasswordResetStart() { - yield takeLatest( - UserActionTypes.VALIDATE_PASSWORD_RESET_START, - validatePasswordResetStart - ); -} - -export function* validatePasswordResetStart({payload: {password, code}}) { - try { - checkActionCode(auth, code); - yield confirmPasswordReset(auth, code, password); - yield put(validatePasswordResetSuccess()); - } catch (error) { - yield put(validatePasswordResetFailure(error.message)); - } -} - -export function* onSetShopDetails() { - yield takeLatest( - UserActionTypes.SET_SHOP_DETAILS, - SetAuthLevelFromShopDetails - ); -} - -export function* SetAuthLevelFromShopDetails({payload}) { - try { - const userEmail = yield select((state) => state.user.currentUser.email); - try { - //console.log("Setting shop timezone."); - // dayjs.tz.setDefault(payload.timezone); - } catch (error) { - console.log(error); - } - - factory.client(payload.imexshopid); - - const authRecord = payload.associations.filter( - (a) => a.useremail.toLowerCase() === userEmail.toLowerCase() - ); - - yield put(setAuthlevel(authRecord[0] ? authRecord[0].authlevel : 0)); - yield put( - updateUserDetailsSuccess( - authRecord[0] - ? {validemail: authRecord[0].user.validemail} - : {validemail: false} - ) - ); - - if (payload.features.singleDeviceOnly) { - const user = yield select((state) => state.user.currentUser); - - if (!(user.email.includes("@imex.") || user.email.includes("@rome."))) - yield put(setInstanceId(user.uid)); - } - - try { - InstanceRenderManager({executeFunction:true,args:[], imex: () => { - window.$crisp.push(["set", "user:company", [payload.shopname]]); - if (authRecord[0] && authRecord[0].user.validemail) { - window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]); - } - }}) - } catch (error) { - console.error("Couldnt find $crisp."); - } - } catch (error) { - yield put(signInFailure(error.message)); + console.error("Couldnt find $crisp."); } + } catch (error) { + yield put(signInFailure(error.message)); + } } export function* userSagas() { - yield all([ - call(onEmailSignInStart), - call(onCheckUserSession), - call(onSignOutStart), - call(onUpdateUserDetails), - call(onSetInstanceId), - call(onCheckInstanceId), - call(onSignInSuccess), - call(onSendPasswordResetStart), - call(onValidatePasswordResetStart), - call(onSetShopDetails), - ]); + yield all([ + call(onEmailSignInStart), + call(onCheckUserSession), + call(onSignOutStart), + call(onUpdateUserDetails), + call(onSetInstanceId), + call(onCheckInstanceId), + call(onSignInSuccess), + call(onSendPasswordResetStart), + call(onValidatePasswordResetStart), + call(onSetShopDetails) + ]); } diff --git a/client/src/redux/user/user.selectors.js b/client/src/redux/user/user.selectors.js index 52f5a2ca7..a2afd47a8 100644 --- a/client/src/redux/user/user.selectors.js +++ b/client/src/redux/user/user.selectors.js @@ -1,43 +1,19 @@ -import {createSelector} from "reselect"; +import { createSelector } from "reselect"; const selectUser = (state) => state.user; -export const selectCurrentUser = createSelector( - [selectUser], - (user) => user.currentUser -); +export const selectCurrentUser = createSelector([selectUser], (user) => user.currentUser); -export const selectSignInError = createSelector( - [selectUser], - (user) => user.error -); +export const selectSignInError = createSelector([selectUser], (user) => user.error); -export const selectBodyshop = createSelector( - [selectUser], - (user) => user.bodyshop -); +export const selectBodyshop = createSelector([selectUser], (user) => user.bodyshop); -export const selectInstanceConflict = createSelector( - [selectUser], - (user) => user.conflict -); +export const selectInstanceConflict = createSelector([selectUser], (user) => user.conflict); -export const selectPasswordReset = createSelector( - [selectUser], - (user) => user.passwordreset -); +export const selectPasswordReset = createSelector([selectUser], (user) => user.passwordreset); -export const selectAuthLevel = createSelector( - [selectUser], - (user) => user.authLevel -); +export const selectAuthLevel = createSelector([selectUser], (user) => user.authLevel); -export const selectLoginLoading = createSelector( - [selectUser], - (user) => user.loginLoading -); +export const selectLoginLoading = createSelector([selectUser], (user) => user.loginLoading); -export const selectCurrentEula = createSelector( - [selectUser], - (user) => user.currentEula -); +export const selectCurrentEula = createSelector([selectUser], (user) => user.currentEula); diff --git a/client/src/redux/user/user.types.js b/client/src/redux/user/user.types.js index 42afc2ca3..59a290ee5 100644 --- a/client/src/redux/user/user.types.js +++ b/client/src/redux/user/user.types.js @@ -1,38 +1,37 @@ const UserActionTypes = { - SET_CURRENT_USER: "SET_CURRENT_USER", - GOOGLE_SIGN_IN_START: "GOOGLE_SIGN_IN_START", - SIGN_IN_SUCCESS: "SIGN_IN_SUCCESS", - SIGN_IN_FAILURE: "SIGN_IN_FAILURE", - EMAIL_SIGN_IN_START: "EMAIL_SIGN_IN_START", - CHECK_USER_SESSION: "CHECK_USER_SESSION", - SIGN_OUT_START: "SIGN_OUT_START", - SIGN_OUT_SUCCESS: "SIGN_OUT_SUCCESS", - SIGN_OUT_FAILURE: "SIGN_OUT_FAILURE", - EMAIL_SIGN_UP_START: "EMAIL_SIGN_UP_START", - EMAIL_SIGN_UP_SUCCESS: "EMAIL_SIGN_UP_SUCCESS", - EMAIL_SIGN_UP_FAILURE: "EMAIL_SIGN_UP_FAILURE", - UNAUTHORIZED_USER: "UNAUTHORIZED_USER", - SET_USER_LANGUAGE: "SET_USER_LANGUAGE", - UPDATE_USER_DETAILS: "UPDATE_USER_DETAILS", - UPDATE_USER_DETAILS_SUCCESS: "UPDATE_USER_DETAILS_SUCCESS", - SET_SHOP_DETAILS: "SET_SHOP_DETAILS", - SET_INSTANCE_ID: "SET_INSTANCE_ID", - CHECK_INSTANCE_ID: "CHECK_INSTANCE_ID", - SET_INSTANCE_CONFLICT: "SET_INSTANCE_CONFLICT", - SET_LOCAL_FINGERPRINT: "SET_LOCAL_FINGERPRINT", - SEND_PASSWORD_RESET_EMAIL_START: "SEND_PASSWORD_RESET_EMAIL_START", - SEND_PASSWORD_RESET_EMAIL_START_AGAIN: - "SEND_PASSWORD_RESET_EMAIL_START_AGAIN", - SEND_PASSWORD_RESET_EMAIL_FAILURE: "SEND_PASSWORD_RESET_EMAIL_FAILURE", - SEND_PASSWORD_RESET_EMAIL_SUCCESS: "SEND_PASSWORD_RESET_EMAIL_SUCCESS", - VALIDATE_PASSWORD_RESET_START: "VALIDATE_PASSWORD_RESET_START", - VALIDATE_PASSWORD_RESET_SUCCESS: "VALIDATE_PASSWORD_RESET_SUCCESS", - VALIDATE_PASSWORD_RESET_FAILURE: "VALIDATE_PASSWORD_RESET_FAILURE", - SET_AUTH_LEVEL: "SET_AUTH_LEVEL", - CHECK_ACTION_CODE_START: "CHECK_ACTION_CODE_START", - CHECK_ACTION_CODE_SUCCESS: "CHECK_ACTION_CODE_SUCCESS", - CHECK_ACTION_CODE_FAILURE: "CHECK_ACTION_CODE_FAILURE", - SET_CURRENT_EULA: "SET_CURRENT_EULA", - EULA_ACCEPTED: "EULA_ACCEPTED", + SET_CURRENT_USER: "SET_CURRENT_USER", + GOOGLE_SIGN_IN_START: "GOOGLE_SIGN_IN_START", + SIGN_IN_SUCCESS: "SIGN_IN_SUCCESS", + SIGN_IN_FAILURE: "SIGN_IN_FAILURE", + EMAIL_SIGN_IN_START: "EMAIL_SIGN_IN_START", + CHECK_USER_SESSION: "CHECK_USER_SESSION", + SIGN_OUT_START: "SIGN_OUT_START", + SIGN_OUT_SUCCESS: "SIGN_OUT_SUCCESS", + SIGN_OUT_FAILURE: "SIGN_OUT_FAILURE", + EMAIL_SIGN_UP_START: "EMAIL_SIGN_UP_START", + EMAIL_SIGN_UP_SUCCESS: "EMAIL_SIGN_UP_SUCCESS", + EMAIL_SIGN_UP_FAILURE: "EMAIL_SIGN_UP_FAILURE", + UNAUTHORIZED_USER: "UNAUTHORIZED_USER", + SET_USER_LANGUAGE: "SET_USER_LANGUAGE", + UPDATE_USER_DETAILS: "UPDATE_USER_DETAILS", + UPDATE_USER_DETAILS_SUCCESS: "UPDATE_USER_DETAILS_SUCCESS", + SET_SHOP_DETAILS: "SET_SHOP_DETAILS", + SET_INSTANCE_ID: "SET_INSTANCE_ID", + CHECK_INSTANCE_ID: "CHECK_INSTANCE_ID", + SET_INSTANCE_CONFLICT: "SET_INSTANCE_CONFLICT", + SET_LOCAL_FINGERPRINT: "SET_LOCAL_FINGERPRINT", + SEND_PASSWORD_RESET_EMAIL_START: "SEND_PASSWORD_RESET_EMAIL_START", + SEND_PASSWORD_RESET_EMAIL_START_AGAIN: "SEND_PASSWORD_RESET_EMAIL_START_AGAIN", + SEND_PASSWORD_RESET_EMAIL_FAILURE: "SEND_PASSWORD_RESET_EMAIL_FAILURE", + SEND_PASSWORD_RESET_EMAIL_SUCCESS: "SEND_PASSWORD_RESET_EMAIL_SUCCESS", + VALIDATE_PASSWORD_RESET_START: "VALIDATE_PASSWORD_RESET_START", + VALIDATE_PASSWORD_RESET_SUCCESS: "VALIDATE_PASSWORD_RESET_SUCCESS", + VALIDATE_PASSWORD_RESET_FAILURE: "VALIDATE_PASSWORD_RESET_FAILURE", + SET_AUTH_LEVEL: "SET_AUTH_LEVEL", + CHECK_ACTION_CODE_START: "CHECK_ACTION_CODE_START", + CHECK_ACTION_CODE_SUCCESS: "CHECK_ACTION_CODE_SUCCESS", + CHECK_ACTION_CODE_FAILURE: "CHECK_ACTION_CODE_FAILURE", + SET_CURRENT_EULA: "SET_CURRENT_EULA", + EULA_ACCEPTED: "EULA_ACCEPTED" }; export default UserActionTypes; diff --git a/client/src/reportWebVitals.js b/client/src/reportWebVitals.js index d7347456c..9ecd33f9c 100644 --- a/client/src/reportWebVitals.js +++ b/client/src/reportWebVitals.js @@ -1,13 +1,13 @@ const reportWebVitals = (onPerfEntry) => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } + if (onPerfEntry && onPerfEntry instanceof Function) { + import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } }; export default reportWebVitals; diff --git a/client/src/setupTests.js b/client/src/setupTests.js index 58f239247..6f413a40b 100644 --- a/client/src/setupTests.js +++ b/client/src/setupTests.js @@ -1,4 +1,4 @@ -import {configure} from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; +import { configure } from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; -configure({adapter: new Adapter()}); \ No newline at end of file +configure({ adapter: new Adapter() }); diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 14a98d82a..e0d6d56ac 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1,3304 +1,3304 @@ { - "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}}.", - "jobconverted": "Job converted and assigned number {{ro_number}}.", - "jobdelivery": "Job intake completed. Status set to {{status}}. Actual completion is {{actual_completion}}.", - "jobexported": "", - "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." - } - }, - "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", - "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", - "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": { - "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?", - "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", - "country": "Country", - "dailybodytarget": "Scoreboard - Daily Body Target", - "dailypainttarget": "Scoreboard - Daily Paint Target", - "default_adjustment_rate": "Default Labor Deduction Adjustment Rate", - "deliver": { - "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" - }, - "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_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", - "pbs_serialnumber": "PBS Serial Number", - "title": "DMS" - }, - "emaillater": "Email Later", - "employee_teams": "Employee Teams", - "employees": "Employees", - "estimators": "Estimators", - "filehandlers": "Adjusters", - "insurancecos": "Insurance Companies", - "intakechecklist": "Intake Checklist", - "jobstatuses": "Job Statuses", - "laborrates": "Labor Rates", - "licensing": "Licensing", - "md_parts_scan": "Parts Scan Rules", - "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" - }, - "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": { - "save": "Shop configuration saved successfully. " - }, - "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": "In Service", - "leasereturn": "Lease Returned", - "out": "Rented", - "sold": "Sold" - }, - "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" - } - }, - "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", - "calculate": "Calculate", - "cancel": "Cancel", - "clear": "Clear", - "close": "Close", - "copied": "Copied!", - "copylink": "Copy Link", - "create": "Create", - "delete": "Delete", - "deleteall": "Delete All", - "deselectall": "Deselect All", - "edit": "Edit", - "login": "Login", - "print": "Print", - "refresh": "Refresh", - "remove": "Remove", - "reset": "Reset your changes.", - "resetpassword": "Reset Password", - "save": "Save", - "saveandnew": "Save and New", - "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", - "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", - "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": { - "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", - "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", - "ah_detail_line": "Mark as Detail Labor Line (Autohouse Only)", - "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": { - "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_tax_in": "Tax Labor Indicator", - "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_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 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_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_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": { - "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", - "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", - "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)", - "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.", - "profileadjustments": "Profile Disc./Mkup", - "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", - "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", - "subletstotal": "Sublets Total", - "subtotal": "Subtotal", - "supplementnote": "The Job had a supplement imported.", - "suspended": "SUSPENDED", - "suspense": "Suspense", - "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", - "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", - "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", - "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", - "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": { - "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. ", - "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", - "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" - }, - "errors": { - "boardupdate": "Error encountered updating Job. {{message}}", - "removing": "Error removing from production board. {{error}}", - "settings": "Error saving board settings: {{error}}" - }, - "labels": { - "actual_in": "Actual In", - "alert": "Alert", - "alertoff": "Remove alert from Job", - "alerton": "Add alert to Job", - "ats": "Alternative Transportation", - "bodyhours": "B", - "bodypriority": "B/P", - "bodyshop": { - "labels": { - "qbo_departmentid": "QBO Department ID", - "qbo_usa": "QBO USA" - } - }, - "cardcolor": "Card Colors", - "cardsettings": "Card Settings", - "clm_no": "Claim Number", - "comment": "Comment", - "compact": "Compact Cards", - "detailpriority": "D/P", - "employeeassignments": "Employee Assignments", - "employeesearch": "Employee Search", - "ins_co_nm": "Insurance Company Name", - "jobdetail": "Job Details", - "laborhrs": "Labor Hours", - "legend": "Legend:", - "note": "Production Note", - "ownr_nm": "Owner 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", - "totalhours": "Total Hrs ", - "touchtime": "T/T", - "viewname": "View Name" - }, - "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", - "timetickets": "Timetickets" - }, - "vendor": "Vendor" - }, - "templates": { - "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_type": "Payments by Date and 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_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_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", - "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." - } - }, - "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}}", - "app": "", - "bc": { - "accounting-payables": "Payables", - "accounting-payments": "Payments", - "accounting-receivables": "Receivables", - "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", - "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", - "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}}", - "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}}", - "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}}" - }, - "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}}.", + "jobconverted": "Job converted and assigned number {{ro_number}}.", + "jobdelivery": "Job intake completed. Status set to {{status}}. Actual completion is {{actual_completion}}.", + "jobexported": "", + "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." + } + }, + "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", + "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", + "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": { + "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?", + "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", + "country": "Country", + "dailybodytarget": "Scoreboard - Daily Body Target", + "dailypainttarget": "Scoreboard - Daily Paint Target", + "default_adjustment_rate": "Default Labor Deduction Adjustment Rate", + "deliver": { + "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" + }, + "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_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", + "pbs_serialnumber": "PBS Serial Number", + "title": "DMS" + }, + "emaillater": "Email Later", + "employee_teams": "Employee Teams", + "employees": "Employees", + "estimators": "Estimators", + "filehandlers": "Adjusters", + "insurancecos": "Insurance Companies", + "intakechecklist": "Intake Checklist", + "jobstatuses": "Job Statuses", + "laborrates": "Labor Rates", + "licensing": "Licensing", + "md_parts_scan": "Parts Scan Rules", + "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" + }, + "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": { + "save": "Shop configuration saved successfully. " + }, + "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": "In Service", + "leasereturn": "Lease Returned", + "out": "Rented", + "sold": "Sold" + }, + "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" + } + }, + "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", + "calculate": "Calculate", + "cancel": "Cancel", + "clear": "Clear", + "close": "Close", + "copied": "Copied!", + "copylink": "Copy Link", + "create": "Create", + "delete": "Delete", + "deleteall": "Delete All", + "deselectall": "Deselect All", + "edit": "Edit", + "login": "Login", + "print": "Print", + "refresh": "Refresh", + "remove": "Remove", + "reset": "Reset your changes.", + "resetpassword": "Reset Password", + "save": "Save", + "saveandnew": "Save and New", + "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", + "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", + "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": { + "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", + "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", + "ah_detail_line": "Mark as Detail Labor Line (Autohouse Only)", + "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": { + "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_tax_in": "Tax Labor Indicator", + "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_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 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_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_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": { + "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", + "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", + "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)", + "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.", + "profileadjustments": "Profile Disc./Mkup", + "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", + "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", + "subletstotal": "Sublets Total", + "subtotal": "Subtotal", + "supplementnote": "The Job had a supplement imported.", + "suspended": "SUSPENDED", + "suspense": "Suspense", + "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", + "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", + "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", + "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", + "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": { + "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. ", + "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", + "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" + }, + "errors": { + "boardupdate": "Error encountered updating Job. {{message}}", + "removing": "Error removing from production board. {{error}}", + "settings": "Error saving board settings: {{error}}" + }, + "labels": { + "actual_in": "Actual In", + "alert": "Alert", + "alertoff": "Remove alert from Job", + "alerton": "Add alert to Job", + "ats": "Alternative Transportation", + "bodyhours": "B", + "bodypriority": "B/P", + "bodyshop": { + "labels": { + "qbo_departmentid": "QBO Department ID", + "qbo_usa": "QBO USA" + } + }, + "cardcolor": "Card Colors", + "cardsettings": "Card Settings", + "clm_no": "Claim Number", + "comment": "Comment", + "compact": "Compact Cards", + "detailpriority": "D/P", + "employeeassignments": "Employee Assignments", + "employeesearch": "Employee Search", + "ins_co_nm": "Insurance Company Name", + "jobdetail": "Job Details", + "laborhrs": "Labor Hours", + "legend": "Legend:", + "note": "Production Note", + "ownr_nm": "Owner 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", + "totalhours": "Total Hrs ", + "touchtime": "T/T", + "viewname": "View Name" + }, + "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", + "timetickets": "Timetickets" + }, + "vendor": "Vendor" + }, + "templates": { + "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_type": "Payments by Date and 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_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_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", + "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." + } + }, + "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}}", + "app": "", + "bc": { + "accounting-payables": "Payables", + "accounting-payments": "Payments", + "accounting-receivables": "Receivables", + "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", + "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", + "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}}", + "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}}", + "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}}" + }, + "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 ba11bb4a3..d5e217592 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1,3303 +1,3303 @@ { - "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": "", - "jobconverted": "", - "jobdelivery": "", - "jobexported": "", - "jobfieldchanged": "", - "jobimported": "", - "jobinproductionchange": "", - "jobintake": "", - "jobinvoiced": "", - "jobioucreated": "", - "jobmodifylbradj": "", - "jobnoteadded": "", - "jobnotedeleted": "", - "jobnoteupdated": "", - "jobspartsorder": "", - "jobspartsreturn": "", - "jobstatuschange": "", - "jobsupplement": "", - "jobsuspend": "", - "jobvoid": "" - } - }, - "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": "", - "noneselected": "", - "onlycmforinvoiced": "", - "printlabels": "", - "retailtotal": "", - "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": { - "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": "", - "bill_allow_post_to_closed": "", - "bill_federal_tax_rate": "", - "bill_local_tax_rate": "", - "bill_state_tax_rate": "", - "city": "", - "closingperiod": "", - "country": "", - "dailybodytarget": "", - "dailypainttarget": "", - "default_adjustment_rate": "", - "deliver": { - "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": "" - }, - "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_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": "", - "pbs_serialnumber": "", - "title": "" - }, - "emaillater": "", - "employee_teams": "", - "employees": "", - "estimators": "", - "filehandlers": "", - "insurancecos": "", - "intakechecklist": "", - "jobstatuses": "", - "laborrates": "", - "licensing": "", - "md_parts_scan": "", - "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": "" - }, - "scheduling": "", - "scoreboardsetup": "", - "shopinfo": "", - "speedprint": "", - "ssbuckets": "", - "systemsettings": "", - "task-presets": "", - "workingdays": "" - }, - "successes": { - "save": "" - }, - "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": "" - }, - "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": "" - } - }, - "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": "", - "calculate": "", - "cancel": "", - "clear": "", - "close": "", - "copied": "", - "copylink": "", - "create": "", - "delete": "Borrar", - "deleteall": "", - "deselectall": "", - "edit": "Editar", - "login": "", - "print": "", - "refresh": "", - "remove": "", - "reset": " Restablecer a original.", - "resetpassword": "", - "save": "Salvar", - "saveandnew": "", - "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": "", - "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": "", - "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", - "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": { - "goback": "", - "proceedtopayment": "", - "refundpayment": "" - }, - "notifications": { - "error": { - "description": "", - "openingip": "", - "title": "" - } - }, - "titles": { - "amount": "", - "dateOfPayment": "", - "descriptions": "", - "payer": "", - "payername": "", - "paymentid": "", - "paymentnum": "", - "paymenttype": "", - "refundamount": "", - "transactionid": "" - } - }, - "joblines": { - "actions": { - "assign_team": "", - "converttolabor": "", - "dispatchparts": "", - "new": "" - }, - "errors": { - "creating": "", - "updating": "" - }, - "fields": { - "act_price": "Precio actual", - "ah_detail_line": "", - "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": { - "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_tax_in": "", - "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_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_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_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": { - "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": "", - "materials": { - "mapa": "" - }, - "missingprofileinfo": "", - "multipayers": "", - "net_repairs": "", - "notes": "Notas", - "othertotal": "", - "override_header": "¿Anular encabezado estimado al importar?", - "ownerassociation": "", - "parts": "Partes", - "parts_lines": "", - "parts_received": "", - "parts_tax_rates": "", - "partsfilter": "", - "partssubletstotal": "", - "partstotal": "", - "pimraryamountpayable": "", - "plitooltips": { - "billtotal": "", - "calculatedcreditsnotreceived": "", - "creditmemos": "", - "creditsnotreceived": "", - "discrep1": "", - "discrep2": "", - "discrep3": "", - "laboradj": "", - "partstotal": "", - "totalreturns": "" - }, - "ppc": "", - "profileadjustments": "", - "prt_dsmk_total": "", - "rates": "Tarifas", - "rates_subtotal": "", - "reconciliation": { - "billlinestotal": "", - "byassoc": "", - "byprice": "", - "clear": "", - "discrepancy": "", - "joblinestotal": "", - "multipleactprices": "", - "multiplebilllines": "", - "multiplebillsforactprice": "", - "removedpartsstrikethrough": "" - }, - "reconciliationheader": "", - "relatedros": "", - "remove_from_ar": "", - "returntotals": "", - "rosaletotal": "", - "sale_additional": "", - "sale_labor": "", - "sale_parts": "", - "sale_sublet": "", - "sales": "", - "savebeforeconversion": "", - "scheduledinchange": "", - "specialcoveragepolicy": "", - "state_tax_amt": "", - "subletstotal": "", - "subtotal": "", - "supplementnote": "", - "suspended": "", - "suspense": "", - "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", - "alljobs": "", - "allpayments": "", - "availablejobs": "Trabajos disponibles", - "bills": "", - "courtesycars": "", - "courtesycars-all": "", - "courtesycars-contracts": "", - "courtesycars-newcontract": "", - "customers": "Clientes", - "dashboard": "", - "enterbills": "", - "entercardpayment": "", - "enterpayment": "", - "entertimeticket": "", - "export": "", - "export-logs": "", - "help": "", - "home": "Casa", - "inventory": "", - "jobs": "Trabajos", - "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", - "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": { - "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": "", - "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": "", - "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": "" - }, - "errors": { - "boardupdate": "", - "removing": "", - "settings": "" - }, - "labels": { - "actual_in": "", - "alert": "", - "alertoff": "", - "alerton": "", - "ats": "", - "bodyhours": "", - "bodypriority": "", - "bodyshop": { - "labels": { - "qbo_departmentid": "", - "qbo_usa": "" - } - }, - "cardcolor": "", - "cardsettings": "", - "clm_no": "", - "comment": "", - "compact": "", - "detailpriority": "", - "employeeassignments": "", - "employeesearch": "", - "ins_co_nm": "", - "jobdetail": "", - "laborhrs": "", - "legend": "", - "note": "", - "ownr_nm": "", - "paintpriority": "", - "partsstatus": "", - "production_note": "", - "refinishhours": "", - "scheduled_completion": "", - "selectview": "", - "stickyheader": "", - "sublets": "", - "totalhours": "", - "touchtime": "", - "viewname": "" - }, - "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": "", - "timetickets": "" - }, - "vendor": "" - }, - "templates": { - "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_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_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_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": "", - "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": "" - } - }, - "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": "", - "app": "", - "bc": { - "accounting-payables": "", - "accounting-payments": "", - "accounting-receivables": "", - "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": "", - "owner-detail": "", - "owners": "", - "parts-queue": "", - "payments-all": "", - "phonebook": "", - "productionboard": "", - "productionlist": "", - "profile": "", - "schedule": "", - "scoreboard": "", - "shop": "", - "shop-csi": "", - "shop-templates": "", - "shop-vendors": "", - "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}}", - "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}}", - "techconsole": "{{app}}", - "techjobclock": "{{app}}", - "techjoblookup": "{{app}}", - "techshiftclock": "{{app}}", - "temporarydocs": "", - "timetickets": "", - "ttapprovals": "", - "vehicledetail": "Detalles del vehículo {{vehicle}} | {{app}}", - "vehicles": "Todos los vehiculos | {{app}}" - }, - "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": "", + "jobconverted": "", + "jobdelivery": "", + "jobexported": "", + "jobfieldchanged": "", + "jobimported": "", + "jobinproductionchange": "", + "jobintake": "", + "jobinvoiced": "", + "jobioucreated": "", + "jobmodifylbradj": "", + "jobnoteadded": "", + "jobnotedeleted": "", + "jobnoteupdated": "", + "jobspartsorder": "", + "jobspartsreturn": "", + "jobstatuschange": "", + "jobsupplement": "", + "jobsuspend": "", + "jobvoid": "" + } + }, + "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": "", + "noneselected": "", + "onlycmforinvoiced": "", + "printlabels": "", + "retailtotal": "", + "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": { + "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": "", + "bill_allow_post_to_closed": "", + "bill_federal_tax_rate": "", + "bill_local_tax_rate": "", + "bill_state_tax_rate": "", + "city": "", + "closingperiod": "", + "country": "", + "dailybodytarget": "", + "dailypainttarget": "", + "default_adjustment_rate": "", + "deliver": { + "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": "" + }, + "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_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": "", + "pbs_serialnumber": "", + "title": "" + }, + "emaillater": "", + "employee_teams": "", + "employees": "", + "estimators": "", + "filehandlers": "", + "insurancecos": "", + "intakechecklist": "", + "jobstatuses": "", + "laborrates": "", + "licensing": "", + "md_parts_scan": "", + "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": "" + }, + "scheduling": "", + "scoreboardsetup": "", + "shopinfo": "", + "speedprint": "", + "ssbuckets": "", + "systemsettings": "", + "task-presets": "", + "workingdays": "" + }, + "successes": { + "save": "" + }, + "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": "" + }, + "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": "" + } + }, + "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": "", + "calculate": "", + "cancel": "", + "clear": "", + "close": "", + "copied": "", + "copylink": "", + "create": "", + "delete": "Borrar", + "deleteall": "", + "deselectall": "", + "edit": "Editar", + "login": "", + "print": "", + "refresh": "", + "remove": "", + "reset": " Restablecer a original.", + "resetpassword": "", + "save": "Salvar", + "saveandnew": "", + "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": "", + "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": "", + "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", + "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": { + "goback": "", + "proceedtopayment": "", + "refundpayment": "" + }, + "notifications": { + "error": { + "description": "", + "openingip": "", + "title": "" + } + }, + "titles": { + "amount": "", + "dateOfPayment": "", + "descriptions": "", + "payer": "", + "payername": "", + "paymentid": "", + "paymentnum": "", + "paymenttype": "", + "refundamount": "", + "transactionid": "" + } + }, + "joblines": { + "actions": { + "assign_team": "", + "converttolabor": "", + "dispatchparts": "", + "new": "" + }, + "errors": { + "creating": "", + "updating": "" + }, + "fields": { + "act_price": "Precio actual", + "ah_detail_line": "", + "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": { + "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_tax_in": "", + "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_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_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_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": { + "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": "", + "materials": { + "mapa": "" + }, + "missingprofileinfo": "", + "multipayers": "", + "net_repairs": "", + "notes": "Notas", + "othertotal": "", + "override_header": "¿Anular encabezado estimado al importar?", + "ownerassociation": "", + "parts": "Partes", + "parts_lines": "", + "parts_received": "", + "parts_tax_rates": "", + "partsfilter": "", + "partssubletstotal": "", + "partstotal": "", + "pimraryamountpayable": "", + "plitooltips": { + "billtotal": "", + "calculatedcreditsnotreceived": "", + "creditmemos": "", + "creditsnotreceived": "", + "discrep1": "", + "discrep2": "", + "discrep3": "", + "laboradj": "", + "partstotal": "", + "totalreturns": "" + }, + "ppc": "", + "profileadjustments": "", + "prt_dsmk_total": "", + "rates": "Tarifas", + "rates_subtotal": "", + "reconciliation": { + "billlinestotal": "", + "byassoc": "", + "byprice": "", + "clear": "", + "discrepancy": "", + "joblinestotal": "", + "multipleactprices": "", + "multiplebilllines": "", + "multiplebillsforactprice": "", + "removedpartsstrikethrough": "" + }, + "reconciliationheader": "", + "relatedros": "", + "remove_from_ar": "", + "returntotals": "", + "rosaletotal": "", + "sale_additional": "", + "sale_labor": "", + "sale_parts": "", + "sale_sublet": "", + "sales": "", + "savebeforeconversion": "", + "scheduledinchange": "", + "specialcoveragepolicy": "", + "state_tax_amt": "", + "subletstotal": "", + "subtotal": "", + "supplementnote": "", + "suspended": "", + "suspense": "", + "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", + "alljobs": "", + "allpayments": "", + "availablejobs": "Trabajos disponibles", + "bills": "", + "courtesycars": "", + "courtesycars-all": "", + "courtesycars-contracts": "", + "courtesycars-newcontract": "", + "customers": "Clientes", + "dashboard": "", + "enterbills": "", + "entercardpayment": "", + "enterpayment": "", + "entertimeticket": "", + "export": "", + "export-logs": "", + "help": "", + "home": "Casa", + "inventory": "", + "jobs": "Trabajos", + "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", + "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": { + "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": "", + "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": "", + "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": "" + }, + "errors": { + "boardupdate": "", + "removing": "", + "settings": "" + }, + "labels": { + "actual_in": "", + "alert": "", + "alertoff": "", + "alerton": "", + "ats": "", + "bodyhours": "", + "bodypriority": "", + "bodyshop": { + "labels": { + "qbo_departmentid": "", + "qbo_usa": "" + } + }, + "cardcolor": "", + "cardsettings": "", + "clm_no": "", + "comment": "", + "compact": "", + "detailpriority": "", + "employeeassignments": "", + "employeesearch": "", + "ins_co_nm": "", + "jobdetail": "", + "laborhrs": "", + "legend": "", + "note": "", + "ownr_nm": "", + "paintpriority": "", + "partsstatus": "", + "production_note": "", + "refinishhours": "", + "scheduled_completion": "", + "selectview": "", + "stickyheader": "", + "sublets": "", + "totalhours": "", + "touchtime": "", + "viewname": "" + }, + "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": "", + "timetickets": "" + }, + "vendor": "" + }, + "templates": { + "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_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_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_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": "", + "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": "" + } + }, + "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": "", + "app": "", + "bc": { + "accounting-payables": "", + "accounting-payments": "", + "accounting-receivables": "", + "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": "", + "owner-detail": "", + "owners": "", + "parts-queue": "", + "payments-all": "", + "phonebook": "", + "productionboard": "", + "productionlist": "", + "profile": "", + "schedule": "", + "scoreboard": "", + "shop": "", + "shop-csi": "", + "shop-templates": "", + "shop-vendors": "", + "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}}", + "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}}", + "techconsole": "{{app}}", + "techjobclock": "{{app}}", + "techjoblookup": "{{app}}", + "techshiftclock": "{{app}}", + "temporarydocs": "", + "timetickets": "", + "ttapprovals": "", + "vehicledetail": "Detalles del vehículo {{vehicle}} | {{app}}", + "vehicles": "Todos los vehiculos | {{app}}" + }, + "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 1117003e0..91f8ae2b4 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1,3303 +1,3303 @@ { - "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": "", - "jobconverted": "", - "jobdelivery": "", - "jobexported": "", - "jobfieldchanged": "", - "jobimported": "", - "jobinproductionchange": "", - "jobintake": "", - "jobinvoiced": "", - "jobioucreated": "", - "jobmodifylbradj": "", - "jobnoteadded": "", - "jobnotedeleted": "", - "jobnoteupdated": "", - "jobspartsorder": "", - "jobspartsreturn": "", - "jobstatuschange": "", - "jobsupplement": "", - "jobsuspend": "", - "jobvoid": "" - } - }, - "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": "", - "noneselected": "", - "onlycmforinvoiced": "", - "printlabels": "", - "retailtotal": "", - "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": { - "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": "", - "bill_allow_post_to_closed": "", - "bill_federal_tax_rate": "", - "bill_local_tax_rate": "", - "bill_state_tax_rate": "", - "city": "", - "closingperiod": "", - "country": "", - "dailybodytarget": "", - "dailypainttarget": "", - "default_adjustment_rate": "", - "deliver": { - "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": "" - }, - "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_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": "", - "pbs_serialnumber": "", - "title": "" - }, - "emaillater": "", - "employee_teams": "", - "employees": "", - "estimators": "", - "filehandlers": "", - "insurancecos": "", - "intakechecklist": "", - "jobstatuses": "", - "laborrates": "", - "licensing": "", - "md_parts_scan": "", - "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": "" - }, - "scheduling": "", - "scoreboardsetup": "", - "shopinfo": "", - "speedprint": "", - "ssbuckets": "", - "systemsettings": "", - "task-presets": "", - "workingdays": "" - }, - "successes": { - "save": "" - }, - "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": "" - }, - "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": "" - } - }, - "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": "", - "calculate": "", - "cancel": "", - "clear": "", - "close": "", - "copied": "", - "copylink": "", - "create": "", - "delete": "Effacer", - "deleteall": "", - "deselectall": "", - "edit": "modifier", - "login": "", - "print": "", - "refresh": "", - "remove": "", - "reset": " Rétablir l'original.", - "resetpassword": "", - "save": "sauvegarder", - "saveandnew": "", - "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": "", - "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": "", - "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", - "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": { - "goback": "", - "proceedtopayment": "", - "refundpayment": "" - }, - "notifications": { - "error": { - "description": "", - "openingip": "", - "title": "" - } - }, - "titles": { - "amount": "", - "dateOfPayment": "", - "descriptions": "", - "payer": "", - "payername": "", - "paymentid": "", - "paymentnum": "", - "paymenttype": "", - "refundamount": "", - "transactionid": "" - } - }, - "joblines": { - "actions": { - "assign_team": "", - "converttolabor": "", - "dispatchparts": "", - "new": "" - }, - "errors": { - "creating": "", - "updating": "" - }, - "fields": { - "act_price": "Prix actuel", - "ah_detail_line": "", - "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": { - "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_tax_in": "", - "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_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_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_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": { - "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": "", - "materials": { - "mapa": "" - }, - "missingprofileinfo": "", - "multipayers": "", - "net_repairs": "", - "notes": "Remarques", - "othertotal": "", - "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": "", - "pimraryamountpayable": "", - "plitooltips": { - "billtotal": "", - "calculatedcreditsnotreceived": "", - "creditmemos": "", - "creditsnotreceived": "", - "discrep1": "", - "discrep2": "", - "discrep3": "", - "laboradj": "", - "partstotal": "", - "totalreturns": "" - }, - "ppc": "", - "profileadjustments": "", - "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": "", - "rosaletotal": "", - "sale_additional": "", - "sale_labor": "", - "sale_parts": "", - "sale_sublet": "", - "sales": "", - "savebeforeconversion": "", - "scheduledinchange": "", - "specialcoveragepolicy": "", - "state_tax_amt": "", - "subletstotal": "", - "subtotal": "", - "supplementnote": "", - "suspended": "", - "suspense": "", - "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", - "alljobs": "", - "allpayments": "", - "availablejobs": "Emplois disponibles", - "bills": "", - "courtesycars": "", - "courtesycars-all": "", - "courtesycars-contracts": "", - "courtesycars-newcontract": "", - "customers": "Les clients", - "dashboard": "", - "enterbills": "", - "entercardpayment": "", - "enterpayment": "", - "entertimeticket": "", - "export": "", - "export-logs": "", - "help": "", - "home": "Accueil", - "inventory": "", - "jobs": "Emplois", - "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", - "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": { - "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": "", - "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": "", - "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": "" - }, - "errors": { - "boardupdate": "", - "removing": "", - "settings": "" - }, - "labels": { - "actual_in": "", - "alert": "", - "alertoff": "", - "alerton": "", - "ats": "", - "bodyhours": "", - "bodypriority": "", - "bodyshop": { - "labels": { - "qbo_departmentid": "", - "qbo_usa": "" - } - }, - "cardcolor": "", - "cardsettings": "", - "clm_no": "", - "comment": "", - "compact": "", - "detailpriority": "", - "employeeassignments": "", - "employeesearch": "", - "ins_co_nm": "", - "jobdetail": "", - "laborhrs": "", - "legend": "", - "note": "", - "ownr_nm": "", - "paintpriority": "", - "partsstatus": "", - "production_note": "", - "refinishhours": "", - "scheduled_completion": "", - "selectview": "", - "stickyheader": "", - "sublets": "", - "totalhours": "", - "touchtime": "", - "viewname": "" - }, - "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": "", - "timetickets": "" - }, - "vendor": "" - }, - "templates": { - "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_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_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_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": "", - "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": "" - } - }, - "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": "", - "app": "", - "bc": { - "accounting-payables": "", - "accounting-payments": "", - "accounting-receivables": "", - "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": "", - "owner-detail": "", - "owners": "", - "parts-queue": "", - "payments-all": "", - "phonebook": "", - "productionboard": "", - "productionlist": "", - "profile": "", - "schedule": "", - "scoreboard": "", - "shop": "", - "shop-csi": "", - "shop-templates": "", - "shop-vendors": "", - "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}}", - "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}}", - "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}}" - }, - "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": "", + "jobconverted": "", + "jobdelivery": "", + "jobexported": "", + "jobfieldchanged": "", + "jobimported": "", + "jobinproductionchange": "", + "jobintake": "", + "jobinvoiced": "", + "jobioucreated": "", + "jobmodifylbradj": "", + "jobnoteadded": "", + "jobnotedeleted": "", + "jobnoteupdated": "", + "jobspartsorder": "", + "jobspartsreturn": "", + "jobstatuschange": "", + "jobsupplement": "", + "jobsuspend": "", + "jobvoid": "" + } + }, + "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": "", + "noneselected": "", + "onlycmforinvoiced": "", + "printlabels": "", + "retailtotal": "", + "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": { + "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": "", + "bill_allow_post_to_closed": "", + "bill_federal_tax_rate": "", + "bill_local_tax_rate": "", + "bill_state_tax_rate": "", + "city": "", + "closingperiod": "", + "country": "", + "dailybodytarget": "", + "dailypainttarget": "", + "default_adjustment_rate": "", + "deliver": { + "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": "" + }, + "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_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": "", + "pbs_serialnumber": "", + "title": "" + }, + "emaillater": "", + "employee_teams": "", + "employees": "", + "estimators": "", + "filehandlers": "", + "insurancecos": "", + "intakechecklist": "", + "jobstatuses": "", + "laborrates": "", + "licensing": "", + "md_parts_scan": "", + "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": "" + }, + "scheduling": "", + "scoreboardsetup": "", + "shopinfo": "", + "speedprint": "", + "ssbuckets": "", + "systemsettings": "", + "task-presets": "", + "workingdays": "" + }, + "successes": { + "save": "" + }, + "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": "" + }, + "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": "" + } + }, + "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": "", + "calculate": "", + "cancel": "", + "clear": "", + "close": "", + "copied": "", + "copylink": "", + "create": "", + "delete": "Effacer", + "deleteall": "", + "deselectall": "", + "edit": "modifier", + "login": "", + "print": "", + "refresh": "", + "remove": "", + "reset": " Rétablir l'original.", + "resetpassword": "", + "save": "sauvegarder", + "saveandnew": "", + "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": "", + "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": "", + "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", + "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": { + "goback": "", + "proceedtopayment": "", + "refundpayment": "" + }, + "notifications": { + "error": { + "description": "", + "openingip": "", + "title": "" + } + }, + "titles": { + "amount": "", + "dateOfPayment": "", + "descriptions": "", + "payer": "", + "payername": "", + "paymentid": "", + "paymentnum": "", + "paymenttype": "", + "refundamount": "", + "transactionid": "" + } + }, + "joblines": { + "actions": { + "assign_team": "", + "converttolabor": "", + "dispatchparts": "", + "new": "" + }, + "errors": { + "creating": "", + "updating": "" + }, + "fields": { + "act_price": "Prix actuel", + "ah_detail_line": "", + "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": { + "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_tax_in": "", + "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_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_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_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": { + "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": "", + "materials": { + "mapa": "" + }, + "missingprofileinfo": "", + "multipayers": "", + "net_repairs": "", + "notes": "Remarques", + "othertotal": "", + "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": "", + "pimraryamountpayable": "", + "plitooltips": { + "billtotal": "", + "calculatedcreditsnotreceived": "", + "creditmemos": "", + "creditsnotreceived": "", + "discrep1": "", + "discrep2": "", + "discrep3": "", + "laboradj": "", + "partstotal": "", + "totalreturns": "" + }, + "ppc": "", + "profileadjustments": "", + "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": "", + "rosaletotal": "", + "sale_additional": "", + "sale_labor": "", + "sale_parts": "", + "sale_sublet": "", + "sales": "", + "savebeforeconversion": "", + "scheduledinchange": "", + "specialcoveragepolicy": "", + "state_tax_amt": "", + "subletstotal": "", + "subtotal": "", + "supplementnote": "", + "suspended": "", + "suspense": "", + "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", + "alljobs": "", + "allpayments": "", + "availablejobs": "Emplois disponibles", + "bills": "", + "courtesycars": "", + "courtesycars-all": "", + "courtesycars-contracts": "", + "courtesycars-newcontract": "", + "customers": "Les clients", + "dashboard": "", + "enterbills": "", + "entercardpayment": "", + "enterpayment": "", + "entertimeticket": "", + "export": "", + "export-logs": "", + "help": "", + "home": "Accueil", + "inventory": "", + "jobs": "Emplois", + "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", + "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": { + "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": "", + "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": "", + "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": "" + }, + "errors": { + "boardupdate": "", + "removing": "", + "settings": "" + }, + "labels": { + "actual_in": "", + "alert": "", + "alertoff": "", + "alerton": "", + "ats": "", + "bodyhours": "", + "bodypriority": "", + "bodyshop": { + "labels": { + "qbo_departmentid": "", + "qbo_usa": "" + } + }, + "cardcolor": "", + "cardsettings": "", + "clm_no": "", + "comment": "", + "compact": "", + "detailpriority": "", + "employeeassignments": "", + "employeesearch": "", + "ins_co_nm": "", + "jobdetail": "", + "laborhrs": "", + "legend": "", + "note": "", + "ownr_nm": "", + "paintpriority": "", + "partsstatus": "", + "production_note": "", + "refinishhours": "", + "scheduled_completion": "", + "selectview": "", + "stickyheader": "", + "sublets": "", + "totalhours": "", + "touchtime": "", + "viewname": "" + }, + "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": "", + "timetickets": "" + }, + "vendor": "" + }, + "templates": { + "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_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_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_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": "", + "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": "" + } + }, + "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": "", + "app": "", + "bc": { + "accounting-payables": "", + "accounting-payments": "", + "accounting-receivables": "", + "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": "", + "owner-detail": "", + "owners": "", + "parts-queue": "", + "payments-all": "", + "phonebook": "", + "productionboard": "", + "productionlist": "", + "profile": "", + "schedule": "", + "scoreboard": "", + "shop": "", + "shop-csi": "", + "shop-templates": "", + "shop-vendors": "", + "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}}", + "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}}", + "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}}" + }, + "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/client/src/translations/i18n.js b/client/src/translations/i18n.js index 13c362124..b26f9c30d 100644 --- a/client/src/translations/i18n.js +++ b/client/src/translations/i18n.js @@ -1,17 +1,17 @@ -import i18n from 'i18next'; -import LanguageDetector from 'i18next-browser-languagedetector'; -import { initReactI18next } from 'react-i18next'; -import en_Translation from './en_us/common.json'; -import es_Translation from './es/common.json'; -import fr_Translation from './fr/common.json'; -import { GenerateTemplates } from '../utils/RenderTemplate'; +import i18n from "i18next"; +import LanguageDetector from "i18next-browser-languagedetector"; +import { initReactI18next } from "react-i18next"; +import en_Translation from "./en_us/common.json"; +import es_Translation from "./es/common.json"; +import fr_Translation from "./fr/common.json"; +import { GenerateTemplates } from "../utils/RenderTemplate"; // the translations // (tip move them in a JSON file and import them) const resources = { - 'en-US': en_Translation, - 'fr-CA': fr_Translation, - 'es-MX': es_Translation, + "en-US": en_Translation, + "fr-CA": fr_Translation, + "es-MX": es_Translation }; i18n .use(LanguageDetector) // passes i18n down to react-i18next @@ -21,16 +21,16 @@ i18n resources, //lng: "en", detection: {}, - fallbackLng: 'en-US', + fallbackLng: "en-US", debug: import.meta.env.DEV, react: { - useSuspense: true, + useSuspense: true }, //keySeparator: false, // we do not use keys in form messages.welcome interpolation: { escapeValue: false, // react already safes from xss - skipOnVariables: false, - }, + skipOnVariables: false + } }, (error) => { GenerateTemplates(); diff --git a/client/src/utils/AuditTrailMappings.js b/client/src/utils/AuditTrailMappings.js index 55bb7a604..f2de52319 100644 --- a/client/src/utils/AuditTrailMappings.js +++ b/client/src/utils/AuditTrailMappings.js @@ -1,67 +1,45 @@ import i18n from "i18next"; const AuditTrailMapping = { - admin_job_remove_from_ar: (status) => - i18n.t("audit_trail.messages.admin_job_remove_from_ar", {status}), - admin_jobfieldchange: (field, value) => - "ADMIN: " + - i18n.t("audit_trail.messages.jobfieldchanged", {field, value}), - admin_jobstatuschange: (status) => - "ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", {status}), - alertToggle: (status) => - i18n.t("audit_trail.messages.alerttoggle", {status}), - appointmentcancel: (lost_sale_reason) => - i18n.t("audit_trail.messages.appointmentcancel", {lost_sale_reason}), - appointmentinsert: (start) => - i18n.t("audit_trail.messages.appointmentinsert", {start}), - billdeleted: (invoice_number) => - i18n.t("audit_trail.messages.billdeleted", { invoice_number }), - billposted: (invoice_number) => - i18n.t("audit_trail.messages.billposted", {invoice_number}), - billupdated: (invoice_number) => - i18n.t("audit_trail.messages.billupdated", {invoice_number}), - jobassignmentchange: (operation, name) => - i18n.t("audit_trail.messages.jobassignmentchange", {operation, name}), - jobassignmentremoved: (operation) => - i18n.t("audit_trail.messages.jobassignmentremoved", {operation}), - jobchecklist: (type, inproduction, status) => - i18n.t("audit_trail.messages.jobchecklist", {type, inproduction, status}), - jobconverted: (ro_number) => - i18n.t("audit_trail.messages.jobconverted", {ro_number}), - jobintake: (status, email, scheduled_completion) => - i18n.t("audit_trail.messages.jobintake", {status, email,scheduled_completion}), - jobdelivery: (status, email, actual_completion) => - i18n.t("audit_trail.messages.jobdelivery", {status, email,actual_completion}), - jobexported: () => i18n.t("audit_trail.messages.jobexported"), - jobfieldchange: (field, value) => - i18n.t("audit_trail.messages.jobfieldchanged", {field, value}), - jobimported: () => i18n.t("audit_trail.messages.jobimported"), - jobinproductionchange: (inproduction) => - i18n.t("audit_trail.messages.jobinproductionchange", {inproduction}), - jobinvoiced: () => i18n.t("audit_trail.messages.jobinvoiced"), - jobmodifylbradj: ({mod_lbr_ty, hours}) => - i18n.t("audit_trail.messages.jobmodifylbradj", {mod_lbr_ty, hours}), - jobnoteadded: () => i18n.t("audit_trail.messages.jobnoteadded"), - jobnoteupdated: () => i18n.t("audit_trail.messages.jobnoteupdated"), - jobnotedeleted: () => i18n.t("audit_trail.messages.jobnotedeleted"), - admin_jobunvoid: () => i18n.t("audit_trail.messages.admin_jobunvoid"), - admin_jobuninvoice: () => i18n.t("audit_trail.messages.admin_jobuninvoice"), - admin_jobmarkforreexport: () => - i18n.t("audit_trail.messages.admin_jobmarkforreexport"), - admin_jobmarkexported: () => - i18n.t("audit_trail.messages.admin_jobmarkexported"), - failedpayment: () => i18n.t("audit_trail.messages.failedpayment"), - assignedlinehours: (team, hours) => - i18n.t("audit_trail.messages.assignedlinehours", {team, hours}), - jobspartsorder: (order_number) => - i18n.t("audit_trail.messages.jobspartsorder", {order_number}), - jobspartsreturn: (order_number) => - i18n.t("audit_trail.messages.jobspartsreturn", {order_number}), - jobstatuschange: (status) => - i18n.t("audit_trail.messages.jobstatuschange", {status}), - jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"), - jobsuspend: (status) => i18n.t("audit_trail.messages.jobsuspend", { status }), - jobvoid: () => i18n.t("audit_trail.messages.jobvoid"), + admin_job_remove_from_ar: (status) => i18n.t("audit_trail.messages.admin_job_remove_from_ar", { status }), + admin_jobfieldchange: (field, value) => "ADMIN: " + i18n.t("audit_trail.messages.jobfieldchanged", { field, value }), + admin_jobstatuschange: (status) => "ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", { status }), + alertToggle: (status) => i18n.t("audit_trail.messages.alerttoggle", { status }), + appointmentcancel: (lost_sale_reason) => i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }), + appointmentinsert: (start) => i18n.t("audit_trail.messages.appointmentinsert", { start }), + billdeleted: (invoice_number) => i18n.t("audit_trail.messages.billdeleted", { invoice_number }), + billposted: (invoice_number) => i18n.t("audit_trail.messages.billposted", { invoice_number }), + billupdated: (invoice_number) => i18n.t("audit_trail.messages.billupdated", { invoice_number }), + jobassignmentchange: (operation, name) => i18n.t("audit_trail.messages.jobassignmentchange", { operation, name }), + jobassignmentremoved: (operation) => i18n.t("audit_trail.messages.jobassignmentremoved", { operation }), + jobchecklist: (type, inproduction, status) => + i18n.t("audit_trail.messages.jobchecklist", { type, inproduction, status }), + jobconverted: (ro_number) => i18n.t("audit_trail.messages.jobconverted", { ro_number }), + jobintake: (status, email, scheduled_completion) => + i18n.t("audit_trail.messages.jobintake", { status, email, scheduled_completion }), + jobdelivery: (status, email, actual_completion) => + i18n.t("audit_trail.messages.jobdelivery", { status, email, actual_completion }), + jobexported: () => i18n.t("audit_trail.messages.jobexported"), + jobfieldchange: (field, value) => i18n.t("audit_trail.messages.jobfieldchanged", { field, value }), + jobimported: () => i18n.t("audit_trail.messages.jobimported"), + jobinproductionchange: (inproduction) => i18n.t("audit_trail.messages.jobinproductionchange", { inproduction }), + jobinvoiced: () => i18n.t("audit_trail.messages.jobinvoiced"), + jobmodifylbradj: ({ mod_lbr_ty, hours }) => i18n.t("audit_trail.messages.jobmodifylbradj", { mod_lbr_ty, hours }), + jobnoteadded: () => i18n.t("audit_trail.messages.jobnoteadded"), + jobnoteupdated: () => i18n.t("audit_trail.messages.jobnoteupdated"), + jobnotedeleted: () => i18n.t("audit_trail.messages.jobnotedeleted"), + admin_jobunvoid: () => i18n.t("audit_trail.messages.admin_jobunvoid"), + admin_jobuninvoice: () => i18n.t("audit_trail.messages.admin_jobuninvoice"), + admin_jobmarkforreexport: () => i18n.t("audit_trail.messages.admin_jobmarkforreexport"), + admin_jobmarkexported: () => i18n.t("audit_trail.messages.admin_jobmarkexported"), + failedpayment: () => i18n.t("audit_trail.messages.failedpayment"), + assignedlinehours: (team, hours) => i18n.t("audit_trail.messages.assignedlinehours", { team, hours }), + jobspartsorder: (order_number) => i18n.t("audit_trail.messages.jobspartsorder", { order_number }), + jobspartsreturn: (order_number) => i18n.t("audit_trail.messages.jobspartsreturn", { order_number }), + jobstatuschange: (status) => i18n.t("audit_trail.messages.jobstatuschange", { status }), + jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"), + jobsuspend: (status) => i18n.t("audit_trail.messages.jobsuspend", { status }), + jobvoid: () => i18n.t("audit_trail.messages.jobvoid") }; export default AuditTrailMapping; diff --git a/client/src/utils/Ciecaselect.jsx b/client/src/utils/Ciecaselect.jsx index 35a2cbffc..3d0bc454b 100644 --- a/client/src/utils/Ciecaselect.jsx +++ b/client/src/utils/Ciecaselect.jsx @@ -1,101 +1,53 @@ import React from "react"; -import {Select} from "antd"; +import { Select } from "antd"; import i18n from "../translations/i18n"; export default function CiecaSelect(parts = true, labor = true) { - return ( + return ( + <> + {labor && ( <> - {labor && ( - <> - - {i18n.t("joblines.fields.lbr_types.LAA")} - - - {i18n.t("joblines.fields.lbr_types.LAB")} - - - {i18n.t("joblines.fields.lbr_types.LAD")} - - - {i18n.t("joblines.fields.lbr_types.LAE")} - - - {i18n.t("joblines.fields.lbr_types.LAF")} - - - {i18n.t("joblines.fields.lbr_types.LAG")} - - - {i18n.t("joblines.fields.lbr_types.LAM")} - - - {i18n.t("joblines.fields.lbr_types.LAR")} - - - {i18n.t("joblines.fields.lbr_types.LAS")} - - - {i18n.t("joblines.fields.lbr_types.LAU")} - - - {i18n.t("joblines.fields.lbr_types.LA1")} - - - {i18n.t("joblines.fields.lbr_types.LA2")} - - - {i18n.t("joblines.fields.lbr_types.LA3")} - - - {i18n.t("joblines.fields.lbr_types.LA4")} - - - )} - {parts && ( - <> - - {i18n.t("joblines.fields.part_types.PAA")} - - - {i18n.t("joblines.fields.part_types.PAC")} - - - - {i18n.t("joblines.fields.part_types.PAL")} - - - {i18n.t("joblines.fields.part_types.PAG")} - - - {i18n.t("joblines.fields.part_types.PAM")} - - - {i18n.t("joblines.fields.part_types.PAP")} - - - {i18n.t("joblines.fields.part_types.PAN")} - - - {i18n.t("joblines.fields.part_types.PAO")} - - - {i18n.t("joblines.fields.part_types.PAR")} - - - {i18n.t("joblines.fields.part_types.PAS")} - - - )} + {i18n.t("joblines.fields.lbr_types.LAA")} + {i18n.t("joblines.fields.lbr_types.LAB")} + {i18n.t("joblines.fields.lbr_types.LAD")} + {i18n.t("joblines.fields.lbr_types.LAE")} + {i18n.t("joblines.fields.lbr_types.LAF")} + {i18n.t("joblines.fields.lbr_types.LAG")} + {i18n.t("joblines.fields.lbr_types.LAM")} + {i18n.t("joblines.fields.lbr_types.LAR")} + {i18n.t("joblines.fields.lbr_types.LAS")} + {i18n.t("joblines.fields.lbr_types.LAU")} + {i18n.t("joblines.fields.lbr_types.LA1")} + {i18n.t("joblines.fields.lbr_types.LA2")} + {i18n.t("joblines.fields.lbr_types.LA3")} + {i18n.t("joblines.fields.lbr_types.LA4")} - ); + )} + {parts && ( + <> + {i18n.t("joblines.fields.part_types.PAA")} + {i18n.t("joblines.fields.part_types.PAC")} + + {i18n.t("joblines.fields.part_types.PAL")} + {i18n.t("joblines.fields.part_types.PAG")} + {i18n.t("joblines.fields.part_types.PAM")} + {i18n.t("joblines.fields.part_types.PAP")} + {i18n.t("joblines.fields.part_types.PAN")} + {i18n.t("joblines.fields.part_types.PAO")} + {i18n.t("joblines.fields.part_types.PAR")} + {i18n.t("joblines.fields.part_types.PAS")} + + )} + + ); } export function GetPartTypeName(part_type) { - if (!part_type) return null; - return i18n.t(`joblines.fields.part_types.${part_type.toUpperCase()}`); + if (!part_type) return null; + return i18n.t(`joblines.fields.part_types.${part_type.toUpperCase()}`); } export function Get(part_type) { - if (!part_type) return null; - return i18n.t(`joblines.fields.part_types.${part_type.toUpperCase()}`); + if (!part_type) return null; + return i18n.t(`joblines.fields.part_types.${part_type.toUpperCase()}`); } diff --git a/client/src/utils/CleanAxios.js b/client/src/utils/CleanAxios.js index ba39140a9..513e71bc8 100644 --- a/client/src/utils/CleanAxios.js +++ b/client/src/utils/CleanAxios.js @@ -1,26 +1,22 @@ import axios from "axios"; -import {auth} from "../firebase/firebase.utils"; +import { auth } from "../firebase/firebase.utils"; axios.defaults.baseURL = - import.meta.env.VITE_APP_AXIOS_BASE_API_URL || - ( - import.meta.env.MODE === "production" ? - "https://api.imex.online/" : - "http://localhost:4000/" - ); + import.meta.env.VITE_APP_AXIOS_BASE_API_URL || + (import.meta.env.MODE === "production" ? "https://api.imex.online/" : "http://localhost:4000/"); export const axiosAuthInterceptorId = axios.interceptors.request.use( - async (config) => { - if (!config.headers.Authorization) { - const token = auth.currentUser && (await auth.currentUser.getIdToken()); - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } - } + async (config) => { + if (!config.headers.Authorization) { + const token = auth.currentUser && (await auth.currentUser.getIdToken()); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + } - return config; - }, - (error) => Promise.reject(error) + return config; + }, + (error) => Promise.reject(error) ); const cleanAxios = axios.create(); diff --git a/client/src/utils/CurrencyFormatter.jsx b/client/src/utils/CurrencyFormatter.jsx index 7ef1fdf34..5510b8789 100644 --- a/client/src/utils/CurrencyFormatter.jsx +++ b/client/src/utils/CurrencyFormatter.jsx @@ -1,15 +1,15 @@ import React from "react"; -import {NumericFormat} from "react-number-format"; +import { NumericFormat } from "react-number-format"; export default function CurrencyFormatter(props) { - return ( - - ); + return ( + + ); } diff --git a/client/src/utils/DateFormatter.jsx b/client/src/utils/DateFormatter.jsx index 516a39222..fe8315beb 100644 --- a/client/src/utils/DateFormatter.jsx +++ b/client/src/utils/DateFormatter.jsx @@ -1,42 +1,32 @@ -import {Tooltip} from "antd"; +import { Tooltip } from "antd"; import dayjs from "../utils/day"; import React from "react"; export function DateFormatter(props) { - return props.children - ? dayjs(props.children).format( - props.includeDay ? "ddd MM/DD/YYYY" : "MM/DD/YYYY" - ) - : null; + return props.children ? dayjs(props.children).format(props.includeDay ? "ddd MM/DD/YYYY" : "MM/DD/YYYY") : null; } export function DateTimeFormatter(props) { - return props.children - ? dayjs(props.children).format( - props.format ? props.format : "MM/DD/YYYY hh:mm a" - ) - : null; + return props.children ? dayjs(props.children).format(props.format ? props.format : "MM/DD/YYYY hh:mm a") : null; } export function DateTimeFormatterFunction(date) { - return dayjs(date).format("MM/DD/YYYY hh:mm a"); + return dayjs(date).format("MM/DD/YYYY hh:mm a"); } export function TimeFormatter(props) { - return props.children - ? dayjs(props.children).format(props.format ? props.format : "hh:mm a") - : null; + return props.children ? dayjs(props.children).format(props.format ? props.format : "hh:mm a") : null; } export function TimeAgoFormatter(props) { - const m = dayjs(props.children); - return props.children ? ( - - {m.fromNow()} - - ) : null; + const m = dayjs(props.children); + return props.children ? ( + + {m.fromNow()} + + ) : null; } export function DateTimeFormat(value) { - return dayjs(value).format("MM/DD/YYYY hh:mm A"); + return dayjs(value).format("MM/DD/YYYY hh:mm A"); } diff --git a/client/src/utils/DatePickerRanges.js b/client/src/utils/DatePickerRanges.js index dd4928fca..e8c671e1a 100644 --- a/client/src/utils/DatePickerRanges.js +++ b/client/src/utils/DatePickerRanges.js @@ -1,72 +1,57 @@ import dayjs from "./day"; const range = [ - { - label: 'Today', - value: [dayjs(), dayjs()] - }, - { - label: 'Last 14 days', - value: [dayjs().subtract(14, "day"), dayjs()] - }, - { - label: 'Last 7 days', - value: [dayjs().subtract(7, "day"), dayjs()] - }, - { - label: 'Next 7 days', - value: [dayjs(), dayjs().add(7, "day")] - }, - { - label: 'Next 14 days', - value: [dayjs(), dayjs().add(14, "day")], - }, - { - label: 'Last Month', - value: [ - dayjs().startOf("month").subtract(1, "month"), - dayjs().startOf("month").subtract(1, "month").endOf("month"), - ] - }, - { - label: 'This Month', - value: [dayjs().startOf("month"), dayjs().endOf("month")] - }, - { - label: 'Next Month', - value: [ - dayjs().startOf("month").add(1, "month"), - dayjs().startOf("month").add(1, "month").endOf("month"), - ] - }, - { - label: 'Last Quarter', - value: [ - dayjs().startOf("quarter").subtract(1, "quarter"), - dayjs().startOf("quarter").subtract(1, "day"), - ] - }, - { - label: 'This Quarter', - value: [ - dayjs().startOf("quarter"), - dayjs().startOf("quarter").add(1, "quarter").subtract(1, "day"), - ] - }, - { - label: 'Last 90 Days', - value: [dayjs().add(-90, "day"), dayjs()], - } + { + label: "Today", + value: [dayjs(), dayjs()] + }, + { + label: "Last 14 days", + value: [dayjs().subtract(14, "day"), dayjs()] + }, + { + label: "Last 7 days", + value: [dayjs().subtract(7, "day"), dayjs()] + }, + { + label: "Next 7 days", + value: [dayjs(), dayjs().add(7, "day")] + }, + { + label: "Next 14 days", + value: [dayjs(), dayjs().add(14, "day")] + }, + { + label: "Last Month", + value: [dayjs().startOf("month").subtract(1, "month"), dayjs().startOf("month").subtract(1, "month").endOf("month")] + }, + { + label: "This Month", + value: [dayjs().startOf("month"), dayjs().endOf("month")] + }, + { + label: "Next Month", + value: [dayjs().startOf("month").add(1, "month"), dayjs().startOf("month").add(1, "month").endOf("month")] + }, + { + label: "Last Quarter", + value: [dayjs().startOf("quarter").subtract(1, "quarter"), dayjs().startOf("quarter").subtract(1, "day")] + }, + { + label: "This Quarter", + value: [dayjs().startOf("quarter"), dayjs().startOf("quarter").add(1, "quarter").subtract(1, "day")] + }, + { + label: "Last 90 Days", + value: [dayjs().add(-90, "day"), dayjs()] + } ]; if (import.meta.env.DEV) { - range.push({ - label: 'Last Year', - value: [ - dayjs().subtract(1, "year"), - dayjs(), - ] - }) + range.push({ + label: "Last Year", + value: [dayjs().subtract(1, "year"), dayjs()] + }); } export default range; diff --git a/client/src/utils/GraphQLClient.js b/client/src/utils/GraphQLClient.js index bdc5f60d5..789bfdf10 100644 --- a/client/src/utils/GraphQLClient.js +++ b/client/src/utils/GraphQLClient.js @@ -1,19 +1,19 @@ -import {ApolloClient, ApolloLink, InMemoryCache, split} from "@apollo/client"; -import {setContext} from "@apollo/client/link/context"; -import {HttpLink} from "@apollo/client/link/http"; //"apollo-link-http"; -import {RetryLink} from "@apollo/client/link/retry"; -import {WebSocketLink} from "@apollo/client/link/ws"; -import {getMainDefinition, offsetLimitPagination,} from "@apollo/client/utilities"; +import { ApolloClient, ApolloLink, InMemoryCache, split } from "@apollo/client"; +import { setContext } from "@apollo/client/link/context"; +import { HttpLink } from "@apollo/client/link/http"; //"apollo-link-http"; +import { RetryLink } from "@apollo/client/link/retry"; +import { WebSocketLink } from "@apollo/client/link/ws"; +import { getMainDefinition, offsetLimitPagination } from "@apollo/client/utilities"; //import { split } from "apollo-link"; import apolloLogger from "apollo-link-logger"; //import axios from "axios"; -import {auth} from "../firebase/firebase.utils"; +import { auth } from "../firebase/firebase.utils"; import errorLink from "../graphql/apollo-error-handling"; -import {SentryLink} from "apollo-link-sentry"; +import { SentryLink } from "apollo-link-sentry"; //import { store } from "../redux/store"; const httpLink = new HttpLink({ - uri: import.meta.env.VITE_APP_GRAPHQL_ENDPOINT, + uri: import.meta.env.VITE_APP_GRAPHQL_ENDPOINT }); const wsLink = new WebSocketLink({ @@ -26,121 +26,112 @@ const wsLink = new WebSocketLink({ if (token) { return { headers: { - authorization: token ? `Bearer ${token}` : "", - }, + authorization: token ? `Bearer ${token}` : "" + } }; } - }, - }, + } + } }); const roundTripLink = new ApolloLink((operation, forward) => { - // Called before operation is sent to server - operation.setContext({start: new Date()}); + // Called before operation is sent to server + operation.setContext({ start: new Date() }); - return forward(operation).map((data) => { - // Called after server responds - const time = new Date() - operation.getContext().start; - // console.log( - // `Operation ${operation.operationName} took ${time} to complete` - // ); - TrackExecutionTime(operation.operationName, time); - return data; - }); + return forward(operation).map((data) => { + // Called after server responds + const time = new Date() - operation.getContext().start; + // console.log( + // `Operation ${operation.operationName} took ${time} to complete` + // ); + TrackExecutionTime(operation.operationName, time); + return data; + }); }); const TrackExecutionTime = async (operationName, time) => { - // if (process.env.NODE_ENV === "development") return; - // const rdxStore = store.getState(); - // try { - // axios.post("/ioevent", { - // operationName, - // time, - // dbevent: true, - // user: - // rdxStore.user && - // rdxStore.user.currentUser && - // rdxStore.user.currentUser.email, - // imexshopid: - // rdxStore.user && - // rdxStore.user.bodyshop && - // rdxStore.user.bodyshop.imexshopid, - // }); - // } catch (error) { - // console.log("IOEvent Error", error); - // } + // if (process.env.NODE_ENV === "development") return; + // const rdxStore = store.getState(); + // try { + // axios.post("/ioevent", { + // operationName, + // time, + // dbevent: true, + // user: + // rdxStore.user && + // rdxStore.user.currentUser && + // rdxStore.user.currentUser.email, + // imexshopid: + // rdxStore.user && + // rdxStore.user.bodyshop && + // rdxStore.user.bodyshop.imexshopid, + // }); + // } catch (error) { + // console.log("IOEvent Error", error); + // } }; const subscriptionMiddleware = { - applyMiddleware: async (options, next) => { - options.authToken = - auth.currentUser && (await auth.currentUser.getIdToken()); - next(); - }, + applyMiddleware: async (options, next) => { + options.authToken = auth.currentUser && (await auth.currentUser.getIdToken()); + next(); + } }; wsLink.subscriptionClient.use([subscriptionMiddleware]); const link = split( - // split based on operation type - ({query}) => { - const definition = getMainDefinition(query); - // console.log( - // "##Intercepted GQL Transaction : " + - // definition.operation + - // "|" + - // definition.name.value + - // "##", - // query - // ); - return ( - definition.kind === "OperationDefinition" && - definition.operation === "subscription" - ); - }, - wsLink, - httpLink + // split based on operation type + ({ query }) => { + const definition = getMainDefinition(query); + // console.log( + // "##Intercepted GQL Transaction : " + + // definition.operation + + // "|" + + // definition.name.value + + // "##", + // query + // ); + return definition.kind === "OperationDefinition" && definition.operation === "subscription"; + }, + wsLink, + httpLink ); -const authLink = setContext((_, {headers}) => { - return ( - auth.currentUser && - auth.currentUser - .getIdToken() - .then((token) => { - if (token) { - return { - headers: { - ...headers, - authorization: token ? `Bearer ${token}` : "", - }, - }; - } else { - console.error( - "Authentication error. Unable to add authorization token because it was empty." - ); - return {headers}; - } - }) - .catch((error) => { - console.error( - "Authentication error. Unable to add authorization token.", - error.message - ); - return {headers}; - }) - ); +const authLink = setContext((_, { headers }) => { + return ( + auth.currentUser && + auth.currentUser + .getIdToken() + .then((token) => { + if (token) { + return { + headers: { + ...headers, + authorization: token ? `Bearer ${token}` : "" + } + }; + } else { + console.error("Authentication error. Unable to add authorization token because it was empty."); + return { headers }; + } + }) + .catch((error) => { + console.error("Authentication error. Unable to add authorization token.", error.message); + return { headers }; + }) + ); }); const retryLink = new RetryLink({ - delay: { - initial: 500, - max: 5, - jitter: true, - }, - attempts: { - max: 5, - retryIf: (error, _operation) => !!error, - }, + delay: { + initial: 500, + max: 5, + jitter: true + }, + attempts: { + max: 5, + retryIf: (error, _operation) => !!error + } }); const middlewares = []; @@ -149,21 +140,17 @@ if (import.meta.env.DEV) { } middlewares.push( - new SentryLink().concat( - roundTripLink.concat( - retryLink.concat(errorLink.concat(authLink.concat(link))) - ) - ) + new SentryLink().concat(roundTripLink.concat(retryLink.concat(errorLink.concat(authLink.concat(link))))) ); const cache = new InMemoryCache({ - typePolicies: { - Query: { - fields: { - conversations: offsetLimitPagination(), - }, - }, - }, + typePolicies: { + Query: { + fields: { + conversations: offsetLimitPagination() + } + } + } }); const client = new ApolloClient({ @@ -174,15 +161,15 @@ const client = new ApolloClient({ watchQuery: { fetchPolicy: "network-only", nextFetchPolicy: "network-only", - errorPolicy: "ignore", + errorPolicy: "ignore" }, query: { fetchPolicy: "network-only", - errorPolicy: "all", + errorPolicy: "all" }, mutate: { - errorPolicy: "all", - }, - }, + errorPolicy: "all" + } + } }); export default client; diff --git a/client/src/utils/PhoneFormatter.jsx b/client/src/utils/PhoneFormatter.jsx index eaedf77b7..c861951e6 100644 --- a/client/src/utils/PhoneFormatter.jsx +++ b/client/src/utils/PhoneFormatter.jsx @@ -3,6 +3,6 @@ import parsePhoneNumber from "libphonenumber-js"; import React from "react"; export default function PhoneNumberFormatter(props) { - const p = parsePhoneNumber(props.children || "", "CA"); - return p ? {p.formatNational()} : null; + const p = parsePhoneNumber(props.children || "", "CA"); + return p ? {p.formatNational()} : null; } diff --git a/client/src/utils/RegisterSw.js b/client/src/utils/RegisterSw.js index 2d18ec170..3f0c5712c 100644 --- a/client/src/utils/RegisterSw.js +++ b/client/src/utils/RegisterSw.js @@ -1,54 +1,69 @@ -import {AlertOutlined} from "@ant-design/icons"; -import {Button, notification, Space} from "antd"; +import { AlertOutlined } from "@ant-design/icons"; +import { Button, notification, Space } from "antd"; import i18n from "i18next"; import React from "react"; import * as serviceWorkerRegistration from "../serviceWorkerRegistration"; -import {store} from "../redux/store"; +import { store } from "../redux/store"; import InstanceRenderManager from "./instanceRenderMgr"; const onServiceWorkerUpdate = (registration) => { - console.log("onServiceWorkerUpdate", registration); + console.log("onServiceWorkerUpdate", registration); - const btn = ( - - - - - ); + const btn = ( + + + + + ); - store.dispatch() + store.dispatch(); - notification.open({ - icon: , - message: i18n.t("general.messages.newversiontitle",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})}), - description: i18n.t("general.messages.newversionmessage", {app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})}), - duration: 0, - btn, - key: "updateavailable", - }); + notification.open({ + icon: , + message: i18n.t("general.messages.newversiontitle", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }), + description: i18n.t("general.messages.newversionmessage", { + app: InstanceRenderManager({ + imex: "$t(titles.imexonline)", + rome: "$t(titles.romeonline)", + promanager: "$t(titles.promanager)" + }) + }), + duration: 0, + btn, + key: "updateavailable" + }); }; -serviceWorkerRegistration.register({onUpdate: onServiceWorkerUpdate}); +serviceWorkerRegistration.register({ onUpdate: onServiceWorkerUpdate }); diff --git a/client/src/utils/RenderTemplate.js b/client/src/utils/RenderTemplate.js index c878246f1..8fb1480cb 100644 --- a/client/src/utils/RenderTemplate.js +++ b/client/src/utils/RenderTemplate.js @@ -1,437 +1,397 @@ -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; import jsreport from "@jsreport/browser-client"; -import {notification} from "antd"; +import { notification } from "antd"; import axios from "axios"; import _ from "lodash"; -import {auth} from "../firebase/firebase.utils"; -import {setEmailOptions} from "../redux/email/email.actions"; -import {store} from "../redux/store"; +import { auth } from "../firebase/firebase.utils"; +import { setEmailOptions } from "../redux/email/email.actions"; +import { store } from "../redux/store"; import client from "../utils/GraphQLClient"; import cleanAxios from "./CleanAxios"; import { TemplateList } from "./TemplateConstants"; -import {generateTemplate} from "./graphQLmodifier"; +import { generateTemplate } from "./graphQLmodifier"; const server = import.meta.env.VITE_APP_REPORTS_SERVER_URL; jsreport.serverUrl = server; let Templates; -export function GenerateTemplates(){ +export function GenerateTemplates() { //Required as a part of the transition to Vite. - //Previous method had the template hash generating before translations loaded, resulting in empty files. - Templates = TemplateList() + //Previous method had the template hash generating before translations loaded, resulting in empty files. + Templates = TemplateList(); } export default async function RenderTemplate( - templateObject, - bodyshop, - renderAsHtml = false, - renderAsExcel = false, - renderAsText = false + templateObject, + bodyshop, + renderAsHtml = false, + renderAsExcel = false, + renderAsText = false ) { - if (window.jsr3) { - jsreport.serverUrl = "https://reports3.test.imex.online/"; - } - const jsrAuth = (await axios.post("/utils/jsr")).data; + if (window.jsr3) { + jsreport.serverUrl = "https://reports3.test.imex.online/"; + } + const jsrAuth = (await axios.post("/utils/jsr")).data; - jsreport.headers["Authorization"] = jsrAuth; + jsreport.headers["Authorization"] = jsrAuth; - //Query assets that match the template name. Must be in format <>.query - let {contextData, useShopSpecificTemplate} = await fetchContextData( - templateObject, - jsrAuth - ); + //Query assets that match the template name. Must be in format <>.query + let { contextData, useShopSpecificTemplate } = await fetchContextData(templateObject, jsrAuth); - const {ignoreCustomMargins} = Templates[templateObject.name]; + const { ignoreCustomMargins } = Templates[templateObject.name]; - let reportRequest = { - template: { - name: useShopSpecificTemplate - ? `/${bodyshop.imexshopid}/${templateObject.name}` - : `/${templateObject.name}`, - ...(renderAsHtml - ? {} - : { - recipe: "chrome-pdf", - ...(!ignoreCustomMargins && { - chrome: { - marginTop: - bodyshop.logo_img_path && - bodyshop.logo_img_path.headerMargin && - bodyshop.logo_img_path.headerMargin > 36 - ? bodyshop.logo_img_path.headerMargin - : "36px", - marginBottom: - bodyshop.logo_img_path && - bodyshop.logo_img_path.footerMargin && - bodyshop.logo_img_path.footerMargin > 50 - ? bodyshop.logo_img_path.footerMargin - : "50px", - }, - }), - }), - ...(renderAsExcel ? {recipe: "html-to-xlsx"} : {}), - ...(renderAsText ? {recipe: "text"} : {}), - }, - data: { - ...contextData, - ...templateObject.variables, - ...templateObject.context, - headerpath: `/${bodyshop.imexshopid}/header.html`, - footerpath: `/${bodyshop.imexshopid}/footer.html`, - bodyshop: bodyshop, - filters: templateObject?.filters, - sorters: templateObject?.sorters, - offset: bodyshop.timezone, //dayjs().utcOffset(), - defaultSorters: templateObject?.defaultSorters, - }, - }; - - try { - const render = await jsreport.render(reportRequest); - - if (!renderAsHtml) { - render.download( - (Templates[templateObject.name] && - Templates[templateObject.name].title) || - "" - ); - } else { - let pdf; - if (bodyshop.attach_pdf_to_email) { - const pdfRequest = _.cloneDeep(reportRequest); //Updates to spread in the header details. - pdfRequest.template = { - ...pdfRequest.template, - ...{ - recipe: "chrome-pdf", - ...(!ignoreCustomMargins && { - chrome: { - marginTop: - bodyshop.logo_img_path && - bodyshop.logo_img_path.headerMargin && - bodyshop.logo_img_path.headerMargin > 36 - ? bodyshop.logo_img_path.headerMargin - : "36px", - marginBottom: - bodyshop.logo_img_path && - bodyshop.logo_img_path.footerMargin && - bodyshop.logo_img_path.footerMargin > 50 - ? bodyshop.logo_img_path.footerMargin - : "50px", - }, - }), - }, - }; - - const pdfRender = await jsreport.render(pdfRequest); - pdf = await pdfRender.toDataURI(); - } - const html = await render.toString(); - return new Promise((resolve, reject) => { - resolve({ - pdf, - filename: - Templates[templateObject.name] && - Templates[templateObject.name].title, - html, - }); - }); - } - } catch (error) { - notification["error"]({message: JSON.stringify(error)}); - } -} - -export async function RenderTemplates( - templateObjects, - bodyshop, - renderAsHtml = false -) { - //Query assets that match the template name. Must be in format <>.query - let unsortedTemplatesAndData = []; - let proms = []; - const jsrAuth = (await axios.post("/utils/jsr")).data; - jsreport.headers["Authorization"] = jsrAuth; - - templateObjects.forEach((template) => { - proms.push( - (async () => { - let {contextData, useShopSpecificTemplate} = await fetchContextData( - template, - jsrAuth - ); - unsortedTemplatesAndData.push({ - templateObject: template, - contextData, - useShopSpecificTemplate, - }); - })() - ); - }); - await Promise.all(proms); - - //Re-order list to follow speedprint. - // var templateAndData = _.sortBy(unsortedTemplatesAndData, function (item) { - // return templateObjects.findIndex( - // (template) => template.name === item.templateObject.name - // ); - // }); - if (window.jsr3) { - jsreport.serverUrl = "https://reports3.test.imex.online/"; - } - - unsortedTemplatesAndData.sort(function (a, b) { - return ( - templateObjects.findIndex((x) => x.name === a.templateObject.name) - - templateObjects.findIndex((x) => x.name === b.templateObject.name) - ); - }); - const templateAndData = unsortedTemplatesAndData; - - let rootTemplate = templateAndData.shift(); - - let reportRequest = { - template: { - name: rootTemplate.useShopSpecificTemplate - ? `/${bodyshop.imexshopid}/${rootTemplate.templateObject.name}` - : `/${rootTemplate.templateObject.name}`, - ...(renderAsHtml - ? {} - : { - recipe: "chrome-pdf", - chrome: { - marginTop: - bodyshop.logo_img_path && - bodyshop.logo_img_path.headerMargin && - bodyshop.logo_img_path.headerMargin > 36 - ? bodyshop.logo_img_path.headerMargin - : "36px", - marginBottom: - bodyshop.logo_img_path && - bodyshop.logo_img_path.footerMargin && - bodyshop.logo_img_path.footerMargin > 50 - ? bodyshop.logo_img_path.footerMargin - : "50px", - }, - }), - pdfOperations: [ - { - template: { - name: "/components/Header-Footer", - recipe: "chrome-pdf", - engine: "handlebars", - }, - type: "merge", - }, - ...templateAndData.map((template) => { - return { - template: { - chrome: { - marginTop: - bodyshop.logo_img_path && - bodyshop.logo_img_path.headerMargin && - bodyshop.logo_img_path.headerMargin > 36 - ? bodyshop.logo_img_path.headerMargin - : "36px", - marginBottom: - bodyshop.logo_img_path && - bodyshop.logo_img_path.footerMargin && - bodyshop.logo_img_path.footerMargin > 50 - ? bodyshop.logo_img_path.footerMargin - : "50px", - }, - name: template.useShopSpecificTemplate - ? `/${bodyshop.imexshopid}/${template.templateObject.name}` - : `/${template.templateObject.name}`, - ...(renderAsHtml ? {} : {recipe: "chrome-pdf"}), - }, - type: "append", - - // mergeWholeDocument: true, - // renderForEveryPage: true, - }; - }), - ], - }, - data: { - ...extend( - rootTemplate.contextData, - ...templateAndData.map((temp) => temp.contextData) - ), - - // ...rootTemplate.templateObject.variables, - // ...rootTemplate.templateObject.context, - headerpath: `/${bodyshop.imexshopid}/header.html`, - footerpath: `/${bodyshop.imexshopid}/footer.html`, - bodyshop: bodyshop, - offset: bodyshop.timezone, - }, - }; - - try { - const render = await jsreport.render(reportRequest); - if (!renderAsHtml) { - render.download("Speed Print"); - } else { - return render.toString(); - } - } catch (error) { - notification["error"]({message: JSON.stringify(error)}); - } -} - -export const GenerateDocument = async ( - template, - messageOptions, - sendType, - jobid -) => { - const bodyshop = store.getState().user.bodyshop; - if (sendType === "e") { - store.dispatch( - setEmailOptions({ - jobid, - messageOptions: { - ...messageOptions, - to: Array.isArray(messageOptions.to) - ? messageOptions.to - : [messageOptions.to], - }, - template, + let reportRequest = { + template: { + name: useShopSpecificTemplate ? `/${bodyshop.imexshopid}/${templateObject.name}` : `/${templateObject.name}`, + ...(renderAsHtml + ? {} + : { + recipe: "chrome-pdf", + ...(!ignoreCustomMargins && { + chrome: { + marginTop: + bodyshop.logo_img_path && + bodyshop.logo_img_path.headerMargin && + bodyshop.logo_img_path.headerMargin > 36 + ? bodyshop.logo_img_path.headerMargin + : "36px", + marginBottom: + bodyshop.logo_img_path && + bodyshop.logo_img_path.footerMargin && + bodyshop.logo_img_path.footerMargin > 50 + ? bodyshop.logo_img_path.footerMargin + : "50px" + } }) - ); - } else if (sendType === "x") { - - await RenderTemplate(template, bodyshop, false, true); - } else if (sendType === "text") { - await RenderTemplate(template, bodyshop, false, false, true); - } else { - await RenderTemplate(template, bodyshop); + }), + ...(renderAsExcel ? { recipe: "html-to-xlsx" } : {}), + ...(renderAsText ? { recipe: "text" } : {}) + }, + data: { + ...contextData, + ...templateObject.variables, + ...templateObject.context, + headerpath: `/${bodyshop.imexshopid}/header.html`, + footerpath: `/${bodyshop.imexshopid}/footer.html`, + bodyshop: bodyshop, + filters: templateObject?.filters, + sorters: templateObject?.sorters, + offset: bodyshop.timezone, //dayjs().utcOffset(), + defaultSorters: templateObject?.defaultSorters } + }; + + try { + const render = await jsreport.render(reportRequest); + + if (!renderAsHtml) { + render.download((Templates[templateObject.name] && Templates[templateObject.name].title) || ""); + } else { + let pdf; + if (bodyshop.attach_pdf_to_email) { + const pdfRequest = _.cloneDeep(reportRequest); //Updates to spread in the header details. + pdfRequest.template = { + ...pdfRequest.template, + ...{ + recipe: "chrome-pdf", + ...(!ignoreCustomMargins && { + chrome: { + marginTop: + bodyshop.logo_img_path && + bodyshop.logo_img_path.headerMargin && + bodyshop.logo_img_path.headerMargin > 36 + ? bodyshop.logo_img_path.headerMargin + : "36px", + marginBottom: + bodyshop.logo_img_path && + bodyshop.logo_img_path.footerMargin && + bodyshop.logo_img_path.footerMargin > 50 + ? bodyshop.logo_img_path.footerMargin + : "50px" + } + }) + } + }; + + const pdfRender = await jsreport.render(pdfRequest); + pdf = await pdfRender.toDataURI(); + } + const html = await render.toString(); + return new Promise((resolve, reject) => { + resolve({ + pdf, + filename: Templates[templateObject.name] && Templates[templateObject.name].title, + html + }); + }); + } + } catch (error) { + notification["error"]({ message: JSON.stringify(error) }); + } +} + +export async function RenderTemplates(templateObjects, bodyshop, renderAsHtml = false) { + //Query assets that match the template name. Must be in format <>.query + let unsortedTemplatesAndData = []; + let proms = []; + const jsrAuth = (await axios.post("/utils/jsr")).data; + jsreport.headers["Authorization"] = jsrAuth; + + templateObjects.forEach((template) => { + proms.push( + (async () => { + let { contextData, useShopSpecificTemplate } = await fetchContextData(template, jsrAuth); + unsortedTemplatesAndData.push({ + templateObject: template, + contextData, + useShopSpecificTemplate + }); + })() + ); + }); + await Promise.all(proms); + + //Re-order list to follow speedprint. + // var templateAndData = _.sortBy(unsortedTemplatesAndData, function (item) { + // return templateObjects.findIndex( + // (template) => template.name === item.templateObject.name + // ); + // }); + if (window.jsr3) { + jsreport.serverUrl = "https://reports3.test.imex.online/"; + } + + unsortedTemplatesAndData.sort(function (a, b) { + return ( + templateObjects.findIndex((x) => x.name === a.templateObject.name) - + templateObjects.findIndex((x) => x.name === b.templateObject.name) + ); + }); + const templateAndData = unsortedTemplatesAndData; + + let rootTemplate = templateAndData.shift(); + + let reportRequest = { + template: { + name: rootTemplate.useShopSpecificTemplate + ? `/${bodyshop.imexshopid}/${rootTemplate.templateObject.name}` + : `/${rootTemplate.templateObject.name}`, + ...(renderAsHtml + ? {} + : { + recipe: "chrome-pdf", + chrome: { + marginTop: + bodyshop.logo_img_path && + bodyshop.logo_img_path.headerMargin && + bodyshop.logo_img_path.headerMargin > 36 + ? bodyshop.logo_img_path.headerMargin + : "36px", + marginBottom: + bodyshop.logo_img_path && + bodyshop.logo_img_path.footerMargin && + bodyshop.logo_img_path.footerMargin > 50 + ? bodyshop.logo_img_path.footerMargin + : "50px" + } + }), + pdfOperations: [ + { + template: { + name: "/components/Header-Footer", + recipe: "chrome-pdf", + engine: "handlebars" + }, + type: "merge" + }, + ...templateAndData.map((template) => { + return { + template: { + chrome: { + marginTop: + bodyshop.logo_img_path && + bodyshop.logo_img_path.headerMargin && + bodyshop.logo_img_path.headerMargin > 36 + ? bodyshop.logo_img_path.headerMargin + : "36px", + marginBottom: + bodyshop.logo_img_path && + bodyshop.logo_img_path.footerMargin && + bodyshop.logo_img_path.footerMargin > 50 + ? bodyshop.logo_img_path.footerMargin + : "50px" + }, + name: template.useShopSpecificTemplate + ? `/${bodyshop.imexshopid}/${template.templateObject.name}` + : `/${template.templateObject.name}`, + ...(renderAsHtml ? {} : { recipe: "chrome-pdf" }) + }, + type: "append" + + // mergeWholeDocument: true, + // renderForEveryPage: true, + }; + }) + ] + }, + data: { + ...extend(rootTemplate.contextData, ...templateAndData.map((temp) => temp.contextData)), + + // ...rootTemplate.templateObject.variables, + // ...rootTemplate.templateObject.context, + headerpath: `/${bodyshop.imexshopid}/header.html`, + footerpath: `/${bodyshop.imexshopid}/footer.html`, + bodyshop: bodyshop, + offset: bodyshop.timezone + } + }; + + try { + const render = await jsreport.render(reportRequest); + if (!renderAsHtml) { + render.download("Speed Print"); + } else { + return render.toString(); + } + } catch (error) { + notification["error"]({ message: JSON.stringify(error) }); + } +} + +export const GenerateDocument = async (template, messageOptions, sendType, jobid) => { + const bodyshop = store.getState().user.bodyshop; + if (sendType === "e") { + store.dispatch( + setEmailOptions({ + jobid, + messageOptions: { + ...messageOptions, + to: Array.isArray(messageOptions.to) ? messageOptions.to : [messageOptions.to] + }, + template + }) + ); + } else if (sendType === "x") { + await RenderTemplate(template, bodyshop, false, true); + } else if (sendType === "text") { + await RenderTemplate(template, bodyshop, false, false, true); + } else { + await RenderTemplate(template, bodyshop); + } }; export const GenerateDocuments = async (templates) => { - const bodyshop = store.getState().user.bodyshop; - await RenderTemplates(templates, bodyshop); + const bodyshop = store.getState().user.bodyshop; + await RenderTemplates(templates, bodyshop); }; -export const fetchFilterData = async ({name}) => { - try { - const bodyshop = store.getState().user.bodyshop; - const jsrAuth = (await axios.post("/utils/jsr")).data; - jsreport.headers["FirebaseAuthorization"] = - "Bearer " + (await auth.currentUser.getIdToken()); - - const folders = await cleanAxios.get(`${server}/odata/folders`, { - headers: {Authorization: jsrAuth}, - }); - const shopSpecificFolder = folders.data.value.find( - (f) => f.name === bodyshop.imexshopid - ); - - const jsReportFilters = await cleanAxios.get( - `${server}/odata/assets?$filter=name eq '${name}.filters'`, - {headers: {Authorization: jsrAuth}} - ); - console.log("🚀 ~ fetchFilterData ~ jsReportFilters:", jsReportFilters); - - let parsedFilterData; - let useShopSpecificTemplate = false; - // let shopSpecificTemplate; - - if (shopSpecificFolder) { - let shopSpecificTemplate = jsReportFilters.data.value.find( - (f) => f?.folder?.shortid === shopSpecificFolder.shortid - ); - if (shopSpecificTemplate) { - useShopSpecificTemplate = true; - parsedFilterData = atob(shopSpecificTemplate.content); - } - } - - if (!parsedFilterData) { - const generalTemplate = jsReportFilters.data.value.find((f) => !f.folder); - useShopSpecificTemplate = false; - if (generalTemplate) parsedFilterData = atob(generalTemplate.content); - } - const data = JSON.parse(parsedFilterData); - return { - data, - useShopSpecificTemplate, - success: true, - } - } catch { - return { - success: false, - } - } -}; - -const fetchContextData = async (templateObject, jsrAuth) => { +export const fetchFilterData = async ({ name }) => { + try { const bodyshop = store.getState().user.bodyshop; - - jsreport.headers["FirebaseAuthorization"] = - "Bearer " + (await auth.currentUser.getIdToken()); + const jsrAuth = (await axios.post("/utils/jsr")).data; + jsreport.headers["FirebaseAuthorization"] = "Bearer " + (await auth.currentUser.getIdToken()); const folders = await cleanAxios.get(`${server}/odata/folders`, { - headers: {Authorization: jsrAuth}, + headers: { Authorization: jsrAuth } }); - const shopSpecificFolder = folders.data.value.find( - (f) => f.name === bodyshop.imexshopid - ); + const shopSpecificFolder = folders.data.value.find((f) => f.name === bodyshop.imexshopid); - const jsReportQueries = await cleanAxios.get( - `${server}/odata/assets?$filter=name eq '${templateObject.name}.query'`, - {headers: {Authorization: jsrAuth}} - ); + const jsReportFilters = await cleanAxios.get(`${server}/odata/assets?$filter=name eq '${name}.filters'`, { + headers: { Authorization: jsrAuth } + }); + console.log("🚀 ~ fetchFilterData ~ jsReportFilters:", jsReportFilters); - let templateQueryToExecute; + let parsedFilterData; let useShopSpecificTemplate = false; // let shopSpecificTemplate; if (shopSpecificFolder) { - let shopSpecificTemplate = jsReportQueries.data.value.find( - (f) => f?.folder?.shortid === shopSpecificFolder.shortid - ); - if (shopSpecificTemplate) { - useShopSpecificTemplate = true; - templateQueryToExecute = atob(shopSpecificTemplate.content); - } + let shopSpecificTemplate = jsReportFilters.data.value.find( + (f) => f?.folder?.shortid === shopSpecificFolder.shortid + ); + if (shopSpecificTemplate) { + useShopSpecificTemplate = true; + parsedFilterData = atob(shopSpecificTemplate.content); + } } - if (!templateQueryToExecute) { - const generalTemplate = jsReportQueries.data.value.find((f) => !f.folder); - useShopSpecificTemplate = false; - templateQueryToExecute = atob(generalTemplate.content); + if (!parsedFilterData) { + const generalTemplate = jsReportFilters.data.value.find((f) => !f.folder); + useShopSpecificTemplate = false; + if (generalTemplate) parsedFilterData = atob(generalTemplate.content); } + const data = JSON.parse(parsedFilterData); + return { + data, + useShopSpecificTemplate, + success: true + }; + } catch { + return { + success: false + }; + } +}; - // Commented out for future revision debugging - // console.log('Template Object'); - // console.dir(templateObject); - // console.log('Unmodified Query'); - // console.dir(templateQueryToExecute); +const fetchContextData = async (templateObject, jsrAuth) => { + const bodyshop = store.getState().user.bodyshop; - const hasFilters = templateObject?.filters?.length > 0; - const hasSorters = templateObject?.sorters?.length > 0; - const hasDefaultSorters = templateObject?.defaultSorters?.length > 0; + jsreport.headers["FirebaseAuthorization"] = "Bearer " + (await auth.currentUser.getIdToken()); - // We have no template filters or sorters, so we can just execute the query and return the data - if (!hasFilters && !hasSorters && !hasDefaultSorters) { - let contextData = {}; - if (templateQueryToExecute) { - const {data} = await client.query({ - query: gql(templateQueryToExecute), - variables: {...templateObject.variables}, - }); - contextData = data; - } - return {contextData, useShopSpecificTemplate}; - } + const folders = await cleanAxios.get(`${server}/odata/folders`, { + headers: { Authorization: jsrAuth } + }); + const shopSpecificFolder = folders.data.value.find((f) => f.name === bodyshop.imexshopid); - return await generateTemplate( - templateQueryToExecute, - templateObject, - useShopSpecificTemplate + const jsReportQueries = await cleanAxios.get( + `${server}/odata/assets?$filter=name eq '${templateObject.name}.query'`, + { headers: { Authorization: jsrAuth } } + ); + + let templateQueryToExecute; + let useShopSpecificTemplate = false; + // let shopSpecificTemplate; + + if (shopSpecificFolder) { + let shopSpecificTemplate = jsReportQueries.data.value.find( + (f) => f?.folder?.shortid === shopSpecificFolder.shortid ); + if (shopSpecificTemplate) { + useShopSpecificTemplate = true; + templateQueryToExecute = atob(shopSpecificTemplate.content); + } + } + + if (!templateQueryToExecute) { + const generalTemplate = jsReportQueries.data.value.find((f) => !f.folder); + useShopSpecificTemplate = false; + templateQueryToExecute = atob(generalTemplate.content); + } + + // Commented out for future revision debugging + // console.log('Template Object'); + // console.dir(templateObject); + // console.log('Unmodified Query'); + // console.dir(templateQueryToExecute); + + const hasFilters = templateObject?.filters?.length > 0; + const hasSorters = templateObject?.sorters?.length > 0; + const hasDefaultSorters = templateObject?.defaultSorters?.length > 0; + + // We have no template filters or sorters, so we can just execute the query and return the data + if (!hasFilters && !hasSorters && !hasDefaultSorters) { + let contextData = {}; + if (templateQueryToExecute) { + const { data } = await client.query({ + query: gql(templateQueryToExecute), + variables: { ...templateObject.variables } + }); + contextData = data; + } + return { contextData, useShopSpecificTemplate }; + } + + return await generateTemplate(templateQueryToExecute, templateObject, useShopSpecificTemplate); }; //export const displayTemplateInWindow = (html) => { @@ -467,21 +427,21 @@ const fetchContextData = async (templateObject, jsrAuth) => { // }; function extend(o1, o2, o3) { - var result = {}, - obj; + var result = {}, + obj; - for (var i = 0; i < arguments.length; i++) { - obj = arguments[i]; - for (var key in obj) { - if (Object.prototype.toString.call(obj[key]) === "[object Object]") { - if (typeof result[key] === "undefined") { - result[key] = {}; - } - result[key] = extend(result[key], obj[key]); - } else { - result[key] = obj[key]; - } + for (var i = 0; i < arguments.length; i++) { + obj = arguments[i]; + for (var key in obj) { + if (Object.prototype.toString.call(obj[key]) === "[object Object]") { + if (typeof result[key] === "undefined") { + result[key] = {}; } + result[key] = extend(result[key], obj[key]); + } else { + result[key] = obj[key]; + } } - return result; + } + return result; } diff --git a/client/src/utils/SSSUtils.js b/client/src/utils/SSSUtils.js index 2f1a10c84..ca53e0681 100644 --- a/client/src/utils/SSSUtils.js +++ b/client/src/utils/SSSUtils.js @@ -1,45 +1,36 @@ import _ from "lodash"; export const CheckJobBucket = (buckets, job) => { - const jobHours = - job.labhrs.aggregate.sum.mod_lb_hrs + job.larhrs.aggregate.sum.mod_lb_hrs; + const jobHours = job.labhrs.aggregate.sum.mod_lb_hrs + job.larhrs.aggregate.sum.mod_lb_hrs; - const matchingBucket = buckets.filter((b) => - b.gte <= jobHours && b.lt ? b.lt > jobHours : true - ); + const matchingBucket = buckets.filter((b) => (b.gte <= jobHours && b.lt ? b.lt > jobHours : true)); - return matchingBucket[0] && matchingBucket[0].id; + return matchingBucket[0] && matchingBucket[0].id; }; export const CalculateLoad = (currentLoad, buckets, jobsIn, jobsOut) => { - //Add the jobs coming - const newLoad = _.cloneDeep(currentLoad); - jobsIn.forEach((job) => { - const bucketId = CheckJobBucket(buckets, job); - if (bucketId) { - newLoad[bucketId].count = newLoad[bucketId].count + 1; - } else { - console.log( - "[Util Arr Job]Uh oh, this job doesn't fit in a bucket!", - job - ); - } - }); + //Add the jobs coming + const newLoad = _.cloneDeep(currentLoad); + jobsIn.forEach((job) => { + const bucketId = CheckJobBucket(buckets, job); + if (bucketId) { + newLoad[bucketId].count = newLoad[bucketId].count + 1; + } else { + console.log("[Util Arr Job]Uh oh, this job doesn't fit in a bucket!", job); + } + }); - jobsOut.forEach((job) => { - const bucketId = CheckJobBucket(buckets, job); - if (bucketId) { - newLoad[bucketId].count = newLoad[bucketId].count - 1; - if (newLoad[bucketId].count < 0) { - console.log("***ERROR: NEGATIVE LOAD", bucketId, job); - } - } else { - console.log( - "[Util Out Job]Uh oh, this job doesn't fit in a bucket!", - job - ); - } - }); + jobsOut.forEach((job) => { + const bucketId = CheckJobBucket(buckets, job); + if (bucketId) { + newLoad[bucketId].count = newLoad[bucketId].count - 1; + if (newLoad[bucketId].count < 0) { + console.log("***ERROR: NEGATIVE LOAD", bucketId, job); + } + } else { + console.log("[Util Out Job]Uh oh, this job doesn't fit in a bucket!", job); + } + }); - return newLoad; + return newLoad; }; diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js index 69fe15fe3..40d9ad6b7 100644 --- a/client/src/utils/TemplateConstants.js +++ b/client/src/utils/TemplateConstants.js @@ -1,2483 +1,2294 @@ import i18n from "i18next"; //import { store } from "../redux/store"; -import InstanceRenderManager from './instanceRenderMgr' +import InstanceRenderManager from "./instanceRenderMgr"; export const EmailSettings = { - fromNameDefault: InstanceRenderManager({ - imex: "ImEX Online", - rome:"Rome Online", - promanager: "ProManager" - }) , - fromAddress: InstanceRenderManager({imex: "noreply@imex.online" ,rome:"noreply@romeonline.io", promanager: "noreply@promanager.web-est.com"}) , + fromNameDefault: InstanceRenderManager({ + imex: "ImEX Online", + rome: "Rome Online", + promanager: "ProManager" + }), + fromAddress: InstanceRenderManager({ + imex: "noreply@imex.online", + rome: "noreply@romeonline.io", + promanager: "noreply@promanager.web-est.com" + }) }; export const TemplateList = (type, context) => { - //const { bodyshop } = store.getState().user; - return { - //If there's no type or the type is job, send it back. - ...(!type || type === "job" - ? { - casl_authorization: { - title: i18n.t("printcenter.jobs.casl_authorization"), - description: "", - subject: i18n.t("printcenter.jobs.casl_authorization"), - key: "casl_authorization", - disabled: false, - group: "authorization", - regions: { - CA: true, - }, - }, - fippa_authorization: { - title: i18n.t("printcenter.jobs.fippa_authorization"), - description: "", - subject: i18n.t("printcenter.jobs.fippa_authorization"), - key: "fippa_authorization", - disabled: false, - group: "authorization", - }, - diagnostic_authorization: { - title: i18n.t("printcenter.jobs.diagnostic_authorization"), - description: "", - subject: i18n.t("printcenter.jobs.diagnostic_authorization"), - key: "diagnostic_authorization", - disabled: false, - group: "authorization", - }, - mechanical_authorization: { - title: i18n.t("printcenter.jobs.mechanical_authorization"), - description: "", - subject: i18n.t("printcenter.jobs.mechanical_authorization"), - key: "mechanical_authorization", - disabled: false, - group: "authorization", - }, - appointment_reminder: { - title: i18n.t("printcenter.jobs.appointment_reminder"), - description: "", - subject: i18n.t("printcenter.jobs.appointment_reminder"), - key: "appointment_reminder", - disabled: false, - group: "pre", - }, - estimate_followup: { - title: i18n.t("printcenter.jobs.estimate_followup"), - description: "", - subject: i18n.t("printcenter.jobs.estimate_followup"), - key: "estimate_followup", - disabled: false, - group: "pre", - }, - express_repair_checklist: { - title: i18n.t("printcenter.jobs.express_repair_checklist"), - description: "", - subject: i18n.t("printcenter.jobs.express_repair_checklist"), - key: "express_repair_checklist", - disabled: false, - group: "pre", - }, - glass_express_checklist: { - title: i18n.t("printcenter.jobs.glass_express_checklist"), - description: "", - subject: i18n.t("printcenter.jobs.glass_express_checklist"), - key: "glass_express_checklist", - disabled: false, - group: "pre", - }, - stolen_recovery_checklist: { - title: i18n.t("printcenter.jobs.stolen_recovery_checklist"), - description: "", - subject: i18n.t("printcenter.jobs.stolen_recovery_checklist"), - key: "stolen_recovery_checklist", - disabled: false, - group: "pre", - }, - vehicle_check_in: { - title: i18n.t("printcenter.jobs.vehicle_check_in"), - description: "", - subject: i18n.t("printcenter.jobs.vehicle_check_in"), - key: "vehicle_check_in", - disabled: false, - group: "pre", - }, - parts_order_history: { - title: i18n.t("printcenter.jobs.parts_order_history"), - description: "", - subject: i18n.t("printcenter.jobs.parts_order_history"), - key: "parts_order_history", - disabled: false, - group: "ro", - }, - job_notes: { - title: i18n.t("printcenter.jobs.job_notes"), - description: "", - subject: i18n.t("printcenter.jobs.job_notes"), - key: "job_notes", - disabled: false, - group: "ro", - }, - ro_with_description: { - title: i18n.t("printcenter.jobs.ro_with_description"), - description: "", - subject: i18n.t("printcenter.jobs.ro_with_description"), - key: "ro_with_description", - disabled: false, - group: "ro", - }, - window_tag: { - title: i18n.t("printcenter.jobs.window_tag"), - description: "", - subject: i18n.t("printcenter.jobs.window_tag"), - key: "window_tag", - disabled: false, - group: "ro", - }, - supplement_request: { - title: i18n.t("printcenter.jobs.supplement_request"), - description: "", - subject: i18n.t("printcenter.jobs.supplement_request"), - key: "supplement_request", - disabled: false, - group: "ro", - }, - estimate: { - title: i18n.t("printcenter.jobs.estimate"), - description: "", - subject: i18n.t("printcenter.jobs.estimate"), - key: "estimate", - disabled: false, - group: "ro", - }, - parts_list: { - title: i18n.t("printcenter.jobs.parts_list"), - description: "", - subject: i18n.t("printcenter.jobs.parts_list"), - key: "parts_list", - disabled: false, - group: "ro", - }, - coversheet_portrait: { - title: i18n.t("printcenter.jobs.coversheet_portrait"), - description: "", - subject: i18n.t("printcenter.jobs.coversheet_portrait"), - key: "coversheet_portrait", - disabled: false, - group: "ro", - }, - coversheet_landscape: { - title: i18n.t("printcenter.jobs.coversheet_landscape"), - description: "", - subject: i18n.t("printcenter.jobs.coversheet_landscape"), - key: "coversheet_landscape", - disabled: false, - group: "ro", - }, - key_tag: { - title: i18n.t("printcenter.jobs.key_tag"), - description: "", - subject: i18n.t("printcenter.jobs.key_tag"), - key: "key_tag", - disabled: false, - group: "ro", - }, - paint_grid: { - title: i18n.t("printcenter.jobs.paint_grid"), - description: "", - subject: i18n.t("printcenter.jobs.paint_grid"), - key: "paint_grid", - disabled: false, - group: "ro", - }, - worksheet_by_line_number: { - title: i18n.t("printcenter.jobs.worksheet_by_line_number"), - description: "", - subject: i18n.t("printcenter.jobs.worksheet_by_line_number"), - key: "worksheet_by_line_number", - disabled: false, - group: "worksheet", - enhanced_payroll: false, - }, - worksheet_by_line_number_enhanced: { - title: i18n.t("printcenter.jobs.worksheet_by_line_number"), - description: "", - subject: i18n.t("printcenter.jobs.worksheet_by_line_number"), - key: "worksheet_by_line_number_enhanced", - disabled: false, - group: "worksheet", - enhanced_payroll: true, - }, - worksheet_sorted_by_operation_type: { - title: i18n.t( - "printcenter.jobs.worksheet_sorted_by_operation_type" - ), - description: "", - subject: i18n.t( - "printcenter.jobs.worksheet_sorted_by_operation_type" - ), - key: "worksheet_sorted_by_operation_type", - disabled: false, - group: "worksheet", - enhanced_payroll: false, - }, - worksheet_sorted_by_operation_type_enhanced: { - title: i18n.t( - "printcenter.jobs.worksheet_sorted_by_operation_type" - ), - description: "", - subject: i18n.t( - "printcenter.jobs.worksheet_sorted_by_operation_type" - ), - key: "worksheet_sorted_by_operation_type_enhanced", - disabled: false, - group: "worksheet", - enhanced_payroll: true, - }, - worksheet_sorted_by_operation: { - title: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"), - description: "", - subject: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"), - key: "worksheet_sorted_by_operation", - disabled: false, - group: "worksheet", - enhanced_payroll: false, - }, - worksheet_sorted_by_operation_enhanced: { - title: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"), - description: "", - subject: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"), - key: "worksheet_sorted_by_operation_enhanced", - disabled: false, - group: "worksheet", - enhanced_payroll: true, - }, - worksheet_sorted_by_operation_no_hours: { - title: i18n.t( - "printcenter.jobs.worksheet_sorted_by_operation_no_hours" - ), - description: "", - subject: i18n.t( - "printcenter.jobs.worksheet_sorted_by_operation_no_hours" - ), - key: "worksheet_sorted_by_operation_no_hours", - disabled: false, - group: "worksheet", - enhanced_payroll: false, - }, - worksheet_sorted_by_operation_no_hours_enhanced: { - title: i18n.t( - "printcenter.jobs.worksheet_sorted_by_operation_no_hours" - ), - description: "", - subject: i18n.t( - "printcenter.jobs.worksheet_sorted_by_operation_no_hours" - ), - key: "worksheet_sorted_by_operation_no_hours_enhanced", - disabled: false, - group: "worksheet", - enhanced_payroll: true, - }, - worksheet_sorted_by_operation_part_type: { - title: i18n.t( - "printcenter.jobs.worksheet_sorted_by_operation_part_type" - ), - description: "", - subject: i18n.t( - "printcenter.jobs.worksheet_sorted_by_operation_part_type" - ), - key: "worksheet_sorted_by_operation_part_type", - disabled: false, - group: "worksheet", - enhanced_payroll: false, - }, - worksheet_sorted_by_operation_part_type_enhanced: { - title: i18n.t( - "printcenter.jobs.worksheet_sorted_by_operation_part_type" - ), - description: "", - subject: i18n.t( - "printcenter.jobs.worksheet_sorted_by_operation_part_type" - ), - key: "worksheet_sorted_by_operation_part_type_enhanced", - disabled: false, - group: "worksheet", - enhanced_payroll: true, - }, - payments_by_job: { - title: i18n.t("printcenter.jobs.payments_by_job"), - description: "", - subject: i18n.t("printcenter.jobs.payments_by_job"), - key: "payments_by_job", - disabled: false, - group: "financial", - }, - final_invoice: { - title: i18n.t("printcenter.jobs.final_invoice"), - description: "", - subject: i18n.t("printcenter.jobs.final_invoice"), - key: "final_invoice", - disabled: false, - group: "financial", - }, - payment_request: { - title: i18n.t("printcenter.jobs.payment_request"), - description: "", - subject: i18n.t("printcenter.jobs.payment_request"), - key: "payment_request", - disabled: false, - group: "financial", - }, - invoice_total_payable: { - title: i18n.t("printcenter.jobs.invoice_total_payable"), - description: "", - subject: i18n.t("printcenter.jobs.invoice_total_payable"), - key: "invoice_total_payable", - disabled: false, - group: "financial", - }, - invoice_customer_payable: { - title: i18n.t("printcenter.jobs.invoice_customer_payable"), - description: "", - subject: i18n.t("printcenter.jobs.invoice_customer_payable"), - key: "invoice_customer_payable", - disabled: false, - group: "financial", - }, - ro_totals: { - title: i18n.t("printcenter.jobs.ro_totals"), - description: "", - subject: i18n.t("printcenter.jobs.ro_totals"), - key: "ro_totals", - disabled: false, - group: "financial", - }, - job_costing_ro: { - title: i18n.t("printcenter.jobs.job_costing_ro"), - description: "", - subject: i18n.t("printcenter.jobs.job_costing_ro"), - key: "job_costing_ro", - disabled: false, - group: "financial", - }, - purchases_by_ro_detail: { - title: i18n.t("printcenter.jobs.purchases_by_ro_detail"), - description: "", - subject: i18n.t("printcenter.jobs.purchases_by_ro_detail"), - key: "purchases_by_ro_detail", - disabled: false, - group: "financial", - }, - purchases_by_ro_summary: { - title: i18n.t("printcenter.jobs.purchases_by_ro_summary"), - description: "", - subject: i18n.t("printcenter.jobs.purchases_by_ro_summary"), - key: "purchases_by_ro_summary", - disabled: false, - group: "financial", - }, - filing_coversheet_portrait: { - title: i18n.t("printcenter.jobs.filing_coversheet_portrait"), - description: "", - subject: i18n.t("printcenter.jobs.filing_coversheet_portrait"), - key: "filing_coversheet_portrait", - disabled: false, - group: "post", - }, - filing_coversheet_landscape: { - title: i18n.t("printcenter.jobs.filing_coversheet_landscape"), - description: "", - subject: i18n.t("printcenter.jobs.filing_coversheet_landscape"), - key: "filing_coversheet_landscape", - disabled: false, - group: "post", - }, - qc_sheet: { - title: i18n.t("printcenter.jobs.qc_sheet"), - description: "", - subject: i18n.t("printcenter.jobs.qc_sheet"), - key: "qc_sheet", - disabled: false, - group: "post", - }, - vehicle_delivery_check: { - title: i18n.t("printcenter.jobs.vehicle_delivery_check"), - description: "", - subject: i18n.t("printcenter.jobs.vehicle_delivery_check"), - key: "vehicle_delivery_check", - disabled: false, - group: "post", - }, - guarantee: { - title: i18n.t("printcenter.jobs.guarantee"), - description: "", - subject: i18n.t("printcenter.jobs.guarantee"), - key: "guarantee", - disabled: false, - group: "post", - }, - csi_invitation: { - title: i18n.t("printcenter.jobs.csi_invitation"), - description: "", - key: "csi_invitation", - subject: i18n.t("printcenter.jobs.csi_invitation"), - disabled: false, - group: "post", - }, - window_tag_sublet: { - title: i18n.t("printcenter.jobs.window_tag_sublet"), - description: "", - key: "window_tag_sublet", - subject: i18n.t("printcenter.jobs.window_tag_sublet"), - disabled: false, - group: "ro", - }, - thank_you_ro: { - title: i18n.t("printcenter.jobs.thank_you_ro"), - description: "", - key: "thank_you_ro", - subject: i18n.t("printcenter.jobs.thank_you_ro"), - disabled: false, - group: "post", - }, - parts_label_single: { - title: i18n.t("printcenter.jobs.parts_label_single"), - description: "", - key: "parts_label_single", - subject: i18n.t("printcenter.jobs.parts_label_single"), - disabled: false, - group: "ro", - ignoreCustomMargins: true, - }, - envelope_return_address: { - title: i18n.t("printcenter.jobs.envelope_return_address"), - description: "", - subject: i18n.t("printcenter.jobs.envelope_return_address"), - key: "envelope_return_address", - disabled: false, - group: "ro", - ignoreCustomMargins: true, - }, - sgi_certificate_of_repairs: { - title: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"), - description: "", - key: "sgi_certificate_of_repairs", - subject: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"), - disabled: false, - group: "ro", - regions: { - CA_SK: true, - }, - }, - sgi_windshield_auth: { - title: i18n.t("printcenter.jobs.sgi_windshield_auth"), - description: "", - key: "sgi_windshield_auth", - subject: i18n.t("printcenter.jobs.sgi_windshield_auth"), - disabled: false, - group: "pre", - regions: { - CA_SK: true, - }, - }, - mpi_final_acct_sheet: { - title: i18n.t("printcenter.jobs.mpi_final_acct_sheet"), - description: "", - key: "mpi_final_acct_sheet", - subject: i18n.t("printcenter.jobs.mpi_final_acct_sheet"), - disabled: false, - group: "post", - regions: { - CA_MB: true, - }, - }, - mpi_final_repair_acct_sheet: { - title: i18n.t("printcenter.jobs.mpi_final_repair_acct_sheet"), - description: "", - key: "mpi_final_repair_acct_sheet", - subject: i18n.t("printcenter.jobs.mpi_final_repair_acct_sheet"), - disabled: false, - group: "post", - regions: { - CA_MB: true, - }, - }, - mpi_eglass_auth: { - title: i18n.t("printcenter.jobs.mpi_eglass_auth"), - description: "", - key: "mpi_eglass_auth", - subject: i18n.t("printcenter.jobs.mpi_eglass_auth"), - disabled: false, - group: "pre", - regions: { - CA_MB: true, - }, - }, - mpi_animal_checklist: { - title: i18n.t("printcenter.jobs.mpi_animal_checklist"), - description: "", - key: "mpi_animal_checklist", - subject: i18n.t("printcenter.jobs.mpi_animal_checklist"), - disabled: false, - group: "pre", - regions: { - CA_MB: true, - }, - }, - ab_proof_of_loss: { - title: i18n.t("printcenter.jobs.ab_proof_of_loss"), - description: "", - key: "ab_proof_of_loss", - subject: i18n.t("printcenter.jobs.ab_proof_of_loss"), - disabled: false, - group: "pre", - regions: { - CA_AB: true, - }, - }, - // parts_label_multi: { - // title: i18n.t("printcenter.jobs.parts_label_multi"), - // description: "", - // key: "parts_label_multi", - // subject: i18n.t("printcenter.jobs.parts_label_multi"), - // disabled: false, - // group: "ro", - // }, - iou_form: { - title: i18n.t("printcenter.jobs.iou_form"), - description: "", - subject: i18n.t("printcenter.jobs.iou_form"), - key: "iou_form", - disabled: false, - group: "post", - }, - lag_time_ro: { - title: i18n.t("printcenter.jobs.lag_time_ro"), - description: "", - subject: i18n.t("printcenter.jobs.lag_time_ro"), - key: "lag_time_ro", - disabled: false, - group: "ro", - }, - rental_reservation: { - title: i18n.t("printcenter.jobs.rental_reservation"), - description: "", - subject: i18n.t("printcenter.jobs.rental_reservation"), - key: "rental_reservation", - disabled: false, - group: "pre", - }, - timetickets_ro: { - title: i18n.t("printcenter.jobs.timetickets_ro"), - description: "", - subject: i18n.t("printcenter.jobs.timetickets_ro"), - key: "timetickets_ro", - disabled: false, - group: "financial", - }, - dms_posting_sheet: { - title: i18n.t("printcenter.jobs.dms_posting_sheet"), - description: "", - subject: i18n.t("printcenter.jobs.dms_posting_sheet"), - key: "dms_posting_sheet", - disabled: false, - group: "financial", - dms: true, - }, - worksheet_sorted_by_team: { - title: i18n.t("printcenter.jobs.worksheet_sorted_by_team"), - description: "", - subject: i18n.t("printcenter.jobs.worksheet_sorted_by_team"), - key: "worksheet_sorted_by_team", - disabled: false, - group: "worksheet", - enhanced_payroll: true, - }, - committed_timetickets_ro: { - title: i18n.t("printcenter.jobs.committed_timetickets_ro"), - description: "", - subject: i18n.t("printcenter.jobs.committed_timetickets_ro"), - key: "committed_timetickets_ro", - disabled: false, - group: "financial", - enhanced_payroll: true, - }, - job_lifecycle_ro: { - title: i18n.t("printcenter.jobs.job_lifecycle_ro"), - description: "", - subject: i18n.t("printcenter.jobs.job_lifecycle_ro"), - key: "job_lifecycle_ro", - disabled: false, - group: "post", - }, + //const { bodyshop } = store.getState().user; + return { + //If there's no type or the type is job, send it back. + ...(!type || type === "job" + ? { + casl_authorization: { + title: i18n.t("printcenter.jobs.casl_authorization"), + description: "", + subject: i18n.t("printcenter.jobs.casl_authorization"), + key: "casl_authorization", + disabled: false, + group: "authorization", + regions: { + CA: true } - : {}), - ...(!type || type === "job_special" - ? { - special_thirdpartypayer: { - title: i18n.t("printcenter.jobs.thirdpartypayer"), - description: "", - key: "special_thirdpartypayer", - disabled: false, - }, - folder_label_multiple: { - title: i18n.t("printcenter.jobs.folder_label_multiple"), - description: "", - key: "folder_label_multiple", - disabled: false, - }, - parts_label_multiple: { - title: i18n.t("printcenter.jobs.parts_label_multiple"), - description: "", - key: "parts_label_multiple", - disabled: false, - }, - parts_invoice_label_single: { - title: i18n.t("printcenter.jobs.parts_invoice_label_single"), - description: "", - key: "parts_invoice_label_single", - disabled: false, - ignoreCustomMargins: true, - }, - csi_invitation_action: { - title: i18n.t("printcenter.jobs.csi_invitation_action"), - description: "", - key: "csi_invitation_action", - subject: i18n.t("printcenter.jobs.csi_invitation_action"), - disabled: false, - }, - individual_job_note: { - title: i18n.t("printcenter.jobs.individual_job_note"), - description: "", - key: "individual_job_note", - subject: i18n.t("printcenter.subjects.jobs.individual_job_note", { - ro_number: (context && context.ro_number) || "", - }), - disabled: false, - }, - parts_dispatch: { - title: i18n.t("printcenter.jobs.parts_dispatch"), - description: "", - key: "parts_dispatch", - subject: i18n.t("printcenter.subjects.jobs.parts_dispatch", { - ro_number: (context && context.ro_number) || "", - }), - disabled: false, - }, + }, + fippa_authorization: { + title: i18n.t("printcenter.jobs.fippa_authorization"), + description: "", + subject: i18n.t("printcenter.jobs.fippa_authorization"), + key: "fippa_authorization", + disabled: false, + group: "authorization" + }, + diagnostic_authorization: { + title: i18n.t("printcenter.jobs.diagnostic_authorization"), + description: "", + subject: i18n.t("printcenter.jobs.diagnostic_authorization"), + key: "diagnostic_authorization", + disabled: false, + group: "authorization" + }, + mechanical_authorization: { + title: i18n.t("printcenter.jobs.mechanical_authorization"), + description: "", + subject: i18n.t("printcenter.jobs.mechanical_authorization"), + key: "mechanical_authorization", + disabled: false, + group: "authorization" + }, + appointment_reminder: { + title: i18n.t("printcenter.jobs.appointment_reminder"), + description: "", + subject: i18n.t("printcenter.jobs.appointment_reminder"), + key: "appointment_reminder", + disabled: false, + group: "pre" + }, + estimate_followup: { + title: i18n.t("printcenter.jobs.estimate_followup"), + description: "", + subject: i18n.t("printcenter.jobs.estimate_followup"), + key: "estimate_followup", + disabled: false, + group: "pre" + }, + express_repair_checklist: { + title: i18n.t("printcenter.jobs.express_repair_checklist"), + description: "", + subject: i18n.t("printcenter.jobs.express_repair_checklist"), + key: "express_repair_checklist", + disabled: false, + group: "pre" + }, + glass_express_checklist: { + title: i18n.t("printcenter.jobs.glass_express_checklist"), + description: "", + subject: i18n.t("printcenter.jobs.glass_express_checklist"), + key: "glass_express_checklist", + disabled: false, + group: "pre" + }, + stolen_recovery_checklist: { + title: i18n.t("printcenter.jobs.stolen_recovery_checklist"), + description: "", + subject: i18n.t("printcenter.jobs.stolen_recovery_checklist"), + key: "stolen_recovery_checklist", + disabled: false, + group: "pre" + }, + vehicle_check_in: { + title: i18n.t("printcenter.jobs.vehicle_check_in"), + description: "", + subject: i18n.t("printcenter.jobs.vehicle_check_in"), + key: "vehicle_check_in", + disabled: false, + group: "pre" + }, + parts_order_history: { + title: i18n.t("printcenter.jobs.parts_order_history"), + description: "", + subject: i18n.t("printcenter.jobs.parts_order_history"), + key: "parts_order_history", + disabled: false, + group: "ro" + }, + job_notes: { + title: i18n.t("printcenter.jobs.job_notes"), + description: "", + subject: i18n.t("printcenter.jobs.job_notes"), + key: "job_notes", + disabled: false, + group: "ro" + }, + ro_with_description: { + title: i18n.t("printcenter.jobs.ro_with_description"), + description: "", + subject: i18n.t("printcenter.jobs.ro_with_description"), + key: "ro_with_description", + disabled: false, + group: "ro" + }, + window_tag: { + title: i18n.t("printcenter.jobs.window_tag"), + description: "", + subject: i18n.t("printcenter.jobs.window_tag"), + key: "window_tag", + disabled: false, + group: "ro" + }, + supplement_request: { + title: i18n.t("printcenter.jobs.supplement_request"), + description: "", + subject: i18n.t("printcenter.jobs.supplement_request"), + key: "supplement_request", + disabled: false, + group: "ro" + }, + estimate: { + title: i18n.t("printcenter.jobs.estimate"), + description: "", + subject: i18n.t("printcenter.jobs.estimate"), + key: "estimate", + disabled: false, + group: "ro" + }, + parts_list: { + title: i18n.t("printcenter.jobs.parts_list"), + description: "", + subject: i18n.t("printcenter.jobs.parts_list"), + key: "parts_list", + disabled: false, + group: "ro" + }, + coversheet_portrait: { + title: i18n.t("printcenter.jobs.coversheet_portrait"), + description: "", + subject: i18n.t("printcenter.jobs.coversheet_portrait"), + key: "coversheet_portrait", + disabled: false, + group: "ro" + }, + coversheet_landscape: { + title: i18n.t("printcenter.jobs.coversheet_landscape"), + description: "", + subject: i18n.t("printcenter.jobs.coversheet_landscape"), + key: "coversheet_landscape", + disabled: false, + group: "ro" + }, + key_tag: { + title: i18n.t("printcenter.jobs.key_tag"), + description: "", + subject: i18n.t("printcenter.jobs.key_tag"), + key: "key_tag", + disabled: false, + group: "ro" + }, + paint_grid: { + title: i18n.t("printcenter.jobs.paint_grid"), + description: "", + subject: i18n.t("printcenter.jobs.paint_grid"), + key: "paint_grid", + disabled: false, + group: "ro" + }, + worksheet_by_line_number: { + title: i18n.t("printcenter.jobs.worksheet_by_line_number"), + description: "", + subject: i18n.t("printcenter.jobs.worksheet_by_line_number"), + key: "worksheet_by_line_number", + disabled: false, + group: "worksheet", + enhanced_payroll: false + }, + worksheet_by_line_number_enhanced: { + title: i18n.t("printcenter.jobs.worksheet_by_line_number"), + description: "", + subject: i18n.t("printcenter.jobs.worksheet_by_line_number"), + key: "worksheet_by_line_number_enhanced", + disabled: false, + group: "worksheet", + enhanced_payroll: true + }, + worksheet_sorted_by_operation_type: { + title: i18n.t("printcenter.jobs.worksheet_sorted_by_operation_type"), + description: "", + subject: i18n.t("printcenter.jobs.worksheet_sorted_by_operation_type"), + key: "worksheet_sorted_by_operation_type", + disabled: false, + group: "worksheet", + enhanced_payroll: false + }, + worksheet_sorted_by_operation_type_enhanced: { + title: i18n.t("printcenter.jobs.worksheet_sorted_by_operation_type"), + description: "", + subject: i18n.t("printcenter.jobs.worksheet_sorted_by_operation_type"), + key: "worksheet_sorted_by_operation_type_enhanced", + disabled: false, + group: "worksheet", + enhanced_payroll: true + }, + worksheet_sorted_by_operation: { + title: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"), + description: "", + subject: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"), + key: "worksheet_sorted_by_operation", + disabled: false, + group: "worksheet", + enhanced_payroll: false + }, + worksheet_sorted_by_operation_enhanced: { + title: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"), + description: "", + subject: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"), + key: "worksheet_sorted_by_operation_enhanced", + disabled: false, + group: "worksheet", + enhanced_payroll: true + }, + worksheet_sorted_by_operation_no_hours: { + title: i18n.t("printcenter.jobs.worksheet_sorted_by_operation_no_hours"), + description: "", + subject: i18n.t("printcenter.jobs.worksheet_sorted_by_operation_no_hours"), + key: "worksheet_sorted_by_operation_no_hours", + disabled: false, + group: "worksheet", + enhanced_payroll: false + }, + worksheet_sorted_by_operation_no_hours_enhanced: { + title: i18n.t("printcenter.jobs.worksheet_sorted_by_operation_no_hours"), + description: "", + subject: i18n.t("printcenter.jobs.worksheet_sorted_by_operation_no_hours"), + key: "worksheet_sorted_by_operation_no_hours_enhanced", + disabled: false, + group: "worksheet", + enhanced_payroll: true + }, + worksheet_sorted_by_operation_part_type: { + title: i18n.t("printcenter.jobs.worksheet_sorted_by_operation_part_type"), + description: "", + subject: i18n.t("printcenter.jobs.worksheet_sorted_by_operation_part_type"), + key: "worksheet_sorted_by_operation_part_type", + disabled: false, + group: "worksheet", + enhanced_payroll: false + }, + worksheet_sorted_by_operation_part_type_enhanced: { + title: i18n.t("printcenter.jobs.worksheet_sorted_by_operation_part_type"), + description: "", + subject: i18n.t("printcenter.jobs.worksheet_sorted_by_operation_part_type"), + key: "worksheet_sorted_by_operation_part_type_enhanced", + disabled: false, + group: "worksheet", + enhanced_payroll: true + }, + payments_by_job: { + title: i18n.t("printcenter.jobs.payments_by_job"), + description: "", + subject: i18n.t("printcenter.jobs.payments_by_job"), + key: "payments_by_job", + disabled: false, + group: "financial" + }, + final_invoice: { + title: i18n.t("printcenter.jobs.final_invoice"), + description: "", + subject: i18n.t("printcenter.jobs.final_invoice"), + key: "final_invoice", + disabled: false, + group: "financial" + }, + payment_request: { + title: i18n.t("printcenter.jobs.payment_request"), + description: "", + subject: i18n.t("printcenter.jobs.payment_request"), + key: "payment_request", + disabled: false, + group: "financial" + }, + invoice_total_payable: { + title: i18n.t("printcenter.jobs.invoice_total_payable"), + description: "", + subject: i18n.t("printcenter.jobs.invoice_total_payable"), + key: "invoice_total_payable", + disabled: false, + group: "financial" + }, + invoice_customer_payable: { + title: i18n.t("printcenter.jobs.invoice_customer_payable"), + description: "", + subject: i18n.t("printcenter.jobs.invoice_customer_payable"), + key: "invoice_customer_payable", + disabled: false, + group: "financial" + }, + ro_totals: { + title: i18n.t("printcenter.jobs.ro_totals"), + description: "", + subject: i18n.t("printcenter.jobs.ro_totals"), + key: "ro_totals", + disabled: false, + group: "financial" + }, + job_costing_ro: { + title: i18n.t("printcenter.jobs.job_costing_ro"), + description: "", + subject: i18n.t("printcenter.jobs.job_costing_ro"), + key: "job_costing_ro", + disabled: false, + group: "financial" + }, + purchases_by_ro_detail: { + title: i18n.t("printcenter.jobs.purchases_by_ro_detail"), + description: "", + subject: i18n.t("printcenter.jobs.purchases_by_ro_detail"), + key: "purchases_by_ro_detail", + disabled: false, + group: "financial" + }, + purchases_by_ro_summary: { + title: i18n.t("printcenter.jobs.purchases_by_ro_summary"), + description: "", + subject: i18n.t("printcenter.jobs.purchases_by_ro_summary"), + key: "purchases_by_ro_summary", + disabled: false, + group: "financial" + }, + filing_coversheet_portrait: { + title: i18n.t("printcenter.jobs.filing_coversheet_portrait"), + description: "", + subject: i18n.t("printcenter.jobs.filing_coversheet_portrait"), + key: "filing_coversheet_portrait", + disabled: false, + group: "post" + }, + filing_coversheet_landscape: { + title: i18n.t("printcenter.jobs.filing_coversheet_landscape"), + description: "", + subject: i18n.t("printcenter.jobs.filing_coversheet_landscape"), + key: "filing_coversheet_landscape", + disabled: false, + group: "post" + }, + qc_sheet: { + title: i18n.t("printcenter.jobs.qc_sheet"), + description: "", + subject: i18n.t("printcenter.jobs.qc_sheet"), + key: "qc_sheet", + disabled: false, + group: "post" + }, + vehicle_delivery_check: { + title: i18n.t("printcenter.jobs.vehicle_delivery_check"), + description: "", + subject: i18n.t("printcenter.jobs.vehicle_delivery_check"), + key: "vehicle_delivery_check", + disabled: false, + group: "post" + }, + guarantee: { + title: i18n.t("printcenter.jobs.guarantee"), + description: "", + subject: i18n.t("printcenter.jobs.guarantee"), + key: "guarantee", + disabled: false, + group: "post" + }, + csi_invitation: { + title: i18n.t("printcenter.jobs.csi_invitation"), + description: "", + key: "csi_invitation", + subject: i18n.t("printcenter.jobs.csi_invitation"), + disabled: false, + group: "post" + }, + window_tag_sublet: { + title: i18n.t("printcenter.jobs.window_tag_sublet"), + description: "", + key: "window_tag_sublet", + subject: i18n.t("printcenter.jobs.window_tag_sublet"), + disabled: false, + group: "ro" + }, + thank_you_ro: { + title: i18n.t("printcenter.jobs.thank_you_ro"), + description: "", + key: "thank_you_ro", + subject: i18n.t("printcenter.jobs.thank_you_ro"), + disabled: false, + group: "post" + }, + parts_label_single: { + title: i18n.t("printcenter.jobs.parts_label_single"), + description: "", + key: "parts_label_single", + subject: i18n.t("printcenter.jobs.parts_label_single"), + disabled: false, + group: "ro", + ignoreCustomMargins: true + }, + envelope_return_address: { + title: i18n.t("printcenter.jobs.envelope_return_address"), + description: "", + subject: i18n.t("printcenter.jobs.envelope_return_address"), + key: "envelope_return_address", + disabled: false, + group: "ro", + ignoreCustomMargins: true + }, + sgi_certificate_of_repairs: { + title: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"), + description: "", + key: "sgi_certificate_of_repairs", + subject: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"), + disabled: false, + group: "ro", + regions: { + CA_SK: true } - : {}), - ...(!type || type === "appointment" - ? { - appointment_confirmation: { - title: i18n.t("printcenter.appointments.appointment_confirmation"), - description: "", - subject: i18n.t( - "printcenter.appointments.appointment_confirmation" - ), - key: "appointment_confirmation", - disabled: false, - }, + }, + sgi_windshield_auth: { + title: i18n.t("printcenter.jobs.sgi_windshield_auth"), + description: "", + key: "sgi_windshield_auth", + subject: i18n.t("printcenter.jobs.sgi_windshield_auth"), + disabled: false, + group: "pre", + regions: { + CA_SK: true } - : {}), - ...(!type || type === "partsorder" - ? { - parts_order: { - title: i18n.t("printcenter.jobs.parts_order"), - description: "", - key: "parts_order", - subject: i18n.t("printcenter.subjects.jobs.parts_order", { - ro_number: context && context.job && context.job.ro_number, - name: ( - (context && context.job && context.job.ownr_ln) || - (context && context.job && context.job.ownr_co_nm) || - "" - ).trim(), - }), - disabled: false, - }, - sublet_order: { - title: i18n.t("printcenter.jobs.sublet_order"), - description: "", - key: "sublet_order", - subject: i18n.t("printcenter.subjects.jobs.sublet_order", { - ro_number: context && context.job && context.job.ro_number, - name: ( - (context && context.job && context.job.ownr_ln) || - (context && context.job && context.job.ownr_co_nm) || - "" - ).trim(), - }), - disabled: false, - }, - parts_return_slip: { - title: i18n.t("printcenter.jobs.parts_return_slip"), - subject: i18n.t("printcenter.subjects.jobs.parts_return_slip", { - ro_number: context && context.job && context.job.ro_number, - name: ( - (context && context.job && context.job.ownr_ln) || - (context && context.job && context.job.ownr_co_nm) || - "" - ).trim(), - }), - description: "", - key: "parts_return_slip", - disabled: false, - }, + }, + mpi_final_acct_sheet: { + title: i18n.t("printcenter.jobs.mpi_final_acct_sheet"), + description: "", + key: "mpi_final_acct_sheet", + subject: i18n.t("printcenter.jobs.mpi_final_acct_sheet"), + disabled: false, + group: "post", + regions: { + CA_MB: true } - : {}), - ...(!type || type === "payment" - ? { - payment_receipt: { - title: i18n.t("printcenter.jobs.payment_receipt"), - description: "", - subject: i18n.t("printcenter.jobs.payment_receipt"), - key: "payment_receipt", - disabled: false, - }, + }, + mpi_final_repair_acct_sheet: { + title: i18n.t("printcenter.jobs.mpi_final_repair_acct_sheet"), + description: "", + key: "mpi_final_repair_acct_sheet", + subject: i18n.t("printcenter.jobs.mpi_final_repair_acct_sheet"), + disabled: false, + group: "post", + regions: { + CA_MB: true } - : {}), - ...(!type || type === "csi" ? {} : {}), - ...(!type || type === "report_center" - ? { - hours_sold_detail_closed: { - title: i18n.t("reportcenter.templates.hours_sold_detail_closed"), - description: "", - subject: i18n.t("reportcenter.templates.hours_sold_detail_closed"), - key: "hours_sold_detail_closed", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - hours_sold_detail_closed_ins_co: { - title: i18n.t( - "reportcenter.templates.hours_sold_detail_closed_ins_co" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_detail_closed_ins_co" - ), - key: "hours_sold_detail_closed_ins_co", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - hours_sold_summary_closed: { - title: i18n.t("reportcenter.templates.hours_sold_summary_closed"), - description: "", - subject: i18n.t("reportcenter.templates.hours_sold_summary_closed"), - key: "hours_sold_summary_closed", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - hours_sold_summary_closed_ins_co: { - title: i18n.t( - "reportcenter.templates.hours_sold_summary_closed_ins_co" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_summary_closed_ins_co" - ), - key: "hours_sold_summary_closed_ins_co", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - hours_sold_detail_open: { - title: i18n.t("reportcenter.templates.hours_sold_detail_open"), - description: "", - subject: i18n.t("reportcenter.templates.hours_sold_detail_open"), - key: "hours_sold_detail_open", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "sales", - }, - hours_sold_detail_open_ins_co: { - title: i18n.t( - "reportcenter.templates.hours_sold_detail_open_ins_co" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_detail_open_ins_co" - ), - key: "hours_sold_detail_open_ins_co", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "sales", - }, - hours_sold_summary_open: { - title: i18n.t("reportcenter.templates.hours_sold_summary_open"), - description: "", - subject: i18n.t("reportcenter.templates.hours_sold_summary_open"), - key: "hours_sold_summary_open", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "sales", - }, - hours_sold_summary_open_ins_co: { - title: i18n.t( - "reportcenter.templates.hours_sold_summary_open_ins_co" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_summary_open_ins_co" - ), - key: "hours_sold_summary_open_ins_co", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "sales", - }, - hours_sold_detail_closed_csr: { - title: i18n.t( - "reportcenter.templates.hours_sold_detail_closed_csr" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_detail_closed_csr" - ), - key: "hours_sold_detail_closed_csr", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - hours_sold_detail_open_csr: { - title: i18n.t("reportcenter.templates.hours_sold_detail_open_csr"), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_detail_open_csr" - ), - key: "hours_sold_detail_open_csr", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "sales", - }, - hours_sold_summary_closed_csr: { - title: i18n.t( - "reportcenter.templates.hours_sold_summary_closed_csr" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_summary_closed_csr" - ), - key: "hours_sold_summary_closed_csr", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - hours_sold_summary_open_csr: { - title: i18n.t("reportcenter.templates.hours_sold_summary_open_csr"), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_summary_open_csr" - ), - key: "hours_sold_summary_open_csr", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - hours_sold_detail_closed_estimator: { - title: i18n.t( - "reportcenter.templates.hours_sold_detail_closed_estimator" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_detail_closed_estimator" - ), - key: "hours_sold_detail_closed_estimator", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - hours_sold_detail_open_estimator: { - title: i18n.t( - "reportcenter.templates.hours_sold_detail_open_estimator" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_detail_open_estimator" - ), - key: "hours_sold_detail_open_estimator", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "sales", - }, - hours_sold_summary_closed_estimator: { - title: i18n.t( - "reportcenter.templates.hours_sold_summary_closed_estimator" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_summary_closed_estimator" - ), - key: "hours_sold_summary_closed_estimator", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - hours_sold_summary_open_estimator: { - title: i18n.t( - "reportcenter.templates.hours_sold_summary_open_estimator" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_summary_open_estimator" - ), - key: "hours_sold_summary_open_estimator", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - hours_sold_summary_open_status: { - title: i18n.t( - "reportcenter.templates.hours_sold_summary_open_status" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_summary_open_status" - ), - key: "hours_sold_summary_open_status", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - hours_sold_summary_closed_status: { - title: i18n.t( - "reportcenter.templates.hours_sold_summary_closed_status" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_summary_closed_status" - ), - key: "hours_sold_summary_closed_status", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - hours_sold_detail_open_status: { - title: i18n.t( - "reportcenter.templates.hours_sold_detail_open_status" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_detail_open_status" - ), - key: "hours_sold_detail_open_status", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - hours_sold_detail_closed_status: { - title: i18n.t( - "reportcenter.templates.hours_sold_detail_closed_status" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.hours_sold_detail_closed_status" - ), - key: "hours_sold_detail_closed_status", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - purchases_by_date_range_detail: { - title: i18n.t( - "reportcenter.templates.purchases_by_date_range_detail" - ), - subject: i18n.t( - "reportcenter.templates.purchases_by_date_range_detail" - ), - key: "purchases_by_date_range_detail", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.bills"), - field: i18n.t("bills.fields.date"), - }, - group: "purchases", - }, - purchases_by_date_range_summary: { - title: i18n.t( - "reportcenter.templates.purchases_by_date_range_summary" - ), - subject: i18n.t( - "reportcenter.templates.purchases_by_date_range_summary" - ), - key: "purchases_by_date_range_summary", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.bills"), - field: i18n.t("bills.fields.date"), - }, - group: "purchases", - }, - purchases_by_vendor_detailed_date_range: { - title: i18n.t( - "reportcenter.templates.purchases_by_vendor_detailed_date_range" - ), - subject: i18n.t( - "reportcenter.templates.purchases_by_vendor_detailed_date_range" - ), - key: "purchases_by_vendor_detailed_date_range", - idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.bills"), - field: i18n.t("bills.fields.date"), - }, - group: "purchases", - }, - purchases_by_vendor_summary_date_range: { - title: i18n.t( - "reportcenter.templates.purchases_by_vendor_summary_date_range" - ), - subject: i18n.t( - "reportcenter.templates.purchases_by_vendor_summary_date_range" - ), - key: "purchases_by_vendor_summary_date_range", - idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.bills"), - field: i18n.t("bills.fields.date"), - }, - group: "purchases", - }, - purchases_by_cost_center_detail: { - title: i18n.t( - "reportcenter.templates.purchases_by_cost_center_detail" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.purchases_by_cost_center_detail" - ), - key: "purchases_by_cost_center_detail", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.bills"), - field: i18n.t("bills.fields.date"), - }, - group: "purchases", - }, - purchases_by_cost_center_summary: { - title: i18n.t( - "reportcenter.templates.purchases_by_cost_center_summary" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.purchases_by_cost_center_summary" - ), - key: "purchases_by_cost_center_summary", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.bills"), - field: i18n.t("bills.fields.date"), - }, - group: "purchases", - }, - purchases_grouped_by_vendor_detailed: { - title: i18n.t( - "reportcenter.templates.purchases_grouped_by_vendor_detailed" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.purchases_grouped_by_vendor_detailed" - ), - key: "purchases_grouped_by_vendor_detailed", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("bills.fields.date"), - }, - group: "purchases", - }, - purchases_grouped_by_vendor_summary: { - title: i18n.t( - "reportcenter.templates.purchases_grouped_by_vendor_summary" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.purchases_grouped_by_vendor_summary" - ), - key: "purchases_grouped_by_vendor_summary", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("bills.fields.date"), - }, - group: "purchases", - }, - job_costing_ro_date_summary: { - title: i18n.t("reportcenter.templates.job_costing_ro_date_summary"), - description: "", - subject: i18n.t( - "reportcenter.templates.job_costing_ro_date_summary" - ), - key: "job_costing_ro_date_summary", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "jobs", - }, - job_costing_ro_csr: { - title: i18n.t("reportcenter.templates.job_costing_ro_csr"), - description: "", - subject: i18n.t("reportcenter.templates.job_costing_ro_csr"), - key: "job_costing_ro_csr", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "jobs", - }, - job_costing_ro_ins_co: { - title: i18n.t("reportcenter.templates.job_costing_ro_ins_co"), - description: "", - subject: i18n.t("reportcenter.templates.job_costing_ro_ins_co"), - key: "job_costing_ro_ins_co", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "jobs", - }, - job_costing_ro_date_detail: { - title: i18n.t("reportcenter.templates.job_costing_ro_date_detail"), - description: "", - subject: i18n.t( - "reportcenter.templates.job_costing_ro_date_detail" - ), - key: "job_costing_ro_date_detail", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "jobs", - }, - job_costing_ro_estimator: { - title: i18n.t("reportcenter.templates.job_costing_ro_estimator"), - description: "", - subject: i18n.t("reportcenter.templates.job_costing_ro_estimator"), - key: "job_costing_ro_estimator", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "jobs", - }, - payments_by_date: { - title: i18n.t("reportcenter.templates.payments_by_date"), - subject: i18n.t("reportcenter.templates.payments_by_date"), - key: "payments_by_date", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.payments"), - field: i18n.t("payments.fields.date"), - }, - group: "customers", - }, - payments_by_date_type: { - title: i18n.t("reportcenter.templates.payments_by_date_type"), - subject: i18n.t("reportcenter.templates.payments_by_date_type"), - key: "payments_by_date_type", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.payments"), - field: i18n.t("payments.fields.date"), - }, - group: "customers", - }, - schedule: { - title: i18n.t("reportcenter.templates.schedule"), - subject: i18n.t("reportcenter.templates.schedule"), - key: "schedule", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.appointments"), - field: i18n.t("appointments.fields.time"), - }, - group: "customers", - }, - timetickets: { - title: i18n.t("reportcenter.templates.timetickets"), - subject: i18n.t("reportcenter.templates.timetickets"), - key: "timetickets", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.timetickets"), - field: i18n.t("timetickets.fields.date"), - }, - group: "payroll", - }, - timetickets_employee: { - title: i18n.t("reportcenter.templates.timetickets_employee"), - subject: i18n.t("reportcenter.templates.timetickets_employee"), - key: "timetickets_employee", - idtype: "employee", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.timetickets"), - field: i18n.t("timetickets.fields.date"), - }, - group: "payroll", - }, - attendance_detail: { - title: i18n.t("reportcenter.templates.attendance_detail"), - subject: i18n.t("reportcenter.templates.attendance_detail"), - key: "attendance_detail", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.timetickets"), - field: i18n.t("timetickets.fields.date"), - }, - group: "payroll", - }, - attendance_summary: { - title: i18n.t("reportcenter.templates.attendance_summary"), - subject: i18n.t("reportcenter.templates.attendance_summary"), - key: "attendance_summary", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.timetickets"), - field: i18n.t("timetickets.fields.date"), - }, - group: "payroll", - }, - attendance_employee: { - title: i18n.t("reportcenter.templates.attendance_employee"), - subject: i18n.t("reportcenter.templates.attendance_employee"), - key: "attendance_employee", - idtype: "employee", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.timetickets"), - field: i18n.t("timetickets.fields.date"), - }, - group: "payroll", - }, - timetickets_summary: { - title: i18n.t("reportcenter.templates.timetickets_summary"), - subject: i18n.t("reportcenter.templates.timetickets_summary"), - key: "timetickets_summary", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.timetickets"), - field: i18n.t("timetickets.fields.date"), - }, - group: "payroll", - }, - estimator_detail: { - title: i18n.t("reportcenter.templates.estimator_detail"), - description: "", - subject: i18n.t("reportcenter.templates.estimator_detail"), - key: "estimator_detail", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - estimator_summary: { - title: i18n.t("reportcenter.templates.estimator_summary"), - description: "", - subject: i18n.t("reportcenter.templates.estimator_summary"), - key: "estimator_summary", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - supplement_ratio_ins_co: { - title: i18n.t("reportcenter.templates.supplement_ratio_ins_co"), - description: "", - subject: i18n.t("reportcenter.templates.supplement_ratio_ins_co"), - key: "supplement_ratio_ins_co", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - credits_not_received_date: { - title: i18n.t("reportcenter.templates.credits_not_received_date"), - description: "", - subject: i18n.t("reportcenter.templates.credits_not_received_date"), - key: "credits_not_received_date", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "purchases", - }, - void_ros: { - title: i18n.t("reportcenter.templates.void_ros"), - description: "", - subject: i18n.t("reportcenter.templates.void_ros"), - key: "void_ros", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_void"), - }, - group: "sales", - }, - gsr_by_csr: { - title: i18n.t("reportcenter.templates.gsr_by_csr"), - description: "", - subject: i18n.t("reportcenter.templates.gsr_by_csr"), - key: "gsr_by_csr", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - gsr_by_make: { - title: i18n.t("reportcenter.templates.gsr_by_make"), - description: "", - subject: i18n.t("reportcenter.templates.gsr_by_make"), - key: "gsr_by_make", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - gsr_by_delivery_date: { - title: i18n.t("reportcenter.templates.gsr_by_delivery_date"), - description: "", - subject: i18n.t("reportcenter.templates.gsr_by_delivery_date"), - key: "gsr_by_delivery_date", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.actual_delivery"), - }, - group: "sales", - }, - gsr_by_referral: { - title: i18n.t("reportcenter.templates.gsr_by_referral"), - description: "", - subject: i18n.t("reportcenter.templates.gsr_by_referral"), - key: "gsr_by_referral", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - gsr_by_ro: { - title: i18n.t("reportcenter.templates.gsr_by_ro"), - description: "", - subject: i18n.t("reportcenter.templates.gsr_by_ro"), - key: "gsr_by_ro", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - gsr_by_ins_co: { - title: i18n.t("reportcenter.templates.gsr_by_ins_co"), - description: "", - subject: i18n.t("reportcenter.templates.gsr_by_ins_co"), - key: "gsr_by_ins_co", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - gsr_by_exported_date: { - title: i18n.t("reportcenter.templates.gsr_by_exported_date"), - description: "", - subject: i18n.t("reportcenter.templates.gsr_by_exported_date"), - key: "gsr_by_exported_date", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_exported"), - }, - group: "sales", - }, - gsr_by_estimator: { - title: i18n.t("reportcenter.templates.gsr_by_estimator"), - description: "", - subject: i18n.t("reportcenter.templates.gsr_by_estimator"), - key: "gsr_by_estimator", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - gsr_by_category: { - title: i18n.t("reportcenter.templates.gsr_by_category"), - description: "", - subject: i18n.t("reportcenter.templates.gsr_by_category"), - key: "gsr_by_category", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - gsr_by_ats: { - title: i18n.t("reportcenter.templates.gsr_by_ats"), - description: "", - subject: i18n.t("reportcenter.templates.gsr_by_ats"), - key: "gsr_by_ats", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - gsr_labor_only: { - title: i18n.t("reportcenter.templates.gsr_labor_only"), - description: "", - subject: i18n.t("reportcenter.templates.gsr_labor_only"), - key: "gsr_labor_only", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - open_orders: { - title: i18n.t("reportcenter.templates.open_orders"), - description: "", - subject: i18n.t("reportcenter.templates.open_orders"), - key: "open_orders", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "jobs", - }, - open_orders_status: { - title: i18n.t("reportcenter.templates.open_orders_status"), - description: "", - subject: i18n.t("reportcenter.templates.open_orders_status"), - key: "open_orders_status", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "jobs", - }, - open_orders_csr: { - title: i18n.t("reportcenter.templates.open_orders_csr"), - description: "", - subject: i18n.t("reportcenter.templates.open_orders_csr"), - key: "open_orders_csr", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "jobs", - }, - open_orders_specific_csr: { - title: i18n.t("reportcenter.templates.open_orders_specific_csr"), - description: "", - subject: i18n.t("reportcenter.templates.open_orders_specific_csr"), - key: "open_orders_specific_csr", - idtype: "employee", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "jobs", - }, - open_orders_estimator: { - title: i18n.t("reportcenter.templates.open_orders_estimator"), - description: "", - subject: i18n.t("reportcenter.templates.open_orders_estimator"), - key: "open_orders_estimator", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "jobs", - }, - open_orders_ins_co: { - title: i18n.t("reportcenter.templates.open_orders_ins_co"), - description: "", - subject: i18n.t("reportcenter.templates.open_orders_ins_co"), - key: "open_orders_ins_co", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "jobs", - }, - open_orders_referral: { - title: i18n.t("reportcenter.templates.open_orders_referral"), - description: "", - subject: i18n.t("reportcenter.templates.open_orders_referral"), - key: "open_orders_referral", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "jobs", - }, - export_payables: { - title: i18n.t("reportcenter.templates.export_payables"), - description: "", - subject: i18n.t("reportcenter.templates.export_payables"), - key: "export_payables", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.exportlogs"), - field: i18n.t("exportlogs.fields.createdat"), - }, - group: "purchases", - }, - export_payments: { - title: i18n.t("reportcenter.templates.export_payments"), - description: "", - subject: i18n.t("reportcenter.templates.export_payments"), - key: "export_payments", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.exportlogs"), - field: i18n.t("exportlogs.fields.createdat"), - }, - group: "customers", - }, - export_receivables: { - title: i18n.t("reportcenter.templates.export_receivables"), - description: "", - subject: i18n.t("reportcenter.templates.export_receivables"), - key: "export_receivables", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.exportlogs"), - field: i18n.t("exportlogs.fields.createdat"), - }, - group: "sales", - }, - parts_backorder: { - title: i18n.t("reportcenter.templates.parts_backorder"), - description: "", - subject: i18n.t("reportcenter.templates.parts_backorder"), - key: "parts_backorder", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.actual_in"), - }, - group: "purchases", - }, - thank_you_date: { - title: i18n.t("reportcenter.templates.thank_you_date"), - description: "", - subject: i18n.t("reportcenter.templates.thank_you_date"), - key: "thank_you_date", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "customers", - }, - unclaimed_hrs: { - title: i18n.t("reportcenter.templates.unclaimed_hrs"), - description: "", - subject: i18n.t("reportcenter.templates.unclaimed_hrs"), - key: "unclaimed_hrs", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "payroll", - }, - work_in_progress_jobs_excel: { - title: i18n.t("reportcenter.templates.work_in_progress_jobs"), - subject: i18n.t("reportcenter.templates.work_in_progress_jobs"), - key: "work_in_progress_jobs_excel", - //idtype: "vendor", - reporttype: "excel", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "jobs", - }, - work_in_progress_labour: { - title: i18n.t("reportcenter.templates.work_in_progress_labour"), - description: "", - subject: i18n.t("reportcenter.templates.work_in_progress_labour"), - key: "work_in_progress_labour", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "jobs", - }, - work_in_progress_committed_labour: { - title: i18n.t( - "reportcenter.templates.work_in_progress_committed_labour" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.work_in_progress_committed_labour" - ), - key: "work_in_progress_committed_labour", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "jobs", - enhanced_payroll: true, - }, - work_in_progress_payables: { - title: i18n.t("reportcenter.templates.work_in_progress_payables"), - description: "", - subject: i18n.t("reportcenter.templates.work_in_progress_payables"), - key: "work_in_progress_payables", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "jobs", - }, - lag_time: { - title: i18n.t("reportcenter.templates.lag_time"), - description: "", - subject: i18n.t("reportcenter.templates.lag_time"), - key: "lag_time", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "jobs", - }, - parts_not_recieved: { - title: i18n.t("reportcenter.templates.parts_not_recieved"), - description: "", - subject: i18n.t("reportcenter.templates.parts_not_recieved"), - key: "parts_not_recieved", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.parts_orders"), - field: i18n.t("parts_orders.fields.order_date"), - }, - group: "purchases", - }, - parts_not_recieved_vendor: { - title: i18n.t("reportcenter.templates.parts_not_recieved_vendor"), - description: "", - subject: i18n.t("reportcenter.templates.parts_not_recieved_vendor"), - key: "parts_not_recieved_vendor", - idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.parts_orders"), - field: i18n.t("parts_orders.fields.order_date"), - }, - group: "purchases", - }, - scoreboard_detail: { - title: i18n.t("reportcenter.templates.scoreboard_detail"), - description: "", - subject: i18n.t("reportcenter.templates.scoreboard_detail"), - key: "scoreboard_detail", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.scoreboard"), - field: i18n.t("scoreboard.fields.date"), - }, - group: "payroll", - }, - scoreboard_summary: { - title: i18n.t("reportcenter.templates.scoreboard_summary"), - description: "", - subject: i18n.t("reportcenter.templates.scoreboard_summary"), - key: "scoreboard_summary", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.scoreboard"), - field: i18n.t("scoreboard.fields.date"), - }, - group: "payroll", - }, - anticipated_revenue: { - title: i18n.t("reportcenter.templates.anticipated_revenue"), - description: "", - subject: i18n.t("reportcenter.templates.anticipated_revenue"), - key: "anticipated_revenue", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.scheduled_completion"), // Also date invoice. - }, - group: "sales", - }, - csi: { - title: i18n.t("reportcenter.templates.csi"), - description: "", - subject: i18n.t("reportcenter.templates.csi"), - key: "csi", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.csi"), - field: i18n.t("csi.fields.created_at"), // Also date invoice. - }, - group: "customers", - }, - estimates_written_converted: { - title: i18n.t("reportcenter.templates.estimates_written_converted"), - description: "", - subject: i18n.t( - "reportcenter.templates.estimates_written_converted" - ), - key: "estimates_written_converted", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: - i18n.t("jobs.fields.date_open") + - "/" + - i18n.t("jobs.fields.date_invoiced"), // Also date invoice. - }, - group: "sales", - }, - credits_not_received_date_vendorid: { - title: i18n.t( - "reportcenter.templates.credits_not_received_date_vendorid" - ), - subject: i18n.t( - "reportcenter.templates.credits_not_received_date_vendorid" - ), - key: "credits_not_received_date_vendorid", - idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "purchases", - }, - jobs_reconcile: { - title: i18n.t("reportcenter.templates.jobs_reconcile"), - subject: i18n.t("reportcenter.templates.jobs_reconcile"), - key: "jobs_reconcile", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "jobs", - }, - parts_received_not_scheduled: { - title: i18n.t( - "reportcenter.templates.parts_received_not_scheduled" - ), - subject: i18n.t( - "reportcenter.templates.parts_received_not_scheduled" - ), - key: "parts_received_not_scheduled", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "jobs", - }, - psr_by_make: { - title: i18n.t("reportcenter.templates.psr_by_make"), - subject: i18n.t("reportcenter.templates.psr_by_make"), - key: "psr_by_make", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "sales", - }, - cycle_time_analysis: { - title: i18n.t("reportcenter.templates.cycle_time_analysis"), - subject: i18n.t("reportcenter.templates.cycle_time_analysis"), - key: "cycle_time_analysis", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.actual_completion"), - }, - group: "jobs", - }, - returns_grouped_by_vendor_summary: { - title: i18n.t( - "reportcenter.templates.returns_grouped_by_vendor_summary" - ), - subject: i18n.t( - "reportcenter.templates.returns_grouped_by_vendor_summary" - ), - key: "returns_grouped_by_vendor_summary", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.parts_orders"), - field: i18n.t("parts_orders.fields.order_date"), - }, - group: "jobs", - }, - returns_grouped_by_vendor_detailed: { - title: i18n.t( - "reportcenter.templates.returns_grouped_by_vendor_detailed" - ), - subject: i18n.t( - "reportcenter.templates.returns_grouped_by_vendor_detailed" - ), - key: "returns_grouped_by_vendor_detailed", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.parts_orders"), - field: i18n.t("parts_orders.fields.order_date"), - }, - group: "jobs", - }, - scheduled_parts_list: { - title: i18n.t("reportcenter.templates.scheduled_parts_list"), - subject: i18n.t("reportcenter.templates.scheduled_parts_list"), - key: "scheduled_parts_list", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.scheduled_in"), - }, - group: "jobs", - }, - jobs_completed_not_invoiced: { - title: i18n.t("reportcenter.templates.jobs_completed_not_invoiced"), - subject: i18n.t( - "reportcenter.templates.jobs_completed_not_invoiced" - ), - key: "jobs_completed_not_invoiced", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "jobs", - }, - jobs_invoiced_not_exported: { - title: i18n.t("reportcenter.templates.jobs_invoiced_not_exported"), - subject: i18n.t( - "reportcenter.templates.jobs_invoiced_not_exported" - ), - key: "jobs_invoiced_not_exported", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "jobs", - }, - purchase_return_ratio_grouped_by_vendor_detail: { - title: i18n.t( - "reportcenter.templates.purchase_return_ratio_grouped_by_vendor_detail" - ), - subject: i18n.t( - "reportcenter.templates.purchase_return_ratio_grouped_by_vendor_detail" - ), - key: "purchase_return_ratio_grouped_by_vendor_detail", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.bills"), - field: i18n.t("bills.fields.date"), - }, - group: "purchases", - }, - purchase_return_ratio_grouped_by_vendor_summary: { - title: i18n.t( - "reportcenter.templates.purchase_return_ratio_grouped_by_vendor_summary" - ), - subject: i18n.t( - "reportcenter.templates.purchase_return_ratio_grouped_by_vendor_summary" - ), - key: "purchase_return_ratio_grouped_by_vendor_summary", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.bills"), - field: i18n.t("bills.fields.date"), - }, - group: "purchases", - }, - production_over_time: { - title: i18n.t("reportcenter.templates.production_over_time"), - subject: i18n.t("reportcenter.templates.production_over_time"), - key: "production_over_time", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.actual_in"), - }, - group: "jobs", - }, - customer_list: { - title: i18n.t("reportcenter.templates.customer_list"), - subject: i18n.t("reportcenter.templates.customer_list"), - key: "customer_list", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "customers", - }, - exported_gsr_by_ro: { - title: i18n.t("reportcenter.templates.exported_gsr_by_ro"), - subject: i18n.t("reportcenter.templates.exported_gsr_by_ro"), - key: "exported_gsr_by_ro", - //idtype: "vendor", - reporttype: "excel", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_exported"), - }, - group: "sales", - }, - exported_gsr_by_ro_labor: { - title: i18n.t("reportcenter.templates.exported_gsr_by_ro_labor"), - subject: i18n.t("reportcenter.templates.exported_gsr_by_ro_labor"), - key: "exported_gsr_by_ro_labor", - //idtype: "vendor", - reporttype: "excel", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_exported"), - }, - group: "sales", - }, - jobs_scheduled_completion: { - title: i18n.t("reportcenter.templates.jobs_scheduled_completion"), - subject: i18n.t("reportcenter.templates.jobs_scheduled_completion"), - key: "jobs_scheduled_completion", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.scheduled_completion"), - }, - group: "jobs", - }, - committed_timetickets: { - title: i18n.t("reportcenter.templates.committed_timetickets"), - subject: i18n.t("reportcenter.templates.committed_timetickets"), - key: "committed_timetickets", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.timetickets"), - field: i18n.t("timetickets.fields.committed_at"), - }, - group: "payroll", - enhanced_payroll: true, - }, - committed_timetickets_employee: { - title: i18n.t( - "reportcenter.templates.committed_timetickets_employee" - ), - subject: i18n.t( - "reportcenter.templates.committed_timetickets_employee" - ), - key: "committed_timetickets_employee", - idtype: "employee", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.timetickets"), - field: i18n.t("timetickets.fields.committed_at"), - }, - group: "payroll", - enhanced_payroll: true, - }, - committed_timetickets_summary: { - title: i18n.t( - "reportcenter.templates.committed_timetickets_summary" - ), - subject: i18n.t( - "reportcenter.templates.committed_timetickets_summary" - ), - key: "committed_timetickets_summary", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.timetickets"), - field: i18n.t("timetickets.fields.committed_at"), - }, - group: "payroll", - enhanced_payroll: true, - }, - lost_sales: { - title: i18n.t("reportcenter.templates.lost_sales"), - subject: i18n.t("reportcenter.templates.lost_sales"), - key: "lost_sales", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_lost_sale"), - }, - group: "customers", - }, - load_level: { - title: i18n.t("reportcenter.templates.load_level"), - subject: i18n.t("reportcenter.templates.load_level"), - key: "load_level", - //idtype: "vendor", - disabled: false, - datedisable: true, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "jobs", - enhanced_payroll: true, - }, - open_orders_excel: { - title: i18n.t("reportcenter.templates.open_orders_excel"), - subject: i18n.t("reportcenter.templates.open_orders_excel"), - key: "open_orders_excel", - //idtype: "vendor", - reporttype: "excel", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), - }, - group: "jobs", - }, - ar_aging: { - title: i18n.t("reportcenter.templates.ar_aging"), - subject: i18n.t("reportcenter.templates.ar_aging"), - key: "ar_aging", - //idtype: "vendor", - disabled: false, - datedisable: true, - group: "customers", - }, - job_lifecycle_date_detail: { - title: i18n.t("reportcenter.templates.job_lifecycle_date_detail"), - subject: i18n.t("reportcenter.templates.job_lifecycle_date_detail"), - key: "job_lifecycle_date_detail", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "jobs", - }, - job_lifecycle_date_summary: { - title: i18n.t("reportcenter.templates.job_lifecycle_date_summary"), - subject: i18n.t("reportcenter.templates.job_lifecycle_date_summary"), - key: "job_lifecycle_date_summary", - //idtype: "vendor", - disabled: false, - rangeFilter: { - object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_invoiced"), - }, - group: "jobs", - }, + }, + mpi_eglass_auth: { + title: i18n.t("printcenter.jobs.mpi_eglass_auth"), + description: "", + key: "mpi_eglass_auth", + subject: i18n.t("printcenter.jobs.mpi_eglass_auth"), + disabled: false, + group: "pre", + regions: { + CA_MB: true } - : {}), - ...(!type || type === "courtesycarcontract" - ? { - courtesy_car_contract: { - title: i18n.t( - "printcenter.courtesycarcontract.courtesy_car_contract" - ), - description: "", - subject: i18n.t( - "printcenter.courtesycarcontract.courtesy_car_contract" - ), - key: "courtesy_car_contract", - disabled: false, - }, - courtesy_car_terms: { - title: i18n.t("printcenter.courtesycarcontract.courtesy_car_terms"), - description: "", - subject: i18n.t( - "printcenter.courtesycarcontract.courtesy_car_terms" - ), - key: "courtesy_car_terms", - disabled: false, - }, - courtesy_car_impound: { - title: i18n.t( - "printcenter.courtesycarcontract.courtesy_car_impound" - ), - description: "", - subject: i18n.t( - "printcenter.courtesycarcontract.courtesy_car_impound" - ), - key: "courtesy_car_impound", - disabled: false, - }, + }, + mpi_animal_checklist: { + title: i18n.t("printcenter.jobs.mpi_animal_checklist"), + description: "", + key: "mpi_animal_checklist", + subject: i18n.t("printcenter.jobs.mpi_animal_checklist"), + disabled: false, + group: "pre", + regions: { + CA_MB: true } - : {}), - ...(!type || type === "courtesycar" - ? { - courtesy_car_inventory: { - title: i18n.t( - "printcenter.courtesycarcontract.courtesy_car_inventory" - ), - description: "", - subject: i18n.t( - "printcenter.courtesycarcontract.courtesy_car_inventory" - ), - key: "courtesy_car_inventory", - disabled: false, - }, + }, + ab_proof_of_loss: { + title: i18n.t("printcenter.jobs.ab_proof_of_loss"), + description: "", + key: "ab_proof_of_loss", + subject: i18n.t("printcenter.jobs.ab_proof_of_loss"), + disabled: false, + group: "pre", + regions: { + CA_AB: true } - : {}), - ...(!type || type === "bill" - ? { - inhouse_invoice: { - title: i18n.t("printcenter.bills.inhouse_invoice"), - description: "", - subject: i18n.t("printcenter.bills.inhouse_invoice"), - key: "inhouse_invoice", - disabled: false, - }, - } - : {}), - ...(!type || type === "timeticket" - ? { - // timetickets: { - // title: i18n.t("printcenter.timetickets.timetickets"), - // description: "", - // subject: `${i18n.t("printcenter.timetickets.timetickets")} - ${ - // context && context.job && context.job.ro_number - // }`, - // key: "timetickets", - // disabled: false, - // }, - } - : {}), - ...(!type || type === "messaging" - ? { - conversation_list: { - title: i18n.t("messaging.render.conversation_list"), - description: "", - subject: i18n.t("messaging.render.conversation_list"), - key: "conversation_list", - disabled: false, - }, - } - : {}), - ...(!type || type === "vendor" - ? { - purchases_by_vendor_detailed: { - title: i18n.t("printcenter.vendors.purchases_by_vendor_detailed"), - description: "", - subject: i18n.t("printcenter.vendors.purchases_by_vendor_detailed"), - key: "purchases_by_vendor_detailed", - disabled: false, - }, - purchases_by_vendor_summary: { - title: i18n.t("printcenter.vendors.purchases_by_vendor_summary"), - description: "", - subject: i18n.t("printcenter.vendors.purchases_by_vendor_summary"), - key: "purchases_by_vendor_summary", - disabled: false, - }, - } - : {}), - ...(!type || type === "production" - ? { - production_by_last_name: { - title: i18n.t("reportcenter.templates.production_by_last_name"), - description: "", - subject: i18n.t("reportcenter.templates.production_by_last_name"), - key: "production_by_last_name", - //idtype: "vendor", - disabled: false, - }, - production_by_repair_status: { - title: i18n.t("reportcenter.templates.production_by_repair_status"), - description: "", - subject: i18n.t( - "reportcenter.templates.production_by_repair_status" - ), - key: "production_by_repair_status", - //idtype: "vendor", - disabled: false, - }, - production_by_target_date: { - title: i18n.t("reportcenter.templates.production_by_target_date"), - description: "", - subject: i18n.t("reportcenter.templates.production_by_target_date"), - key: "production_by_target_date", - //idtype: "vendor", - disabled: false, - }, - production_by_ro: { - title: i18n.t("reportcenter.templates.production_by_ro"), - description: "", - subject: i18n.t("reportcenter.templates.production_by_ro"), - key: "production_by_ro", - //idtype: "vendor", - disabled: false, - }, - production_by_csr: { - title: i18n.t("reportcenter.templates.production_by_csr"), - description: "", - subject: i18n.t("reportcenter.templates.production_by_csr"), - key: "production_by_csr", - //idtype: "vendor", - disabled: false, - }, - production_by_category: { - title: i18n.t("reportcenter.templates.production_by_category"), - description: "", - subject: i18n.t("reportcenter.templates.production_by_category"), - key: "production_by_category", - //idtype: "vendor", - disabled: false, - }, - production_by_technician: { - title: i18n.t("reportcenter.templates.production_by_technician"), - description: "", - subject: i18n.t("reportcenter.templates.production_by_technician"), - key: "production_by_technician", - //idtype: "vendor", - disabled: false, - }, - } - : {}), - ...(!type || type === "special" - ? { - ca_bc_etf_table: { - title: i18n.t("printcenter.payments.ca_bc_etf_table"), - description: "", - subject: i18n.t("printcenter.payments.ca_bc_etf_table"), - key: "ca_bc_etf_table", - disabled: false, - }, - exported_payroll: { - title: i18n.t("printcenter.payments.exported_payroll"), - description: "", - subject: i18n.t("printcenter.payments.exported_payroll"), - key: "exported_payroll", - disabled: false, - }, - attendance_detail_csv: { - title: i18n.t("printcenter.special.attendance_detail_csv"), - description: "", - subject: i18n.t("printcenter.special.attendance_detail_csv"), - key: "attendance_detail_csv", - disabled: false, - }, - production_by_technician_one: { - title: i18n.t( - "reportcenter.templates.production_by_technician_one" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.production_by_technician_one" - ), - key: "production_by_technician_one", - //idtype: "vendor", - disabled: false, - }, - production_by_category_one: { - title: i18n.t("reportcenter.templates.production_by_category_one"), - description: "", - subject: i18n.t( - "reportcenter.templates.production_by_category_one" - ), - key: "production_by_category_one", - //idtype: "vendor", - disabled: false, - }, - production_by_repair_status_one: { - title: i18n.t( - "reportcenter.templates.production_by_repair_status_one" - ), - description: "", - subject: i18n.t( - "reportcenter.templates.production_by_repair_status_one" - ), - key: "production_by_repair_status_one", - //idtype: "vendor", - disabled: false, - }, - } - : {}), - }; + }, + // parts_label_multi: { + // title: i18n.t("printcenter.jobs.parts_label_multi"), + // description: "", + // key: "parts_label_multi", + // subject: i18n.t("printcenter.jobs.parts_label_multi"), + // disabled: false, + // group: "ro", + // }, + iou_form: { + title: i18n.t("printcenter.jobs.iou_form"), + description: "", + subject: i18n.t("printcenter.jobs.iou_form"), + key: "iou_form", + disabled: false, + group: "post" + }, + lag_time_ro: { + title: i18n.t("printcenter.jobs.lag_time_ro"), + description: "", + subject: i18n.t("printcenter.jobs.lag_time_ro"), + key: "lag_time_ro", + disabled: false, + group: "ro" + }, + rental_reservation: { + title: i18n.t("printcenter.jobs.rental_reservation"), + description: "", + subject: i18n.t("printcenter.jobs.rental_reservation"), + key: "rental_reservation", + disabled: false, + group: "pre" + }, + timetickets_ro: { + title: i18n.t("printcenter.jobs.timetickets_ro"), + description: "", + subject: i18n.t("printcenter.jobs.timetickets_ro"), + key: "timetickets_ro", + disabled: false, + group: "financial" + }, + dms_posting_sheet: { + title: i18n.t("printcenter.jobs.dms_posting_sheet"), + description: "", + subject: i18n.t("printcenter.jobs.dms_posting_sheet"), + key: "dms_posting_sheet", + disabled: false, + group: "financial", + dms: true + }, + worksheet_sorted_by_team: { + title: i18n.t("printcenter.jobs.worksheet_sorted_by_team"), + description: "", + subject: i18n.t("printcenter.jobs.worksheet_sorted_by_team"), + key: "worksheet_sorted_by_team", + disabled: false, + group: "worksheet", + enhanced_payroll: true + }, + committed_timetickets_ro: { + title: i18n.t("printcenter.jobs.committed_timetickets_ro"), + description: "", + subject: i18n.t("printcenter.jobs.committed_timetickets_ro"), + key: "committed_timetickets_ro", + disabled: false, + group: "financial", + enhanced_payroll: true + }, + job_lifecycle_ro: { + title: i18n.t("printcenter.jobs.job_lifecycle_ro"), + description: "", + subject: i18n.t("printcenter.jobs.job_lifecycle_ro"), + key: "job_lifecycle_ro", + disabled: false, + group: "post" + } + } + : {}), + ...(!type || type === "job_special" + ? { + special_thirdpartypayer: { + title: i18n.t("printcenter.jobs.thirdpartypayer"), + description: "", + key: "special_thirdpartypayer", + disabled: false + }, + folder_label_multiple: { + title: i18n.t("printcenter.jobs.folder_label_multiple"), + description: "", + key: "folder_label_multiple", + disabled: false + }, + parts_label_multiple: { + title: i18n.t("printcenter.jobs.parts_label_multiple"), + description: "", + key: "parts_label_multiple", + disabled: false + }, + parts_invoice_label_single: { + title: i18n.t("printcenter.jobs.parts_invoice_label_single"), + description: "", + key: "parts_invoice_label_single", + disabled: false, + ignoreCustomMargins: true + }, + csi_invitation_action: { + title: i18n.t("printcenter.jobs.csi_invitation_action"), + description: "", + key: "csi_invitation_action", + subject: i18n.t("printcenter.jobs.csi_invitation_action"), + disabled: false + }, + individual_job_note: { + title: i18n.t("printcenter.jobs.individual_job_note"), + description: "", + key: "individual_job_note", + subject: i18n.t("printcenter.subjects.jobs.individual_job_note", { + ro_number: (context && context.ro_number) || "" + }), + disabled: false + }, + parts_dispatch: { + title: i18n.t("printcenter.jobs.parts_dispatch"), + description: "", + key: "parts_dispatch", + subject: i18n.t("printcenter.subjects.jobs.parts_dispatch", { + ro_number: (context && context.ro_number) || "" + }), + disabled: false + } + } + : {}), + ...(!type || type === "appointment" + ? { + appointment_confirmation: { + title: i18n.t("printcenter.appointments.appointment_confirmation"), + description: "", + subject: i18n.t("printcenter.appointments.appointment_confirmation"), + key: "appointment_confirmation", + disabled: false + } + } + : {}), + ...(!type || type === "partsorder" + ? { + parts_order: { + title: i18n.t("printcenter.jobs.parts_order"), + description: "", + key: "parts_order", + subject: i18n.t("printcenter.subjects.jobs.parts_order", { + ro_number: context && context.job && context.job.ro_number, + name: ( + (context && context.job && context.job.ownr_ln) || + (context && context.job && context.job.ownr_co_nm) || + "" + ).trim() + }), + disabled: false + }, + sublet_order: { + title: i18n.t("printcenter.jobs.sublet_order"), + description: "", + key: "sublet_order", + subject: i18n.t("printcenter.subjects.jobs.sublet_order", { + ro_number: context && context.job && context.job.ro_number, + name: ( + (context && context.job && context.job.ownr_ln) || + (context && context.job && context.job.ownr_co_nm) || + "" + ).trim() + }), + disabled: false + }, + parts_return_slip: { + title: i18n.t("printcenter.jobs.parts_return_slip"), + subject: i18n.t("printcenter.subjects.jobs.parts_return_slip", { + ro_number: context && context.job && context.job.ro_number, + name: ( + (context && context.job && context.job.ownr_ln) || + (context && context.job && context.job.ownr_co_nm) || + "" + ).trim() + }), + description: "", + key: "parts_return_slip", + disabled: false + } + } + : {}), + ...(!type || type === "payment" + ? { + payment_receipt: { + title: i18n.t("printcenter.jobs.payment_receipt"), + description: "", + subject: i18n.t("printcenter.jobs.payment_receipt"), + key: "payment_receipt", + disabled: false + } + } + : {}), + ...(!type || type === "csi" ? {} : {}), + ...(!type || type === "report_center" + ? { + hours_sold_detail_closed: { + title: i18n.t("reportcenter.templates.hours_sold_detail_closed"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_detail_closed"), + key: "hours_sold_detail_closed", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + hours_sold_detail_closed_ins_co: { + title: i18n.t("reportcenter.templates.hours_sold_detail_closed_ins_co"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_detail_closed_ins_co"), + key: "hours_sold_detail_closed_ins_co", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + hours_sold_summary_closed: { + title: i18n.t("reportcenter.templates.hours_sold_summary_closed"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_summary_closed"), + key: "hours_sold_summary_closed", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + hours_sold_summary_closed_ins_co: { + title: i18n.t("reportcenter.templates.hours_sold_summary_closed_ins_co"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_summary_closed_ins_co"), + key: "hours_sold_summary_closed_ins_co", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + hours_sold_detail_open: { + title: i18n.t("reportcenter.templates.hours_sold_detail_open"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_detail_open"), + key: "hours_sold_detail_open", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "sales" + }, + hours_sold_detail_open_ins_co: { + title: i18n.t("reportcenter.templates.hours_sold_detail_open_ins_co"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_detail_open_ins_co"), + key: "hours_sold_detail_open_ins_co", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "sales" + }, + hours_sold_summary_open: { + title: i18n.t("reportcenter.templates.hours_sold_summary_open"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_summary_open"), + key: "hours_sold_summary_open", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "sales" + }, + hours_sold_summary_open_ins_co: { + title: i18n.t("reportcenter.templates.hours_sold_summary_open_ins_co"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_summary_open_ins_co"), + key: "hours_sold_summary_open_ins_co", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "sales" + }, + hours_sold_detail_closed_csr: { + title: i18n.t("reportcenter.templates.hours_sold_detail_closed_csr"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_detail_closed_csr"), + key: "hours_sold_detail_closed_csr", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + hours_sold_detail_open_csr: { + title: i18n.t("reportcenter.templates.hours_sold_detail_open_csr"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_detail_open_csr"), + key: "hours_sold_detail_open_csr", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "sales" + }, + hours_sold_summary_closed_csr: { + title: i18n.t("reportcenter.templates.hours_sold_summary_closed_csr"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_summary_closed_csr"), + key: "hours_sold_summary_closed_csr", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + hours_sold_summary_open_csr: { + title: i18n.t("reportcenter.templates.hours_sold_summary_open_csr"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_summary_open_csr"), + key: "hours_sold_summary_open_csr", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + hours_sold_detail_closed_estimator: { + title: i18n.t("reportcenter.templates.hours_sold_detail_closed_estimator"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_detail_closed_estimator"), + key: "hours_sold_detail_closed_estimator", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + hours_sold_detail_open_estimator: { + title: i18n.t("reportcenter.templates.hours_sold_detail_open_estimator"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_detail_open_estimator"), + key: "hours_sold_detail_open_estimator", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "sales" + }, + hours_sold_summary_closed_estimator: { + title: i18n.t("reportcenter.templates.hours_sold_summary_closed_estimator"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_summary_closed_estimator"), + key: "hours_sold_summary_closed_estimator", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + hours_sold_summary_open_estimator: { + title: i18n.t("reportcenter.templates.hours_sold_summary_open_estimator"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_summary_open_estimator"), + key: "hours_sold_summary_open_estimator", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + hours_sold_summary_open_status: { + title: i18n.t("reportcenter.templates.hours_sold_summary_open_status"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_summary_open_status"), + key: "hours_sold_summary_open_status", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + hours_sold_summary_closed_status: { + title: i18n.t("reportcenter.templates.hours_sold_summary_closed_status"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_summary_closed_status"), + key: "hours_sold_summary_closed_status", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + hours_sold_detail_open_status: { + title: i18n.t("reportcenter.templates.hours_sold_detail_open_status"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_detail_open_status"), + key: "hours_sold_detail_open_status", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + hours_sold_detail_closed_status: { + title: i18n.t("reportcenter.templates.hours_sold_detail_closed_status"), + description: "", + subject: i18n.t("reportcenter.templates.hours_sold_detail_closed_status"), + key: "hours_sold_detail_closed_status", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + purchases_by_date_range_detail: { + title: i18n.t("reportcenter.templates.purchases_by_date_range_detail"), + subject: i18n.t("reportcenter.templates.purchases_by_date_range_detail"), + key: "purchases_by_date_range_detail", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.bills"), + field: i18n.t("bills.fields.date") + }, + group: "purchases" + }, + purchases_by_date_range_summary: { + title: i18n.t("reportcenter.templates.purchases_by_date_range_summary"), + subject: i18n.t("reportcenter.templates.purchases_by_date_range_summary"), + key: "purchases_by_date_range_summary", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.bills"), + field: i18n.t("bills.fields.date") + }, + group: "purchases" + }, + purchases_by_vendor_detailed_date_range: { + title: i18n.t("reportcenter.templates.purchases_by_vendor_detailed_date_range"), + subject: i18n.t("reportcenter.templates.purchases_by_vendor_detailed_date_range"), + key: "purchases_by_vendor_detailed_date_range", + idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.bills"), + field: i18n.t("bills.fields.date") + }, + group: "purchases" + }, + purchases_by_vendor_summary_date_range: { + title: i18n.t("reportcenter.templates.purchases_by_vendor_summary_date_range"), + subject: i18n.t("reportcenter.templates.purchases_by_vendor_summary_date_range"), + key: "purchases_by_vendor_summary_date_range", + idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.bills"), + field: i18n.t("bills.fields.date") + }, + group: "purchases" + }, + purchases_by_cost_center_detail: { + title: i18n.t("reportcenter.templates.purchases_by_cost_center_detail"), + description: "", + subject: i18n.t("reportcenter.templates.purchases_by_cost_center_detail"), + key: "purchases_by_cost_center_detail", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.bills"), + field: i18n.t("bills.fields.date") + }, + group: "purchases" + }, + purchases_by_cost_center_summary: { + title: i18n.t("reportcenter.templates.purchases_by_cost_center_summary"), + description: "", + subject: i18n.t("reportcenter.templates.purchases_by_cost_center_summary"), + key: "purchases_by_cost_center_summary", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.bills"), + field: i18n.t("bills.fields.date") + }, + group: "purchases" + }, + purchases_grouped_by_vendor_detailed: { + title: i18n.t("reportcenter.templates.purchases_grouped_by_vendor_detailed"), + description: "", + subject: i18n.t("reportcenter.templates.purchases_grouped_by_vendor_detailed"), + key: "purchases_grouped_by_vendor_detailed", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("bills.fields.date") + }, + group: "purchases" + }, + purchases_grouped_by_vendor_summary: { + title: i18n.t("reportcenter.templates.purchases_grouped_by_vendor_summary"), + description: "", + subject: i18n.t("reportcenter.templates.purchases_grouped_by_vendor_summary"), + key: "purchases_grouped_by_vendor_summary", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("bills.fields.date") + }, + group: "purchases" + }, + job_costing_ro_date_summary: { + title: i18n.t("reportcenter.templates.job_costing_ro_date_summary"), + description: "", + subject: i18n.t("reportcenter.templates.job_costing_ro_date_summary"), + key: "job_costing_ro_date_summary", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "jobs" + }, + job_costing_ro_csr: { + title: i18n.t("reportcenter.templates.job_costing_ro_csr"), + description: "", + subject: i18n.t("reportcenter.templates.job_costing_ro_csr"), + key: "job_costing_ro_csr", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "jobs" + }, + job_costing_ro_ins_co: { + title: i18n.t("reportcenter.templates.job_costing_ro_ins_co"), + description: "", + subject: i18n.t("reportcenter.templates.job_costing_ro_ins_co"), + key: "job_costing_ro_ins_co", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "jobs" + }, + job_costing_ro_date_detail: { + title: i18n.t("reportcenter.templates.job_costing_ro_date_detail"), + description: "", + subject: i18n.t("reportcenter.templates.job_costing_ro_date_detail"), + key: "job_costing_ro_date_detail", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "jobs" + }, + job_costing_ro_estimator: { + title: i18n.t("reportcenter.templates.job_costing_ro_estimator"), + description: "", + subject: i18n.t("reportcenter.templates.job_costing_ro_estimator"), + key: "job_costing_ro_estimator", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "jobs" + }, + payments_by_date: { + title: i18n.t("reportcenter.templates.payments_by_date"), + subject: i18n.t("reportcenter.templates.payments_by_date"), + key: "payments_by_date", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.payments"), + field: i18n.t("payments.fields.date") + }, + group: "customers" + }, + payments_by_date_type: { + title: i18n.t("reportcenter.templates.payments_by_date_type"), + subject: i18n.t("reportcenter.templates.payments_by_date_type"), + key: "payments_by_date_type", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.payments"), + field: i18n.t("payments.fields.date") + }, + group: "customers" + }, + schedule: { + title: i18n.t("reportcenter.templates.schedule"), + subject: i18n.t("reportcenter.templates.schedule"), + key: "schedule", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.appointments"), + field: i18n.t("appointments.fields.time") + }, + group: "customers" + }, + timetickets: { + title: i18n.t("reportcenter.templates.timetickets"), + subject: i18n.t("reportcenter.templates.timetickets"), + key: "timetickets", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.timetickets"), + field: i18n.t("timetickets.fields.date") + }, + group: "payroll" + }, + timetickets_employee: { + title: i18n.t("reportcenter.templates.timetickets_employee"), + subject: i18n.t("reportcenter.templates.timetickets_employee"), + key: "timetickets_employee", + idtype: "employee", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.timetickets"), + field: i18n.t("timetickets.fields.date") + }, + group: "payroll" + }, + attendance_detail: { + title: i18n.t("reportcenter.templates.attendance_detail"), + subject: i18n.t("reportcenter.templates.attendance_detail"), + key: "attendance_detail", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.timetickets"), + field: i18n.t("timetickets.fields.date") + }, + group: "payroll" + }, + attendance_summary: { + title: i18n.t("reportcenter.templates.attendance_summary"), + subject: i18n.t("reportcenter.templates.attendance_summary"), + key: "attendance_summary", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.timetickets"), + field: i18n.t("timetickets.fields.date") + }, + group: "payroll" + }, + attendance_employee: { + title: i18n.t("reportcenter.templates.attendance_employee"), + subject: i18n.t("reportcenter.templates.attendance_employee"), + key: "attendance_employee", + idtype: "employee", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.timetickets"), + field: i18n.t("timetickets.fields.date") + }, + group: "payroll" + }, + timetickets_summary: { + title: i18n.t("reportcenter.templates.timetickets_summary"), + subject: i18n.t("reportcenter.templates.timetickets_summary"), + key: "timetickets_summary", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.timetickets"), + field: i18n.t("timetickets.fields.date") + }, + group: "payroll" + }, + estimator_detail: { + title: i18n.t("reportcenter.templates.estimator_detail"), + description: "", + subject: i18n.t("reportcenter.templates.estimator_detail"), + key: "estimator_detail", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + estimator_summary: { + title: i18n.t("reportcenter.templates.estimator_summary"), + description: "", + subject: i18n.t("reportcenter.templates.estimator_summary"), + key: "estimator_summary", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + supplement_ratio_ins_co: { + title: i18n.t("reportcenter.templates.supplement_ratio_ins_co"), + description: "", + subject: i18n.t("reportcenter.templates.supplement_ratio_ins_co"), + key: "supplement_ratio_ins_co", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + credits_not_received_date: { + title: i18n.t("reportcenter.templates.credits_not_received_date"), + description: "", + subject: i18n.t("reportcenter.templates.credits_not_received_date"), + key: "credits_not_received_date", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "purchases" + }, + void_ros: { + title: i18n.t("reportcenter.templates.void_ros"), + description: "", + subject: i18n.t("reportcenter.templates.void_ros"), + key: "void_ros", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_void") + }, + group: "sales" + }, + gsr_by_csr: { + title: i18n.t("reportcenter.templates.gsr_by_csr"), + description: "", + subject: i18n.t("reportcenter.templates.gsr_by_csr"), + key: "gsr_by_csr", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + gsr_by_make: { + title: i18n.t("reportcenter.templates.gsr_by_make"), + description: "", + subject: i18n.t("reportcenter.templates.gsr_by_make"), + key: "gsr_by_make", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + gsr_by_delivery_date: { + title: i18n.t("reportcenter.templates.gsr_by_delivery_date"), + description: "", + subject: i18n.t("reportcenter.templates.gsr_by_delivery_date"), + key: "gsr_by_delivery_date", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.actual_delivery") + }, + group: "sales" + }, + gsr_by_referral: { + title: i18n.t("reportcenter.templates.gsr_by_referral"), + description: "", + subject: i18n.t("reportcenter.templates.gsr_by_referral"), + key: "gsr_by_referral", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + gsr_by_ro: { + title: i18n.t("reportcenter.templates.gsr_by_ro"), + description: "", + subject: i18n.t("reportcenter.templates.gsr_by_ro"), + key: "gsr_by_ro", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + gsr_by_ins_co: { + title: i18n.t("reportcenter.templates.gsr_by_ins_co"), + description: "", + subject: i18n.t("reportcenter.templates.gsr_by_ins_co"), + key: "gsr_by_ins_co", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + gsr_by_exported_date: { + title: i18n.t("reportcenter.templates.gsr_by_exported_date"), + description: "", + subject: i18n.t("reportcenter.templates.gsr_by_exported_date"), + key: "gsr_by_exported_date", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_exported") + }, + group: "sales" + }, + gsr_by_estimator: { + title: i18n.t("reportcenter.templates.gsr_by_estimator"), + description: "", + subject: i18n.t("reportcenter.templates.gsr_by_estimator"), + key: "gsr_by_estimator", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + gsr_by_category: { + title: i18n.t("reportcenter.templates.gsr_by_category"), + description: "", + subject: i18n.t("reportcenter.templates.gsr_by_category"), + key: "gsr_by_category", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + gsr_by_ats: { + title: i18n.t("reportcenter.templates.gsr_by_ats"), + description: "", + subject: i18n.t("reportcenter.templates.gsr_by_ats"), + key: "gsr_by_ats", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + gsr_labor_only: { + title: i18n.t("reportcenter.templates.gsr_labor_only"), + description: "", + subject: i18n.t("reportcenter.templates.gsr_labor_only"), + key: "gsr_labor_only", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + open_orders: { + title: i18n.t("reportcenter.templates.open_orders"), + description: "", + subject: i18n.t("reportcenter.templates.open_orders"), + key: "open_orders", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "jobs" + }, + open_orders_status: { + title: i18n.t("reportcenter.templates.open_orders_status"), + description: "", + subject: i18n.t("reportcenter.templates.open_orders_status"), + key: "open_orders_status", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "jobs" + }, + open_orders_csr: { + title: i18n.t("reportcenter.templates.open_orders_csr"), + description: "", + subject: i18n.t("reportcenter.templates.open_orders_csr"), + key: "open_orders_csr", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "jobs" + }, + open_orders_specific_csr: { + title: i18n.t("reportcenter.templates.open_orders_specific_csr"), + description: "", + subject: i18n.t("reportcenter.templates.open_orders_specific_csr"), + key: "open_orders_specific_csr", + idtype: "employee", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "jobs" + }, + open_orders_estimator: { + title: i18n.t("reportcenter.templates.open_orders_estimator"), + description: "", + subject: i18n.t("reportcenter.templates.open_orders_estimator"), + key: "open_orders_estimator", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "jobs" + }, + open_orders_ins_co: { + title: i18n.t("reportcenter.templates.open_orders_ins_co"), + description: "", + subject: i18n.t("reportcenter.templates.open_orders_ins_co"), + key: "open_orders_ins_co", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "jobs" + }, + open_orders_referral: { + title: i18n.t("reportcenter.templates.open_orders_referral"), + description: "", + subject: i18n.t("reportcenter.templates.open_orders_referral"), + key: "open_orders_referral", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "jobs" + }, + export_payables: { + title: i18n.t("reportcenter.templates.export_payables"), + description: "", + subject: i18n.t("reportcenter.templates.export_payables"), + key: "export_payables", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.exportlogs"), + field: i18n.t("exportlogs.fields.createdat") + }, + group: "purchases" + }, + export_payments: { + title: i18n.t("reportcenter.templates.export_payments"), + description: "", + subject: i18n.t("reportcenter.templates.export_payments"), + key: "export_payments", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.exportlogs"), + field: i18n.t("exportlogs.fields.createdat") + }, + group: "customers" + }, + export_receivables: { + title: i18n.t("reportcenter.templates.export_receivables"), + description: "", + subject: i18n.t("reportcenter.templates.export_receivables"), + key: "export_receivables", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.exportlogs"), + field: i18n.t("exportlogs.fields.createdat") + }, + group: "sales" + }, + parts_backorder: { + title: i18n.t("reportcenter.templates.parts_backorder"), + description: "", + subject: i18n.t("reportcenter.templates.parts_backorder"), + key: "parts_backorder", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.actual_in") + }, + group: "purchases" + }, + thank_you_date: { + title: i18n.t("reportcenter.templates.thank_you_date"), + description: "", + subject: i18n.t("reportcenter.templates.thank_you_date"), + key: "thank_you_date", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "customers" + }, + unclaimed_hrs: { + title: i18n.t("reportcenter.templates.unclaimed_hrs"), + description: "", + subject: i18n.t("reportcenter.templates.unclaimed_hrs"), + key: "unclaimed_hrs", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "payroll" + }, + work_in_progress_jobs_excel: { + title: i18n.t("reportcenter.templates.work_in_progress_jobs"), + subject: i18n.t("reportcenter.templates.work_in_progress_jobs"), + key: "work_in_progress_jobs_excel", + //idtype: "vendor", + reporttype: "excel", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "jobs" + }, + work_in_progress_labour: { + title: i18n.t("reportcenter.templates.work_in_progress_labour"), + description: "", + subject: i18n.t("reportcenter.templates.work_in_progress_labour"), + key: "work_in_progress_labour", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "jobs" + }, + work_in_progress_committed_labour: { + title: i18n.t("reportcenter.templates.work_in_progress_committed_labour"), + description: "", + subject: i18n.t("reportcenter.templates.work_in_progress_committed_labour"), + key: "work_in_progress_committed_labour", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "jobs", + enhanced_payroll: true + }, + work_in_progress_payables: { + title: i18n.t("reportcenter.templates.work_in_progress_payables"), + description: "", + subject: i18n.t("reportcenter.templates.work_in_progress_payables"), + key: "work_in_progress_payables", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "jobs" + }, + lag_time: { + title: i18n.t("reportcenter.templates.lag_time"), + description: "", + subject: i18n.t("reportcenter.templates.lag_time"), + key: "lag_time", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "jobs" + }, + parts_not_recieved: { + title: i18n.t("reportcenter.templates.parts_not_recieved"), + description: "", + subject: i18n.t("reportcenter.templates.parts_not_recieved"), + key: "parts_not_recieved", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.parts_orders"), + field: i18n.t("parts_orders.fields.order_date") + }, + group: "purchases" + }, + parts_not_recieved_vendor: { + title: i18n.t("reportcenter.templates.parts_not_recieved_vendor"), + description: "", + subject: i18n.t("reportcenter.templates.parts_not_recieved_vendor"), + key: "parts_not_recieved_vendor", + idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.parts_orders"), + field: i18n.t("parts_orders.fields.order_date") + }, + group: "purchases" + }, + scoreboard_detail: { + title: i18n.t("reportcenter.templates.scoreboard_detail"), + description: "", + subject: i18n.t("reportcenter.templates.scoreboard_detail"), + key: "scoreboard_detail", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.scoreboard"), + field: i18n.t("scoreboard.fields.date") + }, + group: "payroll" + }, + scoreboard_summary: { + title: i18n.t("reportcenter.templates.scoreboard_summary"), + description: "", + subject: i18n.t("reportcenter.templates.scoreboard_summary"), + key: "scoreboard_summary", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.scoreboard"), + field: i18n.t("scoreboard.fields.date") + }, + group: "payroll" + }, + anticipated_revenue: { + title: i18n.t("reportcenter.templates.anticipated_revenue"), + description: "", + subject: i18n.t("reportcenter.templates.anticipated_revenue"), + key: "anticipated_revenue", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.scheduled_completion") // Also date invoice. + }, + group: "sales" + }, + csi: { + title: i18n.t("reportcenter.templates.csi"), + description: "", + subject: i18n.t("reportcenter.templates.csi"), + key: "csi", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.csi"), + field: i18n.t("csi.fields.created_at") // Also date invoice. + }, + group: "customers" + }, + estimates_written_converted: { + title: i18n.t("reportcenter.templates.estimates_written_converted"), + description: "", + subject: i18n.t("reportcenter.templates.estimates_written_converted"), + key: "estimates_written_converted", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + "/" + i18n.t("jobs.fields.date_invoiced") // Also date invoice. + }, + group: "sales" + }, + credits_not_received_date_vendorid: { + title: i18n.t("reportcenter.templates.credits_not_received_date_vendorid"), + subject: i18n.t("reportcenter.templates.credits_not_received_date_vendorid"), + key: "credits_not_received_date_vendorid", + idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "purchases" + }, + jobs_reconcile: { + title: i18n.t("reportcenter.templates.jobs_reconcile"), + subject: i18n.t("reportcenter.templates.jobs_reconcile"), + key: "jobs_reconcile", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "jobs" + }, + parts_received_not_scheduled: { + title: i18n.t("reportcenter.templates.parts_received_not_scheduled"), + subject: i18n.t("reportcenter.templates.parts_received_not_scheduled"), + key: "parts_received_not_scheduled", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "jobs" + }, + psr_by_make: { + title: i18n.t("reportcenter.templates.psr_by_make"), + subject: i18n.t("reportcenter.templates.psr_by_make"), + key: "psr_by_make", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "sales" + }, + cycle_time_analysis: { + title: i18n.t("reportcenter.templates.cycle_time_analysis"), + subject: i18n.t("reportcenter.templates.cycle_time_analysis"), + key: "cycle_time_analysis", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.actual_completion") + }, + group: "jobs" + }, + returns_grouped_by_vendor_summary: { + title: i18n.t("reportcenter.templates.returns_grouped_by_vendor_summary"), + subject: i18n.t("reportcenter.templates.returns_grouped_by_vendor_summary"), + key: "returns_grouped_by_vendor_summary", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.parts_orders"), + field: i18n.t("parts_orders.fields.order_date") + }, + group: "jobs" + }, + returns_grouped_by_vendor_detailed: { + title: i18n.t("reportcenter.templates.returns_grouped_by_vendor_detailed"), + subject: i18n.t("reportcenter.templates.returns_grouped_by_vendor_detailed"), + key: "returns_grouped_by_vendor_detailed", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.parts_orders"), + field: i18n.t("parts_orders.fields.order_date") + }, + group: "jobs" + }, + scheduled_parts_list: { + title: i18n.t("reportcenter.templates.scheduled_parts_list"), + subject: i18n.t("reportcenter.templates.scheduled_parts_list"), + key: "scheduled_parts_list", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.scheduled_in") + }, + group: "jobs" + }, + jobs_completed_not_invoiced: { + title: i18n.t("reportcenter.templates.jobs_completed_not_invoiced"), + subject: i18n.t("reportcenter.templates.jobs_completed_not_invoiced"), + key: "jobs_completed_not_invoiced", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "jobs" + }, + jobs_invoiced_not_exported: { + title: i18n.t("reportcenter.templates.jobs_invoiced_not_exported"), + subject: i18n.t("reportcenter.templates.jobs_invoiced_not_exported"), + key: "jobs_invoiced_not_exported", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "jobs" + }, + purchase_return_ratio_grouped_by_vendor_detail: { + title: i18n.t("reportcenter.templates.purchase_return_ratio_grouped_by_vendor_detail"), + subject: i18n.t("reportcenter.templates.purchase_return_ratio_grouped_by_vendor_detail"), + key: "purchase_return_ratio_grouped_by_vendor_detail", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.bills"), + field: i18n.t("bills.fields.date") + }, + group: "purchases" + }, + purchase_return_ratio_grouped_by_vendor_summary: { + title: i18n.t("reportcenter.templates.purchase_return_ratio_grouped_by_vendor_summary"), + subject: i18n.t("reportcenter.templates.purchase_return_ratio_grouped_by_vendor_summary"), + key: "purchase_return_ratio_grouped_by_vendor_summary", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.bills"), + field: i18n.t("bills.fields.date") + }, + group: "purchases" + }, + production_over_time: { + title: i18n.t("reportcenter.templates.production_over_time"), + subject: i18n.t("reportcenter.templates.production_over_time"), + key: "production_over_time", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.actual_in") + }, + group: "jobs" + }, + customer_list: { + title: i18n.t("reportcenter.templates.customer_list"), + subject: i18n.t("reportcenter.templates.customer_list"), + key: "customer_list", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "customers" + }, + exported_gsr_by_ro: { + title: i18n.t("reportcenter.templates.exported_gsr_by_ro"), + subject: i18n.t("reportcenter.templates.exported_gsr_by_ro"), + key: "exported_gsr_by_ro", + //idtype: "vendor", + reporttype: "excel", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_exported") + }, + group: "sales" + }, + exported_gsr_by_ro_labor: { + title: i18n.t("reportcenter.templates.exported_gsr_by_ro_labor"), + subject: i18n.t("reportcenter.templates.exported_gsr_by_ro_labor"), + key: "exported_gsr_by_ro_labor", + //idtype: "vendor", + reporttype: "excel", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_exported") + }, + group: "sales" + }, + jobs_scheduled_completion: { + title: i18n.t("reportcenter.templates.jobs_scheduled_completion"), + subject: i18n.t("reportcenter.templates.jobs_scheduled_completion"), + key: "jobs_scheduled_completion", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.scheduled_completion") + }, + group: "jobs" + }, + committed_timetickets: { + title: i18n.t("reportcenter.templates.committed_timetickets"), + subject: i18n.t("reportcenter.templates.committed_timetickets"), + key: "committed_timetickets", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.timetickets"), + field: i18n.t("timetickets.fields.committed_at") + }, + group: "payroll", + enhanced_payroll: true + }, + committed_timetickets_employee: { + title: i18n.t("reportcenter.templates.committed_timetickets_employee"), + subject: i18n.t("reportcenter.templates.committed_timetickets_employee"), + key: "committed_timetickets_employee", + idtype: "employee", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.timetickets"), + field: i18n.t("timetickets.fields.committed_at") + }, + group: "payroll", + enhanced_payroll: true + }, + committed_timetickets_summary: { + title: i18n.t("reportcenter.templates.committed_timetickets_summary"), + subject: i18n.t("reportcenter.templates.committed_timetickets_summary"), + key: "committed_timetickets_summary", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.timetickets"), + field: i18n.t("timetickets.fields.committed_at") + }, + group: "payroll", + enhanced_payroll: true + }, + lost_sales: { + title: i18n.t("reportcenter.templates.lost_sales"), + subject: i18n.t("reportcenter.templates.lost_sales"), + key: "lost_sales", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_lost_sale") + }, + group: "customers" + }, + load_level: { + title: i18n.t("reportcenter.templates.load_level"), + subject: i18n.t("reportcenter.templates.load_level"), + key: "load_level", + //idtype: "vendor", + disabled: false, + datedisable: true, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "jobs", + enhanced_payroll: true + }, + open_orders_excel: { + title: i18n.t("reportcenter.templates.open_orders_excel"), + subject: i18n.t("reportcenter.templates.open_orders_excel"), + key: "open_orders_excel", + //idtype: "vendor", + reporttype: "excel", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open") + }, + group: "jobs" + }, + ar_aging: { + title: i18n.t("reportcenter.templates.ar_aging"), + subject: i18n.t("reportcenter.templates.ar_aging"), + key: "ar_aging", + //idtype: "vendor", + disabled: false, + datedisable: true, + group: "customers" + }, + job_lifecycle_date_detail: { + title: i18n.t("reportcenter.templates.job_lifecycle_date_detail"), + subject: i18n.t("reportcenter.templates.job_lifecycle_date_detail"), + key: "job_lifecycle_date_detail", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "jobs" + }, + job_lifecycle_date_summary: { + title: i18n.t("reportcenter.templates.job_lifecycle_date_summary"), + subject: i18n.t("reportcenter.templates.job_lifecycle_date_summary"), + key: "job_lifecycle_date_summary", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "jobs" + } + } + : {}), + ...(!type || type === "courtesycarcontract" + ? { + courtesy_car_contract: { + title: i18n.t("printcenter.courtesycarcontract.courtesy_car_contract"), + description: "", + subject: i18n.t("printcenter.courtesycarcontract.courtesy_car_contract"), + key: "courtesy_car_contract", + disabled: false + }, + courtesy_car_terms: { + title: i18n.t("printcenter.courtesycarcontract.courtesy_car_terms"), + description: "", + subject: i18n.t("printcenter.courtesycarcontract.courtesy_car_terms"), + key: "courtesy_car_terms", + disabled: false + }, + courtesy_car_impound: { + title: i18n.t("printcenter.courtesycarcontract.courtesy_car_impound"), + description: "", + subject: i18n.t("printcenter.courtesycarcontract.courtesy_car_impound"), + key: "courtesy_car_impound", + disabled: false + } + } + : {}), + ...(!type || type === "courtesycar" + ? { + courtesy_car_inventory: { + title: i18n.t("printcenter.courtesycarcontract.courtesy_car_inventory"), + description: "", + subject: i18n.t("printcenter.courtesycarcontract.courtesy_car_inventory"), + key: "courtesy_car_inventory", + disabled: false + } + } + : {}), + ...(!type || type === "bill" + ? { + inhouse_invoice: { + title: i18n.t("printcenter.bills.inhouse_invoice"), + description: "", + subject: i18n.t("printcenter.bills.inhouse_invoice"), + key: "inhouse_invoice", + disabled: false + } + } + : {}), + ...(!type || type === "timeticket" + ? { + // timetickets: { + // title: i18n.t("printcenter.timetickets.timetickets"), + // description: "", + // subject: `${i18n.t("printcenter.timetickets.timetickets")} - ${ + // context && context.job && context.job.ro_number + // }`, + // key: "timetickets", + // disabled: false, + // }, + } + : {}), + ...(!type || type === "messaging" + ? { + conversation_list: { + title: i18n.t("messaging.render.conversation_list"), + description: "", + subject: i18n.t("messaging.render.conversation_list"), + key: "conversation_list", + disabled: false + } + } + : {}), + ...(!type || type === "vendor" + ? { + purchases_by_vendor_detailed: { + title: i18n.t("printcenter.vendors.purchases_by_vendor_detailed"), + description: "", + subject: i18n.t("printcenter.vendors.purchases_by_vendor_detailed"), + key: "purchases_by_vendor_detailed", + disabled: false + }, + purchases_by_vendor_summary: { + title: i18n.t("printcenter.vendors.purchases_by_vendor_summary"), + description: "", + subject: i18n.t("printcenter.vendors.purchases_by_vendor_summary"), + key: "purchases_by_vendor_summary", + disabled: false + } + } + : {}), + ...(!type || type === "production" + ? { + production_by_last_name: { + title: i18n.t("reportcenter.templates.production_by_last_name"), + description: "", + subject: i18n.t("reportcenter.templates.production_by_last_name"), + key: "production_by_last_name", + //idtype: "vendor", + disabled: false + }, + production_by_repair_status: { + title: i18n.t("reportcenter.templates.production_by_repair_status"), + description: "", + subject: i18n.t("reportcenter.templates.production_by_repair_status"), + key: "production_by_repair_status", + //idtype: "vendor", + disabled: false + }, + production_by_target_date: { + title: i18n.t("reportcenter.templates.production_by_target_date"), + description: "", + subject: i18n.t("reportcenter.templates.production_by_target_date"), + key: "production_by_target_date", + //idtype: "vendor", + disabled: false + }, + production_by_ro: { + title: i18n.t("reportcenter.templates.production_by_ro"), + description: "", + subject: i18n.t("reportcenter.templates.production_by_ro"), + key: "production_by_ro", + //idtype: "vendor", + disabled: false + }, + production_by_csr: { + title: i18n.t("reportcenter.templates.production_by_csr"), + description: "", + subject: i18n.t("reportcenter.templates.production_by_csr"), + key: "production_by_csr", + //idtype: "vendor", + disabled: false + }, + production_by_category: { + title: i18n.t("reportcenter.templates.production_by_category"), + description: "", + subject: i18n.t("reportcenter.templates.production_by_category"), + key: "production_by_category", + //idtype: "vendor", + disabled: false + }, + production_by_technician: { + title: i18n.t("reportcenter.templates.production_by_technician"), + description: "", + subject: i18n.t("reportcenter.templates.production_by_technician"), + key: "production_by_technician", + //idtype: "vendor", + disabled: false + } + } + : {}), + ...(!type || type === "special" + ? { + ca_bc_etf_table: { + title: i18n.t("printcenter.payments.ca_bc_etf_table"), + description: "", + subject: i18n.t("printcenter.payments.ca_bc_etf_table"), + key: "ca_bc_etf_table", + disabled: false + }, + exported_payroll: { + title: i18n.t("printcenter.payments.exported_payroll"), + description: "", + subject: i18n.t("printcenter.payments.exported_payroll"), + key: "exported_payroll", + disabled: false + }, + attendance_detail_csv: { + title: i18n.t("printcenter.special.attendance_detail_csv"), + description: "", + subject: i18n.t("printcenter.special.attendance_detail_csv"), + key: "attendance_detail_csv", + disabled: false + }, + production_by_technician_one: { + title: i18n.t("reportcenter.templates.production_by_technician_one"), + description: "", + subject: i18n.t("reportcenter.templates.production_by_technician_one"), + key: "production_by_technician_one", + //idtype: "vendor", + disabled: false + }, + production_by_category_one: { + title: i18n.t("reportcenter.templates.production_by_category_one"), + description: "", + subject: i18n.t("reportcenter.templates.production_by_category_one"), + key: "production_by_category_one", + //idtype: "vendor", + disabled: false + }, + production_by_repair_status_one: { + title: i18n.t("reportcenter.templates.production_by_repair_status_one"), + description: "", + subject: i18n.t("reportcenter.templates.production_by_repair_status_one"), + key: "production_by_repair_status_one", + //idtype: "vendor", + disabled: false + } + } + : {}) + }; }; diff --git a/client/src/utils/TemplateSpecial.jsx b/client/src/utils/TemplateSpecial.jsx index 23254c143..9ffc58b88 100644 --- a/client/src/utils/TemplateSpecial.jsx +++ b/client/src/utils/TemplateSpecial.jsx @@ -1,5 +1,5 @@ import React from "react"; export function PartsLabelMulti() { - return
    ; + return
    ; } diff --git a/client/src/utils/TestingHelpers.js b/client/src/utils/TestingHelpers.js index d904ae7fb..9b0ffd9b0 100644 --- a/client/src/utils/TestingHelpers.js +++ b/client/src/utils/TestingHelpers.js @@ -1,148 +1,139 @@ export const MockBodyshop = { - address1: "123 Fake St", - address2: "Unit #100", - city: "Vancouver", - country: "Canada", - created_at: "2019-12-10T20:03:06.420853+00:00", - email: "snaptsoft@gmail.com", - federal_tax_id: "GST10150492", - id: "52b7357c-0edd-4c95-85c3-dfdbcdfad9ac", - insurance_vendor_id: "F123456", - logo_img_path: "https://www.snapt.ca/assets/logo-placeholder.png", - md_ro_statuses: { - statuses: [ - "Open", - "Scheduled", - "Arrived", - "Repair Plan", - "Parts", - "Body", - "Prep", - "Paint", - "Reassembly", - "Sublet", - "Detail", - "Completed", - "Delivered", - "Invoiced", - "Exported", - ], - open_statuses: [ - "Open", - "Scheduled", - "Arrived", - "Repair Plan", - "Parts", - "Body", - "Prep", - "Paint", - ], - default_arrived: "Arrived", - default_exported: "Exported", - default_imported: "Open", - default_invoiced: "Invoiced", - default_completed: "Completed", - default_delivered: "Delivered", - default_scheduled: "Scheduled", - }, - md_order_statuses: { - statuses: ["Ordered", "Received", "Canceled", "Backordered"], - default_bo: "Backordered", - default_ordered: "Ordered", - default_canceled: "Canceled", - default_received: "Received", - }, - shopname: "Testing Collision", - state: "BC", - state_tax_id: "PST1000-2991", - updated_at: "2020-03-23T22:06:03.509544+00:00", - zip_post: "V6B 1M9", - region_config: "CA_BC", - md_responsibility_centers: { - costs: [ - "Aftermarket", - "ATS", - "Body", - "Detail", - "Daignostic", - "Electrical", - "Chrome", - "Frame", - "Mechanical", - "Refinish", - "Structural", - "Existing", - "Glass", - "LKQ", - "OEM", - "OEM Partial", - "Re-cored", - "Remanufactured", - "Other", - "Sublet", - "Towing", - ], - profits: [ - "Aftermarket", - "ATS", - "Body", - "Detail", - "Daignostic", - "Electrical", - "Chrome", - "Frame", - "Mechanical", - "Refinish", - "Structural", - "Existing", - "Glass", - "LKQ", - "OEM", - "OEM Partial", - "Re-cored", - "Remanufactured", - "Other", - "Sublet", - "Towing", - ], - defaults: { - ATS: "ATS", - LAB: "Body", - LAD: "Diagnostic", - LAE: "Electrical", - LAF: "Frame", - LAG: "Glass", - LAM: "Mechanical", - LAR: "Refinish", - LAS: "Structural", - LAU: "Detail", - PAA: "Aftermarket", - PAC: "Chrome", - PAL: "LKQ", - PAM: "Remanufactured", - PAN: "OEM", - PAO: "Other", - PAP: "OEM Partial", - PAR: "16", - TOW: "Towing", - }, - }, - employees: [ - { - id: "075b744c-8919-49ca-abb2-ccd51040326d", - first_name: "Patrick", - last_name: "BODY123", - employee_number: "101", - cost_center: "Body", - __typename: "employees", - }, - { - id: "8cc787d3-1cfe-49d3-8a15-8469cd5c2e41", - first_name: "Patrick", - last_name: "Painter", - employee_number: "10211", - cost_center: "REFINISH", - __typename: "employees", - }, + address1: "123 Fake St", + address2: "Unit #100", + city: "Vancouver", + country: "Canada", + created_at: "2019-12-10T20:03:06.420853+00:00", + email: "snaptsoft@gmail.com", + federal_tax_id: "GST10150492", + id: "52b7357c-0edd-4c95-85c3-dfdbcdfad9ac", + insurance_vendor_id: "F123456", + logo_img_path: "https://www.snapt.ca/assets/logo-placeholder.png", + md_ro_statuses: { + statuses: [ + "Open", + "Scheduled", + "Arrived", + "Repair Plan", + "Parts", + "Body", + "Prep", + "Paint", + "Reassembly", + "Sublet", + "Detail", + "Completed", + "Delivered", + "Invoiced", + "Exported" ], + open_statuses: ["Open", "Scheduled", "Arrived", "Repair Plan", "Parts", "Body", "Prep", "Paint"], + default_arrived: "Arrived", + default_exported: "Exported", + default_imported: "Open", + default_invoiced: "Invoiced", + default_completed: "Completed", + default_delivered: "Delivered", + default_scheduled: "Scheduled" + }, + md_order_statuses: { + statuses: ["Ordered", "Received", "Canceled", "Backordered"], + default_bo: "Backordered", + default_ordered: "Ordered", + default_canceled: "Canceled", + default_received: "Received" + }, + shopname: "Testing Collision", + state: "BC", + state_tax_id: "PST1000-2991", + updated_at: "2020-03-23T22:06:03.509544+00:00", + zip_post: "V6B 1M9", + region_config: "CA_BC", + md_responsibility_centers: { + costs: [ + "Aftermarket", + "ATS", + "Body", + "Detail", + "Daignostic", + "Electrical", + "Chrome", + "Frame", + "Mechanical", + "Refinish", + "Structural", + "Existing", + "Glass", + "LKQ", + "OEM", + "OEM Partial", + "Re-cored", + "Remanufactured", + "Other", + "Sublet", + "Towing" + ], + profits: [ + "Aftermarket", + "ATS", + "Body", + "Detail", + "Daignostic", + "Electrical", + "Chrome", + "Frame", + "Mechanical", + "Refinish", + "Structural", + "Existing", + "Glass", + "LKQ", + "OEM", + "OEM Partial", + "Re-cored", + "Remanufactured", + "Other", + "Sublet", + "Towing" + ], + defaults: { + ATS: "ATS", + LAB: "Body", + LAD: "Diagnostic", + LAE: "Electrical", + LAF: "Frame", + LAG: "Glass", + LAM: "Mechanical", + LAR: "Refinish", + LAS: "Structural", + LAU: "Detail", + PAA: "Aftermarket", + PAC: "Chrome", + PAL: "LKQ", + PAM: "Remanufactured", + PAN: "OEM", + PAO: "Other", + PAP: "OEM Partial", + PAR: "16", + TOW: "Towing" + } + }, + employees: [ + { + id: "075b744c-8919-49ca-abb2-ccd51040326d", + first_name: "Patrick", + last_name: "BODY123", + employee_number: "101", + cost_center: "Body", + __typename: "employees" + }, + { + id: "8cc787d3-1cfe-49d3-8a15-8469cd5c2e41", + first_name: "Patrick", + last_name: "Painter", + employee_number: "10211", + cost_center: "REFINISH", + __typename: "employees" + } + ] }; diff --git a/client/src/utils/aamva.js b/client/src/utils/aamva.js index c05abe528..ad608f5f2 100644 --- a/client/src/utils/aamva.js +++ b/client/src/utils/aamva.js @@ -1,154 +1,141 @@ (function (global) { - var parse = function (data) { - data = data.replace(/\n/, ""); - // replace spaces with regular space - data = data.replace(/\s/g, " "); + var parse = function (data) { + data = data.replace(/\n/, ""); + // replace spaces with regular space + data = data.replace(/\s/g, " "); - if (/^@/.test(data) === true) { - return pdf417(data); - } else if (/^%/.test(data) === true) { - return stripe(data); - } else { - console.log("couldnt identify format"); - } - }; + if (/^@/.test(data) === true) { + return pdf417(data); + } else if (/^%/.test(data) === true) { + return stripe(data); + } else { + console.log("couldnt identify format"); + } + }; - var parseDate = function (date) { - var start = parseInt(date[0] + date[1]); - if (start < 13) { - return ( - date[4] + - date[5] + - date[6] + - date[7] + - date[0] + - date[1] + - date[2] + - date[3] - ); - } - return date; - }; + var parseDate = function (date) { + var start = parseInt(date[0] + date[1]); + if (start < 13) { + return date[4] + date[5] + date[6] + date[7] + date[0] + date[1] + date[2] + date[3]; + } + return date; + }; - var stripe = function (data) { - data = data.replace(/\n/, ""); - // replace spaces with regular space - data = data.replace(/\s/g, " "); - var track = data.match(/(.*?\?)(.*?\?)(.*?\?)/); - var res1 = track[1].match( - /(%)([A-Z]{2})([^^]{0,13})\^?([^^]{0,35})\^?([^^]{0,60})\^?\s*?\?/ - ); - var res2 = track[2].match( - /(;)(\d{6})(\d{0,13})(=)(\d{4})(\d{8})(\d{0,5})=?\?/ - ); - var res3 = track[3].match( - /(#|%|\+)(\d|!|")(\d|\s|.)([0-9A-Z ]{11})([0-9A-Z ]{2})([0-9A-Z ]{10})([0-9A-Z ]{4})([12MF ]{1})([0-9A-Z ]{3})([0-9A-Z ]{3})([0-9A-Z ]{3})([0-9A-Z ]{3})(.*?)\?/ - ); - var state = res1[2]; + var stripe = function (data) { + data = data.replace(/\n/, ""); + // replace spaces with regular space + data = data.replace(/\s/g, " "); + var track = data.match(/(.*?\?)(.*?\?)(.*?\?)/); + var res1 = track[1].match(/(%)([A-Z]{2})([^^]{0,13})\^?([^^]{0,35})\^?([^^]{0,60})\^?\s*?\?/); + var res2 = track[2].match(/(;)(\d{6})(\d{0,13})(=)(\d{4})(\d{8})(\d{0,5})=?\?/); + var res3 = track[3].match( + /(#|%|\+)(\d|!|")(\d|\s|.)([0-9A-Z ]{11})([0-9A-Z ]{2})([0-9A-Z ]{10})([0-9A-Z ]{4})([12MF ]{1})([0-9A-Z ]{3})([0-9A-Z ]{3})([0-9A-Z ]{3})([0-9A-Z ]{3})(.*?)\?/ + ); + var state = res1[2]; + return { + state: state, + city: res1[3], + name: (function () { + var res = res1[4].match(/([^$]{0,35})\$?([^$]{0,35})?\$?([^$]{0,35})?/); + if (!res) return; return { - state: state, - city: res1[3], - name: (function () { - var res = res1[4].match(/([^$]{0,35})\$?([^$]{0,35})?\$?([^$]{0,35})?/); - if (!res) return; - return { - last: res[1], - first: res[2], - middle: res[3], - }; - })(), - address: res1[5], - iso_iin: res2[2], - dl: res2[3], - expiration_date: parseDate(res2[5]), - birthday: (function () { - var dob = res2[6].match(/(\d{4})(\d{2})(\d{2})/); - if (!dob) return; + last: res[1], + first: res[2], + middle: res[3] + }; + })(), + address: res1[5], + iso_iin: res2[2], + dl: res2[3], + expiration_date: parseDate(res2[5]), + birthday: (function () { + var dob = res2[6].match(/(\d{4})(\d{2})(\d{2})/); + if (!dob) return; - if (dob[2] === "99") { - /* FL decided to reverse 2012 aamva spec, 99 means here + if (dob[2] === "99") { + /* FL decided to reverse 2012 aamva spec, 99 means here that dob month === to expiration month, it should be opposite */ - var exp_dt = res2[5].match(/(\d{2})(\d{2})/); - dob[2] = exp_dt[2]; - } - //dob[2]--; what was this for? - return dob[1] + dob[2] + dob[3]; - })(), - dl_overflow: res2[7], - cds_version: res3[1], - jurisdiction_version: res3[2], - postal_code: res3[4], - klass: res3[5], - class: res3[5], - restrictions: res3[6], - endorsments: res3[7], - sex: (function () { - switch (res3[8]) { - case "1": - case "M": - return "MALE"; + var exp_dt = res2[5].match(/(\d{2})(\d{2})/); + dob[2] = exp_dt[2]; + } + //dob[2]--; what was this for? + return dob[1] + dob[2] + dob[3]; + })(), + dl_overflow: res2[7], + cds_version: res3[1], + jurisdiction_version: res3[2], + postal_code: res3[4], + klass: res3[5], + class: res3[5], + restrictions: res3[6], + endorsments: res3[7], + sex: (function () { + switch (res3[8]) { + case "1": + case "M": + return "MALE"; - case "2": - case "F": - return "FEMALE"; + case "2": + case "F": + return "FEMALE"; - default: - return res3[8] + ": MISSING/INVALID"; - } - })(), - height: res3[9], - weight: res3[10], - hair_color: res3[11], - eye_color: res3[12], - misc: res3[13], - id: (function () { - var id; - switch (state) { - case "FL": - var res = res2[3].match(/(\d{2})(.*)/); - if (!res) return; - id = String.fromCharCode(Number(res[1]) + 64) + res[2] + res2[7]; - break; - default: - id = res2[3]; - break; - } - return id; - })(), - }; + default: + return res3[8] + ": MISSING/INVALID"; + } + })(), + height: res3[9], + weight: res3[10], + hair_color: res3[11], + eye_color: res3[12], + misc: res3[13], + id: (function () { + var id; + switch (state) { + case "FL": + var res = res2[3].match(/(\d{2})(.*)/); + if (!res) return; + id = String.fromCharCode(Number(res[1]) + 64) + res[2] + res2[7]; + break; + default: + id = res2[3]; + break; + } + return id; + })() }; + }; - var pdf417 = function (data) { - data = data.replace(/\n/, ""); - // replace spaces with regular space - data = data.replace(/\s/g, " "); + var pdf417 = function (data) { + data = data.replace(/\n/, ""); + // replace spaces with regular space + data = data.replace(/\s/g, " "); - // get version of aamva (before 2000 or after) - var version = data.match(/[A-Z ]{5}\d{6}(\d{2})/); + // get version of aamva (before 2000 or after) + var version = data.match(/[A-Z ]{5}\d{6}(\d{2})/); - var parseRegex; + var parseRegex; - /* version 01 year 2000 */ - switch (Number(version[1])) { - case 1: { - parseRegex = new RegExp( - "(DAQ.*?)?" + // Drivers license number - "(DAA.*?)?" + // Driver License Name - "(DAG.*?)?" + // Driver Mailing Street Address - "(DAI.*?)?" + // Driver Mailing City - "(DAJ.*?)?" + // Driver Mailing Jurisdiction Code - "(DAK.*?)?" + // Driver Mailing Postal Code - "(DAQ.*?)?" + // Driver License/ID Number - "(DAR.*?)?" + // Driver License Classification Code - "(DAS.*?)?" + // Driver License Restriction Code - "(DAT.*?)?" + // Driver License Endorsements Code - "(DBA.*?)?" + // Driver License Expiration Date - "(DBB.*?)?" + // Date of Birth - "(DBC.*?)?" + // Driver Sex - "(DBD.*?)?" + // Driver License or ID Document Issue Date - /* optional + /* version 01 year 2000 */ + switch (Number(version[1])) { + case 1: { + parseRegex = new RegExp( + "(DAQ.*?)?" + // Drivers license number + "(DAA.*?)?" + // Driver License Name + "(DAG.*?)?" + // Driver Mailing Street Address + "(DAI.*?)?" + // Driver Mailing City + "(DAJ.*?)?" + // Driver Mailing Jurisdiction Code + "(DAK.*?)?" + // Driver Mailing Postal Code + "(DAQ.*?)?" + // Driver License/ID Number + "(DAR.*?)?" + // Driver License Classification Code + "(DAS.*?)?" + // Driver License Restriction Code + "(DAT.*?)?" + // Driver License Endorsements Code + "(DBA.*?)?" + // Driver License Expiration Date + "(DBB.*?)?" + // Date of Birth + "(DBC.*?)?" + // Driver Sex + "(DBD.*?)?" + // Driver License or ID Document Issue Date + /* optional '(DAU.*?)?' + // Height (FT/IN) '(DAW.*?)?' + // Weight (LBS) '(DAY.*?)?' + // Eye Color @@ -188,35 +175,35 @@ '(DBR.*?)?' + // Driver "AKA" Suffix '(DBS.*?)?' // Driver "AKA" Prefix */ - "$" - ); - break; - } - /* version 02 year 2003 */ - case 2: { - parseRegex = new RegExp( - "(DCA.*?)?" + // Jurisdiction-specific vehicle class - "(DCB.*?)?" + // Jurisdiction-specific restriction codes - "(DCD.*?)?" + // Jurisdiction-specific endorsement codes - "(DBA.*?)?" + // Document Expiration Date - "(DCS.*?)?" + // Customer Family Name - "(DCT.*?)?" + // Customer Given Names - "(DCU.*?)?" + // Name Suffix - "(DBD.*?)?" + // Document Issue Date - "(DBB.*?)?" + // Date of Birth - "(DBC.*?)?" + // Physical Description – Sex - "(DAY.*?)?" + // Physical Description – Eye Color - "(DAU.*?)?" + // Physical Description – Height - "(DCE.*?)?" + // Physical Description – Weight Range - "(DAG.*?)?" + // Address – Street 1 - "(DAI.*?)?" + // Address – City - "(DAJ.*?)?" + // Address – Jurisdiction Code - "(DAK.*?)?" + // Address – Postal Code - "(DAQ.*?)?" + // Customer ID Number - "(DCF.*?)?" + // Document Discriminator - "(DCG.*?)?" + // Country Identification - "(DCH.*?)?" + // Federal Commercial Vehicle Codes - /* optional elements + "$" + ); + break; + } + /* version 02 year 2003 */ + case 2: { + parseRegex = new RegExp( + "(DCA.*?)?" + // Jurisdiction-specific vehicle class + "(DCB.*?)?" + // Jurisdiction-specific restriction codes + "(DCD.*?)?" + // Jurisdiction-specific endorsement codes + "(DBA.*?)?" + // Document Expiration Date + "(DCS.*?)?" + // Customer Family Name + "(DCT.*?)?" + // Customer Given Names + "(DCU.*?)?" + // Name Suffix + "(DBD.*?)?" + // Document Issue Date + "(DBB.*?)?" + // Date of Birth + "(DBC.*?)?" + // Physical Description – Sex + "(DAY.*?)?" + // Physical Description – Eye Color + "(DAU.*?)?" + // Physical Description – Height + "(DCE.*?)?" + // Physical Description – Weight Range + "(DAG.*?)?" + // Address – Street 1 + "(DAI.*?)?" + // Address – City + "(DAJ.*?)?" + // Address – Jurisdiction Code + "(DAK.*?)?" + // Address – Postal Code + "(DAQ.*?)?" + // Customer ID Number + "(DCF.*?)?" + // Document Discriminator + "(DCG.*?)?" + // Country Identification + "(DCH.*?)?" + // Federal Commercial Vehicle Codes + /* optional elements '(DAH.*?)?' + // Address – Street 2 '(DAZ.*?)?' + // Hair color '(DCI.*?)?' + // Place of birth @@ -232,33 +219,33 @@ '(DCQ.*?)?' + // Jurisdiction- specific endorsement code description '(DCR.*?)?' // Jurisdiction- specific restriction code description */ - "$" - ); - break; - } - /* version 03 year 2005 */ - case 3: { - parseRegex = new RegExp( - "(DCA.*?)?" + // Jurisdiction-specific vehicle class - "(DCB.*?)?" + // Jurisdiction-specific restriction codes - "(DCD.*?)?" + // Jurisdiction-specific endorsement codes - "(DBA.*?)?" + // Document Expiration Date - "(DCS.*?)?" + // Customer Family Name - "(DCT.*?)?" + // Customer Given Names - "(DBD.*?)?" + // Document Issue Date - "(DBB.*?)?" + // Date of Birth - "(DBC.*?)?" + // Physical Description – Sex - "(DAY.*?)?" + // Physical Description – Eye Color - "(DAU.*?)?" + // Physical Description – Height - "(DAG.*?)?" + // Address – Street 1 - "(DAI.*?)?" + // Address – City - "(DAJ.*?)?" + // Address – Jurisdiction Code - "(DAK.*?)?" + // Address – Postal Code - "(DAQ.*?)?" + // Customer ID Number - "(DCF.*?)?" + // Document Discriminator - "(DCG.*?)?" + // Country Identification - "(DCH.*?)?" + // Federal Commercial Vehicle Codes - /* optional elements + "$" + ); + break; + } + /* version 03 year 2005 */ + case 3: { + parseRegex = new RegExp( + "(DCA.*?)?" + // Jurisdiction-specific vehicle class + "(DCB.*?)?" + // Jurisdiction-specific restriction codes + "(DCD.*?)?" + // Jurisdiction-specific endorsement codes + "(DBA.*?)?" + // Document Expiration Date + "(DCS.*?)?" + // Customer Family Name + "(DCT.*?)?" + // Customer Given Names + "(DBD.*?)?" + // Document Issue Date + "(DBB.*?)?" + // Date of Birth + "(DBC.*?)?" + // Physical Description – Sex + "(DAY.*?)?" + // Physical Description – Eye Color + "(DAU.*?)?" + // Physical Description – Height + "(DAG.*?)?" + // Address – Street 1 + "(DAI.*?)?" + // Address – City + "(DAJ.*?)?" + // Address – Jurisdiction Code + "(DAK.*?)?" + // Address – Postal Code + "(DAQ.*?)?" + // Customer ID Number + "(DCF.*?)?" + // Document Discriminator + "(DCG.*?)?" + // Country Identification + "(DCH.*?)?" + // Federal Commercial Vehicle Codes + /* optional elements + '(DAH.*?)?' + // Address – Street 2 '(DAZ.*?)?' + // Hair color '(DCI.*?)?' + // Place of birth @@ -277,64 +264,64 @@ '(DCQ.*?)?' + // Jurisdiction- specific endorsement code description '(DCR.*?)?' // Jurisdiction- specific restriction code description */ - "$" - ); - break; - } - case 6: { - parseRegex = new RegExp( - "(DAQ.*?)?" + - "(DCS.*?)?" + - "(DDE.*?)?" + - "(DAC.*?)?" + - "(DDF.*?)?" + - "(DAD.*?)?" + - "(DDG.*?)?" + - "(DCA.*?)?" + - "(DCB.*?)?" + - "(DCD.*?)?" + - "(DBD.*?)?" + - "(DBB.*?)?" + - "(DBA.*?)?" + - "(DBC.*?)?" + - "(DAU.*?)?" + - "(DAY.*?)?" + - "(DAG.*?)?" + - "(DAI.*?)?" + - "(DAJ.*?)?" + - "(DAK.*?)?" + - "(DCF.*?)?" + - /* optional */ - "$" - ); - break; - } - /* version 07 year 2012 */ - case 7: { - parseRegex = new RegExp( - "(DCA.*?)?" + // Jurisdiction-specific vehicle class - "(DCB.*?)?" + // Jurisdiction-specific restriction codes - "(DCD.*?)?" + // Jurisdiction-specific endorsement codes - "(DBA.*?)?" + // Document Expiration Date - "(DCS.*?)?" + // Customer Family Name - "(DAC.*?)?" + // Customer First Name - "(DAD.*?)?" + // Customer Middle Name(s) - "(DBD.*?)?" + // Document Issue Date - "(DBB.*?)?" + // Date of Birth - "(DBC.*?)?" + // Physical Description – Sex - "(DAY.*?)?" + // Physical Description – Eye Color - "(DAU.*?)?" + // Physical Description – Height - "(DAG.*?)?" + // Address – Street 1 - "(DAI.*?)?" + // Address – City - "(DAJ.*?)?" + // Address – Jurisdiction Code - "(DAK.*?)?" + // Address – Postal Code - "(DAQ.*?)?" + // Customer ID Number - "(DCF.*?)?" + // Document Discriminator - "(DCG.*?)?" + // Country Identification - "(DDE.*?)?" + // Family name truncation - "(DDF.*?)?" + // First name truncation - "(DDG.*?)?" + // Middle name truncation - /* optional elements + "$" + ); + break; + } + case 6: { + parseRegex = new RegExp( + "(DAQ.*?)?" + + "(DCS.*?)?" + + "(DDE.*?)?" + + "(DAC.*?)?" + + "(DDF.*?)?" + + "(DAD.*?)?" + + "(DDG.*?)?" + + "(DCA.*?)?" + + "(DCB.*?)?" + + "(DCD.*?)?" + + "(DBD.*?)?" + + "(DBB.*?)?" + + "(DBA.*?)?" + + "(DBC.*?)?" + + "(DAU.*?)?" + + "(DAY.*?)?" + + "(DAG.*?)?" + + "(DAI.*?)?" + + "(DAJ.*?)?" + + "(DAK.*?)?" + + "(DCF.*?)?" + + /* optional */ + "$" + ); + break; + } + /* version 07 year 2012 */ + case 7: { + parseRegex = new RegExp( + "(DCA.*?)?" + // Jurisdiction-specific vehicle class + "(DCB.*?)?" + // Jurisdiction-specific restriction codes + "(DCD.*?)?" + // Jurisdiction-specific endorsement codes + "(DBA.*?)?" + // Document Expiration Date + "(DCS.*?)?" + // Customer Family Name + "(DAC.*?)?" + // Customer First Name + "(DAD.*?)?" + // Customer Middle Name(s) + "(DBD.*?)?" + // Document Issue Date + "(DBB.*?)?" + // Date of Birth + "(DBC.*?)?" + // Physical Description – Sex + "(DAY.*?)?" + // Physical Description – Eye Color + "(DAU.*?)?" + // Physical Description – Height + "(DAG.*?)?" + // Address – Street 1 + "(DAI.*?)?" + // Address – City + "(DAJ.*?)?" + // Address – Jurisdiction Code + "(DAK.*?)?" + // Address – Postal Code + "(DAQ.*?)?" + // Customer ID Number + "(DCF.*?)?" + // Document Discriminator + "(DCG.*?)?" + // Country Identification + "(DDE.*?)?" + // Family name truncation + "(DDF.*?)?" + // First name truncation + "(DDG.*?)?" + // Middle name truncation + /* optional elements '(DAH.*?)?' + // Address – Street 2 '(DAZ.*?)?' + // Hair color '(DCI.*?)?' + // Place of birth @@ -364,195 +351,195 @@ '(DDK.*?)?' + // Organ Donor Indicator '(DDL.*?)?' // Veteran Indicator */ - "$" - ); - break; - } - case 8: - case 9: { - var prefixes = [ - "DCA", // jurisdiction vehicle class - "DCB", // jurisdiction restriction codes - "DCD", // jurisdiction endorsement codes - "DBA", // doc. expiration date - "DCS", // customer family name - "DAC", // first name - "DAD", // middle names (comma seperated) - "DBD", // doc. issue date - "DBB", // date of birth (MMDDCCYY for U.S., CCYYMMDD for Canada) - "DBC", // gender (1-name, 2-female, 9-not specified) - "DAY", // eye color (ansi d-20 codes) - "DAU", // height - "DAG", // street 1 - "DAI", // city - "DAJ", // state - "DAK", // zip - "DAQ", // customer id number - "DCF", // doc. distriminator - "DCG", // country identification (USA/CAN) - "DDE", // last name truncated (T-trucated, N-not, U-unknown) - "DDF", // first name truncated (T-trucated, N-not, U-unknown) - "DDG", // middle name truncated (T-trucated, N-not, U-unknown) - // optionals - "DAH", // street address line 2 - "DAZ", // hair color - "DCI", // place of birth - "DCJ", // audit info - "DCK", // inventory control number - "DBN", // alias last name - "DBG", // alias first name - "DBS", // aliast suffix name - "DCU", // name suffix . (JR, SR, 1ST, 2ND...) - "DCE", // weight range - "DCL", // race / ethnicity (AAMVA D20 code) - "DCM", // vehicle classification - "DCN", // standard endorsement code - "DCO", // standard restriction code - "DCP", // vehicle classification description - "DCQ", // endorsement code description - "DCR", // restriction code description - "DDA", // compliance type - "DDB", // card revision date - "DDC", // hazmat endorsement exp. date - "DDD", // limited duration doc. indicator - "DAW", // weight lbs - "DAX", // weight kg - "DDH", // under 18 until, date turns 18 (MMDDCCYY for U.S., CCYYMMDD for Canada) - "DDI", // under 19 until, date turns 19 (MMDDCCYY for U.S., CCYYMMDD for Canada) - "DDJ", // under 21 until, date turns 21 (MMDDCCYY for U.S., CCYYMMDD for Canada) - "DDK", // organ donor (1-yes) - "DDL", // veteran indicator (1-yes) - ]; - var regExStr = ""; - var prefixIdxs = []; - for (var i = 0; i < prefixes.length; i++) { - var idx = data.indexOf(prefixes[i]); - if (idx !== -1) { - prefixIdxs.push({ - prefix: prefixes[i], - index: idx, - }); - } - } - // if prefixes are not in order as found in the string, the regex will not perform as expected - prefixIdxs.sort((a, b) => (a.index > b.index ? 1 : -1)); - prefixIdxs.forEach((obj) => (regExStr += `(${obj.prefix}.*?)?`)); - regExStr += "$"; - - parseRegex = new RegExp(regExStr); - break; - } - default: { - console.log("unable to get version", version); - // probably not a right parse... - } + "$" + ); + break; + } + case 8: + case 9: { + var prefixes = [ + "DCA", // jurisdiction vehicle class + "DCB", // jurisdiction restriction codes + "DCD", // jurisdiction endorsement codes + "DBA", // doc. expiration date + "DCS", // customer family name + "DAC", // first name + "DAD", // middle names (comma seperated) + "DBD", // doc. issue date + "DBB", // date of birth (MMDDCCYY for U.S., CCYYMMDD for Canada) + "DBC", // gender (1-name, 2-female, 9-not specified) + "DAY", // eye color (ansi d-20 codes) + "DAU", // height + "DAG", // street 1 + "DAI", // city + "DAJ", // state + "DAK", // zip + "DAQ", // customer id number + "DCF", // doc. distriminator + "DCG", // country identification (USA/CAN) + "DDE", // last name truncated (T-trucated, N-not, U-unknown) + "DDF", // first name truncated (T-trucated, N-not, U-unknown) + "DDG", // middle name truncated (T-trucated, N-not, U-unknown) + // optionals + "DAH", // street address line 2 + "DAZ", // hair color + "DCI", // place of birth + "DCJ", // audit info + "DCK", // inventory control number + "DBN", // alias last name + "DBG", // alias first name + "DBS", // aliast suffix name + "DCU", // name suffix . (JR, SR, 1ST, 2ND...) + "DCE", // weight range + "DCL", // race / ethnicity (AAMVA D20 code) + "DCM", // vehicle classification + "DCN", // standard endorsement code + "DCO", // standard restriction code + "DCP", // vehicle classification description + "DCQ", // endorsement code description + "DCR", // restriction code description + "DDA", // compliance type + "DDB", // card revision date + "DDC", // hazmat endorsement exp. date + "DDD", // limited duration doc. indicator + "DAW", // weight lbs + "DAX", // weight kg + "DDH", // under 18 until, date turns 18 (MMDDCCYY for U.S., CCYYMMDD for Canada) + "DDI", // under 19 until, date turns 19 (MMDDCCYY for U.S., CCYYMMDD for Canada) + "DDJ", // under 21 until, date turns 21 (MMDDCCYY for U.S., CCYYMMDD for Canada) + "DDK", // organ donor (1-yes) + "DDL" // veteran indicator (1-yes) + ]; + var regExStr = ""; + var prefixIdxs = []; + for (var i = 0; i < prefixes.length; i++) { + var idx = data.indexOf(prefixes[i]); + if (idx !== -1) { + prefixIdxs.push({ + prefix: prefixes[i], + index: idx + }); + } } + // if prefixes are not in order as found in the string, the regex will not perform as expected + prefixIdxs.sort((a, b) => (a.index > b.index ? 1 : -1)); + prefixIdxs.forEach((obj) => (regExStr += `(${obj.prefix}.*?)?`)); + regExStr += "$"; - var parsedData = {}; - var res = data.match(parseRegex); + parseRegex = new RegExp(regExStr); + break; + } + default: { + console.log("unable to get version", version); + // probably not a right parse... + } + } - for (i = 1; i < res.length; i++) { - if (res[i] !== undefined) { - parsedData[String(res[i]).substring(0, 3)] = res[i].substring(3).trim(); - } - } + var parsedData = {}; + var res = data.match(parseRegex); - var name; - switch (Number(version[1])) { - case 1: { - // version one joining all of the names in one string - name = parsedData.DAA.split(","); - parsedData.DCS = name[0]; - parsedData.DAC = name[1]; - parsedData.DAD = name[2]; + for (i = 1; i < res.length; i++) { + if (res[i] !== undefined) { + parsedData[String(res[i]).substring(0, 3)] = res[i].substring(3).trim(); + } + } - // drivers license class - parsedData.DCA = parsedData.DAR; + var name; + switch (Number(version[1])) { + case 1: { + // version one joining all of the names in one string + name = parsedData.DAA.split(","); + parsedData.DCS = name[0]; + parsedData.DAC = name[1]; + parsedData.DAD = name[2]; - // date on 01 is CCYYMMDD while on 07 MMDDCCYY - parsedData.DBB = - parsedData.DBB.substring(4, 6) + // month - parsedData.DBB.substring(6, 8) + // day - parsedData.DBB.substring(0, 4); // year - break; - } - case 3: { - // version 3 putting middle and first names in the same field - name = parsedData.DCT.split(","); - parsedData.DAC = name[0]; // first name - parsedData.DAD = name[1]; // middle name - break; - } - default: { - console.log("no version matched"); - break; - } - } + // drivers license class + parsedData.DCA = parsedData.DAR; - var rawData = { - state: parsedData.DAJ, - city: parsedData.DAI, - name: (function () { - return { - last: parsedData.DCS, - first: parsedData.DAC, - middle: parsedData.DAD, - }; - })(), - address: parsedData.DAG, - iso_iin: undefined, - dl: parsedData.DAQ, - expiration_date: parseDate(parsedData.DBA), - birthday: (function () { - if (!parsedData.DBB) return; - var match = parsedData.DBB.match(/(\d{2})(\d{2})(\d{4})/); - if (!match) return; - return match[3] + match[1] + match[2]; - })(), - dl_overflow: undefined, - cds_version: undefined, - jurisdiction_version: undefined, - postal_code: parsedData.DAK - ? parsedData.DAK.match(/\d{-}\d+/) - ? parsedData.DAK - : parsedData.DAK.substring(0, 5) - : undefined, - klass: parsedData.DCA, - class: parsedData.DCA, - restrictions: undefined, - endorsments: undefined, - sex: (function () { - switch (Number(parsedData.DBC)) { - case 1: - return "MALE"; + // date on 01 is CCYYMMDD while on 07 MMDDCCYY + parsedData.DBB = + parsedData.DBB.substring(4, 6) + // month + parsedData.DBB.substring(6, 8) + // day + parsedData.DBB.substring(0, 4); // year + break; + } + case 3: { + // version 3 putting middle and first names in the same field + name = parsedData.DCT.split(","); + parsedData.DAC = name[0]; // first name + parsedData.DAD = name[1]; // middle name + break; + } + default: { + console.log("no version matched"); + break; + } + } - case 2: - return "FEMALE"; - - default: - if (parsedData.DBC[0] === "M") { - return "MALE"; - } else if (parsedData.DBC[0] === "F") { - return "FEMALE"; - } - return "MISSING/INVALID"; - } - })(), - height: undefined, - weight: undefined, - hair_color: undefined, - eye_color: undefined, - misc: undefined, - id: (function () { - if (!parsedData.DAQ) return; - return parsedData.DAQ.replace(/[^A-ZA-Z0-9]/g, ""); - })(), + var rawData = { + state: parsedData.DAJ, + city: parsedData.DAI, + name: (function () { + return { + last: parsedData.DCS, + first: parsedData.DAC, + middle: parsedData.DAD }; + })(), + address: parsedData.DAG, + iso_iin: undefined, + dl: parsedData.DAQ, + expiration_date: parseDate(parsedData.DBA), + birthday: (function () { + if (!parsedData.DBB) return; + var match = parsedData.DBB.match(/(\d{2})(\d{2})(\d{4})/); + if (!match) return; + return match[3] + match[1] + match[2]; + })(), + dl_overflow: undefined, + cds_version: undefined, + jurisdiction_version: undefined, + postal_code: parsedData.DAK + ? parsedData.DAK.match(/\d{-}\d+/) + ? parsedData.DAK + : parsedData.DAK.substring(0, 5) + : undefined, + klass: parsedData.DCA, + class: parsedData.DCA, + restrictions: undefined, + endorsments: undefined, + sex: (function () { + switch (Number(parsedData.DBC)) { + case 1: + return "MALE"; - return rawData; + case 2: + return "FEMALE"; + + default: + if (parsedData.DBC[0] === "M") { + return "MALE"; + } else if (parsedData.DBC[0] === "F") { + return "FEMALE"; + } + return "MISSING/INVALID"; + } + })(), + height: undefined, + weight: undefined, + hair_color: undefined, + eye_color: undefined, + misc: undefined, + id: (function () { + if (!parsedData.DAQ) return; + return parsedData.DAQ.replace(/[^A-ZA-Z0-9]/g, ""); + })() }; - global.parse = parse; - global.stripe = stripe; - global.pdf417 = pdf417; + return rawData; + }; + + global.parse = parse; + global.stripe = stripe; + global.pdf417 = pdf417; })(this); diff --git a/client/src/utils/arrayHelper.js b/client/src/utils/arrayHelper.js index 0fad3f50c..47108210a 100644 --- a/client/src/utils/arrayHelper.js +++ b/client/src/utils/arrayHelper.js @@ -1,3 +1,3 @@ export function onlyUnique(value, index, self, key) { - return self.indexOf(value) === index; + return self.indexOf(value) === index; } diff --git a/client/src/utils/asyncConfirm.js b/client/src/utils/asyncConfirm.js index 601141f38..3969dda96 100644 --- a/client/src/utils/asyncConfirm.js +++ b/client/src/utils/asyncConfirm.js @@ -1,9 +1,9 @@ function confirmDialog(msg) { - return new Promise(function (resolve, reject) { - let confirmed = window.confirm(msg); + return new Promise(function (resolve, reject) { + let confirmed = window.confirm(msg); - return confirmed ? resolve(true) : resolve(false); - }); + return confirmed ? resolve(true) : resolve(false); + }); } export default confirmDialog; diff --git a/client/src/utils/betaHandler.js b/client/src/utils/betaHandler.js index 19ec72ab2..804b38b7d 100644 --- a/client/src/utils/betaHandler.js +++ b/client/src/utils/betaHandler.js @@ -1,37 +1,36 @@ -export const BETA_KEY = 'betaSwitchImex'; +export const BETA_KEY = "betaSwitchImex"; export const checkBeta = () => { - const cookie = document.cookie.split('; ').find(row => row.startsWith(BETA_KEY)); - return cookie ? cookie.split('=')[1] === 'true' : false; -} - + const cookie = document.cookie.split("; ").find((row) => row.startsWith(BETA_KEY)); + return cookie ? cookie.split("=")[1] === "true" : false; +}; export const setBeta = (value) => { - const domain = window.location.hostname.split('.').slice(-2).join('.'); - document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`; -} + const domain = window.location.hostname.split(".").slice(-2).join("."); + document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`; +}; export const handleBeta = () => { - // If the current host name does not start with beta or test, then we don't need to do anything. - if (window.location.hostname.startsWith('localhost')) { - console.log('Not on beta or test, so no need to handle beta.'); - return; - } + // If the current host name does not start with beta or test, then we don't need to do anything. + if (window.location.hostname.startsWith("localhost")) { + console.log("Not on beta or test, so no need to handle beta."); + return; + } - const isBeta = checkBeta(); + const isBeta = checkBeta(); - const currentHostName = window.location.hostname; + const currentHostName = window.location.hostname; - // Beta is enabled, but the current host name does start with beta. - if (isBeta && !currentHostName.startsWith('beta')) { - const href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`; - window.location.replace(href); - } + // Beta is enabled, but the current host name does start with beta. + if (isBeta && !currentHostName.startsWith("beta")) { + const href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`; + window.location.replace(href); + } - // Beta is not enabled, but the current host name does start with beta. - else if (!isBeta && currentHostName.startsWith('beta')) { - const href = `${window.location.protocol}//${currentHostName.replace('beta.', '')}${window.location.pathname}${window.location.search}${window.location.hash}`; - window.location.replace(href); - } -} -export default handleBeta; \ No newline at end of file + // Beta is not enabled, but the current host name does start with beta. + else if (!isBeta && currentHostName.startsWith("beta")) { + const href = `${window.location.protocol}//${currentHostName.replace("beta.", "")}${window.location.pathname}${window.location.search}${window.location.hash}`; + window.location.replace(href); + } +}; +export default handleBeta; diff --git a/client/src/utils/create-recent-item.js b/client/src/utils/create-recent-item.js index 723225eac..5772b6d22 100644 --- a/client/src/utils/create-recent-item.js +++ b/client/src/utils/create-recent-item.js @@ -1,9 +1,9 @@ import i18n from "i18next"; export function CreateRecentItem(id, type, labelid, url) { - return { - id, - label: `${i18n.t(`general.itemtypes.${type}`)}: ${labelid}`, - url, - }; + return { + id, + label: `${i18n.t(`general.itemtypes.${type}`)}: ${labelid}`, + url + }; } diff --git a/client/src/utils/criticalPartsScan.js b/client/src/utils/criticalPartsScan.js index 847b9171d..11e96b69b 100644 --- a/client/src/utils/criticalPartsScan.js +++ b/client/src/utils/criticalPartsScan.js @@ -1,12 +1,12 @@ import axios from "axios"; -import {notification} from "antd"; +import { notification } from "antd"; async function CriticalPartsScan(jobid) { - try { - await axios.post("/job/partsscan", {jobid}); - } catch (error) { - notification.open({type: "error", message: JSON.stringify(error)}); - } + try { + await axios.post("/job/partsscan", { jobid }); + } catch (error) { + notification.open({ type: "error", message: JSON.stringify(error) }); + } } export default CriticalPartsScan; diff --git a/client/src/utils/day.js b/client/src/utils/day.js index 0b29aac61..3fb824b11 100644 --- a/client/src/utils/day.js +++ b/client/src/utils/day.js @@ -1,36 +1,36 @@ -import dayjs from 'dayjs'; +import dayjs from "dayjs"; import dayjsBusinessDays from "dayjs-business-days2"; import isSameOrAfter from "dayjs/plugin/isSameOrAfter"; -import updateLocale from 'dayjs/plugin/updateLocale'; +import updateLocale from "dayjs/plugin/updateLocale"; import isSameOrBefore from "dayjs/plugin/isSameOrBefore"; -import timezone from 'dayjs/plugin/timezone'; -import utc from 'dayjs/plugin/utc'; -import minMax from 'dayjs/plugin/minMax'; -import isBetween from 'dayjs/plugin/isBetween'; -import customParseFormat from 'dayjs/plugin/customParseFormat'; -import pluralGetSet from 'dayjs/plugin/pluralGetSet'; -import duration from 'dayjs/plugin/duration'; -import advancedFormat from 'dayjs/plugin/advancedFormat'; -import arraySupport from 'dayjs/plugin/arraySupport'; -import calendar from 'dayjs/plugin/calendar'; -import dayOfYear from 'dayjs/plugin/dayOfYear'; -import weekday from 'dayjs/plugin/weekday'; -import weekOfYear from 'dayjs/plugin/weekOfYear'; -import weekYear from 'dayjs/plugin/weekYear'; -import isoWeek from 'dayjs/plugin/isoWeek'; -import isoWeeksInYear from 'dayjs/plugin/isoWeeksInYear'; -import isLeapYear from 'dayjs/plugin/isLeapYear'; -import localeData from 'dayjs/plugin/localeData'; -import localizedFormat from 'dayjs/plugin/localizedFormat'; -import quarterOfYear from 'dayjs/plugin/quarterOfYear'; -import relativeTime from 'dayjs/plugin/relativeTime'; -import isToday from 'dayjs/plugin/isToday'; -import isTomorrow from 'dayjs/plugin/isTomorrow'; -import isYesterday from 'dayjs/plugin/isYesterday'; -import objectSupport from 'dayjs/plugin/objectSupport'; -import toArray from 'dayjs/plugin/toArray'; -import toObject from 'dayjs/plugin/toObject'; +import timezone from "dayjs/plugin/timezone"; +import utc from "dayjs/plugin/utc"; +import minMax from "dayjs/plugin/minMax"; +import isBetween from "dayjs/plugin/isBetween"; +import customParseFormat from "dayjs/plugin/customParseFormat"; +import pluralGetSet from "dayjs/plugin/pluralGetSet"; +import duration from "dayjs/plugin/duration"; +import advancedFormat from "dayjs/plugin/advancedFormat"; +import arraySupport from "dayjs/plugin/arraySupport"; +import calendar from "dayjs/plugin/calendar"; +import dayOfYear from "dayjs/plugin/dayOfYear"; +import weekday from "dayjs/plugin/weekday"; +import weekOfYear from "dayjs/plugin/weekOfYear"; +import weekYear from "dayjs/plugin/weekYear"; +import isoWeek from "dayjs/plugin/isoWeek"; +import isoWeeksInYear from "dayjs/plugin/isoWeeksInYear"; +import isLeapYear from "dayjs/plugin/isLeapYear"; +import localeData from "dayjs/plugin/localeData"; +import localizedFormat from "dayjs/plugin/localizedFormat"; +import quarterOfYear from "dayjs/plugin/quarterOfYear"; +import relativeTime from "dayjs/plugin/relativeTime"; +import isToday from "dayjs/plugin/isToday"; +import isTomorrow from "dayjs/plugin/isTomorrow"; +import isYesterday from "dayjs/plugin/isYesterday"; +import objectSupport from "dayjs/plugin/objectSupport"; +import toArray from "dayjs/plugin/toArray"; +import toObject from "dayjs/plugin/toObject"; dayjs.extend(toObject); dayjs.extend(toArray); @@ -64,4 +64,4 @@ dayjs.extend(minMax); dayjs.extend(isBetween); dayjs.extend(dayjsBusinessDays); -export default dayjs; \ No newline at end of file +export default dayjs; diff --git a/client/src/utils/eulaize.js b/client/src/utils/eulaize.js index 26b972f15..407cc92db 100644 --- a/client/src/utils/eulaize.js +++ b/client/src/utils/eulaize.js @@ -1,16 +1,17 @@ -const fs = require('fs'); - +const fs = require("fs"); const filename = process.argv[2]; -fs.readFile(filename, 'utf8', (err, data) => { - if (err) { - console.error(`Error reading file ${filename}:`, err); - return; - } - const filteredData = JSON.stringify(data); - console.log('Select the content between the quotes below and paste it into the EULA Content field in the EULA Content table in the database.') - console.log('--------------------------------------------------') - console.log(filteredData); - console.log('--------------------------------------------------') -}); \ No newline at end of file +fs.readFile(filename, "utf8", (err, data) => { + if (err) { + console.error(`Error reading file ${filename}:`, err); + return; + } + const filteredData = JSON.stringify(data); + console.log( + "Select the content between the quotes below and paste it into the EULA Content field in the EULA Content table in the database." + ); + console.log("--------------------------------------------------"); + console.log(filteredData); + console.log("--------------------------------------------------"); +}); diff --git a/client/src/utils/fcm-handler.js b/client/src/utils/fcm-handler.js index f62df11f0..0b44abb49 100644 --- a/client/src/utils/fcm-handler.js +++ b/client/src/utils/fcm-handler.js @@ -1,69 +1,69 @@ -export default async function FcmHandler({client, 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; - } +export default async function FcmHandler({ client, 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/client/src/utils/formatbytes.js b/client/src/utils/formatbytes.js index a95456d6e..3e2be1b04 100644 --- a/client/src/utils/formatbytes.js +++ b/client/src/utils/formatbytes.js @@ -1,10 +1,8 @@ export default function formatBytes(a, b = 2) { - if (0 === a || !a) return "0 Bytes"; - const c = 0 > b ? 0 : b, - d = Math.floor(Math.log(a) / Math.log(1024)); - return ( - parseFloat((a / Math.pow(1024, d)).toFixed(c)) + - " " + - ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"][d] - ); + if (0 === a || !a) return "0 Bytes"; + const c = 0 > b ? 0 : b, + d = Math.floor(Math.log(a) / Math.log(1024)); + return ( + parseFloat((a / Math.pow(1024, d)).toFixed(c)) + " " + ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"][d] + ); } diff --git a/client/src/utils/graphQLmodifier.js b/client/src/utils/graphQLmodifier.js index d31c6584a..5e1c74b90 100644 --- a/client/src/utils/graphQLmodifier.js +++ b/client/src/utils/graphQLmodifier.js @@ -1,6 +1,6 @@ -import {Kind, parse, print, visit} from "graphql"; +import { Kind, parse, print, visit } from "graphql"; import client from "./GraphQLClient"; -import {gql} from "@apollo/client"; +import { gql } from "@apollo/client"; /* eslint-disable no-loop-func */ @@ -9,14 +9,14 @@ import {gql} from "@apollo/client"; * @type {[{label: string, value: string},{label: string, value: string},{label: string, value: string},{label: string, value: string},{label: string, value: string},null,null,null]} */ const STRING_OPERATORS = [ - {value: "_eq", label: "equals"}, - {value: "_neq", label: "does not equal"}, - {value: "_like", label: "contains"}, - {value: "_nlike", label: "does not contain"}, - {value: "_ilike", label: "contains case-insensitive"}, - {value: "_nilike", label: "does not contain case-insensitive"}, - {value: "_in", label: "in", type: "array"}, - {value: "_nin", label: "not in", type: "array"} + { value: "_eq", label: "equals" }, + { value: "_neq", label: "does not equal" }, + { value: "_like", label: "contains" }, + { value: "_nlike", label: "does not contain" }, + { value: "_ilike", label: "contains case-insensitive" }, + { value: "_nilike", label: "does not contain case-insensitive" }, + { value: "_in", label: "in", type: "array" }, + { value: "_nin", label: "not in", type: "array" } ]; /** @@ -24,14 +24,14 @@ const STRING_OPERATORS = [ * @type {[{label: string, value: string},{label: string, value: string},{label: string, value: string},{label: string, value: string},{label: string, value: string},null,null,null]} */ const DATE_OPERATORS = [ - {value: "_eq", label: "equals"}, - {value: "_neq", label: "does not equal"}, - {value: "_gt", label: "greater than"}, - {value: "_lt", label: "less than"}, - {value: "_gte", label: "greater than or equal"}, - {value: "_lte", label: "less than or equal"}, - {value: "_in", label: "in", type: "array"}, - {value: "_nin", label: "not in", type: "array"} + { value: "_eq", label: "equals" }, + { value: "_neq", label: "does not equal" }, + { value: "_gt", label: "greater than" }, + { value: "_lt", label: "less than" }, + { value: "_gte", label: "greater than or equal" }, + { value: "_lte", label: "less than or equal" }, + { value: "_in", label: "in", type: "array" }, + { value: "_nin", label: "not in", type: "array" } ]; /** @@ -39,8 +39,8 @@ const DATE_OPERATORS = [ * @type {[{label: string, value: string},{label: string, value: string}]} */ const BOOLEAN_OPERATORS = [ - {value: "_eq", label: "equals"}, - {value: "_neq", label: "does not equal"}, + { value: "_eq", label: "equals" }, + { value: "_neq", label: "does not equal" } ]; /** @@ -48,14 +48,14 @@ const BOOLEAN_OPERATORS = [ * @type {[{label: string, value: string},{label: string, value: string},{label: string, value: string},{label: string, value: string},{label: string, value: string},null,null,null]} */ const NUMBER_OPERATORS = [ - {value: "_eq", label: "equals"}, - {value: "_neq", label: "does not equal"}, - {value: "_gt", label: "greater than"}, - {value: "_lt", label: "less than"}, - {value: "_gte", label: "greater than or equal"}, - {value: "_lte", label: "less than or equal"}, - {value: "_in", label: "in", type: "array"}, - {value: "_nin", label: "not in", type: "array"} + { value: "_eq", label: "equals" }, + { value: "_neq", label: "does not equal" }, + { value: "_gt", label: "greater than" }, + { value: "_lt", label: "less than" }, + { value: "_gte", label: "greater than or equal" }, + { value: "_lte", label: "less than or equal" }, + { value: "_in", label: "in", type: "array" }, + { value: "_nin", label: "not in", type: "array" } ]; /** @@ -63,8 +63,8 @@ const NUMBER_OPERATORS = [ * @type {[{label: string, value: string},{label: string, value: string}]} */ const ORDER_BY_OPERATORS = [ - {value: "asc", label: "ascending"}, - {value: "desc", label: "descending"} + { value: "asc", label: "ascending" }, + { value: "desc", label: "descending" } ]; /** @@ -72,7 +72,7 @@ const ORDER_BY_OPERATORS = [ * @returns {[{label: string, value: string},{label: string, value: string}]} */ export function getOrderOperatorsByType() { - return ORDER_BY_OPERATORS; + return ORDER_BY_OPERATORS; } /** @@ -80,15 +80,15 @@ export function getOrderOperatorsByType() { * @param type * @returns {[{label: string, value: string},{label: string, value: string},{label: string, value: string},{label: string, value: string},{label: string, value: string},null]} */ -export function getWhereOperatorsByType(type = 'string') { - const operators = { - string: STRING_OPERATORS, - number: NUMBER_OPERATORS, - boolean: BOOLEAN_OPERATORS, - bool: BOOLEAN_OPERATORS, - date: DATE_OPERATORS - }; - return operators[type]; +export function getWhereOperatorsByType(type = "string") { + const operators = { + string: STRING_OPERATORS, + number: NUMBER_OPERATORS, + boolean: BOOLEAN_OPERATORS, + bool: BOOLEAN_OPERATORS, + date: DATE_OPERATORS + }; + return operators[type]; } /** @@ -97,7 +97,7 @@ export function getWhereOperatorsByType(type = 'string') { * @returns {DocumentNode} */ export function parseQuery(query) { - return parse(query); + return parse(query); } /** @@ -106,7 +106,7 @@ export function parseQuery(query) { * @returns {string} */ export function printQuery(query) { - return print(query); + return print(query); } /** @@ -117,38 +117,37 @@ export function printQuery(query) { * @returns {Promise<{contextData: {}, useShopSpecificTemplate}>} */ export async function generateTemplate(templateQueryToExecute, templateObject, useShopSpecificTemplate) { - // Advanced Filtering and Sorting modifications start here + // Advanced Filtering and Sorting modifications start here - // Parse the query and apply the filters and sorters - const ast = parseQuery(templateQueryToExecute); + // Parse the query and apply the filters and sorters + const ast = parseQuery(templateQueryToExecute); + if (templateObject?.filters && templateObject?.filters?.length) { + applyFilters(ast, templateObject.filters); + } - if (templateObject?.filters && templateObject?.filters?.length) { - applyFilters(ast, templateObject.filters); - } + if (templateObject?.sorters && templateObject?.sorters?.length) { + applySorters(ast, templateObject.sorters); + } else if (templateObject?.defaultSorters && templateObject?.defaultSorters?.length) { + applySorters(ast, templateObject.defaultSorters); + } - if (templateObject?.sorters && templateObject?.sorters?.length) { - applySorters(ast, templateObject.sorters); - } else if (templateObject?.defaultSorters && templateObject?.defaultSorters?.length) { - applySorters(ast, templateObject.defaultSorters); - } + const finalQuery = printQuery(ast); - const finalQuery = printQuery(ast); + // commented out for future revision debugging + // console.log('Modified Query'); + // console.log(finalQuery); - // commented out for future revision debugging - // console.log('Modified Query'); - // console.log(finalQuery); + let contextData = {}; + if (templateQueryToExecute) { + const { data } = await client.query({ + query: gql(finalQuery), + variables: { ...templateObject.variables } + }); + contextData = data; + } - let contextData = {}; - if (templateQueryToExecute) { - const {data} = await client.query({ - query: gql(finalQuery), - variables: {...templateObject.variables}, - }); - contextData = data; - } - - return {contextData, useShopSpecificTemplate}; + return { contextData, useShopSpecificTemplate }; } /** @@ -157,58 +156,58 @@ export async function generateTemplate(templateQueryToExecute, templateObject, u * @param sorters */ export function applySorters(ast, sorters) { - sorters.forEach((sorter) => { - const fieldPath = sorter.field.split('.'); - visit(ast, { - OperationDefinition: { + sorters.forEach((sorter) => { + const fieldPath = sorter.field.split("."); + visit(ast, { + OperationDefinition: { + enter(node) { + // Loop through each sorter to apply it + // noinspection DuplicatedCode + + let currentSelection = node; // Start with the root operation + + // Navigate down the field path to the correct location + for (let i = 0; i < fieldPath.length - 1; i++) { + let found = false; + visit(currentSelection, { + Field: { enter(node) { - // Loop through each sorter to apply it - // noinspection DuplicatedCode - - let currentSelection = node; // Start with the root operation - - // Navigate down the field path to the correct location - for (let i = 0; i < fieldPath.length - 1; i++) { - let found = false; - visit(currentSelection, { - Field: { - enter(node) { - if (node.name.value === fieldPath[i]) { - currentSelection = node; // Move down to the next level - found = true; - } - } - } - }); - if (!found) break; // Stop if we can't find the next field in the path - } - - // Apply the sorter at the correct level - if (currentSelection) { - const targetFieldName = fieldPath[fieldPath.length - 1]; - let orderByArg = currentSelection.arguments.find(arg => arg.name.value === 'order_by'); - if (!orderByArg) { - orderByArg = { - kind: Kind.ARGUMENT, - name: {kind: Kind.NAME, value: 'order_by'}, - value: {kind: Kind.OBJECT, fields: []}, - }; - currentSelection.arguments.push(orderByArg); - } - - const sorterField = { - kind: Kind.OBJECT_FIELD, - name: {kind: Kind.NAME, value: targetFieldName}, - value: {kind: Kind.ENUM, value: sorter.direction}, // Adjust if your schema uses a different type for sorting directions - }; - - // Add the new sorter condition - orderByArg.value.fields.push(sorterField); - } + if (node.name.value === fieldPath[i]) { + currentSelection = node; // Move down to the next level + found = true; + } } + } + }); + if (!found) break; // Stop if we can't find the next field in the path + } + + // Apply the sorter at the correct level + if (currentSelection) { + const targetFieldName = fieldPath[fieldPath.length - 1]; + let orderByArg = currentSelection.arguments.find((arg) => arg.name.value === "order_by"); + if (!orderByArg) { + orderByArg = { + kind: Kind.ARGUMENT, + name: { kind: Kind.NAME, value: "order_by" }, + value: { kind: Kind.OBJECT, fields: [] } + }; + currentSelection.arguments.push(orderByArg); } - }); + + const sorterField = { + kind: Kind.OBJECT_FIELD, + name: { kind: Kind.NAME, value: targetFieldName }, + value: { kind: Kind.ENUM, value: sorter.direction } // Adjust if your schema uses a different type for sorting directions + }; + + // Add the new sorter condition + orderByArg.value.fields.push(sorterField); + } + } + } }); + }); } /** @@ -218,45 +217,47 @@ export function applySorters(ast, sorters) { * @param filterField */ function applyTopLevelSub(node, fieldPath, filterField) { - // Find or create the where argument for the top-level subfield - let whereArg = node.selectionSet.selections - .find(selection => selection.name.value === fieldPath[0]) - ?.arguments.find(arg => arg.name.value === 'where'); + // Find or create the where argument for the top-level subfield + let whereArg = node.selectionSet.selections + .find((selection) => selection.name.value === fieldPath[0]) + ?.arguments.find((arg) => arg.name.value === "where"); - if (!whereArg) { - whereArg = { - kind: Kind.ARGUMENT, - name: {kind: Kind.NAME, value: 'where'}, - value: {kind: Kind.OBJECT, fields: []}, + if (!whereArg) { + whereArg = { + kind: Kind.ARGUMENT, + name: { kind: Kind.NAME, value: "where" }, + value: { kind: Kind.OBJECT, fields: [] } + }; + const topLevelSubSelection = node.selectionSet.selections.find( + (selection) => selection.name.value === fieldPath[0] + ); + if (topLevelSubSelection) { + topLevelSubSelection.arguments = topLevelSubSelection.arguments || []; + topLevelSubSelection.arguments.push(whereArg); + } + } + + // Correctly position the nested filter without an extra 'where' + if (fieldPath.length > 2) { + // More than one level deep + let currentField = whereArg.value; + fieldPath.slice(1, -1).forEach((path, index) => { + let existingField = currentField.fields.find((f) => f.name.value === path); + if (!existingField) { + existingField = { + kind: Kind.OBJECT_FIELD, + name: { kind: Kind.NAME, value: path }, + value: { kind: Kind.OBJECT, fields: [] } }; - const topLevelSubSelection = node.selectionSet.selections.find(selection => - selection.name.value === fieldPath[0] - ); - if (topLevelSubSelection) { - topLevelSubSelection.arguments = topLevelSubSelection.arguments || []; - topLevelSubSelection.arguments.push(whereArg); - } - } - - // Correctly position the nested filter without an extra 'where' - if (fieldPath.length > 2) { // More than one level deep - let currentField = whereArg.value; - fieldPath.slice(1, -1).forEach((path, index) => { - let existingField = currentField.fields.find(f => f.name.value === path); - if (!existingField) { - existingField = { - kind: Kind.OBJECT_FIELD, - name: {kind: Kind.NAME, value: path}, - value: {kind: Kind.OBJECT, fields: []} - }; - currentField.fields.push(existingField); - } - currentField = existingField.value; - }); - currentField.fields.push(filterField); - } else { // Directly under the top level - whereArg.value.fields.push(filterField); - } + currentField.fields.push(existingField); + } + currentField = existingField.value; + }); + currentField.fields.push(filterField); + } else { + // Directly under the top level + whereArg.value.fields.push(filterField); + } } /** @@ -266,41 +267,41 @@ function applyTopLevelSub(node, fieldPath, filterField) { * @returns {ASTNode} */ export function applyFilters(ast, filters) { - return visit(ast, { - OperationDefinition: { - enter(node) { - filters.forEach(filter => { - const fieldPath = filter.field.split('.'); - let topLevel = false; - let topLevelSub = false; + return visit(ast, { + OperationDefinition: { + enter(node) { + filters.forEach((filter) => { + const fieldPath = filter.field.split("."); + let topLevel = false; + let topLevelSub = false; - // Determine if the filter should be applied at the top level - if (fieldPath.length === 2) { - topLevel = true; - } + // Determine if the filter should be applied at the top level + if (fieldPath.length === 2) { + topLevel = true; + } - if (fieldPath.length > 2 && fieldPath[0].startsWith('[') && fieldPath[0].endsWith(']')) { - fieldPath[0] = fieldPath[0].substring(1, fieldPath[0].length - 1); // Strip the brackets - topLevelSub = true; - } + if (fieldPath.length > 2 && fieldPath[0].startsWith("[") && fieldPath[0].endsWith("]")) { + fieldPath[0] = fieldPath[0].substring(1, fieldPath[0].length - 1); // Strip the brackets + topLevelSub = true; + } - // Construct the filter for a top-level application - const targetFieldName = fieldPath[fieldPath.length - 1]; + // Construct the filter for a top-level application + const targetFieldName = fieldPath[fieldPath.length - 1]; - let filterValue = createFilterValue(filter); - let filterField = createFilterField(targetFieldName, filter, filterValue); + let filterValue = createFilterValue(filter); + let filterField = createFilterField(targetFieldName, filter, filterValue); - if (topLevel) { - applyTopLevelFilter(node, fieldPath, filterField); - } else if (topLevelSub) { - applyTopLevelSub(node, fieldPath, filterField); - } else { - applyNestedFilter(node, fieldPath, filterField); - } - }); - } - } - }); + if (topLevel) { + applyTopLevelFilter(node, fieldPath, filterField); + } else if (topLevelSub) { + applyTopLevelSub(node, fieldPath, filterField); + } else { + applyNestedFilter(node, fieldPath, filterField); + } + }); + } + } + }); } /** @@ -309,22 +310,22 @@ export function applyFilters(ast, filters) { * @returns {{kind: (Kind|Kind.INT), value}|{kind: Kind.LIST, values: *}} */ function createFilterValue(filter) { - if (Array.isArray(filter.value)) { - // If it's an array, create a list value with the array items - return { - kind: Kind.LIST, - values: filter.value.map(item => ({ - kind: getGraphQLKind(item), - value: item, - })), - }; - } else { - // If it's not an array, use the existing logic - return { - kind: getGraphQLKind(filter.value), - value: filter.value, - }; - } + if (Array.isArray(filter.value)) { + // If it's an array, create a list value with the array items + return { + kind: Kind.LIST, + values: filter.value.map((item) => ({ + kind: getGraphQLKind(item), + value: item + })) + }; + } else { + // If it's not an array, use the existing logic + return { + kind: getGraphQLKind(filter.value), + value: filter.value + }; + } } /** @@ -335,18 +336,20 @@ function createFilterValue(filter) { * @returns {{kind: Kind.OBJECT_FIELD, name: {kind: Kind.NAME, value}, value: {kind: Kind.OBJECT, fields: [{kind: Kind.OBJECT_FIELD, name: {kind: Kind.NAME, value}, value}]}}} */ function createFilterField(targetFieldName, filter, filterValue) { - return { - kind: Kind.OBJECT_FIELD, - name: {kind: Kind.NAME, value: targetFieldName}, - value: { - kind: Kind.OBJECT, - fields: [{ - kind: Kind.OBJECT_FIELD, - name: {kind: Kind.NAME, value: filter.operator}, - value: filterValue, - }], - }, - }; + return { + kind: Kind.OBJECT_FIELD, + name: { kind: Kind.NAME, value: targetFieldName }, + value: { + kind: Kind.OBJECT, + fields: [ + { + kind: Kind.OBJECT_FIELD, + name: { kind: Kind.NAME, value: filter.operator }, + value: filterValue + } + ] + } + }; } /** @@ -356,45 +359,45 @@ function createFilterField(targetFieldName, filter, filterValue) { * @param filterField */ function applyTopLevelFilter(node, fieldPath, filterField) { - // Find or create the where argument for the top-level field - let whereArg = node.selectionSet.selections - .find(selection => selection.name.value === fieldPath[0]) - ?.arguments.find(arg => arg.name.value === 'where'); + // Find or create the where argument for the top-level field + let whereArg = node.selectionSet.selections + .find((selection) => selection.name.value === fieldPath[0]) + ?.arguments.find((arg) => arg.name.value === "where"); - if (!whereArg) { - whereArg = { - kind: Kind.ARGUMENT, - name: {kind: Kind.NAME, value: 'where'}, - value: {kind: Kind.OBJECT, fields: []}, + if (!whereArg) { + whereArg = { + kind: Kind.ARGUMENT, + name: { kind: Kind.NAME, value: "where" }, + value: { kind: Kind.OBJECT, fields: [] } + }; + const topLevelSelection = node.selectionSet.selections.find((selection) => selection.name.value === fieldPath[0]); + if (topLevelSelection) { + topLevelSelection.arguments = topLevelSelection.arguments || []; + topLevelSelection.arguments.push(whereArg); + } + } + + // Correctly position the nested filter without an extra 'where' + if (fieldPath.length > 2) { + // More than one level deep + let currentField = whereArg.value; + fieldPath.slice(1, -1).forEach((path, index) => { + let existingField = currentField.fields.find((f) => f.name.value === path); + if (!existingField) { + existingField = { + kind: Kind.OBJECT_FIELD, + name: { kind: Kind.NAME, value: path }, + value: { kind: Kind.OBJECT, fields: [] } }; - const topLevelSelection = node.selectionSet.selections.find(selection => - selection.name.value === fieldPath[0] - ); - if (topLevelSelection) { - topLevelSelection.arguments = topLevelSelection.arguments || []; - topLevelSelection.arguments.push(whereArg); - } - } - - // Correctly position the nested filter without an extra 'where' - if (fieldPath.length > 2) { // More than one level deep - let currentField = whereArg.value; - fieldPath.slice(1, -1).forEach((path, index) => { - let existingField = currentField.fields.find(f => f.name.value === path); - if (!existingField) { - existingField = { - kind: Kind.OBJECT_FIELD, - name: {kind: Kind.NAME, value: path}, - value: {kind: Kind.OBJECT, fields: []} - }; - currentField.fields.push(existingField); - } - currentField = existingField.value; - }); - currentField.fields.push(filterField); - } else { // Directly under the top level - whereArg.value.fields.push(filterField); - } + currentField.fields.push(existingField); + } + currentField = existingField.value; + }); + currentField.fields.push(filterField); + } else { + // Directly under the top level + whereArg.value.fields.push(filterField); + } } /** @@ -404,46 +407,46 @@ function applyTopLevelFilter(node, fieldPath, filterField) { * @param filterField */ function applyNestedFilter(node, fieldPath, filterField) { - // Initialize a reference to the current selection to traverse down the AST - let currentSelection = node; + // Initialize a reference to the current selection to traverse down the AST + let currentSelection = node; - // Iterate over the fieldPath, except for the last entry, to navigate the structure - for (let i = 0; i < fieldPath.length - 1; i++) { - const fieldName = fieldPath[i]; - let fieldFound = false; + // Iterate over the fieldPath, except for the last entry, to navigate the structure + for (let i = 0; i < fieldPath.length - 1; i++) { + const fieldName = fieldPath[i]; + let fieldFound = false; - // Check if the current selection has a selectionSet and selections - if (currentSelection.selectionSet && currentSelection.selectionSet.selections) { - // Look for the field in the current selection's selections - const selection = currentSelection.selectionSet.selections.find(sel => sel.name.value === fieldName); - if (selection) { - // Move down the AST to the found selection - currentSelection = selection; - fieldFound = true; - } - } - - // If the field was not found in the current path, it's an issue - if (!fieldFound) { - console.error(`Field ${fieldName} not found in the current selection.`); - return; // Exit the loop and function due to error - } + // Check if the current selection has a selectionSet and selections + if (currentSelection.selectionSet && currentSelection.selectionSet.selections) { + // Look for the field in the current selection's selections + const selection = currentSelection.selectionSet.selections.find((sel) => sel.name.value === fieldName); + if (selection) { + // Move down the AST to the found selection + currentSelection = selection; + fieldFound = true; + } } - // At this point, currentSelection should be the parent field where the filter needs to be applied - // Check if the 'where' argument already exists in the current selection - const whereArg = currentSelection.arguments.find(arg => arg.name.value === 'where'); - if (!whereArg) { - // If not found, create a new 'where' argument for the current selection - currentSelection.arguments.push({ - kind: Kind.ARGUMENT, - name: {kind: Kind.NAME, value: 'where'}, - value: {kind: Kind.OBJECT, fields: []} // Empty fields array to be populated with the filter - }); + // If the field was not found in the current path, it's an issue + if (!fieldFound) { + console.error(`Field ${fieldName} not found in the current selection.`); + return; // Exit the loop and function due to error } + } - // Add the filter field to the 'where' clause of the current selection - currentSelection.arguments.find(arg => arg.name.value === 'where').value.fields.push(filterField); + // At this point, currentSelection should be the parent field where the filter needs to be applied + // Check if the 'where' argument already exists in the current selection + const whereArg = currentSelection.arguments.find((arg) => arg.name.value === "where"); + if (!whereArg) { + // If not found, create a new 'where' argument for the current selection + currentSelection.arguments.push({ + kind: Kind.ARGUMENT, + name: { kind: Kind.NAME, value: "where" }, + value: { kind: Kind.OBJECT, fields: [] } // Empty fields array to be populated with the filter + }); + } + + // Add the filter field to the 'where' clause of the current selection + currentSelection.arguments.find((arg) => arg.name.value === "where").value.fields.push(filterField); } /** @@ -452,17 +455,17 @@ function applyNestedFilter(node, fieldPath, filterField) { * @returns {Kind|Kind.INT} */ function getGraphQLKind(value) { - if (Array.isArray(value)) { - return Kind.LIST; - } else if (typeof value === 'number') { - return value % 1 === 0 ? Kind.INT : Kind.FLOAT; - } else if (typeof value === 'boolean') { - return Kind.BOOLEAN; - } else if (typeof value === 'string') { - return Kind.STRING; - } else if (value instanceof Date) { - return Kind.STRING; // GraphQL does not have a Date type, so we return it as a string - } + if (Array.isArray(value)) { + return Kind.LIST; + } else if (typeof value === "number") { + return value % 1 === 0 ? Kind.INT : Kind.FLOAT; + } else if (typeof value === "boolean") { + return Kind.BOOLEAN; + } else if (typeof value === "string") { + return Kind.STRING; + } else if (value instanceof Date) { + return Kind.STRING; // GraphQL does not have a Date type, so we return it as a string + } } /* eslint-enable no-loop-func */ diff --git a/client/src/utils/handleBeta.js b/client/src/utils/handleBeta.js index b667c920d..804b38b7d 100644 --- a/client/src/utils/handleBeta.js +++ b/client/src/utils/handleBeta.js @@ -1,37 +1,36 @@ -export const BETA_KEY = 'betaSwitchImex'; +export const BETA_KEY = "betaSwitchImex"; export const checkBeta = () => { - const cookie = document.cookie.split('; ').find(row => row.startsWith(BETA_KEY)); - return cookie ? cookie.split('=')[1] === 'true' : false; -} - + const cookie = document.cookie.split("; ").find((row) => row.startsWith(BETA_KEY)); + return cookie ? cookie.split("=")[1] === "true" : false; +}; export const setBeta = (value) => { - const domain = window.location.hostname.split('.').slice(-2).join('.'); - document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`; -} + const domain = window.location.hostname.split(".").slice(-2).join("."); + document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`; +}; export const handleBeta = () => { - // If the current host name does not start with beta or test, then we don't need to do anything. - if (window.location.hostname.startsWith('localhost')) { - console.log('Not on beta or test, so no need to handle beta.'); - return; - } + // If the current host name does not start with beta or test, then we don't need to do anything. + if (window.location.hostname.startsWith("localhost")) { + console.log("Not on beta or test, so no need to handle beta."); + return; + } - const isBeta = checkBeta(); + const isBeta = checkBeta(); - const currentHostName = window.location.hostname; + const currentHostName = window.location.hostname; - // Beta is enabled, but the current host name does start with beta. - if (isBeta && !currentHostName.startsWith('beta')) { - const href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`; - window.location.replace(href); - } + // Beta is enabled, but the current host name does start with beta. + if (isBeta && !currentHostName.startsWith("beta")) { + const href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`; + window.location.replace(href); + } - // Beta is not enabled, but the current host name does start with beta. - else if (!isBeta && currentHostName.startsWith('beta')) { - const href = `${window.location.protocol}//${currentHostName.replace('beta.', '')}${window.location.pathname}${window.location.search}${window.location.hash}`; - window.location.replace(href); - } -} + // Beta is not enabled, but the current host name does start with beta. + else if (!isBeta && currentHostName.startsWith("beta")) { + const href = `${window.location.protocol}//${currentHostName.replace("beta.", "")}${window.location.pathname}${window.location.search}${window.location.hash}`; + window.location.replace(href); + } +}; export default handleBeta; diff --git a/client/src/utils/instanceRenderMgr.js b/client/src/utils/instanceRenderMgr.js index 36f8f115b..cb436dd0d 100644 --- a/client/src/utils/instanceRenderMgr.js +++ b/client/src/utils/instanceRenderMgr.js @@ -9,34 +9,25 @@ * @property { string | object | function } imex Return this prop if Rome. */ -export default function InstanceRenderManager({ - executeFunction, - rome, - promanager, - imex, - debug, - instance, - args, -}) { +export default function InstanceRenderManager({ executeFunction, rome, promanager, imex, debug, instance, args }) { let propToReturn = null; switch (instance || import.meta.env.VITE_APP_INSTANCE) { - case 'IMEX': + case "IMEX": propToReturn = imex; break; - case 'ROME': - if (rome === 'USE_IMEX') { + case "ROME": + if (rome === "USE_IMEX") { propToReturn = imex; } else { propToReturn = rome; } break; - case 'PROMANAGER': + case "PROMANAGER": //Return the rome prop if USE_ROME. //If not USE_ROME, we want to default back to the rome prop if it's undefined. //If null, we might want to show nothing, so make sure we return null. - propToReturn = - promanager === 'USE_ROME' ? rome : promanager !== undefined ? promanager : rome; + propToReturn = promanager === "USE_ROME" ? rome : promanager !== undefined ? promanager : rome; break; default: propToReturn = imex; @@ -44,16 +35,16 @@ export default function InstanceRenderManager({ } if (debug) { - console.log('InstanceRenderManager Debugger'); - console.log('========================='); + console.log("InstanceRenderManager Debugger"); + console.log("========================="); console.log({ executeFunction, rome, promanager, imex, debug, propToReturn }); - console.log('========================='); + console.log("========================="); } //Checking to see if we need to default to another one. - if (propToReturn === 'imex') { + if (propToReturn === "imex") { propToReturn = imex; } - if (executeFunction && typeof propToReturn === 'function') return propToReturn(...args); + if (executeFunction && typeof propToReturn === "function") return propToReturn(...args); return propToReturn === undefined ? null : propToReturn; } diff --git a/client/src/utils/jobReadOnly.js b/client/src/utils/jobReadOnly.js index d700c3579..e2f85ab9e 100644 --- a/client/src/utils/jobReadOnly.js +++ b/client/src/utils/jobReadOnly.js @@ -1,4 +1,4 @@ export default function IsJobReadOnly(job) { - if (!job) return false; - return job.date_exported || job.date_invoiced || job.voided; + if (!job) return false; + return job.date_exported || job.date_invoiced || job.voided; } diff --git a/client/src/utils/localmedia.js b/client/src/utils/localmedia.js index 1b46e9521..7734d4b2b 100644 --- a/client/src/utils/localmedia.js +++ b/client/src/utils/localmedia.js @@ -1,6 +1,6 @@ -import {store} from "../redux/store"; +import { store } from "../redux/store"; -export function CreateExplorerLinkForJob({jobid}) { - const bodyshop = store.getState().user.bodyshop; - return `imexmedia://${bodyshop.localmediaservernetwork}\\Jobs\\${jobid}`; +export function CreateExplorerLinkForJob({ jobid }) { + const bodyshop = store.getState().user.bodyshop; + return `imexmedia://${bodyshop.localmediaservernetwork}\\Jobs\\${jobid}`; } diff --git a/client/src/utils/prompt.js b/client/src/utils/prompt.js index f7a0746cb..94ad7c0c3 100644 --- a/client/src/utils/prompt.js +++ b/client/src/utils/prompt.js @@ -1,47 +1,46 @@ -import {useBeforeUnload, useBlocker} from "react-router-dom"; -import {useCallback, useEffect, useRef} from "react"; +import { useBeforeUnload, useBlocker } from "react-router-dom"; +import { useCallback, useEffect, useRef } from "react"; -function usePrompt(message, {beforeUnload} = {}) { - - let blocker = useBlocker( - useCallback( - (location) => { - // This has been put in, so it does not affect transitions between the same page - if (location.currentLocation.pathname === location.nextLocation.pathname) { - return false; - } - return typeof message === "string" ? !window.confirm(message) : false; - }, - [message] - ) - ); - - let prevState = useRef(blocker.state); - - useEffect(() => { - if (blocker.state === "blocked") { - blocker.reset(); +function usePrompt(message, { beforeUnload } = {}) { + let blocker = useBlocker( + useCallback( + (location) => { + // This has been put in, so it does not affect transitions between the same page + if (location.currentLocation.pathname === location.nextLocation.pathname) { + return false; } - prevState.current = blocker.state; - }, [blocker]); + return typeof message === "string" ? !window.confirm(message) : false; + }, + [message] + ) + ); - useBeforeUnload( - useCallback( - (event) => { - if (beforeUnload && typeof message === "string") { - event.preventDefault(); - event.returnValue = message; - } - }, - [message, beforeUnload] - ), - {capture: true} - ); + let prevState = useRef(blocker.state); + + useEffect(() => { + if (blocker.state === "blocked") { + blocker.reset(); + } + prevState.current = blocker.state; + }, [blocker]); + + useBeforeUnload( + useCallback( + (event) => { + if (beforeUnload && typeof message === "string") { + event.preventDefault(); + event.returnValue = message; + } + }, + [message, beforeUnload] + ), + { capture: true } + ); } -function Prompt({when, message, ...props}) { - usePrompt(when ? message : false, props); - return null; +function Prompt({ when, message, ...props }) { + usePrompt(when ? message : false, props); + return null; } -export default Prompt; \ No newline at end of file +export default Prompt; diff --git a/client/src/utils/sorters.js b/client/src/utils/sorters.js index 7144eb671..00e1cd853 100644 --- a/client/src/utils/sorters.js +++ b/client/src/utils/sorters.js @@ -1,19 +1,17 @@ export function alphaSort(a, b) { - return (a ? a.toLowerCase() : "").localeCompare(b ? b.toLowerCase() : ""); + return (a ? a.toLowerCase() : "").localeCompare(b ? b.toLowerCase() : ""); - // if (A < B) - // //sort string ascending - // return -1; - // if (A > B) return 1; - // return 0; //default return value (no sorting) + // if (A < B) + // //sort string ascending + // return -1; + // if (A > B) return 1; + // return 0; //default return value (no sorting) } export function dateSort(a, b) { - return new Date(a) - new Date(b); + return new Date(a) - new Date(b); } export function statusSort(a, b, statusList) { - return ( - statusList.findIndex((x) => x === a) - statusList.findIndex((x) => x === b) - ); + return statusList.findIndex((x) => x === a) - statusList.findIndex((x) => x === b); } diff --git a/client/src/utils/undefinedtonull.js b/client/src/utils/undefinedtonull.js index 56a67947a..e19724500 100644 --- a/client/src/utils/undefinedtonull.js +++ b/client/src/utils/undefinedtonull.js @@ -1,10 +1,10 @@ export default function UndefinedToNull(obj, keys) { - Object.keys(obj).forEach((key) => { - if (keys && keys.indexOf(key) >= 0) { - if (obj[key] === undefined) obj[key] = null; - } else { - if (obj[key] === undefined) obj[key] = null; - } - }); - return obj; + Object.keys(obj).forEach((key) => { + if (keys && keys.indexOf(key) >= 0) { + if (obj[key] === undefined) obj[key] = null; + } else { + if (obj[key] === undefined) obj[key] = null; + } + }); + return obj; } diff --git a/client/src/utils/useEffectDebugger.js b/client/src/utils/useEffectDebugger.js index 7f46efd47..ab6014085 100644 --- a/client/src/utils/useEffectDebugger.js +++ b/client/src/utils/useEffectDebugger.js @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react'; +import { useEffect, useRef } from "react"; const usePrevious = (value, initialValue) => { const ref = useRef(initialValue); @@ -18,8 +18,8 @@ const useEffectDebugger = (effectHook, dependencies, dependencyNames = []) => { ...accum, [keyName]: { before: previousDeps[index], - after: dependency, - }, + after: dependency + } }; } @@ -27,7 +27,7 @@ const useEffectDebugger = (effectHook, dependencies, dependencyNames = []) => { }, {}); if (Object.keys(changedDeps).length) { - console.log('[use-effect-debugger] ', changedDeps); + console.log("[use-effect-debugger] ", changedDeps); } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/client/src/utils/useKeyboardShortcut.jsx b/client/src/utils/useKeyboardShortcut.jsx index 32148b9ec..f3ba200a9 100644 --- a/client/src/utils/useKeyboardShortcut.jsx +++ b/client/src/utils/useKeyboardShortcut.jsx @@ -1,128 +1,127 @@ -import {useCallback, useEffect, useReducer, useState} from "react"; +import { useCallback, useEffect, useReducer, useState } from "react"; //Based on https://www.fullstacklabs.co/blog/keyboard-shortcuts-with-react-hooks const blacklistedTargets = []; // ["INPUT", "TEXTAREA"]; export const useKeyboardSaveShortcut = (callback) => - useKeyboardShortcut(["Control", "S"], callback, {overrideSystem: true}); + useKeyboardShortcut(["Control", "S"], callback, { overrideSystem: true }); const keysReducer = (state, action) => { - switch (action.type) { - case "set-key-down": - const keydownState = {...state, [action.key]: true}; - return keydownState; - case "set-key-up": - const keyUpState = {...state, [action.key]: false}; - return keyUpState; - case "reset-keys": - const resetState = {...action.data}; - return resetState; - default: - return state; - } + switch (action.type) { + case "set-key-down": + const keydownState = { ...state, [action.key]: true }; + return keydownState; + case "set-key-up": + const keyUpState = { ...state, [action.key]: false }; + return keyUpState; + case "reset-keys": + const resetState = { ...action.data }; + return resetState; + default: + return state; + } }; const useKeyboardShortcut = (shortcutKeys, callback, options) => { - if (!Array.isArray(shortcutKeys)) - throw new Error( - "The first parameter to `useKeyboardShortcut` must be an ordered array of `KeyboardEvent.key` strings." - ); - - if (!shortcutKeys.length) - throw new Error( - "The first parameter to `useKeyboardShortcut` must contain atleast one `KeyboardEvent.key` string." - ); - - if (!callback || typeof callback !== "function") - throw new Error( - "The second parameter to `useKeyboardShortcut` must be a function that will be envoked when the keys are pressed." - ); - - const {overrideSystem} = options || {}; - const initalKeyMapping = shortcutKeys.reduce((currentKeys, key) => { - currentKeys[key.toLowerCase()] = false; - return currentKeys; - }, {}); - - const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping); - const [listenersAdded, setListenersAdded] = useState(false); - - const keydownListener = useCallback( - (assignedKey) => (keydownEvent) => { - const loweredKey = assignedKey.toLowerCase(); - - if (keydownEvent.repeat) return; - if (blacklistedTargets.includes(keydownEvent.target.tagName)) return; - if (loweredKey !== keydownEvent.key.toLowerCase()) return; - if (keys[loweredKey] === undefined) return; - - if (overrideSystem) { - keydownEvent.preventDefault(); - disabledEventPropagation(keydownEvent); - } - - setKeys({type: "set-key-down", key: loweredKey}); - return false; - }, - [keys, overrideSystem] + if (!Array.isArray(shortcutKeys)) + throw new Error( + "The first parameter to `useKeyboardShortcut` must be an ordered array of `KeyboardEvent.key` strings." ); - const keyupListener = useCallback( - (assignedKey) => (keyupEvent) => { - const raisedKey = assignedKey.toLowerCase(); - - if (blacklistedTargets.includes(keyupEvent.target.tagName)) return; - if (keyupEvent.key.toLowerCase() !== raisedKey) return; - if (keys[raisedKey] === undefined) return; - - if (overrideSystem) { - keyupEvent.preventDefault(); - disabledEventPropagation(keyupEvent); - } - - setKeys({type: "set-key-up", key: raisedKey}); - return false; - }, - [keys, overrideSystem] + if (!shortcutKeys.length) + throw new Error( + "The first parameter to `useKeyboardShortcut` must contain atleast one `KeyboardEvent.key` string." ); - useEffect(() => { - if (!Object.values(keys).filter((value) => !value).length) { - callback(keys); - setKeys({type: "reset-keys", data: initalKeyMapping}); - } else { - setKeys({type: null}); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [callback, keys]); + if (!callback || typeof callback !== "function") + throw new Error( + "The second parameter to `useKeyboardShortcut` must be a function that will be envoked when the keys are pressed." + ); - useEffect(() => { - if (!listenersAdded) { - console.log('Added events for keyup and keydown'); - shortcutKeys.forEach((k) => { - window.addEventListener("keydown", keydownListener(k)); - window.addEventListener("keyup", keyupListener(k)) - }); - } + const { overrideSystem } = options || {}; + const initalKeyMapping = shortcutKeys.reduce((currentKeys, key) => { + currentKeys[key.toLowerCase()] = false; + return currentKeys; + }, {}); - setListenersAdded(true); + const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping); + const [listenersAdded, setListenersAdded] = useState(false); - return () => { - shortcutKeys.forEach((k) => { - window.removeEventListener("keydown", keydownListener(k)); - window.removeEventListener("keyup", keyupListener(k)); - }); - } - }, [listenersAdded]); + const keydownListener = useCallback( + (assignedKey) => (keydownEvent) => { + const loweredKey = assignedKey.toLowerCase(); + if (keydownEvent.repeat) return; + if (blacklistedTargets.includes(keydownEvent.target.tagName)) return; + if (loweredKey !== keydownEvent.key.toLowerCase()) return; + if (keys[loweredKey] === undefined) return; + + if (overrideSystem) { + keydownEvent.preventDefault(); + disabledEventPropagation(keydownEvent); + } + + setKeys({ type: "set-key-down", key: loweredKey }); + return false; + }, + [keys, overrideSystem] + ); + + const keyupListener = useCallback( + (assignedKey) => (keyupEvent) => { + const raisedKey = assignedKey.toLowerCase(); + + if (blacklistedTargets.includes(keyupEvent.target.tagName)) return; + if (keyupEvent.key.toLowerCase() !== raisedKey) return; + if (keys[raisedKey] === undefined) return; + + if (overrideSystem) { + keyupEvent.preventDefault(); + disabledEventPropagation(keyupEvent); + } + + setKeys({ type: "set-key-up", key: raisedKey }); + return false; + }, + [keys, overrideSystem] + ); + + useEffect(() => { + if (!Object.values(keys).filter((value) => !value).length) { + callback(keys); + setKeys({ type: "reset-keys", data: initalKeyMapping }); + } else { + setKeys({ type: null }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [callback, keys]); + + useEffect(() => { + if (!listenersAdded) { + console.log("Added events for keyup and keydown"); + shortcutKeys.forEach((k) => { + window.addEventListener("keydown", keydownListener(k)); + window.addEventListener("keyup", keyupListener(k)); + }); + } + + setListenersAdded(true); + + return () => { + shortcutKeys.forEach((k) => { + window.removeEventListener("keydown", keydownListener(k)); + window.removeEventListener("keyup", keyupListener(k)); + }); + }; + }, [listenersAdded]); }; export default useKeyboardShortcut; function disabledEventPropagation(e) { - if (e) { - if (e.stopPropagation) { - e.stopPropagation(); - } else if (window.event) { - window.event.cancelBubble = true; - } + if (e) { + if (e.stopPropagation) { + e.stopPropagation(); + } else if (window.event) { + window.event.cancelBubble = true; } + } } diff --git a/client/src/utils/useLocalStorage.js b/client/src/utils/useLocalStorage.js index a746508e6..7c433a753 100644 --- a/client/src/utils/useLocalStorage.js +++ b/client/src/utils/useLocalStorage.js @@ -1,40 +1,39 @@ -import {useState} from "react"; +import { useState } from "react"; export default function useLocalStorage(key, initialValue) { - // State to store our value - // Pass initial state function to useState so logic is only executed once - const [storedValue, setStoredValue] = useState(() => { - if (typeof window === "undefined") { - return initialValue; - } - try { - // Get from local storage by key - const item = window.localStorage.getItem(key); - // Parse stored json or if none return initialValue - return item ? JSON.parse(item) : initialValue; - } catch (error) { - // If error also return initialValue - console.log(error); - return initialValue; - } - }); - // Return a wrapped version of useState's setter function that ... - // ... persists the new value to localStorage. - const setValue = (value) => { - try { - // Allow value to be a function so we have same API as useState - const valueToStore = - value instanceof Function ? value(storedValue) : value; - // Save state - setStoredValue(valueToStore); - // Save to local storage - if (typeof window !== "undefined") { - window.localStorage.setItem(key, JSON.stringify(valueToStore)); - } - } catch (error) { - // A more advanced implementation would handle the error case - console.log(error); - } - }; - return [storedValue, setValue]; + // State to store our value + // Pass initial state function to useState so logic is only executed once + const [storedValue, setStoredValue] = useState(() => { + if (typeof window === "undefined") { + return initialValue; + } + try { + // Get from local storage by key + const item = window.localStorage.getItem(key); + // Parse stored json or if none return initialValue + return item ? JSON.parse(item) : initialValue; + } catch (error) { + // If error also return initialValue + console.log(error); + return initialValue; + } + }); + // Return a wrapped version of useState's setter function that ... + // ... persists the new value to localStorage. + const setValue = (value) => { + try { + // Allow value to be a function so we have same API as useState + const valueToStore = value instanceof Function ? value(storedValue) : value; + // Save state + setStoredValue(valueToStore); + // Save to local storage + if (typeof window !== "undefined") { + window.localStorage.setItem(key, JSON.stringify(valueToStore)); + } + } catch (error) { + // A more advanced implementation would handle the error case + console.log(error); + } + }; + return [storedValue, setValue]; } diff --git a/client/src/utils/usetraceupdate.jsx b/client/src/utils/usetraceupdate.jsx index 577cc32b7..efe43577f 100644 --- a/client/src/utils/usetraceupdate.jsx +++ b/client/src/utils/usetraceupdate.jsx @@ -1,19 +1,19 @@ -import {useEffect, useRef} from "react"; +import { useEffect, useRef } from "react"; function useTraceUpdate(props) { - const prev = useRef(props); - useEffect(() => { - const changedProps = Object.entries(props).reduce((ps, [k, v]) => { - if (prev.current[k] !== v) { - ps[k] = [prev.current[k], v]; - } - return ps; - }, {}); - if (Object.keys(changedProps).length > 0) { - console.log("Changed props:", changedProps); - } - prev.current = props; - }); + const prev = useRef(props); + useEffect(() => { + const changedProps = Object.entries(props).reduce((ps, [k, v]) => { + if (prev.current[k] !== v) { + ps[k] = [prev.current[k], v]; + } + return ps; + }, {}); + if (Object.keys(changedProps).length > 0) { + console.log("Changed props:", changedProps); + } + prev.current = props; + }); } export default useTraceUpdate; diff --git a/client/vite.config.js b/client/vite.config.js index a5d9f6734..ab618e43b 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -1,135 +1,133 @@ -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 { defineConfig } from 'vite'; -import { ViteEjsPlugin } from 'vite-plugin-ejs'; +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 { defineConfig } from "vite"; +import { ViteEjsPlugin } from "vite-plugin-ejs"; //import CompressionPlugin from 'vite-plugin-compression'; -import { VitePWA } from 'vite-plugin-pwa'; -import InstanceRenderManager from './src/utils/instanceRenderMgr'; +import { VitePWA } from "vite-plugin-pwa"; +import InstanceRenderManager from "./src/utils/instanceRenderMgr"; -process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString('en-US', { - timeZone: 'America/Los_Angeles', +process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", { + timeZone: "America/Los_Angeles" }); const WRONG_CODE = `import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";`; function reactVirtualized() { return { - name: 'flat:react-virtualized', + 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 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') + 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, ''); + const code = await fsPromises.readFile(file, "utf-8"); + const modified = code.replace(WRONG_CODE, ""); await fsPromises.writeFile(file, modified); - }, + } }; } export default defineConfig({ - base: '/', + base: "/", plugins: [ ViteEjsPlugin((viteConfig) => { // viteConfig is the current Vite resolved config return { env: viteConfig.env }; }), VitePWA({ - injectRegister: 'auto', - registerType: 'prompt', + injectRegister: "auto", + registerType: "prompt", manifest: { short_name: InstanceRenderManager({ instance: process.env.VITE_APP_INSTANCE, - imex: 'ImEX Online', - rome: 'Rome Online', - promanager: 'ProManager', + imex: "ImEX Online", + rome: "Rome Online", + promanager: "ProManager" }), name: InstanceRenderManager({ instance: process.env.VITE_APP_INSTANCE, - imex: 'ImEX Online', - rome: 'Rome Online', - promanager: 'ProManager', + imex: "ImEX Online", + rome: "Rome Online", + promanager: "ProManager" }), - description: 'The ultimate bodyshop management system.', + description: "The ultimate bodyshop management system.", icons: [ - { //TODO:AIO Ensure that these are correct for Rome and IO. + { + //TODO:AIO Ensure that these are correct for Rome and IO. src: InstanceRenderManager({ instance: process.env.VITE_APP_INSTANCE, - imex: 'favicon.png', - rome: 'ro-favicon.png', - promanager: '/pm/pm-favicon.ico', + imex: "favicon.png", + rome: "ro-favicon.png", + promanager: "/pm/pm-favicon.ico" }), - sizes: '64x64 32x32 24x24 16x16', - type: 'image/x-icon', + sizes: "64x64 32x32 24x24 16x16", + type: "image/x-icon" }, { src: InstanceRenderManager({ instance: process.env.VITE_APP_INSTANCE, - imex: 'logo192.png', - rome: 'logo192.png', - promanager: '/pm/pm-icon-192.png', + imex: "logo192.png", + rome: "logo192.png", + promanager: "/pm/pm-icon-192.png" }), - type: 'image/png', - sizes: '192x192', + type: "image/png", + sizes: "192x192" }, { src: InstanceRenderManager({ instance: process.env.VITE_APP_INSTANCE, - imex: 'logo512.png', - rome: 'ro-favicon.png', - promanager: '/pm/pm-icon-512.png', + imex: "logo512.png", + rome: "ro-favicon.png", + promanager: "/pm/pm-icon-512.png" }), - type: 'image/png', - sizes: '512x512', - }, + type: "image/png", + sizes: "512x512" + } ], theme_color: InstanceRenderManager({ instance: process.env.VITE_APP_INSTANCE, - imex: '#1890ff', - rome: '#fff', - promanager: '#1d69a6', + imex: "#1890ff", + rome: "#fff", + promanager: "#1d69a6" }), - background_color: '#fff', - gcm_sender_id: '103953800507', - }, + background_color: "#fff", + gcm_sender_id: "103953800507" + } }), reactVirtualized(), - react(), - // CompressionPlugin(), //Cloudfront already compresses assets, so not needed. + react() + // CompressionPlugin(), //Cloudfront already compresses assets, so not needed. ], - define:{ - "APP_VERSION": JSON.stringify(process.env.npm_package_version) + define: { + APP_VERSION: JSON.stringify(process.env.npm_package_version) }, server: { host: true, port: 3000, - open: true, + open: true }, build: { rollupOptions: { output: { manualChunks: { - antd: ['antd'], - 'react-redux': ['react-redux'], - redux: ['redux'], - }, - }, - }, + antd: ["antd"], + "react-redux": ["react-redux"], + redux: ["redux"] + } + } + } }, optimizeDeps: { - include: ['react', 'react-dom', 'antd', '@apollo/client', '@reduxjs/toolkit', 'axios'], + include: ["react", "react-dom", "antd", "@apollo/client", "@reduxjs/toolkit", "axios"], esbuildOptions: { loader: { - '.js': 'jsx', - }, - }, - }, + ".js": "jsx" + } + } + } }); diff --git a/ecosystem.config.js b/ecosystem.config.js index f5903bb14..995d4c22c 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -1,19 +1,19 @@ module.exports = { - apps: [ - { - name: "ImEX Online API", - script: "./server.js", - watch: true, - watch_delay: 1000, - ignore_watch: ["node_modules", "client/img"], - watch_options: { - followSymlinks: false, - }, - env: { - NODE_ENV: "production", - }, - instances: "max", - exec_mode: "cluster", - }, - ], + apps: [ + { + name: "ImEX Online API", + script: "./server.js", + watch: true, + watch_delay: 1000, + ignore_watch: ["node_modules", "client/img"], + watch_options: { + followSymlinks: false + }, + env: { + NODE_ENV: "production" + }, + instances: "max", + exec_mode: "cluster" + } + ] }; diff --git a/firebase/firebase.json b/firebase/firebase.json index 11a47fc6a..aa2948750 100644 --- a/firebase/firebase.json +++ b/firebase/firebase.json @@ -1,7 +1,5 @@ { "functions": { - "predeploy": [ -"npm --prefix \"$RESOURCE_DIR\" run lint" - ] + "predeploy": ["npm --prefix \"$RESOURCE_DIR\" run lint"] } } diff --git a/firebase/functions/index.js b/firebase/functions/index.js index 68b23529f..e2050642f 100644 --- a/firebase/functions/index.js +++ b/firebase/functions/index.js @@ -4,28 +4,27 @@ admin.initializeApp(functions.config().firebase); // On sign up. exports.processSignUp = functions.auth.user().onCreate((user) => { - // Check if user meets role criteria: - // Your custom logic here: to decide what roles and other `x-hasura-*` should the user get - let customClaims; - if (user.email && user.email.indexOf("@admin.imex.online") !== -1) { - customClaims = { - "https://hasura.io/jwt/claims": { - "x-hasura-default-role": "admin", - "x-hasura-allowed-roles": ["user", "admin"], - "x-hasura-user-id": user.uid, - }, - }; - } else { - customClaims = { - "https://hasura.io/jwt/claims": { - "x-hasura-default-role": "user", - "x-hasura-allowed-roles": ["user"], - "x-hasura-user-id": user.uid, - }, - }; - } - - // Set custom user claims on this newly created user. - return admin.auth().setCustomUserClaims(user.uid, customClaims); + // Check if user meets role criteria: + // Your custom logic here: to decide what roles and other `x-hasura-*` should the user get + let customClaims; + if (user.email && user.email.indexOf("@admin.imex.online") !== -1) { + customClaims = { + "https://hasura.io/jwt/claims": { + "x-hasura-default-role": "admin", + "x-hasura-allowed-roles": ["user", "admin"], + "x-hasura-user-id": user.uid + } + }; + } else { + customClaims = { + "https://hasura.io/jwt/claims": { + "x-hasura-default-role": "user", + "x-hasura-allowed-roles": ["user"], + "x-hasura-user-id": user.uid + } + }; + } + // Set custom user claims on this newly created user. + return admin.auth().setCustomUserClaims(user.uid, customClaims); }); diff --git a/job-totals-testing-util.js b/job-totals-testing-util.js index 68220f54e..e2018661f 100644 --- a/job-totals-testing-util.js +++ b/job-totals-testing-util.js @@ -1,6 +1,6 @@ const path = require("path"); const Dinero = require("dinero.js"); -const {gql} = require("graphql-request"); +const { gql } = require("graphql-request"); const queries = require("./server/graphql-client/queries"); const GraphQLClient = require("graphql-request").GraphQLClient; const logger = require("./server/utils/logger"); @@ -12,115 +12,104 @@ const axios = AxiosLib.create(); Dinero.globalRoundingMode = "HALF_EVEN"; const client = require("./server/graphql-client/graphql-client").client; require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); 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 {jobs} = await client.request( + const bodyshopids = ["b501bb82-22b2-493a-8a0f-152938194869"]; + const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImJhNjI1OTZmNTJmNTJlZDQ0MDQ5Mzk2YmU3ZGYzNGQyYzY0ZjQ1M2UiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUm9tZSBEZXZlbG9wbWVudCIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciJdLCJ4LWhhc3VyYS11c2VyLWlkIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiJ9LCJpb2FkbWluIjp0cnVlLCJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vcm9tZS1wcm9kLTEiLCJhdWQiOiJyb21lLXByb2QtMSIsImF1dGhfdGltZSI6MTcxMDk1MTg1MCwidXNlcl9pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIiLCJzdWIiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwiaWF0IjoxNzExNTczODI1LCJleHAiOjE3MTE1Nzc0MjUsImVtYWlsIjoicGF0cmlja0Byb21lLmRldiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJwYXRyaWNrQHJvbWUuZGV2Il19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.0kBySA9tJznLYj8TtncHGVWJO0IcmLKP2G1UyyXwaj45kTa25bjT9RWjM-NslX_zjOvrvmQZzisFAb6M1Jf6geNjOMLIqb8bhihhzEZK4CcRfvjT6cpZxnOO2Dp_1Y5OePbvOBS_GlfdsovVWa84OLuhYC5G_3QwHT8_2Cttz4CbrC6M_vd7QsGODJYBbVKMhOdZhzpNq7AbOUh3749WRjLMMobpnZDrmQlsyg3PAqtX1FHO25WQS2rma9QahGDSY736JfbkuZJ2XbNn0axEGpK7RQLUcuRkFUlfKqYplNbR_e1Q3kEfRAZpxBPXZysrDcbDNhbkWCoTmJ3fle55OA`; + const { jobs } = await client.request( + gql` + query GET_JOBS($bodyshopids: [uuid!]!) { + jobs(where: { shopid: { _in: $bodyshopids } }, order_by: { created_at: desc }) { + id + ro_number + job_totals + cieca_ttl + } + } + `, + { + bodyshopids + } + ); + + const results = []; + + for (const [index, job] of jobs.entries()) { + process.stdout.cursorTo(0); + process.stdout.write( + `Processing job ${index + 1} of ${jobs.length}. Failed jobs: ${results.filter((r) => r.result !== "PASS").length}` + ); + + 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($bodyshopids: [uuid!]!) { - jobs( - where: { shopid: { _in: $bodyshopids } } - order_by: { created_at: desc } - ) { - id - ro_number - job_totals - cieca_ttl - } + 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 } + } `, { - bodyshopids, + id: job.id } - ); + ); - const results = []; + 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 + }; - for (const [index, job] of jobs.entries()) { - process.stdout.cursorTo(0); - process.stdout.write( - `Processing job ${index + 1} of ${jobs.length}. Failed jobs: ${ - results.filter((r) => r.result !== "PASS").length - }` - ); + const calcTotal = newjob.job_totals.totals.total_repairs.amount; + const ttlTotal = newjob.cieca_ttl.data.g_ttl_amt * 100; + result.difference = (calcTotal - ttlTotal) / 100; - 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, - } - ); + if (Math.abs(calcTotal - ttlTotal) > 3) { + //Diff is greater than 5 cents. Fail it. + result.result = "***FAIL***"; + } else { + result.result = "PASS"; + } + // console.log(`${result.result} => RO ${job.ro_number} - ${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) { - //Diff is greater than 5 cents. Fail it. - result.result = "***FAIL***"; - } else { - result.result = "PASS"; - } - // console.log(`${result.result} => RO ${job.ro_number} - ${job.id} `); - - results.push(result); - } catch (error) { - results.push({ - ro_number: job.ro_number, - id: job.id, - result: "**503 FAILURE**", - }); - } + 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")); - const summary = results.reduce( - (acc, val) => { - if (val.result === "PASS") { - return {...acc, pass: acc.pass + 1}; - } else { - return {...acc, fail: acc.fail + 1}; - } - }, - {pass: 0, fail: 0} - ); - console.log( - "Pass Rate: ", - ((summary.pass / (summary.fail + summary.pass)) * 100).toFixed(1) - ); + console.table(results.filter((r) => r.result !== "PASS")); + const summary = results.reduce( + (acc, val) => { + if (val.result === "PASS") { + return { ...acc, pass: acc.pass + 1 }; + } else { + return { ...acc, fail: acc.fail + 1 }; + } + }, + { pass: 0, fail: 0 } + ); + console.log("Pass Rate: ", ((summary.pass / (summary.fail + summary.pass)) * 100).toFixed(1)); } RunTheTest(); diff --git a/libs/awsUtils.js b/libs/awsUtils.js index 24e3ea353..0cb29337a 100644 --- a/libs/awsUtils.js +++ b/libs/awsUtils.js @@ -1,57 +1,53 @@ require("dotenv").config({ - path: require("path").resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: require("path").resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); -const {isNil} = require('lodash'); +const { isNil } = require("lodash"); const aws4 = require("aws4"); -const {Connection, Client} = require("@opensearch-project/opensearch"); -const {defaultProvider} = require("@aws-sdk/credential-provider-node"); +const { Connection, Client } = require("@opensearch-project/opensearch"); +const { defaultProvider } = require("@aws-sdk/credential-provider-node"); const createAwsConnector = (credentials, region) => { - class AmazonConnection extends Connection { - buildRequestObject(params) { - const request = super.buildRequestObject(params); - request.service = "es"; - request.region = region; - request.headers = request.headers || {}; - request.headers["host"] = request.hostname; + class AmazonConnection extends Connection { + buildRequestObject(params) { + const request = super.buildRequestObject(params); + request.service = "es"; + request.region = region; + request.headers = request.headers || {}; + request.headers["host"] = request.hostname; - return aws4.sign(request, credentials); - } + return aws4.sign(request, credentials); } + } - return { - Connection: AmazonConnection, - }; + return { + Connection: AmazonConnection + }; }; const getClient = async () => { + // We have manual configuration for OpenSearch, + // Return a client using these custom credentials + if ( + !isNil(process.env.OPEN_SEARCH_PASSWORD) && + !isNil(process.env.OPEN_SEARCH_USER) && + !isNil(process.env.OPEN_SEARCH_HOST) && + !isNil(process.env.OPEN_SEARCH_PROTOCOL) + ) { + // The URI is currently being stored in its entirety, so strip protocol prior to rebuilding it. + const hostUrl = process.env.OPEN_SEARCH_HOST.replace(/^https?:\/\//i, ""); + const node = `${process.env.OPEN_SEARCH_PROTOCOL}://${process.env.OPEN_SEARCH_USER}:${process.env.OPEN_SEARCH_PASSWORD}@${hostUrl}`; - // We have manual configuration for OpenSearch, - // Return a client using these custom credentials - if ( - !isNil(process.env.OPEN_SEARCH_PASSWORD) && - !isNil(process.env.OPEN_SEARCH_USER) && - !isNil(process.env.OPEN_SEARCH_HOST) && - !isNil(process.env.OPEN_SEARCH_PROTOCOL) - ) { - // The URI is currently being stored in its entirety, so strip protocol prior to rebuilding it. - const hostUrl = process.env.OPEN_SEARCH_HOST.replace(/^https?:\/\//i, ''); - const node = `${process.env.OPEN_SEARCH_PROTOCOL}://${process.env.OPEN_SEARCH_USER}:${process.env.OPEN_SEARCH_PASSWORD}@${hostUrl}`; - - return new Client({ - node, - }); - } - - // Default to the AWS Credentials Provider. - const credentials = await defaultProvider()(); return new Client({ - ...createAwsConnector(credentials, "ca-central-1"), - node: process.env.OPEN_SEARCH_HOST, + node }); + } + + // Default to the AWS Credentials Provider. + const credentials = await defaultProvider()(); + return new Client({ + ...createAwsConnector(credentials, "ca-central-1"), + node: process.env.OPEN_SEARCH_HOST + }); }; -module.exports = {getClient}; \ No newline at end of file +module.exports = { getClient }; diff --git a/os-loader.js b/os-loader.js index a967739fd..54a799ce2 100644 --- a/os-loader.js +++ b/os-loader.js @@ -1,35 +1,32 @@ require("dotenv").config({ - path: require("path").resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: require("path").resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); -const {omit} = require("lodash"); +const { omit } = require("lodash"); const gqlClient = require("./server/graphql-client/graphql-client").client; -const getClient = require('./libs/awsUtils'); +const getClient = require("./libs/awsUtils"); async function OpenSearchUpdateHandler(req, res) { - try { - const osClient = await getClient(); + try { + const osClient = await getClient(); - //Clear out all current documents - // const deleteResult = await osClient.deleteByQuery({ - // index: ["*"], // ["jobs", "payments", "bills", "vehicles", "owners"], - // body: { - // query: { - // match_all: {}, - // }, - // }, - // }); + //Clear out all current documents + // const deleteResult = await osClient.deleteByQuery({ + // index: ["*"], // ["jobs", "payments", "bills", "vehicles", "owners"], + // body: { + // query: { + // match_all: {}, + // }, + // }, + // }); - // return; + // return; - const batchSize = 1000; - const promiseQueue = []; + const batchSize = 1000; + const promiseQueue = []; - //Jobs Load. - const jobsData = await gqlClient.request(`query{jobs{ + //Jobs Load. + const jobsData = await gqlClient.request(`query{jobs{ id bodyshopid:shopid clm_no @@ -49,21 +46,18 @@ async function OpenSearchUpdateHandler(req, res) { v_make_desc v_model_desc }}`); - for (let i = 0; i <= jobsData.jobs.length / batchSize; i++) { - const slicedArray = jobsData.jobs.slice( - i * batchSize, - i * batchSize + batchSize - ); - const bulkOperation = []; - slicedArray.forEach((job) => { - bulkOperation.push({index: {_index: "jobs", _id: job.id}}); - bulkOperation.push(job); - }); - promiseQueue.push(bulkOperation); - } + for (let i = 0; i <= jobsData.jobs.length / batchSize; i++) { + const slicedArray = jobsData.jobs.slice(i * batchSize, i * batchSize + batchSize); + const bulkOperation = []; + slicedArray.forEach((job) => { + bulkOperation.push({ index: { _index: "jobs", _id: job.id } }); + bulkOperation.push(job); + }); + promiseQueue.push(bulkOperation); + } - //Owner Load - const ownersData = await gqlClient.request(`{ + //Owner Load + const ownersData = await gqlClient.request(`{ owners { id bodyshopid: shopid @@ -75,21 +69,18 @@ async function OpenSearchUpdateHandler(req, res) { } } `); - for (let i = 0; i <= ownersData.owners.length / batchSize; i++) { - const slicedArray = ownersData.owners.slice( - i * batchSize, - i * batchSize + batchSize - ); - const bulkOperation = []; - slicedArray.forEach((owner) => { - bulkOperation.push({index: {_index: "owners", _id: owner.id}}); - bulkOperation.push(owner); - }); - promiseQueue.push(bulkOperation); - } + for (let i = 0; i <= ownersData.owners.length / batchSize; i++) { + const slicedArray = ownersData.owners.slice(i * batchSize, i * batchSize + batchSize); + const bulkOperation = []; + slicedArray.forEach((owner) => { + bulkOperation.push({ index: { _index: "owners", _id: owner.id } }); + bulkOperation.push(owner); + }); + promiseQueue.push(bulkOperation); + } - //Vehicles - const vehiclesData = await gqlClient.request(`{ + //Vehicles + const vehiclesData = await gqlClient.request(`{ vehicles { id bodyshopid: shopid @@ -102,21 +93,18 @@ async function OpenSearchUpdateHandler(req, res) { } } `); - for (let i = 0; i <= vehiclesData.vehicles.length / batchSize; i++) { - const slicedArray = vehiclesData.vehicles.slice( - i * batchSize, - i * batchSize + batchSize - ); - const bulkOperation = []; - slicedArray.forEach((vehicle) => { - bulkOperation.push({index: {_index: "vehicles", _id: vehicle.id}}); - bulkOperation.push(vehicle); - }); - promiseQueue.push(bulkOperation); - } + for (let i = 0; i <= vehiclesData.vehicles.length / batchSize; i++) { + const slicedArray = vehiclesData.vehicles.slice(i * batchSize, i * batchSize + batchSize); + const bulkOperation = []; + slicedArray.forEach((vehicle) => { + bulkOperation.push({ index: { _index: "vehicles", _id: vehicle.id } }); + bulkOperation.push(vehicle); + }); + promiseQueue.push(bulkOperation); + } - //payments - const paymentsData = await gqlClient.request(`{ + //payments + const paymentsData = await gqlClient.request(`{ payments { id amount @@ -147,24 +135,21 @@ async function OpenSearchUpdateHandler(req, res) { } `); - for (let i = 0; i <= paymentsData.payments.length / batchSize; i++) { - const slicedArray = paymentsData.payments.slice( - i * batchSize, - i * batchSize + batchSize - ); - const bulkOperation = []; - slicedArray.forEach((payment) => { - bulkOperation.push({index: {_index: "payments", _id: payment.id}}); - bulkOperation.push({ - ...omit(payment, ["job"]), - bodyshopid: payment.job.bodyshopid, - }); - }); - promiseQueue.push(bulkOperation); - } + for (let i = 0; i <= paymentsData.payments.length / batchSize; i++) { + const slicedArray = paymentsData.payments.slice(i * batchSize, i * batchSize + batchSize); + const bulkOperation = []; + slicedArray.forEach((payment) => { + bulkOperation.push({ index: { _index: "payments", _id: payment.id } }); + bulkOperation.push({ + ...omit(payment, ["job"]), + bodyshopid: payment.job.bodyshopid + }); + }); + promiseQueue.push(bulkOperation); + } - //bills - const billsData = await gqlClient.request(`{ + //bills + const billsData = await gqlClient.request(`{ bills { id date @@ -184,35 +169,29 @@ async function OpenSearchUpdateHandler(req, res) { } } }`); - for (let i = 0; i <= billsData.bills.length / batchSize; i++) { - const slicedArray = billsData.bills.slice( - i * batchSize, - i * batchSize + batchSize - ); - const bulkOperation = []; - slicedArray.forEach((bill) => { - bulkOperation.push({index: {_index: "bills", _id: bill.id}}); - bulkOperation.push({ - ...omit(bill, ["job"]), - bodyshopid: bill.job.bodyshopid, - }); - }); - promiseQueue.push(bulkOperation); - } - - //Load the entire queue. - for (const queueItem of promiseQueue) { - const insertJobsBulk = await osClient.bulk({body: queueItem}); - - console.log( - ` ${insertJobsBulk.body.items.length} Records inserted in ${insertJobsBulk.body.took}.` - ); - if (insertJobsBulk.body.errors) - console.error("*** Error while inserting."); - } - } catch (error) { - console.log(error); + for (let i = 0; i <= billsData.bills.length / batchSize; i++) { + const slicedArray = billsData.bills.slice(i * batchSize, i * batchSize + batchSize); + const bulkOperation = []; + slicedArray.forEach((bill) => { + bulkOperation.push({ index: { _index: "bills", _id: bill.id } }); + bulkOperation.push({ + ...omit(bill, ["job"]), + bodyshopid: bill.job.bodyshopid + }); + }); + promiseQueue.push(bulkOperation); } + + //Load the entire queue. + for (const queueItem of promiseQueue) { + const insertJobsBulk = await osClient.bulk({ body: queueItem }); + + console.log(` ${insertJobsBulk.body.items.length} Records inserted in ${insertJobsBulk.body.took}.`); + if (insertJobsBulk.body.errors) console.error("*** Error while inserting."); + } + } catch (error) { + console.log(error); + } } OpenSearchUpdateHandler(); diff --git a/package.json b/package.json index 8a9cc8589..b99b6e4fe 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "build": "cd client && npm run build", "dev": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\"", "deva": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\" \"npm run admin\"", - "start": "node server.js" + "start": "node server.js", + "makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\"" }, "dependencies": { "@aws-sdk/client-secrets-manager": "^3.525.0", diff --git a/server.js b/server.js index 45b58f44c..8a7072146 100644 --- a/server.js +++ b/server.js @@ -6,11 +6,11 @@ const path = require("path"); const compression = require("compression"); const cookieParser = require("cookie-parser"); const http = require("http"); -const {Server} = require("socket.io"); +const { Server } = require("socket.io"); // Load environment variables require("dotenv").config({ - path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); // Import custom utilities and handlers @@ -21,86 +21,90 @@ const app = express(); const port = process.env.PORT || 5000; const server = http.createServer(app); const io = new Server(server, { - path: "/ws", - cors: { - origin: [ - "https://test.imex.online", - "https://www.test.imex.online", - "http://localhost:3000", - "https://imex.online", - "https://www.imex.online", - "https://romeonline.io", //Added in all RO and PM routes to simplyify setup. - "https://www.romeonline.io", - "https://beta.test.romeonline.io", - "https://www.beta.test.romeonline.io", - "https://beta.romeonline.io", - "https://www.beta.romeonline.io", - "https://beta.test.imex.online", - "https://www.beta.test.imex.online", - "https://beta.imex.online", - "https://www.beta.imex.online", - "https://www.test.promanager.web-est.com", - "https://test.promanager.web-est.com", - "https://www.promanager.web-est.com", - "https://www.promanager.web-est.com" - ], - methods: ["GET", "POST"], - credentials: true, - exposedHeaders: ["set-cookie"], - }, + path: "/ws", + cors: { + origin: [ + "https://test.imex.online", + "https://www.test.imex.online", + "http://localhost:3000", + "https://imex.online", + "https://www.imex.online", + "https://romeonline.io", //Added in all RO and PM routes to simplyify setup. + "https://www.romeonline.io", + "https://beta.test.romeonline.io", + "https://www.beta.test.romeonline.io", + "https://beta.romeonline.io", + "https://www.beta.romeonline.io", + "https://beta.test.imex.online", + "https://www.beta.test.imex.online", + "https://beta.imex.online", + "https://www.beta.imex.online", + "https://www.test.promanager.web-est.com", + "https://test.promanager.web-est.com", + "https://www.promanager.web-est.com", + "https://www.promanager.web-est.com" + ], + methods: ["GET", "POST"], + credentials: true, + exposedHeaders: ["set-cookie"] + } }); exports.io = io; require("./server/web-sockets/web-socket"); - // Middleware app.use(compression()); app.use(cookieParser()); -app.use(bodyParser.json({limit: "50mb"})); -app.use(bodyParser.urlencoded({limit: "50mb", extended: true})); -app.use(cors({credentials: true, exposedHeaders: ["set-cookie"]})); +app.use(bodyParser.json({ limit: "50mb" })); +app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); +app.use(cors({ credentials: true, exposedHeaders: ["set-cookie"] })); // Helper middleware app.use((req, res, next) => { - req.logger = logger; - next(); + req.logger = logger; + next(); }); // Route groupings -app.use('/', require("./server/routes/miscellaneousRoutes")); +app.use("/", require("./server/routes/miscellaneousRoutes")); app.use("/notifications", require("./server/routes/notificationsRoutes")); app.use("/render", require("./server/routes/renderRoutes")); -app.use('/mixdata', require("./server/routes/mixDataRoutes")); -app.use('/accounting', require("./server/routes/accountingRoutes")); -app.use('/qbo', require("./server/routes/qboRoutes")); -app.use('/media', require("./server/routes/mediaRoutes")); -app.use('/sms', require("./server/routes/smsRoutes")); -app.use('/job', require("./server/routes/jobRoutes")); -app.use('/scheduling', require("./server/routes/schedulingRoutes")); -app.use('/utils', require("./server/routes/utilRoutes")); -app.use('/data', require("./server/routes/dataRoutes")); -app.use('/adm', require("./server/routes/adminRoutes")); -app.use('/tech', require("./server/routes/techRoutes")); -app.use('/intellipay', require("./server/routes/intellipayRoutes")); -app.use('/cdk', require("./server/routes/cdkRoutes")); -app.use('/csi', require("./server/routes/csiRoutes")); -app.use('/payroll', require("./server/routes/payrollRoutes")); +app.use("/mixdata", require("./server/routes/mixDataRoutes")); +app.use("/accounting", require("./server/routes/accountingRoutes")); +app.use("/qbo", require("./server/routes/qboRoutes")); +app.use("/media", require("./server/routes/mediaRoutes")); +app.use("/sms", require("./server/routes/smsRoutes")); +app.use("/job", require("./server/routes/jobRoutes")); +app.use("/scheduling", require("./server/routes/schedulingRoutes")); +app.use("/utils", require("./server/routes/utilRoutes")); +app.use("/data", require("./server/routes/dataRoutes")); +app.use("/adm", require("./server/routes/adminRoutes")); +app.use("/tech", require("./server/routes/techRoutes")); +app.use("/intellipay", require("./server/routes/intellipayRoutes")); +app.use("/cdk", require("./server/routes/cdkRoutes")); +app.use("/csi", require("./server/routes/csiRoutes")); +app.use("/payroll", require("./server/routes/payrollRoutes")); // Default route for forbidden access app.get("/", (req, res) => { - res.status(200).send("Access Forbidden."); + res.status(200).send("Access Forbidden."); }); const main = async () => { - await server.listen(port); -} + await server.listen(port); +}; // Start server main() - .then(() => { - logger.log(`[${process.env.NODE_ENV || "DEVELOPMENT"}] Server started on port ${port}`, "INFO", "api"); - }) - .catch((error) => { - logger.log(`[${process.env.NODE_ENV || "DEVELOPMENT"}] Server failed to start on port ${port}`, "ERROR", "api", error); - }); + .then(() => { + logger.log(`[${process.env.NODE_ENV || "DEVELOPMENT"}] Server started on port ${port}`, "INFO", "api"); + }) + .catch((error) => { + logger.log( + `[${process.env.NODE_ENV || "DEVELOPMENT"}] Server failed to start on port ${port}`, + "ERROR", + "api", + error + ); + }); diff --git a/server/accounting/pbs/pbs-ap-allocations.js b/server/accounting/pbs/pbs-ap-allocations.js index 3d3402956..3b8a20996 100644 --- a/server/accounting/pbs/pbs-ap-allocations.js +++ b/server/accounting/pbs/pbs-ap-allocations.js @@ -1,9 +1,6 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const GraphQLClient = require("graphql-request").GraphQLClient; @@ -13,279 +10,240 @@ const moment = require("moment"); const Dinero = require("dinero.js"); const AxiosLib = require("axios").default; const axios = AxiosLib.create(); -const {PBS_ENDPOINTS, PBS_CREDENTIALS} = require("./pbs-constants"); -const {CheckForErrors} = require("./pbs-job-export"); +const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants"); +const { CheckForErrors } = require("./pbs-job-export"); const uuid = require("uuid").v4; axios.interceptors.request.use((x) => { - const socket = x.socket; + const socket = x.socket; - const headers = { - ...x.headers.common, - ...x.headers[x.method], - ...x.headers, - }; - const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${ - x.url - } | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`; - console.log(printable); + const headers = { + ...x.headers.common, + ...x.headers[x.method], + ...x.headers + }; + const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${ + x.url + } | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`; + console.log(printable); - CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data); + CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data); - return x; + return x; }); axios.interceptors.response.use((x) => { - const socket = x.config.socket; + const socket = x.config.socket; - const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify( - x.data - )}`; - console.log(printable); - CdkBase.createJsonEvent( - socket, - "TRACE", - `Raw Response: ${printable}`, - x.data - ); + const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`; + console.log(printable); + CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data); - return x; + return x; }); async function PbsCalculateAllocationsAp(socket, billids) { - try { - CdkBase.createLogEvent( - socket, - "DEBUG", - `Received request to calculate allocations for ${billids}` - ); - const {bills, bodyshops} = await QueryBillData(socket, billids); - const bodyshop = bodyshops[0]; - socket.bodyshop = bodyshop; - socket.bills = bills; + try { + CdkBase.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${billids}`); + const { bills, bodyshops } = await QueryBillData(socket, billids); + const bodyshop = bodyshops[0]; + socket.bodyshop = bodyshop; + socket.bills = bills; - //Each bill will enter it's own top level transaction. + //Each bill will enter it's own top level transaction. - const transactionlist = []; - if (bills.length === 0) { - CdkBase.createLogEvent( - socket, - "ERROR", - `No bills found for export. Ensure they have not already been exported and try again.` - ); - } - bills.forEach((bill) => { - //Keep the allocations at the bill level. - - const transactionObject = { - SerialNumber: socket.bodyshop.pbs_serialnumber, - billid: bill.id, - Posting: { - Reference: bill.invoice_number, - JournalCode: socket.txEnvelope ? socket.txEnvelope.journal : null, - TransactionDate: moment().tz(socket.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z", - //Description: "Bulk AP posting.", - //AdditionalInfo: "String", - Source: "ImEX Online", //TODO:AIO Resolve this for rome online. - Lines: [], //socket.apAllocations, - }, - }; - - const billHash = { - [bodyshop.md_responsibility_centers.taxes.federal_itc.name]: { - Account: - bodyshop.md_responsibility_centers.taxes.federal_itc.dms_acctnumber, - ControlNumber: bill.vendor.dmsid, - Amount: Dinero(), - // Comment: "String", - AdditionalInfo: bill.vendor.name, - InvoiceNumber: bill.invoice_number, - InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString(), - }, - [bodyshop.md_responsibility_centers.taxes.state.name]: { - Account: - bodyshop.md_responsibility_centers.taxes.state.dms_acctnumber, - ControlNumber: bill.vendor.dmsid, - Amount: Dinero(), - // Comment: "String", - AdditionalInfo: bill.vendor.name, - InvoiceNumber: bill.invoice_number, - InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString(), - }, - }; - - bill.billlines.forEach((bl) => { - let lineDinero = Dinero({ - amount: Math.round((bl.actual_cost || 0) * 100), - }) - .multiply(bl.quantity) - .multiply(bill.is_credit_memo ? -1 : 1); - const cc = getCostAccount(bl, bodyshop.md_responsibility_centers); - - if (!billHash[cc.name]) { - billHash[cc.name] = { - Account: - bodyshop.pbs_configuration.appostingaccount === "wip" - ? cc.dms_wip_acctnumber - : cc.dms_acctnumber, - ControlNumber: - bodyshop.pbs_configuration.apcontrol === "ro" - ? bill.job.ro_number - : bill.vendor.dmsid, - Amount: Dinero(), - // Comment: "String", - AdditionalInfo: bill.vendor.name, - InvoiceNumber: bill.invoice_number, - InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString(), - }; - } - - //Add the line amount. - billHash[cc.name] = { - ...billHash[cc.name], - Amount: billHash[cc.name].Amount.add(lineDinero), - }; - - //Does the line have taxes? - if (bl.applicable_taxes.federal) { - billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name] = - { - ...billHash[ - bodyshop.md_responsibility_centers.taxes.federal_itc.name - ], - Amount: billHash[ - bodyshop.md_responsibility_centers.taxes.federal_itc.name - ].Amount.add(lineDinero.percentage(bill.federal_tax_rate || 0)), - }; - } - if (bl.applicable_taxes.state) { - billHash[bodyshop.md_responsibility_centers.taxes.state.name] = { - ...billHash[bodyshop.md_responsibility_centers.taxes.state.name], - Amount: billHash[ - bodyshop.md_responsibility_centers.taxes.state.name - ].Amount.add(lineDinero.percentage(bill.state_tax_rate || 0)), - }; - } - //End tax check - }); - - let APAmount = Dinero(); - Object.keys(billHash).map((key) => { - if (billHash[key].Amount.getAmount() > 0 || billHash[key].Amount.getAmount() < 0) { - transactionObject.Posting.Lines.push({ - ...billHash[key], - Amount: billHash[key].Amount.toFormat("0.00"), - }); - APAmount = APAmount.add(billHash[key].Amount); //Calculate the total expense for the bill iteratively to create the corresponding credit to AP. - } - }); - - transactionObject.Posting.Lines.push({ - Account: bodyshop.md_responsibility_centers.ap.dms_acctnumber, - ControlNumber: bill.vendor.dmsid, - Amount: APAmount.multiply(-1).toFormat("0.00"), - // Comment: "String", - AdditionalInfo: bill.vendor.name, - InvoiceNumber: bill.invoice_number, - InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString(), - }); - - transactionlist.push(transactionObject); - }); - - return transactionlist; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error encountered in PbsCalculateAllocationsAp. ${error}` - ); + const transactionlist = []; + if (bills.length === 0) { + CdkBase.createLogEvent( + socket, + "ERROR", + `No bills found for export. Ensure they have not already been exported and try again.` + ); } + bills.forEach((bill) => { + //Keep the allocations at the bill level. + + const transactionObject = { + SerialNumber: socket.bodyshop.pbs_serialnumber, + billid: bill.id, + Posting: { + Reference: bill.invoice_number, + JournalCode: socket.txEnvelope ? socket.txEnvelope.journal : null, + TransactionDate: moment().tz(socket.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z", + //Description: "Bulk AP posting.", + //AdditionalInfo: "String", + Source: "ImEX Online", //TODO:AIO Resolve this for rome online. + Lines: [] //socket.apAllocations, + } + }; + + const billHash = { + [bodyshop.md_responsibility_centers.taxes.federal_itc.name]: { + Account: bodyshop.md_responsibility_centers.taxes.federal_itc.dms_acctnumber, + ControlNumber: bill.vendor.dmsid, + Amount: Dinero(), + // Comment: "String", + AdditionalInfo: bill.vendor.name, + InvoiceNumber: bill.invoice_number, + InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString() + }, + [bodyshop.md_responsibility_centers.taxes.state.name]: { + Account: bodyshop.md_responsibility_centers.taxes.state.dms_acctnumber, + ControlNumber: bill.vendor.dmsid, + Amount: Dinero(), + // Comment: "String", + AdditionalInfo: bill.vendor.name, + InvoiceNumber: bill.invoice_number, + InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString() + } + }; + + bill.billlines.forEach((bl) => { + let lineDinero = Dinero({ + amount: Math.round((bl.actual_cost || 0) * 100) + }) + .multiply(bl.quantity) + .multiply(bill.is_credit_memo ? -1 : 1); + const cc = getCostAccount(bl, bodyshop.md_responsibility_centers); + + if (!billHash[cc.name]) { + billHash[cc.name] = { + Account: bodyshop.pbs_configuration.appostingaccount === "wip" ? cc.dms_wip_acctnumber : cc.dms_acctnumber, + ControlNumber: bodyshop.pbs_configuration.apcontrol === "ro" ? bill.job.ro_number : bill.vendor.dmsid, + Amount: Dinero(), + // Comment: "String", + AdditionalInfo: bill.vendor.name, + InvoiceNumber: bill.invoice_number, + InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString() + }; + } + + //Add the line amount. + billHash[cc.name] = { + ...billHash[cc.name], + Amount: billHash[cc.name].Amount.add(lineDinero) + }; + + //Does the line have taxes? + if (bl.applicable_taxes.federal) { + billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name] = { + ...billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name], + Amount: billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name].Amount.add( + lineDinero.percentage(bill.federal_tax_rate || 0) + ) + }; + } + if (bl.applicable_taxes.state) { + billHash[bodyshop.md_responsibility_centers.taxes.state.name] = { + ...billHash[bodyshop.md_responsibility_centers.taxes.state.name], + Amount: billHash[bodyshop.md_responsibility_centers.taxes.state.name].Amount.add( + lineDinero.percentage(bill.state_tax_rate || 0) + ) + }; + } + //End tax check + }); + + let APAmount = Dinero(); + Object.keys(billHash).map((key) => { + if (billHash[key].Amount.getAmount() > 0 || billHash[key].Amount.getAmount() < 0) { + transactionObject.Posting.Lines.push({ + ...billHash[key], + Amount: billHash[key].Amount.toFormat("0.00") + }); + APAmount = APAmount.add(billHash[key].Amount); //Calculate the total expense for the bill iteratively to create the corresponding credit to AP. + } + }); + + transactionObject.Posting.Lines.push({ + Account: bodyshop.md_responsibility_centers.ap.dms_acctnumber, + ControlNumber: bill.vendor.dmsid, + Amount: APAmount.multiply(-1).toFormat("0.00"), + // Comment: "String", + AdditionalInfo: bill.vendor.name, + InvoiceNumber: bill.invoice_number, + InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString() + }); + + transactionlist.push(transactionObject); + }); + + return transactionlist; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`); + } } exports.PbsCalculateAllocationsAp = PbsCalculateAllocationsAp; async function QueryBillData(socket, billids) { - CdkBase.createLogEvent( - socket, - "DEBUG", - `Querying bill data for id(s) ${billids}` - ); - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); - const result = await client - .setHeaders({Authorization: `Bearer ${socket.handshake.auth.token}`}) - .request(queries.GET_PBS_AP_ALLOCATIONS, {billids: billids}); - CdkBase.createLogEvent( - socket, - "TRACE", - `Bill data query result ${JSON.stringify(result, null, 2)}` - ); + CdkBase.createLogEvent(socket, "DEBUG", `Querying bill data for id(s) ${billids}`); + const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); + const result = await client + .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) + .request(queries.GET_PBS_AP_ALLOCATIONS, { billids: billids }); + CdkBase.createLogEvent(socket, "TRACE", `Bill data query result ${JSON.stringify(result, null, 2)}`); - return result; + return result; } //@returns the account object. function getCostAccount(billline, respcenters) { - if (!billline.cost_center) return null; + if (!billline.cost_center) return null; - const acctName = respcenters.defaults.costs[billline.cost_center]; + const acctName = respcenters.defaults.costs[billline.cost_center]; - return respcenters.costs.find((c) => c.name === acctName); + return respcenters.costs.find((c) => c.name === acctName); } -exports.PbsExportAp = async function (socket, {billids, txEnvelope}) { - CdkBase.createLogEvent(socket, "DEBUG", `Exporting selected AP.`); +exports.PbsExportAp = async function (socket, { billids, txEnvelope }) { + CdkBase.createLogEvent(socket, "DEBUG", `Exporting selected AP.`); - //apAllocations has the same shap as the lines key for the accounting posting to PBS. - socket.apAllocations = await PbsCalculateAllocationsAp(socket, billids); - socket.txEnvelope = txEnvelope; - for (const allocation of socket.apAllocations) { - const {billid, ...restAllocation} = allocation; - const {data: AccountPostingChange} = await axios.post( - PBS_ENDPOINTS.AccountingPostingChange, - restAllocation, - {auth: PBS_CREDENTIALS, socket} - ); + //apAllocations has the same shap as the lines key for the accounting posting to PBS. + socket.apAllocations = await PbsCalculateAllocationsAp(socket, billids); + socket.txEnvelope = txEnvelope; + for (const allocation of socket.apAllocations) { + const { billid, ...restAllocation } = allocation; + const { data: AccountPostingChange } = await axios.post(PBS_ENDPOINTS.AccountingPostingChange, restAllocation, { + auth: PBS_CREDENTIALS, + socket + }); - CheckForErrors(socket, AccountPostingChange); + CheckForErrors(socket, AccountPostingChange); - if (AccountPostingChange.WasSuccessful) { - CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`); - await MarkApExported(socket, [billid]); + if (AccountPostingChange.WasSuccessful) { + CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`); + await MarkApExported(socket, [billid]); - socket.emit("ap-export-success", billid); - } else { - CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`); - socket.emit("ap-export-failure", { - billid, - error: AccountPostingChange.Message, - }); - } + socket.emit("ap-export-success", billid); + } else { + CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`); + socket.emit("ap-export-failure", { + billid, + error: AccountPostingChange.Message + }); } - socket.emit("ap-export-complete"); + } + socket.emit("ap-export-complete"); }; async function MarkApExported(socket, billids) { - CdkBase.createLogEvent( - socket, - "DEBUG", - `Marking bills as exported for id ${billids}` - ); - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); - const result = await client - .setHeaders({Authorization: `Bearer ${socket.handshake.auth.token}`}) - .request(queries.MARK_BILLS_EXPORTED, { - billids, - bill: { - exported: true, - exported_at: new Date(), - }, - logs: socket.bills.map((bill) => ({ - bodyshopid: socket.bodyshop.id, - billid: bill.id, - successful: true, - useremail: socket.user.email, - })), - }); + CdkBase.createLogEvent(socket, "DEBUG", `Marking bills as exported for id ${billids}`); + const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); + const result = await client + .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) + .request(queries.MARK_BILLS_EXPORTED, { + billids, + bill: { + exported: true, + exported_at: new Date() + }, + logs: socket.bills.map((bill) => ({ + bodyshopid: socket.bodyshop.id, + billid: bill.id, + successful: true, + useremail: socket.user.email + })) + }); - return result; + return result; } diff --git a/server/accounting/pbs/pbs-constants.js b/server/accounting/pbs/pbs-constants.js index fe33e3b6c..4c562c10d 100644 --- a/server/accounting/pbs/pbs-constants.js +++ b/server/accounting/pbs/pbs-constants.js @@ -1,16 +1,13 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const IMEX_PBS_USER = process.env.IMEX_PBS_USER, - IMEX_PBS_PASSWORD = process.env.IMEX_PBS_PASSWORD; + IMEX_PBS_PASSWORD = process.env.IMEX_PBS_PASSWORD; const PBS_CREDENTIALS = { - password: IMEX_PBS_PASSWORD, - username: IMEX_PBS_USER, + password: IMEX_PBS_PASSWORD, + username: IMEX_PBS_USER }; exports.PBS_CREDENTIALS = PBS_CREDENTIALS; @@ -21,10 +18,10 @@ exports.PBS_CREDENTIALS = PBS_CREDENTIALS; const pbsDomain = `https://partnerhub.pbsdealers.com/json/reply`; exports.PBS_ENDPOINTS = { - AccountGet: `${pbsDomain}/AccountGet`, - ContactGet: `${pbsDomain}/ContactGet`, - VehicleGet: `${pbsDomain}/VehicleGet`, - AccountingPostingChange: `${pbsDomain}/AccountingPostingChange`, - ContactChange: `${pbsDomain}/ContactChange`, - VehicleChange: `${pbsDomain}/VehicleChange`, + AccountGet: `${pbsDomain}/AccountGet`, + ContactGet: `${pbsDomain}/ContactGet`, + VehicleGet: `${pbsDomain}/VehicleGet`, + AccountingPostingChange: `${pbsDomain}/AccountingPostingChange`, + ContactChange: `${pbsDomain}/ContactChange`, + VehicleChange: `${pbsDomain}/VehicleChange` }; diff --git a/server/accounting/pbs/pbs-job-export.js b/server/accounting/pbs/pbs-job-export.js index 013d49c1d..7101f2ad4 100644 --- a/server/accounting/pbs/pbs-job-export.js +++ b/server/accounting/pbs/pbs-job-export.js @@ -1,731 +1,619 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const GraphQLClient = require("graphql-request").GraphQLClient; const AxiosLib = require("axios").default; const queries = require("../../graphql-client/queries"); -const {PBS_ENDPOINTS, PBS_CREDENTIALS} = require("./pbs-constants"); +const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants"); //const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl"); -const CalculateAllocations = - require("../../cdk/cdk-calculate-allocations").default; +const CalculateAllocations = require("../../cdk/cdk-calculate-allocations").default; const CdkBase = require("../../web-sockets/web-socket"); const moment = require("moment-timezone"); const Dinero = require("dinero.js"); -const InstanceManager = require("../../utils/instanceMgr").default; +const InstanceManager = require("../../utils/instanceMgr").default; const axios = AxiosLib.create(); axios.interceptors.request.use((x) => { - const socket = x.socket; + const socket = x.socket; - const headers = { - ...x.headers.common, - ...x.headers[x.method], - ...x.headers, - }; - const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${ - x.url - } | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`; - console.log(printable); + const headers = { + ...x.headers.common, + ...x.headers[x.method], + ...x.headers + }; + const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${ + x.url + } | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`; + console.log(printable); - CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data); + CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data); - return x; + return x; }); axios.interceptors.response.use((x) => { - const socket = x.config.socket; + const socket = x.config.socket; - const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify( - x.data - )}`; - console.log(printable); - CdkBase.createJsonEvent( - socket, - "TRACE", - `Raw Response: ${printable}`, - x.data - ); + const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`; + console.log(printable); + CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data); - return x; + return x; }); -exports.default = async function (socket, {txEnvelope, jobid}) { - socket.logEvents = []; - socket.recordid = jobid; - socket.txEnvelope = txEnvelope; - try { - CdkBase.createLogEvent( - socket, - "DEBUG", - `Received Job export request for id ${jobid}` - ); +exports.default = async function (socket, { txEnvelope, jobid }) { + socket.logEvents = []; + socket.recordid = jobid; + socket.txEnvelope = txEnvelope; + try { + CdkBase.createLogEvent(socket, "DEBUG", `Received Job export request for id ${jobid}`); - const JobData = await QueryJobData(socket, jobid); - socket.JobData = JobData; - CdkBase.createLogEvent( - socket, - "DEBUG", - `Querying the DMS for the Vehicle Record.` - ); - //Query for the Vehicle record to get the associated customer. - socket.DmsVeh = await QueryVehicleFromDms(socket); - //Todo: Need to validate the lines and methods below. - if (socket.DmsVeh && socket.DmsVeh.CustomerRef) { - //Get the associated customer from the Vehicle Record. - socket.DMSVehCustomer = await QueryCustomerBycodeFromDms( - socket, - socket.DmsVeh.CustomerRef - ); - } - socket.DMSCustList = await QueryCustomersFromDms(socket); - - socket.emit("pbs-select-customer", [ - ...(socket.DMSVehCustomer - ? [{...socket.DMSVehCustomer, vinOwner: true}] - : []), - ...socket.DMSCustList, - ]); - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error encountered in PbsJobExport. ${error}` - ); + const JobData = await QueryJobData(socket, jobid); + socket.JobData = JobData; + CdkBase.createLogEvent(socket, "DEBUG", `Querying the DMS for the Vehicle Record.`); + //Query for the Vehicle record to get the associated customer. + socket.DmsVeh = await QueryVehicleFromDms(socket); + //Todo: Need to validate the lines and methods below. + if (socket.DmsVeh && socket.DmsVeh.CustomerRef) { + //Get the associated customer from the Vehicle Record. + socket.DMSVehCustomer = await QueryCustomerBycodeFromDms(socket, socket.DmsVeh.CustomerRef); } + socket.DMSCustList = await QueryCustomersFromDms(socket); + + socket.emit("pbs-select-customer", [ + ...(socket.DMSVehCustomer ? [{ ...socket.DMSVehCustomer, vinOwner: true }] : []), + ...socket.DMSCustList + ]); + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsJobExport. ${error}`); + } }; -exports.PbsSelectedCustomer = async function PbsSelectedCustomer( - socket, - selectedCustomerId -) { - try { - if ( - socket.JobData.bodyshop.pbs_configuration.disablecontactvehicle === false - ) { - CdkBase.createLogEvent( - socket, - "DEBUG", - `User selected customer ${selectedCustomerId || "NEW"}` - ); +exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selectedCustomerId) { + try { + if (socket.JobData.bodyshop.pbs_configuration.disablecontactvehicle === false) { + CdkBase.createLogEvent(socket, "DEBUG", `User selected customer ${selectedCustomerId || "NEW"}`); - //Upsert the contact information as per Wafaa's Email. - CdkBase.createLogEvent( - socket, - "DEBUG", - `Upserting contact information to DMS for ${ - socket.JobData.ownr_fn || "" - } ${socket.JobData.ownr_ln || ""} ${socket.JobData.ownr_co_nm || ""}` - ); - const ownerRef = await UpsertContactData(socket, selectedCustomerId); + //Upsert the contact information as per Wafaa's Email. + CdkBase.createLogEvent( + socket, + "DEBUG", + `Upserting contact information to DMS for ${ + socket.JobData.ownr_fn || "" + } ${socket.JobData.ownr_ln || ""} ${socket.JobData.ownr_co_nm || ""}` + ); + const ownerRef = await UpsertContactData(socket, selectedCustomerId); - CdkBase.createLogEvent( - socket, - "DEBUG", - `Upserting vehicle information to DMS for ${socket.JobData.v_vin}` - ); - await UpsertVehicleData(socket, ownerRef.ReferenceId); - } else { - CdkBase.createLogEvent( - socket, - "DEBUG", - `Contact and Vehicle updates disabled. Skipping to accounting data insert.` - ); - } - CdkBase.createLogEvent(socket, "DEBUG", `Inserting account data.`); - CdkBase.createLogEvent( - socket, - "DEBUG", - `Inserting accounting posting data..` - ); - const insertResponse = await InsertAccountPostingData(socket); - - if (insertResponse.WasSuccessful) { - CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported.`); - await MarkJobExported(socket, socket.JobData.id); - - socket.emit("export-success", socket.JobData.id); - } else { - CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`); - } - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error encountered in CdkSelectedCustomer. ${error}` - ); - await InsertFailedExportLog(socket, error); + CdkBase.createLogEvent(socket, "DEBUG", `Upserting vehicle information to DMS for ${socket.JobData.v_vin}`); + await UpsertVehicleData(socket, ownerRef.ReferenceId); + } else { + CdkBase.createLogEvent( + socket, + "DEBUG", + `Contact and Vehicle updates disabled. Skipping to accounting data insert.` + ); } + CdkBase.createLogEvent(socket, "DEBUG", `Inserting account data.`); + CdkBase.createLogEvent(socket, "DEBUG", `Inserting accounting posting data..`); + const insertResponse = await InsertAccountPostingData(socket); + + if (insertResponse.WasSuccessful) { + CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported.`); + await MarkJobExported(socket, socket.JobData.id); + + socket.emit("export-success", socket.JobData.id); + } else { + CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`); + } + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkSelectedCustomer. ${error}`); + await InsertFailedExportLog(socket, error); + } }; async function CheckForErrors(socket, response) { - if (response.WasSuccessful === undefined || response.WasSuccessful === true) { - CdkBase.createLogEvent( - socket, - "DEBUG", - `Successful response from DMS. ${response.Message || ""}` - ); - } else { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error received from DMS: ${response.Message}` - ); - CdkBase.createLogEvent( - socket, - "TRACE", - `Error received from DMS: ${JSON.stringify(response)}` - ); - } + if (response.WasSuccessful === undefined || response.WasSuccessful === true) { + CdkBase.createLogEvent(socket, "DEBUG", `Successful response from DMS. ${response.Message || ""}`); + } else { + CdkBase.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`); + CdkBase.createLogEvent(socket, "TRACE", `Error received from DMS: ${JSON.stringify(response)}`); + } } exports.CheckForErrors = CheckForErrors; async function QueryJobData(socket, jobid) { - CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`); - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); - const result = await client - .setHeaders({Authorization: `Bearer ${socket.handshake.auth.token}`}) - .request(queries.QUERY_JOBS_FOR_PBS_EXPORT, {id: jobid}); - CdkBase.createLogEvent( - socket, - "TRACE", - `Job data query result ${JSON.stringify(result, null, 2)}` - ); - return result.jobs_by_pk; + CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`); + const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); + const result = await client + .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) + .request(queries.QUERY_JOBS_FOR_PBS_EXPORT, { id: jobid }); + CdkBase.createLogEvent(socket, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`); + return result.jobs_by_pk; } async function QueryVehicleFromDms(socket) { - try { - if (!socket.JobData.v_vin) return null; + try { + if (!socket.JobData.v_vin) return null; - const {data: VehicleGetResponse, request} = await axios.post( - PBS_ENDPOINTS.VehicleGet, - { - SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, - // VehicleId: "00000000000000000000000000000000", - // Year: "String", - // Make: "String", - // Model: "String", - // Trim: "String", - // ModelNumber: "String", - // StockNumber: "String", - VIN: socket.JobData.v_vin, - // LicenseNumber: "String", - // Lot: "String", - // Status: "String", - // StatusList: ["String"], - // OwnerRef: "00000000000000000000000000000000", - // ModifiedSince: "0001-01-01T00:00:00.0000000Z", - // ModifiedUntil: "0001-01-01T00:00:00.0000000Z", - // LastSaleSince: "0001-01-01T00:00:00.0000000Z", - // VehicleIDList: ["00000000000000000000000000000000"], - // IncludeInactive: false, - // IncludeBuildVehicles: false, - // ShortVIN: "String", - }, - {auth: PBS_CREDENTIALS, socket} - ); + const { data: VehicleGetResponse, request } = await axios.post( + PBS_ENDPOINTS.VehicleGet, + { + SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, + // VehicleId: "00000000000000000000000000000000", + // Year: "String", + // Make: "String", + // Model: "String", + // Trim: "String", + // ModelNumber: "String", + // StockNumber: "String", + VIN: socket.JobData.v_vin + // LicenseNumber: "String", + // Lot: "String", + // Status: "String", + // StatusList: ["String"], + // OwnerRef: "00000000000000000000000000000000", + // ModifiedSince: "0001-01-01T00:00:00.0000000Z", + // ModifiedUntil: "0001-01-01T00:00:00.0000000Z", + // LastSaleSince: "0001-01-01T00:00:00.0000000Z", + // VehicleIDList: ["00000000000000000000000000000000"], + // IncludeInactive: false, + // IncludeBuildVehicles: false, + // ShortVIN: "String", + }, + { auth: PBS_CREDENTIALS, socket } + ); - CheckForErrors(socket, VehicleGetResponse); - return VehicleGetResponse; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in QueryVehicleFromDms - ${error}` - ); - throw new Error(error); - } + CheckForErrors(socket, VehicleGetResponse); + return VehicleGetResponse; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in QueryVehicleFromDms - ${error}`); + throw new Error(error); + } } async function QueryCustomersFromDms(socket) { - try { - const {data: CustomerGetResponse} = await axios.post( - PBS_ENDPOINTS.ContactGet, - { - SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, - //ContactId: "00000000000000000000000000000000", - // ContactCode: socket.JobData.owner.accountingid, - FirstName: socket.JobData.ownr_fn, - LastName: socket.JobData.ownr_co_nm - ? socket.JobData.ownr_co_nm - : socket.JobData.ownr_ln, - PhoneNumber: socket.JobData.ownr_ph1, - EmailAddress: socket.JobData.ownr_ea, - // ModifiedSince: "0001-01-01T00:00:00.0000000Z", - // ModifiedUntil: "0001-01-01T00:00:00.0000000Z", - // ContactIdList: ["00000000000000000000000000000000"], - // IncludeInactive: false, - // PayableAccount: "String", - // ReceivableAccount: "String", - // DriverLicense: "String", - // ZipCode: "String", - }, - {auth: PBS_CREDENTIALS, socket} - ); - CheckForErrors(socket, CustomerGetResponse); - return CustomerGetResponse && CustomerGetResponse.Contacts; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in QueryCustomersFromDms - ${error}` - ); - throw new Error(error); - } + try { + const { data: CustomerGetResponse } = await axios.post( + PBS_ENDPOINTS.ContactGet, + { + SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, + //ContactId: "00000000000000000000000000000000", + // ContactCode: socket.JobData.owner.accountingid, + FirstName: socket.JobData.ownr_fn, + LastName: socket.JobData.ownr_co_nm ? socket.JobData.ownr_co_nm : socket.JobData.ownr_ln, + PhoneNumber: socket.JobData.ownr_ph1, + EmailAddress: socket.JobData.ownr_ea + // ModifiedSince: "0001-01-01T00:00:00.0000000Z", + // ModifiedUntil: "0001-01-01T00:00:00.0000000Z", + // ContactIdList: ["00000000000000000000000000000000"], + // IncludeInactive: false, + // PayableAccount: "String", + // ReceivableAccount: "String", + // DriverLicense: "String", + // ZipCode: "String", + }, + { auth: PBS_CREDENTIALS, socket } + ); + CheckForErrors(socket, CustomerGetResponse); + return CustomerGetResponse && CustomerGetResponse.Contacts; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`); + throw new Error(error); + } } async function QueryCustomerBycodeFromDms(socket, CustomerRef) { - try { - const {data: CustomerGetResponse} = await axios.post( - PBS_ENDPOINTS.ContactGet, - { - SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, - ContactId: CustomerRef, - //ContactCode: socket.JobData.owner.accountingid, - //FirstName: socket.JobData.ownr_co_nm - // ? socket.JobData.ownr_co_nm - // : socket.JobData.ownr_fn, - //LastName: socket.JobData.ownr_ln, - //PhoneNumber: socket.JobData.ownr_ph1, - // EmailAddress: "String", - // ModifiedSince: "0001-01-01T00:00:00.0000000Z", - // ModifiedUntil: "0001-01-01T00:00:00.0000000Z", - // ContactIdList: ["00000000000000000000000000000000"], - // IncludeInactive: false, - // PayableAccount: "String", - // ReceivableAccount: "String", - // DriverLicense: "String", - // ZipCode: "String", - }, - {auth: PBS_CREDENTIALS, socket} - ); - CheckForErrors(socket, CustomerGetResponse); - return CustomerGetResponse && CustomerGetResponse.Contacts; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in QueryCustomersFromDms - ${error}` - ); - throw new Error(error); - } + try { + const { data: CustomerGetResponse } = await axios.post( + PBS_ENDPOINTS.ContactGet, + { + SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, + ContactId: CustomerRef + //ContactCode: socket.JobData.owner.accountingid, + //FirstName: socket.JobData.ownr_co_nm + // ? socket.JobData.ownr_co_nm + // : socket.JobData.ownr_fn, + //LastName: socket.JobData.ownr_ln, + //PhoneNumber: socket.JobData.ownr_ph1, + // EmailAddress: "String", + // ModifiedSince: "0001-01-01T00:00:00.0000000Z", + // ModifiedUntil: "0001-01-01T00:00:00.0000000Z", + // ContactIdList: ["00000000000000000000000000000000"], + // IncludeInactive: false, + // PayableAccount: "String", + // ReceivableAccount: "String", + // DriverLicense: "String", + // ZipCode: "String", + }, + { auth: PBS_CREDENTIALS, socket } + ); + CheckForErrors(socket, CustomerGetResponse); + return CustomerGetResponse && CustomerGetResponse.Contacts; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`); + throw new Error(error); + } } async function UpsertContactData(socket, selectedCustomerId) { - try { - const {data: ContactChangeResponse} = await axios.post( - PBS_ENDPOINTS.ContactChange, - { - ContactInfo: { - // Id: socket.JobData.owner.id, - ...(selectedCustomerId ? {ContactId: selectedCustomerId} : {}), - SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, - Code: socket.JobData.owner.accountingid, - ...(socket.JobData.ownr_co_nm - ? { - //LastName: socket.JobData.ownr_ln, - FirstName: socket.JobData.ownr_co_nm, - IsBusiness: true, - } - : { - LastName: socket.JobData.ownr_ln, - FirstName: socket.JobData.ownr_fn, - IsBusiness: false, - }), + try { + const { data: ContactChangeResponse } = await axios.post( + PBS_ENDPOINTS.ContactChange, + { + ContactInfo: { + // Id: socket.JobData.owner.id, + ...(selectedCustomerId ? { ContactId: selectedCustomerId } : {}), + SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, + Code: socket.JobData.owner.accountingid, + ...(socket.JobData.ownr_co_nm + ? { + //LastName: socket.JobData.ownr_ln, + FirstName: socket.JobData.ownr_co_nm, + IsBusiness: true + } + : { + LastName: socket.JobData.ownr_ln, + FirstName: socket.JobData.ownr_fn, + IsBusiness: false + }), - //Salutation: "String", - //MiddleName: "String", - //ContactName: "String", - IsInactive: false, + //Salutation: "String", + //MiddleName: "String", + //ContactName: "String", + IsInactive: false, - //ApartmentNumber: "String", - Address: socket.JobData.ownr_addr1, - City: socket.JobData.ownr_city, - //County: socket.JobData.ownr_addr1, - State: socket.JobData.ownr_st, - ZipCode: socket.JobData.ownr_zip, - //BusinessPhone: "String", - //BusinessPhoneExt: "String", - HomePhone: socket.JobData.ownr_ph2, - CellPhone: socket.JobData.ownr_ph1, - //BusinessPhoneRawReverse: "String", - //HomePhoneRawReverse: "String", - //CellPhoneRawReverse: "String", - //FaxNumber: "String", - EmailAddress: socket.JobData.ownr_ea, - //Notes: "String", - //CriticalMemo: "String", - //BirthDate: "0001-01-01T00:00:00.0000000Z", - // Gender: "String", - // DriverLicense: "String", - //PreferredContactMethods: ["String"], - // LastUpdate: "0001-01-01T00:00:00.0000000Z", - // CustomFields: [{ Key: "String", Value: "String", Type: "String" }], - // FleetType: "String", - // CommunicationPreferences: { - // Email: "String", - // Phone: "String", - // TextMessage: "String", - // Letter: "String", - // Preferred: "String", - // }, - // SalesRepRef: "00000000000000000000000000000000", - // Language: "String", - // PayableAccount: "String", - // ReceivableAccount: "String", - // IsStatic: false, - // PrimaryImageRef: "00000000000000000000000000000000", - // PayableAccounts: [{ SerialNumber: "String", Account: "String" }], - // ReceivableAccounts: [{ SerialNumber: "String", Account: "String" }], - // ManufacturerLoyaltyNumber: "String", - }, - IsAsynchronous: false, - // UserRequest: "String", - // UserRef: "00000000000000000000000000000000", - }, - {auth: PBS_CREDENTIALS, socket} - ); - CheckForErrors(socket, ContactChangeResponse); - return ContactChangeResponse; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in UpsertContactData - ${error}` - ); - throw new Error(error); - } + //ApartmentNumber: "String", + Address: socket.JobData.ownr_addr1, + City: socket.JobData.ownr_city, + //County: socket.JobData.ownr_addr1, + State: socket.JobData.ownr_st, + ZipCode: socket.JobData.ownr_zip, + //BusinessPhone: "String", + //BusinessPhoneExt: "String", + HomePhone: socket.JobData.ownr_ph2, + CellPhone: socket.JobData.ownr_ph1, + //BusinessPhoneRawReverse: "String", + //HomePhoneRawReverse: "String", + //CellPhoneRawReverse: "String", + //FaxNumber: "String", + EmailAddress: socket.JobData.ownr_ea + //Notes: "String", + //CriticalMemo: "String", + //BirthDate: "0001-01-01T00:00:00.0000000Z", + // Gender: "String", + // DriverLicense: "String", + //PreferredContactMethods: ["String"], + // LastUpdate: "0001-01-01T00:00:00.0000000Z", + // CustomFields: [{ Key: "String", Value: "String", Type: "String" }], + // FleetType: "String", + // CommunicationPreferences: { + // Email: "String", + // Phone: "String", + // TextMessage: "String", + // Letter: "String", + // Preferred: "String", + // }, + // SalesRepRef: "00000000000000000000000000000000", + // Language: "String", + // PayableAccount: "String", + // ReceivableAccount: "String", + // IsStatic: false, + // PrimaryImageRef: "00000000000000000000000000000000", + // PayableAccounts: [{ SerialNumber: "String", Account: "String" }], + // ReceivableAccounts: [{ SerialNumber: "String", Account: "String" }], + // ManufacturerLoyaltyNumber: "String", + }, + IsAsynchronous: false + // UserRequest: "String", + // UserRef: "00000000000000000000000000000000", + }, + { auth: PBS_CREDENTIALS, socket } + ); + CheckForErrors(socket, ContactChangeResponse); + return ContactChangeResponse; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertContactData - ${error}`); + throw new Error(error); + } } async function UpsertVehicleData(socket, ownerRef) { - try { - const {data: VehicleChangeResponse} = await axios.post( - PBS_ENDPOINTS.VehicleChange, - { - VehicleInfo: { - //Id: "string/00000000-0000-0000-0000-000000000000", - //VehicleId: "00000000000000000000000000000000", - SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, - //StockNumber: "String", - VIN: socket.JobData.v_vin, - LicenseNumber: socket.JobData.plate_no, - //FleetNumber: "String", - //Status: "String", - OwnerRef: ownerRef, // "00000000000000000000000000000000", - ModelNumber: - socket.JobData.vehicle && socket.JobData.vehicle.v_makecode, - Make: socket.JobData.v_make_desc, - Model: socket.JobData.v_model_desc, - Trim: socket.JobData.vehicle && socket.JobData.vehicle.v_trimcode, - //VehicleType: "String", - Year: socket.JobData.v_model_yr, - Odometer: socket.JobData.kmout, - ExteriorColor: { - Code: socket.JobData.v_color, - Description: socket.JobData.v_color, - }, - // InteriorColor: { Code: "String", Description: "String" }, - //Engine: "String", - // Cylinders: "String", - // Transmission: "String", - // DriveWheel: "String", - // Fuel: "String", - // Weight: 0, - // InServiceDate: "0001-01-01T00:00:00.0000000Z", - // LastServiceDate: "0001-01-01T00:00:00.0000000Z", - // LastServiceMileage: 0, - // Lot: "String", - // LotDescription: "String", - // Category: "String", - // Options: [ - // { - // Group: "String", - // Code: "String", - // Description: "String", - // AdditionalInfo: "String", - // Price: 0, - // Cost: 0, - // Residual: 0, - // }, - // ], - // Refurbishments: [ - // { - // ReferenceNumber: "String", - // Description: "String", - // Price: 0, - // Cost: 0, - // Date: "0001-01-01T00:00:00.0000000Z", - // ApplicationModel: "String", - // }, - // ], - // Order: { - // InvoiceNumber: "String", - // Price: 0, - // Status: "String", - // Eta: "String", - // EstimatedCost: 0, - // OrderDate: "String", - // StatusDate: "String", - // IgnitionKeyCode: "String", - // DoorKeyCode: "String", - // Description: "String", - // LocationStatus: "String", - // LocationStatusDate: "0001-01-01T00:00:00.0000000Z", - // }, - // MSR: 0, - // BaseMSR: 0, - // Retail: 0, - // DateReceived: "0001-01-01T00:00:00.0000000Z", - // InternetPrice: 0, - // Lotpack: 0, - // Holdback: 0, - // InternetNotes: "String", - // Notes: "String", - // CriticalMemo: "String", - // IsCertified: false, - // LastSaleDate: "0001-01-01T00:00:00.0000000Z", - // LastUpdate: "0001-01-01T00:00:00.0000000Z", - // AppraisedValue: 0, - // Warranties: [ - // { - // Type: "String", - // CompanyName: "String", - // CoveragePlan: "String", - // Description: "String", - // Price: 0, - // Cost: 0, - // Term: "String", - // Deductible: 0, - // PolicyNumber: "String", - // StartDate: "String", - // StartMileage: 0, - // ExpirationDate: "String", - // ExpirationMileage: 0, - // }, - // ], - // Freight: 0, - // Air: 0, - // Inventory: 0, - // IsInactive: false, - // CustomFields: [{ Key: "String", Value: "String", Type: "String" }], - // FloorPlanCode: "String", - // FloorPlanAmount: 0, - // Insurance: { - // Company: "String", - // Policy: "String", - // ExpiryDate: "0001-01-01T00:00:00.0000000Z", - // AgentName: "String", - // AgentPhoneNumber: "String", - // }, - // Body: "String", - // ShortVIN: "String", - // AdditionalDrivers: ["00000000000000000000000000000000"], - // OrderDetails: { Distributor: "String" }, - // PrimaryImageRef: "00000000000000000000000000000000", - // Hold: { - // VehicleRef: "00000000000000000000000000000000", - // HoldFrom: "0001-01-01T00:00:00.0000000Z", - // HoldUntil: "0001-01-01T00:00:00.0000000Z", - // UserRef: "00000000000000000000000000000000", - // ContactRef: "00000000000000000000000000000000", - // Comments: "String", - // }, - // SeatingCapacity: "String", - // DeliveryDate: "0001-01-01T00:00:00.0000000Z", - // WarrantyExpiry: "0001-01-01T00:00:00.0000000Z", - // IsConditionallySold: false, - // SalesDivision: 0, - // StyleRef: "String", - }, - IsAsynchronous: false, - // UserRequest: "String", - // UserRef: "00000000000000000000000000000000", - }, - {auth: PBS_CREDENTIALS, socket} - ); - CheckForErrors(socket, VehicleChangeResponse); - return VehicleChangeResponse; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in UpsertVehicleData - ${error}` - ); - throw new Error(error); - } + try { + const { data: VehicleChangeResponse } = await axios.post( + PBS_ENDPOINTS.VehicleChange, + { + VehicleInfo: { + //Id: "string/00000000-0000-0000-0000-000000000000", + //VehicleId: "00000000000000000000000000000000", + SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, + //StockNumber: "String", + VIN: socket.JobData.v_vin, + LicenseNumber: socket.JobData.plate_no, + //FleetNumber: "String", + //Status: "String", + OwnerRef: ownerRef, // "00000000000000000000000000000000", + ModelNumber: socket.JobData.vehicle && socket.JobData.vehicle.v_makecode, + Make: socket.JobData.v_make_desc, + Model: socket.JobData.v_model_desc, + Trim: socket.JobData.vehicle && socket.JobData.vehicle.v_trimcode, + //VehicleType: "String", + Year: socket.JobData.v_model_yr, + Odometer: socket.JobData.kmout, + ExteriorColor: { + Code: socket.JobData.v_color, + Description: socket.JobData.v_color + } + // InteriorColor: { Code: "String", Description: "String" }, + //Engine: "String", + // Cylinders: "String", + // Transmission: "String", + // DriveWheel: "String", + // Fuel: "String", + // Weight: 0, + // InServiceDate: "0001-01-01T00:00:00.0000000Z", + // LastServiceDate: "0001-01-01T00:00:00.0000000Z", + // LastServiceMileage: 0, + // Lot: "String", + // LotDescription: "String", + // Category: "String", + // Options: [ + // { + // Group: "String", + // Code: "String", + // Description: "String", + // AdditionalInfo: "String", + // Price: 0, + // Cost: 0, + // Residual: 0, + // }, + // ], + // Refurbishments: [ + // { + // ReferenceNumber: "String", + // Description: "String", + // Price: 0, + // Cost: 0, + // Date: "0001-01-01T00:00:00.0000000Z", + // ApplicationModel: "String", + // }, + // ], + // Order: { + // InvoiceNumber: "String", + // Price: 0, + // Status: "String", + // Eta: "String", + // EstimatedCost: 0, + // OrderDate: "String", + // StatusDate: "String", + // IgnitionKeyCode: "String", + // DoorKeyCode: "String", + // Description: "String", + // LocationStatus: "String", + // LocationStatusDate: "0001-01-01T00:00:00.0000000Z", + // }, + // MSR: 0, + // BaseMSR: 0, + // Retail: 0, + // DateReceived: "0001-01-01T00:00:00.0000000Z", + // InternetPrice: 0, + // Lotpack: 0, + // Holdback: 0, + // InternetNotes: "String", + // Notes: "String", + // CriticalMemo: "String", + // IsCertified: false, + // LastSaleDate: "0001-01-01T00:00:00.0000000Z", + // LastUpdate: "0001-01-01T00:00:00.0000000Z", + // AppraisedValue: 0, + // Warranties: [ + // { + // Type: "String", + // CompanyName: "String", + // CoveragePlan: "String", + // Description: "String", + // Price: 0, + // Cost: 0, + // Term: "String", + // Deductible: 0, + // PolicyNumber: "String", + // StartDate: "String", + // StartMileage: 0, + // ExpirationDate: "String", + // ExpirationMileage: 0, + // }, + // ], + // Freight: 0, + // Air: 0, + // Inventory: 0, + // IsInactive: false, + // CustomFields: [{ Key: "String", Value: "String", Type: "String" }], + // FloorPlanCode: "String", + // FloorPlanAmount: 0, + // Insurance: { + // Company: "String", + // Policy: "String", + // ExpiryDate: "0001-01-01T00:00:00.0000000Z", + // AgentName: "String", + // AgentPhoneNumber: "String", + // }, + // Body: "String", + // ShortVIN: "String", + // AdditionalDrivers: ["00000000000000000000000000000000"], + // OrderDetails: { Distributor: "String" }, + // PrimaryImageRef: "00000000000000000000000000000000", + // Hold: { + // VehicleRef: "00000000000000000000000000000000", + // HoldFrom: "0001-01-01T00:00:00.0000000Z", + // HoldUntil: "0001-01-01T00:00:00.0000000Z", + // UserRef: "00000000000000000000000000000000", + // ContactRef: "00000000000000000000000000000000", + // Comments: "String", + // }, + // SeatingCapacity: "String", + // DeliveryDate: "0001-01-01T00:00:00.0000000Z", + // WarrantyExpiry: "0001-01-01T00:00:00.0000000Z", + // IsConditionallySold: false, + // SalesDivision: 0, + // StyleRef: "String", + }, + IsAsynchronous: false + // UserRequest: "String", + // UserRef: "00000000000000000000000000000000", + }, + { auth: PBS_CREDENTIALS, socket } + ); + CheckForErrors(socket, VehicleChangeResponse); + return VehicleChangeResponse; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`); + throw new Error(error); + } } async function InsertAccountPostingData(socket) { - try { - const allocations = await CalculateAllocations(socket, socket.JobData.id); + try { + const allocations = await CalculateAllocations(socket, socket.JobData.id); - const wips = []; - allocations.forEach((alloc) => { - //Add the sale item from each allocation. - if (alloc.sale.getAmount() > 0 && !alloc.tax) { - const item = { - Account: alloc.profitCenter.dms_acctnumber, - ControlNumber: socket.JobData.ro_number, - Amount: alloc.sale.multiply(-1).toFormat("0.00"), - //Comment: "String", - //AdditionalInfo: "String", - InvoiceNumber: socket.JobData.ro_number, - InvoiceDate: moment(socket.JobData.date_invoiced) - .tz(socket.JobData.bodyshop.timezone) - .toISOString(), - }; - wips.push(item); - } + const wips = []; + allocations.forEach((alloc) => { + //Add the sale item from each allocation. + if (alloc.sale.getAmount() > 0 && !alloc.tax) { + const item = { + Account: alloc.profitCenter.dms_acctnumber, + ControlNumber: socket.JobData.ro_number, + Amount: alloc.sale.multiply(-1).toFormat("0.00"), + //Comment: "String", + //AdditionalInfo: "String", + InvoiceNumber: socket.JobData.ro_number, + InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString() + }; + wips.push(item); + } - //Add the cost Item. - if (alloc.cost.getAmount() > 0 && !alloc.tax) { - const item = { - Account: alloc.costCenter.dms_acctnumber, - ControlNumber: socket.JobData.ro_number, - Amount: alloc.cost.toFormat("0.00"), - //Comment: "String", - //AdditionalInfo: "String", - InvoiceNumber: socket.JobData.ro_number, - InvoiceDate: moment(socket.JobData.date_invoiced) - .tz(socket.JobData.bodyshop.timezone) - .toISOString(), - }; - wips.push(item); + //Add the cost Item. + if (alloc.cost.getAmount() > 0 && !alloc.tax) { + const item = { + Account: alloc.costCenter.dms_acctnumber, + ControlNumber: socket.JobData.ro_number, + Amount: alloc.cost.toFormat("0.00"), + //Comment: "String", + //AdditionalInfo: "String", + InvoiceNumber: socket.JobData.ro_number, + InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString() + }; + wips.push(item); - const itemWip = { - Account: alloc.costCenter.dms_wip_acctnumber, - ControlNumber: socket.JobData.ro_number, - Amount: alloc.cost.multiply(-1).toFormat("0.00"), - //Comment: "String", - //AdditionalInfo: "String", - InvoiceNumber: socket.JobData.ro_number, - InvoiceDate: moment(socket.JobData.date_invoiced) - .tz(socket.JobData.bodyshop.timezone) - .toISOString(), - }; - wips.push(itemWip); - //Add to the WIP account. - } + const itemWip = { + Account: alloc.costCenter.dms_wip_acctnumber, + ControlNumber: socket.JobData.ro_number, + Amount: alloc.cost.multiply(-1).toFormat("0.00"), + //Comment: "String", + //AdditionalInfo: "String", + InvoiceNumber: socket.JobData.ro_number, + InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString() + }; + wips.push(itemWip); + //Add to the WIP account. + } - if (alloc.tax) { - if (alloc.sale.getAmount() > 0) { - const item2 = { - Account: alloc.profitCenter.dms_acctnumber, - ControlNumber: socket.JobData.ro_number, - Amount: alloc.sale.multiply(-1).toFormat("0.00"), - //Comment: "String", - //AdditionalInfo: "String", - InvoiceNumber: socket.JobData.ro_number, - InvoiceDate: moment(socket.JobData.date_invoiced) - .tz(socket.JobData.bodyshop.timezone) - .toISOString(), - }; - wips.push(item2); - } - } - }); - socket.txEnvelope.payers.forEach((payer) => { - const item = { - Account: payer.dms_acctnumber, - ControlNumber: payer.controlnumber, - Amount: Dinero({amount: Math.round(payer.amount * 100)}).toFormat( - "0.0" - ), - //Comment: "String", - //AdditionalInfo: "String", - InvoiceNumber: socket.JobData.ro_number, - InvoiceDate: moment(socket.JobData.date_invoiced) - .tz(socket.JobData.bodyshop.timezone) - .toISOString(), - }; + if (alloc.tax) { + if (alloc.sale.getAmount() > 0) { + const item2 = { + Account: alloc.profitCenter.dms_acctnumber, + ControlNumber: socket.JobData.ro_number, + Amount: alloc.sale.multiply(-1).toFormat("0.00"), + //Comment: "String", + //AdditionalInfo: "String", + InvoiceNumber: socket.JobData.ro_number, + InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString() + }; + wips.push(item2); + } + } + }); + socket.txEnvelope.payers.forEach((payer) => { + const item = { + Account: payer.dms_acctnumber, + ControlNumber: payer.controlnumber, + Amount: Dinero({ amount: Math.round(payer.amount * 100) }).toFormat("0.0"), + //Comment: "String", + //AdditionalInfo: "String", + InvoiceNumber: socket.JobData.ro_number, + InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString() + }; - wips.push(item); - }); - socket.transWips = wips; + wips.push(item); + }); + socket.transWips = wips; - const {data: AccountPostingChange} = await axios.post( - PBS_ENDPOINTS.AccountingPostingChange, - { - SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, - Posting: { - Reference: socket.JobData.ro_number, - JournalCode: socket.txEnvelope.journal, - TransactionDate: moment(socket.JobData.date_invoiced) - .tz(socket.JobData.bodyshop.timezone) - .toISOString(), //"0001-01-01T00:00:00.0000000Z", - Description: socket.txEnvelope.story, - //AdditionalInfo: "String", - Source: InstanceManager({imex: "ImEX Online", rome:"Rome Online"}), - Lines: wips, - }, - }, - {auth: PBS_CREDENTIALS, socket} - ); + const { data: AccountPostingChange } = await axios.post( + PBS_ENDPOINTS.AccountingPostingChange, + { + SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, + Posting: { + Reference: socket.JobData.ro_number, + JournalCode: socket.txEnvelope.journal, + TransactionDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z", + Description: socket.txEnvelope.story, + //AdditionalInfo: "String", + Source: InstanceManager({ imex: "ImEX Online", rome: "Rome Online" }), + Lines: wips + } + }, + { auth: PBS_CREDENTIALS, socket } + ); - CheckForErrors(socket, AccountPostingChange); - return AccountPostingChange; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in InsertAccountPostingData - ${error}` - ); - throw new Error(error); - } + CheckForErrors(socket, AccountPostingChange); + return AccountPostingChange; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in InsertAccountPostingData - ${error}`); + throw new Error(error); + } } async function MarkJobExported(socket, jobid) { - CdkBase.createLogEvent( - socket, - "DEBUG", - `Marking job as exported for id ${jobid}` - ); - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); - const result = await client - .setHeaders({Authorization: `Bearer ${socket.handshake.auth.token}`}) - .request(queries.MARK_JOB_EXPORTED, { - jobId: jobid, - job: { - status: - socket.JobData.bodyshop.md_ro_statuses.default_exported || - "Exported*", - date_exported: new Date(), - }, - log: { - bodyshopid: socket.JobData.bodyshop.id, - jobid: jobid, - successful: true, - useremail: socket.user.email, - metadata: socket.transWips, - }, - bill: { - exported: true, - exported_at: new Date(), - }, - }); + CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported for id ${jobid}`); + const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); + const result = await client + .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) + .request(queries.MARK_JOB_EXPORTED, { + jobId: jobid, + job: { + status: socket.JobData.bodyshop.md_ro_statuses.default_exported || "Exported*", + date_exported: new Date() + }, + log: { + bodyshopid: socket.JobData.bodyshop.id, + jobid: jobid, + successful: true, + useremail: socket.user.email, + metadata: socket.transWips + }, + bill: { + exported: true, + exported_at: new Date() + } + }); - return result; + return result; } async function InsertFailedExportLog(socket, error) { - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); - const result = await client - .setHeaders({Authorization: `Bearer ${socket.handshake.auth.token}`}) - .request(queries.INSERT_EXPORT_LOG, { - log: { - bodyshopid: socket.JobData.bodyshop.id, - jobid: socket.JobData.id, - successful: false, - message: [error], - useremail: socket.user.email, - }, - }); + const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); + const result = await client + .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) + .request(queries.INSERT_EXPORT_LOG, { + log: { + bodyshopid: socket.JobData.bodyshop.id, + jobid: socket.JobData.id, + successful: false, + message: [error], + useremail: socket.user.email + } + }); - return result; + return result; } diff --git a/server/accounting/qb-receivables-lines.js b/server/accounting/qb-receivables-lines.js index 1a6c5f339..c8c7fb174 100644 --- a/server/accounting/qb-receivables-lines.js +++ b/server/accounting/qb-receivables-lines.js @@ -1,999 +1,894 @@ const DineroQbFormat = require("./accounting-constants").DineroQbFormat; const Dinero = require("dinero.js"); -const {DiscountNotAlreadyCounted} = require("../job/job-totals"); +const { DiscountNotAlreadyCounted } = require("../job/job-totals"); const logger = require("../utils/logger"); -const InstanceManager = require("../utils/instanceMgr").default; +const InstanceManager = require("../utils/instanceMgr").default; -exports.default = function ({ - bodyshop, - jobs_by_pk, - qbo = false, - items, - taxCodes, - classes, - }) { - const InvoiceLineAdd = []; - const responsibilityCenters = bodyshop.md_responsibility_centers; +exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes, classes }) { + const InvoiceLineAdd = []; + const responsibilityCenters = bodyshop.md_responsibility_centers; - const invoiceLineHash = {}; //The hash of cost and profit centers based on the center name. + const invoiceLineHash = {}; //The hash of cost and profit centers based on the center name. - //Determine if there are MAPA and MASH lines already on the estimate. - //If there are, don't do anything extra (mitchell estimate) - //Otherwise, calculate them and add them to the default MAPA and MASH centers. - let hasMapaLine = false; - let hasMashLine = false; + //Determine if there are MAPA and MASH lines already on the estimate. + //If there are, don't do anything extra (mitchell estimate) + //Otherwise, calculate them and add them to the default MAPA and MASH centers. + let hasMapaLine = false; + let hasMashLine = false; - //Create the invoice lines mapping. - jobs_by_pk.joblines.map((jobline) => { - if (jobline.db_ref === "936008") { - //If either of these DB REFs change, they also need to change in job-totals/job-costing calculations. - hasMapaLine = true; - } - if (jobline.db_ref === "936007") { - hasMashLine = true; - } - //Parts Lines Mappings. - if (jobline.profitcenter_part) { - //TODO:AIO This appears to be a net 0 change exept for default quantity as 0 instead of 1 for imex. Need to verify. - const discountAmount = - ((jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0) || - (jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0)) && - DiscountNotAlreadyCounted(jobline, jobs_by_pk.joblines) - ? jobline.prt_dsmk_m - ? Dinero({amount: Math.round(jobline.prt_dsmk_m * 100)}) - : Dinero({ - amount: Math.round(jobline.act_price * 100), - }) - .multiply(jobline.part_qty || 0) - .percentage(Math.abs(jobline.prt_dsmk_p || 0)) - .multiply(jobline.prt_dsmk_p > 0 ? 1 : -1) - : Dinero(); - - let DineroAmount = Dinero({ - amount: Math.round((jobline.act_price || 0) * 100), - }) + //Create the invoice lines mapping. + jobs_by_pk.joblines.map((jobline) => { + if (jobline.db_ref === "936008") { + //If either of these DB REFs change, they also need to change in job-totals/job-costing calculations. + hasMapaLine = true; + } + if (jobline.db_ref === "936007") { + hasMashLine = true; + } + //Parts Lines Mappings. + if (jobline.profitcenter_part) { + //TODO:AIO This appears to be a net 0 change exept for default quantity as 0 instead of 1 for imex. Need to verify. + const discountAmount = + ((jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0) || (jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(jobline, jobs_by_pk.joblines) + ? jobline.prt_dsmk_m + ? Dinero({ amount: Math.round(jobline.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(jobline.act_price * 100) + }) .multiply(jobline.part_qty || 0) - .add(discountAmount); + .percentage(Math.abs(jobline.prt_dsmk_p || 0)) + .multiply(jobline.prt_dsmk_p > 0 ? 1 : -1) + : Dinero(); - const account = responsibilityCenters.profits.find( - (i) => jobline.profitcenter_part.toLowerCase() === i.name.toLowerCase() - ); + let DineroAmount = Dinero({ + amount: Math.round((jobline.act_price || 0) * 100) + }) + .multiply(jobline.part_qty || 0) + .add(discountAmount); - if (!account) { - logger.log( - "qbxml-receivables-no-account", - "warn", - null, - jobline.id, - null - ); - throw new Error( - `A matching account does not exist for the part allocation. Center: ${jobline.profitcenter_part}` - ); - } - if (qbo) { - //Do the mapping as per QBO. - //Determine the Tax code grouping. + const account = responsibilityCenters.profits.find( + (i) => jobline.profitcenter_part.toLowerCase() === i.name.toLowerCase() + ); - //Going to always assume that we need to apply GST. - const taxAccountCode = findTaxCode( - { - local: false, - federal: InstanceManager({imex: true, rome: false}), - state: - jobs_by_pk.state_tax_rate === 0 - ? false - : jobline.db_ref === "900511" || - jobline.db_ref === "900510" || - (jobline.mod_lb_hrs === 0 && //Extending IO-1375 as a part of IO-2023 - jobline.act_price > 0 && - jobline.lbr_op === "OP14") - ? true - : jobline.tax_part, - }, - bodyshop.md_responsibility_centers.sales_tax_codes - ); + if (!account) { + logger.log("qbxml-receivables-no-account", "warn", null, jobline.id, null); + throw new Error( + `A matching account does not exist for the part allocation. Center: ${jobline.profitcenter_part}` + ); + } + if (qbo) { + //Do the mapping as per QBO. + //Determine the Tax code grouping. - const QboTaxId = InstanceManager({imex:taxCodes[taxAccountCode], rome: CheckQBOUSATaxID({ - jobline: jobline, - type: "part", - job: jobs_by_pk, - }) }) - if (!invoiceLineHash[account.name]) invoiceLineHash[account.name] = {}; - if (!invoiceLineHash[account.name][QboTaxId]) { - invoiceLineHash[account.name][QboTaxId] = { - DetailType: "SalesItemLineDetail", - Amount: DineroAmount, - SalesItemLineDetail: { - ...(jobs_by_pk.class - ? {ClassRef: {value: classes[jobs_by_pk.class]}} - : {}), - ItemRef: { - value: items[account.accountitem], - }, - TaxCodeRef: { - value: QboTaxId, - }, - Qty: 1, - }, - }; - } else { - invoiceLineHash[account.name][QboTaxId].Amount = - invoiceLineHash[account.name][QboTaxId].Amount.add(DineroAmount); - } - } else { - if (!invoiceLineHash[account.name]) { - invoiceLineHash[account.name] = { - ItemRef: {FullName: account.accountitem}, - Desc: account.accountdesc, - Quantity: 1, //jobline.part_qty, - Amount: DineroAmount, //.toFormat(DineroQbFormat), - SalesTaxCodeRef: - InstanceManager({imex: { - FullName: "E", - }, rome:{ - FullName: - bodyshop.md_responsibility_centers.taxes.itemexemptcode || - "NON", - } }) - , - }; - } else { - invoiceLineHash[account.name].Amount = - invoiceLineHash[account.name].Amount.add(DineroAmount); - } - } - } - //End Parts line mappings. - - // Labor Lines - if (jobline.profitcenter_labor && jobline.mod_lb_hrs) { - const DineroAmount = Dinero({ - amount: Math.round( - jobs_by_pk[`rate_${jobline.mod_lbr_ty.toLowerCase()}`] * 100 - ), - }).multiply(jobline.mod_lb_hrs); - const account = responsibilityCenters.profits.find( - (i) => jobline.profitcenter_labor.toLowerCase() === i.name.toLowerCase() - ); - - if (!account) { - throw new Error( - `A matching account does not exist for the labor allocation. Center: ${jobline.profitcenter_labor}` - ); - } - if (qbo) { - //Going to always assume that we need to apply GST and PST for labor. - const taxAccountCode = findTaxCode( - { - local: false, - federal: InstanceManager({imex: true, rome: false}), - state: jobs_by_pk.state_tax_rate === 0 ? false : true, - }, - bodyshop.md_responsibility_centers.sales_tax_codes - ); - - const QboTaxId = InstanceManager({imex: taxCodes[taxAccountCode], rome: CheckQBOUSATaxID({jobline: jobline, type: "labor"})}) - if (!invoiceLineHash[account.name]) invoiceLineHash[account.name] = {}; - if (!invoiceLineHash[account.name][QboTaxId]) { - invoiceLineHash[account.name][QboTaxId] = { - DetailType: "SalesItemLineDetail", - Amount: DineroAmount, - SalesItemLineDetail: { - ...(jobs_by_pk.class - ? {ClassRef: {value: classes[jobs_by_pk.class]}} - : {}), - ItemRef: { - value: items[account.accountitem], - }, - TaxCodeRef: { - value: QboTaxId, - }, - Qty: 1, - }, - }; - } else { - invoiceLineHash[account.name][QboTaxId].Amount = - invoiceLineHash[account.name][QboTaxId].Amount.add(DineroAmount); - } - } else { - if (!invoiceLineHash[account.name]) { - invoiceLineHash[account.name] = { - ItemRef: {FullName: account.accountitem}, - Desc: account.accountdesc, - Quantity: 1, // jobline.mod_lb_hrs, - Amount: DineroAmount, - //Amount: DineroAmount.toFormat(DineroQbFormat), - SalesTaxCodeRef: - InstanceManager({imex: { - FullName:"E" - }, - rome: { - FullName: - bodyshop.md_responsibility_centers.taxes.itemexemptcode || - "NON", - } - - }) - , - }; - } else { - invoiceLineHash[account.name].Amount = - invoiceLineHash[account.name].Amount.add(DineroAmount); - } - } - } - }); - - if (!hasMapaLine && jobs_by_pk.job_totals.rates.mapa.total.amount > 0) { - // console.log("Adding MAPA Line Manually."); - const mapaAccountName = responsibilityCenters.defaults.profits.MAPA; - - const mapaAccount = responsibilityCenters.profits.find( - (c) => c.name === mapaAccountName + //Going to always assume that we need to apply GST. + const taxAccountCode = findTaxCode( + { + local: false, + federal: InstanceManager({ imex: true, rome: false }), + state: + jobs_by_pk.state_tax_rate === 0 + ? false + : jobline.db_ref === "900511" || + jobline.db_ref === "900510" || + (jobline.mod_lb_hrs === 0 && //Extending IO-1375 as a part of IO-2023 + jobline.act_price > 0 && + jobline.lbr_op === "OP14") + ? true + : jobline.tax_part + }, + bodyshop.md_responsibility_centers.sales_tax_codes ); - if (mapaAccount) { - if (qbo) { - //Add QBO MAPA - - //Going to always assume that we need to apply GST and PST for labor. - const taxAccountCode = findTaxCode( - { - local: false, - federal: InstanceManager({imex: true, rome: false}), - state: jobs_by_pk.state_tax_rate === 0 ? false : true, - }, - bodyshop.md_responsibility_centers.sales_tax_codes - ); - - const QboTaxId = InstanceManager({imex:taxCodes[taxAccountCode] , rome: CheckQBOUSATaxID({ - // jobline: jobline, - job: jobs_by_pk, - type: "materials", - })}); - if (!invoiceLineHash[mapaAccount.name]) - invoiceLineHash[mapaAccount.name] = {}; - if (!invoiceLineHash[mapaAccount.name][QboTaxId]) { - invoiceLineHash[mapaAccount.name][QboTaxId] = { - DetailType: "SalesItemLineDetail", - Amount: Dinero(jobs_by_pk.job_totals.rates.mapa.total), - SalesItemLineDetail: { - ItemRef: { - value: items[mapaAccount.accountitem], - }, - ...(jobs_by_pk.class - ? {ClassRef: {value: classes[jobs_by_pk.class]}} - : {}), - TaxCodeRef: { - value: QboTaxId, - }, - Qty: 1, - }, - }; - } else { - invoiceLineHash[mapaAccount.name][QboTaxId].Amount = invoiceLineHash[ - mapaAccount.name - ][QboTaxId].Amount.add( - Dinero(jobs_by_pk.job_totals.rates.mapa.total) - ); - } - } else { - InvoiceLineAdd.push({ - ItemRef: {FullName: mapaAccount.accountitem}, - Desc: mapaAccount.accountdesc, - Quantity: 1, - Amount: Dinero(jobs_by_pk.job_totals.rates.mapa.total).toFormat( - DineroQbFormat - ), - SalesTaxCodeRef: - InstanceManager({imex: { - FullName: "E", - }, - rome: { - FullName: - bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON", - } - }) - , - }); - } - } else { - //console.log("NO MAPA ACCOUNT FOUND!!"); - } - } - - if (!hasMashLine && jobs_by_pk.job_totals.rates.mash.total.amount > 0) { - // console.log("Adding MASH Line Manually."); - - const mashAccountName = responsibilityCenters.defaults.profits.MASH; - - const mashAccount = responsibilityCenters.profits.find( - (c) => c.name === mashAccountName - ); - - if (mashAccount) { - if (qbo) { - //Going to always assume that we need to apply GST and PST for labor. - const taxAccountCode = findTaxCode( - { - local: false, - federal: InstanceManager({imex: true, rome: false}), - state: jobs_by_pk.state_tax_rate === 0 ? false : true, - }, - bodyshop.md_responsibility_centers.sales_tax_codes - ); - - const QboTaxId = InstanceManager({imex:taxCodes[taxAccountCode], rome: CheckQBOUSATaxID({ - // jobline: jobline, - job: jobs_by_pk, - type: "materials", - })}); - if (!invoiceLineHash[mashAccount.name]) - invoiceLineHash[mashAccount.name] = {}; - if (!invoiceLineHash[mashAccount.name][QboTaxId]) { - invoiceLineHash[mashAccount.name][QboTaxId] = { - DetailType: "SalesItemLineDetail", - Amount: Dinero(jobs_by_pk.job_totals.rates.mash.total), - SalesItemLineDetail: { - ItemRef: { - value: items[mashAccount.accountitem], - }, - ...(jobs_by_pk.class - ? {ClassRef: {value: classes[jobs_by_pk.class]}} - : {}), - TaxCodeRef: { - value: QboTaxId, - }, - Qty: 1, - }, - }; - } else { - invoiceLineHash[mashAccount.name][QboTaxId].Amount = invoiceLineHash[ - mashAccount.name - ][QboTaxId].Amount.add( - Dinero(jobs_by_pk.job_totals.rates.mash.total) - ); - } - } else { - InvoiceLineAdd.push({ - ItemRef: {FullName: mashAccount.accountitem}, - Desc: mashAccount.accountdesc, - Quantity: 1, - Amount: Dinero(jobs_by_pk.job_totals.rates.mash.total).toFormat( - DineroQbFormat - ), - SalesTaxCodeRef: - InstanceManager({imex: - { - FullName: "E", - }, - rome: { - FullName: - bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON", - } - }) - , - }); - } - } else { - // console.log("NO MASH ACCOUNT FOUND!!"); - } - } - - if (qbo) { - Object.keys(invoiceLineHash).forEach((key) => { - Object.keys(invoiceLineHash[key]).forEach((key2) => { - const account = responsibilityCenters.profits.find( - (p) => p.name === key - ); - InvoiceLineAdd.push({ - ...invoiceLineHash[key][key2], - ...(account ? {Description: account.accountdesc} : {}), - Amount: invoiceLineHash[key][key2].Amount.toFormat(DineroQbFormat), - }); - }); + const QboTaxId = InstanceManager({ + imex: taxCodes[taxAccountCode], + rome: CheckQBOUSATaxID({ + jobline: jobline, + type: "part", + job: jobs_by_pk + }) }); - } else { - Object.keys(invoiceLineHash).forEach((key) => { - InvoiceLineAdd.push({ - ...invoiceLineHash[key], - Amount: invoiceLineHash[key].Amount.toFormat(DineroQbFormat), - }); - }); - } - //Convert the hash to an array. - - //Add Towing, storage and adjustment lines. - - ///TODO:AIO Check if this towing check works for imex and not just Rome. - if (jobs_by_pk.job_totals.additional.towing.amount > 0) { - if (qbo) { - //Going to always assume that we need to apply GST and PST for labor. - const taxAccountCode = findTaxCode( - { - local: false, - federal: InstanceManager({imex:true, rome:false}) , - state: jobs_by_pk.state_tax_rate === 0 ? false : true, - }, - bodyshop.md_responsibility_centers.sales_tax_codes - ); - const account = responsibilityCenters.profits.find( - (c) => c.name === responsibilityCenters.defaults.profits["TOW"] - ); - const QboTaxId = InstanceManager({imex:taxCodes[taxAccountCode], rome: CheckQBOUSATaxID({ - // jobline: jobline, - job: jobs_by_pk, - type: "towing", - }) }) - - InvoiceLineAdd.push({ - DetailType: "SalesItemLineDetail", - ///TODO:AIO Check if this towing check works for imex and not just Rome. - Amount: Dinero(jobs_by_pk.job_totals.additional.towing).toFormat( - DineroQbFormat - ), - SalesItemLineDetail: { - ...(jobs_by_pk.class - ? {ClassRef: {value: classes[jobs_by_pk.class]}} - : {}), - ItemRef: { - value: items[account.accountitem], - }, - TaxCodeRef: { - value: QboTaxId, - }, - Qty: 1, - }, - }); - } else { - InvoiceLineAdd.push({ - ItemRef: { - FullName: responsibilityCenters.profits.find( - (c) => c.name === responsibilityCenters.defaults.profits["TOW"] - ).accountitem, - }, - Desc: "Towing", - Quantity: 1, - ///TODO:AIO Check if this towing check works for imex and not just Rome. - Amount: Dinero(jobs_by_pk.job_totals.additional.towing).toFormat( - DineroQbFormat - ), - SalesTaxCodeRef: - InstanceManager({imex: { - FullName: "E", - }, - rome: { - FullName: - bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON", - }}), - }); - } - } - ///TODO:AIO Check if this storage check works for imex and not just Rome. - if (jobs_by_pk.job_totals.additional.storage.amount > 0) { - if (qbo) { - //Going to always assume that we need to apply GST and PST for labor. - const taxAccountCode = findTaxCode( - { - local: false, - federal: InstanceManager({imex: true, rome:false }) , - state: jobs_by_pk.state_tax_rate === 0 ? false : true, - }, - bodyshop.md_responsibility_centers.sales_tax_codes - ); - const account = responsibilityCenters.profits.find( - (c) => c.name === responsibilityCenters.defaults.profits["TOW"] - ); - const QboTaxId = InstanceManager({imex:taxCodes[taxAccountCode], rome: CheckQBOUSATaxID({ - // jobline: jobline, - job: jobs_by_pk, - type: "storage", - }) }) - InvoiceLineAdd.push({ - DetailType: "SalesItemLineDetail", - ///TODO:AIO Check if this storage check works for imex and not just Rome. - - Amount: Dinero( - jobs_by_pk.job_totals.additional.storage.amount - ).toFormat(DineroQbFormat), - SalesItemLineDetail: { - ...(jobs_by_pk.class - ? {ClassRef: {value: classes[jobs_by_pk.class]}} - : {}), - ItemRef: { - value: items[account.accountitem], - }, - TaxCodeRef: { - value: QboTaxId, - }, - Qty: 1, - }, - }); - } else { - InvoiceLineAdd.push({ - ItemRef: { - FullName: responsibilityCenters.profits.find( - (c) => c.name === responsibilityCenters.defaults.profits["TOW"] - ).accountitem, - }, - Desc: "Storage", - Quantity: 1, - ///TODO:AIO Check if this storage check works for imex and not just Rome. - - Amount: Dinero( - jobs_by_pk.job_totals.additional.storage.amount - ).toFormat(DineroQbFormat), - SalesTaxCodeRef: - InstanceManager({imex:{ - FullName: "E", - }, rome: { - FullName: - bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON", - } }) - , - }); - } - } - if ( - jobs_by_pk.adjustment_bottom_line && - jobs_by_pk.adjustment_bottom_line !== 0 - ) { - if (qbo) { - //Going to always assume that we need to apply GST and PST for labor. - const taxAccountCode = findTaxCode( - { - local: false, - federal: InstanceManager({imex: true, rome: false}) , - state: jobs_by_pk.state_tax_rate === 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({ - amount: Math.round((jobs_by_pk.adjustment_bottom_line || 0) * 100), - }).toFormat(DineroQbFormat), - SalesItemLineDetail: { - ...(jobs_by_pk.class - ? {ClassRef: {value: classes[jobs_by_pk.class]}} - : {}), - ItemRef: { - value: items[responsibilityCenters.refund.accountitem], - }, - TaxCodeRef: { - value: QboTaxId, - }, - Qty: 1, - }, - }); - } else { - InvoiceLineAdd.push({ - ItemRef: { - FullName: responsibilityCenters.refund.accountitem, - }, - Desc: "Adjustment", - Quantity: 1, - Amount: Dinero({ - amount: Math.round((jobs_by_pk.adjustment_bottom_line || 0) * 100), - }).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); - const state_tax = Dinero(job_totals.totals.state_tax); - const local_tax = Dinero(job_totals.totals.local_tax); - - const RulesetToUse = InstanceManager({ imex: 'CANADA', rome: 'US', promanager: 'US' }); - - if(RulesetToUse === "CANADA"){ - if (federal_tax.getAmount() > 0) { - if (qbo) { - // do qbo - } else { - InvoiceLineAdd.push({ - ItemRef: { - FullName: - bodyshop.md_responsibility_centers.taxes.federal.accountitem, - }, - Desc: bodyshop.md_responsibility_centers.taxes.federal.accountdesc, - Amount: federal_tax.toFormat(DineroQbFormat), - }); - } - } - - if (state_tax.getAmount() > 0) { - if (qbo) { - // do qbo - } else { - InvoiceLineAdd.push({ - ItemRef: { - FullName: bodyshop.md_responsibility_centers.taxes.state.accountitem, - }, - Desc: bodyshop.md_responsibility_centers.taxes.state.accountdesc, - Amount: state_tax.toFormat(DineroQbFormat), - }); - } - } - - if (local_tax.getAmount() > 0) { - if (qbo) { - // do qbo - } else { - InvoiceLineAdd.push({ - ItemRef: { - FullName: bodyshop.md_responsibility_centers.taxes.local.accountitem, - }, - Desc: bodyshop.md_responsibility_centers.taxes.local.accountdesc, - Amount: local_tax.toFormat(DineroQbFormat), - }); - } - } - - //Region Specific - const {ca_bc_pvrt} = jobs_by_pk; - if (ca_bc_pvrt) { - if (qbo) { - InvoiceLineAdd.push({ - DetailType: "SalesItemLineDetail", - Amount: Dinero({amount: (ca_bc_pvrt || 0) * 100}).toFormat( - DineroQbFormat - ), - SalesItemLineDetail: { - ...(jobs_by_pk.class - ? {ClassRef: {value: classes[jobs_by_pk.class]}} - : {}), - ItemRef: { - value: items["PVRT"], - }, - Qty: 1, - TaxCodeRef: { - value: - taxCodes[ - findTaxCode( - { - local: false, - federal: true, - state: false, - }, - bodyshop.md_responsibility_centers.sales_tax_codes - ) - ], - }, - }, - }); - } else { - InvoiceLineAdd.push({ - ItemRef: { - FullName: bodyshop.md_responsibility_centers.taxes.state.accountitem, - }, - Desc: "PVRT", - Amount: Dinero({amount: (ca_bc_pvrt || 0) * 100}).toFormat( - DineroQbFormat - ), - }); - } - } - - //QB USA with GST - //This was required for the No. 1 Collision Group. - if ( - bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo && - bodyshop.accountingconfig.qbo_usa && - bodyshop.region_config.includes("CA_") - ) { - InvoiceLineAdd.push({ + if (!invoiceLineHash[account.name]) invoiceLineHash[account.name] = {}; + if (!invoiceLineHash[account.name][QboTaxId]) { + invoiceLineHash[account.name][QboTaxId] = { DetailType: "SalesItemLineDetail", - Amount: Dinero(jobs_by_pk.job_totals.totals.federal_tax).toFormat( - DineroQbFormat - ), + Amount: DineroAmount, SalesItemLineDetail: { - ...(jobs_by_pk.class - ? {ClassRef: {value: classes[jobs_by_pk.class]}} - : {}), - ItemRef: { - value: - items[bodyshop.md_responsibility_centers.taxes.federal.accountitem], - }, - Qty: 1, - }, - }); + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), + ItemRef: { + value: items[account.accountitem] + }, + TaxCodeRef: { + value: QboTaxId + }, + Qty: 1 + } + }; + } else { + invoiceLineHash[account.name][QboTaxId].Amount = + invoiceLineHash[account.name][QboTaxId].Amount.add(DineroAmount); + } + } else { + if (!invoiceLineHash[account.name]) { + invoiceLineHash[account.name] = { + ItemRef: { FullName: account.accountitem }, + Desc: account.accountdesc, + Quantity: 1, //jobline.part_qty, + Amount: DineroAmount, //.toFormat(DineroQbFormat), + SalesTaxCodeRef: InstanceManager({ + imex: { + FullName: "E" + }, + rome: { + FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON" + } + }) + }; + } else { + invoiceLineHash[account.name].Amount = invoiceLineHash[account.name].Amount.add(DineroAmount); + } + } } - } -else{ -//Handle insurance profile adjustments -Object.keys(job_totals.parts.adjustments).forEach((key) => { - if (qbo) { + //End Parts line mappings. + + // Labor Lines + if (jobline.profitcenter_labor && jobline.mod_lb_hrs) { + const DineroAmount = Dinero({ + amount: Math.round(jobs_by_pk[`rate_${jobline.mod_lbr_ty.toLowerCase()}`] * 100) + }).multiply(jobline.mod_lb_hrs); + const account = responsibilityCenters.profits.find( + (i) => jobline.profitcenter_labor.toLowerCase() === i.name.toLowerCase() + ); + + if (!account) { + throw new Error( + `A matching account does not exist for the labor allocation. Center: ${jobline.profitcenter_labor}` + ); + } + if (qbo) { //Going to always assume that we need to apply GST and PST for labor. const taxAccountCode = findTaxCode( - { - local: false, - federal: process.env.COUNTRY === "USA" ? false : true, - state: jobs_by_pk.state_tax_rate === 0 ? false : true, + { + local: false, + federal: InstanceManager({ imex: true, rome: false }), + state: jobs_by_pk.state_tax_rate === 0 ? false : true + }, + bodyshop.md_responsibility_centers.sales_tax_codes + ); + + const QboTaxId = InstanceManager({ + imex: taxCodes[taxAccountCode], + rome: CheckQBOUSATaxID({ jobline: jobline, type: "labor" }) + }); + if (!invoiceLineHash[account.name]) invoiceLineHash[account.name] = {}; + if (!invoiceLineHash[account.name][QboTaxId]) { + invoiceLineHash[account.name][QboTaxId] = { + DetailType: "SalesItemLineDetail", + Amount: DineroAmount, + SalesItemLineDetail: { + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), + ItemRef: { + value: items[account.accountitem] + }, + TaxCodeRef: { + value: QboTaxId + }, + Qty: 1 + } + }; + } else { + invoiceLineHash[account.name][QboTaxId].Amount = + invoiceLineHash[account.name][QboTaxId].Amount.add(DineroAmount); + } + } else { + if (!invoiceLineHash[account.name]) { + invoiceLineHash[account.name] = { + ItemRef: { FullName: account.accountitem }, + Desc: account.accountdesc, + Quantity: 1, // jobline.mod_lb_hrs, + Amount: DineroAmount, + //Amount: DineroAmount.toFormat(DineroQbFormat), + SalesTaxCodeRef: InstanceManager({ + imex: { + FullName: "E" + }, + rome: { + FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON" + } + }) + }; + } else { + invoiceLineHash[account.name].Amount = invoiceLineHash[account.name].Amount.add(DineroAmount); + } + } + } + }); + + if (!hasMapaLine && jobs_by_pk.job_totals.rates.mapa.total.amount > 0) { + // console.log("Adding MAPA Line Manually."); + const mapaAccountName = responsibilityCenters.defaults.profits.MAPA; + + const mapaAccount = responsibilityCenters.profits.find((c) => c.name === mapaAccountName); + + if (mapaAccount) { + if (qbo) { + //Add QBO MAPA + + //Going to always assume that we need to apply GST and PST for labor. + const taxAccountCode = findTaxCode( + { + local: false, + federal: InstanceManager({ imex: true, rome: false }), + state: jobs_by_pk.state_tax_rate === 0 ? false : true + }, + bodyshop.md_responsibility_centers.sales_tax_codes + ); + + const QboTaxId = InstanceManager({ + imex: taxCodes[taxAccountCode], + rome: CheckQBOUSATaxID({ + // jobline: jobline, + job: jobs_by_pk, + type: "materials" + }) + }); + if (!invoiceLineHash[mapaAccount.name]) invoiceLineHash[mapaAccount.name] = {}; + if (!invoiceLineHash[mapaAccount.name][QboTaxId]) { + invoiceLineHash[mapaAccount.name][QboTaxId] = { + DetailType: "SalesItemLineDetail", + Amount: Dinero(jobs_by_pk.job_totals.rates.mapa.total), + SalesItemLineDetail: { + ItemRef: { + value: items[mapaAccount.accountitem] + }, + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), + TaxCodeRef: { + value: QboTaxId + }, + Qty: 1 + } + }; + } else { + invoiceLineHash[mapaAccount.name][QboTaxId].Amount = invoiceLineHash[mapaAccount.name][QboTaxId].Amount.add( + Dinero(jobs_by_pk.job_totals.rates.mapa.total) + ); + } + } else { + InvoiceLineAdd.push({ + ItemRef: { FullName: mapaAccount.accountitem }, + Desc: mapaAccount.accountdesc, + Quantity: 1, + Amount: Dinero(jobs_by_pk.job_totals.rates.mapa.total).toFormat(DineroQbFormat), + SalesTaxCodeRef: InstanceManager({ + imex: { + FullName: "E" }, - bodyshop.md_responsibility_centers.sales_tax_codes + rome: { + FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON" + } + }) + }); + } + } else { + //console.log("NO MAPA ACCOUNT FOUND!!"); + } + } + + if (!hasMashLine && jobs_by_pk.job_totals.rates.mash.total.amount > 0) { + // console.log("Adding MASH Line Manually."); + + const mashAccountName = responsibilityCenters.defaults.profits.MASH; + + const mashAccount = responsibilityCenters.profits.find((c) => c.name === mashAccountName); + + if (mashAccount) { + if (qbo) { + //Going to always assume that we need to apply GST and PST for labor. + const taxAccountCode = findTaxCode( + { + local: false, + federal: InstanceManager({ imex: true, rome: false }), + state: jobs_by_pk.state_tax_rate === 0 ? false : true + }, + bodyshop.md_responsibility_centers.sales_tax_codes + ); + + const QboTaxId = InstanceManager({ + imex: taxCodes[taxAccountCode], + rome: CheckQBOUSATaxID({ + // jobline: jobline, + job: jobs_by_pk, + type: "materials" + }) + }); + if (!invoiceLineHash[mashAccount.name]) invoiceLineHash[mashAccount.name] = {}; + if (!invoiceLineHash[mashAccount.name][QboTaxId]) { + invoiceLineHash[mashAccount.name][QboTaxId] = { + DetailType: "SalesItemLineDetail", + Amount: Dinero(jobs_by_pk.job_totals.rates.mash.total), + SalesItemLineDetail: { + ItemRef: { + value: items[mashAccount.accountitem] + }, + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), + TaxCodeRef: { + value: QboTaxId + }, + Qty: 1 + } + }; + } else { + invoiceLineHash[mashAccount.name][QboTaxId].Amount = invoiceLineHash[mashAccount.name][QboTaxId].Amount.add( + Dinero(jobs_by_pk.job_totals.rates.mash.total) + ); + } + } else { + InvoiceLineAdd.push({ + ItemRef: { FullName: mashAccount.accountitem }, + Desc: mashAccount.accountdesc, + Quantity: 1, + Amount: Dinero(jobs_by_pk.job_totals.rates.mash.total).toFormat(DineroQbFormat), + SalesTaxCodeRef: InstanceManager({ + imex: { + FullName: "E" + }, + rome: { + FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON" + } + }) + }); + } + } else { + // console.log("NO MASH ACCOUNT FOUND!!"); + } + } + + if (qbo) { + Object.keys(invoiceLineHash).forEach((key) => { + Object.keys(invoiceLineHash[key]).forEach((key2) => { + const account = responsibilityCenters.profits.find((p) => p.name === key); + InvoiceLineAdd.push({ + ...invoiceLineHash[key][key2], + ...(account ? { Description: account.accountdesc } : {}), + Amount: invoiceLineHash[key][key2].Amount.toFormat(DineroQbFormat) + }); + }); + }); + } else { + Object.keys(invoiceLineHash).forEach((key) => { + InvoiceLineAdd.push({ + ...invoiceLineHash[key], + Amount: invoiceLineHash[key].Amount.toFormat(DineroQbFormat) + }); + }); + } + //Convert the hash to an array. + + //Add Towing, storage and adjustment lines. + + ///TODO:AIO Check if this towing check works for imex and not just Rome. + if (jobs_by_pk.job_totals.additional.towing.amount > 0) { + if (qbo) { + //Going to always assume that we need to apply GST and PST for labor. + const taxAccountCode = findTaxCode( + { + local: false, + federal: InstanceManager({ imex: true, rome: false }), + state: jobs_by_pk.state_tax_rate === 0 ? false : true + }, + bodyshop.md_responsibility_centers.sales_tax_codes + ); + const account = responsibilityCenters.profits.find( + (c) => c.name === responsibilityCenters.defaults.profits["TOW"] + ); + const QboTaxId = InstanceManager({ + imex: taxCodes[taxAccountCode], + rome: CheckQBOUSATaxID({ + // jobline: jobline, + job: jobs_by_pk, + type: "towing" + }) + }); + + InvoiceLineAdd.push({ + DetailType: "SalesItemLineDetail", + ///TODO:AIO Check if this towing check works for imex and not just Rome. + Amount: Dinero(jobs_by_pk.job_totals.additional.towing).toFormat(DineroQbFormat), + SalesItemLineDetail: { + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), + ItemRef: { + value: items[account.accountitem] + }, + TaxCodeRef: { + value: QboTaxId + }, + Qty: 1 + } + }); + } else { + InvoiceLineAdd.push({ + ItemRef: { + FullName: responsibilityCenters.profits.find((c) => c.name === responsibilityCenters.defaults.profits["TOW"]) + .accountitem + }, + Desc: "Towing", + Quantity: 1, + ///TODO:AIO Check if this towing check works for imex and not just Rome. + Amount: Dinero(jobs_by_pk.job_totals.additional.towing).toFormat(DineroQbFormat), + SalesTaxCodeRef: InstanceManager({ + imex: { + FullName: "E" + }, + rome: { + FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON" + } + }) + }); + } + } + ///TODO:AIO Check if this storage check works for imex and not just Rome. + if (jobs_by_pk.job_totals.additional.storage.amount > 0) { + if (qbo) { + //Going to always assume that we need to apply GST and PST for labor. + const taxAccountCode = findTaxCode( + { + local: false, + federal: InstanceManager({ imex: true, rome: false }), + state: jobs_by_pk.state_tax_rate === 0 ? false : true + }, + bodyshop.md_responsibility_centers.sales_tax_codes + ); + const account = responsibilityCenters.profits.find( + (c) => c.name === responsibilityCenters.defaults.profits["TOW"] + ); + const QboTaxId = InstanceManager({ + imex: taxCodes[taxAccountCode], + rome: CheckQBOUSATaxID({ + // jobline: jobline, + job: jobs_by_pk, + type: "storage" + }) + }); + InvoiceLineAdd.push({ + DetailType: "SalesItemLineDetail", + ///TODO:AIO Check if this storage check works for imex and not just Rome. + + Amount: Dinero(jobs_by_pk.job_totals.additional.storage.amount).toFormat(DineroQbFormat), + SalesItemLineDetail: { + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), + ItemRef: { + value: items[account.accountitem] + }, + TaxCodeRef: { + value: QboTaxId + }, + Qty: 1 + } + }); + } else { + InvoiceLineAdd.push({ + ItemRef: { + FullName: responsibilityCenters.profits.find((c) => c.name === responsibilityCenters.defaults.profits["TOW"]) + .accountitem + }, + Desc: "Storage", + Quantity: 1, + ///TODO:AIO Check if this storage check works for imex and not just Rome. + + Amount: Dinero(jobs_by_pk.job_totals.additional.storage.amount).toFormat(DineroQbFormat), + SalesTaxCodeRef: InstanceManager({ + imex: { + FullName: "E" + }, + rome: { + FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON" + } + }) + }); + } + } + if (jobs_by_pk.adjustment_bottom_line && jobs_by_pk.adjustment_bottom_line !== 0) { + if (qbo) { + //Going to always assume that we need to apply GST and PST for labor. + const taxAccountCode = findTaxCode( + { + local: false, + federal: InstanceManager({ imex: true, rome: false }), + state: jobs_by_pk.state_tax_rate === 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({ + amount: Math.round((jobs_by_pk.adjustment_bottom_line || 0) * 100) + }).toFormat(DineroQbFormat), + SalesItemLineDetail: { + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), + ItemRef: { + value: items[responsibilityCenters.refund.accountitem] + }, + TaxCodeRef: { + value: QboTaxId + }, + Qty: 1 + } + }); + } else { + InvoiceLineAdd.push({ + ItemRef: { + FullName: responsibilityCenters.refund.accountitem + }, + Desc: "Adjustment", + Quantity: 1, + Amount: Dinero({ + amount: Math.round((jobs_by_pk.adjustment_bottom_line || 0) * 100) + }).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); + const state_tax = Dinero(job_totals.totals.state_tax); + const local_tax = Dinero(job_totals.totals.local_tax); + + const RulesetToUse = InstanceManager({ imex: "CANADA", rome: "US", promanager: "US" }); + + if (RulesetToUse === "CANADA") { + if (federal_tax.getAmount() > 0) { + if (qbo) { + // do qbo + } else { + InvoiceLineAdd.push({ + ItemRef: { + FullName: bodyshop.md_responsibility_centers.taxes.federal.accountitem + }, + Desc: bodyshop.md_responsibility_centers.taxes.federal.accountdesc, + Amount: federal_tax.toFormat(DineroQbFormat) + }); + } + } + + if (state_tax.getAmount() > 0) { + if (qbo) { + // do qbo + } else { + InvoiceLineAdd.push({ + ItemRef: { + FullName: bodyshop.md_responsibility_centers.taxes.state.accountitem + }, + Desc: bodyshop.md_responsibility_centers.taxes.state.accountdesc, + Amount: state_tax.toFormat(DineroQbFormat) + }); + } + } + + if (local_tax.getAmount() > 0) { + if (qbo) { + // do qbo + } else { + InvoiceLineAdd.push({ + ItemRef: { + FullName: bodyshop.md_responsibility_centers.taxes.local.accountitem + }, + Desc: bodyshop.md_responsibility_centers.taxes.local.accountdesc, + Amount: local_tax.toFormat(DineroQbFormat) + }); + } + } + + //Region Specific + const { ca_bc_pvrt } = jobs_by_pk; + if (ca_bc_pvrt) { + if (qbo) { + InvoiceLineAdd.push({ + DetailType: "SalesItemLineDetail", + Amount: Dinero({ amount: (ca_bc_pvrt || 0) * 100 }).toFormat(DineroQbFormat), + SalesItemLineDetail: { + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), + ItemRef: { + value: items["PVRT"] + }, + Qty: 1, + TaxCodeRef: { + value: + taxCodes[ + findTaxCode( + { + local: false, + federal: true, + state: false + }, + bodyshop.md_responsibility_centers.sales_tax_codes + ) + ] + } + } + }); + } else { + InvoiceLineAdd.push({ + ItemRef: { + FullName: bodyshop.md_responsibility_centers.taxes.state.accountitem + }, + Desc: "PVRT", + Amount: Dinero({ amount: (ca_bc_pvrt || 0) * 100 }).toFormat(DineroQbFormat) + }); + } + } + + //QB USA with GST + //This was required for the No. 1 Collision Group. + if ( + bodyshop.accountingconfig && + bodyshop.accountingconfig.qbo && + bodyshop.accountingconfig.qbo_usa && + bodyshop.region_config.includes("CA_") + ) { + InvoiceLineAdd.push({ + DetailType: "SalesItemLineDetail", + Amount: Dinero(jobs_by_pk.job_totals.totals.federal_tax).toFormat(DineroQbFormat), + SalesItemLineDetail: { + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), + ItemRef: { + value: items[bodyshop.md_responsibility_centers.taxes.federal.accountitem] + }, + Qty: 1 + } + }); + } + } else { + //Handle insurance profile adjustments + Object.keys(job_totals.parts.adjustments).forEach((key) => { + if (qbo) { + //Going to always assume that we need to apply GST and PST for labor. + const taxAccountCode = findTaxCode( + { + local: false, + federal: process.env.COUNTRY === "USA" ? false : true, + state: jobs_by_pk.state_tax_rate === 0 ? false : true + }, + bodyshop.md_responsibility_centers.sales_tax_codes ); const account = responsibilityCenters.profits.find( - (c) => c.name === responsibilityCenters.defaults.profits[key] + (c) => c.name === responsibilityCenters.defaults.profits[key] ); const QboTaxId = - process.env.COUNTRY === "USA" - ? CheckQBOUSATaxID({ - // jobline: jobline, - job: jobs_by_pk, - type: "storage", - }) - : taxCodes[taxAccountCode]; - InvoiceLineAdd.push({ - DetailType: "SalesItemLineDetail", - Amount: Dinero(job_totals.parts.adjustments[key]).toFormat( - DineroQbFormat - ), - Description: `${account.accountdesc} - Adjustment`, - SalesItemLineDetail: { - ...(jobs_by_pk.class - ? {ClassRef: {value: classes[jobs_by_pk.class]}} - : {}), - ItemRef: { - value: items[account.accountitem], - }, - TaxCodeRef: { - value: QboTaxId, - }, - Qty: 1, - }, - }); - } else { + process.env.COUNTRY === "USA" + ? CheckQBOUSATaxID({ + // jobline: jobline, + job: jobs_by_pk, + type: "storage" + }) + : taxCodes[taxAccountCode]; InvoiceLineAdd.push({ + DetailType: "SalesItemLineDetail", + Amount: Dinero(job_totals.parts.adjustments[key]).toFormat(DineroQbFormat), + Description: `${account.accountdesc} - Adjustment`, + SalesItemLineDetail: { + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), ItemRef: { - FullName: responsibilityCenters.profits.find( - (c) => c.name === responsibilityCenters.defaults.profits[key] - ).accountitem, + value: items[account.accountitem] }, - Desc: "Storage", - Quantity: 1, - Amount: Dinero(job_totals.parts.adjustments[key]).toFormat( - DineroQbFormat - ), - SalesTaxCodeRef: { - FullName: - bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON", + TaxCodeRef: { + value: QboTaxId }, + Qty: 1 + } }); - } -}); + } else { + InvoiceLineAdd.push({ + ItemRef: { + FullName: responsibilityCenters.profits.find((c) => c.name === responsibilityCenters.defaults.profits[key]) + .accountitem + }, + Desc: "Storage", + Quantity: 1, + Amount: Dinero(job_totals.parts.adjustments[key]).toFormat(DineroQbFormat), + SalesTaxCodeRef: { + FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON" + } + }); + } + }); -const QboTaxId = - process.env.COUNTRY === "USA" + const QboTaxId = + process.env.COUNTRY === "USA" ? CheckQBOUSATaxID({ // jobline: jobline, type: "adjustment", - job: jobs_by_pk, - }) + job: jobs_by_pk + }) : taxCodes[taxAccountCode]; -for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { - const taxAmount = Dinero( - job_totals.totals.us_sales_tax_breakdown[`ty${tyCounter}Tax`] - ); - console.log(`Tax ${tyCounter}`, taxAmount.toFormat()); - if (taxAmount.getAmount() > 0) { + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + const taxAmount = Dinero(job_totals.totals.us_sales_tax_breakdown[`ty${tyCounter}Tax`]); + console.log(`Tax ${tyCounter}`, taxAmount.toFormat()); + if (taxAmount.getAmount() > 0) { if (qbo) { - InvoiceLineAdd.push({ - DetailType: "SalesItemLineDetail", - Amount: taxAmount.toFormat(DineroQbFormat), - SalesItemLineDetail: { - ...(jobs_by_pk.class - ? {ClassRef: {value: classes[jobs_by_pk.class]}} - : {}), - ItemRef: { - value: - items[ - responsibilityCenters.taxes[`tax_ty${tyCounter}`].accountitem - ], - }, - TaxCodeRef: { - value: QboTaxId, - }, - Qty: 1, - }, - }); - } else { - InvoiceLineAdd.push({ - ItemRef: { - FullName: - bodyshop.md_responsibility_centers.taxes[`tax_ty${tyCounter}`] - .accountitem, - }, - Desc: bodyshop.md_responsibility_centers.taxes[`tax_ty${tyCounter}`] - .accountdesc, - Amount: taxAmount.toFormat(DineroQbFormat), - }); - } - } -} -} - - - if (!qbo && InvoiceLineAdd.length === 0) { - //Handle the scenario where there is a $0 sale invoice. - InvoiceLineAdd.push({ - Desc: "No estimate lines.", - }); - } - - //Check if there are multiple payers. If there are, add a deduction line and make sure we create new invoices. - - if ( - jobs_by_pk.qb_multiple_payers && - jobs_by_pk.qb_multiple_payers.length > 0 - ) { - jobs_by_pk.qb_multiple_payers.forEach((payer) => { - if (qbo) { - InvoiceLineAdd.push({ - DetailType: "SalesItemLineDetail", - Amount: Dinero({ - amount: Math.round((payer.amount || 0) * 100) * -1, - }).toFormat(DineroQbFormat), - SalesItemLineDetail: { - ...(jobs_by_pk.class - ? {ClassRef: {value: classes[jobs_by_pk.class]}} - : {}), - ItemRef: { - value: - items[responsibilityCenters.qb_multiple_payers?.accountitem], - }, - Qty: 1, - TaxCodeRef: { - value: - taxCodes[ - findTaxCode( - { - local: false, - federal: false, - state: false, - }, - bodyshop.md_responsibility_centers.sales_tax_codes - ) - ], - }, - }, - }); - } else { - InvoiceLineAdd.push({ - ItemRef: { - FullName: responsibilityCenters.qb_multiple_payers?.accountitem, - }, - Desc: `${payer.name} Liability`, - Amount: Dinero({ - amount: Math.round((payer.amount || 0) * 100) * -1, - }).toFormat(DineroQbFormat), - }); + InvoiceLineAdd.push({ + DetailType: "SalesItemLineDetail", + Amount: taxAmount.toFormat(DineroQbFormat), + SalesItemLineDetail: { + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), + ItemRef: { + value: items[responsibilityCenters.taxes[`tax_ty${tyCounter}`].accountitem] + }, + TaxCodeRef: { + value: QboTaxId + }, + Qty: 1 } - }); + }); + } else { + InvoiceLineAdd.push({ + ItemRef: { + FullName: bodyshop.md_responsibility_centers.taxes[`tax_ty${tyCounter}`].accountitem + }, + Desc: bodyshop.md_responsibility_centers.taxes[`tax_ty${tyCounter}`].accountdesc, + Amount: taxAmount.toFormat(DineroQbFormat) + }); + } + } } - return InvoiceLineAdd; + } + + if (!qbo && InvoiceLineAdd.length === 0) { + //Handle the scenario where there is a $0 sale invoice. + InvoiceLineAdd.push({ + Desc: "No estimate lines." + }); + } + + //Check if there are multiple payers. If there are, add a deduction line and make sure we create new invoices. + + if (jobs_by_pk.qb_multiple_payers && jobs_by_pk.qb_multiple_payers.length > 0) { + jobs_by_pk.qb_multiple_payers.forEach((payer) => { + if (qbo) { + InvoiceLineAdd.push({ + DetailType: "SalesItemLineDetail", + Amount: Dinero({ + amount: Math.round((payer.amount || 0) * 100) * -1 + }).toFormat(DineroQbFormat), + SalesItemLineDetail: { + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), + ItemRef: { + value: items[responsibilityCenters.qb_multiple_payers?.accountitem] + }, + Qty: 1, + TaxCodeRef: { + value: + taxCodes[ + findTaxCode( + { + local: false, + federal: false, + state: false + }, + bodyshop.md_responsibility_centers.sales_tax_codes + ) + ] + } + } + }); + } else { + InvoiceLineAdd.push({ + ItemRef: { + FullName: responsibilityCenters.qb_multiple_payers?.accountitem + }, + Desc: `${payer.name} Liability`, + Amount: Dinero({ + amount: Math.round((payer.amount || 0) * 100) * -1 + }).toFormat(DineroQbFormat) + }); + } + }); + } + return InvoiceLineAdd; }; -const findTaxCode = ({local, state, federal}, taxcode) => { - const t = taxcode.filter( - (t) => - !!t.local === !!local && - !!t.state === !!state && - !!t.federal === !!federal - ); - if (t.length === 1) { - return t[0].code; - } else if (t.length > 1) { - return "Multiple Tax Codes Match"; - } else { - return ""; - } +const findTaxCode = ({ local, state, federal }, taxcode) => { + const t = taxcode.filter((t) => !!t.local === !!local && !!t.state === !!state && !!t.federal === !!federal); + if (t.length === 1) { + return t[0].code; + } else if (t.length > 1) { + return "Multiple Tax Codes Match"; + } else { + return ""; + } }; exports.findTaxCode = findTaxCode; -exports.createMultiQbPayerLines = function ({ - bodyshop, - jobs_by_pk, - qbo = false, - items, - taxCodes, - classes, - payer, - }) { - const InvoiceLineAdd = []; - const responsibilityCenters = bodyshop.md_responsibility_centers; +exports.createMultiQbPayerLines = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes, classes, payer }) { + const InvoiceLineAdd = []; + const responsibilityCenters = bodyshop.md_responsibility_centers; - const invoiceLineHash = {}; //The hash of cost and profit centers based on the center name. + const invoiceLineHash = {}; //The hash of cost and profit centers based on the center name. - if (qbo) { - //Going to always assume that we need to apply GST and PST for labor. - const taxAccountCode = findTaxCode( - { - local: false, - federal: false, - state: false, - }, - bodyshop.md_responsibility_centers.sales_tax_codes - ); - const QboTaxId = taxCodes[taxAccountCode]; - InvoiceLineAdd.push({ - DetailType: "SalesItemLineDetail", - Amount: Dinero({ - amount: Math.round((payer.amount || 0) * 100), - }).toFormat(DineroQbFormat), - SalesItemLineDetail: { - ...(jobs_by_pk.class - ? {ClassRef: {value: classes[jobs_by_pk.class]}} - : {}), - ItemRef: { - value: items[responsibilityCenters.qb_multiple_payers?.accountitem], - }, - TaxCodeRef: { - value: QboTaxId, - }, - Qty: 1, - }, - }); - } else { - InvoiceLineAdd.push({ - ItemRef: { - FullName: responsibilityCenters.qb_multiple_payers?.accountitem, - }, - Desc: `${payer.name} Liability`, - Quantity: 1, - Amount: Dinero({ - amount: Math.round((payer.amount || 0) * 100), - }).toFormat(DineroQbFormat), - SalesTaxCodeRef: - InstanceManager({imex: { - FullName: "E", - }, rome:{ - FullName: - bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON", - } }) + if (qbo) { + //Going to always assume that we need to apply GST and PST for labor. + const taxAccountCode = findTaxCode( + { + local: false, + federal: false, + state: false + }, + bodyshop.md_responsibility_centers.sales_tax_codes + ); + const QboTaxId = taxCodes[taxAccountCode]; + InvoiceLineAdd.push({ + DetailType: "SalesItemLineDetail", + Amount: Dinero({ + amount: Math.round((payer.amount || 0) * 100) + }).toFormat(DineroQbFormat), + SalesItemLineDetail: { + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), + ItemRef: { + value: items[responsibilityCenters.qb_multiple_payers?.accountitem] + }, + TaxCodeRef: { + value: QboTaxId + }, + Qty: 1 + } + }); + } else { + InvoiceLineAdd.push({ + ItemRef: { + FullName: responsibilityCenters.qb_multiple_payers?.accountitem + }, + Desc: `${payer.name} Liability`, + Quantity: 1, + Amount: Dinero({ + amount: Math.round((payer.amount || 0) * 100) + }).toFormat(DineroQbFormat), + SalesTaxCodeRef: InstanceManager({ + imex: { + FullName: "E" + }, + rome: { + FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON" + } + }) + }); + } - }); - } - - return InvoiceLineAdd; + return InvoiceLineAdd; }; -function CheckQBOUSATaxID({jobline, job, type}) { - //Replacing this to be all non-taxable items with the refactor of parts tax rates. - return "NON"; - // if (type === "labor") { - // return jobline.lbr_tax ? "TAX" : "NON"; - // } else if (type === "part") { - // return jobline.tax_part ? "TAX" : "NON"; - // } else if (type === "materials") { - // return job.tax_paint_mat_rt > 0 ? "TAX" : "NON"; - // } else if (type === " towing") { - // return true ? "TAX" : "NON"; - // } else if (type === "adjustment") { - // return false ? "TAX" : "NON"; - // } else { - // throw new Error(`Unknown type to calculate tax id: ${type} `); - // } +function CheckQBOUSATaxID({ jobline, job, type }) { + //Replacing this to be all non-taxable items with the refactor of parts tax rates. + return "NON"; + // if (type === "labor") { + // return jobline.lbr_tax ? "TAX" : "NON"; + // } else if (type === "part") { + // return jobline.tax_part ? "TAX" : "NON"; + // } else if (type === "materials") { + // return job.tax_paint_mat_rt > 0 ? "TAX" : "NON"; + // } else if (type === " towing") { + // return true ? "TAX" : "NON"; + // } else if (type === "adjustment") { + // return false ? "TAX" : "NON"; + // } else { + // throw new Error(`Unknown type to calculate tax id: ${type} `); + // } } diff --git a/server/accounting/qbo/qbo-authorize.js b/server/accounting/qbo/qbo-authorize.js index c31e3c3b6..b58b0f4c8 100644 --- a/server/accounting/qbo/qbo-authorize.js +++ b/server/accounting/qbo/qbo-authorize.js @@ -1,32 +1,29 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const OAuthClient = require("intuit-oauth"); const logger = require("../../utils/logger"); const oauthClient = new OAuthClient({ - clientId: process.env.QBO_CLIENT_ID, - clientSecret: process.env.QBO_SECRET, - environment: process.env.NODE_ENV === "production" ? "production" : "sandbox", - redirectUri: process.env.QBO_REDIRECT_URI, + clientId: process.env.QBO_CLIENT_ID, + clientSecret: process.env.QBO_SECRET, + environment: process.env.NODE_ENV === "production" ? "production" : "sandbox", + redirectUri: process.env.QBO_REDIRECT_URI }); exports.default = async (req, res) => { - try { - logger.log("qbo-auth-uri", "DEBUG", req.user.email, null, null); - const authUri = oauthClient.authorizeUri({ - scope: [OAuthClient.scopes.Accounting, OAuthClient.scopes.OpenId], - state: req.user.email, - }); // can be an array of multiple scopes ex : {scope:[OAuthClient.scopes.Accounting,OAuthClient.scopes.OpenId]} + try { + logger.log("qbo-auth-uri", "DEBUG", req.user.email, null, null); + const authUri = oauthClient.authorizeUri({ + scope: [OAuthClient.scopes.Accounting, OAuthClient.scopes.OpenId], + state: req.user.email + }); // can be an array of multiple scopes ex : {scope:[OAuthClient.scopes.Accounting,OAuthClient.scopes.OpenId]} - res.send(authUri); - } catch (error) { - logger.log("qbo-auth-uri-error", "ERROR", req.user.email, null, {error}); + res.send(authUri); + } catch (error) { + logger.log("qbo-auth-uri-error", "ERROR", req.user.email, null, { error }); - res.status(500).json(error); - } + res.status(500).json(error); + } }; diff --git a/server/accounting/qbo/qbo-callback.js b/server/accounting/qbo/qbo-callback.js index 815d845ef..af6d244d7 100644 --- a/server/accounting/qbo/qbo-callback.js +++ b/server/accounting/qbo/qbo-callback.js @@ -1,96 +1,82 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const logger = require("../../utils/logger"); const OAuthClient = require("intuit-oauth"); const client = require("../../graphql-client/graphql-client").client; const queries = require("../../graphql-client/queries"); -const {parse, stringify} = require("querystring"); +const { parse, stringify } = require("querystring"); const InstanceManager = require("../../utils/instanceMgr").default; const oauthClient = new OAuthClient({ - clientId: process.env.QBO_CLIENT_ID, - clientSecret: process.env.QBO_SECRET, - environment: process.env.NODE_ENV === "production" ? "production" : "sandbox", - redirectUri: process.env.QBO_REDIRECT_URI, - logging: true, + clientId: process.env.QBO_CLIENT_ID, + clientSecret: process.env.QBO_SECRET, + environment: process.env.NODE_ENV === "production" ? "production" : "sandbox", + redirectUri: process.env.QBO_REDIRECT_URI, + logging: true }); let url; -if (process.env.NODE_ENV === "production") { //TODO:AIO Add in QBO callbacks. - url = InstanceManager({imex: `https://imex.online`, rome: `https://romeonline.io`,}); +if (process.env.NODE_ENV === "production") { + //TODO:AIO Add in QBO callbacks. + url = InstanceManager({ imex: `https://imex.online`, rome: `https://romeonline.io` }); } else if (process.env.NODE_ENV === "test") { - url = InstanceManager({imex: `https://test.imex.online`,rome: `https://test.romeonline.io`}); + url = InstanceManager({ imex: `https://test.imex.online`, rome: `https://test.romeonline.io` }); } else { - url = `http://localhost:3000`; + url = `http://localhost:3000`; } exports.default = async (req, res) => { - const queryString = req.url.split("?").reverse()[0]; - const params = parse(queryString); - try { - logger.log("qbo-callback-create-token", "DEBUG", params.state, null, null); - const authResponse = await oauthClient.createToken(req.url); - if (authResponse.json.error) { - logger.log("qbo-callback-error", "ERROR", params.state, null, { - error: authResponse.json, - }); - res.redirect( - `${url}/manage/accounting/qbo?error=${encodeURIComponent( - JSON.stringify(authResponse.json) - )}` - ); - } else { - await client.request(queries.SET_QBO_AUTH_WITH_REALM, { - email: params.state, - qbo_auth: {...authResponse.json, createdAt: Date.now()}, - qbo_realmId: params.realmId, - }); - logger.log( - "qbo-callback-create-token-success", - "DEBUG", - params.state, - null, - null - ); + const queryString = req.url.split("?").reverse()[0]; + const params = parse(queryString); + try { + logger.log("qbo-callback-create-token", "DEBUG", params.state, null, null); + const authResponse = await oauthClient.createToken(req.url); + if (authResponse.json.error) { + logger.log("qbo-callback-error", "ERROR", params.state, null, { + error: authResponse.json + }); + res.redirect(`${url}/manage/accounting/qbo?error=${encodeURIComponent(JSON.stringify(authResponse.json))}`); + } else { + await client.request(queries.SET_QBO_AUTH_WITH_REALM, { + email: params.state, + qbo_auth: { ...authResponse.json, createdAt: Date.now() }, + qbo_realmId: params.realmId + }); + logger.log("qbo-callback-create-token-success", "DEBUG", params.state, null, null); - res.redirect( - `${url}/manage/accounting/qbo?${stringify(params)}` - ); - } - } catch (e) { - logger.log("qbo-callback-error", "ERROR", params.state, null, { - error: e, - }); - res.status(400).json(e); + res.redirect(`${url}/manage/accounting/qbo?${stringify(params)}`); } + } catch (e) { + logger.log("qbo-callback-error", "ERROR", params.state, null, { + error: e + }); + res.status(400).json(e); + } }; exports.refresh = async (oauthClient, req) => { - try { - // logger.log("qbo-token-refresh", "DEBUG", req.user.email, null, null); - const authResponse = await oauthClient.refresh(); - await client.request(queries.SET_QBO_AUTH, { - email: req.user.email, - qbo_auth: {...authResponse.json, createdAt: Date.now()}, - }); - } catch (error) { - logger.log("qbo-token-refresh-error", "ERROR", req.user.email, null, { - error, - }); - } + try { + // logger.log("qbo-token-refresh", "DEBUG", req.user.email, null, null); + const authResponse = await oauthClient.refresh(); + await client.request(queries.SET_QBO_AUTH, { + email: req.user.email, + qbo_auth: { ...authResponse.json, createdAt: Date.now() } + }); + } catch (error) { + logger.log("qbo-token-refresh-error", "ERROR", req.user.email, null, { + error + }); + } }; exports.setNewRefreshToken = async (email, apiResponse) => { - //logger.log("qbo-token-updated", "DEBUG", email, null, null); + //logger.log("qbo-token-updated", "DEBUG", email, null, null); - await client.request(queries.SET_QBO_AUTH, { - email, - qbo_auth: {...apiResponse.token, createdAt: Date.now()}, - }); + await client.request(queries.SET_QBO_AUTH, { + email, + qbo_auth: { ...apiResponse.token, createdAt: Date.now() } + }); }; diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index f832599ae..2be6e4c12 100644 --- a/server/accounting/qbo/qbo-payables.js +++ b/server/accounting/qbo/qbo-payables.js @@ -2,336 +2,282 @@ const urlBuilder = require("./qbo").urlBuilder; const StandardizeName = require("./qbo").StandardizeName; const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const logger = require("../../utils/logger"); const Dinero = require("dinero.js"); const DineroQbFormat = require("../accounting-constants").DineroQbFormat; const apiGqlClient = require("../../graphql-client/graphql-client").client; const queries = require("../../graphql-client/queries"); -const { - refresh: refreshOauthToken, - setNewRefreshToken, -} = require("./qbo-callback"); +const { refresh: refreshOauthToken, setNewRefreshToken } = require("./qbo-callback"); const OAuthClient = require("intuit-oauth"); const moment = require("moment-timezone"); const findTaxCode = require("../qb-receivables-lines").findTaxCode; exports.default = async (req, res) => { + const oauthClient = new OAuthClient({ + clientId: process.env.QBO_CLIENT_ID, + clientSecret: process.env.QBO_SECRET, + environment: process.env.NODE_ENV === "production" ? "production" : "sandbox", + redirectUri: process.env.QBO_REDIRECT_URI, + logging: true + }); - const oauthClient = new OAuthClient({ - clientId: process.env.QBO_CLIENT_ID, - clientSecret: process.env.QBO_SECRET, - environment: - process.env.NODE_ENV === "production" ? "production" : "sandbox", - redirectUri: process.env.QBO_REDIRECT_URI, - logging: true, + try { + //Fetch the API Access Tokens & Set them for the session. + const response = await apiGqlClient.request(queries.GET_QBO_AUTH, { + email: req.user.email }); - try { - //Fetch the API Access Tokens & Set them for the session. - const response = await apiGqlClient.request(queries.GET_QBO_AUTH, { - email: req.user.email, + const { qbo_realmId } = response.associations[0]; + + oauthClient.setToken(response.associations[0].qbo_auth); + + if (!qbo_realmId) { + res.status(401).json({ error: "No company associated." }); + return; + } + + await refreshOauthToken(oauthClient, req); + + const { bills: billsToQuery, elgen } = req.body; + + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; + + logger.log("qbo-payable-create", "DEBUG", req.user.email, billsToQuery); + + const result = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.QUERY_BILLS_FOR_PAYABLES_EXPORT, { + bills: billsToQuery + }); + + const { bills, bodyshops } = result; + const ret = []; + const bodyshop = bodyshops[0]; + + for (const bill of bills) { + try { + let vendorRecord; + vendorRecord = await QueryVendorRecord(oauthClient, qbo_realmId, req, bill); + + if (!vendorRecord) { + vendorRecord = await InsertVendorRecord(oauthClient, qbo_realmId, req, bill); + } + + const insertResults = await InsertBill(oauthClient, qbo_realmId, req, bill, vendorRecord, bodyshop); + + // //No error. Mark the job exported & insert export log. + if (elgen) { + const result = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.QBO_MARK_BILL_EXPORTED, { + billId: bill.id, + bill: { + exported: true, + exported_at: moment().tz(bodyshop.timezone) + }, + logs: [ + { + bodyshopid: bodyshop.id, + billid: bill.id, + successful: true, + useremail: req.user.email + } + ] + }); + } + + ret.push({ billid: bill.id, success: true }); + } catch (error) { + ret.push({ + billid: bill.id, + success: false, + errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message) }); - const {qbo_realmId} = response.associations[0]; - - oauthClient.setToken(response.associations[0].qbo_auth); - - if (!qbo_realmId) { - res.status(401).json({error: "No company associated."}); - return; + //Add the export log error. + if (elgen) { + const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.INSERT_EXPORT_LOG, { + logs: [ + { + bodyshopid: bodyshop.id, + billid: bill.id, + successful: false, + message: JSON.stringify([ + (error && error.authResponse && error.authResponse.body) || (error && error.message) + ]), + useremail: req.user.email + } + ] + }); } - - await refreshOauthToken(oauthClient, req); - - const {bills: billsToQuery, elgen} = req.body; - - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; - - logger.log("qbo-payable-create", "DEBUG", req.user.email, billsToQuery); - - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QUERY_BILLS_FOR_PAYABLES_EXPORT, { - bills: billsToQuery, - }); - - const {bills, bodyshops} = result; - const ret = []; - const bodyshop = bodyshops[0]; - - for (const bill of bills) { - try { - let vendorRecord; - vendorRecord = await QueryVendorRecord( - oauthClient, - qbo_realmId, - req, - bill - ); - - if (!vendorRecord) { - vendorRecord = await InsertVendorRecord( - oauthClient, - qbo_realmId, - req, - bill - ); - } - - const insertResults = await InsertBill( - oauthClient, - qbo_realmId, - req, - bill, - vendorRecord, - bodyshop - ); - - // //No error. Mark the job exported & insert export log. - if (elgen) { - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QBO_MARK_BILL_EXPORTED, { - billId: bill.id, - bill: { - exported: true, - exported_at: moment().tz(bodyshop.timezone), - }, - logs: [ - { - bodyshopid: bodyshop.id, - billid: bill.id, - successful: true, - useremail: req.user.email, - }, - ], - }); - } - - ret.push({billid: bill.id, success: true}); - } catch (error) { - ret.push({ - billid: bill.id, - success: false, - errorMessage: - (error && error.authResponse && error.authResponse.body) || - (error && error.message), - }); - - //Add the export log error. - if (elgen) { - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.INSERT_EXPORT_LOG, { - logs: [ - { - bodyshopid: bodyshop.id, - billid: bill.id, - successful: false, - message: JSON.stringify([ - (error && error.authResponse && error.authResponse.body) || - (error && error.message), - ]), - useremail: req.user.email, - }, - ], - }); - } - } - } - - res.status(200).json(ret); - } catch (error) { - console.log(error); - logger.log("qbo-payable-create-error", "ERROR", req.user.email, {error: error.message, stack: error.stack}); - res.status(400).json(error); + } } + + res.status(200).json(ret); + } catch (error) { + console.log(error); + logger.log("qbo-payable-create-error", "ERROR", req.user.email, { + error: error.message, + stack: error.stack + }); + res.status(400).json(error); + } }; async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) { - try { - const result = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * From vendor where DisplayName = '${StandardizeName( - bill.vendor.name - )}'` - ), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); - setNewRefreshToken(req.user.email, result); - return ( - result.json && - result.json.QueryResponse && - result.json.QueryResponse.Vendor && - result.json.QueryResponse.Vendor[0] - ); - } catch (error) { - logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: - (error && error.authResponse && error.authResponse.body) || - (error && error.message), - method: "QueryVendorRecord", - }); - throw error; - } + try { + const result = await oauthClient.makeApiCall({ + url: urlBuilder( + qbo_realmId, + "query", + `select * From vendor where DisplayName = '${StandardizeName(bill.vendor.name)}'` + ), + method: "POST", + headers: { + "Content-Type": "application/json" + } + }); + setNewRefreshToken(req.user.email, result); + return ( + result.json && + result.json.QueryResponse && + result.json.QueryResponse.Vendor && + result.json.QueryResponse.Vendor[0] + ); + } catch (error) { + logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { + error: (error && error.authResponse && error.authResponse.body) || (error && error.message), + method: "QueryVendorRecord" + }); + throw error; + } } async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) { - const Vendor = { - DisplayName: bill.vendor.name, - }; - try { - const result = await oauthClient.makeApiCall({ - url: urlBuilder(qbo_realmId, "vendor"), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(Vendor), - }); - setNewRefreshToken(req.user.email, result); - return result && result.json && result.json.Vendor; - } catch (error) { - logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: - (error && error.authResponse && error.authResponse.body) || - (error && error.message), - method: "InsertVendorRecord", - }); - throw error; - } + const Vendor = { + DisplayName: bill.vendor.name + }; + try { + const result = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "vendor"), + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(Vendor) + }); + setNewRefreshToken(req.user.email, result); + return result && result.json && result.json.Vendor; + } catch (error) { + logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { + error: (error && error.authResponse && error.authResponse.body) || (error && error.message), + method: "InsertVendorRecord" + }); + throw error; + } } -async function InsertBill( - oauthClient, - qbo_realmId, - req, - bill, - vendor, - bodyshop -) { - const {accounts, taxCodes, classes} = await QueryMetaData( - oauthClient, - qbo_realmId, - req - ); +async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) { + const { accounts, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req); - const lines = bill.billlines.map((il) => - generateBillLine( - il, - accounts, - bill.job.class, - bodyshop.md_responsibility_centers.sales_tax_codes, - classes, - taxCodes, - bodyshop.md_responsibility_centers.costs + const lines = bill.billlines.map((il) => + generateBillLine( + il, + accounts, + bill.job.class, + bodyshop.md_responsibility_centers.sales_tax_codes, + classes, + taxCodes, + bodyshop.md_responsibility_centers.costs + ) + ); + + //QB USA with GST + //This was required for the No. 1 Collision Group. + if ( + bodyshop.accountingconfig && + bodyshop.accountingconfig.qbo && + bodyshop.accountingconfig.qbo_usa && + bodyshop.region_config.includes("CA_") + ) { + lines.push({ + DetailType: "AccountBasedExpenseLineDetail", + + AccountBasedExpenseLineDetail: { + ...(bill.job.class ? { ClassRef: { value: classes[bill.job.class] } } : {}), + AccountRef: { + value: accounts[bodyshop.md_responsibility_centers.taxes.federal.accountdesc] + } + }, + + Amount: Dinero({ + amount: Math.round( + bill.billlines.reduce((acc, val) => { + return acc + val.actual_cost * val.quantity; + }, 0) * 100 ) - ); + }) + .percentage(bill.federal_tax_rate) - //QB USA with GST - //This was required for the No. 1 Collision Group. - if ( - bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo && - bodyshop.accountingconfig.qbo_usa && - bodyshop.region_config.includes("CA_") - ) { - lines.push({ - DetailType: "AccountBasedExpenseLineDetail", - - AccountBasedExpenseLineDetail: { - ...(bill.job.class - ? {ClassRef: {value: classes[bill.job.class]}} - : {}), - AccountRef: { - value: - accounts[ - bodyshop.md_responsibility_centers.taxes.federal.accountdesc - ], - }, - }, - - Amount: Dinero({ - amount: Math.round( - bill.billlines.reduce((acc, val) => { - return acc + val.actual_cost * val.quantity; - }, 0) * 100 - ), - }) - .percentage(bill.federal_tax_rate) - - .toFormat(DineroQbFormat), - }); - } - - const billQbo = { - VendorRef: { - value: vendor.Id, - }, - TxnDate: moment(bill.date) - //.tz(bill.job.bodyshop.timezone) - .format("YYYY-MM-DD"), - ...(!bill.is_credit_memo && - bill.vendor.due_date && { - DueDate: moment(bill.date) - //.tz(bill.job.bodyshop.timezone) - .add(bill.vendor.due_date, "days") - .format("YYYY-MM-DD"), - }), - DocNumber: bill.invoice_number, - //...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}), - ...(!( - bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo && - bodyshop.accountingconfig.qbo_usa && - bodyshop.region_config.includes("CA_") - ) - ? {GlobalTaxCalculation: "TaxExcluded"} - : {}), - ...(bodyshop.accountingconfig.qbo_departmentid && - bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && { - DepartmentRef: {value: bodyshop.accountingconfig.qbo_departmentid}, - }), - PrivateNote: `RO ${bill.job.ro_number || ""}`, - Line: lines, - }; - logger.log("qbo-payable-objectlog", "DEBUG", req.user.email, bill.id, { - billQbo, + .toFormat(DineroQbFormat) }); - try { - const result = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - bill.is_credit_memo ? "vendorcredit" : "bill" - ), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(billQbo), - }); - setNewRefreshToken(req.user.email, result); - return result && result.json && result.json.Bill; - } catch (error) { - logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: - (error && error.authResponse && error.authResponse.body) || - (error && error.message), - method: "InsertBill", - }); - throw error; - } + } + + const billQbo = { + VendorRef: { + value: vendor.Id + }, + TxnDate: moment(bill.date) + //.tz(bill.job.bodyshop.timezone) + .format("YYYY-MM-DD"), + ...(!bill.is_credit_memo && + bill.vendor.due_date && { + DueDate: moment(bill.date) + //.tz(bill.job.bodyshop.timezone) + .add(bill.vendor.due_date, "days") + .format("YYYY-MM-DD") + }), + DocNumber: bill.invoice_number, + //...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}), + ...(!( + bodyshop.accountingconfig && + bodyshop.accountingconfig.qbo && + bodyshop.accountingconfig.qbo_usa && + bodyshop.region_config.includes("CA_") + ) + ? { GlobalTaxCalculation: "TaxExcluded" } + : {}), + ...(bodyshop.accountingconfig.qbo_departmentid && + bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && { + DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } + }), + PrivateNote: `RO ${bill.job.ro_number || ""}`, + Line: lines + }; + logger.log("qbo-payable-objectlog", "DEBUG", req.user.email, bill.id, { + billQbo + }); + try { + const result = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, bill.is_credit_memo ? "vendorcredit" : "bill"), + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(billQbo) + }); + setNewRefreshToken(req.user.email, result); + return result && result.json && result.json.Bill; + } catch (error) { + logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { + error: (error && error.authResponse && error.authResponse.body) || (error && error.message), + method: "InsertBill" + }); + throw error; + } } // [ @@ -347,97 +293,88 @@ async function InsertBill( // }, // ], -const generateBillLine = ( - billLine, - accounts, - jobClass, - ioSalesTaxCodes, - classes, - taxCodes, - costCenters -) => { - const account = costCenters.find((c) => c.name === billLine.cost_center); +const generateBillLine = (billLine, accounts, jobClass, ioSalesTaxCodes, classes, taxCodes, costCenters) => { + const account = costCenters.find((c) => c.name === billLine.cost_center); - return { - DetailType: "AccountBasedExpenseLineDetail", + return { + DetailType: "AccountBasedExpenseLineDetail", - AccountBasedExpenseLineDetail: { - ...(jobClass ? {ClassRef: {value: classes[jobClass]}} : {}), - TaxCodeRef: { - value: - taxCodes[findTaxCode(billLine.applicable_taxes, ioSalesTaxCodes)], - }, - AccountRef: { - value: accounts[account.accountname], - }, - }, + AccountBasedExpenseLineDetail: { + ...(jobClass ? { ClassRef: { value: classes[jobClass] } } : {}), + TaxCodeRef: { + value: taxCodes[findTaxCode(billLine.applicable_taxes, ioSalesTaxCodes)] + }, + AccountRef: { + value: accounts[account.accountname] + } + }, - Amount: Dinero({ - amount: Math.round(billLine.actual_cost * 100), - }) - .multiply(billLine.quantity || 1) - .toFormat(DineroQbFormat), - }; + Amount: Dinero({ + amount: Math.round(billLine.actual_cost * 100) + }) + .multiply(billLine.quantity || 1) + .toFormat(DineroQbFormat) + }; }; async function QueryMetaData(oauthClient, qbo_realmId, req) { - const accounts = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * From Account where AccountType in ('Cost of Goods Sold', 'Other Current Liability')` - ), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); - setNewRefreshToken(req.user.email, accounts); - const taxCodes = await oauthClient.makeApiCall({ - url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); + const accounts = await oauthClient.makeApiCall({ + url: urlBuilder( + qbo_realmId, + "query", + `select * From Account where AccountType in ('Cost of Goods Sold', 'Other Current Liability')` + ), + method: "POST", + headers: { + "Content-Type": "application/json" + } + }); + setNewRefreshToken(req.user.email, accounts); + const taxCodes = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`), + method: "POST", + headers: { + "Content-Type": "application/json" + } + }); - const classes = await oauthClient.makeApiCall({ - url: urlBuilder(qbo_realmId, "query", `select * From Class`), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); + const classes = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "query", `select * From Class`), + method: "POST", + headers: { + "Content-Type": "application/json" + } + }); - const taxCodeMapping = {}; + const taxCodeMapping = {}; - taxCodes.json && + taxCodes.json && taxCodes.json.QueryResponse && taxCodes.json.QueryResponse.TaxCode && taxCodes.json.QueryResponse.TaxCode.forEach((t) => { - taxCodeMapping[t.Name] = t.Id; + taxCodeMapping[t.Name] = t.Id; }); - const accountMapping = {}; + const accountMapping = {}; - accounts.json && + accounts.json && accounts.json.QueryResponse && accounts.json.QueryResponse.Account && accounts.json.QueryResponse.Account.forEach((t) => { - accountMapping[t.FullyQualifiedName] = t.Id; + accountMapping[t.FullyQualifiedName] = t.Id; }); - const classMapping = {}; - classes.json && + const classMapping = {}; + classes.json && classes.json.QueryResponse && classes.json.QueryResponse.Class && classes.json.QueryResponse.Class.forEach((t) => { - classMapping[t.Name] = t.Id; + classMapping[t.Name] = t.Id; }); - return { - accounts: accountMapping, - taxCodes: taxCodeMapping, - classes: classMapping, - }; + return { + accounts: accountMapping, + taxCodes: taxCodeMapping, + classes: classMapping + }; } diff --git a/server/accounting/qbo/qbo-payments.js b/server/accounting/qbo/qbo-payments.js index 86d6a0d8b..436efca71 100644 --- a/server/accounting/qbo/qbo-payments.js +++ b/server/accounting/qbo/qbo-payments.js @@ -1,531 +1,439 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const logger = require("../../utils/logger"); const Dinero = require("dinero.js"); const apiGqlClient = require("../../graphql-client/graphql-client").client; const queries = require("../../graphql-client/queries"); -const { - refresh: refreshOauthToken, - setNewRefreshToken, -} = require("./qbo-callback"); +const { refresh: refreshOauthToken, setNewRefreshToken } = require("./qbo-callback"); const OAuthClient = require("intuit-oauth"); const moment = require("moment-timezone"); const GraphQLClient = require("graphql-request").GraphQLClient; const { - QueryInsuranceCo, - InsertInsuranceCo, - InsertJob, - InsertOwner, - QueryJob, - QueryOwner, + QueryInsuranceCo, + InsertInsuranceCo, + InsertJob, + InsertOwner, + QueryJob, + QueryOwner } = require("../qbo/qbo-receivables"); -const {urlBuilder} = require("./qbo"); -const {DineroQbFormat} = require("../accounting-constants"); -const {findTaxCode} = require("../qb-receivables-lines"); +const { urlBuilder } = require("./qbo"); +const { DineroQbFormat } = require("../accounting-constants"); +const { findTaxCode } = require("../qb-receivables-lines"); exports.default = async (req, res) => { - const oauthClient = new OAuthClient({ - clientId: process.env.QBO_CLIENT_ID, - clientSecret: process.env.QBO_SECRET, - environment: - process.env.NODE_ENV === "production" ? "production" : "sandbox", - redirectUri: process.env.QBO_REDIRECT_URI, - logging: true, + const oauthClient = new OAuthClient({ + clientId: process.env.QBO_CLIENT_ID, + clientSecret: process.env.QBO_SECRET, + environment: process.env.NODE_ENV === "production" ? "production" : "sandbox", + redirectUri: process.env.QBO_REDIRECT_URI, + logging: true + }); + try { + //Fetch the API Access Tokens & Set them for the session. + const response = await apiGqlClient.request(queries.GET_QBO_AUTH, { + email: req.user.email }); - try { - //Fetch the API Access Tokens & Set them for the session. - const response = await apiGqlClient.request(queries.GET_QBO_AUTH, { - email: req.user.email, - }); - const {qbo_realmId} = response.associations[0]; - oauthClient.setToken(response.associations[0].qbo_auth); - if (!qbo_realmId) { - res.status(401).json({error: "No company associated."}); - return; - } - await refreshOauthToken(oauthClient, req); - - const {payments: paymentsToQuery, elgen} = req.body; - - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; - - logger.log("qbo-payment-create", "DEBUG", req.user.email, paymentsToQuery); - - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QUERY_PAYMENTS_FOR_EXPORT, { - payments: paymentsToQuery, - }); - - const {payments, bodyshops} = result; - const bodyshop = bodyshops[0]; - - const ret = []; - - for (const payment of payments) { - try { - let isThreeTier = bodyshop.accountingconfig.tiers === 3; - let twoTierPref = bodyshop.accountingconfig.twotierpref; - - //Replace this with a for-each loop to check every single Job that's included in the list. - - //QB Multi AR - If it is in this scenario, overwrite whatever defaults are set since multi AR - //will always go Source => RO - if (payment.payer !== "Customer" && payment.payer !== "Insurance") { - payment.job.ins_co_nm = payment.payer; - twoTierPref = "source"; - isThreeTier = false; - } - - let insCoCustomerTier, ownerCustomerTier, jobTier; - if (isThreeTier || (!isThreeTier && twoTierPref === "source")) { - //Insert the insurance company tier. - //Query for top level customer, the insurance company name. - insCoCustomerTier = await QueryInsuranceCo( - oauthClient, - qbo_realmId, - req, - payment.job - ); - if (!insCoCustomerTier) { - //Creating the Insurance Customer. - insCoCustomerTier = await InsertInsuranceCo( - oauthClient, - qbo_realmId, - req, - payment.job, - bodyshop - ); - } - } - - if (isThreeTier || (!isThreeTier && twoTierPref === "name")) { - //Insert the name/owner and account for whether the source should be the ins co in 3 tier.. - ownerCustomerTier = await QueryOwner( - oauthClient, - qbo_realmId, - req, - payment.job, - isThreeTier, - insCoCustomerTier - ); - //Query for the owner itself. - if (!ownerCustomerTier) { - ownerCustomerTier = await InsertOwner( - oauthClient, - qbo_realmId, - req, - payment.job, - isThreeTier, - insCoCustomerTier - ); - } - } - - //Query for the Job or Create it. - jobTier = await QueryJob( - oauthClient, - qbo_realmId, - req, - payment.job, - isThreeTier - ? ownerCustomerTier - : twoTierPref === "source" - ? insCoCustomerTier - : ownerCustomerTier - ); - - // Need to validate that the job tier is associated to the right individual? - - if (!jobTier) { - jobTier = await InsertJob( - oauthClient, - qbo_realmId, - req, - payment.job, - ownerCustomerTier || insCoCustomerTier - ); - } - - if (payment.amount > 0) { - await InsertPayment( - oauthClient, - qbo_realmId, - req, - payment, - jobTier, - bodyshop - ); - } else { - await InsertCreditMemo( - oauthClient, - qbo_realmId, - req, - payment, - jobTier, - bodyshop - ); - } - - // //No error. Mark the payment exported & insert export log. - if (elgen) { - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QBO_MARK_PAYMENT_EXPORTED, { - paymentId: payment.id, - payment: { - exportedat: moment().tz(bodyshop.timezone), - }, - logs: [ - { - bodyshopid: bodyshop.id, - paymentid: payment.id, - successful: true, - useremail: req.user.email, - }, - ], - }); - } - - ret.push({paymentid: payment.id, success: true}); - } catch (error) { - logger.log("qbo-payment-create-error", "ERROR", req.user.email, { - error: - (error && error.authResponse && error.authResponse.body) || - (error && error.message), - }); - //Add the export log error. - if (elgen) { - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.INSERT_EXPORT_LOG, { - logs: [ - { - bodyshopid: bodyshop.id, - paymentid: payment.id, - successful: false, - message: JSON.stringify([ - (error && error.authResponse && error.authResponse.body) || - (error && error.message), - ]), - useremail: req.user.email, - }, - ], - }); - } - - ret.push({ - paymentid: payment.id, - success: false, - errorMessage: - (error && error.authResponse && error.authResponse.body) || - (error && error.message), - }); - } - } - - res.status(200).json(ret); - } catch (error) { - console.log(error); - logger.log("qbo-payment-create-error", "ERROR", req.user.email, { - error: error.message, - stack: error.stack, - }); - res.status(400).json(error); + const { qbo_realmId } = response.associations[0]; + oauthClient.setToken(response.associations[0].qbo_auth); + if (!qbo_realmId) { + res.status(401).json({ error: "No company associated." }); + return; } + await refreshOauthToken(oauthClient, req); + + const { payments: paymentsToQuery, elgen } = req.body; + + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; + + logger.log("qbo-payment-create", "DEBUG", req.user.email, paymentsToQuery); + + const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.QUERY_PAYMENTS_FOR_EXPORT, { + payments: paymentsToQuery + }); + + const { payments, bodyshops } = result; + const bodyshop = bodyshops[0]; + + const ret = []; + + for (const payment of payments) { + try { + let isThreeTier = bodyshop.accountingconfig.tiers === 3; + let twoTierPref = bodyshop.accountingconfig.twotierpref; + + //Replace this with a for-each loop to check every single Job that's included in the list. + + //QB Multi AR - If it is in this scenario, overwrite whatever defaults are set since multi AR + //will always go Source => RO + if (payment.payer !== "Customer" && payment.payer !== "Insurance") { + payment.job.ins_co_nm = payment.payer; + twoTierPref = "source"; + isThreeTier = false; + } + + let insCoCustomerTier, ownerCustomerTier, jobTier; + if (isThreeTier || (!isThreeTier && twoTierPref === "source")) { + //Insert the insurance company tier. + //Query for top level customer, the insurance company name. + insCoCustomerTier = await QueryInsuranceCo(oauthClient, qbo_realmId, req, payment.job); + if (!insCoCustomerTier) { + //Creating the Insurance Customer. + insCoCustomerTier = await InsertInsuranceCo(oauthClient, qbo_realmId, req, payment.job, bodyshop); + } + } + + if (isThreeTier || (!isThreeTier && twoTierPref === "name")) { + //Insert the name/owner and account for whether the source should be the ins co in 3 tier.. + ownerCustomerTier = await QueryOwner( + oauthClient, + qbo_realmId, + req, + payment.job, + isThreeTier, + insCoCustomerTier + ); + //Query for the owner itself. + if (!ownerCustomerTier) { + ownerCustomerTier = await InsertOwner( + oauthClient, + qbo_realmId, + req, + payment.job, + isThreeTier, + insCoCustomerTier + ); + } + } + + //Query for the Job or Create it. + jobTier = await QueryJob( + oauthClient, + qbo_realmId, + req, + payment.job, + isThreeTier ? ownerCustomerTier : twoTierPref === "source" ? insCoCustomerTier : ownerCustomerTier + ); + + // Need to validate that the job tier is associated to the right individual? + + if (!jobTier) { + jobTier = await InsertJob(oauthClient, qbo_realmId, req, payment.job, ownerCustomerTier || insCoCustomerTier); + } + + if (payment.amount > 0) { + await InsertPayment(oauthClient, qbo_realmId, req, payment, jobTier, bodyshop); + } else { + await InsertCreditMemo(oauthClient, qbo_realmId, req, payment, jobTier, bodyshop); + } + + // //No error. Mark the payment exported & insert export log. + if (elgen) { + const result = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.QBO_MARK_PAYMENT_EXPORTED, { + paymentId: payment.id, + payment: { + exportedat: moment().tz(bodyshop.timezone) + }, + logs: [ + { + bodyshopid: bodyshop.id, + paymentid: payment.id, + successful: true, + useremail: req.user.email + } + ] + }); + } + + ret.push({ paymentid: payment.id, success: true }); + } catch (error) { + logger.log("qbo-payment-create-error", "ERROR", req.user.email, { + error: (error && error.authResponse && error.authResponse.body) || (error && error.message) + }); + //Add the export log error. + if (elgen) { + const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.INSERT_EXPORT_LOG, { + logs: [ + { + bodyshopid: bodyshop.id, + paymentid: payment.id, + successful: false, + message: JSON.stringify([ + (error && error.authResponse && error.authResponse.body) || (error && error.message) + ]), + useremail: req.user.email + } + ] + }); + } + + ret.push({ + paymentid: payment.id, + success: false, + errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message) + }); + } + } + + res.status(200).json(ret); + } catch (error) { + console.log(error); + logger.log("qbo-payment-create-error", "ERROR", req.user.email, { + error: error.message, + stack: error.stack + }); + res.status(400).json(error); + } }; -async function InsertPayment( +async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef, bodyshop) { + const { paymentMethods, invoices } = await QueryMetaData( oauthClient, qbo_realmId, req, - payment, - parentRef, - bodyshop -) { - const {paymentMethods, invoices} = await QueryMetaData( - oauthClient, - qbo_realmId, - req, - payment.job.ro_number, - false, - parentRef - ); + payment.job.ro_number, + false, + parentRef + ); - if (invoices && invoices.length !== 1) { - throw new Error( - `More than 1 invoice with DocNumber ${payment.job.ro_number} found.` - ); - } + if (invoices && invoices.length !== 1) { + throw new Error(`More than 1 invoice with DocNumber ${payment.job.ro_number} found.`); + } - const paymentQbo = { - CustomerRef: { - value: parentRef.Id, - }, - TxnDate: moment(payment.date) //.tz(bodyshop.timezone) - .format("YYYY-MM-DD"), - //DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"), - DocNumber: payment.paymentnum, - TotalAmt: Dinero({ - amount: Math.round(payment.amount * 100), - }).toFormat(DineroQbFormat), - PaymentMethodRef: { - value: paymentMethods[payment.type], - }, - PaymentRefNum: payment.transactionid, - ...(invoices && invoices.length === 1 && invoices[0] - ? { - Line: [ - { - Amount: Dinero({ - amount: Math.round(payment.amount * 100), - }).toFormat(DineroQbFormat), - LinkedTxn: [ - { - TxnId: invoices[0].Id, - TxnType: "Invoice", - }, - ], - }, - ], + const paymentQbo = { + CustomerRef: { + value: parentRef.Id + }, + TxnDate: moment(payment.date) //.tz(bodyshop.timezone) + .format("YYYY-MM-DD"), + //DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"), + DocNumber: payment.paymentnum, + TotalAmt: Dinero({ + amount: Math.round(payment.amount * 100) + }).toFormat(DineroQbFormat), + PaymentMethodRef: { + value: paymentMethods[payment.type] + }, + PaymentRefNum: payment.transactionid, + ...(invoices && invoices.length === 1 && invoices[0] + ? { + Line: [ + { + Amount: Dinero({ + amount: Math.round(payment.amount * 100) + }).toFormat(DineroQbFormat), + LinkedTxn: [ + { + TxnId: invoices[0].Id, + TxnType: "Invoice" + } + ] } - : {}), - }; - logger.log("qbo-payments-objectlog", "DEBUG", req.user.email, payment.id, { - paymentQbo, + ] + } + : {}) + }; + logger.log("qbo-payments-objectlog", "DEBUG", req.user.email, payment.id, { + paymentQbo + }); + try { + const result = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "payment"), + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(paymentQbo) }); - try { - const result = await oauthClient.makeApiCall({ - url: urlBuilder(qbo_realmId, "payment"), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(paymentQbo), - }); - setNewRefreshToken(req.user.email, result); - return result && result.Bill; - } catch (error) { - logger.log("qbo-payables-error", "DEBUG", req.user.email, payment.id, { - error: error && error.message, - method: "InsertPayment", - }); - throw error; - } + setNewRefreshToken(req.user.email, result); + return result && result.Bill; + } catch (error) { + logger.log("qbo-payables-error", "DEBUG", req.user.email, payment.id, { + error: error && error.message, + method: "InsertPayment" + }); + throw error; + } } -async function QueryMetaData( - oauthClient, - qbo_realmId, - req, - ro_number, - isCreditMemo, - parentTierRef -) { - const invoice = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * From Invoice where DocNumber like '${ro_number}%'` - ), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); +async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditMemo, parentTierRef) { + const invoice = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "query", `select * From Invoice where DocNumber like '${ro_number}%'`), + method: "POST", + headers: { + "Content-Type": "application/json" + } + }); - const paymentMethods = await oauthClient.makeApiCall({ - url: urlBuilder(qbo_realmId, "query", `select * From PaymentMethod`), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); - setNewRefreshToken(req.user.email, paymentMethods); + const paymentMethods = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "query", `select * From PaymentMethod`), + method: "POST", + headers: { + "Content-Type": "application/json" + } + }); + setNewRefreshToken(req.user.email, paymentMethods); - // const classes = await oauthClient.makeApiCall({ - // url: urlBuilder(qbo_realmId, "query", `select * From Class`), - // method: "POST", - // headers: { - // "Content-Type": "application/json", - // }, - // }); + // const classes = await oauthClient.makeApiCall({ + // url: urlBuilder(qbo_realmId, "query", `select * From Class`), + // method: "POST", + // headers: { + // "Content-Type": "application/json", + // }, + // }); - const paymentMethodMapping = {}; + const paymentMethodMapping = {}; - paymentMethods.json && + paymentMethods.json && paymentMethods.json.QueryResponse && paymentMethods.json.QueryResponse.PaymentMethod && paymentMethods.json.QueryResponse.PaymentMethod.forEach((t) => { - paymentMethodMapping[t.Name] = t.Id; + paymentMethodMapping[t.Name] = t.Id; }); - // const accountMapping = {}; + // const accountMapping = {}; - // accounts.json && - // accounts.json.QueryResponse && - // accounts.json.QueryResponse.Account.forEach((t) => { - // accountMapping[t.Name] = t.Id; - // }); + // accounts.json && + // accounts.json.QueryResponse && + // accounts.json.QueryResponse.Account.forEach((t) => { + // accountMapping[t.Name] = t.Id; + // }); - // const classMapping = {}; - // classes.json && - // classes.json.QueryResponse && - // classes.json.QueryResponse.Class.forEach((t) => { - // accountMapping[t.Name] = t.Id; - // }); - let ret = {}; + // const classMapping = {}; + // classes.json && + // classes.json.QueryResponse && + // classes.json.QueryResponse.Class.forEach((t) => { + // accountMapping[t.Name] = t.Id; + // }); + let ret = {}; - if (isCreditMemo) { - const taxCodes = await oauthClient.makeApiCall({ - url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); - const items = await oauthClient.makeApiCall({ - url: urlBuilder(qbo_realmId, "query", `select * From Item`), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); - setNewRefreshToken(req.user.email, items); + if (isCreditMemo) { + const taxCodes = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`), + method: "POST", + headers: { + "Content-Type": "application/json" + } + }); + const items = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "query", `select * From Item`), + method: "POST", + headers: { + "Content-Type": "application/json" + } + }); + setNewRefreshToken(req.user.email, items); - const itemMapping = {}; + const itemMapping = {}; - items.json && - items.json.QueryResponse && - items.json.QueryResponse.Item && - items.json.QueryResponse.Item.forEach((t) => { - itemMapping[t.Name] = t.Id; - }); - const taxCodeMapping = {}; + items.json && + items.json.QueryResponse && + items.json.QueryResponse.Item && + items.json.QueryResponse.Item.forEach((t) => { + itemMapping[t.Name] = t.Id; + }); + const taxCodeMapping = {}; - taxCodes.json && - taxCodes.json.QueryResponse && - taxCodes.json.QueryResponse.TaxCode && - taxCodes.json.QueryResponse.TaxCode.forEach((t) => { - taxCodeMapping[t.Name] = t.Id; - }); - ret = { - ...ret, - items: itemMapping, - taxCodes: taxCodeMapping, - }; - } - - return { - ...ret, - paymentMethods: paymentMethodMapping, - invoices: - invoice.json && - invoice.json.QueryResponse && - invoice.json.QueryResponse.Invoice && - (parentTierRef - ? [ - invoice.json.QueryResponse.Invoice.find( - (x) => x.CustomerRef.value === parentTierRef.Id - ), - ] - : [invoice.json.QueryResponse.Invoice[0]]), + taxCodes.json && + taxCodes.json.QueryResponse && + taxCodes.json.QueryResponse.TaxCode && + taxCodes.json.QueryResponse.TaxCode.forEach((t) => { + taxCodeMapping[t.Name] = t.Id; + }); + ret = { + ...ret, + items: itemMapping, + taxCodes: taxCodeMapping }; + } + + return { + ...ret, + paymentMethods: paymentMethodMapping, + invoices: + invoice.json && + invoice.json.QueryResponse && + invoice.json.QueryResponse.Invoice && + (parentTierRef + ? [invoice.json.QueryResponse.Invoice.find((x) => x.CustomerRef.value === parentTierRef.Id)] + : [invoice.json.QueryResponse.Invoice[0]]) + }; } -async function InsertCreditMemo( +async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRef, bodyshop) { + const { paymentMethods, invoices, items, taxCodes } = await QueryMetaData( oauthClient, qbo_realmId, req, - payment, - parentRef, - bodyshop -) { - const {paymentMethods, invoices, items, taxCodes} = await QueryMetaData( - oauthClient, - qbo_realmId, - req, - payment.job.ro_number, - true, - parentRef - ); + payment.job.ro_number, + true, + parentRef + ); - if (invoices && invoices.length !== 1) { - throw new Error( - `More than 1 invoice with DocNumber ${payment.ro_number} found.` - ); - } + if (invoices && invoices.length !== 1) { + throw new Error(`More than 1 invoice with DocNumber ${payment.ro_number} found.`); + } - const paymentQbo = { - CustomerRef: { - value: parentRef.Id, - }, - TxnDate: moment(payment.date) - //.tz(bodyshop.timezone) - .format("YYYY-MM-DD"), - DocNumber: payment.paymentnum, - ...(invoices && invoices[0] - ? {InvoiceRef: {value: invoices[0].Id}} - : {}), - PaymentRefNum: payment.transactionid, - Line: [ - { - DetailType: "SalesItemLineDetail", - Amount: Dinero({amount: Math.round(payment.amount * -100)}).toFormat( - DineroQbFormat - ), - SalesItemLineDetail: { - ItemRef: { - value: - items[ - payment.job.bodyshop.md_responsibility_centers.refund - .accountitem - ], - }, - Qty: 1, - TaxCodeRef: { - value: - taxCodes[ - findTaxCode( - { - local: false, - federal: false, - state: false, - }, - payment.job.bodyshop.md_responsibility_centers.sales_tax_codes - ) - ], - }, - }, - }, - ], - }; - logger.log("qbo-payments-objectlog", "DEBUG", req.user.email, payment.id, { - paymentQbo, + const paymentQbo = { + CustomerRef: { + value: parentRef.Id + }, + TxnDate: moment(payment.date) + //.tz(bodyshop.timezone) + .format("YYYY-MM-DD"), + DocNumber: payment.paymentnum, + ...(invoices && invoices[0] ? { InvoiceRef: { value: invoices[0].Id } } : {}), + PaymentRefNum: payment.transactionid, + Line: [ + { + DetailType: "SalesItemLineDetail", + Amount: Dinero({ amount: Math.round(payment.amount * -100) }).toFormat(DineroQbFormat), + SalesItemLineDetail: { + ItemRef: { + value: items[payment.job.bodyshop.md_responsibility_centers.refund.accountitem] + }, + Qty: 1, + TaxCodeRef: { + value: + taxCodes[ + findTaxCode( + { + local: false, + federal: false, + state: false + }, + payment.job.bodyshop.md_responsibility_centers.sales_tax_codes + ) + ] + } + } + } + ] + }; + logger.log("qbo-payments-objectlog", "DEBUG", req.user.email, payment.id, { + paymentQbo + }); + try { + const result = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "creditmemo"), + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(paymentQbo) }); - try { - const result = await oauthClient.makeApiCall({ - url: urlBuilder(qbo_realmId, "creditmemo"), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(paymentQbo), - }); - setNewRefreshToken(req.user.email, result); - return result && result.Bill; - } catch (error) { - logger.log("qbo-payables-error", "DEBUG", req.user.email, payment.id, { - error: error && error.message, - method: "InsertCreditMemo", - }); - throw error; - } + setNewRefreshToken(req.user.email, result); + return result && result.Bill; + } catch (error) { + logger.log("qbo-payables-error", "DEBUG", req.user.email, payment.id, { + error: error && error.message, + method: "InsertCreditMemo" + }); + throw error; + } } diff --git a/server/accounting/qbo/qbo-receivables.js b/server/accounting/qbo/qbo-receivables.js index 9090694a1..11ba20bc5 100644 --- a/server/accounting/qbo/qbo-receivables.js +++ b/server/accounting/qbo/qbo-receivables.js @@ -3,835 +3,695 @@ const StandardizeName = require("./qbo").StandardizeName; const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const logger = require("../../utils/logger"); const apiGqlClient = require("../../graphql-client/graphql-client").client; const queries = require("../../graphql-client/queries"); -const { - refresh: refreshOauthToken, - setNewRefreshToken, -} = require("./qbo-callback"); +const { refresh: refreshOauthToken, setNewRefreshToken } = require("./qbo-callback"); const OAuthClient = require("intuit-oauth"); const CreateInvoiceLines = require("../qb-receivables-lines").default; const moment = require("moment-timezone"); const GraphQLClient = require("graphql-request").GraphQLClient; -const {generateOwnerTier} = require("../qbxml/qbxml-utils"); -const {createMultiQbPayerLines} = require("../qb-receivables-lines"); +const { generateOwnerTier } = require("../qbxml/qbxml-utils"); +const { createMultiQbPayerLines } = require("../qb-receivables-lines"); exports.default = async (req, res) => { - const oauthClient = new OAuthClient({ - clientId: process.env.QBO_CLIENT_ID, - clientSecret: process.env.QBO_SECRET, - environment: - process.env.NODE_ENV === "production" ? "production" : "sandbox", - redirectUri: process.env.QBO_REDIRECT_URI, - logging: true, + const oauthClient = new OAuthClient({ + clientId: process.env.QBO_CLIENT_ID, + clientSecret: process.env.QBO_SECRET, + environment: process.env.NODE_ENV === "production" ? "production" : "sandbox", + redirectUri: process.env.QBO_REDIRECT_URI, + logging: true + }); + try { + //Fetch the API Access Tokens & Set them for the session. + const response = await apiGqlClient.request(queries.GET_QBO_AUTH, { + email: req.user.email }); - try { - //Fetch the API Access Tokens & Set them for the session. - const response = await apiGqlClient.request(queries.GET_QBO_AUTH, { - email: req.user.email, - }); - const {qbo_realmId} = response.associations[0]; - if (!qbo_realmId) { - res.status(401).json({error: "No company associated."}); - return; + const { qbo_realmId } = response.associations[0]; + if (!qbo_realmId) { + res.status(401).json({ error: "No company associated." }); + return; + } + oauthClient.setToken(response.associations[0].qbo_auth); + + await refreshOauthToken(oauthClient, req); + + const { jobIds, elgen } = req.body; + //Query Job Info + + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; + + logger.log("qbo-receivable-create", "DEBUG", req.user.email, jobIds); + + const result = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { + ids: jobIds + }); + const { jobs, bodyshops } = result; + const bodyshop = bodyshops[0]; + + const ret = []; + for (const job of jobs) { + //const job = jobs[0]; + try { + const isThreeTier = bodyshop.accountingconfig.tiers === 3; + const twoTierPref = bodyshop.accountingconfig.twotierpref; + + //Replace this with a for-each loop to check every single Job that's included in the list. + + let insCoCustomerTier, ownerCustomerTier, jobTier; + if (isThreeTier || (!isThreeTier && twoTierPref === "source")) { + //Insert the insurance company tier. + //Query for top level customer, the insurance company name. + insCoCustomerTier = await QueryInsuranceCo(oauthClient, qbo_realmId, req, job); + if (!insCoCustomerTier) { + //Creating the Insurance Customer. + insCoCustomerTier = await InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop); + } } - oauthClient.setToken(response.associations[0].qbo_auth); - await refreshOauthToken(oauthClient, req); + if (isThreeTier || (!isThreeTier && twoTierPref === "name")) { + //Insert the name/owner and account for whether the source should be the ins co in 3 tier.. + ownerCustomerTier = await QueryOwner(oauthClient, qbo_realmId, req, job, isThreeTier, insCoCustomerTier); + //Query for the owner itself. + if (!ownerCustomerTier) { + ownerCustomerTier = await InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, insCoCustomerTier); + } + } - const {jobIds, elgen} = req.body; - //Query Job Info + //Query for the Job or Create it. + jobTier = await QueryJob( + oauthClient, + qbo_realmId, + req, + job, + isThreeTier ? ownerCustomerTier : twoTierPref === "source" ? insCoCustomerTier : ownerCustomerTier + ); - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; + // Need to validate that the job tier is associated to the right individual? - logger.log("qbo-receivable-create", "DEBUG", req.user.email, jobIds); + if (!jobTier) { + jobTier = await InsertJob( + oauthClient, + qbo_realmId, + req, + job, - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { - ids: jobIds, - }); - const {jobs, bodyshops} = result; - const bodyshop = bodyshops[0]; + ownerCustomerTier || insCoCustomerTier + ); + } - const ret = []; - for (const job of jobs) { - //const job = jobs[0]; - try { - const isThreeTier = bodyshop.accountingconfig.tiers === 3; - const twoTierPref = bodyshop.accountingconfig.twotierpref; + if (!req.body.custDataOnly) { + await InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, jobTier); - //Replace this with a for-each loop to check every single Job that's included in the list. + if (job.qb_multiple_payers && job.qb_multiple_payers.length > 0) { + for (const [index, payer] of job.qb_multiple_payers.entries()) { + //do the thing. - let insCoCustomerTier, ownerCustomerTier, jobTier; - if (isThreeTier || (!isThreeTier && twoTierPref === "source")) { - //Insert the insurance company tier. - //Query for top level customer, the insurance company name. - insCoCustomerTier = await QueryInsuranceCo( - oauthClient, - qbo_realmId, - req, - job - ); - if (!insCoCustomerTier) { - //Creating the Insurance Customer. - insCoCustomerTier = await InsertInsuranceCo( - oauthClient, - qbo_realmId, - req, - job, - bodyshop - ); - } - } + //Create the source level. + let insCoCustomerTier, ownerCustomerTier, jobTier; - if (isThreeTier || (!isThreeTier && twoTierPref === "name")) { - //Insert the name/owner and account for whether the source should be the ins co in 3 tier.. - ownerCustomerTier = await QueryOwner( - oauthClient, - qbo_realmId, - req, - job, - isThreeTier, - insCoCustomerTier - ); - //Query for the owner itself. - if (!ownerCustomerTier) { - ownerCustomerTier = await InsertOwner( - oauthClient, - qbo_realmId, - req, - job, - isThreeTier, - insCoCustomerTier - ); - } - } - - //Query for the Job or Create it. - jobTier = await QueryJob( - oauthClient, - qbo_realmId, - req, - job, - isThreeTier - ? ownerCustomerTier - : twoTierPref === "source" - ? insCoCustomerTier - : ownerCustomerTier + //Insert the insurance company tier. + //Query for top level customer, the insurance company name. + insCoCustomerTier = await QueryInsuranceCo(oauthClient, qbo_realmId, req, { + ...job, + ins_co_nm: payer.name + }); + if (!insCoCustomerTier) { + //Creating the Insurance Customer. + insCoCustomerTier = await InsertInsuranceCo( + oauthClient, + qbo_realmId, + req, + { ...job, ins_co_nm: payer.name }, + bodyshop ); + } + //Query for the Job or Create it. + jobTier = await QueryJob(oauthClient, qbo_realmId, req, job, insCoCustomerTier); + // Need to validate that the job tier is associated to the right individual? + if (!jobTier) { + jobTier = await InsertJob(oauthClient, qbo_realmId, req, job, insCoCustomerTier); + } - // Need to validate that the job tier is associated to the right individual? + //Create the RO level - if (!jobTier) { - jobTier = await InsertJob( - oauthClient, - qbo_realmId, - req, - job, - - ownerCustomerTier || insCoCustomerTier - ); - } - - if (!req.body.custDataOnly) { - await InsertInvoice( - oauthClient, - qbo_realmId, - req, - job, - bodyshop, - jobTier - ); - - if (job.qb_multiple_payers && job.qb_multiple_payers.length > 0) { - for (const [index, payer] of job.qb_multiple_payers.entries()) { - //do the thing. - - //Create the source level. - let insCoCustomerTier, ownerCustomerTier, jobTier; - - //Insert the insurance company tier. - //Query for top level customer, the insurance company name. - insCoCustomerTier = await QueryInsuranceCo( - oauthClient, - qbo_realmId, - req, - {...job, ins_co_nm: payer.name} - ); - if (!insCoCustomerTier) { - //Creating the Insurance Customer. - insCoCustomerTier = await InsertInsuranceCo( - oauthClient, - qbo_realmId, - req, - {...job, ins_co_nm: payer.name}, - bodyshop - ); - } - //Query for the Job or Create it. - jobTier = await QueryJob( - oauthClient, - qbo_realmId, - req, - job, - insCoCustomerTier - ); - // Need to validate that the job tier is associated to the right individual? - if (!jobTier) { - jobTier = await InsertJob( - oauthClient, - qbo_realmId, - req, - job, - insCoCustomerTier - ); - } - - //Create the RO level - - await InsertInvoiceMultiPayerInvoice( - oauthClient, - qbo_realmId, - req, - job, - bodyshop, - jobTier, - payer, - `-${index + 1}` - ); - } - } - - // //No error. Mark the job exported & insert export log. - if (elgen) { - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QBO_MARK_JOB_EXPORTED, { - jobId: job.id, - job: { - status: - bodyshop.md_ro_statuses.default_exported || "Exported*", - date_exported: moment().tz(bodyshop.timezone), - }, - logs: [ - { - bodyshopid: bodyshop.id, - jobid: job.id, - successful: true, - useremail: req.user.email, - }, - ], - }); - } - } - ret.push({jobid: job.id, success: true}); - } catch (error) { - ret.push({ - jobid: job.id, - success: false, - errorMessage: - (error && error.authResponse && error.authResponse.body) || - (error && error.message), - }); - console.log(error); - logger.log("qbo-receivable-create-error", "ERROR", req.user.email, { - error: error.message, - stack: error.stack, - }); - //Add the export log error. - if (elgen) { - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.INSERT_EXPORT_LOG, { - logs: [ - { - bodyshopid: bodyshop.id, - jobid: job.id, - successful: false, - message: JSON.stringify([ - (error && error.authResponse && error.authResponse.body) || - (error && error.message), - ]), - useremail: req.user.email, - }, - ], - }); - } + await InsertInvoiceMultiPayerInvoice( + oauthClient, + qbo_realmId, + req, + job, + bodyshop, + jobTier, + payer, + `-${index + 1}` + ); } - } + } - res.status(200).json(ret); - } catch (error) { + // //No error. Mark the job exported & insert export log. + if (elgen) { + const result = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.QBO_MARK_JOB_EXPORTED, { + jobId: job.id, + job: { + status: bodyshop.md_ro_statuses.default_exported || "Exported*", + date_exported: moment().tz(bodyshop.timezone) + }, + logs: [ + { + bodyshopid: bodyshop.id, + jobid: job.id, + successful: true, + useremail: req.user.email + } + ] + }); + } + } + ret.push({ jobid: job.id, success: true }); + } catch (error) { + ret.push({ + jobid: job.id, + success: false, + errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message) + }); console.log(error); logger.log("qbo-receivable-create-error", "ERROR", req.user.email, { - error: error.message, - stack: error.stack, + error: error.message, + stack: error.stack }); - res.status(400).json(error); + //Add the export log error. + if (elgen) { + const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.INSERT_EXPORT_LOG, { + logs: [ + { + bodyshopid: bodyshop.id, + jobid: job.id, + successful: false, + message: JSON.stringify([ + (error && error.authResponse && error.authResponse.body) || (error && error.message) + ]), + useremail: req.user.email + } + ] + }); + } + } } + + res.status(200).json(ret); + } catch (error) { + console.log(error); + logger.log("qbo-receivable-create-error", "ERROR", req.user.email, { + error: error.message, + stack: error.stack + }); + res.status(400).json(error); + } }; async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) { - try { - const result = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * From Customer where DisplayName = '${StandardizeName( - job.ins_co_nm.trim() - )}' and Active = true` - ), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); - setNewRefreshToken(req.user.email, result); - return ( - result.json && - result.json.QueryResponse && - result.json.QueryResponse.Customer && - result.json.QueryResponse.Customer[0] - ); - } catch (error) { - logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { - error, - method: "QueryInsuranceCo", - }); - throw error; - } + try { + const result = await oauthClient.makeApiCall({ + url: urlBuilder( + qbo_realmId, + "query", + `select * From Customer where DisplayName = '${StandardizeName(job.ins_co_nm.trim())}' and Active = true` + ), + method: "POST", + headers: { + "Content-Type": "application/json" + } + }); + setNewRefreshToken(req.user.email, result); + return ( + result.json && + result.json.QueryResponse && + result.json.QueryResponse.Customer && + result.json.QueryResponse.Customer[0] + ); + } catch (error) { + logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { + error, + method: "QueryInsuranceCo" + }); + throw error; + } } exports.QueryInsuranceCo = QueryInsuranceCo; async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) { - const insCo = bodyshop.md_ins_cos.find((i) => i.name === job.ins_co_nm); + const insCo = bodyshop.md_ins_cos.find((i) => i.name === job.ins_co_nm); - if (!insCo) { - throw new Error( - `Insurance Company '${job.ins_co_nm}' not found in shop configuration. Please make sure it exists or change the insurance company name on the job to one that exists.` - ); - return; - } - const Customer = { - DisplayName: job.ins_co_nm.trim(), - BillWithParent: true, - BillAddr: { - City: job.ownr_city, - Line1: insCo.street1, - Line2: insCo.street2, - PostalCode: insCo.zip, - CountrySubDivisionCode: insCo.state, - }, - }; - try { - const result = await oauthClient.makeApiCall({ - url: urlBuilder(qbo_realmId, "customer"), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(Customer), - }); - setNewRefreshToken(req.user.email, result); - return result && result.json.Customer; - } catch (error) { - logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { - error, - method: "InsertInsuranceCo", - }); - throw error; + if (!insCo) { + throw new Error( + `Insurance Company '${job.ins_co_nm}' not found in shop configuration. Please make sure it exists or change the insurance company name on the job to one that exists.` + ); + return; + } + const Customer = { + DisplayName: job.ins_co_nm.trim(), + BillWithParent: true, + BillAddr: { + City: job.ownr_city, + Line1: insCo.street1, + Line2: insCo.street2, + PostalCode: insCo.zip, + CountrySubDivisionCode: insCo.state } + }; + try { + const result = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "customer"), + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(Customer) + }); + setNewRefreshToken(req.user.email, result); + return result && result.json.Customer; + } catch (error) { + logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { + error, + method: "InsertInsuranceCo" + }); + throw error; + } } exports.InsertInsuranceCo = InsertInsuranceCo; -async function QueryOwner( - oauthClient, - qbo_realmId, - req, - job, - isThreeTier, - parentTierRef -) { - const ownerName = generateOwnerTier(job, true, null); - const result = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * From Customer where DisplayName = '${StandardizeName( - ownerName - )}' and Active = true` - ), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); - setNewRefreshToken(req.user.email, result); - return ( - result.json && - result.json.QueryResponse && - result.json.QueryResponse.Customer && - result.json.QueryResponse.Customer.find( - (x) => x.ParentRef?.value === parentTierRef?.Id - ) - ); +async function QueryOwner(oauthClient, qbo_realmId, req, job, isThreeTier, parentTierRef) { + const ownerName = generateOwnerTier(job, true, null); + const result = await oauthClient.makeApiCall({ + url: urlBuilder( + qbo_realmId, + "query", + `select * From Customer where DisplayName = '${StandardizeName(ownerName)}' and Active = true` + ), + method: "POST", + headers: { + "Content-Type": "application/json" + } + }); + setNewRefreshToken(req.user.email, result); + return ( + result.json && + result.json.QueryResponse && + result.json.QueryResponse.Customer && + result.json.QueryResponse.Customer.find((x) => x.ParentRef?.value === parentTierRef?.Id) + ); } exports.QueryOwner = QueryOwner; -async function InsertOwner( - oauthClient, - qbo_realmId, - req, - job, - isThreeTier, - parentTierRef -) { - const ownerName = generateOwnerTier(job, true, null); - const Customer = { - DisplayName: ownerName, - BillWithParent: true, - BillAddr: { - City: job.ownr_city, - Line1: job.ownr_addr1, - Line2: job.ownr_addr2, - PostalCode: job.ownr_zip, - CountrySubDivisionCode: job.ownr_st, - }, - ...(isThreeTier - ? { - Job: true, - ParentRef: { - value: parentTierRef.Id, - }, - } - : {}), - }; - try { - const result = await oauthClient.makeApiCall({ - url: urlBuilder(qbo_realmId, "customer"), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(Customer), - }); - setNewRefreshToken(req.user.email, result); - return result && result.json.Customer; - } catch (error) { - logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { - error, - method: "InsertOwner", - }); - throw error; - } +async function InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, parentTierRef) { + const ownerName = generateOwnerTier(job, true, null); + const Customer = { + DisplayName: ownerName, + BillWithParent: true, + BillAddr: { + City: job.ownr_city, + Line1: job.ownr_addr1, + Line2: job.ownr_addr2, + PostalCode: job.ownr_zip, + CountrySubDivisionCode: job.ownr_st + }, + ...(isThreeTier + ? { + Job: true, + ParentRef: { + value: parentTierRef.Id + } + } + : {}) + }; + try { + const result = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "customer"), + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(Customer) + }); + setNewRefreshToken(req.user.email, result); + return result && result.json.Customer; + } catch (error) { + logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { + error, + method: "InsertOwner" + }); + throw error; + } } exports.InsertOwner = InsertOwner; async function QueryJob(oauthClient, qbo_realmId, req, job, parentTierRef) { - const result = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * From Customer where DisplayName = '${job.ro_number}' and Active = true` - ), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); - setNewRefreshToken(req.user.email, result); - return ( - result.json && - result.json.QueryResponse && - result.json.QueryResponse.Customer && - (parentTierRef - ? result.json.QueryResponse.Customer.find( - (x) => x.ParentRef.value === parentTierRef.Id - ) - : result.json.QueryResponse.Customer[0]) - ); + const result = await oauthClient.makeApiCall({ + url: urlBuilder( + qbo_realmId, + "query", + `select * From Customer where DisplayName = '${job.ro_number}' and Active = true` + ), + method: "POST", + headers: { + "Content-Type": "application/json" + } + }); + setNewRefreshToken(req.user.email, result); + return ( + result.json && + result.json.QueryResponse && + result.json.QueryResponse.Customer && + (parentTierRef + ? result.json.QueryResponse.Customer.find((x) => x.ParentRef.value === parentTierRef.Id) + : result.json.QueryResponse.Customer[0]) + ); } exports.QueryJob = QueryJob; async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) { - const Customer = { - DisplayName: job.ro_number, - BillWithParent: true, - BillAddr: { - City: job.ownr_city, - Line1: job.ownr_addr1, - Line2: job.ownr_addr2, - PostalCode: job.ownr_zip, - CountrySubDivisionCode: job.ownr_st, - }, + const Customer = { + DisplayName: job.ro_number, + BillWithParent: true, + BillAddr: { + City: job.ownr_city, + Line1: job.ownr_addr1, + Line2: job.ownr_addr2, + PostalCode: job.ownr_zip, + CountrySubDivisionCode: job.ownr_st + }, - Job: true, - ParentRef: { - value: parentTierRef.Id, - }, - }; - try { - const result = await oauthClient.makeApiCall({ - url: urlBuilder(qbo_realmId, "customer"), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(Customer), - }); - setNewRefreshToken(req.user.email, result); - return result && result.json.Customer; - } catch (error) { - logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { - error, - method: "InsertOwner", - }); - throw error; + Job: true, + ParentRef: { + value: parentTierRef.Id } + }; + try { + const result = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "customer"), + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(Customer) + }); + setNewRefreshToken(req.user.email, result); + return result && result.json.Customer; + } catch (error) { + logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { + error, + method: "InsertOwner" + }); + throw error; + } } exports.InsertJob = InsertJob; async function QueryMetaData(oauthClient, qbo_realmId, req) { - const items = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * From Item where active=true maxresults 1000` - ), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); - setNewRefreshToken(req.user.email, items); - const taxCodes = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * From TaxCode where active=true` - ), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); + const items = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "query", `select * From Item where active=true maxresults 1000`), + method: "POST", + headers: { + "Content-Type": "application/json" + } + }); + setNewRefreshToken(req.user.email, items); + const taxCodes = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "query", `select * From TaxCode where active=true`), + method: "POST", + headers: { + "Content-Type": "application/json" + } + }); - const classes = await oauthClient.makeApiCall({ - url: urlBuilder(qbo_realmId, "query", `select * From Class`), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); + const classes = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "query", `select * From Class`), + method: "POST", + headers: { + "Content-Type": "application/json" + } + }); - const taxCodeMapping = {}; + const taxCodeMapping = {}; - taxCodes.json && + taxCodes.json && taxCodes.json.QueryResponse && taxCodes.json.QueryResponse.TaxCode && taxCodes.json.QueryResponse.TaxCode.forEach((t) => { - taxCodeMapping[t.Name] = t.Id; + taxCodeMapping[t.Name] = t.Id; }); - const itemMapping = {}; + const itemMapping = {}; - items.json && + items.json && items.json.QueryResponse && items.json.QueryResponse.Item && items.json.QueryResponse.Item.forEach((t) => { - itemMapping[t.Name] = t.Id; + itemMapping[t.Name] = t.Id; }); - const classMapping = {}; - classes.json && + const classMapping = {}; + classes.json && classes.json.QueryResponse && classes.json.QueryResponse.Class && classes.json.QueryResponse.Class.forEach((t) => { - classMapping[t.Name] = t.Id; + classMapping[t.Name] = t.Id; }); - return { - items: itemMapping, - taxCodes: taxCodeMapping, - classes: classMapping, - }; + return { + items: itemMapping, + taxCodes: taxCodeMapping, + classes: classMapping + }; } -async function InsertInvoice( - oauthClient, - qbo_realmId, - req, - job, +async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, parentTierRef) { + const { items, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req); + const InvoiceLineAdd = CreateInvoiceLines({ bodyshop, - parentTierRef -) { - const {items, taxCodes, classes} = await QueryMetaData( - oauthClient, - qbo_realmId, - req - ); - const InvoiceLineAdd = CreateInvoiceLines({ - bodyshop, - jobs_by_pk: job, - qbo: true, - items, - taxCodes, - classes, - }); + jobs_by_pk: job, + qbo: true, + items, + taxCodes, + classes + }); - const invoiceObj = { - Line: InvoiceLineAdd, - TxnDate: moment(job.date_invoiced) - .tz(bodyshop.timezone) - .format("YYYY-MM-DD"), - DocNumber: job.ro_number, - ...(job.class ? {ClassRef: {value: classes[job.class]}} : {}), - CustomerMemo: { - value: `${job.clm_no ? `Claim No: ${job.clm_no}` : ``}${ - job.po_number ? `PO No: ${job.po_number}` : `` - } Vehicle:${job.v_model_yr || ""} ${job.v_make_desc || ""} ${ - job.v_model_desc || "" - } ${job.v_vin || ""} ${job.plate_no || ""} `.trim(), - }, - CustomerRef: { - value: parentTierRef.Id, - }, - ...(bodyshop.accountingconfig.qbo_departmentid && - bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && { - DepartmentRef: {value: bodyshop.accountingconfig.qbo_departmentid}, - }), - CustomField: [ - ...(bodyshop.accountingconfig.ReceivableCustomField1 - ? [ - { - DefinitionId: "1", - StringValue: - job[bodyshop.accountingconfig.ReceivableCustomField1], - Type: "StringType", - }, - ] - : []), - ...(bodyshop.accountingconfig.ReceivableCustomField2 - ? [ - { - DefinitionId: "2", - StringValue: - job[bodyshop.accountingconfig.ReceivableCustomField2], - Type: "StringType", - }, - ] - : []), - ...(bodyshop.accountingconfig.ReceivableCustomField3 - ? [ - { - DefinitionId: "3", - StringValue: - job[bodyshop.accountingconfig.ReceivableCustomField3], - Type: "StringType", - }, - ] - : []), - ], - ...(bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo && - bodyshop.accountingconfig.qbo_usa && { - TxnTaxDetail: { - TxnTaxCodeRef: { - value: - taxCodes[ - bodyshop.md_responsibility_centers.taxes.state.accountitem - ], - }, - }, - }), + const invoiceObj = { + Line: InvoiceLineAdd, + TxnDate: moment(job.date_invoiced).tz(bodyshop.timezone).format("YYYY-MM-DD"), + DocNumber: job.ro_number, + ...(job.class ? { ClassRef: { value: classes[job.class] } } : {}), + CustomerMemo: { + value: `${job.clm_no ? `Claim No: ${job.clm_no}` : ``}${ + job.po_number ? `PO No: ${job.po_number}` : `` + } Vehicle:${job.v_model_yr || ""} ${job.v_make_desc || ""} ${ + job.v_model_desc || "" + } ${job.v_vin || ""} ${job.plate_no || ""} `.trim() + }, + CustomerRef: { + value: parentTierRef.Id + }, + ...(bodyshop.accountingconfig.qbo_departmentid && + bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && { + DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } + }), + CustomField: [ + ...(bodyshop.accountingconfig.ReceivableCustomField1 + ? [ + { + DefinitionId: "1", + StringValue: job[bodyshop.accountingconfig.ReceivableCustomField1], + Type: "StringType" + } + ] + : []), + ...(bodyshop.accountingconfig.ReceivableCustomField2 + ? [ + { + DefinitionId: "2", + StringValue: job[bodyshop.accountingconfig.ReceivableCustomField2], + Type: "StringType" + } + ] + : []), + ...(bodyshop.accountingconfig.ReceivableCustomField3 + ? [ + { + DefinitionId: "3", + StringValue: job[bodyshop.accountingconfig.ReceivableCustomField3], + Type: "StringType" + } + ] + : []) + ], + ...(bodyshop.accountingconfig && + bodyshop.accountingconfig.qbo && + bodyshop.accountingconfig.qbo_usa && { + TxnTaxDetail: { + TxnTaxCodeRef: { + value: taxCodes[bodyshop.md_responsibility_centers.taxes.state.accountitem] + } + } + }), - ...(bodyshop.accountingconfig.printlater - ? {PrintStatus: "NeedToPrint"} - : {}), - ...(bodyshop.accountingconfig.emaillater && job.ownr_ea - ? {EmailStatus: "NeedToSend"} - : {}), - BillAddr: { - Line3: `${job.ownr_city || ""}, ${job.ownr_st || ""} ${ - job.ownr_zip || "" - }`.trim(), - Line2: job.ownr_addr1 || "", - Line1: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${ - job.ownr_co_nm || "" - }`, - }, - }; - - logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, { - invoiceObj, - }); - - try { - const result = await oauthClient.makeApiCall({ - url: urlBuilder(qbo_realmId, "invoice"), - method: "POST", - - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(invoiceObj), - }); - setNewRefreshToken(req.user.email, result); - return result && result.json && result.json.Invoice; - } catch (error) { - logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { - error, - method: "InsertOwner", - }); - throw error; + ...(bodyshop.accountingconfig.printlater ? { PrintStatus: "NeedToPrint" } : {}), + ...(bodyshop.accountingconfig.emaillater && job.ownr_ea ? { EmailStatus: "NeedToSend" } : {}), + BillAddr: { + Line3: `${job.ownr_city || ""}, ${job.ownr_st || ""} ${job.ownr_zip || ""}`.trim(), + Line2: job.ownr_addr1 || "", + Line1: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${job.ownr_co_nm || ""}` } + }; + + logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, { + invoiceObj + }); + + try { + const result = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "invoice"), + method: "POST", + + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(invoiceObj) + }); + setNewRefreshToken(req.user.email, result); + return result && result.json && result.json.Invoice; + } catch (error) { + logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { + error, + method: "InsertOwner" + }); + throw error; + } } async function InsertInvoiceMultiPayerInvoice( - oauthClient, - qbo_realmId, - req, - job, + oauthClient, + qbo_realmId, + req, + job, + bodyshop, + parentTierRef, + payer, + suffix +) { + const { items, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req); + const InvoiceLineAdd = createMultiQbPayerLines({ bodyshop, - parentTierRef, + jobs_by_pk: job, + qbo: true, + items, + taxCodes, + classes, payer, suffix -) { - const {items, taxCodes, classes} = await QueryMetaData( - oauthClient, - qbo_realmId, - req - ); - const InvoiceLineAdd = createMultiQbPayerLines({ - bodyshop, - jobs_by_pk: job, - qbo: true, - items, - taxCodes, - classes, - payer, - suffix, - }); + }); - const invoiceObj = { - Line: InvoiceLineAdd, - TxnDate: moment(job.date_invoiced) - .tz(bodyshop.timezone) - .format("YYYY-MM-DD"), - DocNumber: job.ro_number + suffix, - ...(job.class ? {ClassRef: {value: classes[job.class]}} : {}), - CustomerMemo: { - value: `${job.clm_no ? `Claim No: ${job.clm_no}` : ``}${ - job.po_number ? `PO No: ${job.po_number}` : `` - } Vehicle:${job.v_model_yr || ""} ${job.v_make_desc || ""} ${ - job.v_model_desc || "" - } ${job.v_vin || ""} ${job.plate_no || ""} `.trim(), - }, - CustomerRef: { - value: parentTierRef.Id, - }, - ...(bodyshop.accountingconfig.qbo_departmentid && - bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && { - DepartmentRef: {value: bodyshop.accountingconfig.qbo_departmentid}, - }), - CustomField: [ - ...(bodyshop.accountingconfig.ReceivableCustomField1 - ? [ - { - DefinitionId: "1", - StringValue: - job[bodyshop.accountingconfig.ReceivableCustomField1], - Type: "StringType", - }, - ] - : []), - ...(bodyshop.accountingconfig.ReceivableCustomField2 - ? [ - { - DefinitionId: "2", - StringValue: - job[bodyshop.accountingconfig.ReceivableCustomField2], - Type: "StringType", - }, - ] - : []), - ...(bodyshop.accountingconfig.ReceivableCustomField3 - ? [ - { - DefinitionId: "3", - StringValue: - job[bodyshop.accountingconfig.ReceivableCustomField3], - Type: "StringType", - }, - ] - : []), - ], - ...(bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo && - bodyshop.accountingconfig.qbo_usa && - bodyshop.region_config.includes("CA_") && { - TxnTaxDetail: { - TxnTaxCodeRef: { - value: - taxCodes[ - bodyshop.md_responsibility_centers.taxes.state.accountitem - ], - }, - }, - }), + const invoiceObj = { + Line: InvoiceLineAdd, + TxnDate: moment(job.date_invoiced).tz(bodyshop.timezone).format("YYYY-MM-DD"), + DocNumber: job.ro_number + suffix, + ...(job.class ? { ClassRef: { value: classes[job.class] } } : {}), + CustomerMemo: { + value: `${job.clm_no ? `Claim No: ${job.clm_no}` : ``}${ + job.po_number ? `PO No: ${job.po_number}` : `` + } Vehicle:${job.v_model_yr || ""} ${job.v_make_desc || ""} ${ + job.v_model_desc || "" + } ${job.v_vin || ""} ${job.plate_no || ""} `.trim() + }, + CustomerRef: { + value: parentTierRef.Id + }, + ...(bodyshop.accountingconfig.qbo_departmentid && + bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && { + DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } + }), + CustomField: [ + ...(bodyshop.accountingconfig.ReceivableCustomField1 + ? [ + { + DefinitionId: "1", + StringValue: job[bodyshop.accountingconfig.ReceivableCustomField1], + Type: "StringType" + } + ] + : []), + ...(bodyshop.accountingconfig.ReceivableCustomField2 + ? [ + { + DefinitionId: "2", + StringValue: job[bodyshop.accountingconfig.ReceivableCustomField2], + Type: "StringType" + } + ] + : []), + ...(bodyshop.accountingconfig.ReceivableCustomField3 + ? [ + { + DefinitionId: "3", + StringValue: job[bodyshop.accountingconfig.ReceivableCustomField3], + Type: "StringType" + } + ] + : []) + ], + ...(bodyshop.accountingconfig && + bodyshop.accountingconfig.qbo && + bodyshop.accountingconfig.qbo_usa && + bodyshop.region_config.includes("CA_") && { + TxnTaxDetail: { + TxnTaxCodeRef: { + value: taxCodes[bodyshop.md_responsibility_centers.taxes.state.accountitem] + } + } + }), - ...(bodyshop.accountingconfig.printlater - ? {PrintStatus: "NeedToPrint"} - : {}), - ...(bodyshop.accountingconfig.emaillater && job.ownr_ea - ? {EmailStatus: "NeedToSend"} - : {}), - BillAddr: { - Line3: `${job.ownr_city || ""}, ${job.ownr_st || ""} ${ - job.ownr_zip || "" - }`.trim(), - Line2: job.ownr_addr1 || "", - Line1: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${ - job.ownr_co_nm || "" - }`, - }, - }; - - logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, { - invoiceObj, - }); - - try { - const result = await oauthClient.makeApiCall({ - url: urlBuilder(qbo_realmId, "invoice"), - method: "POST", - - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(invoiceObj), - }); - setNewRefreshToken(req.user.email, result); - return result && result.json && result.json.Invoice; - } catch (error) { - logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { - error, - method: "InsertOwner", - }); - throw error; + ...(bodyshop.accountingconfig.printlater ? { PrintStatus: "NeedToPrint" } : {}), + ...(bodyshop.accountingconfig.emaillater && job.ownr_ea ? { EmailStatus: "NeedToSend" } : {}), + BillAddr: { + Line3: `${job.ownr_city || ""}, ${job.ownr_st || ""} ${job.ownr_zip || ""}`.trim(), + Line2: job.ownr_addr1 || "", + Line1: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${job.ownr_co_nm || ""}` } + }; + + logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, { + invoiceObj + }); + + try { + const result = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "invoice"), + method: "POST", + + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(invoiceObj) + }); + setNewRefreshToken(req.user.email, result); + return result && result.json && result.json.Invoice; + } catch (error) { + logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { + error, + method: "InsertOwner" + }); + throw error; + } } diff --git a/server/accounting/qbo/qbo.js b/server/accounting/qbo/qbo.js index c93bb837f..74cc3c742 100644 --- a/server/accounting/qbo/qbo.js +++ b/server/accounting/qbo/qbo.js @@ -1,21 +1,16 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); function urlBuilder(realmId, object, query = null) { - return `https://${ - process.env.NODE_ENV === "production" ? "" : "sandbox-" - }quickbooks.api.intuit.com/v3/company/${realmId}/${object}${ - query ? `?query=${encodeURIComponent(query)}` : "" - }`; + return `https://${ + process.env.NODE_ENV === "production" ? "" : "sandbox-" + }quickbooks.api.intuit.com/v3/company/${realmId}/${object}${query ? `?query=${encodeURIComponent(query)}` : ""}`; } function StandardizeName(str) { - return str.replace(new RegExp(/'/g), "\\'"); + return str.replace(new RegExp(/'/g), "\\'"); } exports.urlBuilder = urlBuilder; diff --git a/server/accounting/qbxml/qbxml-payables.js b/server/accounting/qbxml/qbxml-payables.js index 380b94d27..9ffe4513f 100644 --- a/server/accounting/qbxml/qbxml-payables.js +++ b/server/accounting/qbxml/qbxml-payables.js @@ -6,154 +6,131 @@ const Dinero = require("dinero.js"); const builder = require("xmlbuilder2"); const QbXmlUtils = require("./qbxml-utils"); const moment = require("moment-timezone"); -const logger = require('../../utils/logger'); +const logger = require("../../utils/logger"); const InstanceManager = require("../../utils/instanceMgr").default; require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); exports.default = async (req, res) => { - const {bills: billsToQuery} = req.body; + const { bills: billsToQuery } = req.body; - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; - try { - logger.log( - "qbxml-payable-create", - "DEBUG", - req.user.email, - req.body.billsToQuery - ); + try { + logger.log("qbxml-payable-create", "DEBUG", req.user.email, req.body.billsToQuery); - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QUERY_BILLS_FOR_PAYABLES_EXPORT, { - bills: billsToQuery, - }); - const {bills, bodyshops} = result; - const bodyshop = bodyshops[0]; + const result = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.QUERY_BILLS_FOR_PAYABLES_EXPORT, { + bills: billsToQuery + }); + const { bills, bodyshops } = result; + const bodyshop = bodyshops[0]; - const QbXmlToExecute = []; - bills.map((i) => { - QbXmlToExecute.push({ - id: i.id, - okStatusCodes: ["0"], - qbxml: generateBill(i, bodyshop), - }); - }); + const QbXmlToExecute = []; + bills.map((i) => { + QbXmlToExecute.push({ + id: i.id, + okStatusCodes: ["0"], + qbxml: generateBill(i, bodyshop) + }); + }); - //For each invoice. - res.status(200).json(QbXmlToExecute); - } catch (error) { - logger.log( - "qbxml-payable-error", - "ERROR", - req.user.email, - req.body.billsToQuery, - {error: error.message, stack: error.stack} - ); - res.status(400).send(JSON.stringify(error)); - } + //For each invoice. + res.status(200).json(QbXmlToExecute); + } catch (error) { + logger.log("qbxml-payable-error", "ERROR", req.user.email, req.body.billsToQuery, { + error: error.message, + stack: error.stack + }); + res.status(400).send(JSON.stringify(error)); + } }; const generateBill = (bill, bodyshop) => { - const billQbxmlObj = { - QBXML: { - QBXMLMsgsRq: { - "@onError": "continueOnError", - [`${bill.is_credit_memo ? "VendorCreditAddRq" : "BillAddRq"}`]: { - [`${bill.is_credit_memo ? "VendorCreditAdd" : "BillAdd"}`]: { - VendorRef: { - FullName: bill.vendor.name, - }, - TxnDate: moment(bill.date) - //.tz(bill.job.bodyshop.timezone) - .format("YYYY-MM-DD"), - ...(!bill.is_credit_memo && - bill.vendor.due_date && { - DueDate: moment(bill.date) - // .tz(bill.job.bodyshop.timezone) - .add(bill.vendor.due_date, "days") - .format("YYYY-MM-DD"), - }), - RefNumber: bill.invoice_number, - Memo: `RO ${bill.job.ro_number || ""}`, - ExpenseLineAdd: bill.billlines.map((il) => - generateBillLine( - il, - bodyshop.md_responsibility_centers, - bill.job.class - ) - ), - }, - }, + const billQbxmlObj = { + QBXML: { + QBXMLMsgsRq: { + "@onError": "continueOnError", + [`${bill.is_credit_memo ? "VendorCreditAddRq" : "BillAddRq"}`]: { + [`${bill.is_credit_memo ? "VendorCreditAdd" : "BillAdd"}`]: { + VendorRef: { + FullName: bill.vendor.name }, - }, - }; + TxnDate: moment(bill.date) + //.tz(bill.job.bodyshop.timezone) + .format("YYYY-MM-DD"), + ...(!bill.is_credit_memo && + bill.vendor.due_date && { + DueDate: moment(bill.date) + // .tz(bill.job.bodyshop.timezone) + .add(bill.vendor.due_date, "days") + .format("YYYY-MM-DD") + }), + RefNumber: bill.invoice_number, + Memo: `RO ${bill.job.ro_number || ""}`, + ExpenseLineAdd: bill.billlines.map((il) => + generateBillLine(il, bodyshop.md_responsibility_centers, bill.job.class) + ) + } + } + } + } + }; - var billQbxml_partial = builder - .create(billQbxmlObj, { - version: "1.30", - encoding: "UTF-8", - headless: true, - }) - .end({pretty: true}); + var billQbxml_partial = builder + .create(billQbxmlObj, { + version: "1.30", + encoding: "UTF-8", + headless: true + }) + .end({ pretty: true }); - const billQbxml_Full = QbXmlUtils.addQbxmlHeader(billQbxml_partial); + const billQbxml_Full = QbXmlUtils.addQbxmlHeader(billQbxml_partial); - return billQbxml_Full; + return billQbxml_Full; }; const generateBillLine = (billLine, responsibilityCenters, jobClass) => { - return { - AccountRef: { - FullName: responsibilityCenters.costs.find( - (c) => c.name === billLine.cost_center - ).accountname, - }, - Amount: Dinero({ - amount: Math.round(billLine.actual_cost * 100), - }) - .multiply(billLine.quantity || 1) - .toFormat(DineroQbFormat), - ...(jobClass ? {ClassRef: {FullName: jobClass}} : {}), - ...InstanceManager({imex:{ - SalesTaxCodeRef: { - FullName: findTaxCode( - billLine, - responsibilityCenters.sales_tax_codes - ), - }, - } }) - }; + return { + AccountRef: { + FullName: responsibilityCenters.costs.find((c) => c.name === billLine.cost_center).accountname + }, + Amount: Dinero({ + amount: Math.round(billLine.actual_cost * 100) + }) + .multiply(billLine.quantity || 1) + .toFormat(DineroQbFormat), + ...(jobClass ? { ClassRef: { FullName: jobClass } } : {}), + ...InstanceManager({ + imex: { + SalesTaxCodeRef: { + FullName: findTaxCode(billLine, responsibilityCenters.sales_tax_codes) + } + } + }) + }; }; const findTaxCode = (billLine, taxcode) => { - const { - applicable_taxes: {local, state, federal}, - } = - billLine.applicable_taxes === null - ? { - ...billLine, - applicable_taxes: {local: false, state: false, federal: false}, - } - : billLine; - const t = taxcode.filter( - (t) => - !!t.local === !!local && - !!t.state === !!state && - !!t.federal === !!federal - ); - if (t.length === 1) { - return t[0].code; - } else if (t.length > 1) { - return "Multiple Tax Codes Match"; - } else { - return "No Tax Code Matches"; - } + const { + applicable_taxes: { local, state, federal } + } = + billLine.applicable_taxes === null + ? { + ...billLine, + applicable_taxes: { local: false, state: false, federal: false } + } + : billLine; + const t = taxcode.filter((t) => !!t.local === !!local && !!t.state === !!state && !!t.federal === !!federal); + if (t.length === 1) { + return t[0].code; + } else if (t.length > 1) { + return "Multiple Tax Codes Match"; + } else { + return "No Tax Code Matches"; + } }; diff --git a/server/accounting/qbxml/qbxml-payments.js b/server/accounting/qbxml/qbxml-payments.js index 677c80691..cdc08d2e4 100644 --- a/server/accounting/qbxml/qbxml-payments.js +++ b/server/accounting/qbxml/qbxml-payments.js @@ -6,205 +6,166 @@ const builder = require("xmlbuilder2"); const moment = require("moment-timezone"); const QbXmlUtils = require("./qbxml-utils"); const QbxmlReceivables = require("./qbxml-receivables"); -const logger = require('../../utils/logger'); +const logger = require("../../utils/logger"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); -const {generateJobTier, generateOwnerTier, generateSourceTier} = QbXmlUtils; +const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils; exports.default = async (req, res) => { - const {payments: paymentsToQuery} = req.body; + const { payments: paymentsToQuery } = req.body; - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; - try { - logger.log( - "qbxml-payments-create", - "DEBUG", - req.user.email, - req.body.paymentsToQuery, - null - ); + try { + logger.log("qbxml-payments-create", "DEBUG", req.user.email, req.body.paymentsToQuery, null); - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QUERY_PAYMENTS_FOR_EXPORT, { - payments: paymentsToQuery, - }); - const {payments, bodyshops} = result; - const bodyshop = bodyshops[0]; - const isThreeTier = bodyshop.accountingconfig.tiers === 3; - const twoTierPref = bodyshop.accountingconfig.twotierpref; + const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.QUERY_PAYMENTS_FOR_EXPORT, { + payments: paymentsToQuery + }); + const { payments, bodyshops } = result; + const bodyshop = bodyshops[0]; + const isThreeTier = bodyshop.accountingconfig.tiers === 3; + const twoTierPref = bodyshop.accountingconfig.twotierpref; - const QbXmlToExecute = []; - payments.map((i) => { - if (isThreeTier) { - QbXmlToExecute.push({ - id: i.id, - okStatusCodes: ["0", "3100"], - qbxml: QbxmlReceivables.generateSourceCustomerQbxml(i.job, bodyshop), // Create the source customer. - }); - } - - QbXmlToExecute.push({ - id: i.id, - okStatusCodes: ["0", "3100"], - qbxml: QbxmlReceivables.generateJobQbxml( - i.job, - bodyshop, - isThreeTier, - 2, - twoTierPref - ), - }); - - QbXmlToExecute.push({ - id: i.id, - okStatusCodes: ["0", "3100"], - qbxml: QbxmlReceivables.generateJobQbxml( - i.job, - bodyshop, - isThreeTier, - 3, - twoTierPref - ), - }); - - QbXmlToExecute.push({ - id: i.id, - okStatusCodes: ["0"], - qbxml: generatePayment(i, isThreeTier, twoTierPref, bodyshop), - }); + const QbXmlToExecute = []; + payments.map((i) => { + if (isThreeTier) { + QbXmlToExecute.push({ + id: i.id, + okStatusCodes: ["0", "3100"], + qbxml: QbxmlReceivables.generateSourceCustomerQbxml(i.job, bodyshop) // Create the source customer. }); + } - res.status(200).json(QbXmlToExecute); - } catch (error) { - logger.log( - "qbxml-payments-error", - "error", - req.user.email, - req.body.paymentsToQuery, - {error: error.message, stack: error.stack} - ); - res.status(400).send(JSON.stringify(error)); - } + QbXmlToExecute.push({ + id: i.id, + okStatusCodes: ["0", "3100"], + qbxml: QbxmlReceivables.generateJobQbxml(i.job, bodyshop, isThreeTier, 2, twoTierPref) + }); + + QbXmlToExecute.push({ + id: i.id, + okStatusCodes: ["0", "3100"], + qbxml: QbxmlReceivables.generateJobQbxml(i.job, bodyshop, isThreeTier, 3, twoTierPref) + }); + + QbXmlToExecute.push({ + id: i.id, + okStatusCodes: ["0"], + qbxml: generatePayment(i, isThreeTier, twoTierPref, bodyshop) + }); + }); + + res.status(200).json(QbXmlToExecute); + } catch (error) { + logger.log("qbxml-payments-error", "error", req.user.email, req.body.paymentsToQuery, { + error: error.message, + stack: error.stack + }); + res.status(400).send(JSON.stringify(error)); + } }; const generatePayment = (payment, isThreeTier, twoTierPref, bodyshop) => { - let paymentQbxmlObj; - if (payment.amount > 0) { - paymentQbxmlObj = { - QBXML: { - QBXMLMsgsRq: { - "@onError": "continueOnError", - ReceivePaymentAddRq: { - ReceivePaymentAdd: { - CustomerRef: { - FullName: (payment.job.bodyshop.accountingconfig.tiers === 3 - ? `${generateSourceTier(payment.job)}:${generateOwnerTier( - payment.job, - isThreeTier, - twoTierPref - )}:${generateJobTier(payment.job)}` - : `${generateOwnerTier( - payment.job, - isThreeTier, - twoTierPref - )}:${generateJobTier(payment.job)}` - ).trim(), - }, - ARAccountRef: { - FullName: - payment.job.bodyshop.md_responsibility_centers.ar.accountname, - }, - TxnDate: moment(payment.date) - // .tz(bodyshop.timezone) - .format("YYYY-MM-DD"), //Trim String - RefNumber: payment.paymentnum || payment.transactionid, - TotalAmount: Dinero({ - amount: Math.round(payment.amount * 100), - }).toFormat(DineroQbFormat), - PaymentMethodRef: { - FullName: payment.type, - }, - Memo: `RO ${payment.job.ro_number || ""} OWNER ${ - payment.job.ownr_fn || "" - } ${payment.job.ownr_ln || ""} ${payment.job.ownr_co_nm || ""} ${ - payment.stripeid || "" - } ${payment.payer ? ` PAID BY ${payment.payer}` : ""}`, - IsAutoApply: true, - }, - }, - }, - }, - }; - } else { - paymentQbxmlObj = { - QBXML: { - QBXMLMsgsRq: { - "@onError": "continueOnError", - CreditMemoAddRq: { - CreditMemoAdd: { - CustomerRef: { - FullName: (payment.job.bodyshop.accountingconfig.tiers === 3 - ? `${generateSourceTier(payment.job)}:${generateOwnerTier( - payment.job, - isThreeTier, - twoTierPref - )}:${generateJobTier(payment.job)}` - : `${generateOwnerTier( - payment.job, - isThreeTier, - twoTierPref - )}:${generateJobTier(payment.job)}` - ).trim(), - }, - ARAccountRef: { - FullName: - payment.job.bodyshop.md_responsibility_centers.ar.accountname, - }, - TxnDate: moment(payment.date) - //.tz(bodyshop.timezone) - .format("YYYY-MM-DD"), //Trim String - RefNumber: - payment.paymentnum || payment.stripeid || payment.transactionid, + let paymentQbxmlObj; + if (payment.amount > 0) { + paymentQbxmlObj = { + QBXML: { + QBXMLMsgsRq: { + "@onError": "continueOnError", + ReceivePaymentAddRq: { + ReceivePaymentAdd: { + CustomerRef: { + FullName: (payment.job.bodyshop.accountingconfig.tiers === 3 + ? `${generateSourceTier(payment.job)}:${generateOwnerTier( + payment.job, + isThreeTier, + twoTierPref + )}:${generateJobTier(payment.job)}` + : `${generateOwnerTier(payment.job, isThreeTier, twoTierPref)}:${generateJobTier(payment.job)}` + ).trim() + }, + ARAccountRef: { + FullName: payment.job.bodyshop.md_responsibility_centers.ar.accountname + }, + TxnDate: moment(payment.date) + // .tz(bodyshop.timezone) + .format("YYYY-MM-DD"), //Trim String + RefNumber: payment.paymentnum || payment.transactionid, + TotalAmount: Dinero({ + amount: Math.round(payment.amount * 100) + }).toFormat(DineroQbFormat), + PaymentMethodRef: { + FullName: payment.type + }, + Memo: `RO ${payment.job.ro_number || ""} OWNER ${ + payment.job.ownr_fn || "" + } ${payment.job.ownr_ln || ""} ${payment.job.ownr_co_nm || ""} ${ + payment.stripeid || "" + } ${payment.payer ? ` PAID BY ${payment.payer}` : ""}`, + IsAutoApply: true + } + } + } + } + }; + } else { + paymentQbxmlObj = { + QBXML: { + QBXMLMsgsRq: { + "@onError": "continueOnError", + CreditMemoAddRq: { + CreditMemoAdd: { + CustomerRef: { + FullName: (payment.job.bodyshop.accountingconfig.tiers === 3 + ? `${generateSourceTier(payment.job)}:${generateOwnerTier( + payment.job, + isThreeTier, + twoTierPref + )}:${generateJobTier(payment.job)}` + : `${generateOwnerTier(payment.job, isThreeTier, twoTierPref)}:${generateJobTier(payment.job)}` + ).trim() + }, + ARAccountRef: { + FullName: payment.job.bodyshop.md_responsibility_centers.ar.accountname + }, + TxnDate: moment(payment.date) + //.tz(bodyshop.timezone) + .format("YYYY-MM-DD"), //Trim String + RefNumber: payment.paymentnum || payment.stripeid || payment.transactionid, - CreditMemoLineAdd: [ - { - ItemRef: { - FullName: - payment.job.bodyshop.md_responsibility_centers.refund - .accountitem, - }, - Desc: payment.memo, - Amount: Dinero({ - amount: Math.round(payment.amount * 100 * -1), - }).toFormat(DineroQbFormat), - SalesTaxCodeRef: {FullName: "E"}, - }, - ], - }, - }, - }, - }, - }; - } + CreditMemoLineAdd: [ + { + ItemRef: { + FullName: payment.job.bodyshop.md_responsibility_centers.refund.accountitem + }, + Desc: payment.memo, + Amount: Dinero({ + amount: Math.round(payment.amount * 100 * -1) + }).toFormat(DineroQbFormat), + SalesTaxCodeRef: { FullName: "E" } + } + ] + } + } + } + } + }; + } - var paymentQbxmlPartial = builder - .create(paymentQbxmlObj, { - version: "1.30", - encoding: "UTF-8", - headless: true, - }) - .end({pretty: true}); + var paymentQbxmlPartial = builder + .create(paymentQbxmlObj, { + version: "1.30", + encoding: "UTF-8", + headless: true + }) + .end({ pretty: true }); - const paymentQbxmlFull = QbXmlUtils.addQbxmlHeader(paymentQbxmlPartial); + const paymentQbxmlFull = QbXmlUtils.addQbxmlHeader(paymentQbxmlPartial); - return paymentQbxmlFull; + return paymentQbxmlFull; }; diff --git a/server/accounting/qbxml/qbxml-receivables.js b/server/accounting/qbxml/qbxml-receivables.js index f0322b0a3..0e8a4f20d 100644 --- a/server/accounting/qbxml/qbxml-receivables.js +++ b/server/accounting/qbxml/qbxml-receivables.js @@ -6,310 +6,255 @@ const moment = require("moment-timezone"); const builder = require("xmlbuilder2"); const QbXmlUtils = require("./qbxml-utils"); const CreateInvoiceLines = require("../qb-receivables-lines").default; -const logger = require('../../utils/logger'); -const InstanceManager = require('../../utils/instanceMgr').default; +const logger = require("../../utils/logger"); +const InstanceManager = require("../../utils/instanceMgr").default; require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); Dinero.globalRoundingMode = "HALF_EVEN"; -const {generateJobTier, generateOwnerTier, generateSourceTier} = QbXmlUtils; +const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils; exports.default = async (req, res) => { - const {jobIds} = req.body; + const { jobIds } = req.body; - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; - try { - logger.log( - "qbxml-receivables-create", - "DEBUG", - req.user.email, - req.body.jobIds, - null - ); + try { + logger.log("qbxml-receivables-create", "DEBUG", req.user.email, req.body.jobIds, null); - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, {ids: jobIds}); - const {jobs, bodyshops} = result; - const QbXmlToExecute = []; + const result = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { ids: jobIds }); + const { jobs, bodyshops } = result; + const QbXmlToExecute = []; - const bodyshop = bodyshops[0]; + const bodyshop = bodyshops[0]; - jobs.map((jobs_by_pk) => { - //Is this a two tier, or 3 tier setup? - const isThreeTier = bodyshop.accountingconfig.tiers === 3; - const twoTierPref = bodyshop.accountingconfig.twotierpref; + jobs.map((jobs_by_pk) => { + //Is this a two tier, or 3 tier setup? + const isThreeTier = bodyshop.accountingconfig.tiers === 3; + const twoTierPref = bodyshop.accountingconfig.twotierpref; - //This is the Insurance Company tier IF 3 tier is selected. - if (isThreeTier) { - QbXmlToExecute.push({ - id: jobs_by_pk.id, - okStatusCodes: ["0", "3100"], - qbxml: generateSourceCustomerQbxml(jobs_by_pk, bodyshop), // Create the source customer. - }); - } - - //If 3 tier, this should be the customer. - //If 2 tier, this should be based on the pref. - QbXmlToExecute.push({ - id: jobs_by_pk.id, - okStatusCodes: ["0", "3100"], - qbxml: generateJobQbxml( - jobs_by_pk, - bodyshop, - isThreeTier, - 2, - twoTierPref - ), - }); - - //This is always going to be the job. - QbXmlToExecute.push({ - id: jobs_by_pk.id, - okStatusCodes: ["0", "3100"], - qbxml: generateJobQbxml( - jobs_by_pk, - bodyshop, - isThreeTier, - 3, - twoTierPref - ), - }); - - if (!req.body.custDataOnly) { - //Generate the actual invoice. - QbXmlToExecute.push({ - id: jobs_by_pk.id, - okStatusCodes: ["0"], - qbxml: generateInvoiceQbxml( - jobs_by_pk, - bodyshop, - isThreeTier, - twoTierPref - ), - }); - } + //This is the Insurance Company tier IF 3 tier is selected. + if (isThreeTier) { + QbXmlToExecute.push({ + id: jobs_by_pk.id, + okStatusCodes: ["0", "3100"], + qbxml: generateSourceCustomerQbxml(jobs_by_pk, bodyshop) // Create the source customer. }); + } - res.status(200).json(QbXmlToExecute); - } catch (error) { - logger.log( - "qbxml-receivables-error", - "error", - req.user.email, - req.body.jobIds, - {error: error.message, stack: error.stack} - ); - res.status(400).send(JSON.stringify(error)); - } + //If 3 tier, this should be the customer. + //If 2 tier, this should be based on the pref. + QbXmlToExecute.push({ + id: jobs_by_pk.id, + okStatusCodes: ["0", "3100"], + qbxml: generateJobQbxml(jobs_by_pk, bodyshop, isThreeTier, 2, twoTierPref) + }); + + //This is always going to be the job. + QbXmlToExecute.push({ + id: jobs_by_pk.id, + okStatusCodes: ["0", "3100"], + qbxml: generateJobQbxml(jobs_by_pk, bodyshop, isThreeTier, 3, twoTierPref) + }); + + if (!req.body.custDataOnly) { + //Generate the actual invoice. + QbXmlToExecute.push({ + id: jobs_by_pk.id, + okStatusCodes: ["0"], + qbxml: generateInvoiceQbxml(jobs_by_pk, bodyshop, isThreeTier, twoTierPref) + }); + } + }); + + res.status(200).json(QbXmlToExecute); + } catch (error) { + logger.log("qbxml-receivables-error", "error", req.user.email, req.body.jobIds, { + error: error.message, + stack: error.stack + }); + res.status(400).send(JSON.stringify(error)); + } }; const generateSourceCustomerQbxml = (jobs_by_pk, bodyshop) => { - const customerQbxmlObj = { - QBXML: { - QBXMLMsgsRq: { - "@onError": "continueOnError", - CustomerAddRq: { - CustomerAdd: { - Name: jobs_by_pk.ins_co_nm.trim(), - // BillAddress: { - // Addr1: jobs_by_pk.ownr_addr1, - // Addr2: jobs_by_pk.ownr_addr2, - // City: jobs_by_pk.ownr_city, - // State: jobs_by_pk.ownr_st, - // PostalCode: jobs_by_pk.ownr_zip, - // }, - }, - }, - }, - }, - }; + const customerQbxmlObj = { + QBXML: { + QBXMLMsgsRq: { + "@onError": "continueOnError", + CustomerAddRq: { + CustomerAdd: { + Name: jobs_by_pk.ins_co_nm.trim() + // BillAddress: { + // Addr1: jobs_by_pk.ownr_addr1, + // Addr2: jobs_by_pk.ownr_addr2, + // City: jobs_by_pk.ownr_city, + // State: jobs_by_pk.ownr_st, + // PostalCode: jobs_by_pk.ownr_zip, + // }, + } + } + } + } + }; - var customerQbxml_partial = builder - .create(customerQbxmlObj, { - version: "1.30", - encoding: "UTF-8", - headless: true, - }) - .end({pretty: true}); + var customerQbxml_partial = builder + .create(customerQbxmlObj, { + version: "1.30", + encoding: "UTF-8", + headless: true + }) + .end({ pretty: true }); - const customerQbxml_Full = QbXmlUtils.addQbxmlHeader(customerQbxml_partial); + const customerQbxml_Full = QbXmlUtils.addQbxmlHeader(customerQbxml_partial); - return customerQbxml_Full; + return customerQbxml_Full; }; exports.generateSourceCustomerQbxml = generateSourceCustomerQbxml; -const generateJobQbxml = ( - jobs_by_pk, - bodyshop, - isThreeTier, - tierLevel, - twoTierPref -) => { - let Name; - let ParentRefName; +const generateJobQbxml = (jobs_by_pk, bodyshop, isThreeTier, tierLevel, twoTierPref) => { + let Name; + let ParentRefName; - if (tierLevel === 2) { - Name = generateOwnerTier(jobs_by_pk, isThreeTier, twoTierPref); - ParentRefName = isThreeTier ? generateSourceTier(jobs_by_pk) : null; - } else if (tierLevel === 3) { - Name = generateJobTier(jobs_by_pk); - ParentRefName = isThreeTier - ? `${generateSourceTier(jobs_by_pk)}:${generateOwnerTier(jobs_by_pk)}` - : generateOwnerTier(jobs_by_pk, isThreeTier, twoTierPref); + if (tierLevel === 2) { + Name = generateOwnerTier(jobs_by_pk, isThreeTier, twoTierPref); + ParentRefName = isThreeTier ? generateSourceTier(jobs_by_pk) : null; + } else if (tierLevel === 3) { + Name = generateJobTier(jobs_by_pk); + ParentRefName = isThreeTier + ? `${generateSourceTier(jobs_by_pk)}:${generateOwnerTier(jobs_by_pk)}` + : generateOwnerTier(jobs_by_pk, isThreeTier, twoTierPref); + } + + const jobQbxmlObj = { + QBXML: { + QBXMLMsgsRq: { + "@onError": "continueOnError", + + CustomerAddRq: { + CustomerAdd: { + Name: Name, + ParentRef: ParentRefName + ? { + FullName: ParentRefName + } + : null, + ...(tierLevel === 3 + ? { + BillAddress: { + Addr1: jobs_by_pk.ownr_addr1, + Addr2: jobs_by_pk.ownr_addr2, + City: jobs_by_pk.ownr_city, + State: jobs_by_pk.ownr_st, + PostalCode: jobs_by_pk.ownr_zip + }, + ShipAddress: { + Addr1: jobs_by_pk.ownr_addr1, + Addr2: jobs_by_pk.ownr_addr2, + City: jobs_by_pk.ownr_city, + State: jobs_by_pk.ownr_st, + PostalCode: jobs_by_pk.ownr_zip + }, + Email: jobs_by_pk.ownr_ea + } + : {}) + } + } + } } + }; - const jobQbxmlObj = { - QBXML: { - QBXMLMsgsRq: { - "@onError": "continueOnError", + var jobQbxml_partial = builder + .create(jobQbxmlObj, { + version: "1.30", + encoding: "UTF-8", + headless: true + }) + .end({ pretty: true }); - CustomerAddRq: { - CustomerAdd: { - Name: Name, - ParentRef: ParentRefName - ? { - FullName: ParentRefName, - } - : null, - ...(tierLevel === 3 - ? { - BillAddress: { - Addr1: jobs_by_pk.ownr_addr1, - Addr2: jobs_by_pk.ownr_addr2, - City: jobs_by_pk.ownr_city, - State: jobs_by_pk.ownr_st, - PostalCode: jobs_by_pk.ownr_zip, - }, - ShipAddress: { - Addr1: jobs_by_pk.ownr_addr1, - Addr2: jobs_by_pk.ownr_addr2, - City: jobs_by_pk.ownr_city, - State: jobs_by_pk.ownr_st, - PostalCode: jobs_by_pk.ownr_zip, - }, - Email: jobs_by_pk.ownr_ea, - } - : {}), - }, - }, - }, - }, - }; + const jobQbxml_Full = QbXmlUtils.addQbxmlHeader(jobQbxml_partial); - var jobQbxml_partial = builder - .create(jobQbxmlObj, { - version: "1.30", - encoding: "UTF-8", - headless: true, - }) - .end({pretty: true}); - - const jobQbxml_Full = QbXmlUtils.addQbxmlHeader(jobQbxml_partial); - - return jobQbxml_Full; + return jobQbxml_Full; }; exports.generateJobQbxml = generateJobQbxml; -const generateInvoiceQbxml = ( - jobs_by_pk, - bodyshop, - isThreeTier, - twoTierPref -) => { - //Build the Invoice XML file. +const generateInvoiceQbxml = (jobs_by_pk, bodyshop, isThreeTier, twoTierPref) => { + //Build the Invoice XML file. - const InvoiceLineAdd = CreateInvoiceLines({bodyshop, jobs_by_pk}); + const InvoiceLineAdd = CreateInvoiceLines({ bodyshop, jobs_by_pk }); - const invoiceQbxmlObj = { - QBXML: { - QBXMLMsgsRq: { - "@onError": "stopOnError", - InvoiceAddRq: { - InvoiceAdd: { - CustomerRef: { - FullName: (bodyshop.accountingconfig.tiers === 3 - ? `${generateSourceTier(jobs_by_pk)}:${generateOwnerTier( - jobs_by_pk - )}:${generateJobTier(jobs_by_pk)}` - : `${generateOwnerTier( - jobs_by_pk, - isThreeTier, - twoTierPref - )}:${generateJobTier(jobs_by_pk)}` - ).trim(), - }, - ...(jobs_by_pk.class - ? {ClassRef: {FullName: jobs_by_pk.class}} - : {}), - - ARAccountRef: { - FullName: bodyshop.md_responsibility_centers.ar.accountname, - }, - TxnDate: moment(jobs_by_pk.date_invoiced) - .tz(bodyshop.timezone) - .format("YYYY-MM-DD"), - RefNumber: jobs_by_pk.ro_number, - BillAddress: { - Addr1: jobs_by_pk.ownr_co_nm - ? jobs_by_pk.ownr_co_nm.substring(0, 30).trim() - : `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}` - .substring(0, 30) - .trim()}`, - Addr2: jobs_by_pk.ownr_addr1, - Addr3: jobs_by_pk.ownr_addr2, - City: jobs_by_pk.ownr_city, - State: jobs_by_pk.ownr_st, - PostalCode: jobs_by_pk.ownr_zip, - }, - ShipAddress: { - Addr1: jobs_by_pk.ownr_co_nm - ? jobs_by_pk.ownr_co_nm.substring(0, 30).trim() - : `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}` - .substring(0, 30) - .trim()}`, - Addr2: jobs_by_pk.ownr_addr1, - Addr3: jobs_by_pk.ownr_addr2, - City: jobs_by_pk.ownr_city, - State: jobs_by_pk.ownr_st, - PostalCode: jobs_by_pk.ownr_zip, - }, - PONumber: jobs_by_pk.clm_no, - ...InstanceManager({rome: { - ItemSalesTaxRef: { - FullName: - bodyshop.md_responsibility_centers.taxes.invoiceexemptcode, - }, - }}), - IsToBePrinted: bodyshop.accountingconfig.printlater, - ...(jobs_by_pk.ownr_ea - ? {IsToBeEmailed: bodyshop.accountingconfig.emaillater} - : {}), - - InvoiceLineAdd: InvoiceLineAdd, - }, - }, + const invoiceQbxmlObj = { + QBXML: { + QBXMLMsgsRq: { + "@onError": "stopOnError", + InvoiceAddRq: { + InvoiceAdd: { + CustomerRef: { + FullName: (bodyshop.accountingconfig.tiers === 3 + ? `${generateSourceTier(jobs_by_pk)}:${generateOwnerTier(jobs_by_pk)}:${generateJobTier(jobs_by_pk)}` + : `${generateOwnerTier(jobs_by_pk, isThreeTier, twoTierPref)}:${generateJobTier(jobs_by_pk)}` + ).trim() }, - }, - }; + ...(jobs_by_pk.class ? { ClassRef: { FullName: jobs_by_pk.class } } : {}), - var invoiceQbxml_partial = builder - .create(invoiceQbxmlObj, { - version: "1.30", - encoding: "UTF-8", - headless: true, - }) - .end({pretty: true}); + ARAccountRef: { + FullName: bodyshop.md_responsibility_centers.ar.accountname + }, + TxnDate: moment(jobs_by_pk.date_invoiced).tz(bodyshop.timezone).format("YYYY-MM-DD"), + RefNumber: jobs_by_pk.ro_number, + BillAddress: { + Addr1: jobs_by_pk.ownr_co_nm + ? jobs_by_pk.ownr_co_nm.substring(0, 30).trim() + : `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`.substring(0, 30).trim()}`, + Addr2: jobs_by_pk.ownr_addr1, + Addr3: jobs_by_pk.ownr_addr2, + City: jobs_by_pk.ownr_city, + State: jobs_by_pk.ownr_st, + PostalCode: jobs_by_pk.ownr_zip + }, + ShipAddress: { + Addr1: jobs_by_pk.ownr_co_nm + ? jobs_by_pk.ownr_co_nm.substring(0, 30).trim() + : `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`.substring(0, 30).trim()}`, + Addr2: jobs_by_pk.ownr_addr1, + Addr3: jobs_by_pk.ownr_addr2, + City: jobs_by_pk.ownr_city, + State: jobs_by_pk.ownr_st, + PostalCode: jobs_by_pk.ownr_zip + }, + PONumber: jobs_by_pk.clm_no, + ...InstanceManager({ + rome: { + ItemSalesTaxRef: { + FullName: bodyshop.md_responsibility_centers.taxes.invoiceexemptcode + } + } + }), + IsToBePrinted: bodyshop.accountingconfig.printlater, + ...(jobs_by_pk.ownr_ea ? { IsToBeEmailed: bodyshop.accountingconfig.emaillater } : {}), - const invoiceQbxml_Full = QbXmlUtils.addQbxmlHeader(invoiceQbxml_partial); + InvoiceLineAdd: InvoiceLineAdd + } + } + } + } + }; - return invoiceQbxml_Full; + var invoiceQbxml_partial = builder + .create(invoiceQbxmlObj, { + version: "1.30", + encoding: "UTF-8", + headless: true + }) + .end({ pretty: true }); + + const invoiceQbxml_Full = QbXmlUtils.addQbxmlHeader(invoiceQbxml_partial); + + return invoiceQbxml_Full; }; // const generateInvoiceLine = (job, allocation, responsibilityCenters) => { diff --git a/server/accounting/qbxml/qbxml-utils.js b/server/accounting/qbxml/qbxml-utils.js index 091a4a453..ff8fa3671 100644 --- a/server/accounting/qbxml/qbxml-utils.js +++ b/server/accounting/qbxml/qbxml-utils.js @@ -1,50 +1,46 @@ exports.addQbxmlHeader = addQbxmlHeader = (xml) => { - return ` + return ` ${xml} `; }; exports.generateSourceTier = (jobs_by_pk) => { - return jobs_by_pk.ins_co_nm && jobs_by_pk.ins_co_nm.trim().replace(":", " "); + return jobs_by_pk.ins_co_nm && jobs_by_pk.ins_co_nm.trim().replace(":", " "); }; exports.generateJobTier = (jobs_by_pk) => { - return jobs_by_pk.ro_number && jobs_by_pk.ro_number.trim().replace(":", " "); + return jobs_by_pk.ro_number && jobs_by_pk.ro_number.trim().replace(":", " "); }; exports.generateOwnerTier = (jobs_by_pk, isThreeTier, twotierpref) => { - if (isThreeTier) { - //It's always gonna be the owner now. Same as 2 tier by name - return ( - jobs_by_pk.ownr_co_nm - ? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${ - jobs_by_pk.owner.accountingid || "" - }` - : `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}` - .substring(0, 30) - .trim()} #${jobs_by_pk.owner.accountingid || ""}` - ) - .trim() - .replace(":", " "); + if (isThreeTier) { + //It's always gonna be the owner now. Same as 2 tier by name + return ( + jobs_by_pk.ownr_co_nm + ? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${jobs_by_pk.owner.accountingid || ""}` + : `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}` + .substring(0, 30) + .trim()} #${jobs_by_pk.owner.accountingid || ""}` + ) + .trim() + .replace(":", " "); + } else { + //What's the 2 tier pref? + if (twotierpref === "source") { + return this.generateSourceTier(jobs_by_pk); + //It should be the insurance co. } else { - //What's the 2 tier pref? - if (twotierpref === "source") { - return this.generateSourceTier(jobs_by_pk); - //It should be the insurance co. - } else { - //Same as 3 tier - return ( - jobs_by_pk.ownr_co_nm - ? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${ - jobs_by_pk.owner.accountingid || "" - }` - : `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}` - .substring(0, 30) - .trim()} #${jobs_by_pk.owner.accountingid || ""}` - ) - .trim() - .replace(":", " "); - } + //Same as 3 tier + return ( + jobs_by_pk.ownr_co_nm + ? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${jobs_by_pk.owner.accountingid || ""}` + : `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}` + .substring(0, 30) + .trim()} #${jobs_by_pk.owner.accountingid || ""}` + ) + .trim() + .replace(":", " "); } + } }; diff --git a/server/accounting/qbxml/qbxmlObject.json b/server/accounting/qbxml/qbxmlObject.json index 7de10d527..85d61be8c 100644 --- a/server/accounting/qbxml/qbxmlObject.json +++ b/server/accounting/qbxml/qbxmlObject.json @@ -1,8 +1,5 @@ { "id": "12345", - "okStatusCodes": [ - "0", - "31400" - ], + "okStatusCodes": ["0", "31400"], "qbxml": "the qbxml string" } diff --git a/server/admin/adminops.js b/server/admin/adminops.js index 224bfa3a9..7e8b1c676 100644 --- a/server/admin/adminops.js +++ b/server/admin/adminops.js @@ -3,23 +3,20 @@ const path = require("path"); const _ = require("lodash"); const logger = require("../utils/logger"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const client = require("../graphql-client/graphql-client").client; exports.createAssociation = async (req, res) => { - logger.log("admin-create-association", "ADMIN", req.user.email, null, { - request: req.body, - ioadmin: true, - }); - const {shopid, authlevel, useremail} = req.body; + logger.log("admin-create-association", "ADMIN", req.user.email, null, { + request: req.body, + ioadmin: true + }); + const { shopid, authlevel, useremail } = req.body; - const result = await client.request( - `mutation INSERT_ASSOCIATION($assoc: associations_insert_input!){ + const result = await client.request( + `mutation INSERT_ASSOCIATION($assoc: associations_insert_input!){ insert_associations_one(object:$assoc){ id authlevel @@ -27,55 +24,55 @@ exports.createAssociation = async (req, res) => { active } }`, - { - assoc: {shopid, authlevel, useremail, active: false}, - } - ); - res.json(result); + { + assoc: { shopid, authlevel, useremail, active: false } + } + ); + res.json(result); }; exports.createShop = async (req, res) => { - logger.log("admin-create-shop", "ADMIN", req.user.email, null, { - request: req.body, - ioadmin: true, - }); - const {bodyshop, ronum} = req.body; + logger.log("admin-create-shop", "ADMIN", req.user.email, null, { + request: req.body, + ioadmin: true + }); + const { bodyshop, ronum } = req.body; - try { - const result = await client.request( - `mutation INSERT_BODYSHOPS($bs: bodyshops_insert_input!){ + try { + const result = await client.request( + `mutation INSERT_BODYSHOPS($bs: bodyshops_insert_input!){ insert_bodyshops_one(object:$bs){ id } }`, - { - bs: { - ...bodyshop, - counters: { - data: [ - {countertype: "ronum", count: ronum}, - {countertype: "ihbnum", count: 1}, - {countertype: "paymentnum", count: 1}, - ], - }, - }, - } - ); - res.json(result); - } catch (error) { - res.status(500).json(error); - } + { + bs: { + ...bodyshop, + counters: { + data: [ + { countertype: "ronum", count: ronum }, + { countertype: "ihbnum", count: 1 }, + { countertype: "paymentnum", count: 1 } + ] + } + } + } + ); + res.json(result); + } catch (error) { + res.status(500).json(error); + } }; exports.updateCounter = async (req, res) => { - logger.log("admin-update-counter", "ADMIN", req.user.email, null, { - request: req.body, - ioadmin: true, - }); - const {id, counter} = req.body; + logger.log("admin-update-counter", "ADMIN", req.user.email, null, { + request: req.body, + ioadmin: true + }); + const { id, counter } = req.body; - try { - const result = await client.request( - `mutation UPDATE_COUNTER($id: uuid!, $counter: counters_set_input!) { + try { + const result = await client.request( + `mutation UPDATE_COUNTER($id: uuid!, $counter: counters_set_input!) { update_counters_by_pk(pk_columns: { id: $id }, _set: $counter) { id countertype @@ -83,38 +80,38 @@ exports.updateCounter = async (req, res) => { prefix } }`, - { - id, - counter, - } - ); - res.json(result); - } catch (error) { - res.status(500).json(error); - } + { + id, + counter + } + ); + res.json(result); + } catch (error) { + res.status(500).json(error); + } }; exports.updateShop = async (req, res) => { - logger.log("admin-update-shop", "ADMIN", req.user.email, null, { - request: req.body, - ioadmin: true, - }); - const {id, bodyshop} = req.body; + logger.log("admin-update-shop", "ADMIN", req.user.email, null, { + request: req.body, + ioadmin: true + }); + const { id, bodyshop } = req.body; - try { - const result = await client.request( - `mutation UPDATE_BODYSHOP($id: uuid!, $bodyshop: bodyshops_set_input!) { + try { + const result = await client.request( + `mutation UPDATE_BODYSHOP($id: uuid!, $bodyshop: bodyshops_set_input!) { update_bodyshops_by_pk(pk_columns: { id: $id }, _set: $bodyshop) { id } }`, - { - id, - bodyshop, - } - ); - res.json(result); - } catch (error) { - res.status(500).json(error); - } + { + id, + bodyshop + } + ); + res.json(result); + } catch (error) { + res.status(500).json(error); + } }; diff --git a/server/ccc/partspricechange.js b/server/ccc/partspricechange.js index 9dbc04966..ecc4227ef 100644 --- a/server/ccc/partspricechange.js +++ b/server/ccc/partspricechange.js @@ -6,40 +6,37 @@ const GraphQLClient = require("graphql-request").GraphQLClient; const moment = require("moment-timezone"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); exports.generatePpc = async (req, res) => { - const {jobid} = req.body; - const BearerToken = req.headers.authorization; - logger.log("generate-ppc", "DEBUG", req.user.email, jobid, null); + const { jobid } = req.body; + const BearerToken = req.headers.authorization; + logger.log("generate-ppc", "DEBUG", req.user.email, jobid, null); - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); - try { - const {jobs_by_pk: job} = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.GET_JOB_FOR_PPC, { - jobid: jobid, - }); - - const ReturnVal = { - ...job, - trans_type: "P", - create_dt: moment().tz(job.bodyshop.timezone).format("yyyyMMDD"), - create_tm: moment().tz(job.bodyshop.timezone).format("HHmmSS"), - incl_est: true, - joblines: job.joblines.map((jl) => ({...jl, tran_code: 2})), - }; - - res.json(ReturnVal); - } catch (error) { - res.status(400).json(error); + const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { + headers: { + Authorization: BearerToken } + }); + try { + const { jobs_by_pk: job } = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.GET_JOB_FOR_PPC, { + jobid: jobid + }); + + const ReturnVal = { + ...job, + trans_type: "P", + create_dt: moment().tz(job.bodyshop.timezone).format("yyyyMMDD"), + create_tm: moment().tz(job.bodyshop.timezone).format("HHmmSS"), + incl_est: true, + joblines: job.joblines.map((jl) => ({ ...jl, tran_code: 2 })) + }; + + res.json(ReturnVal); + } catch (error) { + res.status(400).json(error); + } }; diff --git a/server/cdk/cdk-calculate-allocations.js b/server/cdk/cdk-calculate-allocations.js index 1ec580fd4..82e686236 100644 --- a/server/cdk/cdk-calculate-allocations.js +++ b/server/cdk/cdk-calculate-allocations.js @@ -1,9 +1,6 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const GraphQLClient = require("graphql-request").GraphQLClient; @@ -12,456 +9,380 @@ const CdkBase = require("../web-sockets/web-socket"); const Dinero = require("dinero.js"); const _ = require("lodash"); -const {DiscountNotAlreadyCounted} = require("../job/job-totals"); -const InstanceManager = require('../utils/instanceMgr').default; +const { DiscountNotAlreadyCounted } = require("../job/job-totals"); +const InstanceManager = require("../utils/instanceMgr").default; exports.default = async function (socket, jobid) { - try { - CdkBase.createLogEvent( - socket, - "DEBUG", - `Received request to calculate allocations for ${jobid}` - ); - const job = await QueryJobData(socket, jobid); - const {bodyshop} = job; + try { + CdkBase.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${jobid}`); + const job = await QueryJobData(socket, jobid); + const { bodyshop } = job; - const taxAllocations = - InstanceManager({ - executeFunction:true, - deubg:true, - args: [], - imex: () => ({ - local: { - center: bodyshop.md_responsibility_centers.taxes.local.name, - sale: Dinero(job.job_totals.totals.local_tax), - cost: Dinero(), - profitCenter: bodyshop.md_responsibility_centers.taxes.local, - costCenter: bodyshop.md_responsibility_centers.taxes.local, - }, - state: { - center: bodyshop.md_responsibility_centers.taxes.state.name, - sale: Dinero(job.job_totals.totals.state_tax), - cost: Dinero(), - profitCenter: bodyshop.md_responsibility_centers.taxes.state, - costCenter: bodyshop.md_responsibility_centers.taxes.state, - }, - federal: { - center: bodyshop.md_responsibility_centers.taxes.federal.name, - sale: Dinero(job.job_totals.totals.federal_tax), - cost: Dinero(), - profitCenter: bodyshop.md_responsibility_centers.taxes.federal, - costCenter: bodyshop.md_responsibility_centers.taxes.federal, - }, - }), rome: () => ({ - tax_ty1: { - center: bodyshop.md_responsibility_centers.taxes[`tax_ty1`].name, - sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty1Tax`]), - cost: Dinero(), - profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty1`], - costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty1`], - }, - tax_ty2: { - center: bodyshop.md_responsibility_centers.taxes[`tax_ty2`].name, - sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty2Tax`]), - cost: Dinero(), - profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty2`], - costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty2`], - }, - tax_ty3: { - center: bodyshop.md_responsibility_centers.taxes[`tax_ty3`].name, - sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty3Tax`]), - cost: Dinero(), - profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty3`], - costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty3`], - }, - tax_ty4: { - center: bodyshop.md_responsibility_centers.taxes[`tax_ty4`].name, - sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty4Tax`]), - cost: Dinero(), - profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty4`], - costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty4`], - }, - tax_ty5: { - center: bodyshop.md_responsibility_centers.taxes[`tax_ty5`].name, - sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty5Tax`]), - cost: Dinero(), - profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`], - costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`], - }, - }) }) - - - //Determine if there are MAPA and MASH lines already on the estimate. - //If there are, don't do anything extra (mitchell estimate) - //Otherwise, calculate them and add them to the default MAPA and MASH centers. - let hasMapaLine = false; - let hasMashLine = false; - - const profitCenterHash = job.joblines.reduce((acc, val) => { - //Check the Parts Assignment - if (val.db_ref === "936008") { - //If either of these DB REFs change, they also need to change in job-totals/job-costing calculations. - hasMapaLine = true; - } - if (val.db_ref === "936007") { - hasMashLine = true; - } - - if (val.profitcenter_part) { - if (!acc[val.profitcenter_part]) acc[val.profitcenter_part] = Dinero(); - - let DineroAmount = Dinero({ - amount: Math.round(val.act_price * 100), - }).multiply(val.part_qty || 1); - - DineroAmount = DineroAmount.add( - ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || - (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) && - DiscountNotAlreadyCounted(val, job.joblines) - ? val.prt_dsmk_m - ? Dinero({amount: Math.round(val.prt_dsmk_m * 100)}) - : Dinero({ - amount: Math.round(val.act_price * 100), - }) - .multiply(val.part_qty || 0) - .percentage(Math.abs(val.prt_dsmk_p || 0)) - .multiply(val.prt_dsmk_p > 0 ? 1 : -1) - : Dinero() - ); - - acc[val.profitcenter_part] = - acc[val.profitcenter_part].add(DineroAmount); - } - if (val.profitcenter_labor && val.mod_lbr_ty) { - //Check the Labor Assignment. - - if (!acc[val.profitcenter_labor]) - acc[val.profitcenter_labor] = Dinero(); - - acc[val.profitcenter_labor] = acc[val.profitcenter_labor].add( - Dinero({ - amount: Math.round( - job[`rate_${val.mod_lbr_ty.toLowerCase()}`] * 100 - ), - }).multiply(val.mod_lb_hrs) - ); - } - return acc; - }, {}); - - const selectedDmsAllocationConfig = - bodyshop.md_responsibility_centers.dms_defaults.find( - (d) => d.name === job.dms_allocation - ); - CdkBase.createLogEvent( - socket, - "DEBUG", - `Using DMS Allocation ${ - selectedDmsAllocationConfig && selectedDmsAllocationConfig.name - } for cost export.` - ); - - let costCenterHash = {}; - //Check whether to skip this if PBS and using AP module. - const disablebillwip = !!bodyshop?.pbs_configuration?.disablebillwip; - - if (!disablebillwip) { - costCenterHash = job.bills.reduce((bill_acc, bill_val) => { - bill_val.billlines.map((line_val) => { - if ( - !bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] - ) - bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = - Dinero(); - - let lineDinero = Dinero({ - amount: Math.round((line_val.actual_cost || 0) * 100), - }) - .multiply(line_val.quantity) - .multiply(bill_val.is_credit_memo ? -1 : 1); - - bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = - bill_acc[ - selectedDmsAllocationConfig.costs[line_val.cost_center] - ].add(lineDinero); - return null; - }); - return bill_acc; - }, {}); + const taxAllocations = InstanceManager({ + executeFunction: true, + deubg: true, + args: [], + imex: () => ({ + local: { + center: bodyshop.md_responsibility_centers.taxes.local.name, + sale: Dinero(job.job_totals.totals.local_tax), + cost: Dinero(), + profitCenter: bodyshop.md_responsibility_centers.taxes.local, + costCenter: bodyshop.md_responsibility_centers.taxes.local + }, + state: { + center: bodyshop.md_responsibility_centers.taxes.state.name, + sale: Dinero(job.job_totals.totals.state_tax), + cost: Dinero(), + profitCenter: bodyshop.md_responsibility_centers.taxes.state, + costCenter: bodyshop.md_responsibility_centers.taxes.state + }, + federal: { + center: bodyshop.md_responsibility_centers.taxes.federal.name, + sale: Dinero(job.job_totals.totals.federal_tax), + cost: Dinero(), + profitCenter: bodyshop.md_responsibility_centers.taxes.federal, + costCenter: bodyshop.md_responsibility_centers.taxes.federal } + }), + rome: () => ({ + tax_ty1: { + center: bodyshop.md_responsibility_centers.taxes[`tax_ty1`].name, + sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty1Tax`]), + cost: Dinero(), + profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty1`], + costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty1`] + }, + tax_ty2: { + center: bodyshop.md_responsibility_centers.taxes[`tax_ty2`].name, + sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty2Tax`]), + cost: Dinero(), + profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty2`], + costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty2`] + }, + tax_ty3: { + center: bodyshop.md_responsibility_centers.taxes[`tax_ty3`].name, + sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty3Tax`]), + cost: Dinero(), + profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty3`], + costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty3`] + }, + tax_ty4: { + center: bodyshop.md_responsibility_centers.taxes[`tax_ty4`].name, + sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty4Tax`]), + cost: Dinero(), + profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty4`], + costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty4`] + }, + tax_ty5: { + center: bodyshop.md_responsibility_centers.taxes[`tax_ty5`].name, + sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty5Tax`]), + cost: Dinero(), + profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`], + costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`] + } + }) + }); - job.timetickets.forEach((ticket) => { - //Get the total amount of the ticket. - let TicketTotal = Dinero({ - amount: Math.round( - ticket.rate * - (ticket.employee && ticket.employee.flat_rate - ? ticket.productivehrs || 0 - : ticket.actualhrs || 0) * - 100 - ), - }); - //Add it to the right cost center. - if (!costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]]) - costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]] = - Dinero(); + //Determine if there are MAPA and MASH lines already on the estimate. + //If there are, don't do anything extra (mitchell estimate) + //Otherwise, calculate them and add them to the default MAPA and MASH centers. + let hasMapaLine = false; + let hasMashLine = false; - costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]] = - costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]].add( - TicketTotal - ); + const profitCenterHash = job.joblines.reduce((acc, val) => { + //Check the Parts Assignment + if (val.db_ref === "936008") { + //If either of these DB REFs change, they also need to change in job-totals/job-costing calculations. + hasMapaLine = true; + } + if (val.db_ref === "936007") { + hasMashLine = true; + } + + if (val.profitcenter_part) { + if (!acc[val.profitcenter_part]) acc[val.profitcenter_part] = Dinero(); + + let DineroAmount = Dinero({ + amount: Math.round(val.act_price * 100) + }).multiply(val.part_qty || 1); + + DineroAmount = DineroAmount.add( + ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(val, job.joblines) + ? val.prt_dsmk_m + ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(val.act_price * 100) + }) + .multiply(val.part_qty || 0) + .percentage(Math.abs(val.prt_dsmk_p || 0)) + .multiply(val.prt_dsmk_p > 0 ? 1 : -1) + : Dinero() + ); + + acc[val.profitcenter_part] = acc[val.profitcenter_part].add(DineroAmount); + } + if (val.profitcenter_labor && val.mod_lbr_ty) { + //Check the Labor Assignment. + + if (!acc[val.profitcenter_labor]) acc[val.profitcenter_labor] = Dinero(); + + acc[val.profitcenter_labor] = acc[val.profitcenter_labor].add( + Dinero({ + amount: Math.round(job[`rate_${val.mod_lbr_ty.toLowerCase()}`] * 100) + }).multiply(val.mod_lb_hrs) + ); + } + return acc; + }, {}); + + const selectedDmsAllocationConfig = bodyshop.md_responsibility_centers.dms_defaults.find( + (d) => d.name === job.dms_allocation + ); + CdkBase.createLogEvent( + socket, + "DEBUG", + `Using DMS Allocation ${selectedDmsAllocationConfig && selectedDmsAllocationConfig.name} for cost export.` + ); + + let costCenterHash = {}; + //Check whether to skip this if PBS and using AP module. + const disablebillwip = !!bodyshop?.pbs_configuration?.disablebillwip; + + if (!disablebillwip) { + costCenterHash = job.bills.reduce((bill_acc, bill_val) => { + bill_val.billlines.map((line_val) => { + if (!bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]]) + bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = Dinero(); + + let lineDinero = Dinero({ + amount: Math.round((line_val.actual_cost || 0) * 100) + }) + .multiply(line_val.quantity) + .multiply(bill_val.is_credit_memo ? -1 : 1); + + bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = + bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]].add(lineDinero); + return null; }); + return bill_acc; + }, {}); + } - if (!hasMapaLine && job.job_totals.rates.mapa.total.amount > 0) { - // console.log("Adding MAPA Line Manually."); - const mapaAccountName = selectedDmsAllocationConfig.profits.MAPA; + job.timetickets.forEach((ticket) => { + //Get the total amount of the ticket. + let TicketTotal = Dinero({ + amount: Math.round( + ticket.rate * + (ticket.employee && ticket.employee.flat_rate ? ticket.productivehrs || 0 : ticket.actualhrs || 0) * + 100 + ) + }); + //Add it to the right cost center. + if (!costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]]) + costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]] = Dinero(); - const mapaAccount = bodyshop.md_responsibility_centers.profits.find( - (c) => c.name === mapaAccountName - ); + costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]] = + costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]].add(TicketTotal); + }); - if (mapaAccount) { - if (!profitCenterHash[mapaAccountName]) - profitCenterHash[mapaAccountName] = Dinero(); + if (!hasMapaLine && job.job_totals.rates.mapa.total.amount > 0) { + // console.log("Adding MAPA Line Manually."); + const mapaAccountName = selectedDmsAllocationConfig.profits.MAPA; - profitCenterHash[mapaAccountName] = profitCenterHash[ - mapaAccountName - ].add(Dinero(job.job_totals.rates.mapa.total)); - } else { - //console.log("NO MAPA ACCOUNT FOUND!!"); - } - } + const mapaAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === mapaAccountName); - if (!hasMashLine && job.job_totals.rates.mash.total.amount > 0) { - // console.log("Adding MASH Line Manually."); + if (mapaAccount) { + if (!profitCenterHash[mapaAccountName]) profitCenterHash[mapaAccountName] = Dinero(); - const mashAccountName = selectedDmsAllocationConfig.profits.MASH; - - const mashAccount = bodyshop.md_responsibility_centers.profits.find( - (c) => c.name === mashAccountName - ); - - if (mashAccount) { - if (!profitCenterHash[mashAccountName]) - profitCenterHash[mashAccountName] = Dinero(); - - profitCenterHash[mashAccountName] = profitCenterHash[ - mashAccountName - ].add(Dinero(job.job_totals.rates.mash.total)); - } else { - // console.log("NO MASH ACCOUNT FOUND!!"); - } - } - console.log( - Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting), - typeof Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting) + profitCenterHash[mapaAccountName] = profitCenterHash[mapaAccountName].add( + Dinero(job.job_totals.rates.mapa.total) ); - if (!!bodyshop?.cdk_configuration?.sendmaterialscosting) { - //Manually send the percentage of the costing. + } else { + //console.log("NO MAPA ACCOUNT FOUND!!"); + } + } - //Paint Mat - const mapaAccountName = selectedDmsAllocationConfig.costs.MAPA; - const mapaAccount = bodyshop.md_responsibility_centers.costs.find( - (c) => c.name === mapaAccountName - ); - if (mapaAccount) { - if (!costCenterHash[mapaAccountName]) - costCenterHash[mapaAccountName] = Dinero(); - costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add( - Dinero(job.job_totals.rates.mapa.total).percentage( - bodyshop?.cdk_configuration?.sendmaterialscosting - ) - ); - } else { - //console.log("NO MAPA ACCOUNT FOUND!!"); - } + if (!hasMashLine && job.job_totals.rates.mash.total.amount > 0) { + // console.log("Adding MASH Line Manually."); - //Shop Mat - const mashAccountName = selectedDmsAllocationConfig.costs.MASH; - const mashAccount = bodyshop.md_responsibility_centers.costs.find( - (c) => c.name === mashAccountName - ); - if (mashAccount) { - if (!costCenterHash[mashAccountName]) - costCenterHash[mashAccountName] = Dinero(); - costCenterHash[mashAccountName] = costCenterHash[mashAccountName].add( - Dinero(job.job_totals.rates.mash.total).percentage( - bodyshop?.cdk_configuration?.sendmaterialscosting - ) - ); - } else { - // console.log("NO MASH ACCOUNT FOUND!!"); - } - } + const mashAccountName = selectedDmsAllocationConfig.profits.MASH; - const {ca_bc_pvrt} = job; - if (ca_bc_pvrt) { - // const pvrtAccount = bodyshop.md_responsibility_centers.profits.find( - // (c) => c.name === mashAccountName - // ); + const mashAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === mashAccountName); - taxAllocations.state.sale = taxAllocations.state.sale.add( - Dinero({amount: Math.round((ca_bc_pvrt || 0) * 100)}) - ); - } + if (mashAccount) { + if (!profitCenterHash[mashAccountName]) profitCenterHash[mashAccountName] = Dinero(); - if (job.towing_payable && job.towing_payable !== 0) { - const towAccountName = selectedDmsAllocationConfig.profits.TOW; + profitCenterHash[mashAccountName] = profitCenterHash[mashAccountName].add( + Dinero(job.job_totals.rates.mash.total) + ); + } else { + // console.log("NO MASH ACCOUNT FOUND!!"); + } + } + console.log( + Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting), + typeof Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting) + ); + if (!!bodyshop?.cdk_configuration?.sendmaterialscosting) { + //Manually send the percentage of the costing. - const towAccount = bodyshop.md_responsibility_centers.profits.find( - (c) => c.name === towAccountName - ); + //Paint Mat + const mapaAccountName = selectedDmsAllocationConfig.costs.MAPA; + const mapaAccount = bodyshop.md_responsibility_centers.costs.find((c) => c.name === mapaAccountName); + if (mapaAccount) { + if (!costCenterHash[mapaAccountName]) costCenterHash[mapaAccountName] = Dinero(); + costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add( + Dinero(job.job_totals.rates.mapa.total).percentage(bodyshop?.cdk_configuration?.sendmaterialscosting) + ); + } else { + //console.log("NO MAPA ACCOUNT FOUND!!"); + } - if (towAccount) { - if (!profitCenterHash[towAccountName]) - profitCenterHash[towAccountName] = Dinero(); + //Shop Mat + const mashAccountName = selectedDmsAllocationConfig.costs.MASH; + const mashAccount = bodyshop.md_responsibility_centers.costs.find((c) => c.name === mashAccountName); + if (mashAccount) { + if (!costCenterHash[mashAccountName]) costCenterHash[mashAccountName] = Dinero(); + costCenterHash[mashAccountName] = costCenterHash[mashAccountName].add( + Dinero(job.job_totals.rates.mash.total).percentage(bodyshop?.cdk_configuration?.sendmaterialscosting) + ); + } else { + // console.log("NO MASH ACCOUNT FOUND!!"); + } + } - profitCenterHash[towAccountName] = profitCenterHash[towAccountName].add( - Dinero({ - amount: Math.round((job.towing_payable || 0) * 100), - }) - ); - } else { - // console.log("NO MASH ACCOUNT FOUND!!"); - } - } - if (job.storage_payable && job.storage_payable !== 0) { - const storageAccountName = selectedDmsAllocationConfig.profits.TOW; + const { ca_bc_pvrt } = job; + if (ca_bc_pvrt) { + // const pvrtAccount = bodyshop.md_responsibility_centers.profits.find( + // (c) => c.name === mashAccountName + // ); - const towAccount = bodyshop.md_responsibility_centers.profits.find( - (c) => c.name === storageAccountName - ); + taxAllocations.state.sale = taxAllocations.state.sale.add( + Dinero({ amount: Math.round((ca_bc_pvrt || 0) * 100) }) + ); + } - if (towAccount) { - if (!profitCenterHash[storageAccountName]) - profitCenterHash[storageAccountName] = Dinero(); + if (job.towing_payable && job.towing_payable !== 0) { + const towAccountName = selectedDmsAllocationConfig.profits.TOW; - profitCenterHash[storageAccountName] = profitCenterHash[ - storageAccountName - ].add( - Dinero({ - amount: Math.round((job.storage_payable || 0) * 100), - }) - ); - } else { - // console.log("NO MASH ACCOUNT FOUND!!"); - } - } + const towAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === towAccountName); - if (job.adjustment_bottom_line && job.adjustment_bottom_line !== 0) { - const otherAccountName = selectedDmsAllocationConfig.profits.PAO; + if (towAccount) { + if (!profitCenterHash[towAccountName]) profitCenterHash[towAccountName] = Dinero(); - const otherAccount = bodyshop.md_responsibility_centers.profits.find( - (c) => c.name === otherAccountName - ); + profitCenterHash[towAccountName] = profitCenterHash[towAccountName].add( + Dinero({ + amount: Math.round((job.towing_payable || 0) * 100) + }) + ); + } else { + // console.log("NO MASH ACCOUNT FOUND!!"); + } + } + if (job.storage_payable && job.storage_payable !== 0) { + const storageAccountName = selectedDmsAllocationConfig.profits.TOW; - if (otherAccount) { - if (!profitCenterHash[otherAccountName]) - profitCenterHash[otherAccountName] = Dinero(); + const towAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === storageAccountName); - profitCenterHash[otherAccountName] = profitCenterHash[ - otherAccountName - ].add( - Dinero({ - amount: Math.round((job.adjustment_bottom_line || 0) * 100), - }) - ); - } else { - // console.log("NO MASH ACCOUNT FOUND!!"); - } - } -if(InstanceManager({rome:true})){ + if (towAccount) { + if (!profitCenterHash[storageAccountName]) profitCenterHash[storageAccountName] = Dinero(); + + profitCenterHash[storageAccountName] = profitCenterHash[storageAccountName].add( + Dinero({ + amount: Math.round((job.storage_payable || 0) * 100) + }) + ); + } else { + // console.log("NO MASH ACCOUNT FOUND!!"); + } + } + + if (job.adjustment_bottom_line && job.adjustment_bottom_line !== 0) { + const otherAccountName = selectedDmsAllocationConfig.profits.PAO; + + const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === otherAccountName); + + if (otherAccount) { + if (!profitCenterHash[otherAccountName]) profitCenterHash[otherAccountName] = Dinero(); + + profitCenterHash[otherAccountName] = profitCenterHash[otherAccountName].add( + Dinero({ + amount: Math.round((job.adjustment_bottom_line || 0) * 100) + }) + ); + } else { + // console.log("NO MASH ACCOUNT FOUND!!"); + } + } + if (InstanceManager({ rome: true })) { //profile level adjustments - Object.keys(job.job_totals.parts.adjustments).forEach((key) => { - const accountName = selectedDmsAllocationConfig.profits[key]; + Object.keys(job.job_totals.parts.adjustments).forEach((key) => { + const accountName = selectedDmsAllocationConfig.profits[key]; - const otherAccount = bodyshop.md_responsibility_centers.profits.find( - (c) => c.name === accountName - ); + const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === accountName); - if (otherAccount) { - if (!profitCenterHash[accountName]) - profitCenterHash[accountName] = Dinero(); + if (otherAccount) { + if (!profitCenterHash[accountName]) profitCenterHash[accountName] = Dinero(); - profitCenterHash[accountName] = profitCenterHash[accountName].add( - Dinero(job.job_totals.parts.adjustments[key]) - ); - } else { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}` - ); - } - }); -} - - - const jobAllocations = _.union( - Object.keys(profitCenterHash), - Object.keys(costCenterHash) - ).map((key) => { - const profitCenter = bodyshop.md_responsibility_centers.profits.find( - (c) => c.name === key - ); - const costCenter = bodyshop.md_responsibility_centers.costs.find( - (c) => c.name === key - ); - - return { - center: key, - sale: profitCenterHash[key] ? profitCenterHash[key] : Dinero(), - cost: costCenterHash[key] ? costCenterHash[key] : Dinero(), - profitCenter, - costCenter, - }; - }); - - return [ - ...jobAllocations, - ...Object.keys(taxAllocations) - .filter( - (key) => - taxAllocations[key].sale.getAmount() > 0 || - taxAllocations[key].cost.getAmount() > 0 - ) - .map((key) => { - if ( - key === "federal" && - selectedDmsAllocationConfig.gst_override && - selectedDmsAllocationConfig.gst_override !== "" - ) { - const ret = {...taxAllocations[key], tax: key}; - ret.costCenter.dms_acctnumber = - selectedDmsAllocationConfig.gst_override; - ret.profitCenter.dms_acctnumber = - selectedDmsAllocationConfig.gst_override; - return ret; - } else { - return {...taxAllocations[key], tax: key}; - } - }), - ]; - } catch (error) { - console.log(error) - CdkBase.createLogEvent( + profitCenterHash[accountName] = profitCenterHash[accountName].add( + Dinero(job.job_totals.parts.adjustments[key]) + ); + } else { + CdkBase.createLogEvent( socket, "ERROR", - `Error encountered in CdkCalculateAllocations. ${error}` - ); + `Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}` + ); + } + }); } + + const jobAllocations = _.union(Object.keys(profitCenterHash), Object.keys(costCenterHash)).map((key) => { + const profitCenter = bodyshop.md_responsibility_centers.profits.find((c) => c.name === key); + const costCenter = bodyshop.md_responsibility_centers.costs.find((c) => c.name === key); + + return { + center: key, + sale: profitCenterHash[key] ? profitCenterHash[key] : Dinero(), + cost: costCenterHash[key] ? costCenterHash[key] : Dinero(), + profitCenter, + costCenter + }; + }); + + return [ + ...jobAllocations, + ...Object.keys(taxAllocations) + .filter((key) => taxAllocations[key].sale.getAmount() > 0 || taxAllocations[key].cost.getAmount() > 0) + .map((key) => { + if ( + key === "federal" && + selectedDmsAllocationConfig.gst_override && + selectedDmsAllocationConfig.gst_override !== "" + ) { + const ret = { ...taxAllocations[key], tax: key }; + ret.costCenter.dms_acctnumber = selectedDmsAllocationConfig.gst_override; + ret.profitCenter.dms_acctnumber = selectedDmsAllocationConfig.gst_override; + return ret; + } else { + return { ...taxAllocations[key], tax: key }; + } + }) + ]; + } catch (error) { + console.log(error); + CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`); + } }; async function QueryJobData(socket, jobid) { - CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`); - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); - const result = await client - .setHeaders({Authorization: `Bearer ${socket.handshake.auth.token}`}) - .request(queries.GET_CDK_ALLOCATIONS, {id: jobid}); - CdkBase.createLogEvent( - socket, - "TRACE", - `Job data query result ${JSON.stringify(result, null, 2)}` - ); - return result.jobs_by_pk; + CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`); + const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); + const result = await client + .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) + .request(queries.GET_CDK_ALLOCATIONS, { id: jobid }); + CdkBase.createLogEvent(socket, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`); + return result.jobs_by_pk; } diff --git a/server/cdk/cdk-get-makes.js b/server/cdk/cdk-get-makes.js index 07e1028cc..06ccc53e2 100644 --- a/server/cdk/cdk-get-makes.js +++ b/server/cdk/cdk-get-makes.js @@ -1,9 +1,6 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const soap = require("soap"); const queries = require("../graphql-client/queries"); @@ -11,7 +8,7 @@ const queries = require("../graphql-client/queries"); const CdkWsdl = require("./cdk-wsdl").default; const logger = require("../utils/logger"); -const {CDK_CREDENTIALS, CheckCdkResponseForError} = require("./cdk-wsdl"); +const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl"); // exports.default = async function (socket, cdk_dealerid) { // try { @@ -31,121 +28,83 @@ const {CDK_CREDENTIALS, CheckCdkResponseForError} = require("./cdk-wsdl"); // }; exports.default = async function ReloadCdkMakes(req, res) { - const {bodyshopid, cdk_dealerid} = req.body; - try { - //Query all CDK Models - const newList = await GetCdkMakes(req, cdk_dealerid); + const { bodyshopid, cdk_dealerid } = req.body; + try { + //Query all CDK Models + const newList = await GetCdkMakes(req, cdk_dealerid); - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; - const deleteResult = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.DELETE_ALL_DMS_VEHICLES, {}); - console.log( - "🚀 ~ file: cdk-get-makes.js ~ line 53 ~ deleteResult", - deleteResult - ); + const deleteResult = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.DELETE_ALL_DMS_VEHICLES, {}); + console.log("🚀 ~ file: cdk-get-makes.js ~ line 53 ~ deleteResult", deleteResult); - //Insert the new ones. + //Insert the new ones. - const insertResult = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.INSERT_DMS_VEHICLES, { - vehicles: newList.map((i) => { - return { - bodyshopid, - makecode: i.makeCode, - modelcode: i.modelCode, - make: i.makeFullName, - model: i.modelFullName, - }; - }), - }); + const insertResult = await client.setHeaders({ Authorization: BearerToken }).request(queries.INSERT_DMS_VEHICLES, { + vehicles: newList.map((i) => { + return { + bodyshopid, + makecode: i.makeCode, + modelcode: i.modelCode, + make: i.makeFullName, + model: i.modelFullName + }; + }) + }); - logger.log( - "cdk-replace-makes-models-success", - "DEBUG", - req.user.email, - null, - { - cdk_dealerid, - count: newList.length, - } - ); - res.sendStatus(200); - } catch (error) { - logger.log( - "cdk-replace-makes-models-error", - "ERROR", - req.user.email, - null, - { - cdk_dealerid, - error, - } - ); - res.status(500).json(error); - } + logger.log("cdk-replace-makes-models-success", "DEBUG", req.user.email, null, { + cdk_dealerid, + count: newList.length + }); + res.sendStatus(200); + } catch (error) { + logger.log("cdk-replace-makes-models-error", "ERROR", req.user.email, null, { + cdk_dealerid, + error + }); + res.status(500).json(error); + } }; async function GetCdkMakes(req, cdk_dealerid) { - logger.log("cdk-replace-makes-models", "DEBUG", req.user.email, null, { - cdk_dealerid, + logger.log("cdk-replace-makes-models", "DEBUG", req.user.email, null, { + cdk_dealerid + }); + + try { + const soapClientVehicleInsert = await soap.createClientAsync(CdkWsdl.VehicleInsertUpdate); + + const soapResponseVehicleSearch = await soapClientVehicleInsert.getMakeModelAsync( + { + arg0: CDK_CREDENTIALS, + arg1: { id: cdk_dealerid } + }, + + {} + ); + + CheckCdkResponseForError(null, soapResponseVehicleSearch); + const [result, rawResponse, , rawRequest] = soapResponseVehicleSearch; + logger.log("cdk-replace-makes-models-request", "ERROR", req.user.email, null, { + cdk_dealerid, + xml: rawRequest }); - try { - const soapClientVehicleInsert = await soap.createClientAsync( - CdkWsdl.VehicleInsertUpdate - ); + logger.log("cdk-replace-makes-models-response", "ERROR", req.user.email, null, { + cdk_dealerid, + xml: rawResponse + }); - const soapResponseVehicleSearch = - await soapClientVehicleInsert.getMakeModelAsync( - { - arg0: CDK_CREDENTIALS, - arg1: {id: cdk_dealerid}, - }, + return result.return; + } catch (error) { + logger.log("cdk-replace-makes-models-error", "ERROR", req.user.email, null, { + cdk_dealerid, + error + }); - {} - ); - - CheckCdkResponseForError(null, soapResponseVehicleSearch); - const [result, rawResponse, , rawRequest] = soapResponseVehicleSearch; - logger.log( - "cdk-replace-makes-models-request", - "ERROR", - req.user.email, - null, - { - cdk_dealerid, - xml: rawRequest, - } - ); - - logger.log( - "cdk-replace-makes-models-response", - "ERROR", - req.user.email, - null, - { - cdk_dealerid, - xml: rawResponse, - } - ); - - return result.return; - } catch (error) { - logger.log( - "cdk-replace-makes-models-error", - "ERROR", - req.user.email, - null, - { - cdk_dealerid, - error, - } - ); - - throw new Error(error); - } + throw new Error(error); + } } diff --git a/server/cdk/cdk-job-export.js b/server/cdk/cdk-job-export.js index 1dda38ac1..3766f836c 100644 --- a/server/cdk/cdk-job-export.js +++ b/server/cdk/cdk-job-export.js @@ -1,16 +1,13 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const GraphQLClient = require("graphql-request").GraphQLClient; const soap = require("soap"); const queries = require("../graphql-client/queries"); const CdkBase = require("../web-sockets/web-socket"); const CdkWsdl = require("./cdk-wsdl").default; -const {CDK_CREDENTIALS, CheckCdkResponseForError} = require("./cdk-wsdl"); +const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl"); const CalcualteAllocations = require("./cdk-calculate-allocations").default; const InstanceMgr = require("../utils/instanceMgr").default; @@ -18,1431 +15,989 @@ const moment = require("moment-timezone"); const replaceSpecialRegex = /[^a-zA-Z0-9 .,\n #]+/g; -exports.default = async function (socket, {txEnvelope, jobid}) { - socket.logEvents = []; - socket.recordid = jobid; - socket.txEnvelope = txEnvelope; - try { +exports.default = async function (socket, { txEnvelope, jobid }) { + socket.logEvents = []; + socket.recordid = jobid; + socket.txEnvelope = txEnvelope; + try { + CdkBase.createLogEvent(socket, "DEBUG", `Received Job export request for id ${jobid}`); + + const JobData = await QueryJobData(socket, jobid); + socket.JobData = JobData; + const DealerId = JobData.bodyshop.cdk_dealerid; + CdkBase.createLogEvent(socket, "DEBUG", `Dealer ID detected: ${JSON.stringify(DealerId)}`); + + CdkBase.createLogEvent(socket, "DEBUG", `{1} Begin Calculate DMS Vehicle ID using VIN: ${JobData.v_vin}`); + socket.DMSVid = await CalculateDmsVid(socket, JobData); + + if (socket.DMSVid.newId === "N") { + CdkBase.createLogEvent( + socket, + "DEBUG", + `{2.1} Querying the Vehicle using the DMSVid: ${socket.DMSVid.vehiclesVehId}` + ); + socket.DMSVeh = await QueryDmsVehicleById(socket, JobData, socket.DMSVid); + + const DMSVehCustomer = + socket.DMSVeh && socket.DMSVeh.owners && socket.DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT"); + + if (DMSVehCustomer && DMSVehCustomer.id && DMSVehCustomer.id.value) { CdkBase.createLogEvent( - socket, - "DEBUG", - `Received Job export request for id ${jobid}` - ); - - const JobData = await QueryJobData(socket, jobid); - socket.JobData = JobData; - const DealerId = JobData.bodyshop.cdk_dealerid; - CdkBase.createLogEvent( - socket, - "DEBUG", - `Dealer ID detected: ${JSON.stringify(DealerId)}` - ); - - CdkBase.createLogEvent( - socket, - "DEBUG", - `{1} Begin Calculate DMS Vehicle ID using VIN: ${JobData.v_vin}` - ); - socket.DMSVid = await CalculateDmsVid(socket, JobData); - - if (socket.DMSVid.newId === "N") { - CdkBase.createLogEvent( - socket, - "DEBUG", - `{2.1} Querying the Vehicle using the DMSVid: ${socket.DMSVid.vehiclesVehId}` - ); - socket.DMSVeh = await QueryDmsVehicleById(socket, JobData, socket.DMSVid); - - const DMSVehCustomer = - socket.DMSVeh && - socket.DMSVeh.owners && - socket.DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT"); - - if (DMSVehCustomer && DMSVehCustomer.id && DMSVehCustomer.id.value) { - CdkBase.createLogEvent( - socket, - "DEBUG", - `{2.2} Querying the Customer using the ID from DMSVeh: ${DMSVehCustomer.id.value}` - ); - socket.DMSVehCustomer = await QueryDmsCustomerById( - socket, - JobData, - DMSVehCustomer.id.value - ); - } - } - - CdkBase.createLogEvent( - socket, - "DEBUG", - `{2.3} Querying the Customer using the name.` - ); - - socket.DMSCustList = await QueryDmsCustomerByName(socket, JobData); - - socket.emit("cdk-select-customer", [ - ...(socket.DMSVehCustomer - ? [{...socket.DMSVehCustomer, vinOwner: true}] - : []), - ...socket.DMSCustList, - ]); - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error encountered in CdkJobExport. ${error}` + socket, + "DEBUG", + `{2.2} Querying the Customer using the ID from DMSVeh: ${DMSVehCustomer.id.value}` ); + socket.DMSVehCustomer = await QueryDmsCustomerById(socket, JobData, DMSVehCustomer.id.value); + } } + + CdkBase.createLogEvent(socket, "DEBUG", `{2.3} Querying the Customer using the name.`); + + socket.DMSCustList = await QueryDmsCustomerByName(socket, JobData); + + socket.emit("cdk-select-customer", [ + ...(socket.DMSVehCustomer ? [{ ...socket.DMSVehCustomer, vinOwner: true }] : []), + ...socket.DMSCustList + ]); + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkJobExport. ${error}`); + } }; async function CdkSelectedCustomer(socket, selectedCustomerId) { - try { - socket.selectedCustomerId = selectedCustomerId; - if (selectedCustomerId) { - CdkBase.createLogEvent( - socket, - "DEBUG", - `{3.1} Querying the Customer using Customer ID: ${selectedCustomerId}` - ); - socket.DMSCust = await QueryDmsCustomerById( - socket, - socket.JobData, - selectedCustomerId - ); - } else { - CdkBase.createLogEvent( - socket, - "DEBUG", - `{3.2} Generating a new customer ID.` - ); - const newCustomerId = await GenerateDmsCustomerNumber(socket); - CdkBase.createLogEvent( - socket, - "DEBUG", - `{3.3} Inserting new customer with ID: ${newCustomerId}` - ); - socket.DMSCust = await InsertDmsCustomer(socket, newCustomerId); - } + try { + socket.selectedCustomerId = selectedCustomerId; + if (selectedCustomerId) { + CdkBase.createLogEvent(socket, "DEBUG", `{3.1} Querying the Customer using Customer ID: ${selectedCustomerId}`); + socket.DMSCust = await QueryDmsCustomerById(socket, socket.JobData, selectedCustomerId); + } else { + CdkBase.createLogEvent(socket, "DEBUG", `{3.2} Generating a new customer ID.`); + const newCustomerId = await GenerateDmsCustomerNumber(socket); + CdkBase.createLogEvent(socket, "DEBUG", `{3.3} Inserting new customer with ID: ${newCustomerId}`); + socket.DMSCust = await InsertDmsCustomer(socket, newCustomerId); + } - if (socket.DMSVid.newId === "Y") { - CdkBase.createLogEvent( - socket, - "DEBUG", - `{4.1} Inserting new vehicle with ID: ID ${socket.DMSVid.vehiclesVehId}` - ); - socket.DMSVeh = await InsertDmsVehicle(socket); - } else { - CdkBase.createLogEvent( - socket, - "DEBUG", - `{4.2} Querying Existing Vehicle using ID ${socket.DMSVid.vehiclesVehId}` - ); - socket.DMSVeh = await QueryDmsVehicleById( - socket, - socket.JobData, - socket.DMSVid - ); - CdkBase.createLogEvent( - socket, - "DEBUG", - `{4.3} Updating Existing Vehicle to associate to owner.` - ); - socket.DMSVeh = await UpdateDmsVehicle(socket); - } + if (socket.DMSVid.newId === "Y") { + CdkBase.createLogEvent(socket, "DEBUG", `{4.1} Inserting new vehicle with ID: ID ${socket.DMSVid.vehiclesVehId}`); + socket.DMSVeh = await InsertDmsVehicle(socket); + } else { + CdkBase.createLogEvent( + socket, + "DEBUG", + `{4.2} Querying Existing Vehicle using ID ${socket.DMSVid.vehiclesVehId}` + ); + socket.DMSVeh = await QueryDmsVehicleById(socket, socket.JobData, socket.DMSVid); + CdkBase.createLogEvent(socket, "DEBUG", `{4.3} Updating Existing Vehicle to associate to owner.`); + socket.DMSVeh = await UpdateDmsVehicle(socket); + } - CdkBase.createLogEvent( - socket, - "DEBUG", - `{5} Creating Transaction header with Dms Start WIP` - ); - socket.DMSTransHeader = await InsertDmsStartWip(socket); - CdkBase.createLogEvent( - socket, - "DEBUG", - `{5.1} Creating Transaction with ID ${socket.DMSTransHeader.transID}` - ); + CdkBase.createLogEvent(socket, "DEBUG", `{5} Creating Transaction header with Dms Start WIP`); + socket.DMSTransHeader = await InsertDmsStartWip(socket); + CdkBase.createLogEvent(socket, "DEBUG", `{5.1} Creating Transaction with ID ${socket.DMSTransHeader.transID}`); - socket.DMSBatchTxn = await InsertDmsBatchWip(socket); - CdkBase.createLogEvent( - socket, - "DEBUG", - `{6} Attempting to post Transaction with ID ${socket.DMSTransHeader.transID}` - ); - socket.DmsBatchTxnPost = await PostDmsBatchWip(socket); - if (socket.DmsBatchTxnPost.code === "success") { - //something - CdkBase.createLogEvent( - socket, - "DEBUG", - `{6} Successfully posted sransaction to DMS.` - ); + socket.DMSBatchTxn = await InsertDmsBatchWip(socket); + CdkBase.createLogEvent( + socket, + "DEBUG", + `{6} Attempting to post Transaction with ID ${socket.DMSTransHeader.transID}` + ); + socket.DmsBatchTxnPost = await PostDmsBatchWip(socket); + if (socket.DmsBatchTxnPost.code === "success") { + //something + CdkBase.createLogEvent(socket, "DEBUG", `{6} Successfully posted sransaction to DMS.`); - await MarkJobExported(socket, socket.JobData.id); + await MarkJobExported(socket, socket.JobData.id); - CdkBase.createLogEvent( - socket, - "DEBUG", - `{5} Updating Service Vehicle History.` - ); - socket.DMSVehHistory = await InsertServiceVehicleHistory(socket); - socket.emit("export-success", socket.JobData.id); - } else { - //Get the error code - CdkBase.createLogEvent( - socket, - "DEBUG", - `{6.1} Getting errors for Transaction ID ${socket.DMSTransHeader.transID}` - ); - socket.DmsError = await QueryDmsErrWip(socket); - //Delete the transaction - CdkBase.createLogEvent( - socket, - "DEBUG", - `{6.2} Deleting Transaction ID ${socket.DMSTransHeader.transID}` - ); - socket.DmsBatchTxnPost = await DeleteDmsWip(socket); + CdkBase.createLogEvent(socket, "DEBUG", `{5} Updating Service Vehicle History.`); + socket.DMSVehHistory = await InsertServiceVehicleHistory(socket); + socket.emit("export-success", socket.JobData.id); + } else { + //Get the error code + CdkBase.createLogEvent( + socket, + "DEBUG", + `{6.1} Getting errors for Transaction ID ${socket.DMSTransHeader.transID}` + ); + socket.DmsError = await QueryDmsErrWip(socket); + //Delete the transaction + CdkBase.createLogEvent(socket, "DEBUG", `{6.2} Deleting Transaction ID ${socket.DMSTransHeader.transID}`); + socket.DmsBatchTxnPost = await DeleteDmsWip(socket); - socket.DmsError.errMsg - .split("|") - .map( - (e) => - e !== null && - e !== "" && - CdkBase.createLogEvent( - socket, - "ERROR", - `Error(s) encountered in posting transaction. ${e}` - ) - ); - } - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error encountered in CdkSelectedCustomer. ${error}` - ); - await InsertFailedExportLog(socket, error); - } finally { - //Ensure we always insert logEvents - //GQL to insert logevents. - - CdkBase.createLogEvent( - socket, - "DEBUG", - `Capturing log events to database.` + socket.DmsError.errMsg + .split("|") + .map( + (e) => + e !== null && + e !== "" && + CdkBase.createLogEvent(socket, "ERROR", `Error(s) encountered in posting transaction. ${e}`) ); } + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkSelectedCustomer. ${error}`); + await InsertFailedExportLog(socket, error); + } finally { + //Ensure we always insert logEvents + //GQL to insert logevents. + + CdkBase.createLogEvent(socket, "DEBUG", `Capturing log events to database.`); + } } exports.CdkSelectedCustomer = CdkSelectedCustomer; async function QueryJobData(socket, jobid) { - CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`); - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); - const result = await client - .setHeaders({Authorization: `Bearer ${socket.handshake.auth.token}`}) - .request(queries.QUERY_JOBS_FOR_CDK_EXPORT, {id: jobid}); - CdkBase.createLogEvent( - socket, - "TRACE", - `Job data query result ${JSON.stringify(result, null, 2)}` - ); - return result.jobs_by_pk; + CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`); + const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); + const result = await client + .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) + .request(queries.QUERY_JOBS_FOR_CDK_EXPORT, { id: jobid }); + CdkBase.createLogEvent(socket, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`); + return result.jobs_by_pk; } async function CalculateDmsVid(socket, JobData) { - try { - const soapClientVehicleInsertUpdate = await soap.createClientAsync( - CdkWsdl.VehicleInsertUpdate - ); - const soapResponseVehicleInsertUpdate = - await soapClientVehicleInsertUpdate.getVehIdsAsync({ - arg0: CDK_CREDENTIALS, - arg1: {id: JobData.bodyshop.cdk_dealerid}, - arg2: {VIN: JobData.v_vin}, - }); + try { + const soapClientVehicleInsertUpdate = await soap.createClientAsync(CdkWsdl.VehicleInsertUpdate); + const soapResponseVehicleInsertUpdate = await soapClientVehicleInsertUpdate.getVehIdsAsync({ + arg0: CDK_CREDENTIALS, + arg1: { id: JobData.bodyshop.cdk_dealerid }, + arg2: { VIN: JobData.v_vin } + }); - const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate; - CdkBase.createXmlEvent( - socket, - rawRequest, - `soapClientVehicleInsertUpdate.getVehIdsAsync request.` - ); + const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate; + CdkBase.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.getVehIdsAsync request.`); - CdkBase.createXmlEvent( - socket, - rawResponse, - `soapClientVehicleInsertUpdate.getVehIdsAsync response.` - ); + CdkBase.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.getVehIdsAsync response.`); - CdkBase.createLogEvent( - socket, - "TRACE", - `soapClientVehicleInsertUpdate.getVehIdsAsync Result ${JSON.stringify( - result, - null, - 2 - )}` - ); - CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate); + CdkBase.createLogEvent( + socket, + "TRACE", + `soapClientVehicleInsertUpdate.getVehIdsAsync Result ${JSON.stringify(result, null, 2)}` + ); + CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate); - //if (result && result.return && result.return.length > 1) { - return result.return.find((r) => r.vehiclesVehId); - //} + //if (result && result.return && result.return.length > 1) { + return result.return.find((r) => r.vehiclesVehId); + //} - //return result && result.return && result.return[0]; - } catch (error) { - CdkBase.createXmlEvent( - socket, - error.request, - `soapClientVehicleInsertUpdate.getVehIdsAsync request.`, - true - ); + //return result && result.return && result.return[0]; + } catch (error) { + CdkBase.createXmlEvent(socket, error.request, `soapClientVehicleInsertUpdate.getVehIdsAsync request.`, true); - CdkBase.createXmlEvent( - socket, - error.response && error.response.data, - `soapClientVehicleInsertUpdate.getVehIdsAsync response.`, - true - ); - CdkBase.createLogEvent( - socket, - "ERROR", - `{1} Error in CalculateDmsVid - ${error}` - ); - throw new Error(error); - } + CdkBase.createXmlEvent( + socket, + error.response && error.response.data, + `soapClientVehicleInsertUpdate.getVehIdsAsync response.`, + true + ); + CdkBase.createLogEvent(socket, "ERROR", `{1} Error in CalculateDmsVid - ${error}`); + throw new Error(error); + } } async function QueryDmsVehicleById(socket, JobData, DMSVid) { - try { - const soapClientVehicleInsertUpdate = await soap.createClientAsync( - CdkWsdl.VehicleInsertUpdate - ); + try { + const soapClientVehicleInsertUpdate = await soap.createClientAsync(CdkWsdl.VehicleInsertUpdate); - const soapResponseVehicleInsertUpdate = - await soapClientVehicleInsertUpdate.readAsync({ - arg0: CDK_CREDENTIALS, - arg1: {id: JobData.bodyshop.cdk_dealerid}, - arg2: { - fileType: "VEHICLES", - vehiclesVehicleId: DMSVid.vehiclesVehId, - }, - }); + const soapResponseVehicleInsertUpdate = await soapClientVehicleInsertUpdate.readAsync({ + arg0: CDK_CREDENTIALS, + arg1: { id: JobData.bodyshop.cdk_dealerid }, + arg2: { + fileType: "VEHICLES", + vehiclesVehicleId: DMSVid.vehiclesVehId + } + }); - const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate; + const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate; - CdkBase.createXmlEvent( - socket, - rawRequest, - `soapClientVehicleInsertUpdate.readAsync request.` - ); + CdkBase.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.readAsync request.`); - CdkBase.createLogEvent( - socket, - "TRACE", - `soapClientVehicleInsertUpdate.readAsync Result ${JSON.stringify( - result, - null, - 2 - )}` - ); - CdkBase.createXmlEvent( - socket, - rawResponse, - `soapClientVehicleInsertUpdate.readAsync response.` - ); - CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate); - const VehicleFromDMS = result && result.return && result.return.vehicle; - return VehicleFromDMS; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in QueryDmsVehicleById - ${error}` - ); - throw new Error(error); - } + CdkBase.createLogEvent( + socket, + "TRACE", + `soapClientVehicleInsertUpdate.readAsync Result ${JSON.stringify(result, null, 2)}` + ); + CdkBase.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.readAsync response.`); + CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate); + const VehicleFromDMS = result && result.return && result.return.vehicle; + return VehicleFromDMS; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in QueryDmsVehicleById - ${error}`); + throw new Error(error); + } } async function QueryDmsCustomerById(socket, JobData, CustomerId) { - try { - const soapClientCustomerInsertUpdate = await soap.createClientAsync( - CdkWsdl.CustomerInsertUpdate - ); - const soapResponseCustomerInsertUpdate = - await soapClientCustomerInsertUpdate.readAsync({ - arg0: CDK_CREDENTIALS, - arg1: {dealerId: JobData.bodyshop.cdk_dealerid}, //TODO: Verify why this does not follow the other standards. - arg2: { - // userId: CustomerId, - }, - arg3: CustomerId, - }); + try { + const soapClientCustomerInsertUpdate = await soap.createClientAsync(CdkWsdl.CustomerInsertUpdate); + const soapResponseCustomerInsertUpdate = await soapClientCustomerInsertUpdate.readAsync({ + arg0: CDK_CREDENTIALS, + arg1: { dealerId: JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards. + arg2: { + // userId: CustomerId, + }, + arg3: CustomerId + }); - const [result, rawResponse, , rawRequest] = - soapResponseCustomerInsertUpdate; + const [result, rawResponse, , rawRequest] = soapResponseCustomerInsertUpdate; - CdkBase.createXmlEvent( - socket, - rawRequest, - `soapClientCustomerInsertUpdate.readAsync request.` - ); + CdkBase.createXmlEvent(socket, rawRequest, `soapClientCustomerInsertUpdate.readAsync request.`); - CdkBase.createXmlEvent( - socket, - rawResponse, - `soapClientCustomerInsertUpdate.readAsync response.` - ); - CdkBase.createLogEvent( - socket, - "TRACE", - `soapClientCustomerInsertUpdate.readAsync Result ${JSON.stringify( - result, - null, - 2 - )}` - ); - CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate); - const CustomersFromDms = - result && result.return && result.return.customerParty; - return CustomersFromDms; - } catch (error) { - CdkBase.createXmlEvent( - socket, - error.request, - `soapClientCustomerInsertUpdate.readAsync request.`, - true - ); + CdkBase.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.readAsync response.`); + CdkBase.createLogEvent( + socket, + "TRACE", + `soapClientCustomerInsertUpdate.readAsync Result ${JSON.stringify(result, null, 2)}` + ); + CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate); + const CustomersFromDms = result && result.return && result.return.customerParty; + return CustomersFromDms; + } catch (error) { + CdkBase.createXmlEvent(socket, error.request, `soapClientCustomerInsertUpdate.readAsync request.`, true); - CdkBase.createXmlEvent( - socket, - error.response && error.response.data, - `soapClientCustomerInsertUpdate.readAsync response.`, - true - ); + CdkBase.createXmlEvent( + socket, + error.response && error.response.data, + `soapClientCustomerInsertUpdate.readAsync response.`, + true + ); - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in QueryDmsCustomerById - ${error}` - ); - throw new Error(error); - } + CdkBase.createLogEvent(socket, "ERROR", `Error in QueryDmsCustomerById - ${error}`); + throw new Error(error); + } } async function QueryDmsCustomerByName(socket, JobData) { - const ownerName = ( - JobData.ownr_co_nm && JobData.ownr_co_nm.trim() !== "" - ? JobData.ownr_co_nm - : `${JobData.ownr_ln},${JobData.ownr_fn}` - ).replace(replaceSpecialRegex, ""); + const ownerName = ( + JobData.ownr_co_nm && JobData.ownr_co_nm.trim() !== "" + ? JobData.ownr_co_nm + : `${JobData.ownr_ln},${JobData.ownr_fn}` + ).replace(replaceSpecialRegex, ""); + + CdkBase.createLogEvent(socket, "DEBUG", `Begin Query DMS Customer by Name using: ${ownerName}`); + + try { + const soapClientCustomerSearch = await soap.createClientAsync(CdkWsdl.CustomerSearch); + const soapResponseCustomerSearch = await soapClientCustomerSearch.executeSearchAsync({ + arg0: CDK_CREDENTIALS, + arg1: { dealerId: JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards. + arg2: { + verb: "EXACT", + key: ownerName + } + }); + + const [result, rawResponse, , rawRequest] = soapResponseCustomerSearch; + + CdkBase.createXmlEvent(socket, rawRequest, `soapClientCustomerSearch.executeSearchBulkAsync request.`); + + CdkBase.createXmlEvent(socket, rawResponse, `soapClientCustomerSearch.executeSearchBulkAsync response.`); CdkBase.createLogEvent( - socket, - "DEBUG", - `Begin Query DMS Customer by Name using: ${ownerName}` + socket, + "TRACE", + `soapClientCustomerSearch.executeSearchBulkAsync Result ${JSON.stringify(result, null, 2)}` + ); + CheckCdkResponseForError(socket, soapResponseCustomerSearch); + const CustomersFromDms = (result && result.return) || []; + return CustomersFromDms; + } catch (error) { + CdkBase.createXmlEvent(socket, error.request, `soapClientCustomerSearch.executeSearchBulkAsync request.`, true); + + CdkBase.createXmlEvent( + socket, + error.response && error.response.data, + `soapClientCustomerSearch.executeSearchBulkAsync response.`, + true ); - try { - const soapClientCustomerSearch = await soap.createClientAsync( - CdkWsdl.CustomerSearch - ); - const soapResponseCustomerSearch = - await soapClientCustomerSearch.executeSearchAsync({ - arg0: CDK_CREDENTIALS, - arg1: {dealerId: JobData.bodyshop.cdk_dealerid}, //TODO: Verify why this does not follow the other standards. - arg2: { - verb: "EXACT", - key: ownerName, - }, - }); - - const [result, rawResponse, , rawRequest] = soapResponseCustomerSearch; - - CdkBase.createXmlEvent( - socket, - rawRequest, - `soapClientCustomerSearch.executeSearchBulkAsync request.` - ); - - CdkBase.createXmlEvent( - socket, - rawResponse, - `soapClientCustomerSearch.executeSearchBulkAsync response.` - ); - - CdkBase.createLogEvent( - socket, - "TRACE", - `soapClientCustomerSearch.executeSearchBulkAsync Result ${JSON.stringify( - result, - null, - 2 - )}` - ); - CheckCdkResponseForError(socket, soapResponseCustomerSearch); - const CustomersFromDms = (result && result.return) || []; - return CustomersFromDms; - } catch (error) { - CdkBase.createXmlEvent( - socket, - error.request, - `soapClientCustomerSearch.executeSearchBulkAsync request.`, - true - ); - - CdkBase.createXmlEvent( - socket, - error.response && error.response.data, - `soapClientCustomerSearch.executeSearchBulkAsync response.`, - true - ); - - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in QueryDmsCustomerByName - ${error}` - ); - throw new Error(error); - } + CdkBase.createLogEvent(socket, "ERROR", `Error in QueryDmsCustomerByName - ${error}`); + throw new Error(error); + } } async function GenerateDmsCustomerNumber(socket) { - try { - const soapClientCustomerInsertUpdate = await soap.createClientAsync( - CdkWsdl.CustomerInsertUpdate - ); - const soapResponseCustomerInsertUpdate = - await soapClientCustomerInsertUpdate.getCustomerNumberAsync( - { - arg0: CDK_CREDENTIALS, - arg1: {dealerId: socket.JobData.bodyshop.cdk_dealerid}, //TODO: Verify why this does not follow the other standards. - arg2: {userId: null}, - }, + try { + const soapClientCustomerInsertUpdate = await soap.createClientAsync(CdkWsdl.CustomerInsertUpdate); + const soapResponseCustomerInsertUpdate = await soapClientCustomerInsertUpdate.getCustomerNumberAsync( + { + arg0: CDK_CREDENTIALS, + arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards. + arg2: { userId: null } + }, - {} - ); + {} + ); - const [result, rawResponse, , rawRequest] = - soapResponseCustomerInsertUpdate; + const [result, rawResponse, , rawRequest] = soapResponseCustomerInsertUpdate; - CdkBase.createXmlEvent( - socket, - rawRequest, - `soapClientCustomerInsertUpdate.getCustomerNumberAsync request.` - ); + CdkBase.createXmlEvent(socket, rawRequest, `soapClientCustomerInsertUpdate.getCustomerNumberAsync request.`); - CdkBase.createXmlEvent( - socket, - rawResponse, - `soapClientCustomerInsertUpdate.getCustomerNumberAsync response.` - ); + CdkBase.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.getCustomerNumberAsync response.`); - CdkBase.createLogEvent( - socket, - "TRACE", - `soapClientCustomerInsertUpdate.getCustomerNumberAsync Result ${JSON.stringify( - result, - null, - 2 - )}` - ); - CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate); - const customerNumber = - result && result.return && result.return.customerNumber; - return customerNumber; - } catch (error) { - CdkBase.createXmlEvent( - socket, - error.request, - `soapClientCustomerInsertUpdate.getCustomerNumberAsync request.`, - true - ); + CdkBase.createLogEvent( + socket, + "TRACE", + `soapClientCustomerInsertUpdate.getCustomerNumberAsync Result ${JSON.stringify(result, null, 2)}` + ); + CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate); + const customerNumber = result && result.return && result.return.customerNumber; + return customerNumber; + } catch (error) { + CdkBase.createXmlEvent( + socket, + error.request, + `soapClientCustomerInsertUpdate.getCustomerNumberAsync request.`, + true + ); - CdkBase.createXmlEvent( - socket, - error.response && error.response.data, - `soapClientCustomerInsertUpdate.getCustomerNumberAsync response.`, - true - ); - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in GenerateDmsCustomerNumber - ${error}` - ); - throw new Error(error); - } + CdkBase.createXmlEvent( + socket, + error.response && error.response.data, + `soapClientCustomerInsertUpdate.getCustomerNumberAsync response.`, + true + ); + CdkBase.createLogEvent(socket, "ERROR", `Error in GenerateDmsCustomerNumber - ${error}`); + throw new Error(error); + } } async function InsertDmsCustomer(socket, newCustomerNumber) { - try { - const soapClientCustomerInsertUpdate = await soap.createClientAsync( - CdkWsdl.CustomerInsertUpdate - ); - const soapResponseCustomerInsertUpdate = - await soapClientCustomerInsertUpdate.insertAsync( - { - arg0: CDK_CREDENTIALS, - arg1: {dealerId: socket.JobData.bodyshop.cdk_dealerid}, - arg2: {userId: null}, - arg3: { - //Copied the required fields from the other integration. - //TODO: Verify whether we need to bring more information in. - id: {value: newCustomerNumber}, - address: { - addressLine: - socket.JobData.ownr_addr1 && - socket.JobData.ownr_addr1.replace(replaceSpecialRegex, ""), - city: - socket.JobData.ownr_city && - socket.JobData.ownr_city.replace(replaceSpecialRegex, ""), - country: - socket.JobData.ownr_ctry && - socket.JobData.ownr_ctry.replace(replaceSpecialRegex, ""), - postalCode: InstanceMgr({imex: socket.JobData.ownr_zip && - socket.JobData.ownr_zip //TODO Need to remove for US Based customers. - .toUpperCase() - .replace(/\W/g, "") - .replace(/(...)/, "$1 "), rome: socket.JobData.ownr_zip }), - stateOrProvince: - socket.JobData.ownr_st && - socket.JobData.ownr_st.replace(replaceSpecialRegex, ""), - }, - contactInfo: { - mainTelephoneNumber: { - main: true, - value: - socket.JobData.ownr_ph1 && - socket.JobData.ownr_ph1.replace(replaceSpecialRegex, ""), - }, - email: { - desc: socket.JobData.ownr_ea ? "Other" : "CustomerDeclined", - value: socket.JobData.ownr_ea ? socket.JobData.ownr_ea : null, - }, - }, - demographics: null, - name1: { - companyName: - socket.JobData.ownr_co_nm && - socket.JobData.ownr_co_nm - .replace(replaceSpecialRegex, "") - .toUpperCase(), + try { + const soapClientCustomerInsertUpdate = await soap.createClientAsync(CdkWsdl.CustomerInsertUpdate); + const soapResponseCustomerInsertUpdate = await soapClientCustomerInsertUpdate.insertAsync( + { + arg0: CDK_CREDENTIALS, + arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid }, + arg2: { userId: null }, + arg3: { + //Copied the required fields from the other integration. + //TODO: Verify whether we need to bring more information in. + id: { value: newCustomerNumber }, + address: { + addressLine: socket.JobData.ownr_addr1 && socket.JobData.ownr_addr1.replace(replaceSpecialRegex, ""), + city: socket.JobData.ownr_city && socket.JobData.ownr_city.replace(replaceSpecialRegex, ""), + country: socket.JobData.ownr_ctry && socket.JobData.ownr_ctry.replace(replaceSpecialRegex, ""), + postalCode: InstanceMgr({ + imex: + socket.JobData.ownr_zip && + socket.JobData.ownr_zip //TODO Need to remove for US Based customers. + .toUpperCase() + .replace(/\W/g, "") + .replace(/(...)/, "$1 "), + rome: socket.JobData.ownr_zip + }), + stateOrProvince: socket.JobData.ownr_st && socket.JobData.ownr_st.replace(replaceSpecialRegex, "") + }, + contactInfo: { + mainTelephoneNumber: { + main: true, + value: socket.JobData.ownr_ph1 && socket.JobData.ownr_ph1.replace(replaceSpecialRegex, "") + }, + email: { + desc: socket.JobData.ownr_ea ? "Other" : "CustomerDeclined", + value: socket.JobData.ownr_ea ? socket.JobData.ownr_ea : null + } + }, + demographics: null, + name1: { + companyName: + socket.JobData.ownr_co_nm && socket.JobData.ownr_co_nm.replace(replaceSpecialRegex, "").toUpperCase(), - firstName: - socket.JobData.ownr_fn && - socket.JobData.ownr_fn - .replace(replaceSpecialRegex, "") - .toUpperCase(), - fullname: null, - lastName: - socket.JobData.ownr_ln && - socket.JobData.ownr_ln - .replace(replaceSpecialRegex, "") - .toUpperCase(), - middleName: null, - nameType: - socket.JobData.ownr_co_nm && - String(socket.JobData.ownr_co_nm).trim() !== "" - ? "Business" - : "Person", - suffix: null, - title: null, - }, - }, - }, + firstName: socket.JobData.ownr_fn && socket.JobData.ownr_fn.replace(replaceSpecialRegex, "").toUpperCase(), + fullname: null, + lastName: socket.JobData.ownr_ln && socket.JobData.ownr_ln.replace(replaceSpecialRegex, "").toUpperCase(), + middleName: null, + nameType: + socket.JobData.ownr_co_nm && String(socket.JobData.ownr_co_nm).trim() !== "" ? "Business" : "Person", + suffix: null, + title: null + } + } + }, - {} - ); + {} + ); - const [result, rawResponse, , rawRequest] = - soapResponseCustomerInsertUpdate; - CdkBase.createXmlEvent( - socket, - rawRequest, - `soapClientCustomerInsertUpdate.insertAsync request.` - ); + const [result, rawResponse, , rawRequest] = soapResponseCustomerInsertUpdate; + CdkBase.createXmlEvent(socket, rawRequest, `soapClientCustomerInsertUpdate.insertAsync request.`); - CdkBase.createXmlEvent( - socket, - rawResponse, - `soapClientCustomerInsertUpdate.insertAsync response.` - ); - CdkBase.createLogEvent( - socket, - "TRACE", - `soapClientCustomerInsertUpdate.insertAsync Result ${JSON.stringify( - result, - null, - 2 - )}` - ); - CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate); - const customer = result && result.return && result.return.customerParty; - return customer; - } catch (error) { - CdkBase.createXmlEvent( - socket, - error.request, - `soapClientCustomerInsertUpdate.insertAsync request.`, - true - ); + CdkBase.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.insertAsync response.`); + CdkBase.createLogEvent( + socket, + "TRACE", + `soapClientCustomerInsertUpdate.insertAsync Result ${JSON.stringify(result, null, 2)}` + ); + CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate); + const customer = result && result.return && result.return.customerParty; + return customer; + } catch (error) { + CdkBase.createXmlEvent(socket, error.request, `soapClientCustomerInsertUpdate.insertAsync request.`, true); - CdkBase.createXmlEvent( - socket, - error.response && error.response.data, - `soapClientCustomerInsertUpdate.insertAsync response.`, - true - ); - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in InsertDmsCustomer - ${error}` - ); - throw new Error(error); - } + CdkBase.createXmlEvent( + socket, + error.response && error.response.data, + `soapClientCustomerInsertUpdate.insertAsync response.`, + true + ); + CdkBase.createLogEvent(socket, "ERROR", `Error in InsertDmsCustomer - ${error}`); + throw new Error(error); + } } async function InsertDmsVehicle(socket) { - try { - const soapClientVehicleInsertUpdate = await soap.createClientAsync( - CdkWsdl.VehicleInsertUpdate - ); + try { + const soapClientVehicleInsertUpdate = await soap.createClientAsync(CdkWsdl.VehicleInsertUpdate); - const soapResponseVehicleInsertUpdate = - await soapClientVehicleInsertUpdate.insertAsync({ - arg0: CDK_CREDENTIALS, - arg1: {id: socket.JobData.bodyshop.cdk_dealerid}, - arg2: { - dealer: { - dealerNumber: socket.JobData.bodyshop.cdk_dealerid, - ...(socket.txEnvelope.inservicedate && { - inServiceDate: - socket.txEnvelope.dms_unsold === true - ? "" - : moment(socket.txEnvelope.inservicedate) - //.tz(socket.JobData.bodyshop.timezone) - .startOf("day") - .toISOString(), - }), - vehicleId: socket.DMSVid.vehiclesVehId, - }, - manufacturer: {}, - vehicle: { - deliveryDate: - socket.txEnvelope.dms_unsold === true - ? "" - : moment() - // .tz(socket.JobData.bodyshop.timezone) - .format("YYYYMMDD"), - licensePlateNo: - socket.JobData.plate_no === null - ? null - : String(socket.JobData.plate_no).replace(/([^\w]|_)/g, "") - .length === 0 - ? null - : String(socket.JobData.plate_no) - .replace(/([^\w]|_)/g, "") - .toUpperCase(), - make: socket.txEnvelope.dms_make, - modelAbrev: socket.txEnvelope.dms_model, - modelYear: socket.JobData.v_model_yr, - odometerStatus: socket.txEnvelope.kmout, - saleClassValue: "MISC", - VIN: socket.JobData.v_vin, - }, - owners: { - id: { - assigningPartyId: "CURRENT", - value: socket.DMSCust.id.value, - }, - }, - }, - arg3: "VEHICLES", - }); + const soapResponseVehicleInsertUpdate = await soapClientVehicleInsertUpdate.insertAsync({ + arg0: CDK_CREDENTIALS, + arg1: { id: socket.JobData.bodyshop.cdk_dealerid }, + arg2: { + dealer: { + dealerNumber: socket.JobData.bodyshop.cdk_dealerid, + ...(socket.txEnvelope.inservicedate && { + inServiceDate: + socket.txEnvelope.dms_unsold === true + ? "" + : moment(socket.txEnvelope.inservicedate) + //.tz(socket.JobData.bodyshop.timezone) + .startOf("day") + .toISOString() + }), + vehicleId: socket.DMSVid.vehiclesVehId + }, + manufacturer: {}, + vehicle: { + deliveryDate: + socket.txEnvelope.dms_unsold === true + ? "" + : moment() + // .tz(socket.JobData.bodyshop.timezone) + .format("YYYYMMDD"), + licensePlateNo: + socket.JobData.plate_no === null + ? null + : String(socket.JobData.plate_no).replace(/([^\w]|_)/g, "").length === 0 + ? null + : String(socket.JobData.plate_no) + .replace(/([^\w]|_)/g, "") + .toUpperCase(), + make: socket.txEnvelope.dms_make, + modelAbrev: socket.txEnvelope.dms_model, + modelYear: socket.JobData.v_model_yr, + odometerStatus: socket.txEnvelope.kmout, + saleClassValue: "MISC", + VIN: socket.JobData.v_vin + }, + owners: { + id: { + assigningPartyId: "CURRENT", + value: socket.DMSCust.id.value + } + } + }, + arg3: "VEHICLES" + }); - const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate; + const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate; - CdkBase.createXmlEvent( - socket, - rawRequest, - `soapClientVehicleInsertUpdate.insertAsync request.` - ); + CdkBase.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.insertAsync request.`); - CdkBase.createLogEvent( - socket, - "TRACE", - `soapClientVehicleInsertUpdate.insertAsync Result ${JSON.stringify( - result, - null, - 2 - )}` - ); - CdkBase.createXmlEvent( - socket, - rawResponse, - `soapClientVehicleInsertUpdate.insertAsync response.` - ); - CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate); - const VehicleFromDMS = result && result.return && result.return.vehicle; - return VehicleFromDMS; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in InsertDmsVehicle - ${error}` - ); - throw new Error(error); - } + CdkBase.createLogEvent( + socket, + "TRACE", + `soapClientVehicleInsertUpdate.insertAsync Result ${JSON.stringify(result, null, 2)}` + ); + CdkBase.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.insertAsync response.`); + CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate); + const VehicleFromDMS = result && result.return && result.return.vehicle; + return VehicleFromDMS; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in InsertDmsVehicle - ${error}`); + throw new Error(error); + } } async function UpdateDmsVehicle(socket) { - try { - const soapClientVehicleInsertUpdate = await soap.createClientAsync( - CdkWsdl.VehicleInsertUpdate - ); + try { + const soapClientVehicleInsertUpdate = await soap.createClientAsync(CdkWsdl.VehicleInsertUpdate); - let ids = []; + let ids = []; - //if it's a generic customer, don't update the vehicle owners. + //if it's a generic customer, don't update the vehicle owners. - if ( - socket.selectedCustomerId === - socket.JobData.bodyshop.cdk_configuration.generic_customer_number - ) { - ids = socket.DMSVeh && socket.DMSVeh.owners && socket.DMSVeh.owners; - } else { - const existingOwnerinVeh = - socket.DMSVeh && - socket.DMSVeh.owners && - socket.DMSVeh.owners.find( - (o) => o.id.value === socket.DMSCust.id.value - ); + if (socket.selectedCustomerId === socket.JobData.bodyshop.cdk_configuration.generic_customer_number) { + ids = socket.DMSVeh && socket.DMSVeh.owners && socket.DMSVeh.owners; + } else { + const existingOwnerinVeh = + socket.DMSVeh && + socket.DMSVeh.owners && + socket.DMSVeh.owners.find((o) => o.id.value === socket.DMSCust.id.value); - if (existingOwnerinVeh) { - ids = socket.DMSVeh.owners.map((o) => { - return { - id: { - assigningPartyId: - o.id.value === socket.DMSCust.id.value ? "CURRENT" : "PREVIOUS", - value: o.id.value, - }, - }; - }); - } else { - const oldOwner = - socket.DMSVeh && - socket.DMSVeh.owners && - socket.DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT"); - - ids = [ - { - id: { - assigningPartyId: "CURRENT", - value: socket.DMSCust.id.value, - }, - }, - ...(oldOwner - ? [ - { - id: { - assigningPartyId: "PREVIOUS", - value: oldOwner.id.value, - }, - }, - ] - : []), - ]; + if (existingOwnerinVeh) { + ids = socket.DMSVeh.owners.map((o) => { + return { + id: { + assigningPartyId: o.id.value === socket.DMSCust.id.value ? "CURRENT" : "PREVIOUS", + value: o.id.value } - } + }; + }); + } else { + const oldOwner = + socket.DMSVeh && + socket.DMSVeh.owners && + socket.DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT"); - const soapResponseVehicleInsertUpdate = - await soapClientVehicleInsertUpdate.updateAsync({ - arg0: CDK_CREDENTIALS, - arg1: {id: socket.JobData.bodyshop.cdk_dealerid}, - arg2: { - ...socket.DMSVeh, - dealer: { - ...socket.DMSVeh.dealer, - ...((socket.txEnvelope.inservicedate || - socket.DMSVeh.dealer.inServiceDate) && { - inServiceDate: - socket.txEnvelope.dms_unsold === true - ? "" - : moment( - socket.DMSVeh.dealer.inServiceDate || - socket.txEnvelope.inservicedate - ) - // .tz(socket.JobData.bodyshop.timezone) - .toISOString(), - }), - }, - vehicle: { - ...socket.DMSVeh.vehicle, - ...(socket.txEnvelope.dms_model_override - ? { - make: socket.txEnvelope.dms_make, - modelAbrev: socket.txEnvelope.dms_model, - } - : {}), - deliveryDate: - socket.txEnvelope.dms_unsold === true - ? "" - : moment(socket.DMSVeh.vehicle.deliveryDate) - //.tz(socket.JobData.bodyshop.timezone) - .toISOString(), - }, - owners: ids, - }, - arg3: "VEHICLES", - }); - const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate; - - CdkBase.createXmlEvent( - socket, - rawRequest, - `soapClientVehicleInsertUpdate.updateAsync request.` - ); - - CdkBase.createLogEvent( - socket, - "TRACE", - `soapClientVehicleInsertUpdate.updateAsync Result ${JSON.stringify( - result, - null, - 2 - )}` - ); - CdkBase.createXmlEvent( - socket, - rawResponse, - `soapClientVehicleInsertUpdate.updateAsync response.` - ); - CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate); - const VehicleFromDMS = result && result.return && result.return.vehicle; - return VehicleFromDMS; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in UpdateDmsVehicle - ${error}` - ); - throw new Error(error); + ids = [ + { + id: { + assigningPartyId: "CURRENT", + value: socket.DMSCust.id.value + } + }, + ...(oldOwner + ? [ + { + id: { + assigningPartyId: "PREVIOUS", + value: oldOwner.id.value + } + } + ] + : []) + ]; + } } + + const soapResponseVehicleInsertUpdate = await soapClientVehicleInsertUpdate.updateAsync({ + arg0: CDK_CREDENTIALS, + arg1: { id: socket.JobData.bodyshop.cdk_dealerid }, + arg2: { + ...socket.DMSVeh, + dealer: { + ...socket.DMSVeh.dealer, + ...((socket.txEnvelope.inservicedate || socket.DMSVeh.dealer.inServiceDate) && { + inServiceDate: + socket.txEnvelope.dms_unsold === true + ? "" + : moment(socket.DMSVeh.dealer.inServiceDate || socket.txEnvelope.inservicedate) + // .tz(socket.JobData.bodyshop.timezone) + .toISOString() + }) + }, + vehicle: { + ...socket.DMSVeh.vehicle, + ...(socket.txEnvelope.dms_model_override + ? { + make: socket.txEnvelope.dms_make, + modelAbrev: socket.txEnvelope.dms_model + } + : {}), + deliveryDate: + socket.txEnvelope.dms_unsold === true + ? "" + : moment(socket.DMSVeh.vehicle.deliveryDate) + //.tz(socket.JobData.bodyshop.timezone) + .toISOString() + }, + owners: ids + }, + arg3: "VEHICLES" + }); + const [result, rawResponse, , rawRequest] = soapResponseVehicleInsertUpdate; + + CdkBase.createXmlEvent(socket, rawRequest, `soapClientVehicleInsertUpdate.updateAsync request.`); + + CdkBase.createLogEvent( + socket, + "TRACE", + `soapClientVehicleInsertUpdate.updateAsync Result ${JSON.stringify(result, null, 2)}` + ); + CdkBase.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.updateAsync response.`); + CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate); + const VehicleFromDMS = result && result.return && result.return.vehicle; + return VehicleFromDMS; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in UpdateDmsVehicle - ${error}`); + throw new Error(error); + } } async function InsertServiceVehicleHistory(socket) { - try { - const soapClientServiceHistoryInsert = await soap.createClientAsync( - CdkWsdl.ServiceHistoryInsert - ); + try { + const soapClientServiceHistoryInsert = await soap.createClientAsync(CdkWsdl.ServiceHistoryInsert); - const soapResponseServiceHistoryInsert = - await soapClientServiceHistoryInsert.serviceHistoryHeaderInsertAsync({ - authToken: CDK_CREDENTIALS, - dealerId: {dealerId: socket.JobData.bodyshop.cdk_dealerid}, - serviceHistoryHeader: { - vehID: socket.DMSVid.vehiclesVehId, - roNumber: socket.JobData.ro_number.match(/\d+/g), - mileage: socket.txEnvelope.kmout, - openDate: moment(socket.JobData.actual_in) - .tz(socket.JobData.bodyshop.timezone) - .format("YYYY-MM-DD"), - openTime: moment(socket.JobData.actual_in) - .tz(socket.JobData.bodyshop.timezone) - .format("HH:mm:ss"), - closeDate: moment(socket.JobData.invoice_date) - .tz(socket.JobData.bodyshop.timezone) - .format("YYYY-MM-DD"), - closeTime: moment(socket.JobData.invoice_date) - .tz(socket.JobData.bodyshop.timezone) - .format("HH:mm:ss"), - comments: socket.txEnvelope.story, - cashierID: socket.JobData.bodyshop.cdk_configuration.cashierid, - }, - }); + const soapResponseServiceHistoryInsert = await soapClientServiceHistoryInsert.serviceHistoryHeaderInsertAsync({ + authToken: CDK_CREDENTIALS, + dealerId: { dealerId: socket.JobData.bodyshop.cdk_dealerid }, + serviceHistoryHeader: { + vehID: socket.DMSVid.vehiclesVehId, + roNumber: socket.JobData.ro_number.match(/\d+/g), + mileage: socket.txEnvelope.kmout, + openDate: moment(socket.JobData.actual_in).tz(socket.JobData.bodyshop.timezone).format("YYYY-MM-DD"), + openTime: moment(socket.JobData.actual_in).tz(socket.JobData.bodyshop.timezone).format("HH:mm:ss"), + closeDate: moment(socket.JobData.invoice_date).tz(socket.JobData.bodyshop.timezone).format("YYYY-MM-DD"), + closeTime: moment(socket.JobData.invoice_date).tz(socket.JobData.bodyshop.timezone).format("HH:mm:ss"), + comments: socket.txEnvelope.story, + cashierID: socket.JobData.bodyshop.cdk_configuration.cashierid + } + }); - const [result, rawResponse, , rawRequest] = - soapResponseServiceHistoryInsert; + const [result, rawResponse, , rawRequest] = soapResponseServiceHistoryInsert; - CdkBase.createXmlEvent( - socket, - rawRequest, - `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert request.` - ); + CdkBase.createXmlEvent(socket, rawRequest, `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert request.`); - CdkBase.createLogEvent( - socket, - "TRACE", - `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert Result ${JSON.stringify( - result, - null, - 2 - )}` - ); - CdkBase.createXmlEvent( - socket, - rawResponse, - `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert response.` - ); - CheckCdkResponseForError(socket, soapResponseServiceHistoryInsert); - return result && result.return; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in InsertServiceVehicleHistory - ${error}` - ); - throw new Error(error); - } + CdkBase.createLogEvent( + socket, + "TRACE", + `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert Result ${JSON.stringify(result, null, 2)}` + ); + CdkBase.createXmlEvent(socket, rawResponse, `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert response.`); + CheckCdkResponseForError(socket, soapResponseServiceHistoryInsert); + return result && result.return; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in InsertServiceVehicleHistory - ${error}`); + throw new Error(error); + } } async function InsertDmsStartWip(socket) { - try { - const soapClientAccountingGLInsertUpdate = await soap.createClientAsync( - CdkWsdl.AccountingGLInsertUpdate - ); + try { + const soapClientAccountingGLInsertUpdate = await soap.createClientAsync(CdkWsdl.AccountingGLInsertUpdate); - const soapResponseAccountingGLInsertUpdate = - await soapClientAccountingGLInsertUpdate.doStartWIPAsync({ - arg0: CDK_CREDENTIALS, - arg1: {dealerId: socket.JobData.bodyshop.cdk_dealerid}, - arg2: { - acctgDate: moment() - .tz(socket.JobData.bodyshop.timezone) - .format("YYYY-MM-DD"), - //socket.JobData.invoice_date - desc: - socket.txEnvelope.story && - socket.txEnvelope.story.replace(replaceSpecialRegex, ""), - docType: 10 || 7, //Need to check what this usually would be? Apparently it is almost always 10 or 7. - //1 Cash Receipt , 2 Check, 3 Journal Voucher, 4 Parts invoice, 5 Payable Invoice, 6 Recurring Entry, 7 Repair Order Invoice, 8 Vehicle Purchase Invoice, 9 Vehicle Sale Invoice, 10 Other, 11 Payroll, 12 Finance Charge, 13 FMLR Invoice, 14 Parts Credit Memo, 15 Manufacturer Document, 16 FMLR Credit Memo - m13Flag: 0, - refer: socket.JobData.ro_number, - srcCo: socket.JobData.bodyshop.cdk_configuration.srcco, - srcJrnl: socket.txEnvelope.journal, - userID: socket.JobData.bodyshop.cdk_configuration.cashierid, //Where is this coming from? - //userName: "IMEX", - }, - }); + const soapResponseAccountingGLInsertUpdate = await soapClientAccountingGLInsertUpdate.doStartWIPAsync({ + arg0: CDK_CREDENTIALS, + arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid }, + arg2: { + acctgDate: moment().tz(socket.JobData.bodyshop.timezone).format("YYYY-MM-DD"), + //socket.JobData.invoice_date + desc: socket.txEnvelope.story && socket.txEnvelope.story.replace(replaceSpecialRegex, ""), + docType: 10 || 7, //Need to check what this usually would be? Apparently it is almost always 10 or 7. + //1 Cash Receipt , 2 Check, 3 Journal Voucher, 4 Parts invoice, 5 Payable Invoice, 6 Recurring Entry, 7 Repair Order Invoice, 8 Vehicle Purchase Invoice, 9 Vehicle Sale Invoice, 10 Other, 11 Payroll, 12 Finance Charge, 13 FMLR Invoice, 14 Parts Credit Memo, 15 Manufacturer Document, 16 FMLR Credit Memo + m13Flag: 0, + refer: socket.JobData.ro_number, + srcCo: socket.JobData.bodyshop.cdk_configuration.srcco, + srcJrnl: socket.txEnvelope.journal, + userID: socket.JobData.bodyshop.cdk_configuration.cashierid //Where is this coming from? + //userName: "IMEX", + } + }); - const [result, rawResponse, , rawRequest] = - soapResponseAccountingGLInsertUpdate; + const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate; - CdkBase.createXmlEvent( - socket, - rawRequest, - `soapClientAccountingGLInsertUpdate.doStartWIPAsync request.` - ); + CdkBase.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doStartWIPAsync request.`); - CdkBase.createLogEvent( - socket, - "TRACE", - `soapClientAccountingGLInsertUpdate.doStartWIPAsync Result ${JSON.stringify( - result, - null, - 2 - )}` - ); - CdkBase.createXmlEvent( - socket, - rawResponse, - `soapClientAccountingGLInsertUpdate.doStartWIPAsync response.` - ); - CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate); - const TransactionHeader = result && result.return; - return TransactionHeader; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in InsertDmsStartWip - ${error}` - ); - throw new Error(error); - } + CdkBase.createLogEvent( + socket, + "TRACE", + `soapClientAccountingGLInsertUpdate.doStartWIPAsync Result ${JSON.stringify(result, null, 2)}` + ); + CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doStartWIPAsync response.`); + CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate); + const TransactionHeader = result && result.return; + return TransactionHeader; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in InsertDmsStartWip - ${error}`); + throw new Error(error); + } } async function InsertDmsBatchWip(socket) { - try { - const soapClientAccountingGLInsertUpdate = await soap.createClientAsync( - CdkWsdl.AccountingGLInsertUpdate - ); + try { + const soapClientAccountingGLInsertUpdate = await soap.createClientAsync(CdkWsdl.AccountingGLInsertUpdate); - const soapResponseAccountingGLInsertUpdate = - await soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync({ - arg0: CDK_CREDENTIALS, - arg1: {dealerId: socket.JobData.bodyshop.cdk_dealerid}, - arg2: { - transWIPs: await GenerateTransWips(socket), - }, - }); + const soapResponseAccountingGLInsertUpdate = await soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync({ + arg0: CDK_CREDENTIALS, + arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid }, + arg2: { + transWIPs: await GenerateTransWips(socket) + } + }); - const [result, rawResponse, , rawRequest] = - soapResponseAccountingGLInsertUpdate; + const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate; - CdkBase.createXmlEvent( - socket, - rawRequest, - `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync request.` - ); + CdkBase.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync request.`); - CdkBase.createLogEvent( - socket, - "TRACE", - `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync Result ${JSON.stringify( - result, - null, - 2 - )}` - ); - CdkBase.createXmlEvent( - socket, - rawResponse, - `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync response.` - ); - CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate); - const BatchWipResult = result && result.return; - return BatchWipResult; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in InsertDmsBatchWip - ${error}` - ); - throw new Error(error); - } + CdkBase.createLogEvent( + socket, + "TRACE", + `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync Result ${JSON.stringify(result, null, 2)}` + ); + CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync response.`); + CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate); + const BatchWipResult = result && result.return; + return BatchWipResult; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in InsertDmsBatchWip - ${error}`); + throw new Error(error); + } } async function GenerateTransWips(socket) { - const allocations = await CalcualteAllocations(socket, socket.JobData.id); - const wips = []; - allocations.forEach((alloc) => { - //Add the sale item from each allocation. - if (alloc.sale.getAmount() > 0 && !alloc.tax) { - const item = { - acct: alloc.profitCenter.dms_acctnumber, - cntl: - alloc.profitCenter.dms_control_override && - alloc.profitCenter.dms_control_override !== null && - alloc.profitCenter.dms_control_override !== undefined && - alloc.profitCenter.dms_control_override?.trim() !== "" - ? alloc.profitCenter.dms_control_override - : socket.JobData.ro_number, - cntl2: null, - credtMemoNo: null, - postAmt: alloc.sale.multiply(-1).getAmount(), - postDesc: null, - prod: null, - statCnt: 1, - transID: socket.DMSTransHeader.transID, - trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco, - }; - wips.push(item); - } + const allocations = await CalcualteAllocations(socket, socket.JobData.id); + const wips = []; + allocations.forEach((alloc) => { + //Add the sale item from each allocation. + if (alloc.sale.getAmount() > 0 && !alloc.tax) { + const item = { + acct: alloc.profitCenter.dms_acctnumber, + cntl: + alloc.profitCenter.dms_control_override && + alloc.profitCenter.dms_control_override !== null && + alloc.profitCenter.dms_control_override !== undefined && + alloc.profitCenter.dms_control_override?.trim() !== "" + ? alloc.profitCenter.dms_control_override + : socket.JobData.ro_number, + cntl2: null, + credtMemoNo: null, + postAmt: alloc.sale.multiply(-1).getAmount(), + postDesc: null, + prod: null, + statCnt: 1, + transID: socket.DMSTransHeader.transID, + trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco + }; + wips.push(item); + } - //Add the cost Item. - if (alloc.cost.getAmount() > 0 && !alloc.tax) { - const item = { - acct: alloc.costCenter.dms_acctnumber, - cntl: - alloc.costCenter.dms_control_override && - alloc.costCenter.dms_control_override !== null && - alloc.costCenter.dms_control_override !== undefined && - alloc.costCenter.dms_control_override?.trim() !== "" - ? alloc.costCenter.dms_control_override - : socket.JobData.ro_number, - cntl2: null, - credtMemoNo: null, - postAmt: alloc.cost.getAmount(), - postDesc: null, - prod: null, - statCnt: 1, - transID: socket.DMSTransHeader.transID, - trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco, - }; - wips.push(item); + //Add the cost Item. + if (alloc.cost.getAmount() > 0 && !alloc.tax) { + const item = { + acct: alloc.costCenter.dms_acctnumber, + cntl: + alloc.costCenter.dms_control_override && + alloc.costCenter.dms_control_override !== null && + alloc.costCenter.dms_control_override !== undefined && + alloc.costCenter.dms_control_override?.trim() !== "" + ? alloc.costCenter.dms_control_override + : socket.JobData.ro_number, + cntl2: null, + credtMemoNo: null, + postAmt: alloc.cost.getAmount(), + postDesc: null, + prod: null, + statCnt: 1, + transID: socket.DMSTransHeader.transID, + trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco + }; + wips.push(item); - const itemWip = { - acct: alloc.costCenter.dms_wip_acctnumber, - cntl: - alloc.costCenter.dms_control_override && - alloc.costCenter.dms_control_override !== null && - alloc.costCenter.dms_control_override !== undefined && - alloc.costCenter.dms_control_override?.trim() !== "" - ? alloc.costCenter.dms_control_override - : socket.JobData.ro_number, - cntl2: null, - credtMemoNo: null, - postAmt: alloc.cost.multiply(-1).getAmount(), - postDesc: null, - prod: null, - statCnt: 1, - transID: socket.DMSTransHeader.transID, - trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco, - }; - wips.push(itemWip); - //Add to the WIP account. - } + const itemWip = { + acct: alloc.costCenter.dms_wip_acctnumber, + cntl: + alloc.costCenter.dms_control_override && + alloc.costCenter.dms_control_override !== null && + alloc.costCenter.dms_control_override !== undefined && + alloc.costCenter.dms_control_override?.trim() !== "" + ? alloc.costCenter.dms_control_override + : socket.JobData.ro_number, + cntl2: null, + credtMemoNo: null, + postAmt: alloc.cost.multiply(-1).getAmount(), + postDesc: null, + prod: null, + statCnt: 1, + transID: socket.DMSTransHeader.transID, + trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco + }; + wips.push(itemWip); + //Add to the WIP account. + } - if (alloc.tax) { - // if (alloc.cost.getAmount() > 0) { - // const item = { - // acct: alloc.costCenter.dms_acctnumber, - // cntl: socket.JobData.ro_number, - // cntl2: null, - // credtMemoNo: null, - // postAmt: alloc.cost.getAmount(), - // postDesc: null, - // prod: null, - // statCnt: 1, - // transID: socket.DMSTransHeader.transID, - // trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco, - // }; + if (alloc.tax) { + // if (alloc.cost.getAmount() > 0) { + // const item = { + // acct: alloc.costCenter.dms_acctnumber, + // cntl: socket.JobData.ro_number, + // cntl2: null, + // credtMemoNo: null, + // postAmt: alloc.cost.getAmount(), + // postDesc: null, + // prod: null, + // statCnt: 1, + // transID: socket.DMSTransHeader.transID, + // trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco, + // }; - // wips.push(item); - // } + // wips.push(item); + // } - if (alloc.sale.getAmount() > 0) { - const item2 = { - acct: alloc.profitCenter.dms_acctnumber, - cntl: - alloc.profitCenter.dms_control_override && - alloc.profitCenter.dms_control_override !== null && - alloc.profitCenter.dms_control_override !== undefined && - alloc.profitCenter.dms_control_override?.trim() !== "" - ? alloc.profitCenter.dms_control_override - : socket.JobData.ro_number, - cntl2: null, - credtMemoNo: null, - postAmt: alloc.sale.multiply(-1).getAmount(), - postDesc: null, - prod: null, - statCnt: 1, - transID: socket.DMSTransHeader.transID, - trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco, - }; - wips.push(item2); - } - } - }); - - socket.txEnvelope.payers.forEach((payer) => { - const item = { - acct: payer.dms_acctnumber, - cntl: payer.controlnumber, - cntl2: null, - credtMemoNo: null, - postAmt: Math.round(payer.amount * 100), - postDesc: null, - prod: null, - statCnt: 1, - transID: socket.DMSTransHeader.transID, - trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco, + if (alloc.sale.getAmount() > 0) { + const item2 = { + acct: alloc.profitCenter.dms_acctnumber, + cntl: + alloc.profitCenter.dms_control_override && + alloc.profitCenter.dms_control_override !== null && + alloc.profitCenter.dms_control_override !== undefined && + alloc.profitCenter.dms_control_override?.trim() !== "" + ? alloc.profitCenter.dms_control_override + : socket.JobData.ro_number, + cntl2: null, + credtMemoNo: null, + postAmt: alloc.sale.multiply(-1).getAmount(), + postDesc: null, + prod: null, + statCnt: 1, + transID: socket.DMSTransHeader.transID, + trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco }; + wips.push(item2); + } + } + }); - wips.push(item); - }); - socket.transWips = wips; - return wips; + socket.txEnvelope.payers.forEach((payer) => { + const item = { + acct: payer.dms_acctnumber, + cntl: payer.controlnumber, + cntl2: null, + credtMemoNo: null, + postAmt: Math.round(payer.amount * 100), + postDesc: null, + prod: null, + statCnt: 1, + transID: socket.DMSTransHeader.transID, + trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco + }; + + wips.push(item); + }); + socket.transWips = wips; + return wips; } async function PostDmsBatchWip(socket) { - try { - const soapClientAccountingGLInsertUpdate = await soap.createClientAsync( - CdkWsdl.AccountingGLInsertUpdate - ); + try { + const soapClientAccountingGLInsertUpdate = await soap.createClientAsync(CdkWsdl.AccountingGLInsertUpdate); - const soapResponseAccountingGLInsertUpdate = - await soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync({ - arg0: CDK_CREDENTIALS, - arg1: {dealerId: socket.JobData.bodyshop.cdk_dealerid}, - arg2: { - postWIP: {opCode: "P", transID: socket.DMSTransHeader.transID}, - }, - }); + const soapResponseAccountingGLInsertUpdate = await soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync({ + arg0: CDK_CREDENTIALS, + arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid }, + arg2: { + postWIP: { opCode: "P", transID: socket.DMSTransHeader.transID } + } + }); - const [result, rawResponse, , rawRequest] = - soapResponseAccountingGLInsertUpdate; + const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate; - CdkBase.createXmlEvent( - socket, - rawRequest, - `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync request.` - ); + CdkBase.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync request.`); - CdkBase.createLogEvent( - socket, - "TRACE", - `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync Result ${JSON.stringify( - result, - null, - 2 - )}` - ); - CdkBase.createXmlEvent( - socket, - rawResponse, - `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.` - ); - // CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate); - const PostResult = result && result.return; - return PostResult; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in PostDmsBatchWip - ${error}` - ); - throw new Error(error); - } + CdkBase.createLogEvent( + socket, + "TRACE", + `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync Result ${JSON.stringify(result, null, 2)}` + ); + CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.`); + // CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate); + const PostResult = result && result.return; + return PostResult; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in PostDmsBatchWip - ${error}`); + throw new Error(error); + } } async function QueryDmsErrWip(socket) { - try { - const soapClientAccountingGLInsertUpdate = await soap.createClientAsync( - CdkWsdl.AccountingGLInsertUpdate - ); + try { + const soapClientAccountingGLInsertUpdate = await soap.createClientAsync(CdkWsdl.AccountingGLInsertUpdate); - const soapResponseAccountingGLInsertUpdate = - await soapClientAccountingGLInsertUpdate.doErrWIPAsync({ - arg0: CDK_CREDENTIALS, - arg1: {dealerId: socket.JobData.bodyshop.cdk_dealerid}, - arg2: socket.DMSTransHeader.transID, - }); + const soapResponseAccountingGLInsertUpdate = await soapClientAccountingGLInsertUpdate.doErrWIPAsync({ + arg0: CDK_CREDENTIALS, + arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid }, + arg2: socket.DMSTransHeader.transID + }); - const [result, rawResponse, , rawRequest] = - soapResponseAccountingGLInsertUpdate; + const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate; - CdkBase.createXmlEvent( - socket, - rawRequest, - `soapClientAccountingGLInsertUpdate.doErrWIPAsync request.` - ); + CdkBase.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doErrWIPAsync request.`); - CdkBase.createLogEvent( - socket, - "TRACE", - `soapClientAccountingGLInsertUpdate.doErrWIPAsync Result ${JSON.stringify( - result, - null, - 2 - )}` - ); - CdkBase.createXmlEvent( - socket, - rawResponse, - `soapClientAccountingGLInsertUpdate.doErrWIPAsync response.` - ); - CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate); - const PostResult = result && result.return; - return PostResult; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in QueryDmsErrWip - ${error}` - ); - throw new Error(error); - } + CdkBase.createLogEvent( + socket, + "TRACE", + `soapClientAccountingGLInsertUpdate.doErrWIPAsync Result ${JSON.stringify(result, null, 2)}` + ); + CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doErrWIPAsync response.`); + CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate); + const PostResult = result && result.return; + return PostResult; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in QueryDmsErrWip - ${error}`); + throw new Error(error); + } } async function DeleteDmsWip(socket) { - try { - const soapClientAccountingGLInsertUpdate = await soap.createClientAsync( - CdkWsdl.AccountingGLInsertUpdate - ); + try { + const soapClientAccountingGLInsertUpdate = await soap.createClientAsync(CdkWsdl.AccountingGLInsertUpdate); - const soapResponseAccountingGLInsertUpdate = - await soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync({ - arg0: CDK_CREDENTIALS, - arg1: {dealerId: socket.JobData.bodyshop.cdk_dealerid}, - arg2: { - postWIP: {opCode: "D", transID: socket.DMSTransHeader.transID}, - }, - }); + const soapResponseAccountingGLInsertUpdate = await soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync({ + arg0: CDK_CREDENTIALS, + arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid }, + arg2: { + postWIP: { opCode: "D", transID: socket.DMSTransHeader.transID } + } + }); - const [result, rawResponse, , rawRequest] = - soapResponseAccountingGLInsertUpdate; + const [result, rawResponse, , rawRequest] = soapResponseAccountingGLInsertUpdate; - CdkBase.createXmlEvent( - socket, - rawRequest, - `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync request.` - ); + CdkBase.createXmlEvent(socket, rawRequest, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync request.`); - CdkBase.createLogEvent( - socket, - "TRACE", - `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync Result ${JSON.stringify( - result, - null, - 2 - )}` - ); - CdkBase.createXmlEvent( - socket, - rawResponse, - `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.` - ); - CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate); - const PostResult = result && result.return; - return PostResult; - } catch (error) { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error in PostDmsBatchWip - ${error}` - ); - throw new Error(error); - } + CdkBase.createLogEvent( + socket, + "TRACE", + `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync Result ${JSON.stringify(result, null, 2)}` + ); + CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.`); + CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate); + const PostResult = result && result.return; + return PostResult; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in PostDmsBatchWip - ${error}`); + throw new Error(error); + } } async function MarkJobExported(socket, jobid) { - CdkBase.createLogEvent( - socket, - "DEBUG", - `Marking job as exported for id ${jobid}` - ); - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); - const result = await client - .setHeaders({Authorization: `Bearer ${socket.handshake.auth.token}`}) - .request(queries.MARK_JOB_EXPORTED, { - jobId: jobid, - job: { - status: - socket.JobData.bodyshop.md_ro_statuses.default_exported || - "Exported*", - date_exported: new Date(), - }, - log: { - bodyshopid: socket.JobData.bodyshop.id, - jobid: jobid, - successful: true, - useremail: socket.user.email, - metadata: socket.transWips, - }, - bill: { - exported: true, - exported_at: new Date(), - }, - }); + CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported for id ${jobid}`); + const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); + const result = await client + .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) + .request(queries.MARK_JOB_EXPORTED, { + jobId: jobid, + job: { + status: socket.JobData.bodyshop.md_ro_statuses.default_exported || "Exported*", + date_exported: new Date() + }, + log: { + bodyshopid: socket.JobData.bodyshop.id, + jobid: jobid, + successful: true, + useremail: socket.user.email, + metadata: socket.transWips + }, + bill: { + exported: true, + exported_at: new Date() + } + }); - return result; + return result; } async function InsertFailedExportLog(socket, error) { - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); - const result = await client - .setHeaders({Authorization: `Bearer ${socket.handshake.auth.token}`}) - .request(queries.INSERT_EXPORT_LOG, { - log: { - bodyshopid: socket.JobData.bodyshop.id, - jobid: socket.JobData.id, - successful: false, - message: [error], - useremail: socket.user.email, - }, - }); + const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); + const result = await client + .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) + .request(queries.INSERT_EXPORT_LOG, { + log: { + bodyshopid: socket.JobData.bodyshop.id, + jobid: socket.JobData.id, + successful: false, + message: [error], + useremail: socket.user.email + } + }); - return result; + return result; } diff --git a/server/cdk/cdk-wsdl.js b/server/cdk/cdk-wsdl.js index 3c7363517..032d780c2 100644 --- a/server/cdk/cdk-wsdl.js +++ b/server/cdk/cdk-wsdl.js @@ -1,91 +1,74 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const CdkBase = require("../web-sockets/web-socket"); const IMEX_CDK_USER = process.env.IMEX_CDK_USER, - IMEX_CDK_PASSWORD = process.env.IMEX_CDK_PASSWORD; + IMEX_CDK_PASSWORD = process.env.IMEX_CDK_PASSWORD; const CDK_CREDENTIALS = { - password: IMEX_CDK_PASSWORD, - username: IMEX_CDK_USER, + password: IMEX_CDK_PASSWORD, + username: IMEX_CDK_USER }; exports.CDK_CREDENTIALS = CDK_CREDENTIALS; const cdkDomain = - process.env.NODE_ENV === "production" - ? "https://3pa.dmotorworks.com" - : "https://uat-3pa.dmotorworks.com"; + process.env.NODE_ENV === "production" ? "https://3pa.dmotorworks.com" : "https://uat-3pa.dmotorworks.com"; function CheckCdkResponseForError(socket, soapResponse) { - if (!soapResponse[0]) { - //The response was null, this might be ok, it might not. - CdkBase.createLogEvent( - socket, - "WARNING", - `Warning detected in CDK Response - it appears to be null. Stack: ${ - new Error().stack - }` - ); - return; - } + if (!soapResponse[0]) { + //The response was null, this might be ok, it might not. + CdkBase.createLogEvent( + socket, + "WARNING", + `Warning detected in CDK Response - it appears to be null. Stack: ${new Error().stack}` + ); + return; + } - const ResultToCheck = soapResponse[0].return; + const ResultToCheck = soapResponse[0].return; - if (Array.isArray(ResultToCheck)) { - ResultToCheck.forEach((result) => checkIndividualResult(socket, result)); - } else { - checkIndividualResult(socket, ResultToCheck); - } + if (Array.isArray(ResultToCheck)) { + ResultToCheck.forEach((result) => checkIndividualResult(socket, result)); + } else { + checkIndividualResult(socket, ResultToCheck); + } } exports.CheckCdkResponseForError = CheckCdkResponseForError; function checkIndividualResult(socket, ResultToCheck) { - if ( - ResultToCheck.errorLevel === 0 || - ResultToCheck.errorLevel === "0" || - ResultToCheck.code === "success" || - (!ResultToCheck.code && !ResultToCheck.errorLevel) - ) - //TODO: Verify that this is the best way to detect errors. - return; - else { - CdkBase.createLogEvent( - socket, - "ERROR", - `Error detected in CDK Response - ${JSON.stringify( - ResultToCheck, - null, - 2 - )}` - ); + if ( + ResultToCheck.errorLevel === 0 || + ResultToCheck.errorLevel === "0" || + ResultToCheck.code === "success" || + (!ResultToCheck.code && !ResultToCheck.errorLevel) + ) + //TODO: Verify that this is the best way to detect errors. + return; + else { + CdkBase.createLogEvent( + socket, + "ERROR", + `Error detected in CDK Response - ${JSON.stringify(ResultToCheck, null, 2)}` + ); - throw new Error( - `Error found while validating CDK response for ${JSON.stringify( - ResultToCheck, - null, - 2 - )}:` - ); - } + throw new Error(`Error found while validating CDK response for ${JSON.stringify(ResultToCheck, null, 2)}:`); + } } exports.checkIndividualResult = checkIndividualResult; //const cdkDomain = "https://uat-3pa.dmotorworks.com"; exports.default = { - // VehicleSearch: `${cdkDomain}/pip-vehicle/services/VehicleSearch?wsdl`, - HelpDataBase: `${cdkDomain}/pip-help-database-location/services/HelpDatabaseLocation?wsdl`, - AccountingGLInsertUpdate: `${cdkDomain}/pip-accounting-gl/services/AccountingGLInsertUpdate?wsdl`, - VehicleInsertUpdate: `${cdkDomain}/pip-vehicle/services/VehicleInsertUpdate?wsdl`, - CustomerInsertUpdate: `${cdkDomain}/pip-customer/services/CustomerInsertUpdate?wsdl`, - CustomerSearch: `${cdkDomain}/pip-customer/services/CustomerSearch?wsdl`, - VehicleSearch: `${cdkDomain}/pip-vehicle/services/VehicleSearch?wsdl`, - ServiceHistoryInsert: `${cdkDomain}/pip-service-history-insert/services/ServiceHistoryInsert?wsdl`, + // VehicleSearch: `${cdkDomain}/pip-vehicle/services/VehicleSearch?wsdl`, + HelpDataBase: `${cdkDomain}/pip-help-database-location/services/HelpDatabaseLocation?wsdl`, + AccountingGLInsertUpdate: `${cdkDomain}/pip-accounting-gl/services/AccountingGLInsertUpdate?wsdl`, + VehicleInsertUpdate: `${cdkDomain}/pip-vehicle/services/VehicleInsertUpdate?wsdl`, + CustomerInsertUpdate: `${cdkDomain}/pip-customer/services/CustomerInsertUpdate?wsdl`, + CustomerSearch: `${cdkDomain}/pip-customer/services/CustomerSearch?wsdl`, + VehicleSearch: `${cdkDomain}/pip-vehicle/services/VehicleSearch?wsdl`, + ServiceHistoryInsert: `${cdkDomain}/pip-service-history-insert/services/ServiceHistoryInsert?wsdl` }; // The following login credentials will be used for all PIPs and all environments (User Acceptance Testing and Production). diff --git a/server/csi/csi.js b/server/csi/csi.js index 819a9ebc7..6000c3a3a 100644 --- a/server/csi/csi.js +++ b/server/csi/csi.js @@ -1,2 +1,2 @@ exports.lookup = require("./lookup").default; -exports.submit = require("./submit").default; \ No newline at end of file +exports.submit = require("./submit").default; diff --git a/server/csi/lookup.js b/server/csi/lookup.js index a3c156e96..57c9a0ef6 100644 --- a/server/csi/lookup.js +++ b/server/csi/lookup.js @@ -2,23 +2,20 @@ const path = require("path"); const queries = require("../graphql-client/queries"); const logger = require("../utils/logger"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const client = require("../graphql-client/graphql-client").client; exports.default = async (req, res) => { - try { - logger.log("csi-surveyID-lookup", "DEBUG", "csi", req.body.surveyId, null); - const gql_response = await client.request(queries.QUERY_SURVEY, { - surveyId: req.body.surveyId, - }); - res.status(200).json(gql_response); - } catch (error) { - logger.log("csi-surveyID-lookup", "ERROR", "csi", req.body.surveyId, error); - res.status(400).json(error); - } + try { + logger.log("csi-surveyID-lookup", "DEBUG", "csi", req.body.surveyId, null); + const gql_response = await client.request(queries.QUERY_SURVEY, { + surveyId: req.body.surveyId + }); + res.status(200).json(gql_response); + } catch (error) { + logger.log("csi-surveyID-lookup", "ERROR", "csi", req.body.surveyId, error); + res.status(400).json(error); + } }; diff --git a/server/csi/submit.js b/server/csi/submit.js index ea6e54164..45d8e56c0 100644 --- a/server/csi/submit.js +++ b/server/csi/submit.js @@ -2,28 +2,25 @@ const path = require("path"); const queries = require("../graphql-client/queries"); const logger = require("../utils/logger"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const client = require("../graphql-client/graphql-client").client; exports.default = async (req, res) => { - try { - logger.log("csi-surveyID-submit", "DEBUG", "csi", req.body.surveyId, null); - const gql_response = await client.request(queries.COMPLETE_SURVEY, { - surveyId: req.body.surveyId, - survey: { - response: req.body.values, - valid: false, - completedon: new Date(), - }, - }); - res.status(200).json(gql_response); - } catch (error) { - logger.log("csi-surveyID-submit", "ERROR", "csi", req.body.surveyId, error); - res.status(400).json(error); - } + try { + logger.log("csi-surveyID-submit", "DEBUG", "csi", req.body.surveyId, null); + const gql_response = await client.request(queries.COMPLETE_SURVEY, { + surveyId: req.body.surveyId, + survey: { + response: req.body.values, + valid: false, + completedon: new Date() + } + }); + res.status(200).json(gql_response); + } catch (error) { + logger.log("csi-surveyID-submit", "ERROR", "csi", req.body.surveyId, error); + res.status(400).json(error); + } }; diff --git a/server/data/arms.js b/server/data/arms.js index 53055665f..ec3d026e8 100644 --- a/server/data/arms.js +++ b/server/data/arms.js @@ -7,18 +7,15 @@ const storage = require("node-persist"); const _ = require("lodash"); const logger = require("../utils/logger"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const soap = require("soap"); -const {sendServerEmail} = require("../email/sendemail"); +const { sendServerEmail } = require("../email/sendemail"); const entegralEndpoint = - process.env.NODE_ENV === "production" - ? "https://ws.entegral.com/RepairOrderFolderService/RepairOrderFolderService.asmx?op=RepairOrderFolderAddRq" - : "https://uat-ws.armsbusinesssolutions.net/RepairOrderFolderService/RepairOrderFolderService.asmx?WSDL"; + process.env.NODE_ENV === "production" + ? "https://ws.entegral.com/RepairOrderFolderService/RepairOrderFolderService.asmx?op=RepairOrderFolderAddRq" + : "https://uat-ws.armsbusinesssolutions.net/RepairOrderFolderService/RepairOrderFolderService.asmx?WSDL"; const client = require("../graphql-client/graphql-client").client; const uuid = require("uuid").v4; @@ -26,1012 +23,945 @@ const uuid = require("uuid").v4; const momentFormat = "yyyy-MM-DDTHH:mm:ss.SSS"; function pollFunc(fn, timeout, interval) { - var startTime = new Date().getTime(); - (interval = interval || 1000), (canPoll = true); + var startTime = new Date().getTime(); + (interval = interval || 1000), (canPoll = true); - (function p() { - canPoll = - timeout === 0 ? true : new Date().getTime() - startTime <= timeout; - if (fn() && canPoll) { - // ensures the function exucutes - setTimeout(p, interval); - } - })(); + (function p() { + canPoll = timeout === 0 ? true : new Date().getTime() - startTime <= timeout; + if (fn() && canPoll) { + // ensures the function exucutes + setTimeout(p, interval); + } + })(); } pollFunc(getEntegralShopData, 0, 5 * 60 * 1000); //Set the metadata to refresh every 5 minutes. async function getEntegralShopData() { - // await storage.init({ logging: true }); - // const { bodyshops } = await client.request(queries.GET_ENTEGRAL_SHOPS); - // logger.log("set-entegral-shops-local-storage", "DEBUG", "API", null, null); - // await storage.setItem("entegralShops", bodyshops); - // return true; //Continue execution. + // await storage.init({ logging: true }); + // const { bodyshops } = await client.request(queries.GET_ENTEGRAL_SHOPS); + // logger.log("set-entegral-shops-local-storage", "DEBUG", "API", null, null); + // await storage.setItem("entegralShops", bodyshops); + // return true; //Continue execution. } exports.default = async (req, res) => { - res.sendStatus(401); - return; - //Query for the List of Bodyshop Clients. - const job = req.body.event.data.new; - logger.log("arms-job-update", "DEBUG", "api", job.id, null); + res.sendStatus(401); + return; + //Query for the List of Bodyshop Clients. + const job = req.body.event.data.new; + logger.log("arms-job-update", "DEBUG", "api", job.id, null); - let allEntegralShops = await storage.getItem("entegralShops"); + let allEntegralShops = await storage.getItem("entegralShops"); - if (!allEntegralShops) { - await getEntegralShopData(); - allEntegralShops = await storage.getItem("entegralShops"); - } + if (!allEntegralShops) { + await getEntegralShopData(); + allEntegralShops = await storage.getItem("entegralShops"); + } - //Is this job part of an entegral shop? - const bodyshop = allEntegralShops.find((b) => b.id === job.shopid); - if (!bodyshop) { - //This job is not for entegral based shops. - - res.sendStatus(200); - return; - } - - if (process.env.NODE_ENV === "PRODUCTION") { - res.sendStatus(200); - return; - } - //TODO: Check if an update should even be sent. - if (false) { - res.sendStatus(200); - return; - } - - try { - const transId = uuid(); // Can this actually be the job id? - let obj = { - RqUID: transId, - DocumentInfo: { - BMSVer: "4.0.0", - DocumentType: "RO", - DocumentVerCode: "EM", - DocumentVerNum: GetSupplementNumber(job.joblines), //TODO Get Supplement Number - DocumentStatus: GetDocumentstatus(job, bodyshop), - CreateDateTime: moment().format(momentFormat), - TransmitDateTime: moment().format(momentFormat), // Omitted from ARMS docs - }, - EventInfo: { - AssignmentEvent: { - CreateDateTime: - job.asgn_date && moment(job.asgn_date).format(momentFormat), - }, - // EstimateEvent: { - // UploadDateTime: moment().format(momentFormat), - // }, - RepairEvent: { - CreatedDateTime: (job.date_open - ? moment(job.date_open).tz(bodyshop.timezone) - : moment() - ).format(momentFormat), - ArrivalDateTime: - job.actual_in && - moment(job.actual_in).tz(bodyshop.timezone).format(momentFormat), - ArrivalOdometerReading: job.kmin, - TargetCompletionDateTime: - job.scheduled_completion && - moment(job.scheduled_completion) - .tz(bodyshop.timezone) - .format(momentFormat), - ActualCompletionDateTime: - job.actual_completion && - moment(job.actual_completion) - .tz(bodyshop.timezone) - .format(momentFormat), - ActualPickUpDateTime: - job.actual_delivery && - moment(job.actual_delivery) - .tz(bodyshop.timezone) - .format(momentFormat), - CloseDateTime: - job.date_exported && - moment(job.date_exported) - .tz(bodyshop.timezone) - .format(momentFormat), - }, - }, - RepairOrderHeader: { - AdminInfo: { - InsuranceCompany: { - Party: { - OrgInfo: { - CompanyName: job.ins_co_nm, - IDInfo: { - IDQualifierCode: "US", - //IDNum: 44, // ** Not sure where to get this entegral ID from? - }, - // Communications: [ - // { - // CommQualifier: "WA", - // Address: { - // Address1: job.ins_addr1, - // Address2: job.ins_addr2, - // City: job.ins_city, - // StateProvince: job.ins_st, - // PostalCode: job.ins_zip, - // CountryCode: job.ins_ctry, - // }, - // }, - // { - // CommQualifier: "WP", - // CommPhone: job.ins_ph1, - // }, - // { - // CommQualifier: "WF", - // CommPhone: job.ins_ph2, - // }, - // ], - }, - // ContactInfo: { - // ContactJobTitle: "Adjuster", - // ContactName: { - // FirstName: job.est_ct_fn, - // LastName: job.est_ct_ln, - // }, - // }, - }, - }, - // InsuranceAgent: { - // Party: { - // OrgInfo: { - // CompanyName: "Nationwide Insurance", - // Communications: { - // CommQualifier: "WP", - // CommPhone: "714-5551212", - // }, - // }, - // ContactInfo: { - // ContactJobTitle: "Insurance Agent", - // ContactName: { - // FirstName: "Paul", - // LastName: "White", - // }, - // }, - // }, - // }, - // Insured: { - // Party: { - // PersonInfo: { - // PersonName: { - // FirstName: job.insd_fn, - // LastName: job.insd_ln, - // }, - // }, - // }, - // }, - Owner: { - Party: { - PersonInfo: { - PersonName: { - FirstName: job.ownr_co_nm ? "N/A" : job.ownr_fn, - LastName: job.ownr_co_nm ? job.ownr_co_nm : job.ownr_ln, - }, - // Communications: [ - // { - // CommQualifier: "HA", - // Address: { - // Address1: job.ownr_addr1, - - // City: job.ownr_city, - // StateProvince: job.ownr_st, - // PostalCode: job.ownr_zip, - // CountryCode: job.ownr_ctry, - // }, - // }, - // { - // CommQualifier: "HP", - // CommPhone: job.ownr_ph1, - // }, - // { - // CommQualifier: "WP", - // CommPhone: job.ownr_ph2, - // }, - // { - // CommQualifier: "CP", - // CommPhone: job.ownr_ph1, - // }, - // { - // CommQualifier: "EM", - // CommEmail: job.ownr_ea, - // }, - // ], - }, - }, - }, - // Claimant: { - // Party: { - // PersonInfo: { - // PersonName: { - // FirstName: job.clm_ct_fn, - // LastName: job.clm_ct_ln, - // }, - // }, - // }, - // OwnerInd: true, - // }, - // Estimator: { - // Party: { - // PersonInfo: { - // PersonName: { - // FirstName: job.est_ct_fn, - // LastName: job.est_ct_ln, - // }, - // // IDInfo: { - // // IDQualifierCode: "US", - // // IDNum: 2941, - // // }, - // }, - // }, - // }, - RepairFacility: { - Party: { - OrgInfo: { - CompanyName: - process.env.NODE_ENV === "production" - ? bodyshop.shopname - : "IMEX Test Canadian Shop", - IDInfo: { - IDQualifierCode: "US", - IDNum: bodyshop.entegral_id, - }, - }, - }, - }, - }, - RepairOrderIDs: { - RepairOrderNum: job.ro_number, - // VendorCode: "C", - // EstimateDocumentID: "1223HJ76", - }, - RepairOrderType: "DirectRepairProgram", //Need to get from Entegral - //ReferralSourceType: "Yellow Pages", - VehicleInfo: { - VINInfo: { - VIN: { - VINNum: job.v_vin, - }, - }, - License: { - LicensePlateNum: job.plate_no, - }, - VehicleDesc: { - //ProductionDate: "2009-10", - ModelYear: - parseInt(job.v_model_yr) < 1900 - ? parseInt(job.v_model_yr) < - moment().tz(bodyshop.timezone).format("YY") - ? `20${job.v_model_yr}` - : `19${job.v_model_yr}` - : job.v_model_yr, - MakeDesc: job.v_make_desc, - ModelName: job.v_model_desc, - }, - // Paint: { - // Exterior: { - // Color: { - // ColorName: job.v_color, - // // OEMColorCode: "1M3", - // }, - // }, - // }, - // Body: { - // BodyStyle: "2 Door Convertible", - // Trim: { - // TrimCode: "1B3", - // }, - // }, - // Condition: { - // DrivableInd: job.driveable ? "Y" : "N", - // }, - }, - ClaimInfo: { - ClaimNum: job.clm_no, - PolicyInfo: { - PolicyNum: job.policy_no, - }, - LossInfo: { - Facts: { - LossDateTime: - job.loss_date && - moment(job.loss_date) - //.tz(bodyshop.timezone) - .format(momentFormat), - LossDescCode: "Collision", - PrimaryPOI: { - POICode: job.area_of_damage && job.area_of_damage.impact1, - }, - SecondaryPOI: { - POICode: job.area_of_damage && job.area_of_damage.impact2, - }, - }, - TotalLossInd: job.tlos_ind, - }, - }, - }, - ProfileInfo: { - ProfileName: "ImEX", - RateInfo: [ - { - RateType: "PA", - RateDesc: "Parts Tax", - TaxInfo: { - TaxType: "LS", - TaxableInd: true, - TaxTierInfo: { - TierNum: 1, - Percentage: job.parts_tax_rates.PAN.prt_tax_rt * 100, //TODO Find the best place to take the tax rates for parts. - }, - }, - }, - { - RateType: "LA", - RateDesc: "Labor Tax", - TaxInfo: { - TaxType: "LS", - TaxableInd: true, - TaxTierInfo: { - TierNum: 1, - Percentage: job.parts_tax_rates.PAN.prt_tax_rt * 100, //TODO Find the best place to take the tax rates for labor. - }, - }, - }, - { - RateType: "LAB", - RateDesc: "Body Labor", - RateTierInfo: { - TierNum: 1, - Rate: job.rate_lab, - }, - }, - { - RateType: "LAS", - RateDesc: "Structural Labor", - RateTierInfo: { - TierNum: 1, - Rate: job.rate_las, - }, - }, - { - RateType: "LAR", - RateDesc: "Refinish Labor", - RateTierInfo: { - TierNum: 1, - Rate: job.rate_lar, - }, - }, - { - RateType: "LAG", - RateDesc: "Glass Labor", - RateTierInfo: { - TierNum: 1, - Rate: job.rate_lag, - }, - }, - { - RateType: "LAF", - RateDesc: "Frame Labor", - RateTierInfo: { - TierNum: 1, - Rate: job.rate_laf, - }, - }, - { - RateType: "LAM", - RateDesc: "Mechancial Labor", - RateTierInfo: { - TierNum: 1, - Rate: job.rate_lam, - }, - }, - { - RateType: "LAU", - RateDesc: "User Defined Labor", - RateTierInfo: { - TierNum: 1, - Rate: job.rate_lau, - }, - }, - { - RateType: "MAPA", - RateDesc: "Paint Materials", - RateTierInfo: { - TierNum: 1, - Rate: job.rate_mapa, - ThresholdAmt: 0, - }, - MaterialCalcSettings: { - CalcMethodCode: 2, - CalcMaxAmt: 9999.99, //TODO Find threshold amts. - }, - }, - { - RateType: "MASH", - RateDesc: "Shop Materials", - RateTierInfo: { - TierNum: 1, - Rate: job.rate_mash, - }, - MaterialCalcSettings: { - CalcMethodCode: 4, - CalcMaxAmt: 9999.99, //TODO Find threshold amounts. - }, - }, - { - RateType: "MAHW", - RateDesc: "Hazardous Wastes Removal", - RateTierInfo: { - TierNum: 1, - Rate: job.rate_mahw, - }, - MaterialCalcSettings: { - //Todo Capture Calc Settings - CalcMethodCode: 2, - CalcMaxAmt: 10, - }, - }, - { - RateType: "MA2S", - RateDesc: "Two Stage Paint", - RateTierInfo: { - TierNum: 1, - Rate: job.rate_ma2s, - }, - MaterialCalcSettings: { - CalcMethodCode: 1, - CalcMaxAmt: 999999.99, - }, - }, - { - RateType: "MA2T", - RateDesc: "Two Tone Paint", - RateTierInfo: { - TierNum: 1, - Rate: job.rate_ma2t, - }, - MaterialCalcSettings: { - CalcMethodCode: 1, - CalcMaxAmt: 999999.99, - }, - }, - { - RateType: "MA3S", - RateDesc: "Three Stage Paint", - RateTierInfo: { - TierNum: 1, - Rate: job.rate_ma3s, - }, - MaterialCalcSettings: { - CalcMethodCode: 1, - CalcMaxAmt: 999999.99, - }, - }, - ], - }, - //StorageDuration: 17, - RepairTotalsInfo: { - LaborTotalsInfo: [ - { - TotalType: "LAB", - TotalTypeDesc: "Body Labor", - TotalHours: job.job_totals.rates.lab.hours, - TotalAmt: Dinero(job.job_totals.rates.lab.total).toFormat("0.00"), - }, - { - TotalType: "LAF", - TotalTypeDesc: "Frame Labor", - TotalHours: job.job_totals.rates.laf.hours, - TotalAmt: Dinero(job.job_totals.rates.laf.total).toFormat("0.00"), - }, - { - TotalType: "LAM", - TotalTypeDesc: "Mechanical Labor", - TotalHours: job.job_totals.rates.lam.hours, - TotalAmt: Dinero(job.job_totals.rates.lam.total).toFormat("0.00"), - }, - { - TotalType: "LAR", - TotalTypeDesc: "Refinish Labor", - TotalHours: job.job_totals.rates.lar.hours, - TotalAmt: Dinero(job.job_totals.rates.lar.total).toFormat("0.00"), - }, - ], - PartsTotalsInfo: [ - { - TotalType: "PAA", - TotalTypeDesc: "Aftermarket Parts", - TotalAmt: Dinero( - job.job_totals.parts.parts.list.PAA && - job.job_totals.parts.parts.list.PAA.total - ).toFormat("0.00"), - }, - { - TotalType: "PAC", - TotalTypeDesc: "Re-Chromed Parts", - TotalAmt: Dinero( - job.job_totals.parts.parts.list.PAC && - job.job_totals.parts.parts.list.PAC.total - ).toFormat("0.00"), - }, - { - TotalType: "PAG", - TotalTypeDesc: "Glass Parts", - TotalAmt: Dinero( - job.job_totals.parts.parts.list.PAG && - job.job_totals.parts.parts.list.PAG.total - ).toFormat("0.00"), - }, - { - TotalType: "PAL", - TotalTypeDesc: "LKQ/Used Parts", - TotalAmt: Dinero( - job.job_totals.parts.parts.list.PAL && - job.job_totals.parts.parts.list.PAL.total - ).toFormat("0.00"), - }, - { - TotalType: "PAM", - TotalTypeDesc: "Remanufactured Parts", - TotalAmt: Dinero( - job.job_totals.parts.parts.list.PAM && - job.job_totals.parts.parts.list.PAM.total - ).toFormat("0.00"), - }, - { - TotalType: "PAN", - TotalTypeDesc: "New Parts", - TotalAmt: Dinero( - job.job_totals.parts.parts.list.PAN && - job.job_totals.parts.parts.list.PAN.total - ).toFormat("0.00"), - }, - { - TotalType: "PAR", - TotalTypeDesc: "Recored Parts", - TotalAmt: Dinero( - job.job_totals.parts.parts.list.PAR && - job.job_totals.parts.parts.list.PAR.total - ).toFormat("0.00"), - }, - ], - OtherChargesTotalsInfo: [ - { - TotalType: "OTSL", - TotalTypeDesc: "Sublet", - TotalAmt: Dinero(job.job_totals.parts.sublets.total).toFormat( - "0.00" - ), - }, - { - TotalType: "MAPA", - TotalTypeDesc: "Paint Materials", - TotalAmt: Dinero(job.job_totals.rates.mapa.total).toFormat("0.00"), - }, - { - TotalType: "MASH", - TotalTypeDesc: "Shop Materials", - TotalAmt: Dinero(job.job_totals.rates.mash.total).toFormat("0.00"), - }, - // { - // TotalType: "MAHW", - // TotalTypeDesc: "Hazardous Wastes Removal", - // TotalAmt: Dinero(job.job_totals.rates.mahw.total).toFormat( - // 0.0 - // ), - // }, - { - TotalType: "OTST", - TotalTypeDesc: "Storage", - TotalAmt: Dinero(job.job_totals.additional.storage).toFormat( - "0.00" - ), - }, - { - TotalType: "OTTW", - TotalTypeDesc: "Towing", - TotalAmt: Dinero(job.job_totals.additional.towing).toFormat("0.00"), - }, - { - TotalType: "OTAC", - TotalTypeDesc: "Additional Charges", - TotalAmt: Dinero(job.job_totals.additional.additionalCosts) - .add(Dinero(job.job_totals.additional.pvrt)) - .toFormat("0.00"), - }, - ], - SummaryTotalsInfo: [ - { - TotalType: "TOT", - TotalSubType: "TT", - TotalTypeDesc: "Gross Total", - TotalAmt: Dinero(job.job_totals.totals.total_repairs).toFormat( - "0.00" - ), - }, - { - TotalType: "TOT", - TotalSubType: "T2", - TotalTypeDesc: "Net Total", - TotalAmt: Dinero(job.job_totals.totals.subtotal).toFormat("0.00"), - }, - { - TotalType: "TOT", - TotalSubType: "SM", - TotalTypeDesc: "Supplement Total", - TotalAmt: job.cieca_ttl - ? job.cieca_ttl.data.supp_amt - : Dinero().toFormat("0.00"), - }, - { - TotalType: "TOT", - TotalSubType: "F7", - TotalTypeDesc: "Sales Tax", - TotalAmt: Dinero(job.job_totals.totals.state_tax).toFormat("0.00"), - }, - { - TotalType: "TOT", - TotalSubType: "GST", - TotalTypeDesc: "GST Tax", - TotalAmt: Dinero(job.job_totals.totals.federal_tax).toFormat( - "0.00" - ), - }, - { - TotalType: "TOT", - TotalSubType: "D8", - TotalTypeDesc: "Bottom Line Discount", - TotalAmt: Dinero(job.job_totals.additional.adjustments).toFormat( - "0.00" - ), - }, - { - TotalType: "TOT", - TotalSubType: "D2", - TotalTypeDesc: "Deductible", - TotalAmt: Dinero({ - amount: Math.round((job.ded_amt || 0) * 100), - }).toFormat("0.00"), - }, - { - TotalType: "TOT", - TotalSubType: "BTR", - TotalTypeDesc: "Betterment", - TotalAmt: Dinero( - job.job_totals.totals.custPayable.dep_taxes - ).toFormat("0.00"), - }, - { - TotalType: "TOT", - TotalSubType: "AA", - TotalTypeDesc: "Appearance Allowance", - TotalAmt: Dinero().toFormat("0.00"), - }, - { - TotalType: "TOT", - TotalSubType: "DEPOSIT", - TotalTypeDesc: "Deposit", - TotalAmt: Dinero().toFormat("0.00"), - }, - { - TotalType: "TOT", - TotalSubType: "INS", - TotalTypeDesc: "Insurance Pay", - TotalAmt: Dinero(job.job_totals.totals.total_repairs) - .subtract(Dinero(job.job_totals.totals.custPayable.total)) - .toFormat("0.00"), - }, - { - TotalType: "TOT", - TotalSubType: "CUST", - TotalTypeDesc: "Customer Pay", - TotalAmt: Dinero(job.job_totals.totals.custPayable.total).toFormat( - "0.00" - ), - }, - ], - // RepairTotalsType: 1, - }, - // RepairLabor: { - // LaborAllocations: { - // LaborAllocation: [ - // { - // LaborAllocationUUID: - // "426cce3a-efa7-44d9-b76e-50b9102c4198", - // LaborType: "LAB", - // Technician: { - // Employee: { - // PersonInfo: { - // PersonName: { - // FirstName: "Jose", - // LastName: "Gonzalez", - // }, - // IDInfo: { - // IDQualifierCode: "US", - // IDNum: 2987, - // }, - // }, - // }, - // }, - // AllocatedHours: 3.5, - // }, - // { - // LaborAllocationUUID: - // "426cce3a-efa7-44d9-b76e-50b9102c4199", - // LaborType: "LAR", - // Technician: { - // Employee: { - // PersonInfo: { - // PersonName: { - // FirstName: "Rcardo", - // LastName: "Himenez", - // }, - // IDInfo: { - // IDQualifierCode: "US", - // IDNum: 2989, - // }, - // }, - // }, - // }, - // AllocatedHours: 5.5, - // }, - // ], - // }, - // }, - ProductionStatus: { - ProductionStage: { - ProductionStageCode: GetProductionStageCode(job, bodyshop), - ProductionStageDateTime: moment() - .tz(bodyshop.timezone) - .format(momentFormat), - // ProductionStageStatusComment: - // "Going to be painted this afternoon", - }, - RepairStatus: { - RepairStatusCode: GetRepairStatusCode(job), - RepairStatusDateTime: moment() - .tz(bodyshop.timezone) - .format(momentFormat), - // RepairStatusMemo: "Waiting on back ordered parts", - }, - }, - // RepairOrderNotes: { - // RepairOrderNote: { - // LineSequenceNum: 1, - // Note: "Revision Requested : approved--but needs est separated.8/22/2008 11:58:53 AM", - // CreateDateTime: "2008-08-22T11:58:53", - // AuthoredBy: { - // FirstName: { - // "#text": "Elizabeth/FirstName>", - // LastName: "Unis", - // }, - // }, - // RepairOrderNote: { - // LineSequenceNum: 2, - // Note: "Approved : 8/26/2008 12:21:08 PM", - // CreateDateTime: "2008-08-26T12:21:08", - // AuthoredBy: { - // FirstName: { - // "#text": "Elizabeth/FirstName>", - // LastName: "Unis", - // }, - // }, - // }, - // }, - // }, - }; - - deleteNullKeys(obj); - - try { - const entegralSoapClient = await soap.createClientAsync( - entegralEndpoint, - { - ignoredNamespaces: true, - wsdl_options: { - // useEmptyTag: true, - }, - wsdl_headers: { - Authorization: `Basic ${new Buffer.from( - `${process.env.ENTEGRAL_USER}:${process.env.ENTEGRAL_PASSWORD}` - ).toString("base64")}`, - }, - } - ); - - entegralSoapClient.setSecurity( - new soap.BasicAuthSecurity( - process.env.ENTEGRAL_USER, - process.env.ENTEGRAL_PASSWORD - ) - ); - - const entegralResponse = - await entegralSoapClient.RepairOrderFolderAddRqAsync( - obj, - function (err, result, rawResponse, soapHeader, rawRequest) { - fs.writeFileSync(`./logs/arms-request.xml`, rawRequest); - fs.writeFileSync(`./logs/arms-response.xml`, rawResponse); - - logger.log("arms-job-xml-request", "DEBUG", "api", job.id, { - xml: rawRequest, - }); - logger.log("arms-job-xml-response", "DEBUG", "api", job.id, { - xml: rawResponse, - }); - - if (err) { - sendServerEmail({ - subject: `ARMS Update Failed: ${bodyshop.shopname} - ${job.ro_number}`, - text: `Error: ${JSON.stringify(error)}`, - }); - } - - res.status(200).json(err || result); - } - ); - - const [result, rawResponse, , rawRequest] = entegralResponse; - } catch (error) { - logger.log("arms-failed-job-upload", "ERROR", "api", job.shopid, { - job: JSON.stringify({id: job.id, ro_number: job.ro_number}), - error: error.message || JSON.stringify(error), - }); - console.log(error); - } - } catch (error) { - logger.log("arms-failed-job", "ERROR", "api", job.shopid, { - job: JSON.stringify({id: job.id, ro_number: job.ro_number}), - error: error.message || JSON.stringify(error), - }); - } + //Is this job part of an entegral shop? + const bodyshop = allEntegralShops.find((b) => b.id === job.shopid); + if (!bodyshop) { + //This job is not for entegral based shops. res.sendStatus(200); return; - const allErrors = []; - try { - for (const bodyshop of bodyshops) { - logger.log("arms-start-shop-extract", "DEBUG", "api", bodyshop.id, { - shopname: bodyshop.shopname, - }); - const erroredJobs = []; - try { - const {jobs} = await client.request(queries.ENTEGRAL_EXPORT, { - bodyshopid: bodyshop.id, - }); - const jobsToPush = []; + } - if (erroredJobs.length > 0) { - logger.log("arms-failed-jobs", "ERROR", "api", bodyshop.id, { - count: erroredJobs.length, - jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)), - }); - allErrors = [...allErrors, ...erroredJobs]; + if (process.env.NODE_ENV === "PRODUCTION") { + res.sendStatus(200); + return; + } + //TODO: Check if an update should even be sent. + if (false) { + res.sendStatus(200); + return; + } + + try { + const transId = uuid(); // Can this actually be the job id? + let obj = { + RqUID: transId, + DocumentInfo: { + BMSVer: "4.0.0", + DocumentType: "RO", + DocumentVerCode: "EM", + DocumentVerNum: GetSupplementNumber(job.joblines), //TODO Get Supplement Number + DocumentStatus: GetDocumentstatus(job, bodyshop), + CreateDateTime: moment().format(momentFormat), + TransmitDateTime: moment().format(momentFormat) // Omitted from ARMS docs + }, + EventInfo: { + AssignmentEvent: { + CreateDateTime: job.asgn_date && moment(job.asgn_date).format(momentFormat) + }, + // EstimateEvent: { + // UploadDateTime: moment().format(momentFormat), + // }, + RepairEvent: { + CreatedDateTime: (job.date_open ? moment(job.date_open).tz(bodyshop.timezone) : moment()).format( + momentFormat + ), + ArrivalDateTime: job.actual_in && moment(job.actual_in).tz(bodyshop.timezone).format(momentFormat), + ArrivalOdometerReading: job.kmin, + TargetCompletionDateTime: + job.scheduled_completion && moment(job.scheduled_completion).tz(bodyshop.timezone).format(momentFormat), + ActualCompletionDateTime: + job.actual_completion && moment(job.actual_completion).tz(bodyshop.timezone).format(momentFormat), + ActualPickUpDateTime: + job.actual_delivery && moment(job.actual_delivery).tz(bodyshop.timezone).format(momentFormat), + CloseDateTime: job.date_exported && moment(job.date_exported).tz(bodyshop.timezone).format(momentFormat) + } + }, + RepairOrderHeader: { + AdminInfo: { + InsuranceCompany: { + Party: { + OrgInfo: { + CompanyName: job.ins_co_nm, + IDInfo: { + IDQualifierCode: "US" + //IDNum: 44, // ** Not sure where to get this entegral ID from? } - - logger.log("arms-end-shop-extract", "DEBUG", "api", bodyshop.id, { - shopname: bodyshop.shopname, - }); - - try { - const entegralSoapClient = await soap.createClientAsync( - entegralEndpoint, - { - ignoredNamespaces: true, - wsdl_options: { - // useEmptyTag: true, - }, - wsdl_headers: { - Authorization: `Basic ${new Buffer.from( - `${process.env.ENTEGRAL_USER}:${process.env.ENTEGRAL_PASSWORD}` - ).toString("base64")}`, - }, - } - ); - - entegralSoapClient.setSecurity( - new soap.BasicAuthSecurity( - process.env.ENTEGRAL_USER, - process.env.ENTEGRAL_PASSWORD - ) - ); - - const entegralResponse = - await entegralSoapClient.RepairOrderFolderAddRqAsync( - [jobsToPush[0]], - function (err, result, rawResponse, soapHeader, rawRequest) { - fs.writeFileSync(`./logs/arms-request.xml`, rawRequest); - fs.writeFileSync(`./logs/arms-response.xml`, rawResponse); - - res.json(err || result); - } - ); - - const [result, rawResponse, , rawRequest] = entegralResponse; - } catch (error) { - fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); - console.log(error); - } - } catch (error) { - //Error at the shop level. - logger.log("arms-error-shop", "ERROR", "api", bodyshop.id, { - error, - }); - - allErrors.push({ - bodyshopid: bodyshop.id, - imexshopid: bodyshop.imexshopid, - fatal: true, - errors: [error.toString()], - }); - } finally { - allErrors.push({ - bodyshopid: bodyshop.id, - imexshopid: bodyshop.imexshopid, - errors: erroredJobs, - }); + // Communications: [ + // { + // CommQualifier: "WA", + // Address: { + // Address1: job.ins_addr1, + // Address2: job.ins_addr2, + // City: job.ins_city, + // StateProvince: job.ins_st, + // PostalCode: job.ins_zip, + // CountryCode: job.ins_ctry, + // }, + // }, + // { + // CommQualifier: "WP", + // CommPhone: job.ins_ph1, + // }, + // { + // CommQualifier: "WF", + // CommPhone: job.ins_ph2, + // }, + // ], + } + // ContactInfo: { + // ContactJobTitle: "Adjuster", + // ContactName: { + // FirstName: job.est_ct_fn, + // LastName: job.est_ct_ln, + // }, + // }, } + }, + // InsuranceAgent: { + // Party: { + // OrgInfo: { + // CompanyName: "Nationwide Insurance", + // Communications: { + // CommQualifier: "WP", + // CommPhone: "714-5551212", + // }, + // }, + // ContactInfo: { + // ContactJobTitle: "Insurance Agent", + // ContactName: { + // FirstName: "Paul", + // LastName: "White", + // }, + // }, + // }, + // }, + // Insured: { + // Party: { + // PersonInfo: { + // PersonName: { + // FirstName: job.insd_fn, + // LastName: job.insd_ln, + // }, + // }, + // }, + // }, + Owner: { + Party: { + PersonInfo: { + PersonName: { + FirstName: job.ownr_co_nm ? "N/A" : job.ownr_fn, + LastName: job.ownr_co_nm ? job.ownr_co_nm : job.ownr_ln + } + // Communications: [ + // { + // CommQualifier: "HA", + // Address: { + // Address1: job.ownr_addr1, + + // City: job.ownr_city, + // StateProvince: job.ownr_st, + // PostalCode: job.ownr_zip, + // CountryCode: job.ownr_ctry, + // }, + // }, + // { + // CommQualifier: "HP", + // CommPhone: job.ownr_ph1, + // }, + // { + // CommQualifier: "WP", + // CommPhone: job.ownr_ph2, + // }, + // { + // CommQualifier: "CP", + // CommPhone: job.ownr_ph1, + // }, + // { + // CommQualifier: "EM", + // CommEmail: job.ownr_ea, + // }, + // ], + } + } + }, + // Claimant: { + // Party: { + // PersonInfo: { + // PersonName: { + // FirstName: job.clm_ct_fn, + // LastName: job.clm_ct_ln, + // }, + // }, + // }, + // OwnerInd: true, + // }, + // Estimator: { + // Party: { + // PersonInfo: { + // PersonName: { + // FirstName: job.est_ct_fn, + // LastName: job.est_ct_ln, + // }, + // // IDInfo: { + // // IDQualifierCode: "US", + // // IDNum: 2941, + // // }, + // }, + // }, + // }, + RepairFacility: { + Party: { + OrgInfo: { + CompanyName: process.env.NODE_ENV === "production" ? bodyshop.shopname : "IMEX Test Canadian Shop", + IDInfo: { + IDQualifierCode: "US", + IDNum: bodyshop.entegral_id + } + } + } + } + }, + RepairOrderIDs: { + RepairOrderNum: job.ro_number + // VendorCode: "C", + // EstimateDocumentID: "1223HJ76", + }, + RepairOrderType: "DirectRepairProgram", //Need to get from Entegral + //ReferralSourceType: "Yellow Pages", + VehicleInfo: { + VINInfo: { + VIN: { + VINNum: job.v_vin + } + }, + License: { + LicensePlateNum: job.plate_no + }, + VehicleDesc: { + //ProductionDate: "2009-10", + ModelYear: + parseInt(job.v_model_yr) < 1900 + ? parseInt(job.v_model_yr) < moment().tz(bodyshop.timezone).format("YY") + ? `20${job.v_model_yr}` + : `19${job.v_model_yr}` + : job.v_model_yr, + MakeDesc: job.v_make_desc, + ModelName: job.v_model_desc + } + // Paint: { + // Exterior: { + // Color: { + // ColorName: job.v_color, + // // OEMColorCode: "1M3", + // }, + // }, + // }, + // Body: { + // BodyStyle: "2 Door Convertible", + // Trim: { + // TrimCode: "1B3", + // }, + // }, + // Condition: { + // DrivableInd: job.driveable ? "Y" : "N", + // }, + }, + ClaimInfo: { + ClaimNum: job.clm_no, + PolicyInfo: { + PolicyNum: job.policy_no + }, + LossInfo: { + Facts: { + LossDateTime: + job.loss_date && + moment(job.loss_date) + //.tz(bodyshop.timezone) + .format(momentFormat), + LossDescCode: "Collision", + PrimaryPOI: { + POICode: job.area_of_damage && job.area_of_damage.impact1 + }, + SecondaryPOI: { + POICode: job.area_of_damage && job.area_of_damage.impact2 + } + }, + TotalLossInd: job.tlos_ind + } + } + }, + ProfileInfo: { + ProfileName: "ImEX", + RateInfo: [ + { + RateType: "PA", + RateDesc: "Parts Tax", + TaxInfo: { + TaxType: "LS", + TaxableInd: true, + TaxTierInfo: { + TierNum: 1, + Percentage: job.parts_tax_rates.PAN.prt_tax_rt * 100 //TODO Find the best place to take the tax rates for parts. + } + } + }, + { + RateType: "LA", + RateDesc: "Labor Tax", + TaxInfo: { + TaxType: "LS", + TaxableInd: true, + TaxTierInfo: { + TierNum: 1, + Percentage: job.parts_tax_rates.PAN.prt_tax_rt * 100 //TODO Find the best place to take the tax rates for labor. + } + } + }, + { + RateType: "LAB", + RateDesc: "Body Labor", + RateTierInfo: { + TierNum: 1, + Rate: job.rate_lab + } + }, + { + RateType: "LAS", + RateDesc: "Structural Labor", + RateTierInfo: { + TierNum: 1, + Rate: job.rate_las + } + }, + { + RateType: "LAR", + RateDesc: "Refinish Labor", + RateTierInfo: { + TierNum: 1, + Rate: job.rate_lar + } + }, + { + RateType: "LAG", + RateDesc: "Glass Labor", + RateTierInfo: { + TierNum: 1, + Rate: job.rate_lag + } + }, + { + RateType: "LAF", + RateDesc: "Frame Labor", + RateTierInfo: { + TierNum: 1, + Rate: job.rate_laf + } + }, + { + RateType: "LAM", + RateDesc: "Mechancial Labor", + RateTierInfo: { + TierNum: 1, + Rate: job.rate_lam + } + }, + { + RateType: "LAU", + RateDesc: "User Defined Labor", + RateTierInfo: { + TierNum: 1, + Rate: job.rate_lau + } + }, + { + RateType: "MAPA", + RateDesc: "Paint Materials", + RateTierInfo: { + TierNum: 1, + Rate: job.rate_mapa, + ThresholdAmt: 0 + }, + MaterialCalcSettings: { + CalcMethodCode: 2, + CalcMaxAmt: 9999.99 //TODO Find threshold amts. + } + }, + { + RateType: "MASH", + RateDesc: "Shop Materials", + RateTierInfo: { + TierNum: 1, + Rate: job.rate_mash + }, + MaterialCalcSettings: { + CalcMethodCode: 4, + CalcMaxAmt: 9999.99 //TODO Find threshold amounts. + } + }, + { + RateType: "MAHW", + RateDesc: "Hazardous Wastes Removal", + RateTierInfo: { + TierNum: 1, + Rate: job.rate_mahw + }, + MaterialCalcSettings: { + //Todo Capture Calc Settings + CalcMethodCode: 2, + CalcMaxAmt: 10 + } + }, + { + RateType: "MA2S", + RateDesc: "Two Stage Paint", + RateTierInfo: { + TierNum: 1, + Rate: job.rate_ma2s + }, + MaterialCalcSettings: { + CalcMethodCode: 1, + CalcMaxAmt: 999999.99 + } + }, + { + RateType: "MA2T", + RateDesc: "Two Tone Paint", + RateTierInfo: { + TierNum: 1, + Rate: job.rate_ma2t + }, + MaterialCalcSettings: { + CalcMethodCode: 1, + CalcMaxAmt: 999999.99 + } + }, + { + RateType: "MA3S", + RateDesc: "Three Stage Paint", + RateTierInfo: { + TierNum: 1, + Rate: job.rate_ma3s + }, + MaterialCalcSettings: { + CalcMethodCode: 1, + CalcMaxAmt: 999999.99 + } + } + ] + }, + //StorageDuration: 17, + RepairTotalsInfo: { + LaborTotalsInfo: [ + { + TotalType: "LAB", + TotalTypeDesc: "Body Labor", + TotalHours: job.job_totals.rates.lab.hours, + TotalAmt: Dinero(job.job_totals.rates.lab.total).toFormat("0.00") + }, + { + TotalType: "LAF", + TotalTypeDesc: "Frame Labor", + TotalHours: job.job_totals.rates.laf.hours, + TotalAmt: Dinero(job.job_totals.rates.laf.total).toFormat("0.00") + }, + { + TotalType: "LAM", + TotalTypeDesc: "Mechanical Labor", + TotalHours: job.job_totals.rates.lam.hours, + TotalAmt: Dinero(job.job_totals.rates.lam.total).toFormat("0.00") + }, + { + TotalType: "LAR", + TotalTypeDesc: "Refinish Labor", + TotalHours: job.job_totals.rates.lar.hours, + TotalAmt: Dinero(job.job_totals.rates.lar.total).toFormat("0.00") + } + ], + PartsTotalsInfo: [ + { + TotalType: "PAA", + TotalTypeDesc: "Aftermarket Parts", + TotalAmt: Dinero(job.job_totals.parts.parts.list.PAA && job.job_totals.parts.parts.list.PAA.total).toFormat( + "0.00" + ) + }, + { + TotalType: "PAC", + TotalTypeDesc: "Re-Chromed Parts", + TotalAmt: Dinero(job.job_totals.parts.parts.list.PAC && job.job_totals.parts.parts.list.PAC.total).toFormat( + "0.00" + ) + }, + { + TotalType: "PAG", + TotalTypeDesc: "Glass Parts", + TotalAmt: Dinero(job.job_totals.parts.parts.list.PAG && job.job_totals.parts.parts.list.PAG.total).toFormat( + "0.00" + ) + }, + { + TotalType: "PAL", + TotalTypeDesc: "LKQ/Used Parts", + TotalAmt: Dinero(job.job_totals.parts.parts.list.PAL && job.job_totals.parts.parts.list.PAL.total).toFormat( + "0.00" + ) + }, + { + TotalType: "PAM", + TotalTypeDesc: "Remanufactured Parts", + TotalAmt: Dinero(job.job_totals.parts.parts.list.PAM && job.job_totals.parts.parts.list.PAM.total).toFormat( + "0.00" + ) + }, + { + TotalType: "PAN", + TotalTypeDesc: "New Parts", + TotalAmt: Dinero(job.job_totals.parts.parts.list.PAN && job.job_totals.parts.parts.list.PAN.total).toFormat( + "0.00" + ) + }, + { + TotalType: "PAR", + TotalTypeDesc: "Recored Parts", + TotalAmt: Dinero(job.job_totals.parts.parts.list.PAR && job.job_totals.parts.parts.list.PAR.total).toFormat( + "0.00" + ) + } + ], + OtherChargesTotalsInfo: [ + { + TotalType: "OTSL", + TotalTypeDesc: "Sublet", + TotalAmt: Dinero(job.job_totals.parts.sublets.total).toFormat("0.00") + }, + { + TotalType: "MAPA", + TotalTypeDesc: "Paint Materials", + TotalAmt: Dinero(job.job_totals.rates.mapa.total).toFormat("0.00") + }, + { + TotalType: "MASH", + TotalTypeDesc: "Shop Materials", + TotalAmt: Dinero(job.job_totals.rates.mash.total).toFormat("0.00") + }, + // { + // TotalType: "MAHW", + // TotalTypeDesc: "Hazardous Wastes Removal", + // TotalAmt: Dinero(job.job_totals.rates.mahw.total).toFormat( + // 0.0 + // ), + // }, + { + TotalType: "OTST", + TotalTypeDesc: "Storage", + TotalAmt: Dinero(job.job_totals.additional.storage).toFormat("0.00") + }, + { + TotalType: "OTTW", + TotalTypeDesc: "Towing", + TotalAmt: Dinero(job.job_totals.additional.towing).toFormat("0.00") + }, + { + TotalType: "OTAC", + TotalTypeDesc: "Additional Charges", + TotalAmt: Dinero(job.job_totals.additional.additionalCosts) + .add(Dinero(job.job_totals.additional.pvrt)) + .toFormat("0.00") + } + ], + SummaryTotalsInfo: [ + { + TotalType: "TOT", + TotalSubType: "TT", + TotalTypeDesc: "Gross Total", + TotalAmt: Dinero(job.job_totals.totals.total_repairs).toFormat("0.00") + }, + { + TotalType: "TOT", + TotalSubType: "T2", + TotalTypeDesc: "Net Total", + TotalAmt: Dinero(job.job_totals.totals.subtotal).toFormat("0.00") + }, + { + TotalType: "TOT", + TotalSubType: "SM", + TotalTypeDesc: "Supplement Total", + TotalAmt: job.cieca_ttl ? job.cieca_ttl.data.supp_amt : Dinero().toFormat("0.00") + }, + { + TotalType: "TOT", + TotalSubType: "F7", + TotalTypeDesc: "Sales Tax", + TotalAmt: Dinero(job.job_totals.totals.state_tax).toFormat("0.00") + }, + { + TotalType: "TOT", + TotalSubType: "GST", + TotalTypeDesc: "GST Tax", + TotalAmt: Dinero(job.job_totals.totals.federal_tax).toFormat("0.00") + }, + { + TotalType: "TOT", + TotalSubType: "D8", + TotalTypeDesc: "Bottom Line Discount", + TotalAmt: Dinero(job.job_totals.additional.adjustments).toFormat("0.00") + }, + { + TotalType: "TOT", + TotalSubType: "D2", + TotalTypeDesc: "Deductible", + TotalAmt: Dinero({ + amount: Math.round((job.ded_amt || 0) * 100) + }).toFormat("0.00") + }, + { + TotalType: "TOT", + TotalSubType: "BTR", + TotalTypeDesc: "Betterment", + TotalAmt: Dinero(job.job_totals.totals.custPayable.dep_taxes).toFormat("0.00") + }, + { + TotalType: "TOT", + TotalSubType: "AA", + TotalTypeDesc: "Appearance Allowance", + TotalAmt: Dinero().toFormat("0.00") + }, + { + TotalType: "TOT", + TotalSubType: "DEPOSIT", + TotalTypeDesc: "Deposit", + TotalAmt: Dinero().toFormat("0.00") + }, + { + TotalType: "TOT", + TotalSubType: "INS", + TotalTypeDesc: "Insurance Pay", + TotalAmt: Dinero(job.job_totals.totals.total_repairs) + .subtract(Dinero(job.job_totals.totals.custPayable.total)) + .toFormat("0.00") + }, + { + TotalType: "TOT", + TotalSubType: "CUST", + TotalTypeDesc: "Customer Pay", + TotalAmt: Dinero(job.job_totals.totals.custPayable.total).toFormat("0.00") + } + ] + // RepairTotalsType: 1, + }, + // RepairLabor: { + // LaborAllocations: { + // LaborAllocation: [ + // { + // LaborAllocationUUID: + // "426cce3a-efa7-44d9-b76e-50b9102c4198", + // LaborType: "LAB", + // Technician: { + // Employee: { + // PersonInfo: { + // PersonName: { + // FirstName: "Jose", + // LastName: "Gonzalez", + // }, + // IDInfo: { + // IDQualifierCode: "US", + // IDNum: 2987, + // }, + // }, + // }, + // }, + // AllocatedHours: 3.5, + // }, + // { + // LaborAllocationUUID: + // "426cce3a-efa7-44d9-b76e-50b9102c4199", + // LaborType: "LAR", + // Technician: { + // Employee: { + // PersonInfo: { + // PersonName: { + // FirstName: "Rcardo", + // LastName: "Himenez", + // }, + // IDInfo: { + // IDQualifierCode: "US", + // IDNum: 2989, + // }, + // }, + // }, + // }, + // AllocatedHours: 5.5, + // }, + // ], + // }, + // }, + ProductionStatus: { + ProductionStage: { + ProductionStageCode: GetProductionStageCode(job, bodyshop), + ProductionStageDateTime: moment().tz(bodyshop.timezone).format(momentFormat) + // ProductionStageStatusComment: + // "Going to be painted this afternoon", + }, + RepairStatus: { + RepairStatusCode: GetRepairStatusCode(job), + RepairStatusDateTime: moment().tz(bodyshop.timezone).format(momentFormat) + // RepairStatusMemo: "Waiting on back ordered parts", + } + } + // RepairOrderNotes: { + // RepairOrderNote: { + // LineSequenceNum: 1, + // Note: "Revision Requested : approved--but needs est separated.8/22/2008 11:58:53 AM", + // CreateDateTime: "2008-08-22T11:58:53", + // AuthoredBy: { + // FirstName: { + // "#text": "Elizabeth/FirstName>", + // LastName: "Unis", + // }, + // }, + // RepairOrderNote: { + // LineSequenceNum: 2, + // Note: "Approved : 8/26/2008 12:21:08 PM", + // CreateDateTime: "2008-08-26T12:21:08", + // AuthoredBy: { + // FirstName: { + // "#text": "Elizabeth/FirstName>", + // LastName: "Unis", + // }, + // }, + // }, + // }, + // }, + }; + + deleteNullKeys(obj); + + try { + const entegralSoapClient = await soap.createClientAsync(entegralEndpoint, { + ignoredNamespaces: true, + wsdl_options: { + // useEmptyTag: true, + }, + wsdl_headers: { + Authorization: `Basic ${new Buffer.from( + `${process.env.ENTEGRAL_USER}:${process.env.ENTEGRAL_PASSWORD}` + ).toString("base64")}` + } + }); + + entegralSoapClient.setSecurity( + new soap.BasicAuthSecurity(process.env.ENTEGRAL_USER, process.env.ENTEGRAL_PASSWORD) + ); + + const entegralResponse = await entegralSoapClient.RepairOrderFolderAddRqAsync( + obj, + function (err, result, rawResponse, soapHeader, rawRequest) { + fs.writeFileSync(`./logs/arms-request.xml`, rawRequest); + fs.writeFileSync(`./logs/arms-response.xml`, rawResponse); + + logger.log("arms-job-xml-request", "DEBUG", "api", job.id, { + xml: rawRequest + }); + logger.log("arms-job-xml-response", "DEBUG", "api", job.id, { + xml: rawResponse + }); + + if (err) { + sendServerEmail({ + subject: `ARMS Update Failed: ${bodyshop.shopname} - ${job.ro_number}`, + text: `Error: ${JSON.stringify(error)}` + }); + } + + res.status(200).json(err || result); + } + ); + + const [result, rawResponse, , rawRequest] = entegralResponse; + } catch (error) { + logger.log("arms-failed-job-upload", "ERROR", "api", job.shopid, { + job: JSON.stringify({ id: job.id, ro_number: job.ro_number }), + error: error.message || JSON.stringify(error) + }); + console.log(error); + } + } catch (error) { + logger.log("arms-failed-job", "ERROR", "api", job.shopid, { + job: JSON.stringify({ id: job.id, ro_number: job.ro_number }), + error: error.message || JSON.stringify(error) + }); + } + + res.sendStatus(200); + return; + const allErrors = []; + try { + for (const bodyshop of bodyshops) { + logger.log("arms-start-shop-extract", "DEBUG", "api", bodyshop.id, { + shopname: bodyshop.shopname + }); + const erroredJobs = []; + try { + const { jobs } = await client.request(queries.ENTEGRAL_EXPORT, { + bodyshopid: bodyshop.id + }); + const jobsToPush = []; + + if (erroredJobs.length > 0) { + logger.log("arms-failed-jobs", "ERROR", "api", bodyshop.id, { + count: erroredJobs.length, + jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)) + }); + allErrors = [...allErrors, ...erroredJobs]; } - res.sendStatus(200); - } catch (error) { - res.status(200).json(error); + logger.log("arms-end-shop-extract", "DEBUG", "api", bodyshop.id, { + shopname: bodyshop.shopname + }); + + try { + const entegralSoapClient = await soap.createClientAsync(entegralEndpoint, { + ignoredNamespaces: true, + wsdl_options: { + // useEmptyTag: true, + }, + wsdl_headers: { + Authorization: `Basic ${new Buffer.from( + `${process.env.ENTEGRAL_USER}:${process.env.ENTEGRAL_PASSWORD}` + ).toString("base64")}` + } + }); + + entegralSoapClient.setSecurity( + new soap.BasicAuthSecurity(process.env.ENTEGRAL_USER, process.env.ENTEGRAL_PASSWORD) + ); + + const entegralResponse = await entegralSoapClient.RepairOrderFolderAddRqAsync( + [jobsToPush[0]], + function (err, result, rawResponse, soapHeader, rawRequest) { + fs.writeFileSync(`./logs/arms-request.xml`, rawRequest); + fs.writeFileSync(`./logs/arms-response.xml`, rawResponse); + + res.json(err || result); + } + ); + + const [result, rawResponse, , rawRequest] = entegralResponse; + } catch (error) { + fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); + console.log(error); + } + } catch (error) { + //Error at the shop level. + logger.log("arms-error-shop", "ERROR", "api", bodyshop.id, { + error + }); + + allErrors.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + fatal: true, + errors: [error.toString()] + }); + } finally { + allErrors.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + errors: erroredJobs + }); + } } + + res.sendStatus(200); + } catch (error) { + res.status(200).json(error); + } }; function GetSupplementNumber(joblines) { - if (!joblines) return 0; - const max = _.max( - joblines.map((jl) => parseInt((jl.line_ind || "0").replace(/[^\d.-]/g, ""))) - ); + if (!joblines) return 0; + const max = _.max(joblines.map((jl) => parseInt((jl.line_ind || "0").replace(/[^\d.-]/g, "")))); - return max || 0; + return max || 0; } function GetDocumentstatus(job, bodyshop) { - switch (job.status) { - case bodyshop.md_ro_statuses.default_void: - return "V"; - case bodyshop.md_ro_statuses.default_invoiced: - case bodyshop.md_ro_statuses.default_exported: - return "Z"; + switch (job.status) { + case bodyshop.md_ro_statuses.default_void: + return "V"; + case bodyshop.md_ro_statuses.default_invoiced: + case bodyshop.md_ro_statuses.default_exported: + return "Z"; - default: - return "O"; - } + default: + return "O"; + } } function GetRepairStatusCode(job) { - return "25"; + return "25"; } function GetProductionStageCode(job, bodyshop) { - const result = (bodyshop.features?.entegral).find( - (k) => k.status === job.status - ); + const result = (bodyshop.features?.entegral).find((k) => k.status === job.status); - return result?.code || "33"; + return result?.code || "33"; } function isEmpty(obj) { - for (var key in obj) return false; + for (var key in obj) return false; - return true; + return true; } function deleteNullKeys(app) { - for (var key in app) { - if (app[key] !== null && typeof app[key] === "object") { - deleteNullKeys(app[key]); + for (var key in app) { + if (app[key] !== null && typeof app[key] === "object") { + deleteNullKeys(app[key]); - if (isEmpty(app[key])) { - delete app[key]; - } - } - if (app[key] === null) { - delete app[key]; - } + if (isEmpty(app[key])) { + delete app[key]; + } } + if (app[key] === null) { + delete app[key]; + } + } } diff --git a/server/data/autohouse.js b/server/data/autohouse.js index 55eb15901..6b5f30af9 100644 --- a/server/data/autohouse.js +++ b/server/data/autohouse.js @@ -7,15 +7,12 @@ const _ = require("lodash"); const logger = require("../utils/logger"); const fs = require("fs"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); let Client = require("ssh2-sftp-client"); const client = require("../graphql-client/graphql-client").client; -const {sendServerEmail} = require("../email/sendemail"); +const { sendServerEmail } = require("../email/sendemail"); const AHDineroFormat = "0.00"; const AhDateFormat = "MMDDYYYY"; @@ -23,1046 +20,852 @@ const repairOpCodes = ["OP4", "OP9", "OP10"]; const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"]; const ftpSetup = { - host: process.env.AUTOHOUSE_HOST, - 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), - algorithms: { - serverHostKey: ["ssh-rsa", "ssh-dss"], - }, + host: process.env.AUTOHOUSE_HOST, + 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), + algorithms: { + serverHostKey: ["ssh-rsa", "ssh-dss"] + } }; exports.default = async (req, res) => { - //Query for the List of Bodyshop Clients. - logger.log("autohouse-start", "DEBUG", "api", null, null); - const {bodyshops} = await client.request(queries.GET_AUTOHOUSE_SHOPS); + //Query for the List of Bodyshop Clients. + logger.log("autohouse-start", "DEBUG", "api", null, null); + const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS); - const specificShopIds = req.body.bodyshopIds; // ['uuid] - const {start, end, skipUpload} = req.body; //YYYY-MM-DD - if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) { - res.sendStatus(401); - return; - } - const allxmlsToUpload = []; - const allErrors = []; - try { - for (const bodyshop of specificShopIds - ? bodyshops.filter((b) => specificShopIds.includes(b.id)) - : bodyshops) { - logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, { - shopname: bodyshop.shopname, - }); - const erroredJobs = []; - try { - const {jobs, bodyshops_by_pk} = await client.request( - queries.AUTOHOUSE_QUERY, - { - bodyshopid: bodyshop.id, - start: start - ? moment(start).startOf("day") - : moment().subtract(5, "days").startOf("day"), - ...(end && {end: moment(end).endOf("day")}), - } - ); - - const autoHouseObject = { - AutoHouseExport: { - RepairOrder: jobs.map((j) => - CreateRepairOrderTag( - {...j, bodyshop: bodyshops_by_pk}, - function ({job, error}) { - erroredJobs.push({job: job, error: error.toString()}); - } - ) - ), - }, - }; - - if (erroredJobs.length > 0) { - logger.log("autohouse-failed-jobs", "ERROR", "api", bodyshop.id, { - count: erroredJobs.length, - jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)), - }); - } - - var ret = builder - .create( - { - // version: "1.0", - // encoding: "UTF-8", - //keepNullNodes: true, - }, - autoHouseObject - ) - .end({allowEmptyTags: true}); - - allxmlsToUpload.push({ - count: autoHouseObject.AutoHouseExport.RepairOrder.length, - xml: ret, - filename: `IM_${bodyshop.autohouseid}_${moment().format( - "DDMMYYYY_HHMMss" - )}.xml`, - }); - - logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, { - shopname: bodyshop.shopname, - }); - } catch (error) { - //Error at the shop level. - logger.log("autohouse-error-shop", "ERROR", "api", bodyshop.id, { - ...error, - }); - - allErrors.push({ - bodyshopid: bodyshop.id, - imexshopid: bodyshop.imexshopid, - autuhouseid: bodyshop.autuhouseid, - fatal: true, - errors: [error.toString()], - }); - } finally { - allErrors.push({ - bodyshopid: bodyshop.id, - imexshopid: bodyshop.imexshopid, - autohouseid: bodyshop.autohouseid, - errors: erroredJobs.map((ej) => ({ - ro_number: ej.job?.ro_number, - jobid: ej.job?.id, - error: ej.error, - })), - }); - } - } - - if (skipUpload) { - for (const xmlObj of allxmlsToUpload) { - fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); - } - - res.json(allxmlsToUpload); - sendServerEmail({ - subject: `Autohouse Report ${moment().format("MM-DD-YY")}`, - text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} - Uploaded: ${JSON.stringify( - allxmlsToUpload.map((x) => ({filename: x.filename, count: x.count})), - null, - 2 - )} - `, - }); - return; - } - - let sftp = new Client(); - sftp.on("error", (errors) => - logger.log("autohouse-sftp-error", "ERROR", "api", null, { - ...errors, - }) - ); - try { - //Connect to the FTP and upload all. - - await sftp.connect(ftpSetup); - - for (const xmlObj of allxmlsToUpload) { - logger.log("autohouse-sftp-upload", "DEBUG", "api", null, { - filename: xmlObj.filename, - }); - - const uploadResult = await sftp.put( - Buffer.from(xmlObj.xml), - `/${xmlObj.filename}` - ); - logger.log("autohouse-sftp-upload-result", "DEBUG", "api", null, { - uploadResult, - }); - } - - //***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml - } catch (error) { - logger.log("autohouse-sftp-error", "ERROR", "api", null, { - ...error, - }); - } finally { - sftp.end(); - } - sendServerEmail({ - subject: `Autohouse Report ${moment().format("MM-DD-YY")}`, - text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} - Uploaded: ${JSON.stringify( - allxmlsToUpload.map((x) => ({filename: x.filename, count: x.count})), - null, - 2 - )} - `, + const specificShopIds = req.body.bodyshopIds; // ['uuid] + const { start, end, skipUpload } = req.body; //YYYY-MM-DD + if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) { + res.sendStatus(401); + return; + } + const allxmlsToUpload = []; + const allErrors = []; + try { + for (const bodyshop of specificShopIds ? bodyshops.filter((b) => specificShopIds.includes(b.id)) : bodyshops) { + logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, { + shopname: bodyshop.shopname + }); + const erroredJobs = []; + try { + const { jobs, bodyshops_by_pk } = await client.request(queries.AUTOHOUSE_QUERY, { + bodyshopid: bodyshop.id, + start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"), + ...(end && { end: moment(end).endOf("day") }) }); - res.sendStatus(200); - } catch (error) { - res.status(200).json(error); + + const autoHouseObject = { + AutoHouseExport: { + RepairOrder: jobs.map((j) => + CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) { + erroredJobs.push({ job: job, error: error.toString() }); + }) + ) + } + }; + + if (erroredJobs.length > 0) { + logger.log("autohouse-failed-jobs", "ERROR", "api", bodyshop.id, { + count: erroredJobs.length, + jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)) + }); + } + + var ret = builder + .create( + { + // version: "1.0", + // encoding: "UTF-8", + //keepNullNodes: true, + }, + autoHouseObject + ) + .end({ allowEmptyTags: true }); + + allxmlsToUpload.push({ + count: autoHouseObject.AutoHouseExport.RepairOrder.length, + xml: ret, + filename: `IM_${bodyshop.autohouseid}_${moment().format("DDMMYYYY_HHMMss")}.xml` + }); + + logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, { + shopname: bodyshop.shopname + }); + } catch (error) { + //Error at the shop level. + logger.log("autohouse-error-shop", "ERROR", "api", bodyshop.id, { + ...error + }); + + allErrors.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + autuhouseid: bodyshop.autuhouseid, + fatal: true, + errors: [error.toString()] + }); + } finally { + allErrors.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + autohouseid: bodyshop.autohouseid, + errors: erroredJobs.map((ej) => ({ + ro_number: ej.job?.ro_number, + jobid: ej.job?.id, + error: ej.error + })) + }); + } } + + if (skipUpload) { + for (const xmlObj of allxmlsToUpload) { + fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); + } + + res.json(allxmlsToUpload); + sendServerEmail({ + subject: `Autohouse Report ${moment().format("MM-DD-YY")}`, + text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} + Uploaded: ${JSON.stringify( + allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })), + null, + 2 + )} + ` + }); + return; + } + + let sftp = new Client(); + sftp.on("error", (errors) => + logger.log("autohouse-sftp-error", "ERROR", "api", null, { + ...errors + }) + ); + try { + //Connect to the FTP and upload all. + + await sftp.connect(ftpSetup); + + for (const xmlObj of allxmlsToUpload) { + logger.log("autohouse-sftp-upload", "DEBUG", "api", null, { + filename: xmlObj.filename + }); + + const uploadResult = await sftp.put(Buffer.from(xmlObj.xml), `/${xmlObj.filename}`); + logger.log("autohouse-sftp-upload-result", "DEBUG", "api", null, { + uploadResult + }); + } + + //***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml + } catch (error) { + logger.log("autohouse-sftp-error", "ERROR", "api", null, { + ...error + }); + } finally { + sftp.end(); + } + sendServerEmail({ + subject: `Autohouse Report ${moment().format("MM-DD-YY")}`, + text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} + Uploaded: ${JSON.stringify( + allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })), + null, + 2 + )} + ` + }); + res.sendStatus(200); + } catch (error) { + res.status(200).json(error); + } }; const CreateRepairOrderTag = (job, errorCallback) => { - //Level 2 + //Level 2 - if (!job.job_totals) { - errorCallback({ - jobid: job.id, - job: job, - ro_number: job.ro_number, - error: {toString: () => "No job totals for RO."}, - }); - return {}; - } + if (!job.job_totals) { + errorCallback({ + jobid: job.id, + job: job, + ro_number: job.ro_number, + error: { toString: () => "No job totals for RO." } + }); + return {}; + } - const repairCosts = CreateCosts(job); + const repairCosts = CreateCosts(job); - if (job.ro_number === "QBD209") { - console.log("Stop here"); - } - //Calculate detail only lines. - const detailAdjustments = job.joblines - .filter((jl) => jl.ah_detail_line && jl.mod_lbr_ty) - .reduce( - (acc, val) => { - return { - hours: acc.hours + val.mod_lb_hrs, - amount: acc.amount.add( - Dinero({ - amount: Math.round( - (job.job_totals.rates[val.mod_lbr_ty.toLowerCase()].rate || 0) * - val.mod_lb_hrs * - 100 - ), - }) - ), - }; - }, - {hours: 0, amount: Dinero()} - ); - - try { - const ret = { - RepairOrderInformation: { - ShopInternalName: job.bodyshop.autohouseid, - ID: parseInt(job.ro_number.match(/\d/g).join(""), 10), - RO: job.ro_number, - Est: parseInt(job.ro_number.match(/\d/g).join(""), 10), //We no longer use estimate id. - GUID: job.id, - TransType: StatusMapping(job.status, job.bodyshop.md_ro_statuses), - ShopName: job.bodyshop.shopname, - ShopAddress: job.bodyshop.address1, - ShopCity: job.bodyshop.city, - ShopState: job.bodyshop.state, - ShopZip: job.bodyshop.zip_post, - ShopPhone: job.bodyshop.phone, - EstimatorID: `${job.est_ct_ln ? job.est_ct_ln : ""}${ - job.est_ct_ln ? ", " : "" - }${job.est_ct_fn ? job.est_ct_fn : ""}`, - EstimatorName: `${job.est_ct_ln ? job.est_ct_ln : ""}${ - job.est_ct_ln ? ", " : "" - }${job.est_ct_fn ? job.est_ct_fn : ""}`, - }, - CustomerInformation: { - FirstName: "", - LastName: "", - Street: "", - City: "", - State: "", - Zip: (job.ownr_zip && job.ownr_zip.substring(0, 3)) || "", - Phone1: "", - Phone2: null, - Phone2Extension: null, - Phone3: null, - Phone3Extension: null, - FileComments: null, - Source: null, - Email: "", - RetWhsl: null, - Cat: null, - InsuredorClaimantFlag: null, - }, - VehicleInformation: { - Year: job.v_model_yr - ? parseInt(job.v_model_yr.match(/\d/g)) - ? parseInt(job.v_model_yr.match(/\d/g).join(""), 10) - : "" - : "", - Make: job.v_make_desc || "", - Model: job.v_model_desc || "", - VIN: job.v_vin || "", - License: job.plate_no, - MileageIn: job.kmin || 0, - Vehiclecolor: job.v_color, - VehicleProductionDate: null, - VehiclePaintCode: null, - VehicleTrimCode: null, - VehicleBodyStyle: null, - DriveableFlag: job.driveable ? "Y" : "N", - }, - - InsuranceInformation: { - InsuranceCo: job.ins_co_nm || "", - CompanyName: job.ins_co_nm || "", - Address: job.ins_addr1 || "", - City: job.ins_addr1 || "", - State: job.ins_city || "", - Zip: job.ins_zip || "", - Phone: job.ins_ph1 || "", - Fax: job.ins_fax || "", - ClaimType: null, - LossType: job.loss_type || "", - Policy: job.policy_no || "", - Claim: job.clm_no || "", - InsuredLastName: null, - InsuredFirstName: null, - ClaimantLastName: null, - ClaimantFirstName: null, - Assignment: null, - InsuranceAgentLastName: null, - InsuranceAgentFirstName: null, - InsAgentPhone: null, - InsideAdjuster: null, - OutsideAdjuster: null, - }, - Dates: { - DateofLoss: - (job.loss_date && moment(job.loss_date).format(AhDateFormat)) || "", - InitialCustomerContactDate: null, - FirstFollowUpDate: null, - ReferralDate: null, - EstimateAppointmentDate: null, - SecondFollowUpDate: null, - AssignedDate: - (job.asgn_date && moment(job.asgn_date).format(AhDateFormat)) || "", - EstComplete: null, - CustomerAuthorizationDate: null, - InsuranceAuthorizationDate: null, - DateOpened: - (job.date_open && - moment(job.date_open) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - (job.created_at && - moment(job.created_at) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - ScheduledArrivalDate: - (job.scheduled_in && - moment(job.scheduled_in) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - CarinShop: - (job.actual_in && - moment(job.actual_in) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - InsInspDate: null, - StartDate: job.date_repairstarted - ? (job.date_repairstarted && - moment(job.date_repairstarted) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "" - : (job.date_repairstarted && - moment(job.actual_in) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - PartsOrder: null, - TeardownHold: null, - SupplementSubmittedDate: null, - SupplementApprovedDate: null, - AssntoBody: null, - AssntoMech: null, - AssntoPaint: null, - AssntoDetail: null, - // PromiseDate: - // (job.scheduled_completion && - // moment(job.scheduled_completion).format(AhDateFormat)) || - // "", - //InsuranceTargetOut: null, - CarComplete: - (job.actual_completion && - moment(job.actual_completion) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - DeliveryAppointmentDate: - // (job.scheduled_delivery && - // moment(job.scheduled_delivery) - // .tz(job.bodyshop.timezone) - // .format(AhDateFormat)) || - (job.scheduled_completion && - moment(job.scheduled_completion) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - DateClosed: - (job.date_invoiced && - moment(job.date_invoiced) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - CustomerPaidInFullDate: null, - InsurancePaidInFullDate: null, - CustPickup: - (job.actual_delivery && - moment(job.actual_delivery) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - AccountPostedDate: - job.date_exported && - moment(job.date_exported) - .tz(job.bodyshop.timezone) - .format(AhDateFormat), - CSIProcessedDate: null, - ThankYouLetterSent: null, - AdditionalFollowUpDate: null, - }, - Rates: { - BodyRate: job.rate_lab || 0, - RefinishRate: job.rate_lar || 0, - MechanicalRate: job.rate_lam || 0, - StructuralRate: job.rate_las || 0, - ElectricalRate: job.rate_lae || 0, - FrameRate: job.rate_laf || 0, - GlassRate: job.rate_lag || 0, - DetailRate: 0, // job.rate_lad || 0, - LaborMiscRate: 0, - PMRate: job.rate_mapa || 0, - BMRate: job.rate_mash || 0, - TaxRate: - (job.parts_tax_rates && - job.parts_tax_rates.PAN && - job.parts_tax_rates.PAN.prt_tax_rt) || - 0, - StorageRateperDay: 0, - DaysStored: 0, - }, - // EstimateTotals: { - // BodyHours: null, - // RefinishHours: null, - // MechanicalHours: null, - // StructuralHours: null, - // PartsTotal: null, - // PartsOEM: null, - // PartsAM: null, - // PartsReconditioned: null, - // PartsRecycled: null, - // PartsOther: null, - // SubletTotal: null, - // BodyLaborTotal: null, - // RefinishLaborTotal: null, - // MechanicalLaborTotal: null, - // StructuralLaborTotal: null, - // MiscellaneousChargeTotal: null, - // PMTotal: null, - // BMTotal: null, - // MiscTotal: null, - // TowingTotal: null, - // StorageTotal: null, - // DetailTotal: null, - // SalesTaxTotal: null, - // GrossTotal: null, - // DeductibleTotal: null, - // DepreciationTotal: null, - // Discount: null, - // CustomerPay: null, - // InsurancePay: null, - // Deposit: null, - // AmountDue: null, - // }, - // SupplementTotals: { - // BodyHours: null, - // RefinishHours: null, - // MechanicalHours: null, - // StructuralHours: null, - // PartsTotal: null, - // PartsOEM: null, - // PartsAM: null, - // PartsReconditioned: null, - // PartsRecycled: null, - // PartsOther: null, - // SubletTotal: null, - // BodyLaborTotal: null, - // RefinishLaborTotal: null, - // MechanicalLaborTotal: null, - // StructuralLaborTotal: null, - // MiscellaneousChargeTotal: null, - // PMTotal: null, - // BMTotal: null, - // MiscTotal: null, - // TowingTotal: null, - // StorageTotal: null, - // DetailTotal: null, - // SalesTaxTotal: null, - // GrossTotal: null, - // DeductibleTotal: null, - // DepreciationTotal: null, - // Discount: null, - // CustomerPay: null, - // InsurancePay: null, - // Deposit: null, - // AmountDue: null, - // }, - RevisedTotals: { - BodyHours: job.job_totals.rates.lab.hours.toFixed(2), - BodyRepairHours: job.joblines - .filter((line) => repairOpCodes.includes(line.lbr_op)) - .reduce((acc, val) => acc + val.mod_lb_hrs, 0) - .toFixed(2), - BodyReplaceHours: job.joblines - .filter((line) => replaceOpCodes.includes(line.lbr_op)) - .reduce((acc, val) => acc + val.mod_lb_hrs, 0) - .toFixed(2), - RefinishHours: job.job_totals.rates.lar.hours.toFixed(2), - MechanicalHours: job.job_totals.rates.lam.hours.toFixed(2), - StructuralHours: job.job_totals.rates.las.hours.toFixed(2), - - ElectricalHours: job.job_totals.rates.lae.hours.toFixed(2), - FrameHours: job.job_totals.rates.laf.hours.toFixed(2), - GlassHours: job.job_totals.rates.lag.hours.toFixed(2), - DetailHours: detailAdjustments.hours, //job.job_totals.rates.lad.hours.toFixed(2), - LaborMiscHours: ( - job.job_totals.rates.la1.hours + - job.job_totals.rates.la2.hours + - job.job_totals.rates.la3.hours + - job.job_totals.rates.la4.hours + - job.job_totals.rates.lau.hours - - detailAdjustments.hours - ).toFixed(2), - - PartsTotal: Dinero(job.job_totals.parts.parts.total).toFormat( - AHDineroFormat - ), - PartsTotalCost: repairCosts.PartsTotalCost.toFormat(AHDineroFormat), - PartsOEM: Dinero( - job.job_totals.parts.parts.list.PAN && - job.job_totals.parts.parts.list.PAN.total - ) - .add( - Dinero( - job.job_totals.parts.parts.list.PAP && - job.job_totals.parts.parts.list.PAP.total - ) - ) - .toFormat(AHDineroFormat), - PartsOEMCost: repairCosts.PartsOemCost.toFormat(AHDineroFormat), - PartsAM: Dinero( - job.job_totals.parts.parts.list.PAA && - job.job_totals.parts.parts.list.PAA.total - ).toFormat(AHDineroFormat), - PartsAMCost: repairCosts.PartsAMCost.toFormat(AHDineroFormat), - PartsReconditioned: - repairCosts.PartsReconditionedCost.toFormat(AHDineroFormat), - PartsReconditionedCost: - repairCosts.PartsReconditionedCost.toFormat(AHDineroFormat), - PartsRecycled: Dinero( - job.job_totals.parts.parts.list.PAL && - job.job_totals.parts.parts.list.PAL.total - ).toFormat(AHDineroFormat), - PartsRecycledCost: - repairCosts.PartsRecycledCost.toFormat(AHDineroFormat), - PartsOther: Dinero( - job.job_totals.parts.parts.list.PAO && - job.job_totals.parts.parts.list.PAO.total - ).toFormat(AHDineroFormat), - PartsOtherCost: repairCosts.PartsOtherCost.toFormat(AHDineroFormat), - SubletTotal: Dinero(job.job_totals.parts.sublets.total).toFormat( - AHDineroFormat - ), - SubletTotalCost: repairCosts.SubletTotalCost.toFormat(AHDineroFormat), - BodyLaborTotal: Dinero(job.job_totals.rates.lab.total).toFormat( - AHDineroFormat - ), - BodyLaborTotalCost: - repairCosts.BodyLaborTotalCost.toFormat(AHDineroFormat), - RefinishLaborTotal: Dinero(job.job_totals.rates.lar.total).toFormat( - AHDineroFormat - ), - RefinishLaborTotalCost: - repairCosts.RefinishLaborTotalCost.toFormat(AHDineroFormat), - MechanicalLaborTotal: Dinero(job.job_totals.rates.lam.total).toFormat( - AHDineroFormat - ), - MechanicalLaborTotalCost: - repairCosts.MechanicalLaborTotalCost.toFormat(AHDineroFormat), - StructuralLaborTotal: Dinero(job.job_totals.rates.las.total).toFormat( - AHDineroFormat - ), - StructuralLaborTotalCost: - repairCosts.StructuralLaborTotalCost.toFormat(AHDineroFormat), - - ElectricalLaborTotal: Dinero(job.job_totals.rates.lae.total).toFormat( - AHDineroFormat - ), - ElectricalLaborTotalCost: - repairCosts.ElectricalLaborTotalCost.toFormat(AHDineroFormat), - FrameLaborTotal: Dinero(job.job_totals.rates.laf.total).toFormat( - AHDineroFormat - ), - FrameLaborTotalCost: - repairCosts.FrameLaborTotalCost.toFormat(AHDineroFormat), - GlassLaborTotal: Dinero(job.job_totals.rates.lag.total).toFormat( - AHDineroFormat - ), - GlassLaborTotalCost: - repairCosts.GlassLaborTotalCost.toFormat(AHDineroFormat), - DetailLaborTotal: detailAdjustments.amount.toFormat(AHDineroFormat), - // Dinero(job.job_totals.rates.lad.total).toFormat( - // AHDineroFormat - // ), - DetailLaborTotalCost: Dinero().toFormat(AHDineroFormat), - // repairCosts.DetailLaborTotalCost.toFormat(AHDineroFormat), - LaborMiscTotal: Dinero(job.job_totals.rates.la1.total) - .add(Dinero(job.job_totals.rates.la2.total)) - .add(Dinero(job.job_totals.rates.la3.total)) - .add(Dinero(job.job_totals.rates.la4.total)) - .add(Dinero(job.job_totals.rates.lau.total)) - .subtract(detailAdjustments.amount) - .toFormat(AHDineroFormat), - LaborMiscTotalCost: 0, - MiscellaneousChargeTotal: 0, - MiscellaneousChargeTotalCost: 0, - PMTotal: Dinero(job.job_totals.rates.mapa.total).toFormat( - AHDineroFormat - ), - PMTotalCost: repairCosts.PMTotalCost.toFormat(AHDineroFormat), - BMTotal: Dinero(job.job_totals.rates.mash.total).toFormat( - AHDineroFormat - ), - BMTotalCost: repairCosts.BMTotalCost.toFormat(AHDineroFormat), - MiscTotal: Dinero(job.job_totals.additional.additionalCosts).toFormat( - AHDineroFormat - ), - MiscTotalCost: 0, - TowingTotal: Dinero(job.job_totals.additional.towing).toFormat( - AHDineroFormat - ), - TowingTotalCost: repairCosts.TowingTotalCost.toFormat(AHDineroFormat), - StorageTotal: Dinero(job.job_totals.additional.storage).toFormat( - AHDineroFormat - ), - StorageTotalCost: repairCosts.StorageTotalCost.toFormat(AHDineroFormat), - DetailTotal: detailAdjustments.amount.toFormat(AHDineroFormat), - DetailTotalCost: 0, - SalesTaxTotal: Dinero(job.job_totals.totals.local_tax) - .add(Dinero(job.job_totals.totals.state_tax)) - .add(Dinero(job.job_totals.totals.federal_tax)) - .add(Dinero(job.job_totals.additional.pvrt)) - .toFormat(AHDineroFormat), - SalesTaxTotalCost: 0, - GrossTotal: Dinero(job.job_totals.totals.total_repairs).toFormat( - AHDineroFormat - ), - DeductibleTotal: Dinero({ - amount: Math.round((job.ded_amt || 0) * 100), - }).toFormat(AHDineroFormat), - DepreciationTotal: Dinero( - job.job_totals.totals.custPayable.dep_taxes - ).toFormat(AHDineroFormat), - Discount: Dinero(job.job_totals.additional.adjustments).toFormat( - AHDineroFormat - ), - CustomerPay: Dinero(job.job_totals.totals.custPayable.total).toFormat( - AHDineroFormat - ), - InsurancePay: Dinero(job.job_totals.totals.total_repairs) - .subtract(Dinero(job.job_totals.totals.custPayable.total)) - .toFormat(AHDineroFormat), - Deposit: 0, - AmountDue: 0, - }, - Misc: { - ProductionStatus: null, - StatusDescription: null, - Hub50Comment: null, - DateofChange: null, - BodyTechName: null, - TotalLossYN: job.tlos_ind ? "Y" : "N", - InsScreenCommentsLine1: null, - InsScreenCommentsLine2: null, - AssignmentCaller: null, - AssignmentDivision: null, - LocationofPrimaryImpact: - (job.area_of_damage && job.area_of_damage.impact1) || 0, - LocationofSecondaryImpact: - (job.area_of_damage && job.area_of_damage.impact2) || 0, - PaintTechID: null, - PaintTechName: null, - ImportType: null, - ImportFile: null, - GSTTax: Dinero(job.job_totals.totals.federal_tax).toFormat( - AHDineroFormat - ), - RepairDelayStatusCode: null, - RepairDelaycomment: null, - AgentMktgID: null, - AgentCity: null, - Picture1: null, - Picture2: null, - ExtNoteDate: null, - RentalOrdDate: null, - RentalPUDate: null, - RentalDueDate: null, - RentalActRetDate: null, - RentalCompanyID: null, - // CSIID: null, - InsGroupCode: null, - }, - - DetailLines: { - DetailLine: - job.joblines.length > 0 - ? job.joblines.map((jl) => - GenerateDetailLines(job, jl, job.bodyshop.md_order_statuses) - ) - : [generateNullDetailLine()], - }, + if (job.ro_number === "QBD209") { + console.log("Stop here"); + } + //Calculate detail only lines. + const detailAdjustments = job.joblines + .filter((jl) => jl.ah_detail_line && jl.mod_lbr_ty) + .reduce( + (acc, val) => { + return { + hours: acc.hours + val.mod_lb_hrs, + amount: acc.amount.add( + Dinero({ + amount: Math.round((job.job_totals.rates[val.mod_lbr_ty.toLowerCase()].rate || 0) * val.mod_lb_hrs * 100) + }) + ) }; - return ret; - } catch (error) { - logger.log("autohouse-job-calculate-error", "ERROR", "api", null, { - error, - }); + }, + { hours: 0, amount: Dinero() } + ); - errorCallback({jobid: job.id, ro_number: job.ro_number, error}); - } + try { + const ret = { + RepairOrderInformation: { + ShopInternalName: job.bodyshop.autohouseid, + ID: parseInt(job.ro_number.match(/\d/g).join(""), 10), + RO: job.ro_number, + Est: parseInt(job.ro_number.match(/\d/g).join(""), 10), //We no longer use estimate id. + GUID: job.id, + TransType: StatusMapping(job.status, job.bodyshop.md_ro_statuses), + ShopName: job.bodyshop.shopname, + ShopAddress: job.bodyshop.address1, + ShopCity: job.bodyshop.city, + ShopState: job.bodyshop.state, + ShopZip: job.bodyshop.zip_post, + ShopPhone: job.bodyshop.phone, + EstimatorID: `${job.est_ct_ln ? job.est_ct_ln : ""}${ + job.est_ct_ln ? ", " : "" + }${job.est_ct_fn ? job.est_ct_fn : ""}`, + EstimatorName: `${job.est_ct_ln ? job.est_ct_ln : ""}${ + job.est_ct_ln ? ", " : "" + }${job.est_ct_fn ? job.est_ct_fn : ""}` + }, + CustomerInformation: { + FirstName: "", + LastName: "", + Street: "", + City: "", + State: "", + Zip: (job.ownr_zip && job.ownr_zip.substring(0, 3)) || "", + Phone1: "", + Phone2: null, + Phone2Extension: null, + Phone3: null, + Phone3Extension: null, + FileComments: null, + Source: null, + Email: "", + RetWhsl: null, + Cat: null, + InsuredorClaimantFlag: null + }, + VehicleInformation: { + Year: job.v_model_yr + ? parseInt(job.v_model_yr.match(/\d/g)) + ? parseInt(job.v_model_yr.match(/\d/g).join(""), 10) + : "" + : "", + Make: job.v_make_desc || "", + Model: job.v_model_desc || "", + VIN: job.v_vin || "", + License: job.plate_no, + MileageIn: job.kmin || 0, + Vehiclecolor: job.v_color, + VehicleProductionDate: null, + VehiclePaintCode: null, + VehicleTrimCode: null, + VehicleBodyStyle: null, + DriveableFlag: job.driveable ? "Y" : "N" + }, + + InsuranceInformation: { + InsuranceCo: job.ins_co_nm || "", + CompanyName: job.ins_co_nm || "", + Address: job.ins_addr1 || "", + City: job.ins_addr1 || "", + State: job.ins_city || "", + Zip: job.ins_zip || "", + Phone: job.ins_ph1 || "", + Fax: job.ins_fax || "", + ClaimType: null, + LossType: job.loss_type || "", + Policy: job.policy_no || "", + Claim: job.clm_no || "", + InsuredLastName: null, + InsuredFirstName: null, + ClaimantLastName: null, + ClaimantFirstName: null, + Assignment: null, + InsuranceAgentLastName: null, + InsuranceAgentFirstName: null, + InsAgentPhone: null, + InsideAdjuster: null, + OutsideAdjuster: null + }, + Dates: { + DateofLoss: (job.loss_date && moment(job.loss_date).format(AhDateFormat)) || "", + InitialCustomerContactDate: null, + FirstFollowUpDate: null, + ReferralDate: null, + EstimateAppointmentDate: null, + SecondFollowUpDate: null, + AssignedDate: (job.asgn_date && moment(job.asgn_date).format(AhDateFormat)) || "", + EstComplete: null, + CustomerAuthorizationDate: null, + InsuranceAuthorizationDate: null, + DateOpened: + (job.date_open && moment(job.date_open).tz(job.bodyshop.timezone).format(AhDateFormat)) || + (job.created_at && moment(job.created_at).tz(job.bodyshop.timezone).format(AhDateFormat)) || + "", + ScheduledArrivalDate: + (job.scheduled_in && moment(job.scheduled_in).tz(job.bodyshop.timezone).format(AhDateFormat)) || "", + CarinShop: (job.actual_in && moment(job.actual_in).tz(job.bodyshop.timezone).format(AhDateFormat)) || "", + InsInspDate: null, + StartDate: job.date_repairstarted + ? (job.date_repairstarted && moment(job.date_repairstarted).tz(job.bodyshop.timezone).format(AhDateFormat)) || + "" + : (job.date_repairstarted && moment(job.actual_in).tz(job.bodyshop.timezone).format(AhDateFormat)) || "", + PartsOrder: null, + TeardownHold: null, + SupplementSubmittedDate: null, + SupplementApprovedDate: null, + AssntoBody: null, + AssntoMech: null, + AssntoPaint: null, + AssntoDetail: null, + // PromiseDate: + // (job.scheduled_completion && + // moment(job.scheduled_completion).format(AhDateFormat)) || + // "", + //InsuranceTargetOut: null, + CarComplete: + (job.actual_completion && moment(job.actual_completion).tz(job.bodyshop.timezone).format(AhDateFormat)) || "", + DeliveryAppointmentDate: + // (job.scheduled_delivery && + // moment(job.scheduled_delivery) + // .tz(job.bodyshop.timezone) + // .format(AhDateFormat)) || + (job.scheduled_completion && + moment(job.scheduled_completion).tz(job.bodyshop.timezone).format(AhDateFormat)) || + "", + DateClosed: + (job.date_invoiced && moment(job.date_invoiced).tz(job.bodyshop.timezone).format(AhDateFormat)) || "", + CustomerPaidInFullDate: null, + InsurancePaidInFullDate: null, + CustPickup: + (job.actual_delivery && moment(job.actual_delivery).tz(job.bodyshop.timezone).format(AhDateFormat)) || "", + AccountPostedDate: + job.date_exported && moment(job.date_exported).tz(job.bodyshop.timezone).format(AhDateFormat), + CSIProcessedDate: null, + ThankYouLetterSent: null, + AdditionalFollowUpDate: null + }, + Rates: { + BodyRate: job.rate_lab || 0, + RefinishRate: job.rate_lar || 0, + MechanicalRate: job.rate_lam || 0, + StructuralRate: job.rate_las || 0, + ElectricalRate: job.rate_lae || 0, + FrameRate: job.rate_laf || 0, + GlassRate: job.rate_lag || 0, + DetailRate: 0, // job.rate_lad || 0, + LaborMiscRate: 0, + PMRate: job.rate_mapa || 0, + BMRate: job.rate_mash || 0, + TaxRate: (job.parts_tax_rates && job.parts_tax_rates.PAN && job.parts_tax_rates.PAN.prt_tax_rt) || 0, + StorageRateperDay: 0, + DaysStored: 0 + }, + // EstimateTotals: { + // BodyHours: null, + // RefinishHours: null, + // MechanicalHours: null, + // StructuralHours: null, + // PartsTotal: null, + // PartsOEM: null, + // PartsAM: null, + // PartsReconditioned: null, + // PartsRecycled: null, + // PartsOther: null, + // SubletTotal: null, + // BodyLaborTotal: null, + // RefinishLaborTotal: null, + // MechanicalLaborTotal: null, + // StructuralLaborTotal: null, + // MiscellaneousChargeTotal: null, + // PMTotal: null, + // BMTotal: null, + // MiscTotal: null, + // TowingTotal: null, + // StorageTotal: null, + // DetailTotal: null, + // SalesTaxTotal: null, + // GrossTotal: null, + // DeductibleTotal: null, + // DepreciationTotal: null, + // Discount: null, + // CustomerPay: null, + // InsurancePay: null, + // Deposit: null, + // AmountDue: null, + // }, + // SupplementTotals: { + // BodyHours: null, + // RefinishHours: null, + // MechanicalHours: null, + // StructuralHours: null, + // PartsTotal: null, + // PartsOEM: null, + // PartsAM: null, + // PartsReconditioned: null, + // PartsRecycled: null, + // PartsOther: null, + // SubletTotal: null, + // BodyLaborTotal: null, + // RefinishLaborTotal: null, + // MechanicalLaborTotal: null, + // StructuralLaborTotal: null, + // MiscellaneousChargeTotal: null, + // PMTotal: null, + // BMTotal: null, + // MiscTotal: null, + // TowingTotal: null, + // StorageTotal: null, + // DetailTotal: null, + // SalesTaxTotal: null, + // GrossTotal: null, + // DeductibleTotal: null, + // DepreciationTotal: null, + // Discount: null, + // CustomerPay: null, + // InsurancePay: null, + // Deposit: null, + // AmountDue: null, + // }, + RevisedTotals: { + BodyHours: job.job_totals.rates.lab.hours.toFixed(2), + BodyRepairHours: job.joblines + .filter((line) => repairOpCodes.includes(line.lbr_op)) + .reduce((acc, val) => acc + val.mod_lb_hrs, 0) + .toFixed(2), + BodyReplaceHours: job.joblines + .filter((line) => replaceOpCodes.includes(line.lbr_op)) + .reduce((acc, val) => acc + val.mod_lb_hrs, 0) + .toFixed(2), + RefinishHours: job.job_totals.rates.lar.hours.toFixed(2), + MechanicalHours: job.job_totals.rates.lam.hours.toFixed(2), + StructuralHours: job.job_totals.rates.las.hours.toFixed(2), + + ElectricalHours: job.job_totals.rates.lae.hours.toFixed(2), + FrameHours: job.job_totals.rates.laf.hours.toFixed(2), + GlassHours: job.job_totals.rates.lag.hours.toFixed(2), + DetailHours: detailAdjustments.hours, //job.job_totals.rates.lad.hours.toFixed(2), + LaborMiscHours: ( + job.job_totals.rates.la1.hours + + job.job_totals.rates.la2.hours + + job.job_totals.rates.la3.hours + + job.job_totals.rates.la4.hours + + job.job_totals.rates.lau.hours - + detailAdjustments.hours + ).toFixed(2), + + PartsTotal: Dinero(job.job_totals.parts.parts.total).toFormat(AHDineroFormat), + PartsTotalCost: repairCosts.PartsTotalCost.toFormat(AHDineroFormat), + PartsOEM: Dinero(job.job_totals.parts.parts.list.PAN && job.job_totals.parts.parts.list.PAN.total) + .add(Dinero(job.job_totals.parts.parts.list.PAP && job.job_totals.parts.parts.list.PAP.total)) + .toFormat(AHDineroFormat), + PartsOEMCost: repairCosts.PartsOemCost.toFormat(AHDineroFormat), + PartsAM: Dinero(job.job_totals.parts.parts.list.PAA && job.job_totals.parts.parts.list.PAA.total).toFormat( + AHDineroFormat + ), + PartsAMCost: repairCosts.PartsAMCost.toFormat(AHDineroFormat), + PartsReconditioned: repairCosts.PartsReconditionedCost.toFormat(AHDineroFormat), + PartsReconditionedCost: repairCosts.PartsReconditionedCost.toFormat(AHDineroFormat), + PartsRecycled: Dinero( + job.job_totals.parts.parts.list.PAL && job.job_totals.parts.parts.list.PAL.total + ).toFormat(AHDineroFormat), + PartsRecycledCost: repairCosts.PartsRecycledCost.toFormat(AHDineroFormat), + PartsOther: Dinero(job.job_totals.parts.parts.list.PAO && job.job_totals.parts.parts.list.PAO.total).toFormat( + AHDineroFormat + ), + PartsOtherCost: repairCosts.PartsOtherCost.toFormat(AHDineroFormat), + SubletTotal: Dinero(job.job_totals.parts.sublets.total).toFormat(AHDineroFormat), + SubletTotalCost: repairCosts.SubletTotalCost.toFormat(AHDineroFormat), + BodyLaborTotal: Dinero(job.job_totals.rates.lab.total).toFormat(AHDineroFormat), + BodyLaborTotalCost: repairCosts.BodyLaborTotalCost.toFormat(AHDineroFormat), + RefinishLaborTotal: Dinero(job.job_totals.rates.lar.total).toFormat(AHDineroFormat), + RefinishLaborTotalCost: repairCosts.RefinishLaborTotalCost.toFormat(AHDineroFormat), + MechanicalLaborTotal: Dinero(job.job_totals.rates.lam.total).toFormat(AHDineroFormat), + MechanicalLaborTotalCost: repairCosts.MechanicalLaborTotalCost.toFormat(AHDineroFormat), + StructuralLaborTotal: Dinero(job.job_totals.rates.las.total).toFormat(AHDineroFormat), + StructuralLaborTotalCost: repairCosts.StructuralLaborTotalCost.toFormat(AHDineroFormat), + + ElectricalLaborTotal: Dinero(job.job_totals.rates.lae.total).toFormat(AHDineroFormat), + ElectricalLaborTotalCost: repairCosts.ElectricalLaborTotalCost.toFormat(AHDineroFormat), + FrameLaborTotal: Dinero(job.job_totals.rates.laf.total).toFormat(AHDineroFormat), + FrameLaborTotalCost: repairCosts.FrameLaborTotalCost.toFormat(AHDineroFormat), + GlassLaborTotal: Dinero(job.job_totals.rates.lag.total).toFormat(AHDineroFormat), + GlassLaborTotalCost: repairCosts.GlassLaborTotalCost.toFormat(AHDineroFormat), + DetailLaborTotal: detailAdjustments.amount.toFormat(AHDineroFormat), + // Dinero(job.job_totals.rates.lad.total).toFormat( + // AHDineroFormat + // ), + DetailLaborTotalCost: Dinero().toFormat(AHDineroFormat), + // repairCosts.DetailLaborTotalCost.toFormat(AHDineroFormat), + LaborMiscTotal: Dinero(job.job_totals.rates.la1.total) + .add(Dinero(job.job_totals.rates.la2.total)) + .add(Dinero(job.job_totals.rates.la3.total)) + .add(Dinero(job.job_totals.rates.la4.total)) + .add(Dinero(job.job_totals.rates.lau.total)) + .subtract(detailAdjustments.amount) + .toFormat(AHDineroFormat), + LaborMiscTotalCost: 0, + MiscellaneousChargeTotal: 0, + MiscellaneousChargeTotalCost: 0, + PMTotal: Dinero(job.job_totals.rates.mapa.total).toFormat(AHDineroFormat), + PMTotalCost: repairCosts.PMTotalCost.toFormat(AHDineroFormat), + BMTotal: Dinero(job.job_totals.rates.mash.total).toFormat(AHDineroFormat), + BMTotalCost: repairCosts.BMTotalCost.toFormat(AHDineroFormat), + MiscTotal: Dinero(job.job_totals.additional.additionalCosts).toFormat(AHDineroFormat), + MiscTotalCost: 0, + TowingTotal: Dinero(job.job_totals.additional.towing).toFormat(AHDineroFormat), + TowingTotalCost: repairCosts.TowingTotalCost.toFormat(AHDineroFormat), + StorageTotal: Dinero(job.job_totals.additional.storage).toFormat(AHDineroFormat), + StorageTotalCost: repairCosts.StorageTotalCost.toFormat(AHDineroFormat), + DetailTotal: detailAdjustments.amount.toFormat(AHDineroFormat), + DetailTotalCost: 0, + SalesTaxTotal: Dinero(job.job_totals.totals.local_tax) + .add(Dinero(job.job_totals.totals.state_tax)) + .add(Dinero(job.job_totals.totals.federal_tax)) + .add(Dinero(job.job_totals.additional.pvrt)) + .toFormat(AHDineroFormat), + SalesTaxTotalCost: 0, + GrossTotal: Dinero(job.job_totals.totals.total_repairs).toFormat(AHDineroFormat), + DeductibleTotal: Dinero({ + amount: Math.round((job.ded_amt || 0) * 100) + }).toFormat(AHDineroFormat), + DepreciationTotal: Dinero(job.job_totals.totals.custPayable.dep_taxes).toFormat(AHDineroFormat), + Discount: Dinero(job.job_totals.additional.adjustments).toFormat(AHDineroFormat), + CustomerPay: Dinero(job.job_totals.totals.custPayable.total).toFormat(AHDineroFormat), + InsurancePay: Dinero(job.job_totals.totals.total_repairs) + .subtract(Dinero(job.job_totals.totals.custPayable.total)) + .toFormat(AHDineroFormat), + Deposit: 0, + AmountDue: 0 + }, + Misc: { + ProductionStatus: null, + StatusDescription: null, + Hub50Comment: null, + DateofChange: null, + BodyTechName: null, + TotalLossYN: job.tlos_ind ? "Y" : "N", + InsScreenCommentsLine1: null, + InsScreenCommentsLine2: null, + AssignmentCaller: null, + AssignmentDivision: null, + LocationofPrimaryImpact: (job.area_of_damage && job.area_of_damage.impact1) || 0, + LocationofSecondaryImpact: (job.area_of_damage && job.area_of_damage.impact2) || 0, + PaintTechID: null, + PaintTechName: null, + ImportType: null, + ImportFile: null, + GSTTax: Dinero(job.job_totals.totals.federal_tax).toFormat(AHDineroFormat), + RepairDelayStatusCode: null, + RepairDelaycomment: null, + AgentMktgID: null, + AgentCity: null, + Picture1: null, + Picture2: null, + ExtNoteDate: null, + RentalOrdDate: null, + RentalPUDate: null, + RentalDueDate: null, + RentalActRetDate: null, + RentalCompanyID: null, + // CSIID: null, + InsGroupCode: null + }, + + DetailLines: { + DetailLine: + job.joblines.length > 0 + ? job.joblines.map((jl) => GenerateDetailLines(job, jl, job.bodyshop.md_order_statuses)) + : [generateNullDetailLine()] + } + }; + return ret; + } catch (error) { + logger.log("autohouse-job-calculate-error", "ERROR", "api", null, { + error + }); + + errorCallback({ jobid: job.id, ro_number: job.ro_number, error }); + } }; const CreateCosts = (job) => { - //Create a mapping based on AH Requirements + //Create a mapping based on AH Requirements - //For DMS, the keys in the object below are the CIECA part types. - const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => { - //At the bill level. - bill_val.billlines.map((line_val) => { - //At the bill line level. + //For DMS, the keys in the object below are the CIECA part types. + const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => { + //At the bill level. + bill_val.billlines.map((line_val) => { + //At the bill line level. - if (!bill_acc[line_val.cost_center]) - bill_acc[line_val.cost_center] = Dinero(); + if (!bill_acc[line_val.cost_center]) bill_acc[line_val.cost_center] = Dinero(); - bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add( - Dinero({ - amount: Math.round((line_val.actual_cost || 0) * 100), - }) - .multiply(line_val.quantity) - .multiply(bill_val.is_credit_memo ? -1 : 1) - ); + bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add( + Dinero({ + amount: Math.round((line_val.actual_cost || 0) * 100) + }) + .multiply(line_val.quantity) + .multiply(bill_val.is_credit_memo ? -1 : 1) + ); - return null; + return null; + }); + 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 || + typeof job.bodyshop.jc_hourly_rates.mapa === "number" || + isNaN(job.bodyshop.jc_hourly_rates.mapa) === false) + ) { + if (!billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA]) + billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = Dinero(); + if (job.bodyshop.use_paint_scale_data === true) { + if (job.mixdata.length > 0) { + billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = Dinero({ + amount: Math.round(((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100) }); - 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 || - typeof job.bodyshop.jc_hourly_rates.mapa === "number" || - isNaN(job.bodyshop.jc_hourly_rates.mapa) === false) - ) { - if ( - !billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] - ) - billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = Dinero(); - if (job.bodyshop.use_paint_scale_data === true) { - if (job.mixdata.length > 0) { - billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = Dinero({ - amount: Math.round( - ((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100 - ), - }); - } else { - billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ].add( - Dinero({ - amount: Math.round( - (job.bodyshop.jc_hourly_rates && - job.bodyshop.jc_hourly_rates.mapa * 100) || - 0 - ), - }).multiply(job.job_totals.rates.mapa.hours) - ); - } - } else { - billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ].add( - Dinero({ - amount: Math.round( - (job.bodyshop.jc_hourly_rates && - job.bodyshop.jc_hourly_rates.mapa * 100) || - 0 - ), - }).multiply(job.job_totals.rates.mapa.hours) - ); - } - } - 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: Math.round( - (job.bodyshop.jc_hourly_rates && - job.bodyshop.jc_hourly_rates.mash * 100) || - 0 - ), - }).multiply(job.job_totals.rates.mash.hours) + } else { + billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ].add( + Dinero({ + amount: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa * 100) || 0) + }).multiply(job.job_totals.rates.mapa.hours) ); + } + } else { + billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ].add( + Dinero({ + amount: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa * 100) || 0) + }).multiply(job.job_totals.rates.mapa.hours) + ); } - //Uses CIECA Labor types. - const ticketTotalsByCostCenter = job.timetickets.reduce( - (ticket_acc, ticket_val) => { - //At the invoice level. - if (!ticket_acc[ticket_val.cost_center]) - ticket_acc[ticket_val.cost_center] = Dinero(); - - ticket_acc[ticket_val.cost_center] = ticket_acc[ - ticket_val.cost_center - ].add( - Dinero({ - amount: Math.round((ticket_val.rate || 0) * 100), - }).multiply( - (ticket_val.flat_rate - ? ticket_val.productivehrs - : ticket_val.actualhrs) || 0 - ) - ); - - return ticket_acc; - }, - {} + } + 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: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash * 100) || 0) + }).multiply(job.job_totals.rates.mash.hours) ); - //CIECA STANDARD MAPPING OBJECT. + } + //Uses CIECA Labor types. + const ticketTotalsByCostCenter = job.timetickets.reduce((ticket_acc, ticket_val) => { + //At the invoice level. + if (!ticket_acc[ticket_val.cost_center]) ticket_acc[ticket_val.cost_center] = Dinero(); - const ciecaObj = { - ATS: "ATS", - LA1: "LA1", - LA2: "LA2", - LA3: "LA3", - LA4: "LA4", - LAA: "LAA", - LAB: "LAB", - LAD: "LAD", - LAE: "LAE", - LAF: "LAF", - LAG: "LAG", - LAM: "LAM", - LAR: "LAR", - LAS: "LAS", - LAU: "LAU", - PAA: "PAA", - PAC: "PAC", - PAG: "PAG", - PAL: "PAL", - PAM: "PAM", - PAN: "PAN", - PAO: "PAO", - PAP: "PAP", - PAR: "PAR", - PAS: "PAS", - TOW: "TOW", - MAPA: "MAPA", - MASH: "MASH", - PASL: "PASL", - }; - const defaultCosts = - job.bodyshop.cdk_dealerid || job.bodyshop.pbs_serialnumber - ? ciecaObj - : job.bodyshop.md_responsibility_centers.defaults.costs; + ticket_acc[ticket_val.cost_center] = ticket_acc[ticket_val.cost_center].add( + Dinero({ + amount: Math.round((ticket_val.rate || 0) * 100) + }).multiply((ticket_val.flat_rate ? ticket_val.productivehrs : ticket_val.actualhrs) || 0) + ); - return { - PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => { - if ( - key !== defaultCosts.PAS && - key !== defaultCosts.PASL && - key !== defaultCosts.MAPA && - key !== defaultCosts.MASH && - key !== defaultCosts.TOW - ) - return acc.add(billTotalsByCostCenters[key]); - return acc; - }, Dinero()), - PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add( - billTotalsByCostCenters[defaultCosts.PAP] || Dinero() - ), - PartsAMCost: billTotalsByCostCenters[defaultCosts.PAA] || Dinero(), - PartsReconditionedCost: - billTotalsByCostCenters[defaultCosts.PAM] || Dinero(), - PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAL] || Dinero(), - PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), - SubletTotalCost: - billTotalsByCostCenters[defaultCosts.PAS] || - Dinero(billTotalsByCostCenters[defaultCosts.PASL] || Dinero()), - BodyLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(), - RefinishLaborTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(), - MechanicalLaborTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(), - StructuralLaborTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(), - ElectricalLaborTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(), - FrameLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(), - GlassLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(), - DetailLaborTotalCost: Dinero(), - // ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(), - LaborMiscTotalCost: (ticketTotalsByCostCenter[defaultCosts.LA1] || Dinero()) - .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero()) - .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero()) - .add(ticketTotalsByCostCenter[defaultCosts.LA3] || Dinero()) - .add(ticketTotalsByCostCenter[defaultCosts.LA4] || Dinero()) - .add(ticketTotalsByCostCenter[defaultCosts.LAU] || Dinero()), - PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(), - BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(), - MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), - TowingTotalCost: billTotalsByCostCenters[defaultCosts.TOW] || Dinero(), - StorageTotalCost: Dinero(), - DetailTotal: Dinero(), - DetailTotalCost: Dinero(), - SalesTaxTotalCost: Dinero(), - }; + return ticket_acc; + }, {}); + //CIECA STANDARD MAPPING OBJECT. + + const ciecaObj = { + ATS: "ATS", + LA1: "LA1", + LA2: "LA2", + LA3: "LA3", + LA4: "LA4", + LAA: "LAA", + LAB: "LAB", + LAD: "LAD", + LAE: "LAE", + LAF: "LAF", + LAG: "LAG", + LAM: "LAM", + LAR: "LAR", + LAS: "LAS", + LAU: "LAU", + PAA: "PAA", + PAC: "PAC", + PAG: "PAG", + PAL: "PAL", + PAM: "PAM", + PAN: "PAN", + PAO: "PAO", + PAP: "PAP", + PAR: "PAR", + PAS: "PAS", + TOW: "TOW", + MAPA: "MAPA", + MASH: "MASH", + PASL: "PASL" + }; + const defaultCosts = + job.bodyshop.cdk_dealerid || job.bodyshop.pbs_serialnumber + ? ciecaObj + : job.bodyshop.md_responsibility_centers.defaults.costs; + + return { + PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => { + if ( + key !== defaultCosts.PAS && + key !== defaultCosts.PASL && + key !== defaultCosts.MAPA && + key !== defaultCosts.MASH && + key !== defaultCosts.TOW + ) + return acc.add(billTotalsByCostCenters[key]); + return acc; + }, Dinero()), + PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add( + billTotalsByCostCenters[defaultCosts.PAP] || Dinero() + ), + PartsAMCost: billTotalsByCostCenters[defaultCosts.PAA] || Dinero(), + PartsReconditionedCost: billTotalsByCostCenters[defaultCosts.PAM] || Dinero(), + PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAL] || Dinero(), + PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), + SubletTotalCost: + billTotalsByCostCenters[defaultCosts.PAS] || Dinero(billTotalsByCostCenters[defaultCosts.PASL] || Dinero()), + BodyLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(), + RefinishLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(), + MechanicalLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(), + StructuralLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(), + ElectricalLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(), + FrameLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(), + GlassLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(), + DetailLaborTotalCost: Dinero(), + // ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(), + LaborMiscTotalCost: (ticketTotalsByCostCenter[defaultCosts.LA1] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA3] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA4] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LAU] || Dinero()), + PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(), + BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(), + MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), + TowingTotalCost: billTotalsByCostCenters[defaultCosts.TOW] || Dinero(), + StorageTotalCost: Dinero(), + DetailTotal: Dinero(), + DetailTotalCost: Dinero(), + SalesTaxTotalCost: Dinero() + }; }; const StatusMapping = (status, md_ro_statuses) => { - //Possible return statuses EST, SCH, ARR, IPR, RDY, DEL, CLO, CAN, UNDEFINED. - const { - default_imported, - default_open, - default_scheduled, - default_arrived, - default_completed, - default_delivered, - default_invoiced, - default_exported, - default_void, - } = md_ro_statuses; + //Possible return statuses EST, SCH, ARR, IPR, RDY, DEL, CLO, CAN, UNDEFINED. + const { + default_imported, + default_open, + default_scheduled, + default_arrived, + default_completed, + default_delivered, + default_invoiced, + default_exported, + default_void + } = md_ro_statuses; - if (status === default_open || status === default_imported) return "EST"; - else if (status === default_scheduled) return "SCH"; - else if (status === default_arrived) return "ARR"; - else if (status === default_completed) return "RDY"; - else if (status === default_delivered) return "DEL"; - else if (status === default_invoiced || status === default_exported) - return "CLO"; - else if (status === default_void) return "VOID"; - else if (md_ro_statuses.production_statuses.includes(status)) return "IPR"; - else return "UNDEFINED"; + if (status === default_open || status === default_imported) return "EST"; + else if (status === default_scheduled) return "SCH"; + else if (status === default_arrived) return "ARR"; + else if (status === default_completed) return "RDY"; + else if (status === default_delivered) return "DEL"; + else if (status === default_invoiced || status === default_exported) return "CLO"; + else if (status === default_void) return "VOID"; + else if (md_ro_statuses.production_statuses.includes(status)) return "IPR"; + else return "UNDEFINED"; }; const GenerateDetailLines = (job, line, statuses) => { - const ret = { - BackOrdered: line.status === statuses.default_bo ? "1" : "0", - Cost: - (line.billlines[0] && - (line.billlines[0].actual_cost * line.billlines[0].quantity).toFixed( - 2 - )) || - 0, - //Critical: null, - Description: line.line_desc - ? line.line_desc.replace(/[^\x00-\x7F]/g, "") - : "", - DiscountMarkup: line.prt_dsmk_m || 0, - InvoiceNumber: line.billlines[0] && line.billlines[0].bill.invoice_number, - IOUPart: 0, - LineNumber: line.line_no || 0, - MarkUp: null, - OrderedOn: - (line.parts_order_lines[0] && - moment(line.parts_order_lines[0].parts_order.order_date).format( - AhDateFormat - )) || - "", - OriginalCost: null, - OriginalInvoiceNumber: null, - PriceEach: line.act_price || 0, - PartNumber: line.oem_partno - ? line.oem_partno.replace(/[^\x00-\x7F]/g, "") - : "", - ProfitPercent: null, - PurchaseOrderNumber: null, - Qty: line.part_qty || 0, - Status: line.status || "", - SupplementNumber: line.line_ind ? line.line_ind.replace(/[^\d.-]/g, "") : 0, - Type: line.part_type || "", - Vendor: (line.billlines[0] && line.billlines[0].bill.vendor.name) || "", - VendorPaid: null, - VendorPrice: - (line.billlines[0] && line.billlines[0].actual_price.toFixed(2)) || 0, - Deleted: null, - ExpectedOn: null, - ReceivedOn: - line.billlines[0] && - moment(line.billlines[0].bill.date).format(AhDateFormat), - OrderedBy: null, - ShipVia: null, - VendorContact: null, - EstimateAmount: (line.act_price * line.part_qty).toFixed(2) || 0, //Rebecca - }; - return ret; + const ret = { + BackOrdered: line.status === statuses.default_bo ? "1" : "0", + Cost: (line.billlines[0] && (line.billlines[0].actual_cost * line.billlines[0].quantity).toFixed(2)) || 0, + //Critical: null, + Description: line.line_desc ? line.line_desc.replace(/[^\x00-\x7F]/g, "") : "", + DiscountMarkup: line.prt_dsmk_m || 0, + InvoiceNumber: line.billlines[0] && line.billlines[0].bill.invoice_number, + IOUPart: 0, + LineNumber: line.line_no || 0, + MarkUp: null, + OrderedOn: + (line.parts_order_lines[0] && moment(line.parts_order_lines[0].parts_order.order_date).format(AhDateFormat)) || + "", + OriginalCost: null, + OriginalInvoiceNumber: null, + PriceEach: line.act_price || 0, + PartNumber: line.oem_partno ? line.oem_partno.replace(/[^\x00-\x7F]/g, "") : "", + ProfitPercent: null, + PurchaseOrderNumber: null, + Qty: line.part_qty || 0, + Status: line.status || "", + SupplementNumber: line.line_ind ? line.line_ind.replace(/[^\d.-]/g, "") : 0, + Type: line.part_type || "", + Vendor: (line.billlines[0] && line.billlines[0].bill.vendor.name) || "", + VendorPaid: null, + VendorPrice: (line.billlines[0] && line.billlines[0].actual_price.toFixed(2)) || 0, + Deleted: null, + ExpectedOn: null, + ReceivedOn: line.billlines[0] && moment(line.billlines[0].bill.date).format(AhDateFormat), + OrderedBy: null, + ShipVia: null, + VendorContact: null, + EstimateAmount: (line.act_price * line.part_qty).toFixed(2) || 0 //Rebecca + }; + return ret; }; const generateNullDetailLine = () => { - return { - BackOrdered: "0", - Cost: 0, - Critical: null, - Description: "No Lines on Estimate", - DiscountMarkup: 0, - InvoiceNumber: null, - IOUPart: 0, - LineNumber: 0, - MarkUp: null, - OrderedOn: "", - OriginalCost: null, - OriginalInvoiceNumber: null, - PriceEach: 0, - PartNumber: 0, - ProfitPercent: null, - PurchaseOrderNumber: null, - Qty: 0, - Status: "", - SupplementNumber: 0, - Type: "", - Vendor: "", - VendorPaid: null, - VendorPrice: 0, - Deleted: 0, - ExpectedOn: "", - ReceivedOn: "", - OrderedBy: "", - ShipVia: "", - VendorContact: "", - EstimateAmount: 0, - }; + return { + BackOrdered: "0", + Cost: 0, + Critical: null, + Description: "No Lines on Estimate", + DiscountMarkup: 0, + InvoiceNumber: null, + IOUPart: 0, + LineNumber: 0, + MarkUp: null, + OrderedOn: "", + OriginalCost: null, + OriginalInvoiceNumber: null, + PriceEach: 0, + PartNumber: 0, + ProfitPercent: null, + PurchaseOrderNumber: null, + Qty: 0, + Status: "", + SupplementNumber: 0, + Type: "", + Vendor: "", + VendorPaid: null, + VendorPrice: 0, + Deleted: 0, + ExpectedOn: "", + ReceivedOn: "", + OrderedBy: "", + ShipVia: "", + VendorContact: "", + EstimateAmount: 0 + }; }; diff --git a/server/data/claimscorp.js b/server/data/claimscorp.js index feb884a44..fb3012b7a 100644 --- a/server/data/claimscorp.js +++ b/server/data/claimscorp.js @@ -7,15 +7,12 @@ const _ = require("lodash"); const logger = require("../utils/logger"); const fs = require("fs"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); let Client = require("ssh2-sftp-client"); const client = require("../graphql-client/graphql-client").client; -const {sendServerEmail} = require("../email/sendemail"); +const { sendServerEmail } = require("../email/sendemail"); const CCDineroFormat = "0,0.00"; const AhDateFormat = "MM/DD/YYYY"; @@ -23,824 +20,656 @@ const repairOpCodes = ["OP4", "OP9", "OP10"]; const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"]; const ftpSetup = { - host: process.env.CLAIMSCORP_HOST, - 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), - algorithms: { - serverHostKey: ["ssh-rsa", "ssh-dss"], - }, + host: process.env.CLAIMSCORP_HOST, + 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), + algorithms: { + serverHostKey: ["ssh-rsa", "ssh-dss"] + } }; exports.default = async (req, res) => { - //Query for the List of Bodyshop Clients. - logger.log("claimscorp-start", "DEBUG", "api", null, null); - const {bodyshops} = await client.request(queries.GET_CLAIMSCORP_SHOPS); + //Query for the List of Bodyshop Clients. + logger.log("claimscorp-start", "DEBUG", "api", null, null); + const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS); - const specificShopIds = req.body.bodyshopIds; // ['uuid] - const {start, end, skipUpload} = req.body; //YYYY-MM-DD - if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) { - res.sendStatus(401); - return; - } - const allxmlsToUpload = []; - const allErrors = []; - try { - for (const bodyshop of specificShopIds - ? bodyshops.filter((b) => specificShopIds.includes(b.id)) - : bodyshops) { - logger.log("claimscorp-start-shop-extract", "DEBUG", "api", bodyshop.id, { - shopname: bodyshop.shopname, - }); - const erroredJobs = []; - try { - const {jobs, bodyshops_by_pk} = await client.request( - queries.CLAIMSCORP_QUERY, - { - bodyshopid: bodyshop.id, - start: start - ? moment(start).startOf("day") - : moment().subtract(5, "days").startOf("day"), - ...(end && {end: moment(end).endOf("day")}), - } - ); - - const claimsCorpObject = { - DataFeed: { - ShopInfo: { - ShopID: bodyshops_by_pk.claimscorpid, - ShopName: bodyshops_by_pk.shopname, - RO: jobs.map((j) => - CreateRepairOrderTag( - {...j, bodyshop: bodyshops_by_pk}, - function ({job, error}) { - erroredJobs.push({job: job, error: error.toString()}); - } - ) - ), - }, - }, - }; - - if (erroredJobs.length > 0) { - logger.log("claimscorp-failed-jobs", "ERROR", "api", bodyshop.id, { - count: erroredJobs.length, - jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)), - }); - } - - var ret = builder - .create( - { - // version: "1.0", - // encoding: "UTF-8", - //keepNullNodes: true, - }, - claimsCorpObject - ) - .end({allowEmptyTags: true}); - - allxmlsToUpload.push({ - count: claimsCorpObject.DataFeed.ShopInfo.RO.length, - xml: ret, - filename: `${bodyshop.claimscorpid}-${moment().format( - "YYYYMMDDTHHMMss" - )}.xml`, - }); - - logger.log("claimscorp-end-shop-extract", "DEBUG", "api", bodyshop.id, { - shopname: bodyshop.shopname, - }); - } catch (error) { - //Error at the shop level. - logger.log("claimscorp-error-shop", "ERROR", "api", bodyshop.id, { - ...error, - }); - - allErrors.push({ - bodyshopid: bodyshop.id, - imexshopid: bodyshop.imexshopid, - claimscorpid: bodyshop.claimscorpid, - fatal: true, - errors: [error.toString()], - }); - } finally { - allErrors.push({ - bodyshopid: bodyshop.id, - imexshopid: bodyshop.imexshopid, - claimscorpid: bodyshop.claimscorpid, - errors: erroredJobs.map((ej) => ({ - ro_number: ej.job?.ro_number, - jobid: ej.job?.id, - error: ej.error, - })), - }); - } - } - - if (skipUpload) { - for (const xmlObj of allxmlsToUpload) { - fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); - } - - res.json(allxmlsToUpload); - sendServerEmail({ - subject: `ClaimsCorp Report ${moment().format("MM-DD-YY")}`, - text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} - Uploaded: ${JSON.stringify( - allxmlsToUpload.map((x) => ({filename: x.filename, count: x.count})), - null, - 2 - )} - `, - }); - return; - } - - let sftp = new Client(); - sftp.on("error", (errors) => - logger.log("claimscorp-sftp-error", "ERROR", "api", null, { - ...errors, - }) - ); - try { - //Connect to the FTP and upload all. - - await sftp.connect(ftpSetup); - - for (const xmlObj of allxmlsToUpload) { - logger.log("claimscorp-sftp-upload", "DEBUG", "api", null, { - filename: xmlObj.filename, - }); - - const uploadResult = await sftp.put( - Buffer.from(xmlObj.xml), - `/${xmlObj.filename}` - ); - logger.log("claimscorp-sftp-upload-result", "DEBUG", "api", null, { - uploadResult, - }); - } - - //***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml - } catch (error) { - logger.log("claimscorp-sftp-error", "ERROR", "api", null, { - ...error, - }); - } finally { - sftp.end(); - } - sendServerEmail({ - subject: `ClaimsCorp Report ${moment().format("MM-DD-YY")}`, - text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} - Uploaded: ${JSON.stringify( - allxmlsToUpload.map((x) => ({filename: x.filename, count: x.count})), - null, - 2 - )} - `, + const specificShopIds = req.body.bodyshopIds; // ['uuid] + const { start, end, skipUpload } = req.body; //YYYY-MM-DD + if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) { + res.sendStatus(401); + return; + } + const allxmlsToUpload = []; + const allErrors = []; + try { + for (const bodyshop of specificShopIds ? bodyshops.filter((b) => specificShopIds.includes(b.id)) : bodyshops) { + logger.log("claimscorp-start-shop-extract", "DEBUG", "api", bodyshop.id, { + shopname: bodyshop.shopname + }); + const erroredJobs = []; + try { + const { jobs, bodyshops_by_pk } = await client.request(queries.CLAIMSCORP_QUERY, { + bodyshopid: bodyshop.id, + start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"), + ...(end && { end: moment(end).endOf("day") }) }); - res.sendStatus(200); - } catch (error) { - res.status(200).json(error); + + const claimsCorpObject = { + DataFeed: { + ShopInfo: { + ShopID: bodyshops_by_pk.claimscorpid, + ShopName: bodyshops_by_pk.shopname, + RO: jobs.map((j) => + CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) { + erroredJobs.push({ job: job, error: error.toString() }); + }) + ) + } + } + }; + + if (erroredJobs.length > 0) { + logger.log("claimscorp-failed-jobs", "ERROR", "api", bodyshop.id, { + count: erroredJobs.length, + jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)) + }); + } + + var ret = builder + .create( + { + // version: "1.0", + // encoding: "UTF-8", + //keepNullNodes: true, + }, + claimsCorpObject + ) + .end({ allowEmptyTags: true }); + + allxmlsToUpload.push({ + count: claimsCorpObject.DataFeed.ShopInfo.RO.length, + xml: ret, + filename: `${bodyshop.claimscorpid}-${moment().format("YYYYMMDDTHHMMss")}.xml` + }); + + logger.log("claimscorp-end-shop-extract", "DEBUG", "api", bodyshop.id, { + shopname: bodyshop.shopname + }); + } catch (error) { + //Error at the shop level. + logger.log("claimscorp-error-shop", "ERROR", "api", bodyshop.id, { + ...error + }); + + allErrors.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + claimscorpid: bodyshop.claimscorpid, + fatal: true, + errors: [error.toString()] + }); + } finally { + allErrors.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + claimscorpid: bodyshop.claimscorpid, + errors: erroredJobs.map((ej) => ({ + ro_number: ej.job?.ro_number, + jobid: ej.job?.id, + error: ej.error + })) + }); + } } + + if (skipUpload) { + for (const xmlObj of allxmlsToUpload) { + fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); + } + + res.json(allxmlsToUpload); + sendServerEmail({ + subject: `ClaimsCorp Report ${moment().format("MM-DD-YY")}`, + text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} + Uploaded: ${JSON.stringify( + allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })), + null, + 2 + )} + ` + }); + return; + } + + let sftp = new Client(); + sftp.on("error", (errors) => + logger.log("claimscorp-sftp-error", "ERROR", "api", null, { + ...errors + }) + ); + try { + //Connect to the FTP and upload all. + + await sftp.connect(ftpSetup); + + for (const xmlObj of allxmlsToUpload) { + logger.log("claimscorp-sftp-upload", "DEBUG", "api", null, { + filename: xmlObj.filename + }); + + const uploadResult = await sftp.put(Buffer.from(xmlObj.xml), `/${xmlObj.filename}`); + logger.log("claimscorp-sftp-upload-result", "DEBUG", "api", null, { + uploadResult + }); + } + + //***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml + } catch (error) { + logger.log("claimscorp-sftp-error", "ERROR", "api", null, { + ...error + }); + } finally { + sftp.end(); + } + sendServerEmail({ + subject: `ClaimsCorp Report ${moment().format("MM-DD-YY")}`, + text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} + Uploaded: ${JSON.stringify( + allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })), + null, + 2 + )} + ` + }); + res.sendStatus(200); + } catch (error) { + res.status(200).json(error); + } }; const CreateRepairOrderTag = (job, errorCallback) => { - //Level 2 + //Level 2 - if (!job.job_totals) { - errorCallback({ - jobid: job.id, - job: job, - ro_number: job.ro_number, - error: {toString: () => "No job totals for RO."}, - }); - return {}; - } + if (!job.job_totals) { + errorCallback({ + jobid: job.id, + job: job, + ro_number: job.ro_number, + error: { toString: () => "No job totals for RO." } + }); + return {}; + } - const repairCosts = CreateCosts(job); + const repairCosts = CreateCosts(job); - //Calculate detail only lines. - const detailAdjustments = job.joblines - .filter((jl) => jl.ah_detail_line && jl.mod_lbr_ty) - .reduce( - (acc, val) => { - return { - hours: acc.hours + val.mod_lb_hrs, - amount: acc.amount.add( - Dinero({ - amount: Math.round( - (job.job_totals.rates[val.mod_lbr_ty.toLowerCase()].rate || 0) * - val.mod_lb_hrs * - 100 - ), - }) - ), - }; - }, - {hours: 0, amount: Dinero()} - ); - - try { - const ret = { - RoNumber: job.ro_number, - Customer: { - CustomerZip: (job.ownr_zip && job.ownr_zip.substring(0, 3)) || "", - CustomerState: job.ownr_st || "", - }, - Vehicle: { - Year: job.v_model_yr - ? parseInt(job.v_model_yr.match(/\d/g)) - ? parseInt(job.v_model_yr.match(/\d/g).join(""), 10) - : "" - : "", - Make: job.v_make_desc || "", - Model: job.v_model_desc || "", - BodyStyle: (job.vehicle && job.vehicle.v_bstyle) || "", - Color: job.v_color || "", - VIN: job.v_vin || "", - }, - Carrier: { - UniqueID: job.ins_co_nm || "", - InsuranceCompany: job.ins_co_nm || "", - }, - Claim: job.clm_no || "", - Contacts: { - PC: job.employee_csr_rel - ? `${ - job.employee_csr_rel.last_name - ? job.employee_csr_rel.last_name - : "" - }${job.employee_csr_rel.last_name ? ", " : ""}${ - job.employee_csr_rel.first_name - ? job.employee_csr_rel.first_name - : "" - }` - : "", - Phone1: "", - Phone2: "", - Estimator: `${job.est_ct_ln ? job.est_ct_ln : ""}${ - job.est_ct_ln ? ", " : "" - }${job.est_ct_fn ? job.est_ct_fn : ""}`, - BodyTechnician: job.employee_body_rel - ? `${ - job.employee_body_rel.last_name - ? job.employee_body_rel.last_name - : "" - }${job.employee_body_rel.last_name ? ", " : ""}${ - job.employee_body_rel.first_name - ? job.employee_body_rel.first_name - : "" - }` - : "", - PaintTechnician: job.employee_refinish_rel - ? `${ - job.employee_refinish_rel.last_name - ? job.employee_refinish_rel.last_name - : "" - }${job.employee_refinish_rel.last_name ? ", " : ""}${ - job.employee_refinish_rel.first_name - ? job.employee_refinish_rel.first_name - : "" - }` - : "", - }, - Dates: { - DateCreated: - (job.date_estimated && - moment(job.date_estimated).format(AhDateFormat)) || - "", - DateLoss: - (job.loss_date && moment(job.loss_date).format(AhDateFormat)) || "", - DateFNOL: "", - DateContact: "", - DateEstimated: - (job.date_estimated && - moment(job.date_estimated).format(AhDateFormat)) || - "", - DateScheduled: - (job.scheduled_in && - moment(job.scheduled_in) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - DateArrived: - (job.actual_in && - moment(job.actual_in) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - DateFirstPartsOrdered: - (job.parts_orders && - job.parts_orders[0] && - moment(job.parts_orders[0].created_at) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - DateStart: job.date_repairstarted - ? (job.date_repairstarted && - moment(job.date_repairstarted) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "" - : (job.date_repairstarted && - moment(job.actual_in) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - BodyStart: "", - BodyEnd: "", - FrameStart: "", - FrameEnd: "", - PrepStart: "", - PrepEnd: "", - SprayStart: "", - SprayEnd: "", - DateReady: - (job.actual_completion && - moment(job.actual_completion) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - DateScheduledDelivery: - (job.scheduled_delivery && - moment(job.scheduled_delivery) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - DateDelivered: - (job.actual_delivery && - moment(job.actual_delivery) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - DateClosed: - (job.date_invoiced && - moment(job.date_invoiced) - .tz(job.bodyshop.timezone) - .format(AhDateFormat)) || - "", - BilledDate: "", - PaidInFullDate: "", - ROStatus: job.tlos_ind - ? "TOT" - : StatusMapping(job.status, job.bodyshop.md_ro_statuses), - }, - Sales: { - Body: Dinero(job.job_totals.rates.lab.total) - .add(Dinero(job.job_totals.rates.laa.total)) - .add(Dinero(job.job_totals.rates.lad.total)) - .add(Dinero(job.job_totals.rates.las.total)) - .toFormat(CCDineroFormat), - Paint: Dinero(job.job_totals.rates.lar.total).toFormat(CCDineroFormat), - Prep: Dinero().toFormat(CCDineroFormat), - Frame: Dinero(job.job_totals.rates.laf.total).toFormat(CCDineroFormat), - Mech: Dinero(job.job_totals.rates.lam.total).toFormat(CCDineroFormat), - Glass: Dinero(job.job_totals.rates.lag.total).toFormat(CCDineroFormat), - Elec: Dinero(job.job_totals.rates.lae.total).toFormat(CCDineroFormat), - Detail: detailAdjustments.amount.toFormat(CCDineroFormat), - Reassem: Dinero().toFormat(CCDineroFormat), - OtherLabor: Dinero(job.job_totals.rates.la1.total) - .add(Dinero(job.job_totals.rates.la2.total)) - .add(Dinero(job.job_totals.rates.la3.total)) - .add(Dinero(job.job_totals.rates.la4.total)) - .add(Dinero(job.job_totals.rates.lau.total)) - .subtract(detailAdjustments.amount) - .toFormat(CCDineroFormat), - BMatl: Dinero(job.job_totals.rates.mash.total).toFormat(CCDineroFormat), - PMatl: Dinero(job.job_totals.rates.mapa.total).toFormat(CCDineroFormat), - OEM: Dinero( - job.job_totals.parts.parts.list.PAN && - job.job_totals.parts.parts.list.PAN.total - ) - .add( - Dinero( - job.job_totals.parts.parts.list.PAP && - job.job_totals.parts.parts.list.PAP.total - ) - ) - .toFormat(CCDineroFormat), - LKQ: Dinero( - job.job_totals.parts.parts.list.PAL && - job.job_totals.parts.parts.list.PAL.total - ).toFormat(CCDineroFormat), - AM: Dinero( - job.job_totals.parts.parts.list.PAA && - job.job_totals.parts.parts.list.PAA.total - ).toFormat(CCDineroFormat), - MechParts: Dinero().toFormat(CCDineroFormat), - OtherParts: Dinero( - job.job_totals.parts.parts.list.PAO && - job.job_totals.parts.parts.list.PAO.total - ).toFormat(CCDineroFormat), - OtherSales: Dinero(job.job_totals.additional.storage).toFormat( - CCDineroFormat - ), - Sublet: Dinero(job.job_totals.parts.sublets.total).toFormat( - CCDineroFormat - ), - Towing: Dinero(job.job_totals.additional.towing).toFormat( - CCDineroFormat - ), - Storage: "0.00", - Rental: - job.job_totals.additional.additionalCostItems.includes( - "ATS Amount" - ) === true - ? Dinero( - job.job_totals.additional.additionalCostItems[ - job.job_totals.additional.additionalCostItems.indexOf( - "ATS Amount" - ) - ].total - ).toFormat(CCDineroFormat) - : Dinero().toFormat(CCDineroFormat), - HazWaste: Dinero().toFormat(CCDineroFormat), - Discounts: Dinero(job.job_totals.additional.adjustments).toFormat( - CCDineroFormat - ), - Tax: Dinero(job.job_totals.totals.local_tax) - .add(Dinero(job.job_totals.totals.state_tax)) - .add(Dinero(job.job_totals.totals.federal_tax)) - .add(Dinero(job.job_totals.additional.pvrt)) - .toFormat(CCDineroFormat), - NetSaleTotal: Dinero(job.job_totals.totals.subtotal).toFormat( - CCDineroFormat - ), - SaleTotal: Dinero(job.job_totals.totals.total_repairs).toFormat( - CCDineroFormat - ), - }, - SaleHours: { - Body: job.job_totals.rates.lab.hours.toFixed(2), - BodyRepairHours: job.joblines - .filter((line) => repairOpCodes.includes(line.lbr_op)) - .reduce((acc, val) => acc + val.mod_lb_hrs, 0) - .toFixed(2), - BodyReplacehours: job.joblines - .filter((line) => replaceOpCodes.includes(line.lbr_op)) - .reduce((acc, val) => acc + val.mod_lb_hrs, 0) - .toFixed(2), - Paint: job.job_totals.rates.lar.hours.toFixed(2), - Prep: "0.00", - Frame: job.job_totals.rates.laf.hours.toFixed(2), - Mech: job.job_totals.rates.lam.hours.toFixed(2), - Glass: job.job_totals.rates.lag.hours.toFixed(2), - Elec: job.job_totals.rates.lae.hours.toFixed(2), - Detail: detailAdjustments.hours, - Reassem: "0.00", - Other: ( - job.job_totals.rates.la1.hours + - job.job_totals.rates.la2.hours + - job.job_totals.rates.la3.hours + - job.job_totals.rates.la4.hours + - job.job_totals.rates.lau.hours - - detailAdjustments.hours - ).toFixed(2), - TotalHours: job.joblines - .reduce((acc, val) => acc + val.mod_lb_hrs, 0) - .toFixed(2), - }, - Costs: { - Body: repairCosts.BodyLaborTotalCost.toFormat(CCDineroFormat), - Paint: repairCosts.RefinishLaborTotalCost.toFormat(CCDineroFormat), - Prep: Dinero().toFormat(CCDineroFormat), - Frame: repairCosts.FrameLaborTotalCost.toFormat(CCDineroFormat), - Mech: repairCosts.MechanicalLaborTotalCost.toFormat(CCDineroFormat), - Glass: repairCosts.GlassLaborTotalCost.toFormat(CCDineroFormat), - Elec: repairCosts.ElectricalLaborTotalCost.toFormat(CCDineroFormat), - Detail: Dinero().toFormat(CCDineroFormat), - Reassem: Dinero().toFormat(CCDineroFormat), - OtherLabor: repairCosts.LaborMiscTotalCost.toFormat(CCDineroFormat), - Bmatl: repairCosts.BMTotalCost.toFormat(CCDineroFormat), - Pmatl: repairCosts.PMTotalCost.toFormat(CCDineroFormat), - OEM: repairCosts.PartsOemCost.toFormat(CCDineroFormat), - LKQ: repairCosts.PartsRecycledCost.toFormat(CCDineroFormat), - AM: repairCosts.PartsAMCost.toFormat(CCDineroFormat), - MechParts: Dinero().toFormat(CCDineroFormat), - OtherParts: Dinero().toFormat(CCDineroFormat), //Check Synergy - OtherCost: repairCosts.PartsOtherCost.toFormat(CCDineroFormat), - Sublet: repairCosts.SubletTotalCost.toFormat(CCDineroFormat), - Towing: repairCosts.TowingTotalCost.toFormat(CCDineroFormat), - Storage: repairCosts.StorageTotalCost.toFormat(CCDineroFormat), - Rental: Dinero().toFormat(CCDineroFormat), - HazWaste: Dinero().toFormat(CCDineroFormat), - CostTotal: repairCosts.TotalCost.toFormat(CCDineroFormat), - }, - CostHours: { - Body: repairCosts.BodyLaborTotalHrs.toFixed(2), - Paint: repairCosts.RefinishLaborTotalHrs.toFixed(2), - Prep: "0.00", - Frame: repairCosts.FrameLaborTotalHrs.toFixed(2), - Mech: repairCosts.MechanicalLaborTotalHrs.toFixed(2), - Glass: repairCosts.GlassLaborTotalHrs.toFixed(2), - Elec: repairCosts.ElectricalLaborTotalHrs.toFixed(2), - Detail: "0.00", - Other: repairCosts.LaborMiscTotalHrs.toFixed(2), - CostTotalHours: repairCosts.TotalHrs.toFixed(2), - }, + //Calculate detail only lines. + const detailAdjustments = job.joblines + .filter((jl) => jl.ah_detail_line && jl.mod_lbr_ty) + .reduce( + (acc, val) => { + return { + hours: acc.hours + val.mod_lb_hrs, + amount: acc.amount.add( + Dinero({ + amount: Math.round((job.job_totals.rates[val.mod_lbr_ty.toLowerCase()].rate || 0) * val.mod_lb_hrs * 100) + }) + ) }; - return ret; - } catch (error) { - logger.log("claimscorp-job-calculate-error", "ERROR", "api", null, { - error, - }); + }, + { hours: 0, amount: Dinero() } + ); - errorCallback({jobid: job.id, ro_number: job.ro_number, error}); - } + try { + const ret = { + RoNumber: job.ro_number, + Customer: { + CustomerZip: (job.ownr_zip && job.ownr_zip.substring(0, 3)) || "", + CustomerState: job.ownr_st || "" + }, + Vehicle: { + Year: job.v_model_yr + ? parseInt(job.v_model_yr.match(/\d/g)) + ? parseInt(job.v_model_yr.match(/\d/g).join(""), 10) + : "" + : "", + Make: job.v_make_desc || "", + Model: job.v_model_desc || "", + BodyStyle: (job.vehicle && job.vehicle.v_bstyle) || "", + Color: job.v_color || "", + VIN: job.v_vin || "" + }, + Carrier: { + UniqueID: job.ins_co_nm || "", + InsuranceCompany: job.ins_co_nm || "" + }, + Claim: job.clm_no || "", + Contacts: { + PC: job.employee_csr_rel + ? `${ + job.employee_csr_rel.last_name ? job.employee_csr_rel.last_name : "" + }${job.employee_csr_rel.last_name ? ", " : ""}${ + job.employee_csr_rel.first_name ? job.employee_csr_rel.first_name : "" + }` + : "", + Phone1: "", + Phone2: "", + Estimator: `${job.est_ct_ln ? job.est_ct_ln : ""}${ + job.est_ct_ln ? ", " : "" + }${job.est_ct_fn ? job.est_ct_fn : ""}`, + BodyTechnician: job.employee_body_rel + ? `${ + job.employee_body_rel.last_name ? job.employee_body_rel.last_name : "" + }${job.employee_body_rel.last_name ? ", " : ""}${ + job.employee_body_rel.first_name ? job.employee_body_rel.first_name : "" + }` + : "", + PaintTechnician: job.employee_refinish_rel + ? `${ + job.employee_refinish_rel.last_name ? job.employee_refinish_rel.last_name : "" + }${job.employee_refinish_rel.last_name ? ", " : ""}${ + job.employee_refinish_rel.first_name ? job.employee_refinish_rel.first_name : "" + }` + : "" + }, + Dates: { + DateCreated: (job.date_estimated && moment(job.date_estimated).format(AhDateFormat)) || "", + DateLoss: (job.loss_date && moment(job.loss_date).format(AhDateFormat)) || "", + DateFNOL: "", + DateContact: "", + DateEstimated: (job.date_estimated && moment(job.date_estimated).format(AhDateFormat)) || "", + DateScheduled: + (job.scheduled_in && moment(job.scheduled_in).tz(job.bodyshop.timezone).format(AhDateFormat)) || "", + DateArrived: (job.actual_in && moment(job.actual_in).tz(job.bodyshop.timezone).format(AhDateFormat)) || "", + DateFirstPartsOrdered: + (job.parts_orders && + job.parts_orders[0] && + moment(job.parts_orders[0].created_at).tz(job.bodyshop.timezone).format(AhDateFormat)) || + "", + DateStart: job.date_repairstarted + ? (job.date_repairstarted && moment(job.date_repairstarted).tz(job.bodyshop.timezone).format(AhDateFormat)) || + "" + : (job.date_repairstarted && moment(job.actual_in).tz(job.bodyshop.timezone).format(AhDateFormat)) || "", + BodyStart: "", + BodyEnd: "", + FrameStart: "", + FrameEnd: "", + PrepStart: "", + PrepEnd: "", + SprayStart: "", + SprayEnd: "", + DateReady: + (job.actual_completion && moment(job.actual_completion).tz(job.bodyshop.timezone).format(AhDateFormat)) || "", + DateScheduledDelivery: + (job.scheduled_delivery && moment(job.scheduled_delivery).tz(job.bodyshop.timezone).format(AhDateFormat)) || + "", + DateDelivered: + (job.actual_delivery && moment(job.actual_delivery).tz(job.bodyshop.timezone).format(AhDateFormat)) || "", + DateClosed: + (job.date_invoiced && moment(job.date_invoiced).tz(job.bodyshop.timezone).format(AhDateFormat)) || "", + BilledDate: "", + PaidInFullDate: "", + ROStatus: job.tlos_ind ? "TOT" : StatusMapping(job.status, job.bodyshop.md_ro_statuses) + }, + Sales: { + Body: Dinero(job.job_totals.rates.lab.total) + .add(Dinero(job.job_totals.rates.laa.total)) + .add(Dinero(job.job_totals.rates.lad.total)) + .add(Dinero(job.job_totals.rates.las.total)) + .toFormat(CCDineroFormat), + Paint: Dinero(job.job_totals.rates.lar.total).toFormat(CCDineroFormat), + Prep: Dinero().toFormat(CCDineroFormat), + Frame: Dinero(job.job_totals.rates.laf.total).toFormat(CCDineroFormat), + Mech: Dinero(job.job_totals.rates.lam.total).toFormat(CCDineroFormat), + Glass: Dinero(job.job_totals.rates.lag.total).toFormat(CCDineroFormat), + Elec: Dinero(job.job_totals.rates.lae.total).toFormat(CCDineroFormat), + Detail: detailAdjustments.amount.toFormat(CCDineroFormat), + Reassem: Dinero().toFormat(CCDineroFormat), + OtherLabor: Dinero(job.job_totals.rates.la1.total) + .add(Dinero(job.job_totals.rates.la2.total)) + .add(Dinero(job.job_totals.rates.la3.total)) + .add(Dinero(job.job_totals.rates.la4.total)) + .add(Dinero(job.job_totals.rates.lau.total)) + .subtract(detailAdjustments.amount) + .toFormat(CCDineroFormat), + BMatl: Dinero(job.job_totals.rates.mash.total).toFormat(CCDineroFormat), + PMatl: Dinero(job.job_totals.rates.mapa.total).toFormat(CCDineroFormat), + OEM: Dinero(job.job_totals.parts.parts.list.PAN && job.job_totals.parts.parts.list.PAN.total) + .add(Dinero(job.job_totals.parts.parts.list.PAP && job.job_totals.parts.parts.list.PAP.total)) + .toFormat(CCDineroFormat), + LKQ: Dinero(job.job_totals.parts.parts.list.PAL && job.job_totals.parts.parts.list.PAL.total).toFormat( + CCDineroFormat + ), + AM: Dinero(job.job_totals.parts.parts.list.PAA && job.job_totals.parts.parts.list.PAA.total).toFormat( + CCDineroFormat + ), + MechParts: Dinero().toFormat(CCDineroFormat), + OtherParts: Dinero(job.job_totals.parts.parts.list.PAO && job.job_totals.parts.parts.list.PAO.total).toFormat( + CCDineroFormat + ), + OtherSales: Dinero(job.job_totals.additional.storage).toFormat(CCDineroFormat), + Sublet: Dinero(job.job_totals.parts.sublets.total).toFormat(CCDineroFormat), + Towing: Dinero(job.job_totals.additional.towing).toFormat(CCDineroFormat), + Storage: "0.00", + Rental: + job.job_totals.additional.additionalCostItems.includes("ATS Amount") === true + ? Dinero( + job.job_totals.additional.additionalCostItems[ + job.job_totals.additional.additionalCostItems.indexOf("ATS Amount") + ].total + ).toFormat(CCDineroFormat) + : Dinero().toFormat(CCDineroFormat), + HazWaste: Dinero().toFormat(CCDineroFormat), + Discounts: Dinero(job.job_totals.additional.adjustments).toFormat(CCDineroFormat), + Tax: Dinero(job.job_totals.totals.local_tax) + .add(Dinero(job.job_totals.totals.state_tax)) + .add(Dinero(job.job_totals.totals.federal_tax)) + .add(Dinero(job.job_totals.additional.pvrt)) + .toFormat(CCDineroFormat), + NetSaleTotal: Dinero(job.job_totals.totals.subtotal).toFormat(CCDineroFormat), + SaleTotal: Dinero(job.job_totals.totals.total_repairs).toFormat(CCDineroFormat) + }, + SaleHours: { + Body: job.job_totals.rates.lab.hours.toFixed(2), + BodyRepairHours: job.joblines + .filter((line) => repairOpCodes.includes(line.lbr_op)) + .reduce((acc, val) => acc + val.mod_lb_hrs, 0) + .toFixed(2), + BodyReplacehours: job.joblines + .filter((line) => replaceOpCodes.includes(line.lbr_op)) + .reduce((acc, val) => acc + val.mod_lb_hrs, 0) + .toFixed(2), + Paint: job.job_totals.rates.lar.hours.toFixed(2), + Prep: "0.00", + Frame: job.job_totals.rates.laf.hours.toFixed(2), + Mech: job.job_totals.rates.lam.hours.toFixed(2), + Glass: job.job_totals.rates.lag.hours.toFixed(2), + Elec: job.job_totals.rates.lae.hours.toFixed(2), + Detail: detailAdjustments.hours, + Reassem: "0.00", + Other: ( + job.job_totals.rates.la1.hours + + job.job_totals.rates.la2.hours + + job.job_totals.rates.la3.hours + + job.job_totals.rates.la4.hours + + job.job_totals.rates.lau.hours - + detailAdjustments.hours + ).toFixed(2), + TotalHours: job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0).toFixed(2) + }, + Costs: { + Body: repairCosts.BodyLaborTotalCost.toFormat(CCDineroFormat), + Paint: repairCosts.RefinishLaborTotalCost.toFormat(CCDineroFormat), + Prep: Dinero().toFormat(CCDineroFormat), + Frame: repairCosts.FrameLaborTotalCost.toFormat(CCDineroFormat), + Mech: repairCosts.MechanicalLaborTotalCost.toFormat(CCDineroFormat), + Glass: repairCosts.GlassLaborTotalCost.toFormat(CCDineroFormat), + Elec: repairCosts.ElectricalLaborTotalCost.toFormat(CCDineroFormat), + Detail: Dinero().toFormat(CCDineroFormat), + Reassem: Dinero().toFormat(CCDineroFormat), + OtherLabor: repairCosts.LaborMiscTotalCost.toFormat(CCDineroFormat), + Bmatl: repairCosts.BMTotalCost.toFormat(CCDineroFormat), + Pmatl: repairCosts.PMTotalCost.toFormat(CCDineroFormat), + OEM: repairCosts.PartsOemCost.toFormat(CCDineroFormat), + LKQ: repairCosts.PartsRecycledCost.toFormat(CCDineroFormat), + AM: repairCosts.PartsAMCost.toFormat(CCDineroFormat), + MechParts: Dinero().toFormat(CCDineroFormat), + OtherParts: Dinero().toFormat(CCDineroFormat), //Check Synergy + OtherCost: repairCosts.PartsOtherCost.toFormat(CCDineroFormat), + Sublet: repairCosts.SubletTotalCost.toFormat(CCDineroFormat), + Towing: repairCosts.TowingTotalCost.toFormat(CCDineroFormat), + Storage: repairCosts.StorageTotalCost.toFormat(CCDineroFormat), + Rental: Dinero().toFormat(CCDineroFormat), + HazWaste: Dinero().toFormat(CCDineroFormat), + CostTotal: repairCosts.TotalCost.toFormat(CCDineroFormat) + }, + CostHours: { + Body: repairCosts.BodyLaborTotalHrs.toFixed(2), + Paint: repairCosts.RefinishLaborTotalHrs.toFixed(2), + Prep: "0.00", + Frame: repairCosts.FrameLaborTotalHrs.toFixed(2), + Mech: repairCosts.MechanicalLaborTotalHrs.toFixed(2), + Glass: repairCosts.GlassLaborTotalHrs.toFixed(2), + Elec: repairCosts.ElectricalLaborTotalHrs.toFixed(2), + Detail: "0.00", + Other: repairCosts.LaborMiscTotalHrs.toFixed(2), + CostTotalHours: repairCosts.TotalHrs.toFixed(2) + } + }; + return ret; + } catch (error) { + logger.log("claimscorp-job-calculate-error", "ERROR", "api", null, { + error + }); + + errorCallback({ jobid: job.id, ro_number: job.ro_number, error }); + } }; const CreateCosts = (job) => { - //Create a mapping based on AH Requirements + //Create a mapping based on AH Requirements - //For DMS, the keys in the object below are the CIECA part types. - const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => { - //At the bill level. - bill_val.billlines.map((line_val) => { - //At the bill line level. + //For DMS, the keys in the object below are the CIECA part types. + const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => { + //At the bill level. + bill_val.billlines.map((line_val) => { + //At the bill line level. - if (!bill_acc[line_val.cost_center]) - bill_acc[line_val.cost_center] = Dinero(); + if (!bill_acc[line_val.cost_center]) bill_acc[line_val.cost_center] = Dinero(); - bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add( - Dinero({ - amount: Math.round((line_val.actual_cost || 0) * 100), - }) - .multiply(line_val.quantity) - .multiply(bill_val.is_credit_memo ? -1 : 1) - ); + bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add( + Dinero({ + amount: Math.round((line_val.actual_cost || 0) * 100) + }) + .multiply(line_val.quantity) + .multiply(bill_val.is_credit_memo ? -1 : 1) + ); - return null; + return null; + }); + 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 || + typeof job.bodyshop.jc_hourly_rates.mapa === "number" || + isNaN(job.bodyshop.jc_hourly_rates.mapa) === false) + ) { + if (!billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA]) + billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = Dinero(); + if (job.bodyshop.use_paint_scale_data === true) { + if (job.mixdata.length > 0) { + billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = Dinero({ + amount: Math.round(((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100) }); - 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 || - typeof job.bodyshop.jc_hourly_rates.mapa === "number" || - isNaN(job.bodyshop.jc_hourly_rates.mapa) === false) - ) { - if ( - !billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] - ) - billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = Dinero(); - if (job.bodyshop.use_paint_scale_data === true) { - if (job.mixdata.length > 0) { - billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = Dinero({ - amount: Math.round( - ((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100 - ), - }); - } else { - billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ].add( - Dinero({ - amount: Math.round( - (job.bodyshop.jc_hourly_rates && - job.bodyshop.jc_hourly_rates.mapa * 100) || - 0 - ), - }).multiply(job.job_totals.rates.mapa.hours) - ); - } - } else { - billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ].add( - Dinero({ - amount: Math.round( - (job.bodyshop.jc_hourly_rates && - job.bodyshop.jc_hourly_rates.mapa * 100) || - 0 - ), - }).multiply(job.job_totals.rates.mapa.hours) - ); - } - } - 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: Math.round( - (job.bodyshop.jc_hourly_rates && - job.bodyshop.jc_hourly_rates.mash * 100) || - 0 - ), - }).multiply(job.job_totals.rates.mash.hours) + } else { + billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ].add( + Dinero({ + amount: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa * 100) || 0) + }).multiply(job.job_totals.rates.mapa.hours) ); + } + } else { + billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ].add( + Dinero({ + amount: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa * 100) || 0) + }).multiply(job.job_totals.rates.mapa.hours) + ); } - //Uses CIECA Labor types. - const ticketTotalsByCostCenter = job.timetickets.reduce( - (ticket_acc, ticket_val) => { - //At the invoice level. - if (!ticket_acc[ticket_val.cost_center]) - ticket_acc[ticket_val.cost_center] = Dinero(); - - ticket_acc[ticket_val.cost_center] = ticket_acc[ - ticket_val.cost_center - ].add( - Dinero({ - amount: Math.round((ticket_val.rate || 0) * 100), - }).multiply( - (ticket_val.flat_rate - ? ticket_val.productivehrs - : ticket_val.actualhrs) || 0 - ) - ); - - return ticket_acc; - }, - {} + } + 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: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash * 100) || 0) + }).multiply(job.job_totals.rates.mash.hours) ); - const ticketHrsByCostCenter = job.timetickets.reduce( - (ticket_acc, ticket_val) => { - //At the invoice level. - if (!ticket_acc[ticket_val.cost_center]) - ticket_acc[ticket_val.cost_center] = 0; + } + //Uses CIECA Labor types. + const ticketTotalsByCostCenter = job.timetickets.reduce((ticket_acc, ticket_val) => { + //At the invoice level. + if (!ticket_acc[ticket_val.cost_center]) ticket_acc[ticket_val.cost_center] = Dinero(); - ticket_acc[ticket_val.cost_center] = - ticket_acc[ticket_val.cost_center] + - (ticket_val.flat_rate - ? ticket_val.productivehrs - : ticket_val.actualhrs) || 0; - - return ticket_acc; - }, - {} + ticket_acc[ticket_val.cost_center] = ticket_acc[ticket_val.cost_center].add( + Dinero({ + amount: Math.round((ticket_val.rate || 0) * 100) + }).multiply((ticket_val.flat_rate ? ticket_val.productivehrs : ticket_val.actualhrs) || 0) ); - //CIECA STANDARD MAPPING OBJECT. - const ciecaObj = { - ATS: "ATS", - LA1: "LA1", - LA2: "LA2", - LA3: "LA3", - LA4: "LA4", - LAA: "LAA", - LAB: "LAB", - LAD: "LAD", - LAE: "LAE", - LAF: "LAF", - LAG: "LAG", - LAM: "LAM", - LAR: "LAR", - LAS: "LAS", - LAU: "LAU", - PAA: "PAA", - PAC: "PAC", - PAG: "PAG", - PAL: "PAL", - PAM: "PAM", - PAN: "PAN", - PAO: "PAO", - PAP: "PAP", - PAR: "PAR", - PAS: "PAS", - TOW: "TOW", - MAPA: "MAPA", - MASH: "MASH", - PASL: "PASL", - }; - const defaultCosts = - job.bodyshop.cdk_dealerid || job.bodyshop.pbs_serialnumber - ? ciecaObj - : job.bodyshop.md_responsibility_centers.defaults.costs; + return ticket_acc; + }, {}); + const ticketHrsByCostCenter = job.timetickets.reduce((ticket_acc, ticket_val) => { + //At the invoice level. + if (!ticket_acc[ticket_val.cost_center]) ticket_acc[ticket_val.cost_center] = 0; - return { - PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => { - if ( - key !== defaultCosts.PAS && - key !== defaultCosts.PASL && - key !== defaultCosts.MAPA && - key !== defaultCosts.MASH && - key !== defaultCosts.TOW - ) - return acc.add(billTotalsByCostCenters[key]); - return acc; - }, Dinero()), - PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add( - billTotalsByCostCenters[defaultCosts.PAP] || Dinero() - ), - PartsAMCost: billTotalsByCostCenters[defaultCosts.PAA] || Dinero(), - PartsReconditionedCost: - billTotalsByCostCenters[defaultCosts.PAM] || Dinero(), - PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAL] || Dinero(), - PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), - SubletTotalCost: - billTotalsByCostCenters[defaultCosts.PAS] || - Dinero(billTotalsByCostCenters[defaultCosts.PASL] || Dinero()), - BodyLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(), - BodyLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAB] || 0, - RefinishLaborTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(), - RefinishLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAR] || 0, - MechanicalLaborTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(), - MechanicalLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAM] || 0, - StructuralLaborTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(), - StructuralLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAS] || 0, - ElectricalLaborTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(), - ElectricalLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAE] || 0, - FrameLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(), - FrameLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAF] || 0, - GlassLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(), - GlassLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAG] || 0, - DetailLaborTotalCost: Dinero(), - // ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(), - LaborMiscTotalCost: (ticketTotalsByCostCenter[defaultCosts.LA1] || Dinero()) - .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero()) - .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero()) - .add(ticketTotalsByCostCenter[defaultCosts.LA3] || Dinero()) - .add(ticketTotalsByCostCenter[defaultCosts.LA4] || Dinero()) - .add(ticketTotalsByCostCenter[defaultCosts.LAU] || Dinero()), - LaborMiscTotalHrs: - (ticketHrsByCostCenter[defaultCosts.LA1] || 0) + - (ticketHrsByCostCenter[defaultCosts.LA2] || 0) + - (ticketHrsByCostCenter[defaultCosts.LA3] || 0) + - (ticketHrsByCostCenter[defaultCosts.LA4] || 0) + - (ticketHrsByCostCenter[defaultCosts.LAU] || 0), - PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(), - BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(), - MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), - TowingTotalCost: billTotalsByCostCenters[defaultCosts.TOW] || Dinero(), - StorageTotalCost: Dinero(), - DetailTotal: Dinero(), - DetailTotalCost: Dinero(), - SalesTaxTotalCost: Dinero(), - LabourTotalCost: Object.keys(ticketTotalsByCostCenter).reduce( - (acc, key) => { - return acc.add(ticketTotalsByCostCenter[key]); - }, - Dinero() - ), - TotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => { - return acc.add(billTotalsByCostCenters[key]); - }, Dinero()), - TotalHrs: job.timetickets.reduce((acc, ticket_val) => { - return ( - acc + - (ticket_val.flat_rate - ? ticket_val.productivehrs - : ticket_val.actualhrs) || 0 - ); - }, 0), - }; + ticket_acc[ticket_val.cost_center] = + ticket_acc[ticket_val.cost_center] + (ticket_val.flat_rate ? ticket_val.productivehrs : ticket_val.actualhrs) || + 0; + + return ticket_acc; + }, {}); + //CIECA STANDARD MAPPING OBJECT. + + const ciecaObj = { + ATS: "ATS", + LA1: "LA1", + LA2: "LA2", + LA3: "LA3", + LA4: "LA4", + LAA: "LAA", + LAB: "LAB", + LAD: "LAD", + LAE: "LAE", + LAF: "LAF", + LAG: "LAG", + LAM: "LAM", + LAR: "LAR", + LAS: "LAS", + LAU: "LAU", + PAA: "PAA", + PAC: "PAC", + PAG: "PAG", + PAL: "PAL", + PAM: "PAM", + PAN: "PAN", + PAO: "PAO", + PAP: "PAP", + PAR: "PAR", + PAS: "PAS", + TOW: "TOW", + MAPA: "MAPA", + MASH: "MASH", + PASL: "PASL" + }; + const defaultCosts = + job.bodyshop.cdk_dealerid || job.bodyshop.pbs_serialnumber + ? ciecaObj + : job.bodyshop.md_responsibility_centers.defaults.costs; + + return { + PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => { + if ( + key !== defaultCosts.PAS && + key !== defaultCosts.PASL && + key !== defaultCosts.MAPA && + key !== defaultCosts.MASH && + key !== defaultCosts.TOW + ) + return acc.add(billTotalsByCostCenters[key]); + return acc; + }, Dinero()), + PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add( + billTotalsByCostCenters[defaultCosts.PAP] || Dinero() + ), + PartsAMCost: billTotalsByCostCenters[defaultCosts.PAA] || Dinero(), + PartsReconditionedCost: billTotalsByCostCenters[defaultCosts.PAM] || Dinero(), + PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAL] || Dinero(), + PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), + SubletTotalCost: + billTotalsByCostCenters[defaultCosts.PAS] || Dinero(billTotalsByCostCenters[defaultCosts.PASL] || Dinero()), + BodyLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(), + BodyLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAB] || 0, + RefinishLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(), + RefinishLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAR] || 0, + MechanicalLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(), + MechanicalLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAM] || 0, + StructuralLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(), + StructuralLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAS] || 0, + ElectricalLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(), + ElectricalLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAE] || 0, + FrameLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(), + FrameLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAF] || 0, + GlassLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(), + GlassLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAG] || 0, + DetailLaborTotalCost: Dinero(), + // ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(), + LaborMiscTotalCost: (ticketTotalsByCostCenter[defaultCosts.LA1] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA3] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA4] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LAU] || Dinero()), + LaborMiscTotalHrs: + (ticketHrsByCostCenter[defaultCosts.LA1] || 0) + + (ticketHrsByCostCenter[defaultCosts.LA2] || 0) + + (ticketHrsByCostCenter[defaultCosts.LA3] || 0) + + (ticketHrsByCostCenter[defaultCosts.LA4] || 0) + + (ticketHrsByCostCenter[defaultCosts.LAU] || 0), + PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(), + BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(), + MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), + TowingTotalCost: billTotalsByCostCenters[defaultCosts.TOW] || Dinero(), + StorageTotalCost: Dinero(), + DetailTotal: Dinero(), + DetailTotalCost: Dinero(), + SalesTaxTotalCost: Dinero(), + LabourTotalCost: Object.keys(ticketTotalsByCostCenter).reduce((acc, key) => { + return acc.add(ticketTotalsByCostCenter[key]); + }, Dinero()), + TotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => { + return acc.add(billTotalsByCostCenters[key]); + }, Dinero()), + TotalHrs: job.timetickets.reduce((acc, ticket_val) => { + return acc + (ticket_val.flat_rate ? ticket_val.productivehrs : ticket_val.actualhrs) || 0; + }, 0) + }; }; const StatusMapping = (status, md_ro_statuses) => { - //Possible return statuses CLO, CAN, OPN - const { - default_imported, - default_open, - default_scheduled, - default_arrived, - default_completed, - default_delivered, - default_invoiced, - default_exported, - default_void, - } = md_ro_statuses; + //Possible return statuses CLO, CAN, OPN + const { + default_imported, + default_open, + default_scheduled, + default_arrived, + default_completed, + default_delivered, + default_invoiced, + default_exported, + default_void + } = md_ro_statuses; - if ( - status === default_open || - status === default_imported || - status === default_scheduled || - status === default_arrived || - status === default_completed || - status === default_delivered || - md_ro_statuses.production_statuses.includes(status) - ) - return "OPN"; - else if (status === default_invoiced || status === default_exported) - return "CLO"; - else if (status === default_void) return "CAN"; - else return "UNDEFINED"; + if ( + status === default_open || + status === default_imported || + status === default_scheduled || + status === default_arrived || + status === default_completed || + status === default_delivered || + md_ro_statuses.production_statuses.includes(status) + ) + return "OPN"; + else if (status === default_invoiced || status === default_exported) return "CLO"; + else if (status === default_void) return "CAN"; + else return "UNDEFINED"; }; diff --git a/server/data/data.js b/server/data/data.js index 18ec4c321..1656c1878 100644 --- a/server/data/data.js +++ b/server/data/data.js @@ -1,4 +1,4 @@ exports.arms = require("./arms").default; exports.autohouse = require("./autohouse").default; exports.claimscorp = require("./claimscorp").default; -exports.kaizen = require("./kaizen").default; \ No newline at end of file +exports.kaizen = require("./kaizen").default; diff --git a/server/data/kaizen.js b/server/data/kaizen.js index d6dc1c7eb..6e2e26edb 100644 --- a/server/data/kaizen.js +++ b/server/data/kaizen.js @@ -7,15 +7,12 @@ const _ = require("lodash"); const logger = require("../utils/logger"); const fs = require("fs"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); let Client = require("ssh2-sftp-client"); const client = require("../graphql-client/graphql-client").client; -const {sendServerEmail} = require("../email/sendemail"); +const { sendServerEmail } = require("../email/sendemail"); const DineroFormat = "0,0.00"; const DateFormat = "MM/DD/YYYY"; @@ -23,180 +20,158 @@ const repairOpCodes = ["OP4", "OP9", "OP10"]; const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"]; const ftpSetup = { - host: process.env.KAIZEN_HOST, - 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), - algorithms: { - serverHostKey: [ - "ssh-rsa", - "ssh-dss", - "rsa-sha2-256", - "rsa-sha2-512", - "ecdsa-sha2-nistp256", - "ecdsa-sha2-nistp384", - ], - }, + host: process.env.KAIZEN_HOST, + 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), + algorithms: { + serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"] + } }; exports.default = async (req, res) => { - //Query for the List of Bodyshop Clients. - logger.log("kaizen-start", "DEBUG", "api", null, null); - const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE"]; + //Query for the List of Bodyshop Clients. + logger.log("kaizen-start", "DEBUG", "api", null, null); + const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE"]; - const {bodyshops} = await client.request(queries.GET_KAIZEN_SHOPS, { - imexshopid: kaizenShopsIDs, - }); + const { bodyshops } = await client.request(queries.GET_KAIZEN_SHOPS, { + imexshopid: kaizenShopsIDs + }); - const specificShopIds = req.body.bodyshopIds; // ['uuid] - const {start, end, skipUpload} = req.body; //YYYY-MM-DD - if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) { - res.sendStatus(401); - return; + const specificShopIds = req.body.bodyshopIds; // ['uuid] + const { start, end, skipUpload } = req.body; //YYYY-MM-DD + if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) { + res.sendStatus(401); + return; + } + const allxmlsToUpload = []; + const allErrors = []; + try { + for (const bodyshop of specificShopIds ? bodyshops.filter((b) => specificShopIds.includes(b.id)) : bodyshops) { + logger.log("kaizen-start-shop-extract", "DEBUG", "api", bodyshop.id, { + shopname: bodyshop.shopname + }); + const erroredJobs = []; + try { + const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, { + bodyshopid: bodyshop.id, + start: start ? moment(start).startOf("hours") : moment().subtract(2, "hours").startOf("hour"), + ...(end && { end: moment(end).endOf("hours") }) + }); + + const kaizenObject = { + DataFeed: { + ShopInfo: { + ShopName: bodyshops_by_pk.shopname, + Jobs: jobs.map((j) => + CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) { + erroredJobs.push({ job: job, error: error.toString() }); + }) + ) + } + } + }; + + if (erroredJobs.length > 0) { + logger.log("kaizen-failed-jobs", "ERROR", "api", bodyshop.id, { + count: erroredJobs.length, + jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)) + }); + } + + var ret = builder + .create( + { + // version: "1.0", + // encoding: "UTF-8", + //keepNullNodes: true, + }, + kaizenObject + ) + .end({ allowEmptyTags: true }); + + allxmlsToUpload.push({ + count: kaizenObject.DataFeed.ShopInfo.Jobs.length, + xml: ret, + filename: `${bodyshop.shopname}-${moment().format("YYYYMMDDTHHMMss")}.xml` + }); + + logger.log("kaizen-end-shop-extract", "DEBUG", "api", bodyshop.id, { + shopname: bodyshop.shopname + }); + } catch (error) { + //Error at the shop level. + logger.log("kaizen-error-shop", "ERROR", "api", bodyshop.id, { + ...error + }); + + allErrors.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + shopname: bodyshop.shopname, + fatal: true, + errors: [error.toString()] + }); + } finally { + allErrors.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + shopname: bodyshop.shopname, + errors: erroredJobs.map((ej) => ({ + ro_number: ej.job?.ro_number, + jobid: ej.job?.id, + error: ej.error + })) + }); + } } - const allxmlsToUpload = []; - const allErrors = []; - try { - for (const bodyshop of specificShopIds - ? bodyshops.filter((b) => specificShopIds.includes(b.id)) - : bodyshops) { - logger.log("kaizen-start-shop-extract", "DEBUG", "api", bodyshop.id, { - shopname: bodyshop.shopname, - }); - const erroredJobs = []; - try { - const {jobs, bodyshops_by_pk} = await client.request( - queries.KAIZEN_QUERY, - { - bodyshopid: bodyshop.id, - start: start - ? moment(start).startOf("hours") - : moment().subtract(2, "hours").startOf("hour"), - ...(end && {end: moment(end).endOf("hours")}), - } - ); - const kaizenObject = { - DataFeed: { - ShopInfo: { - ShopName: bodyshops_by_pk.shopname, - Jobs: jobs.map((j) => - CreateRepairOrderTag( - {...j, bodyshop: bodyshops_by_pk}, - function ({job, error}) { - erroredJobs.push({job: job, error: error.toString()}); - } - ) - ), - }, - }, - }; + if (skipUpload) { + for (const xmlObj of allxmlsToUpload) { + fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); + } - if (erroredJobs.length > 0) { - logger.log("kaizen-failed-jobs", "ERROR", "api", bodyshop.id, { - count: erroredJobs.length, - jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)), - }); - } - - var ret = builder - .create( - { - // version: "1.0", - // encoding: "UTF-8", - //keepNullNodes: true, - }, - kaizenObject - ) - .end({allowEmptyTags: true}); - - allxmlsToUpload.push({ - count: kaizenObject.DataFeed.ShopInfo.Jobs.length, - xml: ret, - filename: `${bodyshop.shopname}-${moment().format( - "YYYYMMDDTHHMMss" - )}.xml`, - }); - - logger.log("kaizen-end-shop-extract", "DEBUG", "api", bodyshop.id, { - shopname: bodyshop.shopname, - }); - } catch (error) { - //Error at the shop level. - logger.log("kaizen-error-shop", "ERROR", "api", bodyshop.id, { - ...error, - }); - - allErrors.push({ - bodyshopid: bodyshop.id, - imexshopid: bodyshop.imexshopid, - shopname: bodyshop.shopname, - fatal: true, - errors: [error.toString()], - }); - } finally { - allErrors.push({ - bodyshopid: bodyshop.id, - imexshopid: bodyshop.imexshopid, - shopname: bodyshop.shopname, - errors: erroredJobs.map((ej) => ({ - ro_number: ej.job?.ro_number, - jobid: ej.job?.id, - error: ej.error, - })), - }); - } - } - - if (skipUpload) { - for (const xmlObj of allxmlsToUpload) { - fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); - } - - res.json(allxmlsToUpload); - sendServerEmail({ - subject: `Kaizen Report ${moment().format("MM-DD-YY")}`, - text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} + res.json(allxmlsToUpload); + sendServerEmail({ + subject: `Kaizen Report ${moment().format("MM-DD-YY")}`, + text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} Uploaded: ${JSON.stringify( - allxmlsToUpload.map((x) => ({filename: x.filename, count: x.count})), - null, - 2 - )} - `, - }); - return; - } + allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })), + null, + 2 + )} + ` + }); + return; + } - let sftp = new Client(); - sftp.on("error", (errors) => - logger.log("kaizen-sftp-error", "ERROR", "api", null, { - ...errors, - }) - ); - try { - //Connect to the FTP and upload all. + let sftp = new Client(); + sftp.on("error", (errors) => + logger.log("kaizen-sftp-error", "ERROR", "api", null, { + ...errors + }) + ); + try { + //Connect to the FTP and upload all. - await sftp.connect(ftpSetup); + await sftp.connect(ftpSetup); - for (const xmlObj of allxmlsToUpload) { - logger.log("kaizen-sftp-upload", "DEBUG", "api", null, { - filename: xmlObj.filename, - }); + for (const xmlObj of allxmlsToUpload) { + logger.log("kaizen-sftp-upload", "DEBUG", "api", null, { + filename: xmlObj.filename + }); - const uploadResult = await sftp.put( - Buffer.from(xmlObj.xml), - `/${xmlObj.filename}` - ); - logger.log("kaizen-sftp-upload-result", "DEBUG", "api", null, { - uploadResult, - }); - } + const uploadResult = await sftp.put(Buffer.from(xmlObj.xml), `/${xmlObj.filename}`); + logger.log("kaizen-sftp-upload-result", "DEBUG", "api", null, { + uploadResult + }); + } //***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml } catch (error) { logger.log("kaizen-sftp-error", "ERROR", "api", null, { - ...error, + ...error }); } finally { sftp.end(); @@ -214,629 +189,439 @@ exports.default = async (req, res) => { res.sendStatus(200); } catch (error) { res.status(200).json(error); - sendServerEmail({ - subject: `Kaizen Report ${moment().format("MM-DD-YY @ HH:mm:ss")}`, - text: `Errors: JSON.stringify(error)} - All Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}`, - }); + sendServerEmail({ + subject: `Kaizen Report ${moment().format("MM-DD-YY @ HH:mm:ss")}`, + text: `Errors: JSON.stringify(error)} + All Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}` + }); } }; const CreateRepairOrderTag = (job, errorCallback) => { - //Level 2 + //Level 2 - if (!job.job_totals) { - errorCallback({ - jobid: job.id, - job: job, - ro_number: job.ro_number, - error: {toString: () => "No job totals for RO."}, - }); - return {}; - } + if (!job.job_totals) { + errorCallback({ + jobid: job.id, + job: job, + ro_number: job.ro_number, + error: { toString: () => "No job totals for RO." } + }); + return {}; + } - const repairCosts = CreateCosts(job); + const repairCosts = CreateCosts(job); - try { - const ret = { - JobID: job.id, - RoNumber: job.ro_number, - JobStatus: job.tlos_ind - ? "Total Loss" - : job.ro_number - ? job.status - : "Estimate", - Customer: { - CompanyName: job.ownr_co_nm?.trim() || "", - FirstName: job.ownr_fn?.trim() || "", - LastName: job.ownr_ln?.trim() || "", - Address1: job.ownr_addr1?.trim() || "", - Address2: job.ownr_addr2?.trim() || "", - City: job.ownr_city?.trim() || "", - State: job.ownr_st?.trim() || "", - Zip: job.ownr_zip?.trim() || "", - }, - Vehicle: { - Year: job.v_model_yr - ? parseInt(job.v_model_yr.match(/\d/g)) - ? parseInt(job.v_model_yr.match(/\d/g).join(""), 10) - : "" - : "", - Make: job.v_make_desc || "", - Model: job.v_model_desc || "", - BodyStyle: job.vehicle?.v_bstyle || "", - Color: job.v_color || "", - VIN: job.v_vin || "", - PlateNo: job.plate_no || "", - }, - InsuranceCompany: job.ins_co_nm || "", - Claim: job.clm_no || "", - Contacts: { - CSR: job.employee_csr_rel - ? `${ - job.employee_csr_rel.last_name - ? job.employee_csr_rel.last_name - : "" - }${job.employee_csr_rel.last_name ? ", " : ""}${ - job.employee_csr_rel.first_name - ? job.employee_csr_rel.first_name - : "" - }` - : "", - Estimator: `${job.est_ct_ln ? job.est_ct_ln : ""}${ - job.est_ct_ln ? ", " : "" - }${job.est_ct_fn ? job.est_ct_fn : ""}`, - }, - Dates: { - DateEstimated: - (job.date_estimated && - moment(job.date_estimated).format(DateFormat)) || - "", - DateOpened: - (job.date_opened && moment(job.date_opened).format(DateFormat)) || "", - DateScheduled: - (job.scheduled_in && - moment(job.scheduled_in) - .tz(job.bodyshop.timezone) - .format(DateFormat)) || - "", - DateArrived: - (job.actual_in && - moment(job.actual_in) - .tz(job.bodyshop.timezone) - .format(DateFormat)) || - "", - DateStart: job.date_repairstarted - ? (job.date_repairstarted && - moment(job.date_repairstarted) - .tz(job.bodyshop.timezone) - .format(DateFormat)) || - "" - : (job.actual_in && - moment(job.actual_in) - .tz(job.bodyshop.timezone) - .format(DateFormat)) || - "", - DateScheduledCompletion: - (job.scheduled_completion && - moment(job.scheduled_completion) - .tz(job.bodyshop.timezone) - .format(DateFormat)) || - "", - DateCompleted: - (job.actual_completion && - moment(job.actual_completion) - .tz(job.bodyshop.timezone) - .format(DateFormat)) || - "", - DateScheduledDelivery: - (job.scheduled_delivery && - moment(job.scheduled_delivery) - .tz(job.bodyshop.timezone) - .format(DateFormat)) || - "", - DateDelivered: - (job.actual_delivery && - moment(job.actual_delivery) - .tz(job.bodyshop.timezone) - .format(DateFormat)) || - "", - DateInvoiced: - (job.date_invoiced && - moment(job.date_invoiced) - .tz(job.bodyshop.timezone) - .format(DateFormat)) || - "", - DateExported: - (job.date_exported && - moment(job.date_exported) - .tz(job.bodyshop.timezone) - .format(DateFormat)) || - "", - }, - Sales: { - Labour: { - Aluminum: Dinero(job.job_totals.rates.laa.total).toFormat( - DineroFormat - ), - Body: Dinero(job.job_totals.rates.lab.total).toFormat(DineroFormat), - Diagnostic: Dinero(job.job_totals.rates.lad.total).toFormat( - DineroFormat - ), - Electrical: Dinero(job.job_totals.rates.lae.total).toFormat( - DineroFormat - ), - Frame: Dinero(job.job_totals.rates.laf.total).toFormat(DineroFormat), - Glass: Dinero(job.job_totals.rates.lag.total).toFormat(DineroFormat), - Mechanical: Dinero(job.job_totals.rates.lam.total).toFormat( - DineroFormat - ), - OtherLabour: Dinero(job.job_totals.rates.la1.total) - .add(Dinero(job.job_totals.rates.la2.total)) - .add(Dinero(job.job_totals.rates.la3.total)) - .add(Dinero(job.job_totals.rates.la4.total)) - .add(Dinero(job.job_totals.rates.lau.total)) - .toFormat(DineroFormat), - Refinish: Dinero(job.job_totals.rates.lar.total).toFormat( - DineroFormat - ), - Structural: Dinero(job.job_totals.rates.las.total).toFormat( - DineroFormat - ), - }, - Materials: { - Body: Dinero(job.job_totals.rates.mash.total).toFormat(DineroFormat), - Refinish: Dinero(job.job_totals.rates.mapa.total).toFormat( - DineroFormat - ), - }, - Parts: { - Aftermarket: Dinero( - job.job_totals.parts.parts.list.PAA && - job.job_totals.parts.parts.list.PAA.total - ).toFormat(DineroFormat), - LKQ: Dinero( - job.job_totals.parts.parts.list.PAL && - job.job_totals.parts.parts.list.PAL.total - ).toFormat(DineroFormat), - OEM: Dinero( - job.job_totals.parts.parts.list.PAN && - job.job_totals.parts.parts.list.PAN.total - ) - .add( - Dinero( - job.job_totals.parts.parts.list.PAP && - job.job_totals.parts.parts.list.PAP.total - ) - ) - .toFormat(DineroFormat), - OtherParts: Dinero( - job.job_totals.parts.parts.list.PAO && - job.job_totals.parts.parts.list.PAO.total - ).toFormat(DineroFormat), - Reconditioned: Dinero( - job.job_totals.parts.parts.list.PAM && - job.job_totals.parts.parts.list.PAM.total - ).toFormat(DineroFormat), - TotalParts: Dinero( - job.job_totals.parts.parts.list.PAA && - job.job_totals.parts.parts.list.PAA.total - ) - .add( - Dinero( - job.job_totals.parts.parts.list.PAL && - job.job_totals.parts.parts.list.PAL.total - ) - ) - .add( - Dinero( - job.job_totals.parts.parts.list.PAN && - job.job_totals.parts.parts.list.PAN.total - ) - ) - .add( - Dinero( - job.job_totals.parts.parts.list.PAO && - job.job_totals.parts.parts.list.PAO.total - ) - ) - .add( - Dinero( - job.job_totals.parts.parts.list.PAM && - job.job_totals.parts.parts.list.PAM.total - ) - ) - .toFormat(DineroFormat), - }, - OtherSales: Dinero(job.job_totals.additional.storage).toFormat( - DineroFormat - ), - Sublet: Dinero(job.job_totals.parts.sublets.total).toFormat( - DineroFormat - ), - Towing: Dinero(job.job_totals.additional.towing).toFormat(DineroFormat), - ATS: - job.job_totals.additional.additionalCostItems.includes( - "ATS Amount" - ) === true - ? Dinero( - job.job_totals.additional.additionalCostItems[ - job.job_totals.additional.additionalCostItems.indexOf( - "ATS Amount" - ) - ].total - ).toFormat(DineroFormat) - : Dinero().toFormat(DineroFormat), - SaleSubtotal: Dinero(job.job_totals.totals.subtotal).toFormat( - DineroFormat - ), - Tax: Dinero(job.job_totals.totals.local_tax) - .add(Dinero(job.job_totals.totals.state_tax)) - .add(Dinero(job.job_totals.totals.federal_tax)) - .add(Dinero(job.job_totals.additional.pvrt)) - .toFormat(DineroFormat), - SaleTotal: Dinero(job.job_totals.totals.total_repairs).toFormat( - DineroFormat - ), - }, - SaleHours: { - Aluminum: job.job_totals.rates.laa.hours.toFixed(2), - Body: job.job_totals.rates.lab.hours.toFixed(2), - Diagnostic: job.job_totals.rates.lad.hours.toFixed(2), - Electrical: job.job_totals.rates.lae.hours.toFixed(2), - Frame: job.job_totals.rates.laf.hours.toFixed(2), - Glass: job.job_totals.rates.lag.hours.toFixed(2), - Mechanical: job.job_totals.rates.lam.hours.toFixed(2), - Other: ( - job.job_totals.rates.la1.hours + - job.job_totals.rates.la2.hours + - job.job_totals.rates.la3.hours + - job.job_totals.rates.la4.hours + - job.job_totals.rates.lau.hours - ).toFixed(2), - Refinish: job.job_totals.rates.lar.hours.toFixed(2), - Structural: job.job_totals.rates.las.hours.toFixed(2), - TotalHours: job.joblines - .reduce((acc, val) => acc + val.mod_lb_hrs, 0) - .toFixed(2), - }, - Costs: { - Labour: { - Aluminum: repairCosts.AluminumLabourTotalCost.toFormat(DineroFormat), - Body: repairCosts.BodyLabourTotalCost.toFormat(DineroFormat), - Diagnostic: - repairCosts.DiagnosticLabourTotalCost.toFormat(DineroFormat), - Electrical: - repairCosts.ElectricalLabourTotalCost.toFormat(DineroFormat), - Frame: repairCosts.FrameLabourTotalCost.toFormat(DineroFormat), - Glass: repairCosts.GlassLabourTotalCost.toFormat(DineroFormat), - Mechancial: - repairCosts.MechanicalLabourTotalCost.toFormat(DineroFormat), - OtherLabour: repairCosts.LabourMiscTotalCost.toFormat(DineroFormat), - Refinish: repairCosts.RefinishLabourTotalCost.toFormat(DineroFormat), - Structural: - repairCosts.StructuralLabourTotalCost.toFormat(DineroFormat), - TotalLabour: repairCosts.LabourTotalCost.toFormat(DineroFormat), - }, - Materials: { - Body: repairCosts.BMTotalCost.toFormat(DineroFormat), - Refinish: repairCosts.PMTotalCost.toFormat(DineroFormat), - }, - Parts: { - Aftermarket: repairCosts.PartsAMCost.toFormat(DineroFormat), - LKQ: repairCosts.PartsRecycledCost.toFormat(DineroFormat), - OEM: repairCosts.PartsOemCost.toFormat(DineroFormat), - OtherCost: repairCosts.PartsOtherCost.toFormat(DineroFormat), - Reconditioned: - repairCosts.PartsReconditionedCost.toFormat(DineroFormat), - TotalParts: repairCosts.PartsAMCost.add(repairCosts.PartsRecycledCost) - .add(repairCosts.PartsReconditionedCost) - .add(repairCosts.PartsOemCost) - .add(repairCosts.PartsOtherCost) - .toFormat(DineroFormat), - }, - Sublet: repairCosts.SubletTotalCost.toFormat(DineroFormat), - Towing: repairCosts.TowingTotalCost.toFormat(DineroFormat), - ATS: Dinero().toFormat(DineroFormat), - Storage: repairCosts.StorageTotalCost.toFormat(DineroFormat), - CostTotal: repairCosts.TotalCost.toFormat(DineroFormat), - }, - CostHours: { - Aluminum: repairCosts.AluminumLabourTotalHrs.toFixed(2), - Body: repairCosts.BodyLabourTotalHrs.toFixed(2), - Diagnostic: repairCosts.DiagnosticLabourTotalHrs.toFixed(2), - Refinish: repairCosts.RefinishLabourTotalHrs.toFixed(2), - Frame: repairCosts.FrameLabourTotalHrs.toFixed(2), - Mechanical: repairCosts.MechanicalLabourTotalHrs.toFixed(2), - Glass: repairCosts.GlassLabourTotalHrs.toFixed(2), - Electrical: repairCosts.ElectricalLabourTotalHrs.toFixed(2), - Structural: repairCosts.StructuralLabourTotalHrs.toFixed(2), - Other: repairCosts.LabourMiscTotalHrs.toFixed(2), - CostTotalHours: repairCosts.TotalHrs.toFixed(2), - }, - }; - return ret; - } catch (error) { - logger.log("kaizen-job-calculate-error", "ERROR", "api", null, { - error, - }); + try { + const ret = { + JobID: job.id, + RoNumber: job.ro_number, + JobStatus: job.tlos_ind ? "Total Loss" : job.ro_number ? job.status : "Estimate", + Customer: { + CompanyName: job.ownr_co_nm?.trim() || "", + FirstName: job.ownr_fn?.trim() || "", + LastName: job.ownr_ln?.trim() || "", + Address1: job.ownr_addr1?.trim() || "", + Address2: job.ownr_addr2?.trim() || "", + City: job.ownr_city?.trim() || "", + State: job.ownr_st?.trim() || "", + Zip: job.ownr_zip?.trim() || "" + }, + Vehicle: { + Year: job.v_model_yr + ? parseInt(job.v_model_yr.match(/\d/g)) + ? parseInt(job.v_model_yr.match(/\d/g).join(""), 10) + : "" + : "", + Make: job.v_make_desc || "", + Model: job.v_model_desc || "", + BodyStyle: job.vehicle?.v_bstyle || "", + Color: job.v_color || "", + VIN: job.v_vin || "", + PlateNo: job.plate_no || "" + }, + InsuranceCompany: job.ins_co_nm || "", + Claim: job.clm_no || "", + Contacts: { + CSR: job.employee_csr_rel + ? `${ + job.employee_csr_rel.last_name ? job.employee_csr_rel.last_name : "" + }${job.employee_csr_rel.last_name ? ", " : ""}${ + job.employee_csr_rel.first_name ? job.employee_csr_rel.first_name : "" + }` + : "", + Estimator: `${job.est_ct_ln ? job.est_ct_ln : ""}${ + job.est_ct_ln ? ", " : "" + }${job.est_ct_fn ? job.est_ct_fn : ""}` + }, + Dates: { + DateEstimated: (job.date_estimated && moment(job.date_estimated).format(DateFormat)) || "", + DateOpened: (job.date_opened && moment(job.date_opened).format(DateFormat)) || "", + DateScheduled: + (job.scheduled_in && moment(job.scheduled_in).tz(job.bodyshop.timezone).format(DateFormat)) || "", + DateArrived: (job.actual_in && moment(job.actual_in).tz(job.bodyshop.timezone).format(DateFormat)) || "", + DateStart: job.date_repairstarted + ? (job.date_repairstarted && moment(job.date_repairstarted).tz(job.bodyshop.timezone).format(DateFormat)) || + "" + : (job.actual_in && moment(job.actual_in).tz(job.bodyshop.timezone).format(DateFormat)) || "", + DateScheduledCompletion: + (job.scheduled_completion && moment(job.scheduled_completion).tz(job.bodyshop.timezone).format(DateFormat)) || + "", + DateCompleted: + (job.actual_completion && moment(job.actual_completion).tz(job.bodyshop.timezone).format(DateFormat)) || "", + DateScheduledDelivery: + (job.scheduled_delivery && moment(job.scheduled_delivery).tz(job.bodyshop.timezone).format(DateFormat)) || "", + DateDelivered: + (job.actual_delivery && moment(job.actual_delivery).tz(job.bodyshop.timezone).format(DateFormat)) || "", + DateInvoiced: + (job.date_invoiced && moment(job.date_invoiced).tz(job.bodyshop.timezone).format(DateFormat)) || "", + DateExported: + (job.date_exported && moment(job.date_exported).tz(job.bodyshop.timezone).format(DateFormat)) || "" + }, + Sales: { + Labour: { + Aluminum: Dinero(job.job_totals.rates.laa.total).toFormat(DineroFormat), + Body: Dinero(job.job_totals.rates.lab.total).toFormat(DineroFormat), + Diagnostic: Dinero(job.job_totals.rates.lad.total).toFormat(DineroFormat), + Electrical: Dinero(job.job_totals.rates.lae.total).toFormat(DineroFormat), + Frame: Dinero(job.job_totals.rates.laf.total).toFormat(DineroFormat), + Glass: Dinero(job.job_totals.rates.lag.total).toFormat(DineroFormat), + Mechanical: Dinero(job.job_totals.rates.lam.total).toFormat(DineroFormat), + OtherLabour: Dinero(job.job_totals.rates.la1.total) + .add(Dinero(job.job_totals.rates.la2.total)) + .add(Dinero(job.job_totals.rates.la3.total)) + .add(Dinero(job.job_totals.rates.la4.total)) + .add(Dinero(job.job_totals.rates.lau.total)) + .toFormat(DineroFormat), + Refinish: Dinero(job.job_totals.rates.lar.total).toFormat(DineroFormat), + Structural: Dinero(job.job_totals.rates.las.total).toFormat(DineroFormat) + }, + Materials: { + Body: Dinero(job.job_totals.rates.mash.total).toFormat(DineroFormat), + Refinish: Dinero(job.job_totals.rates.mapa.total).toFormat(DineroFormat) + }, + Parts: { + Aftermarket: Dinero( + job.job_totals.parts.parts.list.PAA && job.job_totals.parts.parts.list.PAA.total + ).toFormat(DineroFormat), + LKQ: Dinero(job.job_totals.parts.parts.list.PAL && job.job_totals.parts.parts.list.PAL.total).toFormat( + DineroFormat + ), + OEM: Dinero(job.job_totals.parts.parts.list.PAN && job.job_totals.parts.parts.list.PAN.total) + .add(Dinero(job.job_totals.parts.parts.list.PAP && job.job_totals.parts.parts.list.PAP.total)) + .toFormat(DineroFormat), + OtherParts: Dinero(job.job_totals.parts.parts.list.PAO && job.job_totals.parts.parts.list.PAO.total).toFormat( + DineroFormat + ), + Reconditioned: Dinero( + job.job_totals.parts.parts.list.PAM && job.job_totals.parts.parts.list.PAM.total + ).toFormat(DineroFormat), + TotalParts: Dinero(job.job_totals.parts.parts.list.PAA && job.job_totals.parts.parts.list.PAA.total) + .add(Dinero(job.job_totals.parts.parts.list.PAL && job.job_totals.parts.parts.list.PAL.total)) + .add(Dinero(job.job_totals.parts.parts.list.PAN && job.job_totals.parts.parts.list.PAN.total)) + .add(Dinero(job.job_totals.parts.parts.list.PAO && job.job_totals.parts.parts.list.PAO.total)) + .add(Dinero(job.job_totals.parts.parts.list.PAM && job.job_totals.parts.parts.list.PAM.total)) + .toFormat(DineroFormat) + }, + OtherSales: Dinero(job.job_totals.additional.storage).toFormat(DineroFormat), + Sublet: Dinero(job.job_totals.parts.sublets.total).toFormat(DineroFormat), + Towing: Dinero(job.job_totals.additional.towing).toFormat(DineroFormat), + ATS: + job.job_totals.additional.additionalCostItems.includes("ATS Amount") === true + ? Dinero( + job.job_totals.additional.additionalCostItems[ + job.job_totals.additional.additionalCostItems.indexOf("ATS Amount") + ].total + ).toFormat(DineroFormat) + : Dinero().toFormat(DineroFormat), + SaleSubtotal: Dinero(job.job_totals.totals.subtotal).toFormat(DineroFormat), + Tax: Dinero(job.job_totals.totals.local_tax) + .add(Dinero(job.job_totals.totals.state_tax)) + .add(Dinero(job.job_totals.totals.federal_tax)) + .add(Dinero(job.job_totals.additional.pvrt)) + .toFormat(DineroFormat), + SaleTotal: Dinero(job.job_totals.totals.total_repairs).toFormat(DineroFormat) + }, + SaleHours: { + Aluminum: job.job_totals.rates.laa.hours.toFixed(2), + Body: job.job_totals.rates.lab.hours.toFixed(2), + Diagnostic: job.job_totals.rates.lad.hours.toFixed(2), + Electrical: job.job_totals.rates.lae.hours.toFixed(2), + Frame: job.job_totals.rates.laf.hours.toFixed(2), + Glass: job.job_totals.rates.lag.hours.toFixed(2), + Mechanical: job.job_totals.rates.lam.hours.toFixed(2), + Other: ( + job.job_totals.rates.la1.hours + + job.job_totals.rates.la2.hours + + job.job_totals.rates.la3.hours + + job.job_totals.rates.la4.hours + + job.job_totals.rates.lau.hours + ).toFixed(2), + Refinish: job.job_totals.rates.lar.hours.toFixed(2), + Structural: job.job_totals.rates.las.hours.toFixed(2), + TotalHours: job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0).toFixed(2) + }, + Costs: { + Labour: { + Aluminum: repairCosts.AluminumLabourTotalCost.toFormat(DineroFormat), + Body: repairCosts.BodyLabourTotalCost.toFormat(DineroFormat), + Diagnostic: repairCosts.DiagnosticLabourTotalCost.toFormat(DineroFormat), + Electrical: repairCosts.ElectricalLabourTotalCost.toFormat(DineroFormat), + Frame: repairCosts.FrameLabourTotalCost.toFormat(DineroFormat), + Glass: repairCosts.GlassLabourTotalCost.toFormat(DineroFormat), + Mechancial: repairCosts.MechanicalLabourTotalCost.toFormat(DineroFormat), + OtherLabour: repairCosts.LabourMiscTotalCost.toFormat(DineroFormat), + Refinish: repairCosts.RefinishLabourTotalCost.toFormat(DineroFormat), + Structural: repairCosts.StructuralLabourTotalCost.toFormat(DineroFormat), + TotalLabour: repairCosts.LabourTotalCost.toFormat(DineroFormat) + }, + Materials: { + Body: repairCosts.BMTotalCost.toFormat(DineroFormat), + Refinish: repairCosts.PMTotalCost.toFormat(DineroFormat) + }, + Parts: { + Aftermarket: repairCosts.PartsAMCost.toFormat(DineroFormat), + LKQ: repairCosts.PartsRecycledCost.toFormat(DineroFormat), + OEM: repairCosts.PartsOemCost.toFormat(DineroFormat), + OtherCost: repairCosts.PartsOtherCost.toFormat(DineroFormat), + Reconditioned: repairCosts.PartsReconditionedCost.toFormat(DineroFormat), + TotalParts: repairCosts.PartsAMCost.add(repairCosts.PartsRecycledCost) + .add(repairCosts.PartsReconditionedCost) + .add(repairCosts.PartsOemCost) + .add(repairCosts.PartsOtherCost) + .toFormat(DineroFormat) + }, + Sublet: repairCosts.SubletTotalCost.toFormat(DineroFormat), + Towing: repairCosts.TowingTotalCost.toFormat(DineroFormat), + ATS: Dinero().toFormat(DineroFormat), + Storage: repairCosts.StorageTotalCost.toFormat(DineroFormat), + CostTotal: repairCosts.TotalCost.toFormat(DineroFormat) + }, + CostHours: { + Aluminum: repairCosts.AluminumLabourTotalHrs.toFixed(2), + Body: repairCosts.BodyLabourTotalHrs.toFixed(2), + Diagnostic: repairCosts.DiagnosticLabourTotalHrs.toFixed(2), + Refinish: repairCosts.RefinishLabourTotalHrs.toFixed(2), + Frame: repairCosts.FrameLabourTotalHrs.toFixed(2), + Mechanical: repairCosts.MechanicalLabourTotalHrs.toFixed(2), + Glass: repairCosts.GlassLabourTotalHrs.toFixed(2), + Electrical: repairCosts.ElectricalLabourTotalHrs.toFixed(2), + Structural: repairCosts.StructuralLabourTotalHrs.toFixed(2), + Other: repairCosts.LabourMiscTotalHrs.toFixed(2), + CostTotalHours: repairCosts.TotalHrs.toFixed(2) + } + }; + return ret; + } catch (error) { + logger.log("kaizen-job-calculate-error", "ERROR", "api", null, { + error + }); - errorCallback({jobid: job.id, ro_number: job.ro_number, error}); - } + errorCallback({ jobid: job.id, ro_number: job.ro_number, error }); + } }; const CreateCosts = (job) => { - //Create a mapping based on AH Requirements + //Create a mapping based on AH Requirements - //For DMS, the keys in the object below are the CIECA part types. - const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => { - //At the bill level. - bill_val.billlines.map((line_val) => { - //At the bill line level. + //For DMS, the keys in the object below are the CIECA part types. + const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => { + //At the bill level. + bill_val.billlines.map((line_val) => { + //At the bill line level. - if (!bill_acc[line_val.cost_center]) - bill_acc[line_val.cost_center] = Dinero(); + if (!bill_acc[line_val.cost_center]) bill_acc[line_val.cost_center] = Dinero(); - bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add( - Dinero({ - amount: Math.round((line_val.actual_cost || 0) * 100), - }) - .multiply(line_val.quantity) - .multiply(bill_val.is_credit_memo ? -1 : 1) - ); + bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add( + Dinero({ + amount: Math.round((line_val.actual_cost || 0) * 100) + }) + .multiply(line_val.quantity) + .multiply(bill_val.is_credit_memo ? -1 : 1) + ); - return null; + return null; + }); + 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 || + typeof job.bodyshop.jc_hourly_rates.mapa === "number" || + isNaN(job.bodyshop.jc_hourly_rates.mapa) === false) + ) { + if (!billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA]) + billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = Dinero(); + if (job.bodyshop.use_paint_scale_data === true) { + if (job.mixdata.length > 0) { + billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = Dinero({ + amount: Math.round(((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100) }); - 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 || - typeof job.bodyshop.jc_hourly_rates.mapa === "number" || - isNaN(job.bodyshop.jc_hourly_rates.mapa) === false) - ) { - if ( - !billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] - ) - billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = Dinero(); - if (job.bodyshop.use_paint_scale_data === true) { - if (job.mixdata.length > 0) { - billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = Dinero({ - amount: Math.round( - ((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100 - ), - }); - } else { - billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ].add( - Dinero({ - amount: Math.round( - (job.bodyshop.jc_hourly_rates && - job.bodyshop.jc_hourly_rates.mapa * 100) || - 0 - ), - }).multiply(job.job_totals.rates.mapa.hours) - ); - } - } else { - billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = billTotalsByCostCenters[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ].add( - Dinero({ - amount: Math.round( - (job.bodyshop.jc_hourly_rates && - job.bodyshop.jc_hourly_rates.mapa * 100) || - 0 - ), - }).multiply(job.job_totals.rates.mapa.hours) - ); - } - } - 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: Math.round( - (job.bodyshop.jc_hourly_rates && - job.bodyshop.jc_hourly_rates.mash * 100) || - 0 - ), - }).multiply(job.job_totals.rates.mash.hours) + } else { + billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ].add( + Dinero({ + amount: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa * 100) || 0) + }).multiply(job.job_totals.rates.mapa.hours) ); + } + } else { + billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ].add( + Dinero({ + amount: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa * 100) || 0) + }).multiply(job.job_totals.rates.mapa.hours) + ); } - //Uses CIECA Labour types. - const ticketTotalsByCostCenter = job.timetickets.reduce( - (ticket_acc, ticket_val) => { - //At the invoice level. - if (!ticket_acc[ticket_val.cost_center]) - ticket_acc[ticket_val.cost_center] = Dinero(); - - ticket_acc[ticket_val.cost_center] = ticket_acc[ - ticket_val.cost_center - ].add( - Dinero({ - amount: Math.round((ticket_val.rate || 0) * 100), - }).multiply( - (ticket_val.flat_rate - ? ticket_val.productivehrs - : ticket_val.actualhrs) || 0 - ) - ); - - return ticket_acc; - }, - {} + } + 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: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash * 100) || 0) + }).multiply(job.job_totals.rates.mash.hours) ); - const ticketHrsByCostCenter = job.timetickets.reduce( - (ticket_acc, ticket_val) => { - //At the invoice level. - if (!ticket_acc[ticket_val.cost_center]) - ticket_acc[ticket_val.cost_center] = 0; + } + //Uses CIECA Labour types. + const ticketTotalsByCostCenter = job.timetickets.reduce((ticket_acc, ticket_val) => { + //At the invoice level. + if (!ticket_acc[ticket_val.cost_center]) ticket_acc[ticket_val.cost_center] = Dinero(); - ticket_acc[ticket_val.cost_center] = - ticket_acc[ticket_val.cost_center] + - (ticket_val.flat_rate - ? ticket_val.productivehrs - : ticket_val.actualhrs) || 0; - - return ticket_acc; - }, - {} + ticket_acc[ticket_val.cost_center] = ticket_acc[ticket_val.cost_center].add( + Dinero({ + amount: Math.round((ticket_val.rate || 0) * 100) + }).multiply((ticket_val.flat_rate ? ticket_val.productivehrs : ticket_val.actualhrs) || 0) ); - //CIECA STANDARD MAPPING OBJECT. - const ciecaObj = { - ATS: "ATS", - LA1: "LA1", - LA2: "LA2", - LA3: "LA3", - LA4: "LA4", - LAA: "LAA", - LAB: "LAB", - LAD: "LAD", - LAE: "LAE", - LAF: "LAF", - LAG: "LAG", - LAM: "LAM", - LAR: "LAR", - LAS: "LAS", - LAU: "LAU", - PAA: "PAA", - PAC: "PAC", - PAG: "PAG", - PAL: "PAL", - PAM: "PAM", - PAN: "PAN", - PAO: "PAO", - PAP: "PAP", - PAR: "PAR", - PAS: "PAS", - TOW: "TOW", - MAPA: "MAPA", - MASH: "MASH", - PASL: "PASL", - }; - const defaultCosts = - job.bodyshop.cdk_dealerid || job.bodyshop.pbs_serialnumber - ? ciecaObj - : job.bodyshop.md_responsibility_centers.defaults.costs; + return ticket_acc; + }, {}); + const ticketHrsByCostCenter = job.timetickets.reduce((ticket_acc, ticket_val) => { + //At the invoice level. + if (!ticket_acc[ticket_val.cost_center]) ticket_acc[ticket_val.cost_center] = 0; - return { - PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => { - if ( - key !== defaultCosts.PAS && - key !== defaultCosts.PASL && - key !== defaultCosts.MAPA && - key !== defaultCosts.MASH && - key !== defaultCosts.TOW - ) - return acc.add(billTotalsByCostCenters[key]); - return acc; - }, Dinero()), - PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add( - billTotalsByCostCenters[defaultCosts.PAP] || Dinero() - ), - PartsAMCost: billTotalsByCostCenters[defaultCosts.PAA] || Dinero(), - PartsReconditionedCost: - billTotalsByCostCenters[defaultCosts.PAM] || Dinero(), - PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAL] || Dinero(), - PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), + ticket_acc[ticket_val.cost_center] = + ticket_acc[ticket_val.cost_center] + (ticket_val.flat_rate ? ticket_val.productivehrs : ticket_val.actualhrs) || + 0; - SubletTotalCost: - billTotalsByCostCenters[defaultCosts.PAS] || - Dinero(billTotalsByCostCenters[defaultCosts.PASL] || Dinero()), + return ticket_acc; + }, {}); + //CIECA STANDARD MAPPING OBJECT. - AluminumLabourTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAA] || Dinero(), - AluminumLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAA] || 0, - BodyLabourTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(), - BodyLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAB] || 0, - DiagnosticLabourTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(), - DiagnosticLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAD] || 0, - ElectricalLabourTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(), - ElectricalLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAE] || 0, - FrameLabourTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(), - FrameLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAF] || 0, - GlassLabourTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(), - GlassLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAG] || 0, - LabourMiscTotalCost: ( - ticketTotalsByCostCenter[defaultCosts.LA1] || Dinero() - ) - .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero()) - .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero()) - .add(ticketTotalsByCostCenter[defaultCosts.LA3] || Dinero()) - .add(ticketTotalsByCostCenter[defaultCosts.LA4] || Dinero()) - .add(ticketTotalsByCostCenter[defaultCosts.LAU] || Dinero()), - LabourMiscTotalHrs: - (ticketHrsByCostCenter[defaultCosts.LA1] || 0) + - (ticketHrsByCostCenter[defaultCosts.LA2] || 0) + - (ticketHrsByCostCenter[defaultCosts.LA3] || 0) + - (ticketHrsByCostCenter[defaultCosts.LA4] || 0) + - (ticketHrsByCostCenter[defaultCosts.LAU] || 0), - MechanicalLabourTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(), - MechanicalLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAM] || 0, - RefinishLabourTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(), - RefinishLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAR] || 0, - StructuralLabourTotalCost: - ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(), - StructuralLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAS] || 0, + const ciecaObj = { + ATS: "ATS", + LA1: "LA1", + LA2: "LA2", + LA3: "LA3", + LA4: "LA4", + LAA: "LAA", + LAB: "LAB", + LAD: "LAD", + LAE: "LAE", + LAF: "LAF", + LAG: "LAG", + LAM: "LAM", + LAR: "LAR", + LAS: "LAS", + LAU: "LAU", + PAA: "PAA", + PAC: "PAC", + PAG: "PAG", + PAL: "PAL", + PAM: "PAM", + PAN: "PAN", + PAO: "PAO", + PAP: "PAP", + PAR: "PAR", + PAS: "PAS", + TOW: "TOW", + MAPA: "MAPA", + MASH: "MASH", + PASL: "PASL" + }; + const defaultCosts = + job.bodyshop.cdk_dealerid || job.bodyshop.pbs_serialnumber + ? ciecaObj + : job.bodyshop.md_responsibility_centers.defaults.costs; - PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(), - BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(), + return { + PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => { + if ( + key !== defaultCosts.PAS && + key !== defaultCosts.PASL && + key !== defaultCosts.MAPA && + key !== defaultCosts.MASH && + key !== defaultCosts.TOW + ) + return acc.add(billTotalsByCostCenters[key]); + return acc; + }, Dinero()), + PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add( + billTotalsByCostCenters[defaultCosts.PAP] || Dinero() + ), + PartsAMCost: billTotalsByCostCenters[defaultCosts.PAA] || Dinero(), + PartsReconditionedCost: billTotalsByCostCenters[defaultCosts.PAM] || Dinero(), + PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAL] || Dinero(), + PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), - MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), - TowingTotalCost: billTotalsByCostCenters[defaultCosts.TOW] || Dinero(), - StorageTotalCost: Dinero(), - DetailTotal: Dinero(), - DetailTotalCost: Dinero(), + SubletTotalCost: + billTotalsByCostCenters[defaultCosts.PAS] || Dinero(billTotalsByCostCenters[defaultCosts.PASL] || Dinero()), - SalesTaxTotalCost: Dinero(), - LabourTotalCost: Object.keys(ticketTotalsByCostCenter).reduce( - (acc, key) => { - return acc.add(ticketTotalsByCostCenter[key]); - }, - Dinero() - ), - TotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => { - return acc.add(billTotalsByCostCenters[key]); - }, Dinero()), - TotalHrs: job.timetickets.reduce((acc, ticket_val) => { - return ( - acc + - (ticket_val.flat_rate - ? ticket_val.productivehrs - : ticket_val.actualhrs) || 0 - ); - }, 0), - }; + AluminumLabourTotalCost: ticketTotalsByCostCenter[defaultCosts.LAA] || Dinero(), + AluminumLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAA] || 0, + BodyLabourTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(), + BodyLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAB] || 0, + DiagnosticLabourTotalCost: ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(), + DiagnosticLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAD] || 0, + ElectricalLabourTotalCost: ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(), + ElectricalLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAE] || 0, + FrameLabourTotalCost: ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(), + FrameLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAF] || 0, + GlassLabourTotalCost: ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(), + GlassLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAG] || 0, + LabourMiscTotalCost: (ticketTotalsByCostCenter[defaultCosts.LA1] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA3] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA4] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LAU] || Dinero()), + LabourMiscTotalHrs: + (ticketHrsByCostCenter[defaultCosts.LA1] || 0) + + (ticketHrsByCostCenter[defaultCosts.LA2] || 0) + + (ticketHrsByCostCenter[defaultCosts.LA3] || 0) + + (ticketHrsByCostCenter[defaultCosts.LA4] || 0) + + (ticketHrsByCostCenter[defaultCosts.LAU] || 0), + MechanicalLabourTotalCost: ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(), + MechanicalLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAM] || 0, + RefinishLabourTotalCost: ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(), + RefinishLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAR] || 0, + StructuralLabourTotalCost: ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(), + StructuralLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAS] || 0, + + PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(), + BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(), + + MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), + TowingTotalCost: billTotalsByCostCenters[defaultCosts.TOW] || Dinero(), + StorageTotalCost: Dinero(), + DetailTotal: Dinero(), + DetailTotalCost: Dinero(), + + SalesTaxTotalCost: Dinero(), + LabourTotalCost: Object.keys(ticketTotalsByCostCenter).reduce((acc, key) => { + return acc.add(ticketTotalsByCostCenter[key]); + }, Dinero()), + TotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => { + return acc.add(billTotalsByCostCenters[key]); + }, Dinero()), + TotalHrs: job.timetickets.reduce((acc, ticket_val) => { + return acc + (ticket_val.flat_rate ? ticket_val.productivehrs : ticket_val.actualhrs) || 0; + }, 0) + }; }; diff --git a/server/email/sendemail.js b/server/email/sendemail.js index ba6c76286..fa8a38075 100644 --- a/server/email/sendemail.js +++ b/server/email/sendemail.js @@ -1,9 +1,6 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const axios = require("axios"); let nodemailer = require("nodemailer"); @@ -15,302 +12,284 @@ const client = require("../graphql-client/graphql-client").client; const queries = require("../graphql-client/queries"); const ses = new aws.SES({ - // The key apiVersion is no longer supported in v3, and can be removed. - // @deprecated The client uses the "latest" apiVersion. - apiVersion: "latest", - defaultProvider, - region: InstanceManager({ - imex: "ca-central-1", - rome: "us-east-2", - }), + // The key apiVersion is no longer supported in v3, and can be removed. + // @deprecated The client uses the "latest" apiVersion. + apiVersion: "latest", + defaultProvider, + region: InstanceManager({ + imex: "ca-central-1", + rome: "us-east-2" + }) }); let transporter = nodemailer.createTransport({ - SES: { ses, aws }, + SES: { ses, aws } }); exports.sendServerEmail = async function ({ subject, text }) { - if (process.env.NODE_ENV === undefined) return; - try { - transporter.sendMail( + if (process.env.NODE_ENV === undefined) return; + try { + transporter.sendMail( + { + from: InstanceManager({ + imex: `ImEX Online API - ${process.env.NODE_ENV} `, + rome: `Rome Online API - ${process.env.NODE_ENV} `, + promanager: `ProManager API - ${process.env.NODE_ENV} ` + }), + to: ["patrick@imexsystems.ca"], + subject: subject, + text: text, + ses: { + // optional extra arguments for SendRawEmail + Tags: [ { - from: InstanceManager({ - imex: `ImEX Online API - ${process.env.NODE_ENV} `, - rome: `Rome Online API - ${process.env.NODE_ENV} `, - promanager: `ProManager API - ${process.env.NODE_ENV} `, - }), - to: ["patrick@imexsystems.ca"], - subject: subject, - text: text, - ses: { - // optional extra arguments for SendRawEmail - Tags: [ - { - Name: "tag_name", - Value: "tag_value", - }, - ], - }, - }, - (err, info) => { - console.log(err || info); + Name: "tag_name", + Value: "tag_value" } - ); - } catch (error) { - console.log(error); - logger.log("server-email-failure", "error", null, null, error); - } + ] + } + }, + (err, info) => { + console.log(err || info); + } + ); + } catch (error) { + console.log(error); + logger.log("server-email-failure", "error", null, null, error); + } }; exports.sendTaskEmail = async function ({ to, subject, text, attachments }) { - try { - transporter.sendMail( - { - from: InstanceManager({ - imex: `ImEX Online `, - rome: `Rome Online `, - promanager: `ProManager `, - }), - to: to, - subject: subject, - text: text, - attachments: attachments || null, - }, - (err, info) => { - console.log(err || info); - } - ); - } catch (error) { - console.log(error); - logger.log("server-email-failure", "error", null, null, error); - } + try { + transporter.sendMail( + { + from: InstanceManager({ + imex: `ImEX Online `, + rome: `Rome Online `, + promanager: `ProManager ` + }), + to: to, + subject: subject, + text: text, + attachments: attachments || null + }, + (err, info) => { + console.log(err || info); + } + ); + } catch (error) { + console.log(error); + logger.log("server-email-failure", "error", null, null, error); + } }; exports.sendEmail = async (req, res) => { - logger.log("send-email", "DEBUG", req.user.email, null, { - from: `${req.body.from.name} <${req.body.from.address}>`, - replyTo: req.body.ReplyTo.Email, - to: req.body.to, - cc: req.body.cc, - subject: req.body.subject, - }); + logger.log("send-email", "DEBUG", req.user.email, null, { + from: `${req.body.from.name} <${req.body.from.address}>`, + replyTo: req.body.ReplyTo.Email, + to: req.body.to, + cc: req.body.cc, + subject: req.body.subject + }); - let downloadedMedia = []; - if (req.body.media && req.body.media.length > 0) { - downloadedMedia = await Promise.all( - req.body.media.map((m) => { - try { - return getImage(m); - } catch (error) { - logger.log( - "send-email-error", - "ERROR", - req.user.email, - null, - { - from: `${req.body.from.name} <${req.body.from.address}>`, - replyTo: req.body.ReplyTo.Email, - to: req.body.to, - cc: req.body.cc, - subject: req.body.subject, - error, - } - ); - } - }) - ); - } - - transporter.sendMail( - { + let downloadedMedia = []; + if (req.body.media && req.body.media.length > 0) { + downloadedMedia = await Promise.all( + req.body.media.map((m) => { + try { + return getImage(m); + } catch (error) { + logger.log("send-email-error", "ERROR", req.user.email, null, { from: `${req.body.from.name} <${req.body.from.address}>`, replyTo: req.body.ReplyTo.Email, to: req.body.to, cc: req.body.cc, subject: req.body.subject, - attachments: - [ - ...((req.body.attachments && - req.body.attachments.map((a) => { - return { - filename: a.filename, - path: a.path, - }; - })) || - []), - ...downloadedMedia.map((a) => { - return { - path: a, - }; - }), - ] || null, - html: req.body.html, - ses: { - // optional extra arguments for SendRawEmail - Tags: [ - { - Name: "tag_name", - Value: "tag_value", - }, - ], - }, - }, - (err, info) => { - console.log(err || info); - if (info) { - logger.log( - "send-email-success", - "DEBUG", - req.user.email, - null, - { - from: `${req.body.from.name} <${req.body.from.address}>`, - replyTo: req.body.ReplyTo.Email, - to: req.body.to, - cc: req.body.cc, - subject: req.body.subject, - // info, - } - ); - logEmail(req, { - to: req.body.to, - cc: req.body.cc, - subject: req.body.subject, - messageId: info.response, - }); - res.json({ - success: true, //response: info - }); - } else { - logger.log( - "send-email-failure", - "ERROR", - req.user.email, - null, - { - from: `${req.body.from.name} <${req.body.from.address}>`, - replyTo: req.body.ReplyTo.Email, - to: req.body.to, - cc: req.body.cc, - subject: req.body.subject, - error: err, - } - ); - logEmail(req, { - to: req.body.to, - cc: req.body.cc, - subject: req.body.subject, - bodyshopid: req.body.bodyshopid, - }); - res.status(500).json({ success: false, error: err }); - } + error + }); } + }) ); + } + + transporter.sendMail( + { + from: `${req.body.from.name} <${req.body.from.address}>`, + replyTo: req.body.ReplyTo.Email, + to: req.body.to, + cc: req.body.cc, + subject: req.body.subject, + attachments: + [ + ...((req.body.attachments && + req.body.attachments.map((a) => { + return { + filename: a.filename, + path: a.path + }; + })) || + []), + ...downloadedMedia.map((a) => { + return { + path: a + }; + }) + ] || null, + html: req.body.html, + ses: { + // optional extra arguments for SendRawEmail + Tags: [ + { + Name: "tag_name", + Value: "tag_value" + } + ] + } + }, + (err, info) => { + console.log(err || info); + if (info) { + logger.log("send-email-success", "DEBUG", req.user.email, null, { + from: `${req.body.from.name} <${req.body.from.address}>`, + replyTo: req.body.ReplyTo.Email, + to: req.body.to, + cc: req.body.cc, + subject: req.body.subject + // info, + }); + logEmail(req, { + to: req.body.to, + cc: req.body.cc, + subject: req.body.subject, + messageId: info.response + }); + res.json({ + success: true //response: info + }); + } else { + logger.log("send-email-failure", "ERROR", req.user.email, null, { + from: `${req.body.from.name} <${req.body.from.address}>`, + replyTo: req.body.ReplyTo.Email, + to: req.body.to, + cc: req.body.cc, + subject: req.body.subject, + error: err + }); + logEmail(req, { + to: req.body.to, + cc: req.body.cc, + subject: req.body.subject, + bodyshopid: req.body.bodyshopid + }); + res.status(500).json({ success: false, error: err }); + } + } + ); }; async function getImage(imageUrl) { - let image = await axios.get(imageUrl, { responseType: "arraybuffer" }); - let raw = Buffer.from(image.data).toString("base64"); - return "data:" + image.headers["content-type"] + ";base64," + raw; + let image = await axios.get(imageUrl, { responseType: "arraybuffer" }); + let raw = Buffer.from(image.data).toString("base64"); + return "data:" + image.headers["content-type"] + ";base64," + raw; } async function logEmail(req, email) { - try { - const insertresult = await client.request(queries.INSERT_EMAIL_AUDIT, { - email: { - to: email.to, - cc: email.cc, - subject: email.subject, - bodyshopid: req.body.bodyshopid, - useremail: req.user.email, - contents: req.body.html, - jobid: req.body.jobid, - sesmessageid: email.messageId, - status: "Sent", - }, - }); - console.log(insertresult); - } catch (error) { - logger.log("email-log-error", "error", req.user.email, null, { - from: `${req.body.from.name} <${req.body.from.address}>`, - to: req.body.to, - cc: req.body.cc, - subject: req.body.subject, - // info, - }); - } + try { + const insertresult = await client.request(queries.INSERT_EMAIL_AUDIT, { + email: { + to: email.to, + cc: email.cc, + subject: email.subject, + bodyshopid: req.body.bodyshopid, + useremail: req.user.email, + contents: req.body.html, + jobid: req.body.jobid, + sesmessageid: email.messageId, + status: "Sent" + } + }); + console.log(insertresult); + } catch (error) { + logger.log("email-log-error", "error", req.user.email, null, { + from: `${req.body.from.name} <${req.body.from.address}>`, + to: req.body.to, + cc: req.body.cc, + subject: req.body.subject + // info, + }); + } } exports.emailBounce = async function (req, res) { - try { - const body = JSON.parse(req.body); - if (body.Type === "SubscriptionConfirmation") { - logger.log("SNS-message", "DEBUG", "api", null, { - body: req.body, - }); + try { + const body = JSON.parse(req.body); + if (body.Type === "SubscriptionConfirmation") { + logger.log("SNS-message", "DEBUG", "api", null, { + body: req.body + }); + } + const message = JSON.parse(body.Message); + if (message.notificationType === "Bounce") { + let replyTo, subject, messageId; + message.mail.headers.forEach((header) => { + if (header.name === "Reply-To") { + replyTo = header.value; + } else if (header.name === "Subject") { + subject = header.value; } - const message = JSON.parse(body.Message); - if (message.notificationType === "Bounce") { - let replyTo, subject, messageId; - message.mail.headers.forEach((header) => { - if (header.name === "Reply-To") { - replyTo = header.value; - } else if (header.name === "Subject") { - subject = header.value; - } - }); - messageId = message.mail.messageId; - if ( - replyTo === - InstanceManager({ - imex: "noreply@imex.online", - rome: "noreply@romeonline.io", - promanager: "noreply@promanager.web-est.com", - }) - ) { - res.sendStatus(200); - return; - } - //If it's bounced, log it as bounced in audit log. Send an email to the user. - const result = await client.request(queries.UPDATE_EMAIL_AUDIT, { - sesid: messageId, - status: "Bounced", - context: message.bounce?.bouncedRecipients, - }); - transporter.sendMail( - { - from: InstanceMgr({ - imex: `ImEX Online `, - rome: `Rome Online `, - }), - to: replyTo, - //bcc: "patrick@snapt.ca", - subject: `${InstanceMgr({ - imex: "ImEX Online", - rome: "Rome Online", - promanager: "ProManager", - })} Bounced Email - RE: ${subject}`, - text: `${InstanceMgr({ - imex: "ImEX Online", - rome: "Rome Online", - promanager: "ProManager", - })} has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error. + }); + messageId = message.mail.messageId; + if ( + replyTo === + InstanceManager({ + imex: "noreply@imex.online", + rome: "noreply@romeonline.io", + promanager: "noreply@promanager.web-est.com" + }) + ) { + res.sendStatus(200); + return; + } + //If it's bounced, log it as bounced in audit log. Send an email to the user. + const result = await client.request(queries.UPDATE_EMAIL_AUDIT, { + sesid: messageId, + status: "Bounced", + context: message.bounce?.bouncedRecipients + }); + transporter.sendMail( + { + from: InstanceMgr({ + imex: `ImEX Online `, + rome: `Rome Online ` + }), + to: replyTo, + //bcc: "patrick@snapt.ca", + subject: `${InstanceMgr({ + imex: "ImEX Online", + rome: "Rome Online", + promanager: "ProManager" + })} Bounced Email - RE: ${subject}`, + text: `${InstanceMgr({ + imex: "ImEX Online", + rome: "Rome Online", + promanager: "ProManager" + })} has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error. ${body.bounce?.bouncedRecipients.map( - (r) => - `Recipient: ${r.emailAddress} | Status: ${r.action} | Code: ${r.diagnosticCode} + (r) => + `Recipient: ${r.emailAddress} | Status: ${r.action} | Code: ${r.diagnosticCode} ` )} - `, - }, - (err, info) => { - console.log("***", err || info); - } - ); + ` + }, + (err, info) => { + console.log("***", err || info); } - } catch (error) { - logger.log("sns-error", "ERROR", "api", null, { - error: JSON.stringify(error), - }); + ); } - res.sendStatus(200); + } catch (error) { + logger.log("sns-error", "ERROR", "api", null, { + error: JSON.stringify(error) + }); + } + res.sendStatus(200); }; diff --git a/server/firebase/firebase-handler.js b/server/firebase/firebase-handler.js index 7e7abb5b9..509bedce9 100644 --- a/server/firebase/firebase-handler.js +++ b/server/firebase/firebase-handler.js @@ -1,13 +1,10 @@ const admin = require("firebase-admin"); const logger = require("../utils/logger"); const path = require("path"); -const {auth} = require("firebase-admin"); +const { auth } = require("firebase-admin"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const client = require("../graphql-client/graphql-client").client; @@ -15,202 +12,181 @@ const serviceAccount = require(process.env.FIREBASE_ADMINSDK_JSON); const adminEmail = require("../utils/adminEmail"); admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), - databaseURL: process.env.FIREBASE_DATABASE_URL, + credential: admin.credential.cert(serviceAccount), + databaseURL: process.env.FIREBASE_DATABASE_URL }); exports.admin = admin; exports.createUser = async (req, res) => { - logger.log("admin-create-user", "ADMIN", req.user.email, null, { - request: req.body, - ioadmin: true, - }); + logger.log("admin-create-user", "ADMIN", req.user.email, null, { + request: req.body, + ioadmin: true + }); - const {email, displayName, password, shopid, authlevel} = req.body; - try { - const userRecord = await admin - .auth() - .createUser({email, displayName, password}); + const { email, displayName, password, shopid, authlevel } = req.body; + try { + const userRecord = await admin.auth().createUser({ email, displayName, password }); - // See the UserRecord reference doc for the contents of userRecord. + // See the UserRecord reference doc for the contents of userRecord. - const result = await client.request( - ` + const result = await client.request( + ` mutation INSERT_USER($user: users_insert_input!) { insert_users_one(object: $user) { email } } `, - { - user: { - email: email.toLowerCase(), - authid: userRecord.uid, - associations: { - data: [{shopid, authlevel, active: true}], - }, - }, - } - ); + { + user: { + email: email.toLowerCase(), + authid: userRecord.uid, + associations: { + data: [{ shopid, authlevel, active: true }] + } + } + } + ); - res.json({userRecord, result}); - } catch (error) { - logger.log("admin-update-user-error", "ERROR", req.user.email, null, { - error, - }); - res.status(500).json(error); - } + res.json({ userRecord, result }); + } catch (error) { + logger.log("admin-update-user-error", "ERROR", req.user.email, null, { + error + }); + res.status(500).json(error); + } }; exports.updateUser = (req, res) => { - logger.log("admin-update-user", "ADMIN", req.user.email, null, { - request: req.body, - ioadmin: true, + logger.log("admin-update-user", "ADMIN", req.user.email, null, { + request: req.body, + ioadmin: true + }); + + if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) { + logger.log("admin-update-user-unauthorized", "ERROR", req.user.email, null, { + request: req.body, + user: req.user }); + res.sendStatus(404); + return; + } - if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) { - logger.log( - "admin-update-user-unauthorized", - "ERROR", - req.user.email, - null, - { - request: req.body, - user: req.user, - } - ); - res.sendStatus(404); - return; - } + admin + .auth() + .updateUser( + req.body.uid, + req.body.user + // { + // email: "modifiedUser@example.com", + // phoneNumber: "+11234567890", + // emailVerified: true, + // password: "newPassword", + // displayName: "Jane Doe", + // photoURL: "http://www.example.com/12345678/photo.png", + // disabled: true, + // } + ) + .then((userRecord) => { + // See the UserRecord reference doc for the contents of userRecord. - admin - .auth() - .updateUser( - req.body.uid, - req.body.user - // { - // email: "modifiedUser@example.com", - // phoneNumber: "+11234567890", - // emailVerified: true, - // password: "newPassword", - // displayName: "Jane Doe", - // photoURL: "http://www.example.com/12345678/photo.png", - // disabled: true, - // } - ) - .then((userRecord) => { - // See the UserRecord reference doc for the contents of userRecord. - - logger.log("admin-update-user-success", "ADMIN", req.user.email, null, { - userRecord, - ioadmin: true, - }); - res.json(userRecord); - }) - .catch((error) => { - logger.log("admin-update-user-error", "ERROR", req.user.email, null, { - error, - }); - res.status(500).json(error); - }); + logger.log("admin-update-user-success", "ADMIN", req.user.email, null, { + userRecord, + ioadmin: true + }); + res.json(userRecord); + }) + .catch((error) => { + logger.log("admin-update-user-error", "ERROR", req.user.email, null, { + error + }); + res.status(500).json(error); + }); }; exports.getUser = (req, res) => { - logger.log("admin-get-user", "ADMIN", req.user.email, null, { - request: req.body, - ioadmin: true, + logger.log("admin-get-user", "ADMIN", req.user.email, null, { + request: req.body, + ioadmin: true + }); + + if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) { + logger.log("admin-update-user-unauthorized", "ERROR", req.user.email, null, { + request: req.body, + user: req.user }); + res.sendStatus(404); + return; + } - if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) { - logger.log( - "admin-update-user-unauthorized", - "ERROR", - req.user.email, - null, - { - request: req.body, - user: req.user, - } - ); - res.sendStatus(404); - return; - } - - admin - .auth() - .getUser(req.body.uid) - .then((userRecord) => { - res.json(userRecord); - }) - .catch((error) => { - logger.log("admin-get-user-error", "ERROR", req.user.email, null, { - error, - }); - res.status(500).json(error); - }); + admin + .auth() + .getUser(req.body.uid) + .then((userRecord) => { + res.json(userRecord); + }) + .catch((error) => { + logger.log("admin-get-user-error", "ERROR", req.user.email, null, { + error + }); + res.status(500).json(error); + }); }; exports.sendNotification = async (req, res) => { - setTimeout(() => { - // Send a message to the device corresponding to the provided - // registration token. - admin - .messaging() - .send({ - topic: "PRD_PATRICK-messaging", - notification: { - title: `ImEX Online Message - `, - body: "Test Noti.", - //imageUrl: "https://thinkimex.com/img/io-fcm.png", - }, - data: { - type: "messaging-inbound", - conversationid: "e0eb17c3-3a78-4e3f-b932-55ef35aa2297", - text: "Hello. ", - image_path: "", - phone_num: "+16049992002", - }, - }) - .then((response) => { - // Response is a message ID string. - console.log("Successfully sent message:", response); - }) - .catch((error) => { - console.log("Error sending message:", error); - }); + setTimeout(() => { + // Send a message to the device corresponding to the provided + // registration token. + admin + .messaging() + .send({ + topic: "PRD_PATRICK-messaging", + notification: { + title: `ImEX Online Message - `, + body: "Test Noti." + //imageUrl: "https://thinkimex.com/img/io-fcm.png", + }, + data: { + type: "messaging-inbound", + conversationid: "e0eb17c3-3a78-4e3f-b932-55ef35aa2297", + text: "Hello. ", + image_path: "", + phone_num: "+16049992002" + } + }) + .then((response) => { + // Response is a message ID string. + console.log("Successfully sent message:", response); + }) + .catch((error) => { + console.log("Error sending message:", error); + }); - res.sendStatus(200); - }, 500); + res.sendStatus(200); + }, 500); }; exports.subscribe = async (req, res) => { - const result = await admin - .messaging() - .subscribeToTopic( - req.body.fcm_tokens, - `${req.body.imexshopid}-${req.body.type}` - ); + const result = await admin + .messaging() + .subscribeToTopic(req.body.fcm_tokens, `${req.body.imexshopid}-${req.body.type}`); - res.json(result); + res.json(result); }; exports.unsubscribe = async (req, res) => { - try { - const result = await admin - .messaging() - .unsubscribeFromTopic( - req.body.fcm_tokens, - `${req.body.imexshopid}-${req.body.type}` - ); + try { + const result = await admin + .messaging() + .unsubscribeFromTopic(req.body.fcm_tokens, `${req.body.imexshopid}-${req.body.type}`); - res.json(result); - } catch (error) { - res.sendStatus(500); - } + res.json(result); + } catch (error) { + res.sendStatus(500); + } }; - //Admin claims code. // const uid = "JEqqYlsadwPEXIiyRBR55fflfko1"; diff --git a/server/graphql-client/graphql-client.js b/server/graphql-client/graphql-client.js index 0f0bd7aca..069386b73 100644 --- a/server/graphql-client/graphql-client.js +++ b/server/graphql-client/graphql-client.js @@ -1,10 +1,7 @@ const GraphQLClient = require("graphql-request").GraphQLClient; const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); //New bug introduced with Graphql Request. // https://github.com/prisma-labs/graphql-request/issues/206 @@ -12,9 +9,9 @@ require("dotenv").config({ // global.Headers = global.Headers || Headers; exports.client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - "x-hasura-admin-secret": process.env.HASURA_ADMIN_SECRET, - }, + headers: { + "x-hasura-admin-secret": process.env.HASURA_ADMIN_SECRET + } }); exports.unauthclient = new GraphQLClient(process.env.GRAPHQL_ENDPOINT); diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 4b7f0aa32..f5cbdb3b1 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -1987,20 +1987,20 @@ exports.UPDATE_OLD_TRANSITION = `mutation UPDATE_OLD_TRANSITION($jobid: uuid!, $ }`; exports.INSERT_NEW_TRANSITION = ( - includeOldTransition + includeOldTransition ) => `mutation INSERT_NEW_TRANSITION($newTransition: transitions_insert_input!, ${ - includeOldTransition ? `$oldTransitionId: uuid!, $duration: numeric` : "" + includeOldTransition ? `$oldTransitionId: uuid!, $duration: numeric` : "" }) { insert_transitions_one(object: $newTransition) { id } ${ includeOldTransition - ? `update_transitions(where: {id: {_eq: $oldTransitionId}}, _set: {duration: $duration}) { + ? `update_transitions(where: {id: {_eq: $oldTransitionId}}, _set: {duration: $duration}) { affected_rows }` - : "" -} + : "" + } }`; exports.QUERY_JOB_ID_MIXDATA = `query QUERY_JOB_ID_MIXDATA($roNumbers: [String!]!) { diff --git a/server/intellipay/aws-secrets-manager.js b/server/intellipay/aws-secrets-manager.js index 2d71bdf13..d5ab93718 100644 --- a/server/intellipay/aws-secrets-manager.js +++ b/server/intellipay/aws-secrets-manager.js @@ -3,44 +3,43 @@ const awsSecretManager = require("@aws-sdk/client-secrets-manager"); class SecretsManager { - /** - * Uses AWS Secrets Manager to retrieve a secret - */ - static async getSecret(secretName, region) { - const config = {region: region}; - let secretsManager = new awsSecretManager.SecretsManager(config); - try { - let secretValue = await secretsManager - .getSecretValue({SecretId: secretName}); - if ("SecretString" in secretValue) { - return secretValue.SecretString; - } else { - let buff = new Buffer(secretValue.SecretBinary, "base64"); - return buff.toString("ascii"); - } - } catch (err) { - if (err.code === "DecryptionFailureException") - // Secrets Manager can't decrypt the protected secret text using the provided KMS key. - // Deal with the exception here, and/or rethrow at your discretion. - throw err; - else if (err.code === "InternalServiceErrorException") - // An error occurred on the server side. - // Deal with the exception here, and/or rethrow at your discretion. - throw err; - else if (err.code === "InvalidParameterException") - // You provided an invalid value for a parameter. - // Deal with the exception here, and/or rethrow at your discretion. - throw err; - else if (err.code === "InvalidRequestException") - // You provided a parameter value that is not valid for the current state of the resource. - // Deal with the exception here, and/or rethrow at your discretion. - throw err; - else if (err.code === "ResourceNotFoundException") - // We can't find the resource that you asked for. - // Deal with the exception here, and/or rethrow at your discretion. - throw err; - } + /** + * Uses AWS Secrets Manager to retrieve a secret + */ + static async getSecret(secretName, region) { + const config = { region: region }; + let secretsManager = new awsSecretManager.SecretsManager(config); + try { + let secretValue = await secretsManager.getSecretValue({ SecretId: secretName }); + if ("SecretString" in secretValue) { + return secretValue.SecretString; + } else { + let buff = new Buffer(secretValue.SecretBinary, "base64"); + return buff.toString("ascii"); + } + } catch (err) { + if (err.code === "DecryptionFailureException") + // Secrets Manager can't decrypt the protected secret text using the provided KMS key. + // Deal with the exception here, and/or rethrow at your discretion. + throw err; + else if (err.code === "InternalServiceErrorException") + // An error occurred on the server side. + // Deal with the exception here, and/or rethrow at your discretion. + throw err; + else if (err.code === "InvalidParameterException") + // You provided an invalid value for a parameter. + // Deal with the exception here, and/or rethrow at your discretion. + throw err; + else if (err.code === "InvalidRequestException") + // You provided a parameter value that is not valid for the current state of the resource. + // Deal with the exception here, and/or rethrow at your discretion. + throw err; + else if (err.code === "ResourceNotFoundException") + // We can't find the resource that you asked for. + // Deal with the exception here, and/or rethrow at your discretion. + throw err; } + } } module.exports = SecretsManager; diff --git a/server/intellipay/intellipay.js b/server/intellipay/intellipay.js index f2d57de16..23d70478e 100644 --- a/server/intellipay/intellipay.js +++ b/server/intellipay/intellipay.js @@ -8,207 +8,187 @@ const moment = require("moment"); const logger = require("../utils/logger"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const domain = process.env.NODE_ENV ? "secure" : "test"; -const { - SecretsManagerClient, - GetSecretValueCommand, -} = require("@aws-sdk/client-secrets-manager"); +const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager"); const client = new SecretsManagerClient({ - region: "ca-central-1", + region: "ca-central-1" }); const gqlClient = require("../graphql-client/graphql-client").client; const getShopCredentials = async (bodyshop) => { - // Development only - if (process.env.NODE_ENV === undefined) { - return { - merchantkey: process.env.INTELLIPAY_MERCHANTKEY, - apikey: process.env.INTELLIPAY_APIKEY, - }; - } + // Development only + if (process.env.NODE_ENV === undefined) { + return { + merchantkey: process.env.INTELLIPAY_MERCHANTKEY, + apikey: process.env.INTELLIPAY_APIKEY + }; + } - // Production code - if (bodyshop?.imexshopid) { - try { - const secret = await client.send( - new GetSecretValueCommand({ - SecretId: `intellipay-credentials-${bodyshop.imexshopid}`, - VersionStage: "AWSCURRENT", // VersionStage defaults to AWSCURRENT if unspecified - }) - ); - return JSON.parse(secret.SecretString); - } catch (error) { - return { - error: error.message, - }; - } + // Production code + if (bodyshop?.imexshopid) { + try { + const secret = await client.send( + new GetSecretValueCommand({ + SecretId: `intellipay-credentials-${bodyshop.imexshopid}`, + VersionStage: "AWSCURRENT" // VersionStage defaults to AWSCURRENT if unspecified + }) + ); + return JSON.parse(secret.SecretString); + } catch (error) { + return { + error: error.message + }; } + } }; exports.lightbox_credentials = async (req, res) => { - logger.log( - "intellipay-lightbox-credentials", - "DEBUG", - req.user?.email, - null, - null - ); + logger.log("intellipay-lightbox-credentials", "DEBUG", req.user?.email, null, null); - const shopCredentials = await getShopCredentials(req.body.bodyshop); + const shopCredentials = await getShopCredentials(req.body.bodyshop); - if (shopCredentials.error) { - res.json(shopCredentials); - return; - } - try { - const options = { - method: "POST", - headers: {"content-type": "application/x-www-form-urlencoded"}, - data: qs.stringify({ - ...shopCredentials, - operatingenv: "businessattended", - }), - url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal${ - req.body.refresh ? "_refresh" : "" - }`, //autoterminal_refresh - }; + if (shopCredentials.error) { + res.json(shopCredentials); + return; + } + try { + const options = { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" }, + data: qs.stringify({ + ...shopCredentials, + operatingenv: "businessattended" + }), + url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal${req.body.refresh ? "_refresh" : ""}` //autoterminal_refresh + }; - const response = await axios(options); + const response = await axios(options); - res.send(response.data); - } catch (error) { - console.log(error); - logger.log( - "intellipay-lightbox-credentials-error", - "ERROR", - req.user?.email, - null, - {error: JSON.stringify(error)} - ); - res.json({error}); - } + res.send(response.data); + } catch (error) { + console.log(error); + logger.log("intellipay-lightbox-credentials-error", "ERROR", req.user?.email, null, { + error: JSON.stringify(error) + }); + res.json({ error }); + } }; exports.payment_refund = async (req, res) => { - logger.log("intellipay-refund", "DEBUG", req.user?.email, null, null); + logger.log("intellipay-refund", "DEBUG", req.user?.email, null, null); - const shopCredentials = await getShopCredentials(req.body.bodyshop); + const shopCredentials = await getShopCredentials(req.body.bodyshop); - try { - const options = { - method: "POST", - headers: {"content-type": "application/x-www-form-urlencoded"}, + try { + const options = { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" }, - data: qs.stringify({ - method: "payment_refund", - ...shopCredentials, - paymentid: req.body.paymentid, - amount: req.body.amount, - }), - url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund`, - }; + data: qs.stringify({ + method: "payment_refund", + ...shopCredentials, + paymentid: req.body.paymentid, + amount: req.body.amount + }), + url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund` + }; - const response = await axios(options); + const response = await axios(options); - res.send(response.data); - } catch (error) { - console.log(error); - logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, { - error: JSON.stringify(error), - }); - res.json({error}); - } + res.send(response.data); + } catch (error) { + console.log(error); + logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, { + error: JSON.stringify(error) + }); + res.json({ error }); + } }; exports.generate_payment_url = async (req, res) => { - logger.log("intellipay-payment-url", "DEBUG", req.user?.email, null, null); - const shopCredentials = await getShopCredentials(req.body.bodyshop); + logger.log("intellipay-payment-url", "DEBUG", req.user?.email, null, null); + 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({ - ...shopCredentials, - //...req.body, - amount: Dinero({amount: Math.round(req.body.amount * 100)}).toFormat( - "0.00" - ), - account: req.body.account, - invoice: req.body.invoice, - createshorturl: true, - //The postback URL is set at the CP teller global terminal settings page. - }), - url: `https://${domain}.cpteller.com/api/custapi.cfc?method=generate_lightbox_url`, - }; + try { + const options = { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" }, + //TODO: Move these to environment variables/database. + data: qs.stringify({ + ...shopCredentials, + //...req.body, + amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat("0.00"), + account: req.body.account, + invoice: req.body.invoice, + createshorturl: true + //The postback URL is set at the CP teller global terminal settings page. + }), + url: `https://${domain}.cpteller.com/api/custapi.cfc?method=generate_lightbox_url` + }; - const response = await axios(options); + const response = await axios(options); - res.send(response.data); - } catch (error) { - console.log(error); - logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, { - error: JSON.stringify(error), - }); - res.json({error}); - } + res.send(response.data); + } catch (error) { + console.log(error); + logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, { + error: JSON.stringify(error) + }); + res.json({ error }); + } }; exports.postback = async (req, res) => { - logger.log("intellipay-postback", "ERROR", req.user?.email, null, req.body); - const {body: values} = req; + logger.log("intellipay-postback", "ERROR", req.user?.email, null, req.body); + const { body: values } = req; - if (!values.invoice) { - res.sendStatus(200); - return; - } - // TODO query job by account name - const job = await gqlClient.request(queries.GET_JOB_BY_PK, { - id: values.invoice, + if (!values.invoice) { + res.sendStatus(200); + return; + } + // TODO query job by account name + const job = await gqlClient.request(queries.GET_JOB_BY_PK, { + id: values.invoice + }); + // TODO add mutation to database + + try { + const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, { + paymentInput: { + amount: values.total, + transactionid: `C00 ${values.authcode}`, + payer: "Customer", + type: values.cardtype, + jobid: values.invoice, + date: moment(Date.now()) + } }); - // TODO add mutation to database - try { - const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, { - paymentInput: { - amount: values.total, - transactionid: `C00 ${values.authcode}`, - payer: "Customer", - type: values.cardtype, - jobid: values.invoice, - date: moment(Date.now()), - }, - }); + await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, { + paymentResponse: { + amount: values.total, + bodyshopid: job.jobs_by_pk.shopid, + paymentid: paymentResult.id, + jobid: values.invoice, + declinereason: "Approved", + ext_paymentid: values.paymentid, + successful: true, + response: values + } + }); - await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, { - paymentResponse: { - amount: values.total, - bodyshopid: job.jobs_by_pk.shopid, - paymentid: paymentResult.id, - jobid: values.invoice, - declinereason: "Approved", - ext_paymentid: values.paymentid, - successful: true, - response: values, - }, - }); - - res.send({message: "Postback Successful"}); - } catch (error) { - logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, { - error: JSON.stringify(error), - body: req.body, - }); - res.status(400).json({succesful: false, error: error.message}); - } + res.send({ message: "Postback Successful" }); + } catch (error) { + logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, { + error: JSON.stringify(error), + body: req.body + }); + res.status(400).json({ succesful: false, error: error.message }); + } }; diff --git a/server/ioevent/ioevent.js b/server/ioevent/ioevent.js index 46d5564ba..16c5ad63a 100644 --- a/server/ioevent/ioevent.js +++ b/server/ioevent/ioevent.js @@ -4,47 +4,35 @@ const path = require("path"); const logger = require("../utils/logger"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); exports.default = async (req, res) => { - const { - useremail, - bodyshopid, - operationName, - variables, - env, + const { useremail, bodyshopid, operationName, variables, env, time, dbevent, user } = req.body; + + try { + await client.request(queries.INSERT_IOEVENT, { + event: { + operationname: operationName, time, dbevent, - user, - } = req.body; - - try { - await client.request(queries.INSERT_IOEVENT, { - event: { - operationname: operationName, - time, - dbevent, - env, - variables, - bodyshopid, - useremail, - }, - }); - res.sendStatus(200); - } catch (error) { - logger.log("ioevent-error", "trace", user, null, { - operationname: operationName, - time, - dbevent, - env, - variables, - bodyshopid, - useremail, - }); - res.sendStatus(200); - } + env, + variables, + bodyshopid, + useremail + } + }); + res.sendStatus(200); + } catch (error) { + logger.log("ioevent-error", "trace", user, null, { + operationname: operationName, + time, + dbevent, + env, + variables, + bodyshopid, + useremail + }); + res.sendStatus(200); + } }; diff --git a/server/job/job-costing.js b/server/job/job-costing.js index b1706edfa..df00f093c 100644 --- a/server/job/job-costing.js +++ b/server/job/job-costing.js @@ -1,751 +1,75 @@ const _ = require("lodash"); const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); -const logger = require('../utils/logger'); -const {DiscountNotAlreadyCounted} = require("./job-totals"); +const logger = require("../utils/logger"); +const { DiscountNotAlreadyCounted } = require("./job-totals"); // Dinero.defaultCurrency = "USD"; // Dinero.globalLocale = "en-CA"; Dinero.globalRoundingMode = "HALF_EVEN"; async function JobCosting(req, res) { - const {jobid} = req.body; + const { jobid } = req.body; - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; - logger.log("job-costing-start", "DEBUG", req.user.email, jobid, null); + logger.log("job-costing-start", "DEBUG", req.user.email, jobid, null); - try { - const resp = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QUERY_JOB_COSTING_DETAILS, { - id: jobid, - }); + try { + const resp = await client.setHeaders({ Authorization: BearerToken }).request(queries.QUERY_JOB_COSTING_DETAILS, { + id: jobid + }); - const ret = GenerateCostingData(resp.jobs_by_pk); + const ret = GenerateCostingData(resp.jobs_by_pk); - res.status(200).json(ret); - } catch (error) { - logger.log("job-costing-error", "ERROR", req.user.email, jobid, { - message: error.message, - stack: error.stack, - }); + res.status(200).json(ret); + } catch (error) { + logger.log("job-costing-error", "ERROR", req.user.email, jobid, { + message: error.message, + stack: error.stack + }); - res.status(400).send(JSON.stringify(error)); - } + res.status(400).send(JSON.stringify(error)); + } } async function JobCostingMulti(req, res) { - const {jobids} = req.body; + const { jobids } = req.body; - const logger = req.logger; - const BearerToken = req.BearerToken - const client = req.userGraphQLClient; + const logger = req.logger; + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; - logger.log("job-costing-multi-start", "DEBUG", req.user.email, jobids, null); + logger.log("job-costing-multi-start", "DEBUG", req.user.email, jobids, null); - try { - const resp = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QUERY_JOB_COSTING_DETAILS_MULTI, { - ids: jobids, - }); + try { + const resp = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.QUERY_JOB_COSTING_DETAILS_MULTI, { + ids: jobids + }); - const multiSummary = { - costCenterData: [], - summaryData: { - totalLaborSales: Dinero({amount: 0}), - totalPartsSales: Dinero({amount: 0}), - totalAdditionalSales: Dinero({amount: 0}), - totalSubletSales: Dinero({amount: 0}), - totalSales: Dinero({amount: 0}), - totalLaborCost: Dinero({amount: 0}), - totalPartsCost: Dinero({amount: 0}), - totalAdditionalCost: Dinero({amount: 0}), - totalSubletCost: Dinero({amount: 0}), - totalCost: Dinero({amount: 0}), - gpdollars: Dinero({amount: 0}), - gppercent: null, - gppercentFormatted: null, - totalLaborGp: Dinero({amount: 0}), - totalPartsGp: Dinero({amount: 0}), - totalAdditionalGp: Dinero({amount: 0}), - totalSubletGp: Dinero({amount: 0}), - totalLaborGppercent: null, - totalLaborGppercentFormatted: null, - totalPartsGppercent: null, - totalPartsGppercentFormatted: null, - totalAdditionalGppercent: null, - totalAdditionalGppercentFormatted: null, - totalSubletGppercent: null, - totalSubletGppercentFormatted: null, - }, - }; - - const ret = {}; - resp.jobs.map((job) => { - const costingData = GenerateCostingData(job); - ret[job.id] = costingData; - - //Merge on a cost center basis. - - costingData.costCenterData.forEach((c) => { - //Find the Cost Center if it exists. - - const CostCenterIndex = multiSummary.costCenterData.findIndex( - (x) => x.cost_center === c.cost_center - ); - - if (CostCenterIndex >= 0) { - //Add it in place - multiSummary.costCenterData[CostCenterIndex] = { - ...multiSummary.costCenterData[CostCenterIndex], - sale_labor_dinero: multiSummary.costCenterData[ - CostCenterIndex - ].sale_labor_dinero.add(c.sale_labor_dinero), - sale_parts_dinero: multiSummary.costCenterData[ - CostCenterIndex - ].sale_parts_dinero.add(c.sale_parts_dinero), - sale_additional_dinero: multiSummary.costCenterData[ - CostCenterIndex - ].sale_additional_dinero.add(c.sale_additional_dinero), - sale_sublet_dinero: multiSummary.costCenterData[ - CostCenterIndex - ].sale_sublet_dinero.add(c.sale_sublet_dinero), - cost_labor_dinero: multiSummary.costCenterData[ - CostCenterIndex - ].cost_labor_dinero.add(c.cost_labor_dinero), - cost_parts_dinero: multiSummary.costCenterData[ - CostCenterIndex - ].cost_parts_dinero.add(c.cost_parts_dinero), - cost_additional_dinero: multiSummary.costCenterData[ - CostCenterIndex - ].cost_additional_dinero.add(c.cost_additional_dinero), - cost_sublet_dinero: multiSummary.costCenterData[ - CostCenterIndex - ].cost_sublet_dinero.add(c.cost_sublet_dinero), - gpdollars_dinero: multiSummary.costCenterData[ - CostCenterIndex - ].gpdollars_dinero.add(c.gpdollars_dinero), - costs_dinero: multiSummary.costCenterData[ - CostCenterIndex - ].costs_dinero.add(c.costs_dinero), - sales_dinero: multiSummary.costCenterData[ - CostCenterIndex - ].sales_dinero.add(c.sales_dinero), - }; - } else { - //Add it to the list instead. - multiSummary.costCenterData.push(c); - } - }); - - //Add all summary data. - multiSummary.summaryData.totalPartsSales = - multiSummary.summaryData.totalPartsSales.add( - costingData.summaryData.totalPartsSales - ); - multiSummary.summaryData.totalAdditionalSales = - multiSummary.summaryData.totalAdditionalSales.add( - costingData.summaryData.totalAdditionalSales - ); - multiSummary.summaryData.totalSubletSales = - multiSummary.summaryData.totalSubletSales.add( - costingData.summaryData.totalSubletSales - ); - 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.totalAdditionalCost = - multiSummary.summaryData.totalAdditionalCost.add( - costingData.summaryData.totalAdditionalCost - ); - multiSummary.summaryData.totalSubletCost = - multiSummary.summaryData.totalSubletCost.add( - costingData.summaryData.totalSubletCost - ); - multiSummary.summaryData.totalCost = - multiSummary.summaryData.totalCost.add( - costingData.summaryData.totalCost - ); - multiSummary.summaryData.gpdollars = - multiSummary.summaryData.gpdollars.add( - costingData.summaryData.gpdollars - ); - - multiSummary.summaryData.totalLaborGp = - multiSummary.summaryData.totalLaborGp.add( - costingData.summaryData.totalLaborGp - ); - multiSummary.summaryData.totalPartsGp = - multiSummary.summaryData.totalPartsGp.add( - costingData.summaryData.totalPartsGp - ); - multiSummary.summaryData.totalAdditionalGp = - multiSummary.summaryData.totalAdditionalGp.add( - costingData.summaryData.totalAdditionalGp - ); - multiSummary.summaryData.totalSubletGp = - multiSummary.summaryData.totalSubletGp.add( - costingData.summaryData.totalSubletGp - ); - - //Take the summary data & add it to total summary data. - }); - - //For each center, recalculate and toFormat() the values. - - multiSummary.summaryData.totalLaborGppercent = ( - (multiSummary.summaryData.totalLaborGp.getAmount() / - multiSummary.summaryData.totalLaborSales.getAmount()) * - 100 - ).toFixed(1); - multiSummary.summaryData.totalLaborGppercentFormatted = formatGpPercent( - multiSummary.summaryData.totalLaborGppercent - ); - - multiSummary.summaryData.totalPartsGppercent = ( - (multiSummary.summaryData.totalPartsGp.getAmount() / - multiSummary.summaryData.totalPartsSales.getAmount()) * - 100 - ).toFixed(1); - - multiSummary.summaryData.totalPartsGppercentFormatted = formatGpPercent( - multiSummary.summaryData.totalPartsGppercent - ); - - multiSummary.summaryData.totalAdditionalGppercent = ( - (multiSummary.summaryData.totalAdditionalGp.getAmount() / - multiSummary.summaryData.totalAdditionalSales.getAmount()) * - 100 - ).toFixed(1); - - multiSummary.summaryData.totalAdditionalGppercentFormatted = - formatGpPercent(multiSummary.summaryData.totalAdditionalGppercent); - - multiSummary.summaryData.totalSubletGppercent = ( - (multiSummary.summaryData.totalSubletGp.getAmount() / - multiSummary.summaryData.totalSubletSales.getAmount()) * - 100 - ).toFixed(1); - - multiSummary.summaryData.totalSubletGppercentFormatted = formatGpPercent( - multiSummary.summaryData.totalSubletGppercent - ); - - multiSummary.summaryData.gppercent = ( - (multiSummary.summaryData.gpdollars.getAmount() / - multiSummary.summaryData.totalSales.getAmount()) * - 100 - ).toFixed(1); - - multiSummary.summaryData.gppercentFormatted = formatGpPercent( - multiSummary.summaryData.gppercent - ); - - const finalCostingdata = multiSummary.costCenterData.map((c) => { - return { - ...c, - sale_labor: c.sale_labor_dinero && c.sale_labor_dinero.toFormat(), - sale_parts: c.sale_parts_dinero && c.sale_parts_dinero.toFormat(), - sale_additional: - c.sale_additional_dinero && c.sale_additional_dinero.toFormat(), - sale_sublet: c.sale_sublet_dinero && c.sale_sublet_dinero.toFormat(), - sales: c.sales_dinero.toFormat(), - cost_parts: c.cost_parts_dinero && c.cost_parts_dinero.toFormat(), - cost_labor: c.cost_labor_dinero && c.cost_labor_dinero.toFormat(), - cost_additional: - c.cost_additional_dinero && c.cost_additional_dinero.toFormat(), - cost_sublet: c.cost_sublet_dinero && c.cost_sublet_dinero.toFormat(), - costs: c.costs_dinero.toFormat(), - gpdollars: c.gpdollars_dinero.toFormat(), - gppercent: formatGpPercent( - ( - (c.gpdollars_dinero.getAmount() / c.sales_dinero.getAmount()) * - 100 - ).toFixed(1) - ), - }; - }); - - //Calculate thte total gross profit percentages. - - res.status(200).json({ - allCostCenterData: finalCostingdata, - allSummaryData: multiSummary.summaryData, - data: ret, - }); - } catch (error) { - logger.log("job-costing-multi-error", "ERROR", req.user.email, [jobids], { - message: error.message, - stack: error.stack, - }); - res.status(400).send(error); - } -} - -function GenerateCostingData(job) { - const defaultProfits = - job.bodyshop.md_responsibility_centers.defaults.profits; - const allCenters = _.union( - job.bodyshop.md_responsibility_centers.profits.map((p) => p.name), - job.bodyshop.md_responsibility_centers.costs.map((p) => p.name), - ["Unknown"] - ); - - const materialsHours = {mapaHrs: 0, mashHrs: 0}; - let hasMapaLine = false; - let hasMashLine = false; - - //Massage the data. - const jobLineTotalsByProfitCenter = - job && - job.joblines.reduce( - (acc, val) => { - //Parts Lines - if (val.db_ref === "936008") { - //If either of these DB REFs change, they also need to change in job-totals/job-costing calculations. - hasMapaLine = true; - } - if (val.db_ref === "936007") { - hasMashLine = true; - } - if (val.mod_lbr_ty) { - const laborProfitCenter = - val.profitcenter_labor || - defaultProfits[val.mod_lbr_ty] || - "Unknown"; - - if (laborProfitCenter === "Unknown") - console.log("Unknown type", val.line_desc, val.mod_lbr_ty); - - const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`; - - const laborAmount = Dinero({ - amount: Math.round((job[rateName] || 0) * 100), - }).multiply(val.mod_lb_hrs || 0); - if (!acc.labor[laborProfitCenter]) - acc.labor[laborProfitCenter] = Dinero(); - acc.labor[laborProfitCenter] = - acc.labor[laborProfitCenter].add(laborAmount); - - if ( - val.mod_lb_hrs === 0 && - val.act_price > 0 && - val.lbr_op === "OP14" - ) { - //Scenario where SGI may pay out hours using a part price. - acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add( - Dinero({ - amount: Math.round((val.act_price || 0) * 100), - }).multiply(val.part_qty) - ); - } - - if (val.mod_lbr_ty === "LAR") { - materialsHours.mapaHrs += val.mod_lb_hrs || 0; - } - if (val.mod_lbr_ty !== "LAR") { - materialsHours.mashHrs += val.mod_lb_hrs || 0; - } - } - - if ( - val.part_type && - val.part_type !== "PAE" && - val.part_type !== "PAS" && - val.part_type !== "PASL" - ) { - const partsProfitCenter = - val.profitcenter_part || defaultProfits[val.part_type] || "Unknown"; - - if (partsProfitCenter === "Unknown") - console.log("Unknown type", val.line_desc, val.part_type); - - if (!partsProfitCenter) - console.log( - "Unknown cost/profit center mapping for parts.", - val.line_desc, - val.part_type - ); - const partsAmount = Dinero({ - amount: Math.round((val.act_price || 0) * 100), - }) - .multiply(val.part_qty || 1) - .add( - ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || - (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) && - DiscountNotAlreadyCounted(val, job.joblines) - ? val.prt_dsmk_m - ? Dinero({amount: Math.round(val.prt_dsmk_m * 100)}) - : Dinero({ - amount: Math.round(val.act_price * 100), - }) - .multiply(val.part_qty || 0) - .percentage(Math.abs(val.prt_dsmk_p || 0)) - .multiply(val.prt_dsmk_p > 0 ? 1 : -1) - : Dinero() - ); - if (!acc.parts[partsProfitCenter]) - acc.parts[partsProfitCenter] = Dinero(); - acc.parts[partsProfitCenter] = - acc.parts[partsProfitCenter].add(partsAmount); - } - if ( - val.part_type && - val.part_type !== "PAE" && - (val.part_type === "PAS" || val.part_type === "PASL") - ) { - const partsProfitCenter = - val.profitcenter_part || defaultProfits[val.part_type] || "Unknown"; - - if (partsProfitCenter === "Unknown") - console.log("Unknown type", val.line_desc, val.part_type); - - if (!partsProfitCenter) - console.log( - "Unknown cost/profit center mapping for sublet.", - val.line_desc, - val.part_type - ); - const partsAmount = Dinero({ - amount: Math.round((val.act_price || 0) * 100), - }) - .multiply(val.part_qty || 1) - .add( - ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || - (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) && - DiscountNotAlreadyCounted(val, job.joblines) - ? val.prt_dsmk_m - ? Dinero({amount: Math.round(val.prt_dsmk_m * 100)}) - : Dinero({ - amount: Math.round(val.act_price * 100), - }) - .multiply(val.part_qty || 0) - .percentage(Math.abs(val.prt_dsmk_p || 0)) - .multiply(val.prt_dsmk_p > 0 ? 1 : -1) - : Dinero() - ); - if (!acc.sublet[partsProfitCenter]) - acc.sublet[partsProfitCenter] = Dinero(); - acc.sublet[partsProfitCenter] = - acc.sublet[partsProfitCenter].add(partsAmount); - } - - //To deal with additional costs. - if (!val.part_type && !val.mod_lbr_ty) { - //Does it already have a defined profit center? - //If so, use it, otherwise try to use the same from the auto-allocate logic in IO app jobs-close-auto-allocate. - const partsProfitCenter = - val.profitcenter_part || - getAdditionalCostCenter(val, defaultProfits) || - "Unknown"; - - if (partsProfitCenter === "Unknown") { - console.log("Unknown type", val.line_desc, val.part_type); - } - const partsAmount = Dinero({ - amount: Math.round((val.act_price || 0) * 100), - }) - .multiply(val.part_qty || 1) - .add( - ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || - (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) && - DiscountNotAlreadyCounted(val, job.joblines) - ? val.prt_dsmk_m - ? Dinero({amount: Math.round(val.prt_dsmk_m * 100)}) - : Dinero({ - amount: Math.round(val.act_price * 100), - }) - .multiply(val.part_qty || 0) - .percentage(Math.abs(val.prt_dsmk_p || 0)) - .multiply(val.prt_dsmk_p > 0 ? 1 : -1) - : Dinero() - ); - - if (!acc.additional[partsProfitCenter]) - acc.additional[partsProfitCenter] = Dinero(); - acc.additional[partsProfitCenter] = - acc.additional[partsProfitCenter].add(partsAmount); - } - - return acc; - }, - {parts: {}, labor: {}, additional: {}, sublet: {}} - ); - - if (!hasMapaLine) { - if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]]) - jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = Dinero(); - jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = - jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]].add( - Dinero({ - amount: Math.round((job.rate_mapa || 0) * 100), - }).multiply(materialsHours.mapaHrs || 0) - ); - } - if (!hasMashLine) { - if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]]) - jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = Dinero(); - jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = - jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]].add( - Dinero({ - amount: Math.round((job.rate_mash || 0) * 100), - }).multiply(materialsHours.mashHrs || 0) - ); - } - - //Is it a DMS Setup? - const selectedDmsAllocationConfig = - (job.bodyshop.md_responsibility_centers.dms_defaults && - job.bodyshop.md_responsibility_centers.dms_defaults.find( - (d) => d.name === job.dms_allocation - )) || - job.bodyshop.md_responsibility_centers.defaults; - - const billTotalsByCostCenters = job.bills.reduce( - (bill_acc, bill_val) => { - //At the bill level. - bill_val.billlines.map((line_val) => { - //At the bill line level. - if (job.bodyshop.pbs_serialnumber || job.bodyshop.cdk_dealerid) { - if ( - !bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] - ) - bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = - Dinero(); - - bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = - bill_acc[ - selectedDmsAllocationConfig.costs[line_val.cost_center] - ].add( - Dinero({ - amount: Math.round((line_val.actual_cost || 0) * 100), - }) - .multiply(line_val.quantity) - .multiply(bill_val.is_credit_memo ? -1 : 1) - ); - } else { - const isSubletCostCenter = - line_val.cost_center === - job.bodyshop.md_responsibility_centers.defaults.costs.PAS || - line_val.cost_center === - job.bodyshop.md_responsibility_centers.defaults.costs.PASL; - - const isAdditionalCostCenter = - // line_val.cost_center === - // job.bodyshop.md_responsibility_centers.defaults.costs.PAS || - // line_val.cost_center === - // job.bodyshop.md_responsibility_centers.defaults.costs.PASL || - line_val.cost_center === - job.bodyshop.md_responsibility_centers.defaults.costs.TOW || - line_val.cost_center === - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA || - line_val.cost_center === - job.bodyshop.md_responsibility_centers.defaults.costs.MASH; - - if (isAdditionalCostCenter) { - if (!bill_acc.additionalCosts[line_val.cost_center]) - bill_acc.additionalCosts[line_val.cost_center] = Dinero(); - - bill_acc.additionalCosts[line_val.cost_center] = - bill_acc.additionalCosts[line_val.cost_center].add( - Dinero({ - amount: Math.round((line_val.actual_cost || 0) * 100), - }) - .multiply(line_val.quantity) - .multiply(bill_val.is_credit_memo ? -1 : 1) - ); - } else if (isSubletCostCenter) { - if (!bill_acc.subletCosts[line_val.cost_center]) - bill_acc.subletCosts[line_val.cost_center] = Dinero(); - - bill_acc.subletCosts[line_val.cost_center] = bill_acc.subletCosts[ - line_val.cost_center - ].add( - Dinero({ - amount: Math.round((line_val.actual_cost || 0) * 100), - }) - .multiply(line_val.quantity) - .multiply(bill_val.is_credit_memo ? -1 : 1) - ); - } else { - if (!bill_acc[line_val.cost_center]) - bill_acc[line_val.cost_center] = Dinero(); - - bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add( - Dinero({ - amount: Math.round((line_val.actual_cost || 0) * 100), - }) - .multiply(line_val.quantity) - .multiply(bill_val.is_credit_memo ? -1 : 1) - ); - } - } - - return null; - }); - return bill_acc; - }, - {additionalCosts: {}, subletCosts: {}} - ); - - //If the hourly rates for job costing are set, add them in. - - if ( - job.bodyshop.jc_hourly_rates && - (job.bodyshop.jc_hourly_rates.mapa || - typeof job.bodyshop.jc_hourly_rates.mapa === "number" || - isNaN(job.bodyshop.jc_hourly_rates.mapa) === false) - ) { - if ( - !billTotalsByCostCenters.additionalCosts[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] - ) - billTotalsByCostCenters.additionalCosts[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = Dinero(); - if (job.bodyshop.use_paint_scale_data === true) { - if (job.mixdata.length > 0) { - billTotalsByCostCenters.additionalCosts[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = Dinero({ - amount: Math.round( - ((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100 - ), - }); - } else { - billTotalsByCostCenters.additionalCosts[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = billTotalsByCostCenters.additionalCosts[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ].add( - Dinero({ - amount: Math.round( - (job.bodyshop.jc_hourly_rates && - job.bodyshop.jc_hourly_rates.mapa * 100) || - 0 - ), - }).multiply(materialsHours.mapaHrs) - ); - } - } else { - billTotalsByCostCenters.additionalCosts[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ] = billTotalsByCostCenters.additionalCosts[ - job.bodyshop.md_responsibility_centers.defaults.costs.MAPA - ].add( - Dinero({ - amount: Math.round( - (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.additionalCosts[ - job.bodyshop.md_responsibility_centers.defaults.costs.MASH - ] - ) - billTotalsByCostCenters.additionalCosts[ - job.bodyshop.md_responsibility_centers.defaults.costs.MASH - ] = Dinero(); - billTotalsByCostCenters.additionalCosts[ - job.bodyshop.md_responsibility_centers.defaults.costs.MASH - ] = billTotalsByCostCenters.additionalCosts[ - job.bodyshop.md_responsibility_centers.defaults.costs.MASH - ].add( - Dinero({ - amount: Math.round( - (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. - - if (job.bodyshop.pbs_serialnumber || job.bodyshop.cdk_dealerid) { - if ( - !ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]] - ) - ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]] = - Dinero(); - - ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]] = - ticket_acc[ - selectedDmsAllocationConfig.costs[ticket_val.ciecacode] - ].add( - Dinero({ - amount: Math.round((ticket_val.rate || 0) * 100), - }).multiply( - ticket_val.flat_rate - ? ticket_val.productivehrs || ticket_val.actualhrs || 0 - : ticket_val.actualhrs || ticket_val.productivehrs || 0 - ) //Should base this on the employee. - ); - } else { - if (!ticket_acc[ticket_val.cost_center]) - ticket_acc[ticket_val.cost_center] = Dinero(); - - ticket_acc[ticket_val.cost_center] = ticket_acc[ - ticket_val.cost_center - ].add( - Dinero({ - amount: Math.round((ticket_val.rate || 0) * 100), - }).multiply( - ticket_val.flat_rate - ? ticket_val.productivehrs || ticket_val.actualhrs || 0 - : ticket_val.actualhrs || ticket_val.productivehrs || 0 - ) //Should base this on the employee. - ); - } - - return ticket_acc; - }, - {} - ); - - const summaryData = { - totalLaborSales: Dinero({amount: 0}), - totalPartsSales: Dinero({amount: 0}), - totalAdditionalSales: Dinero({amount: 0}), - totalSubletSales: Dinero({amount: 0}), - totalSales: Dinero({amount: 0}), - totalLaborCost: Dinero({amount: 0}), - totalPartsCost: Dinero({amount: 0}), - totalAdditionalCost: Dinero({amount: 0}), - totalSubletCost: Dinero({amount: 0}), - totalCost: Dinero({amount: 0}), - totalLaborGp: Dinero({amount: 0}), - totalPartsGp: Dinero({amount: 0}), - totalAdditionalGp: Dinero({amount: 0}), - totalSubletGp: Dinero({amount: 0}), - gpdollars: Dinero({amount: 0}), + const multiSummary = { + costCenterData: [], + summaryData: { + totalLaborSales: Dinero({ amount: 0 }), + totalPartsSales: Dinero({ amount: 0 }), + totalAdditionalSales: Dinero({ amount: 0 }), + totalSubletSales: Dinero({ amount: 0 }), + totalSales: Dinero({ amount: 0 }), + totalLaborCost: Dinero({ amount: 0 }), + totalPartsCost: Dinero({ amount: 0 }), + totalAdditionalCost: Dinero({ amount: 0 }), + totalSubletCost: Dinero({ amount: 0 }), + totalCost: Dinero({ amount: 0 }), + gpdollars: Dinero({ amount: 0 }), + gppercent: null, + gppercentFormatted: null, + totalLaborGp: Dinero({ amount: 0 }), + totalPartsGp: Dinero({ amount: 0 }), + totalAdditionalGp: Dinero({ amount: 0 }), + totalSubletGp: Dinero({ amount: 0 }), totalLaborGppercent: null, totalLaborGppercentFormatted: null, totalPartsGppercent: null, @@ -753,223 +77,702 @@ function GenerateCostingData(job) { totalAdditionalGppercent: null, totalAdditionalGppercentFormatted: null, totalSubletGppercent: null, - totalSubletGppercentFormatted: null, - gppercent: null, - gppercentFormatted: null, + totalSubletGppercentFormatted: null + } }; - const costCenterData = allCenters.map((key, idx) => { - const ccVal = key; // defaultProfits[key]; - const sale_labor = - jobLineTotalsByProfitCenter.labor[ccVal] || Dinero({amount: 0}); - const sale_parts = - jobLineTotalsByProfitCenter.parts[ccVal] || Dinero({amount: 0}); - const sale_additional = - jobLineTotalsByProfitCenter.additional[ccVal] || Dinero({amount: 0}); - const sale_sublet = - jobLineTotalsByProfitCenter.sublet[ccVal] || Dinero({amount: 0}); + const ret = {}; + resp.jobs.map((job) => { + const costingData = GenerateCostingData(job); + ret[job.id] = costingData; - const cost_labor = ticketTotalsByCostCenter[ccVal] || Dinero({amount: 0}); - const cost_parts = billTotalsByCostCenters[ccVal] || Dinero({amount: 0}); - const cost_additional = - billTotalsByCostCenters.additionalCosts[ccVal] || Dinero({amount: 0}); - const cost_sublet = - billTotalsByCostCenters.subletCosts[ccVal] || Dinero({amount: 0}); + //Merge on a cost center basis. - const costs = cost_labor - .add(cost_parts) - .add(cost_additional) - .add(cost_sublet); - const totalSales = sale_labor - .add(sale_parts) - .add(sale_additional) - .add(sale_sublet); - const gpdollars = totalSales.subtract(costs); - const gppercent = ( - (gpdollars.getAmount() / Math.abs(totalSales.getAmount())) * - 100 - ).toFixed(1); + costingData.costCenterData.forEach((c) => { + //Find the Cost Center if it exists. - //Push summary data to avoid extra loop. - summaryData.totalLaborSales = summaryData.totalLaborSales.add(sale_labor); - summaryData.totalPartsSales = summaryData.totalPartsSales.add(sale_parts); - summaryData.totalAdditionalSales = - summaryData.totalAdditionalSales.add(sale_additional); - summaryData.totalSubletSales = - summaryData.totalSubletSales.add(sale_sublet); - summaryData.totalSales = summaryData.totalSales.add(totalSales); - summaryData.totalLaborCost = summaryData.totalLaborCost.add(cost_labor); - summaryData.totalPartsCost = summaryData.totalPartsCost.add(cost_parts); - summaryData.totalAdditionalCost = - summaryData.totalAdditionalCost.add(cost_additional); - summaryData.totalSubletCost = summaryData.totalSubletCost.add(cost_sublet); - summaryData.totalCost = summaryData.totalCost.add(costs); + const CostCenterIndex = multiSummary.costCenterData.findIndex((x) => x.cost_center === c.cost_center); - return { - id: idx, - cost_center: ccVal, - sale_labor: sale_labor && sale_labor.toFormat(), - sale_labor_dinero: sale_labor, - sale_parts: sale_parts && sale_parts.toFormat(), - sale_parts_dinero: sale_parts, - sale_additional: sale_additional && sale_additional.toFormat(), - sale_additional_dinero: sale_additional, - sale_sublet: sale_sublet && sale_sublet.toFormat(), - sale_sublet_dinero: sale_sublet, - sales: totalSales.toFormat(), - sales_dinero: totalSales, - cost_parts: cost_parts && cost_parts.toFormat(), - cost_parts_dinero: cost_parts, - cost_labor: cost_labor && cost_labor.toFormat(), - cost_labor_dinero: cost_labor, - cost_additional: cost_additional && cost_additional.toFormat(), - cost_additional_dinero: cost_additional, - cost_sublet: cost_sublet && cost_sublet.toFormat(), - cost_sublet_dinero: cost_sublet, - costs: costs.toFormat(), - costs_dinero: costs, - gpdollars_dinero: gpdollars, - gpdollars: gpdollars.toFormat(), - gppercent: formatGpPercent(gppercent), - }; + if (CostCenterIndex >= 0) { + //Add it in place + multiSummary.costCenterData[CostCenterIndex] = { + ...multiSummary.costCenterData[CostCenterIndex], + sale_labor_dinero: multiSummary.costCenterData[CostCenterIndex].sale_labor_dinero.add(c.sale_labor_dinero), + sale_parts_dinero: multiSummary.costCenterData[CostCenterIndex].sale_parts_dinero.add(c.sale_parts_dinero), + sale_additional_dinero: multiSummary.costCenterData[CostCenterIndex].sale_additional_dinero.add( + c.sale_additional_dinero + ), + sale_sublet_dinero: multiSummary.costCenterData[CostCenterIndex].sale_sublet_dinero.add( + c.sale_sublet_dinero + ), + cost_labor_dinero: multiSummary.costCenterData[CostCenterIndex].cost_labor_dinero.add(c.cost_labor_dinero), + cost_parts_dinero: multiSummary.costCenterData[CostCenterIndex].cost_parts_dinero.add(c.cost_parts_dinero), + cost_additional_dinero: multiSummary.costCenterData[CostCenterIndex].cost_additional_dinero.add( + c.cost_additional_dinero + ), + cost_sublet_dinero: multiSummary.costCenterData[CostCenterIndex].cost_sublet_dinero.add( + c.cost_sublet_dinero + ), + gpdollars_dinero: multiSummary.costCenterData[CostCenterIndex].gpdollars_dinero.add(c.gpdollars_dinero), + costs_dinero: multiSummary.costCenterData[CostCenterIndex].costs_dinero.add(c.costs_dinero), + sales_dinero: multiSummary.costCenterData[CostCenterIndex].sales_dinero.add(c.sales_dinero) + }; + } else { + //Add it to the list instead. + multiSummary.costCenterData.push(c); + } + }); + + //Add all summary data. + multiSummary.summaryData.totalPartsSales = multiSummary.summaryData.totalPartsSales.add( + costingData.summaryData.totalPartsSales + ); + multiSummary.summaryData.totalAdditionalSales = multiSummary.summaryData.totalAdditionalSales.add( + costingData.summaryData.totalAdditionalSales + ); + multiSummary.summaryData.totalSubletSales = multiSummary.summaryData.totalSubletSales.add( + costingData.summaryData.totalSubletSales + ); + 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.totalAdditionalCost = multiSummary.summaryData.totalAdditionalCost.add( + costingData.summaryData.totalAdditionalCost + ); + multiSummary.summaryData.totalSubletCost = multiSummary.summaryData.totalSubletCost.add( + costingData.summaryData.totalSubletCost + ); + multiSummary.summaryData.totalCost = multiSummary.summaryData.totalCost.add(costingData.summaryData.totalCost); + multiSummary.summaryData.gpdollars = multiSummary.summaryData.gpdollars.add(costingData.summaryData.gpdollars); + + multiSummary.summaryData.totalLaborGp = multiSummary.summaryData.totalLaborGp.add( + costingData.summaryData.totalLaborGp + ); + multiSummary.summaryData.totalPartsGp = multiSummary.summaryData.totalPartsGp.add( + costingData.summaryData.totalPartsGp + ); + multiSummary.summaryData.totalAdditionalGp = multiSummary.summaryData.totalAdditionalGp.add( + costingData.summaryData.totalAdditionalGp + ); + multiSummary.summaryData.totalSubletGp = multiSummary.summaryData.totalSubletGp.add( + costingData.summaryData.totalSubletGp + ); + + //Take the summary data & add it to total summary data. }); - //Push adjustments to bottom line. - if (job.adjustment_bottom_line) { - //Add to totals. - const Adjustment = Dinero({ - amount: Math.round(job.adjustment_bottom_line * 100), - }); //Need to invert, since this is being assigned as a cost. - summaryData.totalLaborSales = summaryData.totalLaborSales.add(Adjustment); - summaryData.totalSales = summaryData.totalSales.add(Adjustment); - //Add to lines. - costCenterData.push({ - id: "Adj", - cost_center: "Adjustment", - sale_labor: Adjustment.toFormat(), - sale_labor_dinero: Adjustment, - sale_parts: Dinero().toFormat(), - sale_parts_dinero: Dinero(), - sale_additional: Dinero(), - sale_additional_dinero: Dinero(), - 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), + //For each center, recalculate and toFormat() the values. + + multiSummary.summaryData.totalLaborGppercent = ( + (multiSummary.summaryData.totalLaborGp.getAmount() / multiSummary.summaryData.totalLaborSales.getAmount()) * + 100 + ).toFixed(1); + multiSummary.summaryData.totalLaborGppercentFormatted = formatGpPercent( + multiSummary.summaryData.totalLaborGppercent + ); + + multiSummary.summaryData.totalPartsGppercent = ( + (multiSummary.summaryData.totalPartsGp.getAmount() / multiSummary.summaryData.totalPartsSales.getAmount()) * + 100 + ).toFixed(1); + + multiSummary.summaryData.totalPartsGppercentFormatted = formatGpPercent( + multiSummary.summaryData.totalPartsGppercent + ); + + multiSummary.summaryData.totalAdditionalGppercent = ( + (multiSummary.summaryData.totalAdditionalGp.getAmount() / + multiSummary.summaryData.totalAdditionalSales.getAmount()) * + 100 + ).toFixed(1); + + multiSummary.summaryData.totalAdditionalGppercentFormatted = formatGpPercent( + multiSummary.summaryData.totalAdditionalGppercent + ); + + multiSummary.summaryData.totalSubletGppercent = ( + (multiSummary.summaryData.totalSubletGp.getAmount() / multiSummary.summaryData.totalSubletSales.getAmount()) * + 100 + ).toFixed(1); + + multiSummary.summaryData.totalSubletGppercentFormatted = formatGpPercent( + multiSummary.summaryData.totalSubletGppercent + ); + + multiSummary.summaryData.gppercent = ( + (multiSummary.summaryData.gpdollars.getAmount() / multiSummary.summaryData.totalSales.getAmount()) * + 100 + ).toFixed(1); + + multiSummary.summaryData.gppercentFormatted = formatGpPercent(multiSummary.summaryData.gppercent); + + const finalCostingdata = multiSummary.costCenterData.map((c) => { + return { + ...c, + sale_labor: c.sale_labor_dinero && c.sale_labor_dinero.toFormat(), + sale_parts: c.sale_parts_dinero && c.sale_parts_dinero.toFormat(), + sale_additional: c.sale_additional_dinero && c.sale_additional_dinero.toFormat(), + sale_sublet: c.sale_sublet_dinero && c.sale_sublet_dinero.toFormat(), + sales: c.sales_dinero.toFormat(), + cost_parts: c.cost_parts_dinero && c.cost_parts_dinero.toFormat(), + cost_labor: c.cost_labor_dinero && c.cost_labor_dinero.toFormat(), + cost_additional: c.cost_additional_dinero && c.cost_additional_dinero.toFormat(), + cost_sublet: c.cost_sublet_dinero && c.cost_sublet_dinero.toFormat(), + costs: c.costs_dinero.toFormat(), + gpdollars: c.gpdollars_dinero.toFormat(), + gppercent: formatGpPercent(((c.gpdollars_dinero.getAmount() / c.sales_dinero.getAmount()) * 100).toFixed(1)) + }; + }); + + //Calculate thte total gross profit percentages. + + res.status(200).json({ + allCostCenterData: finalCostingdata, + allSummaryData: multiSummary.summaryData, + data: ret + }); + } catch (error) { + logger.log("job-costing-multi-error", "ERROR", req.user.email, [jobids], { + message: error.message, + stack: error.stack + }); + res.status(400).send(error); + } +} + +function GenerateCostingData(job) { + const defaultProfits = job.bodyshop.md_responsibility_centers.defaults.profits; + const allCenters = _.union( + job.bodyshop.md_responsibility_centers.profits.map((p) => p.name), + job.bodyshop.md_responsibility_centers.costs.map((p) => p.name), + ["Unknown"] + ); + + const materialsHours = { mapaHrs: 0, mashHrs: 0 }; + let hasMapaLine = false; + let hasMashLine = false; + + //Massage the data. + const jobLineTotalsByProfitCenter = + job && + job.joblines.reduce( + (acc, val) => { + //Parts Lines + if (val.db_ref === "936008") { + //If either of these DB REFs change, they also need to change in job-totals/job-costing calculations. + hasMapaLine = true; + } + if (val.db_ref === "936007") { + hasMashLine = true; + } + if (val.mod_lbr_ty) { + const laborProfitCenter = val.profitcenter_labor || defaultProfits[val.mod_lbr_ty] || "Unknown"; + + if (laborProfitCenter === "Unknown") console.log("Unknown type", val.line_desc, val.mod_lbr_ty); + + const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`; + + const laborAmount = Dinero({ + amount: Math.round((job[rateName] || 0) * 100) + }).multiply(val.mod_lb_hrs || 0); + if (!acc.labor[laborProfitCenter]) acc.labor[laborProfitCenter] = Dinero(); + acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(laborAmount); + + if (val.mod_lb_hrs === 0 && val.act_price > 0 && val.lbr_op === "OP14") { + //Scenario where SGI may pay out hours using a part price. + acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add( + Dinero({ + amount: Math.round((val.act_price || 0) * 100) + }).multiply(val.part_qty) + ); + } + + if (val.mod_lbr_ty === "LAR") { + materialsHours.mapaHrs += val.mod_lb_hrs || 0; + } + if (val.mod_lbr_ty !== "LAR") { + materialsHours.mashHrs += val.mod_lb_hrs || 0; + } + } + + if (val.part_type && val.part_type !== "PAE" && val.part_type !== "PAS" && val.part_type !== "PASL") { + const partsProfitCenter = val.profitcenter_part || defaultProfits[val.part_type] || "Unknown"; + + if (partsProfitCenter === "Unknown") console.log("Unknown type", val.line_desc, val.part_type); + + if (!partsProfitCenter) + console.log("Unknown cost/profit center mapping for parts.", val.line_desc, val.part_type); + const partsAmount = Dinero({ + amount: Math.round((val.act_price || 0) * 100) + }) + .multiply(val.part_qty || 1) + .add( + ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(val, job.joblines) + ? val.prt_dsmk_m + ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(val.act_price * 100) + }) + .multiply(val.part_qty || 0) + .percentage(Math.abs(val.prt_dsmk_p || 0)) + .multiply(val.prt_dsmk_p > 0 ? 1 : -1) + : Dinero() + ); + if (!acc.parts[partsProfitCenter]) acc.parts[partsProfitCenter] = Dinero(); + acc.parts[partsProfitCenter] = acc.parts[partsProfitCenter].add(partsAmount); + } + if (val.part_type && val.part_type !== "PAE" && (val.part_type === "PAS" || val.part_type === "PASL")) { + const partsProfitCenter = val.profitcenter_part || defaultProfits[val.part_type] || "Unknown"; + + if (partsProfitCenter === "Unknown") console.log("Unknown type", val.line_desc, val.part_type); + + if (!partsProfitCenter) + console.log("Unknown cost/profit center mapping for sublet.", val.line_desc, val.part_type); + const partsAmount = Dinero({ + amount: Math.round((val.act_price || 0) * 100) + }) + .multiply(val.part_qty || 1) + .add( + ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(val, job.joblines) + ? val.prt_dsmk_m + ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(val.act_price * 100) + }) + .multiply(val.part_qty || 0) + .percentage(Math.abs(val.prt_dsmk_p || 0)) + .multiply(val.prt_dsmk_p > 0 ? 1 : -1) + : Dinero() + ); + if (!acc.sublet[partsProfitCenter]) acc.sublet[partsProfitCenter] = Dinero(); + acc.sublet[partsProfitCenter] = acc.sublet[partsProfitCenter].add(partsAmount); + } + + //To deal with additional costs. + if (!val.part_type && !val.mod_lbr_ty) { + //Does it already have a defined profit center? + //If so, use it, otherwise try to use the same from the auto-allocate logic in IO app jobs-close-auto-allocate. + const partsProfitCenter = val.profitcenter_part || getAdditionalCostCenter(val, defaultProfits) || "Unknown"; + + if (partsProfitCenter === "Unknown") { + console.log("Unknown type", val.line_desc, val.part_type); + } + const partsAmount = Dinero({ + amount: Math.round((val.act_price || 0) * 100) + }) + .multiply(val.part_qty || 1) + .add( + ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(val, job.joblines) + ? val.prt_dsmk_m + ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(val.act_price * 100) + }) + .multiply(val.part_qty || 0) + .percentage(Math.abs(val.prt_dsmk_p || 0)) + .multiply(val.prt_dsmk_p > 0 ? 1 : -1) + : Dinero() + ); + + if (!acc.additional[partsProfitCenter]) acc.additional[partsProfitCenter] = Dinero(); + acc.additional[partsProfitCenter] = acc.additional[partsProfitCenter].add(partsAmount); + } + + return acc; + }, + { parts: {}, labor: {}, additional: {}, sublet: {} } + ); + + if (!hasMapaLine) { + if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]]) + jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = Dinero(); + jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = jobLineTotalsByProfitCenter.additional[ + defaultProfits["MAPA"] + ].add( + Dinero({ + amount: Math.round((job.rate_mapa || 0) * 100) + }).multiply(materialsHours.mapaHrs || 0) + ); + } + if (!hasMashLine) { + if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]]) + jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = Dinero(); + jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = jobLineTotalsByProfitCenter.additional[ + defaultProfits["MASH"] + ].add( + Dinero({ + amount: Math.round((job.rate_mash || 0) * 100) + }).multiply(materialsHours.mashHrs || 0) + ); + } + + //Is it a DMS Setup? + const selectedDmsAllocationConfig = + (job.bodyshop.md_responsibility_centers.dms_defaults && + job.bodyshop.md_responsibility_centers.dms_defaults.find((d) => d.name === job.dms_allocation)) || + job.bodyshop.md_responsibility_centers.defaults; + + const billTotalsByCostCenters = job.bills.reduce( + (bill_acc, bill_val) => { + //At the bill level. + bill_val.billlines.map((line_val) => { + //At the bill line level. + if (job.bodyshop.pbs_serialnumber || job.bodyshop.cdk_dealerid) { + if (!bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]]) + bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = Dinero(); + + bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = bill_acc[ + selectedDmsAllocationConfig.costs[line_val.cost_center] + ].add( + Dinero({ + amount: Math.round((line_val.actual_cost || 0) * 100) + }) + .multiply(line_val.quantity) + .multiply(bill_val.is_credit_memo ? -1 : 1) + ); + } else { + const isSubletCostCenter = + line_val.cost_center === job.bodyshop.md_responsibility_centers.defaults.costs.PAS || + line_val.cost_center === job.bodyshop.md_responsibility_centers.defaults.costs.PASL; + + const isAdditionalCostCenter = + // line_val.cost_center === + // job.bodyshop.md_responsibility_centers.defaults.costs.PAS || + // line_val.cost_center === + // job.bodyshop.md_responsibility_centers.defaults.costs.PASL || + line_val.cost_center === job.bodyshop.md_responsibility_centers.defaults.costs.TOW || + line_val.cost_center === job.bodyshop.md_responsibility_centers.defaults.costs.MAPA || + line_val.cost_center === job.bodyshop.md_responsibility_centers.defaults.costs.MASH; + + if (isAdditionalCostCenter) { + if (!bill_acc.additionalCosts[line_val.cost_center]) + bill_acc.additionalCosts[line_val.cost_center] = Dinero(); + + bill_acc.additionalCosts[line_val.cost_center] = bill_acc.additionalCosts[line_val.cost_center].add( + Dinero({ + amount: Math.round((line_val.actual_cost || 0) * 100) + }) + .multiply(line_val.quantity) + .multiply(bill_val.is_credit_memo ? -1 : 1) + ); + } else if (isSubletCostCenter) { + if (!bill_acc.subletCosts[line_val.cost_center]) bill_acc.subletCosts[line_val.cost_center] = Dinero(); + + bill_acc.subletCosts[line_val.cost_center] = bill_acc.subletCosts[line_val.cost_center].add( + Dinero({ + amount: Math.round((line_val.actual_cost || 0) * 100) + }) + .multiply(line_val.quantity) + .multiply(bill_val.is_credit_memo ? -1 : 1) + ); + } else { + if (!bill_acc[line_val.cost_center]) bill_acc[line_val.cost_center] = Dinero(); + + bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add( + Dinero({ + amount: Math.round((line_val.actual_cost || 0) * 100) + }) + .multiply(line_val.quantity) + .multiply(bill_val.is_credit_memo ? -1 : 1) + ); + } + } + + return null; + }); + return bill_acc; + }, + { additionalCosts: {}, subletCosts: {} } + ); + + //If the hourly rates for job costing are set, add them in. + + if ( + job.bodyshop.jc_hourly_rates && + (job.bodyshop.jc_hourly_rates.mapa || + typeof job.bodyshop.jc_hourly_rates.mapa === "number" || + isNaN(job.bodyshop.jc_hourly_rates.mapa) === false) + ) { + if (!billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA]) + billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = Dinero(); + if (job.bodyshop.use_paint_scale_data === true) { + if (job.mixdata.length > 0) { + billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = Dinero({ + amount: Math.round(((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100) }); + } else { + billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = + billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA].add( + Dinero({ + amount: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa * 100) || 0) + }).multiply(materialsHours.mapaHrs) + ); + } + } else { + billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = + billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA].add( + Dinero({ + amount: Math.round((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.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MASH]) + billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MASH] = Dinero(); + billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MASH] = + billTotalsByCostCenters.additionalCosts[job.bodyshop.md_responsibility_centers.defaults.costs.MASH].add( + Dinero({ + amount: Math.round((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. + + if (job.bodyshop.pbs_serialnumber || job.bodyshop.cdk_dealerid) { + if (!ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]]) + ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]] = Dinero(); + + ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]] = ticket_acc[ + selectedDmsAllocationConfig.costs[ticket_val.ciecacode] + ].add( + Dinero({ + amount: Math.round((ticket_val.rate || 0) * 100) + }).multiply( + ticket_val.flat_rate + ? ticket_val.productivehrs || ticket_val.actualhrs || 0 + : ticket_val.actualhrs || ticket_val.productivehrs || 0 + ) //Should base this on the employee. + ); + } else { + if (!ticket_acc[ticket_val.cost_center]) ticket_acc[ticket_val.cost_center] = Dinero(); + + ticket_acc[ticket_val.cost_center] = ticket_acc[ticket_val.cost_center].add( + Dinero({ + amount: Math.round((ticket_val.rate || 0) * 100) + }).multiply( + ticket_val.flat_rate + ? ticket_val.productivehrs || ticket_val.actualhrs || 0 + : ticket_val.actualhrs || ticket_val.productivehrs || 0 + ) //Should base this on the employee. + ); } - //Final summary data massaging. + return ticket_acc; + }, {}); - summaryData.totalLaborGp = summaryData.totalLaborSales.subtract( - summaryData.totalLaborCost - ); - summaryData.totalLaborGppercent = ( - (summaryData.totalLaborGp.getAmount() / - summaryData.totalLaborSales.getAmount()) * - 100 - ).toFixed(1); - summaryData.totalLaborGppercentFormatted = formatGpPercent( - summaryData.totalLaborGppercent - ); + const summaryData = { + totalLaborSales: Dinero({ amount: 0 }), + totalPartsSales: Dinero({ amount: 0 }), + totalAdditionalSales: Dinero({ amount: 0 }), + totalSubletSales: Dinero({ amount: 0 }), + totalSales: Dinero({ amount: 0 }), + totalLaborCost: Dinero({ amount: 0 }), + totalPartsCost: Dinero({ amount: 0 }), + totalAdditionalCost: Dinero({ amount: 0 }), + totalSubletCost: Dinero({ amount: 0 }), + totalCost: Dinero({ amount: 0 }), + totalLaborGp: Dinero({ amount: 0 }), + totalPartsGp: Dinero({ amount: 0 }), + totalAdditionalGp: Dinero({ amount: 0 }), + totalSubletGp: Dinero({ amount: 0 }), + gpdollars: Dinero({ amount: 0 }), + totalLaborGppercent: null, + totalLaborGppercentFormatted: null, + totalPartsGppercent: null, + totalPartsGppercentFormatted: null, + totalAdditionalGppercent: null, + totalAdditionalGppercentFormatted: null, + totalSubletGppercent: null, + totalSubletGppercentFormatted: null, + gppercent: null, + gppercentFormatted: null + }; - summaryData.totalPartsGp = summaryData.totalPartsSales.subtract( - summaryData.totalPartsCost - ); - summaryData.totalPartsGppercent = ( - (summaryData.totalPartsGp.getAmount() / - summaryData.totalPartsSales.getAmount()) * - 100 - ).toFixed(1); - summaryData.totalPartsGppercentFormatted = formatGpPercent( - summaryData.totalPartsGppercent - ); - summaryData.totalAdditionalGp = summaryData.totalAdditionalSales.subtract( - summaryData.totalAdditionalCost - ); - summaryData.totalAdditionalGppercent = ( - (summaryData.totalAdditionalGp.getAmount() / - summaryData.totalAdditionalSales.getAmount()) * - 100 - ).toFixed(1); - summaryData.totalAdditionalGppercentFormatted = formatGpPercent( - summaryData.totalAdditionalGppercent - ); - summaryData.totalSubletGp = summaryData.totalSubletSales.subtract( - summaryData.totalSubletCost - ); - summaryData.totalSubletGppercent = ( - (summaryData.totalSubletGp.getAmount() / - summaryData.totalSubletSales.getAmount()) * - 100 - ).toFixed(1); - summaryData.totalSubletGppercentFormatted = formatGpPercent( - summaryData.totalSubletGppercent - ); + const costCenterData = allCenters.map((key, idx) => { + const ccVal = key; // defaultProfits[key]; + const sale_labor = jobLineTotalsByProfitCenter.labor[ccVal] || Dinero({ amount: 0 }); + const sale_parts = jobLineTotalsByProfitCenter.parts[ccVal] || Dinero({ amount: 0 }); + const sale_additional = jobLineTotalsByProfitCenter.additional[ccVal] || Dinero({ amount: 0 }); + const sale_sublet = jobLineTotalsByProfitCenter.sublet[ccVal] || Dinero({ amount: 0 }); - summaryData.gpdollars = summaryData.totalSales.subtract( - summaryData.totalCost - ); - summaryData.gppercent = ( - (summaryData.gpdollars.getAmount() / - Math.abs(summaryData.totalSales.getAmount())) * - 100 - ).toFixed(1); + const cost_labor = ticketTotalsByCostCenter[ccVal] || Dinero({ amount: 0 }); + const cost_parts = billTotalsByCostCenters[ccVal] || Dinero({ amount: 0 }); + const cost_additional = billTotalsByCostCenters.additionalCosts[ccVal] || Dinero({ amount: 0 }); + const cost_sublet = billTotalsByCostCenters.subletCosts[ccVal] || Dinero({ amount: 0 }); - if (isNaN(summaryData.gppercent)) summaryData.gppercentFormatted = 0; - else if (!isFinite(summaryData.gppercent)) - summaryData.gppercentFormatted = "- ∞"; - else { - summaryData.gppercentFormatted = `${summaryData.gppercent}%`; - } + const costs = cost_labor.add(cost_parts).add(cost_additional).add(cost_sublet); + const totalSales = sale_labor.add(sale_parts).add(sale_additional).add(sale_sublet); + const gpdollars = totalSales.subtract(costs); + const gppercent = ((gpdollars.getAmount() / Math.abs(totalSales.getAmount())) * 100).toFixed(1); - return {summaryData, costCenterData}; + //Push summary data to avoid extra loop. + summaryData.totalLaborSales = summaryData.totalLaborSales.add(sale_labor); + summaryData.totalPartsSales = summaryData.totalPartsSales.add(sale_parts); + summaryData.totalAdditionalSales = summaryData.totalAdditionalSales.add(sale_additional); + summaryData.totalSubletSales = summaryData.totalSubletSales.add(sale_sublet); + summaryData.totalSales = summaryData.totalSales.add(totalSales); + summaryData.totalLaborCost = summaryData.totalLaborCost.add(cost_labor); + summaryData.totalPartsCost = summaryData.totalPartsCost.add(cost_parts); + summaryData.totalAdditionalCost = summaryData.totalAdditionalCost.add(cost_additional); + summaryData.totalSubletCost = summaryData.totalSubletCost.add(cost_sublet); + summaryData.totalCost = summaryData.totalCost.add(costs); + + return { + id: idx, + cost_center: ccVal, + sale_labor: sale_labor && sale_labor.toFormat(), + sale_labor_dinero: sale_labor, + sale_parts: sale_parts && sale_parts.toFormat(), + sale_parts_dinero: sale_parts, + sale_additional: sale_additional && sale_additional.toFormat(), + sale_additional_dinero: sale_additional, + sale_sublet: sale_sublet && sale_sublet.toFormat(), + sale_sublet_dinero: sale_sublet, + sales: totalSales.toFormat(), + sales_dinero: totalSales, + cost_parts: cost_parts && cost_parts.toFormat(), + cost_parts_dinero: cost_parts, + cost_labor: cost_labor && cost_labor.toFormat(), + cost_labor_dinero: cost_labor, + cost_additional: cost_additional && cost_additional.toFormat(), + cost_additional_dinero: cost_additional, + cost_sublet: cost_sublet && cost_sublet.toFormat(), + cost_sublet_dinero: cost_sublet, + costs: costs.toFormat(), + costs_dinero: costs, + gpdollars_dinero: gpdollars, + gpdollars: gpdollars.toFormat(), + gppercent: formatGpPercent(gppercent) + }; + }); + + //Push adjustments to bottom line. + if (job.adjustment_bottom_line) { + //Add to totals. + const Adjustment = Dinero({ + amount: Math.round(job.adjustment_bottom_line * 100) + }); //Need to invert, since this is being assigned as a cost. + summaryData.totalLaborSales = summaryData.totalLaborSales.add(Adjustment); + summaryData.totalSales = summaryData.totalSales.add(Adjustment); + //Add to lines. + costCenterData.push({ + id: "Adj", + cost_center: "Adjustment", + sale_labor: Adjustment.toFormat(), + sale_labor_dinero: Adjustment, + sale_parts: Dinero().toFormat(), + sale_parts_dinero: Dinero(), + sale_additional: Dinero(), + sale_additional_dinero: Dinero(), + 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. + + summaryData.totalLaborGp = summaryData.totalLaborSales.subtract(summaryData.totalLaborCost); + summaryData.totalLaborGppercent = ( + (summaryData.totalLaborGp.getAmount() / summaryData.totalLaborSales.getAmount()) * + 100 + ).toFixed(1); + summaryData.totalLaborGppercentFormatted = formatGpPercent(summaryData.totalLaborGppercent); + + summaryData.totalPartsGp = summaryData.totalPartsSales.subtract(summaryData.totalPartsCost); + summaryData.totalPartsGppercent = ( + (summaryData.totalPartsGp.getAmount() / summaryData.totalPartsSales.getAmount()) * + 100 + ).toFixed(1); + summaryData.totalPartsGppercentFormatted = formatGpPercent(summaryData.totalPartsGppercent); + summaryData.totalAdditionalGp = summaryData.totalAdditionalSales.subtract(summaryData.totalAdditionalCost); + summaryData.totalAdditionalGppercent = ( + (summaryData.totalAdditionalGp.getAmount() / summaryData.totalAdditionalSales.getAmount()) * + 100 + ).toFixed(1); + summaryData.totalAdditionalGppercentFormatted = formatGpPercent(summaryData.totalAdditionalGppercent); + summaryData.totalSubletGp = summaryData.totalSubletSales.subtract(summaryData.totalSubletCost); + summaryData.totalSubletGppercent = ( + (summaryData.totalSubletGp.getAmount() / summaryData.totalSubletSales.getAmount()) * + 100 + ).toFixed(1); + summaryData.totalSubletGppercentFormatted = formatGpPercent(summaryData.totalSubletGppercent); + + summaryData.gpdollars = summaryData.totalSales.subtract(summaryData.totalCost); + summaryData.gppercent = ( + (summaryData.gpdollars.getAmount() / Math.abs(summaryData.totalSales.getAmount())) * + 100 + ).toFixed(1); + + if (isNaN(summaryData.gppercent)) summaryData.gppercentFormatted = 0; + else if (!isFinite(summaryData.gppercent)) summaryData.gppercentFormatted = "- ∞"; + else { + summaryData.gppercentFormatted = `${summaryData.gppercent}%`; + } + + return { summaryData, costCenterData }; } exports.JobCosting = JobCosting; exports.JobCostingMulti = JobCostingMulti; const formatGpPercent = (gppercent) => { - let gppercentFormatted; - if (isNaN(gppercent)) gppercentFormatted = "0%"; - else if (!isFinite(gppercent)) gppercentFormatted = "- ∞"; - else { - gppercentFormatted = `${gppercent}%`; - } + let gppercentFormatted; + if (isNaN(gppercent)) gppercentFormatted = "0%"; + else if (!isFinite(gppercent)) gppercentFormatted = "- ∞"; + else { + gppercentFormatted = `${gppercent}%`; + } - return gppercentFormatted; + return gppercentFormatted; }; //Verify that this stays in line with jobs-close-auto-allocate logic from the application. const getAdditionalCostCenter = (jl, profitCenters) => { - if (!jl.part_type && !jl.mod_lbr_ty) { - const lineDesc = jl.line_desc ? jl.line_desc.toLowerCase() : ""; + if (!jl.part_type && !jl.mod_lbr_ty) { + const lineDesc = jl.line_desc ? jl.line_desc.toLowerCase() : ""; - if (lineDesc.includes("shop mat")) { - return profitCenters["MASH"]; - } else if (lineDesc.includes("paint/mat")) { - return profitCenters["MAPA"]; - } else if (lineDesc.includes("ats amount")) { - return profitCenters["ATS"]; - } else if (lineDesc.includes("towing")) { - return profitCenters["TOW"]; - } else if (jl.act_price > 0) { //TODO:AIO Ensure that this is tested. - ret.profitcenter_part = defaults.profits["PAO"]; - } else { - return null; - } + if (lineDesc.includes("shop mat")) { + return profitCenters["MASH"]; + } else if (lineDesc.includes("paint/mat")) { + return profitCenters["MAPA"]; + } else if (lineDesc.includes("ats amount")) { + return profitCenters["ATS"]; + } else if (lineDesc.includes("towing")) { + return profitCenters["TOW"]; + } else if (jl.act_price > 0) { + //TODO:AIO Ensure that this is tested. + ret.profitcenter_part = defaults.profits["PAO"]; + } else { + return null; } + } }; diff --git a/server/job/job-lifecycle.js b/server/job/job-lifecycle.js index b94f9ab68..744639bcf 100644 --- a/server/job/job-lifecycle.js +++ b/server/job/job-lifecycle.js @@ -6,106 +6,103 @@ const calculateStatusDuration = require("../utils/calculateStatusDuration"); const getLifecycleStatusColor = require("../utils/getLifecycleStatusColor"); const jobLifecycle = async (req, res) => { - // Grab the jobids and statuses from the request body - const { - jobids, - statuses - } = req.body; + // Grab the jobids and statuses from the request body + const { jobids, statuses } = req.body; - if (!jobids) { - return res.status(400).json({ - error: "Missing jobids" - }); - } - - const jobIDs = _.isArray(jobids) ? jobids : [jobids]; - const client = req.userGraphQLClient; - const resp = await client.request(queries.QUERY_TRANSITIONS_BY_JOBID, {jobids: jobIDs,}); - - const transitions = resp.transitions; - - if (!transitions) { - return res.status(200).json({ - jobIDs, - transitions: [] - }); - } - - const transitionsByJobId = _.groupBy(resp.transitions, 'jobid'); - - const groupedTransitions = {}; - const allDurations = []; - - for (let jobId in transitionsByJobId) { - let lifecycle = transitionsByJobId[jobId].map(transition => { - transition.start_readable = transition.start ? moment(transition.start).fromNow() : 'N/A'; - transition.end_readable = transition.end ? moment(transition.end).fromNow() : 'N/A'; - - if (transition.duration) { - transition.duration_seconds = Math.round(transition.duration / 1000); - transition.duration_minutes = Math.round(transition.duration_seconds / 60); - let duration = moment.duration(transition.duration); - transition.duration_readable = durationToHumanReadable(duration); - } else { - transition.duration_seconds = 0; - transition.duration_minutes = 0; - transition.duration_readable = 'N/A'; - } - return transition; - }); - - const durations = calculateStatusDuration(lifecycle, statuses); - - groupedTransitions[jobId] = { - lifecycle, - durations - }; - - if (durations?.summations) { - allDurations.push(durations.summations); - } - } - - const finalSummations = []; - const flatGroupedAllDurations = _.groupBy(allDurations.flat(),'status'); - - const finalStatusCounts = Object.keys(flatGroupedAllDurations).reduce((acc, status) => { - acc[status] = flatGroupedAllDurations[status].length; - return acc; - }, {}); - // Calculate total value of all statuses - const finalTotal = Object.values(flatGroupedAllDurations).reduce((total, statusArr) => { - return total + statusArr.reduce((acc, curr) => acc + curr.value, 0); - }, 0); - - Object.keys(flatGroupedAllDurations).forEach(status => { - const value = flatGroupedAllDurations[status].reduce((acc, curr) => acc + curr.value, 0); - const humanReadable = durationToHumanReadable(moment.duration(value)); - const percentage = (value / finalTotal) * 100; - const color = getLifecycleStatusColor(status); - const roundedPercentage = `${Math.round(percentage)}%`; - finalSummations.push({ - status, - value, - humanReadable, - percentage, - color, - roundedPercentage - }); + if (!jobids) { + return res.status(400).json({ + error: "Missing jobids" }); + } + const jobIDs = _.isArray(jobids) ? jobids : [jobids]; + const client = req.userGraphQLClient; + const resp = await client.request(queries.QUERY_TRANSITIONS_BY_JOBID, { jobids: jobIDs }); + + const transitions = resp.transitions; + + if (!transitions) { return res.status(200).json({ - jobIDs, - transition: groupedTransitions, - durations: { - jobs: jobIDs.length, - summations: finalSummations, - totalStatuses: finalSummations.length, - total: finalTotal, - statusCounts: finalStatusCounts, - humanReadable: durationToHumanReadable(moment.duration(finalTotal)) - } + jobIDs, + transitions: [] }); -} + } -module.exports = jobLifecycle; \ No newline at end of file + const transitionsByJobId = _.groupBy(resp.transitions, "jobid"); + + const groupedTransitions = {}; + const allDurations = []; + + for (let jobId in transitionsByJobId) { + let lifecycle = transitionsByJobId[jobId].map((transition) => { + transition.start_readable = transition.start ? moment(transition.start).fromNow() : "N/A"; + transition.end_readable = transition.end ? moment(transition.end).fromNow() : "N/A"; + + if (transition.duration) { + transition.duration_seconds = Math.round(transition.duration / 1000); + transition.duration_minutes = Math.round(transition.duration_seconds / 60); + let duration = moment.duration(transition.duration); + transition.duration_readable = durationToHumanReadable(duration); + } else { + transition.duration_seconds = 0; + transition.duration_minutes = 0; + transition.duration_readable = "N/A"; + } + return transition; + }); + + const durations = calculateStatusDuration(lifecycle, statuses); + + groupedTransitions[jobId] = { + lifecycle, + durations + }; + + if (durations?.summations) { + allDurations.push(durations.summations); + } + } + + const finalSummations = []; + const flatGroupedAllDurations = _.groupBy(allDurations.flat(), "status"); + + const finalStatusCounts = Object.keys(flatGroupedAllDurations).reduce((acc, status) => { + acc[status] = flatGroupedAllDurations[status].length; + return acc; + }, {}); + // Calculate total value of all statuses + const finalTotal = Object.values(flatGroupedAllDurations).reduce((total, statusArr) => { + return total + statusArr.reduce((acc, curr) => acc + curr.value, 0); + }, 0); + + Object.keys(flatGroupedAllDurations).forEach((status) => { + const value = flatGroupedAllDurations[status].reduce((acc, curr) => acc + curr.value, 0); + const humanReadable = durationToHumanReadable(moment.duration(value)); + const percentage = (value / finalTotal) * 100; + const color = getLifecycleStatusColor(status); + const roundedPercentage = `${Math.round(percentage)}%`; + finalSummations.push({ + status, + value, + humanReadable, + percentage, + color, + roundedPercentage + }); + }); + + return res.status(200).json({ + jobIDs, + transition: groupedTransitions, + durations: { + jobs: jobIDs.length, + summations: finalSummations, + totalStatuses: finalSummations.length, + total: finalTotal, + statusCounts: finalStatusCounts, + humanReadable: durationToHumanReadable(moment.duration(finalTotal)) + } + }); +}; + +module.exports = jobLifecycle; diff --git a/server/job/job-status-transition.js b/server/job/job-status-transition.js index aa7128bf7..623c94cfa 100644 --- a/server/job/job-status-transition.js +++ b/server/job/job-status-transition.js @@ -11,91 +11,68 @@ const path = require("path"); const client = require("../graphql-client/graphql-client").client; require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); async function StatusTransition(req, res) { - const { - id: jobid, - status: value, - shopid: bodyshopid, - } = req.body.event.data.new; + const { id: jobid, status: value, shopid: bodyshopid } = req.body.event.data.new; - // Create record OPEN on new item, enter state - // If change to SCHEDULE, update the last record and create a new record (update status and end time on old record, create a new record saying we came from previous status going to previous status - // (Timeline) - // Final status is exported, there is no end date as there is no further transition (has no end date) - try { - const {update_transitions} = await client.request( - queries.UPDATE_OLD_TRANSITION, - { - jobid: jobid, - existingTransition: { - end: new Date(), - next_value: value, + // Create record OPEN on new item, enter state + // If change to SCHEDULE, update the last record and create a new record (update status and end time on old record, create a new record saying we came from previous status going to previous status + // (Timeline) + // Final status is exported, there is no end date as there is no further transition (has no end date) + try { + const { update_transitions } = await client.request(queries.UPDATE_OLD_TRANSITION, { + jobid: jobid, + existingTransition: { + end: new Date(), + next_value: value - //duration - }, - } - ); + //duration + } + }); - let duration = - update_transitions.affected_rows === 0 - ? 0 - : new Date(update_transitions.returning[0].end) - - new Date(update_transitions.returning[0].start); + let duration = + update_transitions.affected_rows === 0 + ? 0 + : new Date(update_transitions.returning[0].end) - new Date(update_transitions.returning[0].start); - const resp2 = await client.request( - queries.INSERT_NEW_TRANSITION(update_transitions.affected_rows > 0), - { - ...(update_transitions.affected_rows > 0 - ? { - oldTransitionId: - update_transitions.affected_rows === 0 - ? null - : update_transitions.returning[0].id, - duration, - } - : {}), - newTransition: { - bodyshopid: bodyshopid, - jobid: jobid, - start: - update_transitions.affected_rows === 0 - ? new Date() - : update_transitions.returning[0].end, - prev_value: - update_transitions.affected_rows === 0 - ? null - : update_transitions.returning[0].value, - value: value, - type: "status", - }, - } - ); + const resp2 = await client.request(queries.INSERT_NEW_TRANSITION(update_transitions.affected_rows > 0), { + ...(update_transitions.affected_rows > 0 + ? { + oldTransitionId: update_transitions.affected_rows === 0 ? null : update_transitions.returning[0].id, + duration + } + : {}), + newTransition: { + bodyshopid: bodyshopid, + jobid: jobid, + start: update_transitions.affected_rows === 0 ? new Date() : update_transitions.returning[0].end, + prev_value: update_transitions.affected_rows === 0 ? null : update_transitions.returning[0].value, + value: value, + type: "status" + } + }); - logger.log("job-transition-update-result", "DEBUG", null, jobid, resp2); + logger.log("job-transition-update-result", "DEBUG", null, jobid, resp2); - //Check to see if there is an existing status transition record. - //Query using Job ID, start is not null, end is null. + //Check to see if there is an existing status transition record. + //Query using Job ID, start is not null, end is null. - //If there is no existing record, this is the start of the transition life cycle. - // Create the initial transition record. + //If there is no existing record, this is the start of the transition life cycle. + // Create the initial transition record. - //If there is a current status transition record, update it with the end date, duration, and next value. + //If there is a current status transition record, update it with the end date, duration, and next value. - res.sendStatus(200); //.json(ret); - } catch (error) { - logger.log("job-status-transition-error", "ERROR", req.user?.email, jobid, { - message: error.message, - stack: error.stack, - }); + res.sendStatus(200); //.json(ret); + } catch (error) { + logger.log("job-status-transition-error", "ERROR", req.user?.email, jobid, { + message: error.message, + stack: error.stack + }); - res.status(400).send(JSON.stringify(error)); - } + res.status(400).send(JSON.stringify(error)); + } } exports.statustransition = StatusTransition; diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index b8b7f75b5..700ea451d 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -16,1132 +16,1053 @@ const InstanceMgr = require("../utils/instanceMgr").default; //****************************************************** */ //****************************************************** */ - // Dinero.defaultCurrency = "USD"; // Dinero.globalLocale = "en-CA"; Dinero.globalRoundingMode = "HALF_EVEN"; exports.totalsSsu = async function (req, res) { - const {id} = req.body; + const { id } = req.body; - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; - logger.log("job-totals-ssu-USA", "DEBUG", req.user.email, id, null); + logger.log("job-totals-ssu-USA", "DEBUG", req.user.email, id, null); - try { - const job = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.GET_JOB_BY_PK, { - id: id, - }); + try { + const job = await client.setHeaders({ Authorization: BearerToken }).request(queries.GET_JOB_BY_PK, { + id: id + }); - const newTotals = await TotalsServerSide( - {body: {job: job.jobs_by_pk, client: client}}, - res, - true - ); + const newTotals = await TotalsServerSide({ body: { job: job.jobs_by_pk, client: client } }, res, true); - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.UPDATE_JOB, { - jobId: id, - job: { - clm_total: newTotals.totals.total_repairs.toFormat("0.00"), - owner_owing: newTotals.totals.custPayable.total.toFormat("0.00"), - job_totals: newTotals, - //queued_for_parts: true, - }, - }); + const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.UPDATE_JOB, { + jobId: id, + job: { + clm_total: newTotals.totals.total_repairs.toFormat("0.00"), + owner_owing: newTotals.totals.custPayable.total.toFormat("0.00"), + job_totals: newTotals + //queued_for_parts: true, + } + }); - res.status(200).send(); - } catch (error) { - logger.log("job-totals-ssu-USA-error", "ERROR", req.user.email, id, { - jobid: id, - error, - }); - res.status(503).send(); - } + res.status(200).send(); + } catch (error) { + logger.log("job-totals-ssu-USA-error", "ERROR", req.user.email, id, { + jobid: id, + error + }); + res.status(503).send(); + } }; //IMPORTANT*** These two functions MUST be mirrrored. async function TotalsServerSide(req, res) { - const {job, client} = req.body; - await AutoAddAtsIfRequired({job: job, client: client}); + const { job, client } = req.body; + await AutoAddAtsIfRequired({ job: job, client: client }); - try { - let ret = { - rates: await CalculateRatesTotals({job, client}), - parts: CalculatePartsTotals(job.joblines, job.parts_tax_rates, job), - }; - ret.additional = CalculateAdditional(job); - ret.totals = CalculateTaxesTotals(job, ret); + try { + let ret = { + rates: await CalculateRatesTotals({ job, client }), + parts: CalculatePartsTotals(job.joblines, job.parts_tax_rates, job) + }; + ret.additional = CalculateAdditional(job); + ret.totals = CalculateTaxesTotals(job, ret); - return ret; - } catch (error) { - logger.log("job-totals-ssu-USA-error", "ERROR", req.user?.email, job.id, { - jobid: job.id, - error, - }); - res.status(400).send(JSON.stringify(error)); - } + return ret; + } catch (error) { + logger.log("job-totals-ssu-USA-error", "ERROR", req.user?.email, job.id, { + jobid: job.id, + error + }); + res.status(400).send(JSON.stringify(error)); + } } async function Totals(req, res) { - const {job, id} = req.body; + const { job, id } = req.body; - const logger = req.logger; - const client = req.userGraphQLClient; + const logger = req.logger; + const client = req.userGraphQLClient; - logger.log("job-totals-USA", "DEBUG", req.user.email, job.id, { - jobid: job.id, - }); + logger.log("job-totals-USA", "DEBUG", req.user.email, job.id, { + jobid: job.id + }); - logger.log("job-totals-ssu-USA", "DEBUG", req.user.email, id, null); + logger.log("job-totals-ssu-USA", "DEBUG", req.user.email, id, null); - await AutoAddAtsIfRequired({job, client}); - - try { - let ret = { - rates: await CalculateRatesTotals({job, client}), - parts: CalculatePartsTotals(job.joblines, job.parts_tax_rates, job), - }; - ret.additional = CalculateAdditional(job); - ret.totals = CalculateTaxesTotals(job, ret); - - res.status(200).json(ret); - } catch (error) { - logger.log("job-totals-USA-error", "ERROR", req.user.email, job.id, { - jobid: job.id, - error, - }); - res.status(400).send(JSON.stringify(error)); - } -} - -async function AutoAddAtsIfRequired({job, client}) { - //Check if ATS should be automatically added. - if (job.auto_add_ats) { - //Get the total sum of hours that should be the ATS amount. - //Check to see if an ATS line exists. - let atsLineIndex = null; - const atsHours = job.joblines.reduce((acc, val, index) => { - if (val.line_desc && val.line_desc.toLowerCase() === "ats amount") { - atsLineIndex = index; - } - - if ( - val.mod_lbr_ty !== "LA1" && - val.mod_lbr_ty !== "LA2" && - val.mod_lbr_ty !== "LA3" && - val.mod_lbr_ty !== "LA4" && - val.mod_lbr_ty !== "LAU" && - val.mod_lbr_ty !== "LAG" && - val.mod_lbr_ty !== "LAS" && - val.mod_lbr_ty !== "LAA" - ) { - acc = acc + val.mod_lb_hrs; - } - - return acc; - }, 0); - - const atsAmount = atsHours * (job.rate_ats || 0); - //If it does, update it in place, and make sure it is updated for local calculations. - if (atsLineIndex === null) { - const newAtsLine = { - jobid: job.id, - alt_partm: null, - line_no: 35, - unq_seq: 0, - line_ind: "E", - line_desc: "ATS Amount", - line_ref: 0.0, - part_type: null, - oem_partno: null, - db_price: 0.0, - act_price: atsAmount, - part_qty: 1, - mod_lbr_ty: null, - db_hrs: 0.0, - mod_lb_hrs: 0.0, - lbr_op: "OP13", - lbr_amt: 0.0, - op_code_desc: "ADDITIONAL COSTS", - status: null, - location: null, - tax_part: true, - db_ref: null, - manual_line: true, - prt_dsmk_p: 0.0, - prt_dsmk_m: 0.0, - }; - - const result = await client.request(queries.INSERT_NEW_JOB_LINE, { - lineInput: [newAtsLine], - }); - - job.joblines.push(newAtsLine); - } - //If it does not, create one for local calculations and insert it. - else { - const result = await client.request(queries.UPDATE_JOB_LINE, { - line: {act_price: atsAmount}, - lineId: job.joblines[atsLineIndex].id, - }); - job.joblines[atsLineIndex].act_price = atsAmount; - } - } -} - -async function CalculateRatesTotals({job, client}) { - const jobLines = job.joblines.filter((jl) => !jl.removed); + await AutoAddAtsIfRequired({ job, client }); + try { let ret = { - la1: { - hours: 0, - rate: job.rate_la1 || 0, - }, - la2: { - hours: 0, - rate: job.rate_la2 || 0, - }, - la3: { - rate: job.rate_la3 || 0, - hours: 0, - }, - la4: { - rate: job.rate_la4 || 0, - hours: 0, - }, - laa: { - rate: job.rate_laa || 0, - hours: 0, - }, - lab: { - rate: job.rate_lab || 0, - hours: 0, - }, - lad: { - rate: job.rate_lad || 0, - hours: 0, - }, - lae: { - rate: job.rate_lae || 0, - hours: 0, - }, - laf: { - rate: job.rate_laf || 0, - hours: 0, - }, - lag: { - rate: job.rate_lag || 0, - hours: 0, - }, - lam: { - rate: job.rate_lam || 0, - hours: 0, - }, - lar: { - rate: job.rate_lar || 0, - hours: 0, - }, - las: { - rate: job.rate_las || 0, - hours: 0, - }, - lau: { - rate: job.rate_lau || 0, - hours: 0, - }, - mapa: { - rate: job.rate_mapa || 0, - hours: 0, - }, - mash: { - rate: job.rate_mash || 0, - hours: 0, - }, + rates: await CalculateRatesTotals({ job, client }), + parts: CalculatePartsTotals(job.joblines, job.parts_tax_rates, job) }; + ret.additional = CalculateAdditional(job); + ret.totals = CalculateTaxesTotals(job, ret); - //Determine if there are MAPA and MASH lines already on the estimate. - //If there are, don't do anything extra (mitchell estimate) - //Otherwise, calculate them and add them to the default MAPA and MASH centers. - let hasMapaLine = false; - let hasMashLine = false; - let hasMahwLine = false; - let hasCustomMahwLine; - let mapaOpCodes = ParseCalopCode(job.materials["MAPA"]?.cal_opcode); - let mashOpCodes = ParseCalopCode(job.materials["MASH"]?.cal_opcode); - - jobLines.forEach((item) => { - //IO-1317 Use the lines on the estimate if they exist instead. - if (item.db_ref === "936008") { - //If either of these DB REFs change, they also need to change in job-totals/job-costing calculations. - hasMapaLine = true; - ret["mapa"].total = Dinero({ - amount: Math.round((item.act_price || 0) * 100), - }); - } - if (item.db_ref === "936007") { - hasMashLine = true; - ret["mash"].total = Dinero({ - amount: Math.round((item.act_price || 0) * 100), - }); - } - //We might add a hazardous waste line. So we'll need to make sure we only pick up the CCC one. - if ( - item.line_desc?.toLowerCase().includes("hazardous waste") && - !item.manual_line && - item.part_type === null && - item.lbr_op !== "OP16" //Seems to be that it is OP16 for sublet lines. - ) { - hasMahwLine = item; - } - if ( - item.line_desc?.toLowerCase().includes("hazardous waste") && - item.manual_line - ) { - hasCustomMahwLine = item; - } - - if (item.mod_lbr_ty) { - //Check to see if it has 0 hours and a price instead. - if ( - item.mod_lb_hrs === 0 && - item.act_price > 0 && - item.lbr_op === "OP14" - ) { - //Scenario where SGI may pay out hours using a part price. - if (!ret[item.mod_lbr_ty.toLowerCase()].total) { - ret[item.mod_lbr_ty.toLowerCase()].total = Dinero(); - } - ret[item.mod_lbr_ty.toLowerCase()].total = ret[ - item.mod_lbr_ty.toLowerCase() - ].total.add( - Dinero({amount: Math.round((item.act_price || 0) * 100)}).multiply( - item.part_qty - ) - ); - } - - //There's a labor type, assign the hours. - ret[item.mod_lbr_ty.toLowerCase()].hours = - ret[item.mod_lbr_ty.toLowerCase()].hours + item.mod_lb_hrs; - - //Count up the number of materials/paint hours. - - //Following change may be CCC specific. - if (item.mod_lbr_ty === "LAR") { - // if (mapaOpCodes.includes(item.lbr_op)) { //Unknown if this is needed. Seems to be ignored. - ret.mapa.hours = ret.mapa.hours + item.mod_lb_hrs; - // } - } else { - if (mashOpCodes.length === 0 || mashOpCodes.includes(item.lbr_op)) { - // Added when processing CIECA ID 14A60015 to have materials match. - ret.mash.hours = ret.mash.hours + item.mod_lb_hrs; //Apparently there may be an exclusion for glass hours in BC. - } - } - } + res.status(200).json(ret); + } catch (error) { + logger.log("job-totals-USA-error", "ERROR", req.user.email, job.id, { + jobid: job.id, + error }); + res.status(400).send(JSON.stringify(error)); + } +} - let subtotal = Dinero({amount: 0}); - let rates_subtotal = Dinero({amount: 0}); +async function AutoAddAtsIfRequired({ job, client }) { + //Check if ATS should be automatically added. + if (job.auto_add_ats) { + //Get the total sum of hours that should be the ATS amount. + //Check to see if an ATS line exists. + let atsLineIndex = null; + const atsHours = job.joblines.reduce((acc, val, index) => { + if (val.line_desc && val.line_desc.toLowerCase() === "ats amount") { + atsLineIndex = index; + } - for (const property in ret) { - //Skip calculating mapa and mash if we got the amounts. - if ( - !( - (property === "mapa" && hasMapaLine) || - (property === "mash" && hasMashLine) - ) - ) { - if (!ret[property].total) { - ret[property].total = Dinero(); - } - let threshold; - //Check if there is a max for this type. - if (job.materials && job.materials[property.toUpperCase()]) { - // + if ( + val.mod_lbr_ty !== "LA1" && + val.mod_lbr_ty !== "LA2" && + val.mod_lbr_ty !== "LA3" && + val.mod_lbr_ty !== "LA4" && + val.mod_lbr_ty !== "LAU" && + val.mod_lbr_ty !== "LAG" && + val.mod_lbr_ty !== "LAS" && + val.mod_lbr_ty !== "LAA" + ) { + acc = acc + val.mod_lb_hrs; + } - if ( - job.materials[property.toUpperCase()].cal_maxdlr !== undefined && - job.materials[property.toUpperCase()].cal_maxdlr >= 0 - ) { - //It has an upper threshhold. - threshold = Dinero({ - amount: Math.round( - job.materials[property.toUpperCase()].cal_maxdlr * 100 - ), - }); - } - } + return acc; + }, 0); - const total = Dinero({ - amount: Math.round((ret[property].rate || 0) * 100), - }).multiply(ret[property].hours); + const atsAmount = atsHours * (job.rate_ats || 0); + //If it does, update it in place, and make sure it is updated for local calculations. + if (atsLineIndex === null) { + const newAtsLine = { + jobid: job.id, + alt_partm: null, + line_no: 35, + unq_seq: 0, + line_ind: "E", + line_desc: "ATS Amount", + line_ref: 0.0, + part_type: null, + oem_partno: null, + db_price: 0.0, + act_price: atsAmount, + part_qty: 1, + mod_lbr_ty: null, + db_hrs: 0.0, + mod_lb_hrs: 0.0, + lbr_op: "OP13", + lbr_amt: 0.0, + op_code_desc: "ADDITIONAL COSTS", + status: null, + location: null, + tax_part: true, + db_ref: null, + manual_line: true, + prt_dsmk_p: 0.0, + prt_dsmk_m: 0.0 + }; - if (threshold && total.greaterThanOrEqual(threshold)) { - ret[property].total = ret[property].total.add(threshold); - } else { - ret[property].total = ret[property].total.add(total); - } - } + const result = await client.request(queries.INSERT_NEW_JOB_LINE, { + lineInput: [newAtsLine] + }); - subtotal = subtotal.add(ret[property].total); - - if (property !== "mapa" && property !== "mash") - rates_subtotal = rates_subtotal.add(ret[property].total); + job.joblines.push(newAtsLine); } + //If it does not, create one for local calculations and insert it. + else { + const result = await client.request(queries.UPDATE_JOB_LINE, { + line: { act_price: atsAmount }, + lineId: job.joblines[atsLineIndex].id + }); + job.joblines[atsLineIndex].act_price = atsAmount; + } + } +} - const stlMahw = job.cieca_stl?.data.find((c) => c.ttl_typecd === "MAHW"); +async function CalculateRatesTotals({ job, client }) { + const jobLines = job.joblines.filter((jl) => !jl.removed); + let ret = { + la1: { + hours: 0, + rate: job.rate_la1 || 0 + }, + la2: { + hours: 0, + rate: job.rate_la2 || 0 + }, + la3: { + rate: job.rate_la3 || 0, + hours: 0 + }, + la4: { + rate: job.rate_la4 || 0, + hours: 0 + }, + laa: { + rate: job.rate_laa || 0, + hours: 0 + }, + lab: { + rate: job.rate_lab || 0, + hours: 0 + }, + lad: { + rate: job.rate_lad || 0, + hours: 0 + }, + lae: { + rate: job.rate_lae || 0, + hours: 0 + }, + laf: { + rate: job.rate_laf || 0, + hours: 0 + }, + lag: { + rate: job.rate_lag || 0, + hours: 0 + }, + lam: { + rate: job.rate_lam || 0, + hours: 0 + }, + lar: { + rate: job.rate_lar || 0, + hours: 0 + }, + las: { + rate: job.rate_las || 0, + hours: 0 + }, + lau: { + rate: job.rate_lau || 0, + hours: 0 + }, + mapa: { + rate: job.rate_mapa || 0, + hours: 0 + }, + mash: { + rate: job.rate_mash || 0, + hours: 0 + } + }; + + //Determine if there are MAPA and MASH lines already on the estimate. + //If there are, don't do anything extra (mitchell estimate) + //Otherwise, calculate them and add them to the default MAPA and MASH centers. + let hasMapaLine = false; + let hasMashLine = false; + let hasMahwLine = false; + let hasCustomMahwLine; + let mapaOpCodes = ParseCalopCode(job.materials["MAPA"]?.cal_opcode); + let mashOpCodes = ParseCalopCode(job.materials["MASH"]?.cal_opcode); + + jobLines.forEach((item) => { + //IO-1317 Use the lines on the estimate if they exist instead. + if (item.db_ref === "936008") { + //If either of these DB REFs change, they also need to change in job-totals/job-costing calculations. + hasMapaLine = true; + ret["mapa"].total = Dinero({ + amount: Math.round((item.act_price || 0) * 100) + }); + } + if (item.db_ref === "936007") { + hasMashLine = true; + ret["mash"].total = Dinero({ + amount: Math.round((item.act_price || 0) * 100) + }); + } + //We might add a hazardous waste line. So we'll need to make sure we only pick up the CCC one. if ( - stlMahw && - stlMahw.ttl_amt !== 0 && - (!hasMahwLine || hasMahwLine.act_price !== stlMahw.ttl_amt) + item.line_desc?.toLowerCase().includes("hazardous waste") && + !item.manual_line && + item.part_type === null && + item.lbr_op !== "OP16" //Seems to be that it is OP16 for sublet lines. ) { - //The Mahw line that has been added doesn't match with what we have in the STL. Add/update the adjusting line so that the balance is correct. - - //Add a hazardous waste material line in case there isn't one on the estimate. - let newPrice = stlMahw.ttl_amt; - if (hasCustomMahwLine) { - //Update it - job.joblines.forEach((jl) => { - if (jl.id === hasCustomMahwLine.id) { - jl.act_price = newPrice; - jl.manual_line = true; - jl.tax_part = stlMahw.tax_amt > 0 ? true : false; - } - }); - await client.request(queries.UPDATE_JOB_LINE, { - lineId: hasCustomMahwLine.id, - line: { - act_price: newPrice, - manual_line: true, - tax_part: stlMahw.tax_amt > 0 ? true : false, - }, - }); - } else { - const newMahwLine = { - line_desc: "Hazardous Waste Removal*", - part_type: null, - oem_partno: null, - db_price: 0, - act_price: newPrice, - part_qty: 1, - mod_lbr_ty: "LAB", - db_hrs: 0, - mod_lb_hrs: 0, - lbr_op: "OP0", - lbr_amt: 0, - op_code_desc: "REMOVE / REPLACE", - tax_part: stlMahw.tax_amt > 0 ? true : false, - db_ref: null, - manual_line: true, - jobid: job.id, - }; - job.joblines.push(newMahwLine); - await client.request(queries.INSERT_NEW_JOB_LINE, { - lineInput: [newMahwLine], - }); - } + hasMahwLine = item; + } + if (item.line_desc?.toLowerCase().includes("hazardous waste") && item.manual_line) { + hasCustomMahwLine = item; } - //Materials Scrubbing as required by CCC. - let matTotalLine = job.cieca_stl?.data?.find((l) => l.ttl_typecd === "MAT"); - let shopMatLine = job.cieca_stl?.data?.find((l) => l.ttl_typecd === "MASH"); - - if (matTotalLine && shopMatLine) { - //Check to see if theyre different - let calcMapaAmount = Dinero({ - amount: Math.round( - (matTotalLine?.ttl_amt - shopMatLine?.ttl_amt - stlMahw?.ttl_amt) * 100 - ), - }); - let mapaDifference = calcMapaAmount.subtract(ret.mapa.total); - if (mapaDifference.getAmount() > 0) { - //fix it. - ret.mapa.total = calcMapaAmount; - //Add the difference to the subt total as well. - subtotal = subtotal.add(mapaDifference); + if (item.mod_lbr_ty) { + //Check to see if it has 0 hours and a price instead. + if (item.mod_lb_hrs === 0 && item.act_price > 0 && item.lbr_op === "OP14") { + //Scenario where SGI may pay out hours using a part price. + if (!ret[item.mod_lbr_ty.toLowerCase()].total) { + ret[item.mod_lbr_ty.toLowerCase()].total = Dinero(); } - } - ret.subtotal = subtotal; - ret.rates_subtotal = rates_subtotal; + ret[item.mod_lbr_ty.toLowerCase()].total = ret[item.mod_lbr_ty.toLowerCase()].total.add( + Dinero({ amount: Math.round((item.act_price || 0) * 100) }).multiply(item.part_qty) + ); + } - ret.mapa.hasMapaLine = hasMapaLine; - ret.mash.hasMashLine = hasMashLine; - return ret; + //There's a labor type, assign the hours. + ret[item.mod_lbr_ty.toLowerCase()].hours = ret[item.mod_lbr_ty.toLowerCase()].hours + item.mod_lb_hrs; + + //Count up the number of materials/paint hours. + + //Following change may be CCC specific. + if (item.mod_lbr_ty === "LAR") { + // if (mapaOpCodes.includes(item.lbr_op)) { //Unknown if this is needed. Seems to be ignored. + ret.mapa.hours = ret.mapa.hours + item.mod_lb_hrs; + // } + } else { + if (mashOpCodes.length === 0 || mashOpCodes.includes(item.lbr_op)) { + // Added when processing CIECA ID 14A60015 to have materials match. + ret.mash.hours = ret.mash.hours + item.mod_lb_hrs; //Apparently there may be an exclusion for glass hours in BC. + } + } + } + }); + + let subtotal = Dinero({ amount: 0 }); + let rates_subtotal = Dinero({ amount: 0 }); + + for (const property in ret) { + //Skip calculating mapa and mash if we got the amounts. + if (!((property === "mapa" && hasMapaLine) || (property === "mash" && hasMashLine))) { + if (!ret[property].total) { + ret[property].total = Dinero(); + } + let threshold; + //Check if there is a max for this type. + if (job.materials && job.materials[property.toUpperCase()]) { + // + + if ( + job.materials[property.toUpperCase()].cal_maxdlr !== undefined && + job.materials[property.toUpperCase()].cal_maxdlr >= 0 + ) { + //It has an upper threshhold. + threshold = Dinero({ + amount: Math.round(job.materials[property.toUpperCase()].cal_maxdlr * 100) + }); + } + } + + const total = Dinero({ + amount: Math.round((ret[property].rate || 0) * 100) + }).multiply(ret[property].hours); + + if (threshold && total.greaterThanOrEqual(threshold)) { + ret[property].total = ret[property].total.add(threshold); + } else { + ret[property].total = ret[property].total.add(total); + } + } + + subtotal = subtotal.add(ret[property].total); + + if (property !== "mapa" && property !== "mash") rates_subtotal = rates_subtotal.add(ret[property].total); + } + + const stlMahw = job.cieca_stl?.data.find((c) => c.ttl_typecd === "MAHW"); + + if (stlMahw && stlMahw.ttl_amt !== 0 && (!hasMahwLine || hasMahwLine.act_price !== stlMahw.ttl_amt)) { + //The Mahw line that has been added doesn't match with what we have in the STL. Add/update the adjusting line so that the balance is correct. + + //Add a hazardous waste material line in case there isn't one on the estimate. + let newPrice = stlMahw.ttl_amt; + if (hasCustomMahwLine) { + //Update it + job.joblines.forEach((jl) => { + if (jl.id === hasCustomMahwLine.id) { + jl.act_price = newPrice; + jl.manual_line = true; + jl.tax_part = stlMahw.tax_amt > 0 ? true : false; + } + }); + await client.request(queries.UPDATE_JOB_LINE, { + lineId: hasCustomMahwLine.id, + line: { + act_price: newPrice, + manual_line: true, + tax_part: stlMahw.tax_amt > 0 ? true : false + } + }); + } else { + const newMahwLine = { + line_desc: "Hazardous Waste Removal*", + part_type: null, + oem_partno: null, + db_price: 0, + act_price: newPrice, + part_qty: 1, + mod_lbr_ty: "LAB", + db_hrs: 0, + mod_lb_hrs: 0, + lbr_op: "OP0", + lbr_amt: 0, + op_code_desc: "REMOVE / REPLACE", + tax_part: stlMahw.tax_amt > 0 ? true : false, + db_ref: null, + manual_line: true, + jobid: job.id + }; + job.joblines.push(newMahwLine); + await client.request(queries.INSERT_NEW_JOB_LINE, { + lineInput: [newMahwLine] + }); + } + } + + //Materials Scrubbing as required by CCC. + let matTotalLine = job.cieca_stl?.data?.find((l) => l.ttl_typecd === "MAT"); + let shopMatLine = job.cieca_stl?.data?.find((l) => l.ttl_typecd === "MASH"); + + if (matTotalLine && shopMatLine) { + //Check to see if theyre different + let calcMapaAmount = Dinero({ + amount: Math.round((matTotalLine?.ttl_amt - shopMatLine?.ttl_amt - stlMahw?.ttl_amt) * 100) + }); + let mapaDifference = calcMapaAmount.subtract(ret.mapa.total); + if (mapaDifference.getAmount() > 0) { + //fix it. + ret.mapa.total = calcMapaAmount; + //Add the difference to the subt total as well. + subtotal = subtotal.add(mapaDifference); + } + } + ret.subtotal = subtotal; + ret.rates_subtotal = rates_subtotal; + + ret.mapa.hasMapaLine = hasMapaLine; + ret.mash.hasMashLine = hasMashLine; + return ret; } function CalculatePartsTotals(jobLines, parts_tax_rates, job) { - const jl = jobLines.filter((jl) => !jl.removed); - const ret = jl.reduce( - (acc, value) => { - switch (value.part_type) { - case "PAS": - case "PASL": - return { - ...acc, - sublets: { - ...acc.sublets, - subtotal: acc.sublets.subtotal.add( - Dinero({ - amount: Math.round(value.act_price * 100), - }) - .multiply(value.part_qty || 0) - .add( - ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || - (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && - DiscountNotAlreadyCounted(value, jl) - ? value.prt_dsmk_m - ? Dinero({amount: Math.round(value.prt_dsmk_m * 100)}) - : Dinero({ - amount: Math.round(value.act_price * 100), - }) - .multiply(value.part_qty || 0) - .percentage(Math.abs(value.prt_dsmk_p || 0)) - .multiply(value.prt_dsmk_p > 0 ? 1 : -1) - : Dinero() - ) - ), - }, - }; - - default: - if ( - !value.part_type && - value.db_ref !== "900510" && - value.db_ref !== "900511" - ) - return acc; - - const discountAmount = - ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || - (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && - DiscountNotAlreadyCounted(value, jl) - ? value.prt_dsmk_m - ? Dinero({amount: Math.round(value.prt_dsmk_m * 100)}) - : Dinero({ - amount: Math.round(value.act_price * 100), - }) - .multiply(value.part_qty || 0) - .percentage(Math.abs(value.prt_dsmk_p || 0)) - .multiply(value.prt_dsmk_p > 0 ? 1 : -1) - : Dinero(); - - return { - ...acc, - parts: { - ...acc.parts, - prt_dsmk_total: acc.parts.prt_dsmk_total.add(discountAmount), - ...(value.part_type - ? { - list: { - ...acc.parts.list, - [value.part_type]: - acc.parts.list[value.part_type] && - acc.parts.list[value.part_type].total - ? { - total: acc.parts.list[value.part_type].total - .add( - Dinero({ - amount: Math.round( - (value.act_price || 0) * 100 - ), - }).multiply(value.part_qty || 0) - ) - .add(discountAmount), - } - : { - total: Dinero({ - amount: Math.round( - (value.act_price || 0) * 100 - ), - }) - .multiply(value.part_qty || 0) - .add(discountAmount), - }, - }, - } - : {}), - subtotal: acc.parts.subtotal - .add( - Dinero({ - amount: Math.round(value.act_price * 100), - }).multiply(value.part_qty || 0) - ) - .add( - ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || - (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && - DiscountNotAlreadyCounted(value, jl) - ? value.prt_dsmk_m - ? Dinero({amount: Math.round(value.prt_dsmk_m * 100)}) - : Dinero({ - amount: Math.round(value.act_price * 100), - }) - .multiply(value.part_qty || 0) - .percentage(Math.abs(value.prt_dsmk_p || 0)) - .multiply(value.prt_dsmk_p > 0 ? 1 : -1) - : Dinero() - ), - }, - }; - } - }, - { - parts: { - list: {}, - prt_dsmk_total: Dinero(), - subtotal: Dinero({amount: 0}), - total: Dinero({amount: 0}), - }, + const jl = jobLines.filter((jl) => !jl.removed); + const ret = jl.reduce( + (acc, value) => { + switch (value.part_type) { + case "PAS": + case "PASL": + return { + ...acc, sublets: { - subtotal: Dinero({amount: 0}), - - total: Dinero({amount: 0}), - }, - } - ); - - //Apply insurance based parts discounts/markups. - let adjustments = {}; - //Track all adjustments that need to be made. - - const linesToAdjustForDiscount = []; - Object.keys(parts_tax_rates).forEach((key) => { - //Check if there's a discount or a mark up. - let disc = Dinero(), - markup = Dinero(); - - let discountRate, markupRate; - if ( - parts_tax_rates[key].prt_discp !== undefined && - parts_tax_rates[key].prt_discp >= 0 - ) { - //Check if there's any parts in this part type. - if (ret.parts.list[key] !== undefined) { - discountRate = - Math.abs(parts_tax_rates[key].prt_discp) > 1 - ? parts_tax_rates[key].prt_discp - : parts_tax_rates[key].prt_discp * 100; - - disc = ret.parts.list[key].total.percentage(discountRate).multiply(-1); + ...acc.sublets, + subtotal: acc.sublets.subtotal.add( + Dinero({ + amount: Math.round(value.act_price * 100) + }) + .multiply(value.part_qty || 0) + .add( + ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(value, jl) + ? value.prt_dsmk_m + ? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(value.act_price * 100) + }) + .multiply(value.part_qty || 0) + .percentage(Math.abs(value.prt_dsmk_p || 0)) + .multiply(value.prt_dsmk_p > 0 ? 1 : -1) + : Dinero() + ) + ) } - } - if ( - parts_tax_rates[key].prt_mkupp !== undefined && - parts_tax_rates[key].prt_mkupp >= 0 - ) { - //Check if there's any parts in this part type. - if (ret.parts.list[key] !== undefined) { - markupRate = - Math.abs(parts_tax_rates[key].prt_mkupp) > 1 - ? parts_tax_rates[key].prt_mkupp - : parts_tax_rates[key].prt_mkupp * 100; //Seems that mark up is written as decimal not %. + }; - markup = ret.parts.list[key].total.percentage(markupRate); + default: + if (!value.part_type && value.db_ref !== "900510" && value.db_ref !== "900511") return acc; + + const discountAmount = + ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(value, jl) + ? value.prt_dsmk_m + ? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(value.act_price * 100) + }) + .multiply(value.part_qty || 0) + .percentage(Math.abs(value.prt_dsmk_p || 0)) + .multiply(value.prt_dsmk_p > 0 ? 1 : -1) + : Dinero(); + + return { + ...acc, + parts: { + ...acc.parts, + prt_dsmk_total: acc.parts.prt_dsmk_total.add(discountAmount), + ...(value.part_type + ? { + list: { + ...acc.parts.list, + [value.part_type]: + acc.parts.list[value.part_type] && acc.parts.list[value.part_type].total + ? { + total: acc.parts.list[value.part_type].total + .add( + Dinero({ + amount: Math.round((value.act_price || 0) * 100) + }).multiply(value.part_qty || 0) + ) + .add(discountAmount) + } + : { + total: Dinero({ + amount: Math.round((value.act_price || 0) * 100) + }) + .multiply(value.part_qty || 0) + .add(discountAmount) + } + } + } + : {}), + subtotal: acc.parts.subtotal + .add( + Dinero({ + amount: Math.round(value.act_price * 100) + }).multiply(value.part_qty || 0) + ) + .add( + ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(value, jl) + ? value.prt_dsmk_m + ? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(value.act_price * 100) + }) + .multiply(value.part_qty || 0) + .percentage(Math.abs(value.prt_dsmk_p || 0)) + .multiply(value.prt_dsmk_p > 0 ? 1 : -1) + : Dinero() + ) } - } - const correspondingCiecaStlTotalLine = job.cieca_stl?.data.find( - (c) => c.ttl_typecd === key - ); + }; + } + }, + { + parts: { + list: {}, + prt_dsmk_total: Dinero(), + subtotal: Dinero({ amount: 0 }), + total: Dinero({ amount: 0 }) + }, + sublets: { + subtotal: Dinero({ amount: 0 }), - //If the difference is greater than a penny, fix it. - //This usually ties into whether or not the profile has part type discounts overall in the PFP. - if ( - correspondingCiecaStlTotalLine && - Math.abs( - ret.parts.list[key]?.total.getAmount() - - correspondingCiecaStlTotalLine.ttl_amt * 100 - ) > 1 - ) { - let adjustment = disc.add(markup); - adjustments[key] = adjustment; - ret.parts.subtotal = ret.parts.subtotal.add(adjustment); - ret.parts.total = ret.parts.total.add(adjustment); - } - }); + total: Dinero({ amount: 0 }) + } + } + ); - return { - adjustments, - parts: { - ...ret.parts, - total: ret.parts.subtotal, - }, - sublets: { - ...ret.sublets, - total: ret.sublets.subtotal, - }, - }; + //Apply insurance based parts discounts/markups. + let adjustments = {}; + //Track all adjustments that need to be made. + + const linesToAdjustForDiscount = []; + Object.keys(parts_tax_rates).forEach((key) => { + //Check if there's a discount or a mark up. + let disc = Dinero(), + markup = Dinero(); + + let discountRate, markupRate; + if (parts_tax_rates[key].prt_discp !== undefined && parts_tax_rates[key].prt_discp >= 0) { + //Check if there's any parts in this part type. + if (ret.parts.list[key] !== undefined) { + discountRate = + Math.abs(parts_tax_rates[key].prt_discp) > 1 + ? parts_tax_rates[key].prt_discp + : parts_tax_rates[key].prt_discp * 100; + + disc = ret.parts.list[key].total.percentage(discountRate).multiply(-1); + } + } + if (parts_tax_rates[key].prt_mkupp !== undefined && parts_tax_rates[key].prt_mkupp >= 0) { + //Check if there's any parts in this part type. + if (ret.parts.list[key] !== undefined) { + markupRate = + Math.abs(parts_tax_rates[key].prt_mkupp) > 1 + ? parts_tax_rates[key].prt_mkupp + : parts_tax_rates[key].prt_mkupp * 100; //Seems that mark up is written as decimal not %. + + markup = ret.parts.list[key].total.percentage(markupRate); + } + } + const correspondingCiecaStlTotalLine = job.cieca_stl?.data.find((c) => c.ttl_typecd === key); + + //If the difference is greater than a penny, fix it. + //This usually ties into whether or not the profile has part type discounts overall in the PFP. + if ( + correspondingCiecaStlTotalLine && + Math.abs(ret.parts.list[key]?.total.getAmount() - correspondingCiecaStlTotalLine.ttl_amt * 100) > 1 + ) { + let adjustment = disc.add(markup); + adjustments[key] = adjustment; + ret.parts.subtotal = ret.parts.subtotal.add(adjustment); + ret.parts.total = ret.parts.total.add(adjustment); + } + }); + + return { + adjustments, + parts: { + ...ret.parts, + total: ret.parts.subtotal + }, + sublets: { + ...ret.sublets, + total: ret.sublets.subtotal + } + }; } function IsAdditionalCost(jobLine) { - //May be able to use db_ref here to help. - //936012 is Haz Waste Dispoal - //936008 is Paint/Materials - //936007 is Shop/Materials + //May be able to use db_ref here to help. + //936012 is Haz Waste Dispoal + //936008 is Paint/Materials + //936007 is Shop/Materials - //Remove paint and shop mat lines. They're calculated under rates. - const isPaintOrShopMat = - jobLine.db_ref === "936008" || jobLine.db_ref === "936007"; + //Remove paint and shop mat lines. They're calculated under rates. + const isPaintOrShopMat = jobLine.db_ref === "936008" || jobLine.db_ref === "936007"; - return ( - (jobLine.lbr_op === "OP13" || //Added to resolve manual job lines coming into other totals because they have no reference. - (jobLine.part_type === null && (jobLine.act_price || 0 > 0)) || - (jobLine.db_ref && jobLine.db_ref.startsWith("9360"))) && //This ref works in Canada, but DB_REFS in the US do not fill in. - !isPaintOrShopMat - ); + return ( + (jobLine.lbr_op === "OP13" || //Added to resolve manual job lines coming into other totals because they have no reference. + (jobLine.part_type === null && (jobLine.act_price || 0 > 0)) || + (jobLine.db_ref && jobLine.db_ref.startsWith("9360"))) && //This ref works in Canada, but DB_REFS in the US do not fill in. + !isPaintOrShopMat + ); } function CalculateAdditional(job) { - const stlTowing = job.cieca_stl?.data.find((c) => c.ttl_type === "OTTW"); - const stlStorage = job.cieca_stl?.data.find((c) => c.ttl_type === "OTST"); + const stlTowing = job.cieca_stl?.data.find((c) => c.ttl_type === "OTTW"); + const stlStorage = job.cieca_stl?.data.find((c) => c.ttl_type === "OTST"); - let ret = { - additionalCosts: null, - additionalCostItems: [], - adjustments: null, - towing: null, - shipping: Dinero(), - storage: null, - pvrt: null, - total: null, - }; - ret.towing = stlTowing - ? Dinero({amount: Math.round(stlTowing.ttl_amt * 100)}) - : Dinero({ - amount: Math.round((job.towing_payable || 0) * 100), - }); + let ret = { + additionalCosts: null, + additionalCostItems: [], + adjustments: null, + towing: null, + shipping: Dinero(), + storage: null, + pvrt: null, + total: null + }; + ret.towing = stlTowing + ? Dinero({ amount: Math.round(stlTowing.ttl_amt * 100) }) + : Dinero({ + amount: Math.round((job.towing_payable || 0) * 100) + }); - ret.additionalCosts = job.joblines - .filter((jl) => !jl.removed && IsAdditionalCost(jl)) - .reduce((acc, val) => { - const lineValue = Dinero({ - amount: Math.round((val.act_price || 0) * 100), - }).multiply(val.part_qty); + ret.additionalCosts = job.joblines + .filter((jl) => !jl.removed && IsAdditionalCost(jl)) + .reduce((acc, val) => { + const lineValue = Dinero({ + amount: Math.round((val.act_price || 0) * 100) + }).multiply(val.part_qty); - if (val.db_ref === "936004") { - //Shipping line IO-1921. - ret.shipping = ret.shipping.add(lineValue); - } + if (val.db_ref === "936004") { + //Shipping line IO-1921. + ret.shipping = ret.shipping.add(lineValue); + } - if (val.line_desc.toLowerCase().includes("towing")) { - ret.towing = lineValue; - return acc; - } else { - ret.additionalCostItems.push({key: val.line_desc, total: lineValue}); - return acc.add(lineValue); - } - }, Dinero()); - ret.adjustments = Dinero({ - amount: Math.round((job.adjustment_bottom_line || 0) * 100), - }); - ret.storage = stlStorage - ? Dinero({amount: Math.round(stlStorage.ttl_amt * 100)}) - : Dinero({ - amount: Math.round((job.storage_payable || 0) * 100), - }); - ret.pvrt = Dinero({ - amount: Math.round((job.ca_bc_pvrt || 0) * 100), - }); - ret.total = ret.additionalCosts - .add(ret.adjustments) //IO-813 Adjustment takes care of GST & PST at labor rate. - .add(ret.towing) - .add(ret.storage); + if (val.line_desc.toLowerCase().includes("towing")) { + ret.towing = lineValue; + return acc; + } else { + ret.additionalCostItems.push({ key: val.line_desc, total: lineValue }); + return acc.add(lineValue); + } + }, Dinero()); + ret.adjustments = Dinero({ + amount: Math.round((job.adjustment_bottom_line || 0) * 100) + }); + ret.storage = stlStorage + ? Dinero({ amount: Math.round(stlStorage.ttl_amt * 100) }) + : Dinero({ + amount: Math.round((job.storage_payable || 0) * 100) + }); + ret.pvrt = Dinero({ + amount: Math.round((job.ca_bc_pvrt || 0) * 100) + }); + ret.total = ret.additionalCosts + .add(ret.adjustments) //IO-813 Adjustment takes care of GST & PST at labor rate. + .add(ret.towing) + .add(ret.storage); - return ret; + return ret; } function CalculateTaxesTotals(job, otherTotals) { - const subtotal = otherTotals.parts.parts.subtotal - .add(otherTotals.parts.sublets.subtotal) - .add(otherTotals.rates.subtotal) //No longer using just rates subtotal to include mapa/mash. - .add(otherTotals.additional.total); + const subtotal = otherTotals.parts.parts.subtotal + .add(otherTotals.parts.sublets.subtotal) + .add(otherTotals.rates.subtotal) //No longer using just rates subtotal to include mapa/mash. + .add(otherTotals.additional.total); - //Potential issue here with Sublet Calculation. Sublets are calculated under labor in Mitchell, but it's done in IO - //Under the parts rates. + //Potential issue here with Sublet Calculation. Sublets are calculated under labor in Mitchell, but it's done in IO + //Under the parts rates. - let statePartsTax = Dinero(); - let additionalItemsTax = Dinero(); - let us_sales_tax_breakdown; + let statePartsTax = Dinero(); + let additionalItemsTax = Dinero(); + let us_sales_tax_breakdown; - //Audatex sends additional glass part types. IO-774 - const BackupGlassTax = - job.parts_tax_rates && - (job.parts_tax_rates.PAGD || - job.parts_tax_rates.PAGF || - job.parts_tax_rates.PAGP || - job.parts_tax_rates.PAGQ || - job.parts_tax_rates.PAGR); + //Audatex sends additional glass part types. IO-774 + const BackupGlassTax = + job.parts_tax_rates && + (job.parts_tax_rates.PAGD || + job.parts_tax_rates.PAGF || + job.parts_tax_rates.PAGP || + job.parts_tax_rates.PAGQ || + job.parts_tax_rates.PAGR); - const taxableAmounts = { - PAA: Dinero(), - PAN: Dinero(), - PAL: Dinero(), - PAR: Dinero(), - PAC: Dinero(), - PAG: Dinero(), - PAO: Dinero(), - PAS: Dinero(), - PAP: Dinero(), - PAM: Dinero(), + const taxableAmounts = { + PAA: Dinero(), + PAN: Dinero(), + PAL: Dinero(), + PAR: Dinero(), + PAC: Dinero(), + PAG: Dinero(), + PAO: Dinero(), + PAS: Dinero(), + PAP: Dinero(), + PAM: Dinero(), - LA1: Dinero(), - LA2: Dinero(), - LA3: Dinero(), - LA4: Dinero(), - LAU: Dinero(), - LAA: Dinero(), - LAB: Dinero(), - LAD: Dinero(), - LAE: Dinero(), - LAF: Dinero(), - LAG: Dinero(), - LAM: Dinero(), - LAR: Dinero(), - LAS: Dinero(), + LA1: Dinero(), + LA2: Dinero(), + LA3: Dinero(), + LA4: Dinero(), + LAU: Dinero(), + LAA: Dinero(), + LAB: Dinero(), + LAD: Dinero(), + LAE: Dinero(), + LAF: Dinero(), + LAG: Dinero(), + LAM: Dinero(), + LAR: Dinero(), + LAS: Dinero(), - MAPA: Dinero(), - MASH: Dinero(), - TOW: Dinero(), - STOR: Dinero(), - }; + MAPA: Dinero(), + MASH: Dinero(), + TOW: Dinero(), + STOR: Dinero() + }; - //For each line, determine if it's taxable, and if it is, add the line amount to the taxable amounts total. - job.joblines - .filter((jl) => !jl.removed) - .forEach((val) => { - if (!val.tax_part) return; - if (!val.part_type && IsAdditionalCost(val)) { - taxableAmounts.PAO = taxableAmounts.PAO.add( - Dinero({amount: Math.round((val.act_price || 0) * 100)}).multiply( - val.part_qty || 0 - ) - ); - } else if (!val.part_type) { - //Do nothing for now. - } else { - const typeOfPart = val.part_type; + //For each line, determine if it's taxable, and if it is, add the line amount to the taxable amounts total. + job.joblines + .filter((jl) => !jl.removed) + .forEach((val) => { + if (!val.tax_part) return; + if (!val.part_type && IsAdditionalCost(val)) { + taxableAmounts.PAO = taxableAmounts.PAO.add( + Dinero({ amount: Math.round((val.act_price || 0) * 100) }).multiply(val.part_qty || 0) + ); + } else if (!val.part_type) { + //Do nothing for now. + } else { + const typeOfPart = val.part_type; - const discMarkupAmount = - val.prt_dsmk_m && - val.prt_dsmk_m !== 0 && - DiscountNotAlreadyCounted(val, job.joblines) // DO WE NEED TO COUNT PFP DISCOUNT HERE? - ? val.prt_dsmk_m - ? Dinero({amount: Math.round(val.prt_dsmk_m * 100)}) - : Dinero({ - amount: Math.round(val.act_price * 100), - }) - .multiply(val.part_qty || 0) - .percentage(Math.abs(val.prt_dsmk_p || 0)) - .multiply(val.prt_dsmk_p > 0 ? 1 : -1) - : Dinero(); - - const partAmount = Dinero({ - amount: Math.round((val.act_price || 0) * 100), + const discMarkupAmount = + val.prt_dsmk_m && val.prt_dsmk_m !== 0 && DiscountNotAlreadyCounted(val, job.joblines) // DO WE NEED TO COUNT PFP DISCOUNT HERE? + ? val.prt_dsmk_m + ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(val.act_price * 100) }) - .multiply(val.part_qty || 0) - .add(discMarkupAmount); - taxableAmounts[typeOfPart] = taxableAmounts[typeOfPart].add(partAmount); - } - }); + .multiply(val.part_qty || 0) + .percentage(Math.abs(val.prt_dsmk_p || 0)) + .multiply(val.prt_dsmk_p > 0 ? 1 : -1) + : Dinero(); - //Check in the PFL file which types of labor are taxable. Add the amount that is considered taxable to the taxable amounts total. - Object.keys(taxableAmounts) - .filter((key) => key.startsWith("LA")) - .map((key) => { - const isLaborTypeTaxable = job.cieca_pfl[key]?.lbr_tax_in; - if (isLaborTypeTaxable) { - taxableAmounts[key] = taxableAmounts[key].add( - otherTotals.rates[key.toLowerCase()].total - ); - } - }); - - Object.keys(taxableAmounts) - .filter((key) => key.startsWith("MA")) - .map((key) => { - const isTypeTaxable = job.materials[key]?.tax_ind; - if (isTypeTaxable) { - taxableAmounts[key] = taxableAmounts[key].add( - otherTotals.rates[key.toLowerCase()].total - ); - } - }); - //Add towing and storage taxable amounts - const stlTowing = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTTW"); - const stlStorage = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTST"); - - if (stlTowing) - taxableAmounts.TOW = Dinero({ - amount: Math.round(stlTowing.t_amt * 100), - }); - if (stlStorage) - taxableAmounts.TOW = Dinero({ - amount: Math.round(stlStorage.t_amt * 100), - }); - - const pfp = job.parts_tax_rates; - - //For any profile level markups/discounts, add them in now as well. - Object.keys(otherTotals.parts.adjustments).forEach((key) => { - const adjustmentAmount = otherTotals.parts.adjustments[key]; - if (adjustmentAmount.getAmount() !== 0 && pfp[key]?.prt_tax_in) { - taxableAmounts[key] = taxableAmounts[key].add(adjustmentAmount); - } + const partAmount = Dinero({ + amount: Math.round((val.act_price || 0) * 100) + }) + .multiply(val.part_qty || 0) + .add(discMarkupAmount); + taxableAmounts[typeOfPart] = taxableAmounts[typeOfPart].add(partAmount); + } }); - console.log("*** Taxable Amounts***"); - console.table(JSON.parse(JSON.stringify(taxableAmounts))); - - //For the taxable amounts, figure out which tax type applies. - //Then sum up the total of that tax type and then calculate the thresholds. - - const taxableAmountsByTier = { - ty1Tax: Dinero(), - ty2Tax: Dinero(), - ty3Tax: Dinero(), - ty4Tax: Dinero(), - ty5Tax: Dinero(), - ty6Tax: Dinero(), - }; - const totalTaxByTier = { - ty1Tax: Dinero(), - ty2Tax: Dinero(), - ty3Tax: Dinero(), - ty4Tax: Dinero(), - ty5Tax: Dinero(), - ty6Tax: Dinero(), - }; - - const pfl = job.cieca_pfl; - const pfm = job.materials; - const pfo = job.cieca_pfo; - Object.keys(taxableAmounts).map((key) => { - try { - if (key.startsWith("PA")) { - const typeOfPart = key; // === "PAM" ? "PAC" : key; - //At least one of these scenarios must be taxable. - for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { - if (IsTrueOrYes(pfp[typeOfPart][`prt_tx_in${tyCounter}`])) { - //This amount is taxable for this type. - taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ - `ty${tyCounter}Tax` - ].add(taxableAmounts[typeOfPart]); - } - } - } else if (key.startsWith("MA")) { - //Materials Handling - for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { - if (IsTrueOrYes(pfm[key][`mat_tx_in${tyCounter}`])) { - //This amount is taxable for this type. - taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ - `ty${tyCounter}Tax` - ].add(taxableAmounts[key]); - } - } - } else if (key.startsWith("LA")) { - //Labor. - for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { - if (IsTrueOrYes(pfl[key][`lbr_tx_in${tyCounter}`])) { - //This amount is taxable for this type. - taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ - `ty${tyCounter}Tax` - ].add(taxableAmounts[key]); - } - } - } else if (key === "TOW") { - for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { - if (IsTrueOrYes(pfo[`tow_t_in${tyCounter}`])) { - //This amount is taxable for this type. - taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ - `ty${tyCounter}Tax` - ].add(taxableAmounts[key]); - } - } - } else if (key === "STOR") { - for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { - if (IsTrueOrYes(pfo[`stor_t_in${tyCounter}`])) { - //This amount is taxable for this type. - taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ - `ty${tyCounter}Tax` - ].add(taxableAmounts[key]); - } - } - } - } catch (error) { - console.error("Key with issue", key); - } + //Check in the PFL file which types of labor are taxable. Add the amount that is considered taxable to the taxable amounts total. + Object.keys(taxableAmounts) + .filter((key) => key.startsWith("LA")) + .map((key) => { + const isLaborTypeTaxable = job.cieca_pfl[key]?.lbr_tax_in; + if (isLaborTypeTaxable) { + taxableAmounts[key] = taxableAmounts[key].add(otherTotals.rates[key.toLowerCase()].total); + } }); - const remainingTaxableAmounts = taxableAmountsByTier; - console.log("*** Taxable Amounts by Tier***"); - console.table(JSON.parse(JSON.stringify(taxableAmountsByTier))); + Object.keys(taxableAmounts) + .filter((key) => key.startsWith("MA")) + .map((key) => { + const isTypeTaxable = job.materials[key]?.tax_ind; + if (isTypeTaxable) { + taxableAmounts[key] = taxableAmounts[key].add(otherTotals.rates[key.toLowerCase()].total); + } + }); + //Add towing and storage taxable amounts + const stlTowing = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTTW"); + const stlStorage = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTST"); - Object.keys(taxableAmountsByTier).forEach((taxTierKey) => { - try { - let tyCounter = taxTierKey[2]; //Get the number from the key. - //i represents the tax number. If we got here, this type of tax is applicable. Now we need to add based on the thresholds. - for (let threshCounter = 1; threshCounter <= 5; threshCounter++) { - const thresholdAmount = parseFloat( - job.cieca_pft[`ty${tyCounter}_thres${threshCounter}`] - ); - const thresholdTaxRate = parseFloat( - job.cieca_pft[`ty${tyCounter}_rate${threshCounter}`] - ); - - let taxableAmountInThisThreshold; - if (thresholdAmount === 9999.99 || InstanceMgr({debug:true,imex:false, rome: false, promanager:thresholdAmount === 0 && parseInt(tyCounter) === 1 }) ) { // - // THis is the last threshold. Tax the entire remaining amount. - taxableAmountInThisThreshold = remainingTaxableAmounts[taxTierKey]; - remainingTaxableAmounts[taxTierKey] = Dinero(); - } else { - if ( - thresholdAmount >= - remainingTaxableAmounts[taxTierKey].getAmount() / 100 - ) { - //This threshold is bigger than the remaining taxable balance. Add it all. - taxableAmountInThisThreshold = remainingTaxableAmounts[taxTierKey]; - remainingTaxableAmounts[taxTierKey] = Dinero(); - } else { - //Take the size of the threshold from the remaining amount, tax it, and do it all over. - taxableAmountInThisThreshold = Dinero({ - amount: Math.round(thresholdAmount * 100), - }); - remainingTaxableAmounts[taxTierKey] = remainingTaxableAmounts[ - taxTierKey - ].subtract( - Dinero({ - amount: Math.round(taxableAmountInThisThreshold * 100), - }) - ); - } - } - - const taxAmountToAdd = - taxableAmountInThisThreshold.percentage(thresholdTaxRate); - - totalTaxByTier[taxTierKey] = - totalTaxByTier[taxTierKey].add(taxAmountToAdd); - } - } catch (error) { - console.error("PFP Calculation error", error); - } + if (stlTowing) + taxableAmounts.TOW = Dinero({ + amount: Math.round(stlTowing.t_amt * 100) + }); + if (stlStorage) + taxableAmounts.TOW = Dinero({ + amount: Math.round(stlStorage.t_amt * 100) }); - // console.log("*** Total Tax by Tier Amounts***"); - // console.table(JSON.parse(JSON.stringify(totalTaxByTier))); + const pfp = job.parts_tax_rates; - statePartsTax = statePartsTax - .add(totalTaxByTier.ty1Tax) - .add(totalTaxByTier.ty2Tax) - .add(totalTaxByTier.ty3Tax) - .add(totalTaxByTier.ty4Tax) - .add(totalTaxByTier.ty5Tax) - .add(totalTaxByTier.ty6Tax); - us_sales_tax_breakdown = totalTaxByTier; - //console.log("Tiered Taxes Total for Parts/Labor", statePartsTax.toFormat()); - - let laborTaxTotal = Dinero(); - - if (Object.keys(job.cieca_pfl).length > 0) { - //Ignore it now, we have calculated it above. - //This was previously used for JCS before parts were also calculated at a different rate. - } else { - //We don't have it, just add in how it was before. - laborTaxTotal = otherTotals.rates.subtotal.percentage( - (job.tax_lbr_rt || 0) * 100 - ); // THis is currently using the lbr tax rate from PFH not PFL. + //For any profile level markups/discounts, add them in now as well. + Object.keys(otherTotals.parts.adjustments).forEach((key) => { + const adjustmentAmount = otherTotals.parts.adjustments[key]; + if (adjustmentAmount.getAmount() !== 0 && pfp[key]?.prt_tax_in) { + taxableAmounts[key] = taxableAmounts[key].add(adjustmentAmount); } + }); - //console.log("Labor Tax Total", laborTaxTotal.toFormat()); + console.log("*** Taxable Amounts***"); + console.table(JSON.parse(JSON.stringify(taxableAmounts))); - let ret = { - subtotal: subtotal, - federal_tax: subtotal - .percentage((job.federal_tax_rate || 0) * 100) - .add( - otherTotals.additional.pvrt.percentage( - (job.federal_tax_rate || 0) * 100 - ) - ), - statePartsTax, - us_sales_tax_breakdown, - state_tax: statePartsTax, - local_tax: subtotal.percentage((job.local_tax_rate || 0) * 100), - }; - ret.total_repairs = ret.subtotal - .add(ret.federal_tax) - .add(ret.state_tax) - .add(ret.local_tax) - .add(otherTotals.additional.pvrt); + //For the taxable amounts, figure out which tax type applies. + //Then sum up the total of that tax type and then calculate the thresholds. - ret.custPayable = { - deductible: Dinero({amount: Math.round((job.ded_amt || 0) * 100)}) || 0, - federal_tax: job.ca_gst_registrant - ? job.ca_customer_gst === 0 || job.ca_customer_gst === null - ? ret.federal_tax - : Dinero({amount: Math.round(job.ca_customer_gst * 100)}) - : Dinero(), - other_customer_amount: Dinero({ - amount: Math.round((job.other_amount_payable || 0) * 100), - }), - dep_taxes: Dinero({ - amount: Math.round((job.depreciation_taxes || 0) * 100), - }), - }; + const taxableAmountsByTier = { + ty1Tax: Dinero(), + ty2Tax: Dinero(), + ty3Tax: Dinero(), + ty4Tax: Dinero(), + ty5Tax: Dinero(), + ty6Tax: Dinero() + }; + const totalTaxByTier = { + ty1Tax: Dinero(), + ty2Tax: Dinero(), + ty3Tax: Dinero(), + ty4Tax: Dinero(), + ty5Tax: Dinero(), + ty6Tax: Dinero() + }; - ret.custPayable.total = ret.custPayable.deductible - .add(ret.custPayable.federal_tax) - .add(ret.custPayable.other_customer_amount) - .add(ret.custPayable.dep_taxes); + const pfl = job.cieca_pfl; + const pfm = job.materials; + const pfo = job.cieca_pfo; + Object.keys(taxableAmounts).map((key) => { + try { + if (key.startsWith("PA")) { + const typeOfPart = key; // === "PAM" ? "PAC" : key; + //At least one of these scenarios must be taxable. + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + if (IsTrueOrYes(pfp[typeOfPart][`prt_tx_in${tyCounter}`])) { + //This amount is taxable for this type. + taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add( + taxableAmounts[typeOfPart] + ); + } + } + } else if (key.startsWith("MA")) { + //Materials Handling + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + if (IsTrueOrYes(pfm[key][`mat_tx_in${tyCounter}`])) { + //This amount is taxable for this type. + taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add( + taxableAmounts[key] + ); + } + } + } else if (key.startsWith("LA")) { + //Labor. + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + if (IsTrueOrYes(pfl[key][`lbr_tx_in${tyCounter}`])) { + //This amount is taxable for this type. + taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add( + taxableAmounts[key] + ); + } + } + } else if (key === "TOW") { + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + if (IsTrueOrYes(pfo[`tow_t_in${tyCounter}`])) { + //This amount is taxable for this type. + taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add( + taxableAmounts[key] + ); + } + } + } else if (key === "STOR") { + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + if (IsTrueOrYes(pfo[`stor_t_in${tyCounter}`])) { + //This amount is taxable for this type. + taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add( + taxableAmounts[key] + ); + } + } + } + } catch (error) { + console.error("Key with issue", key); + } + }); - ret.net_repairs = ret.total_repairs.subtract(ret.custPayable.total); + const remainingTaxableAmounts = taxableAmountsByTier; + console.log("*** Taxable Amounts by Tier***"); + console.table(JSON.parse(JSON.stringify(taxableAmountsByTier))); - return ret; + Object.keys(taxableAmountsByTier).forEach((taxTierKey) => { + try { + let tyCounter = taxTierKey[2]; //Get the number from the key. + //i represents the tax number. If we got here, this type of tax is applicable. Now we need to add based on the thresholds. + for (let threshCounter = 1; threshCounter <= 5; threshCounter++) { + const thresholdAmount = parseFloat(job.cieca_pft[`ty${tyCounter}_thres${threshCounter}`]); + const thresholdTaxRate = parseFloat(job.cieca_pft[`ty${tyCounter}_rate${threshCounter}`]); + + let taxableAmountInThisThreshold; + if ( + thresholdAmount === 9999.99 || + InstanceMgr({ + debug: true, + imex: false, + rome: false, + promanager: thresholdAmount === 0 && parseInt(tyCounter) === 1 + }) + ) { + // + // THis is the last threshold. Tax the entire remaining amount. + taxableAmountInThisThreshold = remainingTaxableAmounts[taxTierKey]; + remainingTaxableAmounts[taxTierKey] = Dinero(); + } else { + if (thresholdAmount >= remainingTaxableAmounts[taxTierKey].getAmount() / 100) { + //This threshold is bigger than the remaining taxable balance. Add it all. + taxableAmountInThisThreshold = remainingTaxableAmounts[taxTierKey]; + remainingTaxableAmounts[taxTierKey] = Dinero(); + } else { + //Take the size of the threshold from the remaining amount, tax it, and do it all over. + taxableAmountInThisThreshold = Dinero({ + amount: Math.round(thresholdAmount * 100) + }); + remainingTaxableAmounts[taxTierKey] = remainingTaxableAmounts[taxTierKey].subtract( + Dinero({ + amount: Math.round(taxableAmountInThisThreshold * 100) + }) + ); + } + } + + const taxAmountToAdd = taxableAmountInThisThreshold.percentage(thresholdTaxRate); + + totalTaxByTier[taxTierKey] = totalTaxByTier[taxTierKey].add(taxAmountToAdd); + } + } catch (error) { + console.error("PFP Calculation error", error); + } + }); + + // console.log("*** Total Tax by Tier Amounts***"); + // console.table(JSON.parse(JSON.stringify(totalTaxByTier))); + + statePartsTax = statePartsTax + .add(totalTaxByTier.ty1Tax) + .add(totalTaxByTier.ty2Tax) + .add(totalTaxByTier.ty3Tax) + .add(totalTaxByTier.ty4Tax) + .add(totalTaxByTier.ty5Tax) + .add(totalTaxByTier.ty6Tax); + us_sales_tax_breakdown = totalTaxByTier; + //console.log("Tiered Taxes Total for Parts/Labor", statePartsTax.toFormat()); + + let laborTaxTotal = Dinero(); + + if (Object.keys(job.cieca_pfl).length > 0) { + //Ignore it now, we have calculated it above. + //This was previously used for JCS before parts were also calculated at a different rate. + } else { + //We don't have it, just add in how it was before. + laborTaxTotal = otherTotals.rates.subtotal.percentage((job.tax_lbr_rt || 0) * 100); // THis is currently using the lbr tax rate from PFH not PFL. + } + + //console.log("Labor Tax Total", laborTaxTotal.toFormat()); + + let ret = { + subtotal: subtotal, + federal_tax: subtotal + .percentage((job.federal_tax_rate || 0) * 100) + .add(otherTotals.additional.pvrt.percentage((job.federal_tax_rate || 0) * 100)), + statePartsTax, + us_sales_tax_breakdown, + state_tax: statePartsTax, + local_tax: subtotal.percentage((job.local_tax_rate || 0) * 100) + }; + ret.total_repairs = ret.subtotal + .add(ret.federal_tax) + .add(ret.state_tax) + .add(ret.local_tax) + .add(otherTotals.additional.pvrt); + + ret.custPayable = { + deductible: Dinero({ amount: Math.round((job.ded_amt || 0) * 100) }) || 0, + federal_tax: job.ca_gst_registrant + ? job.ca_customer_gst === 0 || job.ca_customer_gst === null + ? ret.federal_tax + : Dinero({ amount: Math.round(job.ca_customer_gst * 100) }) + : Dinero(), + other_customer_amount: Dinero({ + amount: Math.round((job.other_amount_payable || 0) * 100) + }), + dep_taxes: Dinero({ + amount: Math.round((job.depreciation_taxes || 0) * 100) + }) + }; + + ret.custPayable.total = ret.custPayable.deductible + .add(ret.custPayable.federal_tax) + .add(ret.custPayable.other_customer_amount) + .add(ret.custPayable.dep_taxes); + + ret.net_repairs = ret.total_repairs.subtract(ret.custPayable.total); + + return ret; } exports.default = Totals; function DiscountNotAlreadyCounted(jobline, joblines) { - return false; + return false; } exports.DiscountNotAlreadyCounted = DiscountNotAlreadyCounted; function ParseCalopCode(opcode) { - if (!opcode) return []; - return opcode.trim().split(" "); + if (!opcode) return []; + return opcode.trim().split(" "); } function IsTrueOrYes(value) { - return value === true || value === "Y" || value === "y"; + return value === true || value === "Y" || value === "y"; } async function UpdateJobLines(joblinesToUpdate) { - if (joblinesToUpdate.length === 0) return; - const updateQueries = joblinesToUpdate.map((line, index) => - generateUpdateQuery(_.pick(line, ["id", "prt_dsmk_m", "prt_dsmk_p"]), index) - ); - const query = ` + if (joblinesToUpdate.length === 0) return; + const updateQueries = joblinesToUpdate.map((line, index) => + generateUpdateQuery(_.pick(line, ["id", "prt_dsmk_m", "prt_dsmk_p"]), index) + ); + const query = ` mutation UPDATE_EST_LINES{ ${updateQueries} } `; - const result = await adminClient.request(query); + const result = await adminClient.request(query); } const generateUpdateQuery = (lineToUpdate, index) => { - return ` + return ` update_joblines${index}: update_joblines(where: { id: { _eq: "${ - lineToUpdate.id - }" } }, _set: ${JSON.stringify(lineToUpdate).replace( - /"(\w+)"\s*:/g, - "$1:" - )}) { + lineToUpdate.id + }" } }, _set: ${JSON.stringify(lineToUpdate).replace(/"(\w+)"\s*:/g, "$1:")}) { returning { id } diff --git a/server/job/job-totals.js b/server/job/job-totals.js index cdfff6726..0da291120 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -15,715 +15,654 @@ const logger = require("../utils/logger"); //****************************************************** */ //****************************************************** */ - // Dinero.defaultCurrency = "USD"; // Dinero.globalLocale = "en-CA"; Dinero.globalRoundingMode = "HALF_EVEN"; exports.totalsSsu = async function (req, res) { - const {id} = req.body; + const { id } = req.body; - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; - logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null); + logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null); - try { - const job = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.GET_JOB_BY_PK, { - id: id, - }); + try { + const job = await client.setHeaders({ Authorization: BearerToken }).request(queries.GET_JOB_BY_PK, { + id: id + }); - const newTotals = await TotalsServerSide( - {body: {job: job.jobs_by_pk, client: client}}, - res, - true - ); + const newTotals = await TotalsServerSide({ body: { job: job.jobs_by_pk, client: client } }, res, true); - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.UPDATE_JOB, { - jobId: id, - job: { - clm_total: newTotals.totals.total_repairs.toFormat("0.00"), - owner_owing: newTotals.totals.custPayable.total.toFormat("0.00"), - job_totals: newTotals, - //queued_for_parts: true, - }, - }); + const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.UPDATE_JOB, { + jobId: id, + job: { + clm_total: newTotals.totals.total_repairs.toFormat("0.00"), + owner_owing: newTotals.totals.custPayable.total.toFormat("0.00"), + job_totals: newTotals + //queued_for_parts: true, + } + }); - res.status(200).send(); - } catch (error) { - logger.log("job-totals-ssu-error", "ERROR", req.user.email, id, { - jobid: id, - error, - }); - res.status(503).send(); - } + res.status(200).send(); + } catch (error) { + logger.log("job-totals-ssu-error", "ERROR", req.user.email, id, { + jobid: id, + error + }); + res.status(503).send(); + } }; //IMPORTANT*** These two functions MUST be mirrrored. async function TotalsServerSide(req, res) { - const {job, client} = req.body; - await AutoAddAtsIfRequired({job: job, client: client}); + const { job, client } = req.body; + await AutoAddAtsIfRequired({ job: job, client: client }); - try { - let ret = { - parts: CalculatePartsTotals(job.joblines), - rates: CalculateRatesTotals(job), - additional: CalculateAdditional(job), - }; - ret.totals = CalculateTaxesTotals(job, ret); + try { + let ret = { + parts: CalculatePartsTotals(job.joblines), + rates: CalculateRatesTotals(job), + additional: CalculateAdditional(job) + }; + ret.totals = CalculateTaxesTotals(job, ret); - return ret; - } catch (error) { - logger.log("job-totals-ssu-error", "ERROR", req?.user?.email, job.id, { - jobid: job.id, - error, - }); - res.status(400).send(JSON.stringify(error)); - } + return ret; + } catch (error) { + logger.log("job-totals-ssu-error", "ERROR", req?.user?.email, job.id, { + jobid: job.id, + error + }); + res.status(400).send(JSON.stringify(error)); + } } async function Totals(req, res) { - const {job, id} = req.body; + const { job, id } = req.body; - const logger = req.logger; - const client = req.userGraphQLClient; + const logger = req.logger; + const client = req.userGraphQLClient; - logger.log("job-totals", "DEBUG", req.user.email, job.id, { - jobid: job.id, + logger.log("job-totals", "DEBUG", req.user.email, job.id, { + jobid: job.id + }); + + logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null); + + await AutoAddAtsIfRequired({ job, client }); + + try { + let ret = { + parts: CalculatePartsTotals(job.joblines), + rates: CalculateRatesTotals(job), + additional: CalculateAdditional(job) + }; + ret.totals = CalculateTaxesTotals(job, ret); + + res.status(200).json(ret); + } catch (error) { + logger.log("job-totals-error", "ERROR", req.user.email, job.id, { + jobid: job.id, + error }); - - logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null); - - await AutoAddAtsIfRequired({job, client}); - - try { - let ret = { - parts: CalculatePartsTotals(job.joblines), - rates: CalculateRatesTotals(job), - additional: CalculateAdditional(job), - }; - ret.totals = CalculateTaxesTotals(job, ret); - - res.status(200).json(ret); - } catch (error) { - logger.log("job-totals-error", "ERROR", req.user.email, job.id, { - jobid: job.id, - error, - }); - res.status(400).send(JSON.stringify(error)); - } + res.status(400).send(JSON.stringify(error)); + } } -async function AutoAddAtsIfRequired({job, client}) { - //Check if ATS should be automatically added. - if (job.auto_add_ats) { - //Get the total sum of hours that should be the ATS amount. - //Check to see if an ATS line exists. - let atsLineIndex = null; - const atsHours = job.joblines.reduce((acc, val, index) => { - if (val.line_desc && val.line_desc.toLowerCase() === "ats amount") { - atsLineIndex = index; - } +async function AutoAddAtsIfRequired({ job, client }) { + //Check if ATS should be automatically added. + if (job.auto_add_ats) { + //Get the total sum of hours that should be the ATS amount. + //Check to see if an ATS line exists. + let atsLineIndex = null; + const atsHours = job.joblines.reduce((acc, val, index) => { + if (val.line_desc && val.line_desc.toLowerCase() === "ats amount") { + atsLineIndex = index; + } - if ( - val.mod_lbr_ty !== "LA1" && - val.mod_lbr_ty !== "LA2" && - val.mod_lbr_ty !== "LA3" && - val.mod_lbr_ty !== "LA4" && - val.mod_lbr_ty !== "LAU" && - val.mod_lbr_ty !== "LAG" && - val.mod_lbr_ty !== "LAS" && - val.mod_lbr_ty !== "LAA" - ) { - acc = acc + val.mod_lb_hrs; - } + if ( + val.mod_lbr_ty !== "LA1" && + val.mod_lbr_ty !== "LA2" && + val.mod_lbr_ty !== "LA3" && + val.mod_lbr_ty !== "LA4" && + val.mod_lbr_ty !== "LAU" && + val.mod_lbr_ty !== "LAG" && + val.mod_lbr_ty !== "LAS" && + val.mod_lbr_ty !== "LAA" + ) { + acc = acc + val.mod_lb_hrs; + } - return acc; - }, 0); + return acc; + }, 0); - const atsAmount = atsHours * (job.rate_ats || 0); - //If it does, update it in place, and make sure it is updated for local calculations. - if (atsLineIndex === null) { - const newAtsLine = { - jobid: job.id, - alt_partm: null, - line_no: 35, - unq_seq: 0, - line_ind: "E", - line_desc: "ATS Amount", - line_ref: 0.0, - part_type: null, - oem_partno: null, - db_price: 0.0, - act_price: atsAmount, - part_qty: 1, - mod_lbr_ty: null, - db_hrs: 0.0, - mod_lb_hrs: 0.0, - lbr_op: "OP13", - lbr_amt: 0.0, - op_code_desc: "ADDITIONAL COSTS", - status: null, - location: null, - tax_part: true, - db_ref: null, - manual_line: true, - prt_dsmk_p: 0.0, - prt_dsmk_m: 0.0, - }; + const atsAmount = atsHours * (job.rate_ats || 0); + //If it does, update it in place, and make sure it is updated for local calculations. + if (atsLineIndex === null) { + const newAtsLine = { + jobid: job.id, + alt_partm: null, + line_no: 35, + unq_seq: 0, + line_ind: "E", + line_desc: "ATS Amount", + line_ref: 0.0, + part_type: null, + oem_partno: null, + db_price: 0.0, + act_price: atsAmount, + part_qty: 1, + mod_lbr_ty: null, + db_hrs: 0.0, + mod_lb_hrs: 0.0, + lbr_op: "OP13", + lbr_amt: 0.0, + op_code_desc: "ADDITIONAL COSTS", + status: null, + location: null, + tax_part: true, + db_ref: null, + manual_line: true, + prt_dsmk_p: 0.0, + prt_dsmk_m: 0.0 + }; - const result = await client.request(queries.INSERT_NEW_JOB_LINE, { - lineInput: [newAtsLine], - }); + const result = await client.request(queries.INSERT_NEW_JOB_LINE, { + lineInput: [newAtsLine] + }); - job.joblines.push(newAtsLine); - } - //If it does not, create one for local calculations and insert it. - else { - const result = await client.request(queries.UPDATE_JOB_LINE, { - line: {act_price: atsAmount}, - lineId: job.joblines[atsLineIndex].id, - }); - job.joblines[atsLineIndex].act_price = atsAmount; - } - - console.log(job.jobLines); + job.joblines.push(newAtsLine); } + //If it does not, create one for local calculations and insert it. + else { + const result = await client.request(queries.UPDATE_JOB_LINE, { + line: { act_price: atsAmount }, + lineId: job.joblines[atsLineIndex].id + }); + job.joblines[atsLineIndex].act_price = atsAmount; + } + + console.log(job.jobLines); + } } function CalculateRatesTotals(ratesList) { - const jobLines = ratesList.joblines.filter((jl) => !jl.removed); + const jobLines = ratesList.joblines.filter((jl) => !jl.removed); - let ret = { - la1: { - hours: 0, - rate: ratesList.rate_la1 || 0, - }, - la2: { - hours: 0, - rate: ratesList.rate_la2 || 0, - }, - la3: { - rate: ratesList.rate_la3 || 0, - hours: 0, - }, - la4: { - rate: ratesList.rate_la4 || 0, - hours: 0, - }, - laa: { - rate: ratesList.rate_laa || 0, - hours: 0, - }, - lab: { - rate: ratesList.rate_lab || 0, - hours: 0, - }, - lad: { - rate: ratesList.rate_lad || 0, - hours: 0, - }, - lae: { - rate: ratesList.rate_lae || 0, - hours: 0, - }, - laf: { - rate: ratesList.rate_laf || 0, - hours: 0, - }, - lag: { - rate: ratesList.rate_lag || 0, - hours: 0, - }, - lam: { - rate: ratesList.rate_lam || 0, - hours: 0, - }, - lar: { - rate: ratesList.rate_lar || 0, - hours: 0, - }, - las: { - rate: ratesList.rate_las || 0, - hours: 0, - }, - lau: { - rate: ratesList.rate_lau || 0, - hours: 0, - }, - mapa: { - rate: ratesList.rate_mapa || 0, - hours: 0, - }, - mash: { - rate: ratesList.rate_mash || 0, - hours: 0, - }, - }; + let ret = { + la1: { + hours: 0, + rate: ratesList.rate_la1 || 0 + }, + la2: { + hours: 0, + rate: ratesList.rate_la2 || 0 + }, + la3: { + rate: ratesList.rate_la3 || 0, + hours: 0 + }, + la4: { + rate: ratesList.rate_la4 || 0, + hours: 0 + }, + laa: { + rate: ratesList.rate_laa || 0, + hours: 0 + }, + lab: { + rate: ratesList.rate_lab || 0, + hours: 0 + }, + lad: { + rate: ratesList.rate_lad || 0, + hours: 0 + }, + lae: { + rate: ratesList.rate_lae || 0, + hours: 0 + }, + laf: { + rate: ratesList.rate_laf || 0, + hours: 0 + }, + lag: { + rate: ratesList.rate_lag || 0, + hours: 0 + }, + lam: { + rate: ratesList.rate_lam || 0, + hours: 0 + }, + lar: { + rate: ratesList.rate_lar || 0, + hours: 0 + }, + las: { + rate: ratesList.rate_las || 0, + hours: 0 + }, + lau: { + rate: ratesList.rate_lau || 0, + hours: 0 + }, + mapa: { + rate: ratesList.rate_mapa || 0, + hours: 0 + }, + mash: { + rate: ratesList.rate_mash || 0, + hours: 0 + } + }; - //Determine if there are MAPA and MASH lines already on the estimate. - //If there are, don't do anything extra (mitchell estimate) - //Otherwise, calculate them and add them to the default MAPA and MASH centers. - let hasMapaLine = false; - let hasMashLine = false; + //Determine if there are MAPA and MASH lines already on the estimate. + //If there are, don't do anything extra (mitchell estimate) + //Otherwise, calculate them and add them to the default MAPA and MASH centers. + let hasMapaLine = false; + let hasMashLine = false; - jobLines.forEach((item) => { - //IO-1317 Use the lines on the estimate if they exist instead. - if (item.db_ref === "936008") { - //If either of these DB REFs change, they also need to change in job-totals/job-costing calculations. - hasMapaLine = true; - ret["mapa"].total = Dinero({ - amount: Math.round((item.act_price || 0) * 100), - }); - } - if (item.db_ref === "936007") { - hasMashLine = true; - ret["mash"].total = Dinero({ - amount: Math.round((item.act_price || 0) * 100), - }); - } - - if (item.mod_lbr_ty) { - //Check to see if it has 0 hours and a price instead. - if ( - item.mod_lb_hrs === 0 && - item.act_price > 0 && - item.lbr_op === "OP14" - ) { - //Scenario where SGI may pay out hours using a part price. - if (!ret[item.mod_lbr_ty.toLowerCase()].total) { - ret[item.mod_lbr_ty.toLowerCase()].total = Dinero(); - } - ret[item.mod_lbr_ty.toLowerCase()].total = ret[ - item.mod_lbr_ty.toLowerCase() - ].total.add( - Dinero({amount: Math.round((item.act_price || 0) * 100)}).multiply( - item.part_qty - ) - ); - } - - //There's a labor type, assign the hours. - ret[item.mod_lbr_ty.toLowerCase()].hours = - ret[item.mod_lbr_ty.toLowerCase()].hours + item.mod_lb_hrs; - - if (item.mod_lbr_ty === "LAR") { - ret.mapa.hours = ret.mapa.hours + item.mod_lb_hrs; - } else { - ret.mash.hours = ret.mash.hours + item.mod_lb_hrs; //Apparently there may be an exclusion for glass hours in BC. - } - } - }); - - let subtotal = Dinero({amount: 0}); - let rates_subtotal = Dinero({amount: 0}); - - for (const property in ret) { - //Skip calculating mapa and mash if we got the amounts. - if ( - !( - (property === "mapa" && hasMapaLine) || - (property === "mash" && hasMashLine) - ) - ) { - if (!ret[property].total) { - ret[property].total = Dinero(); - } - let threshold; - //Check if there is a max for this type. - if (ratesList.materials && ratesList.materials[property]) { - // - if ( - ratesList.materials[property].cal_maxdlr && - ratesList.materials[property].cal_maxdlr > 0 - ) { - //It has an upper threshhold. - threshold = Dinero({ - amount: Math.round(ratesList.materials[property].cal_maxdlr * 100), - }); - } - } - - const total = Dinero({ - amount: Math.round((ret[property].rate || 0) * 100), - }).multiply(ret[property].hours); - - if (threshold && total.greaterThanOrEqual(threshold)) { - ret[property].total = ret[property].total.add(threshold); - } else { - ret[property].total = ret[property].total.add(total); - } - } - - subtotal = subtotal.add(ret[property].total); - - if (property !== "mapa" && property !== "mash") - rates_subtotal = rates_subtotal.add(ret[property].total); + jobLines.forEach((item) => { + //IO-1317 Use the lines on the estimate if they exist instead. + if (item.db_ref === "936008") { + //If either of these DB REFs change, they also need to change in job-totals/job-costing calculations. + hasMapaLine = true; + ret["mapa"].total = Dinero({ + amount: Math.round((item.act_price || 0) * 100) + }); + } + if (item.db_ref === "936007") { + hasMashLine = true; + ret["mash"].total = Dinero({ + amount: Math.round((item.act_price || 0) * 100) + }); } - ret.subtotal = subtotal; - ret.rates_subtotal = rates_subtotal; + if (item.mod_lbr_ty) { + //Check to see if it has 0 hours and a price instead. + if (item.mod_lb_hrs === 0 && item.act_price > 0 && item.lbr_op === "OP14") { + //Scenario where SGI may pay out hours using a part price. + if (!ret[item.mod_lbr_ty.toLowerCase()].total) { + ret[item.mod_lbr_ty.toLowerCase()].total = Dinero(); + } + ret[item.mod_lbr_ty.toLowerCase()].total = ret[item.mod_lbr_ty.toLowerCase()].total.add( + Dinero({ amount: Math.round((item.act_price || 0) * 100) }).multiply(item.part_qty) + ); + } - return ret; + //There's a labor type, assign the hours. + ret[item.mod_lbr_ty.toLowerCase()].hours = ret[item.mod_lbr_ty.toLowerCase()].hours + item.mod_lb_hrs; + + if (item.mod_lbr_ty === "LAR") { + ret.mapa.hours = ret.mapa.hours + item.mod_lb_hrs; + } else { + ret.mash.hours = ret.mash.hours + item.mod_lb_hrs; //Apparently there may be an exclusion for glass hours in BC. + } + } + }); + + let subtotal = Dinero({ amount: 0 }); + let rates_subtotal = Dinero({ amount: 0 }); + + for (const property in ret) { + //Skip calculating mapa and mash if we got the amounts. + if (!((property === "mapa" && hasMapaLine) || (property === "mash" && hasMashLine))) { + if (!ret[property].total) { + ret[property].total = Dinero(); + } + let threshold; + //Check if there is a max for this type. + if (ratesList.materials && ratesList.materials[property]) { + // + if (ratesList.materials[property].cal_maxdlr && ratesList.materials[property].cal_maxdlr > 0) { + //It has an upper threshhold. + threshold = Dinero({ + amount: Math.round(ratesList.materials[property].cal_maxdlr * 100) + }); + } + } + + const total = Dinero({ + amount: Math.round((ret[property].rate || 0) * 100) + }).multiply(ret[property].hours); + + if (threshold && total.greaterThanOrEqual(threshold)) { + ret[property].total = ret[property].total.add(threshold); + } else { + ret[property].total = ret[property].total.add(total); + } + } + + subtotal = subtotal.add(ret[property].total); + + if (property !== "mapa" && property !== "mash") rates_subtotal = rates_subtotal.add(ret[property].total); + } + + ret.subtotal = subtotal; + ret.rates_subtotal = rates_subtotal; + + return ret; } function CalculatePartsTotals(jobLines) { - const jl = jobLines.filter((jl) => !jl.removed); + const jl = jobLines.filter((jl) => !jl.removed); - const ret = jl.reduce( - (acc, value) => { - switch (value.part_type) { - case "PAS": - case "PASL": - return { - ...acc, - sublets: { - ...acc.sublets, - subtotal: acc.sublets.subtotal.add( - Dinero({ - amount: Math.round(value.act_price * 100), - }) - .multiply(value.part_qty || 0) - .add( - ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || - (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && - DiscountNotAlreadyCounted(value, jl) - ? value.prt_dsmk_m - ? Dinero({amount: Math.round(value.prt_dsmk_m * 100)}) - : Dinero({ - amount: Math.round(value.act_price * 100), - }) - .multiply(value.part_qty || 0) - .percentage(Math.abs(value.prt_dsmk_p || 0)) - .multiply(value.prt_dsmk_p > 0 ? 1 : -1) - : Dinero() - ) - ), - }, - }; - - default: - if ( - !value.part_type && - value.db_ref !== "900510" && - value.db_ref !== "900511" - ) - return acc; - return { - ...acc, - parts: { - ...acc.parts, - prt_dsmk_total: acc.parts.prt_dsmk_total.add( - ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || - (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && - DiscountNotAlreadyCounted(value, jl) - ? value.prt_dsmk_m - ? Dinero({amount: Math.round(value.prt_dsmk_m * 100)}) - : Dinero({ - amount: Math.round(value.act_price * 100), - }) - .multiply(value.part_qty || 0) - .percentage(Math.abs(value.prt_dsmk_p || 0)) - .multiply(value.prt_dsmk_p > 0 ? 1 : -1) - : Dinero() - ), - ...(value.part_type - ? { - list: { - ...acc.parts.list, - [value.part_type]: - acc.parts.list[value.part_type] && - acc.parts.list[value.part_type].total - ? { - total: acc.parts.list[value.part_type].total.add( - Dinero({ - amount: Math.round( - (value.act_price || 0) * 100 - ), - }).multiply(value.part_qty || 0) - ), - } - : { - total: Dinero({ - amount: Math.round( - (value.act_price || 0) * 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( - ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || - (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && - DiscountNotAlreadyCounted(value, jl) - ? value.prt_dsmk_m - ? Dinero({amount: Math.round(value.prt_dsmk_m * 100)}) - : Dinero({ - amount: Math.round(value.act_price * 100), - }) - .multiply(value.part_qty || 0) - .percentage(Math.abs(value.prt_dsmk_p || 0)) - .multiply(value.prt_dsmk_p > 0 ? 1 : -1) - : Dinero() - ), - }, - }; - } - }, - { - parts: { - list: {}, - prt_dsmk_total: Dinero(), - subtotal: Dinero({amount: 0}), - total: Dinero({amount: 0}), - }, + const ret = jl.reduce( + (acc, value) => { + switch (value.part_type) { + case "PAS": + case "PASL": + return { + ...acc, sublets: { - subtotal: Dinero({amount: 0}), + ...acc.sublets, + subtotal: acc.sublets.subtotal.add( + Dinero({ + amount: Math.round(value.act_price * 100) + }) + .multiply(value.part_qty || 0) + .add( + ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(value, jl) + ? value.prt_dsmk_m + ? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(value.act_price * 100) + }) + .multiply(value.part_qty || 0) + .percentage(Math.abs(value.prt_dsmk_p || 0)) + .multiply(value.prt_dsmk_p > 0 ? 1 : -1) + : Dinero() + ) + ) + } + }; - total: Dinero({amount: 0}), - }, - } - ); + default: + if (!value.part_type && value.db_ref !== "900510" && value.db_ref !== "900511") return acc; + return { + ...acc, + parts: { + ...acc.parts, + prt_dsmk_total: acc.parts.prt_dsmk_total.add( + ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(value, jl) + ? value.prt_dsmk_m + ? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(value.act_price * 100) + }) + .multiply(value.part_qty || 0) + .percentage(Math.abs(value.prt_dsmk_p || 0)) + .multiply(value.prt_dsmk_p > 0 ? 1 : -1) + : Dinero() + ), + ...(value.part_type + ? { + list: { + ...acc.parts.list, + [value.part_type]: + acc.parts.list[value.part_type] && acc.parts.list[value.part_type].total + ? { + total: acc.parts.list[value.part_type].total.add( + Dinero({ + amount: Math.round((value.act_price || 0) * 100) + }).multiply(value.part_qty || 0) + ) + } + : { + total: Dinero({ + amount: Math.round((value.act_price || 0) * 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( + ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(value, jl) + ? value.prt_dsmk_m + ? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(value.act_price * 100) + }) + .multiply(value.part_qty || 0) + .percentage(Math.abs(value.prt_dsmk_p || 0)) + .multiply(value.prt_dsmk_p > 0 ? 1 : -1) + : Dinero() + ) + } + }; + } + }, + { + parts: { + list: {}, + prt_dsmk_total: Dinero(), + subtotal: Dinero({ amount: 0 }), + total: Dinero({ amount: 0 }) + }, + sublets: { + subtotal: Dinero({ amount: 0 }), - return { - parts: { - ...ret.parts, - total: ret.parts.subtotal, - }, - sublets: { - ...ret.sublets, - total: ret.sublets.subtotal, - }, - }; + total: Dinero({ amount: 0 }) + } + } + ); + + return { + parts: { + ...ret.parts, + total: ret.parts.subtotal + }, + sublets: { + ...ret.sublets, + total: ret.sublets.subtotal + } + }; } function IsAdditionalCost(jobLine) { - //May be able to use db_ref here to help. - //936012 is Haz Waste Dispoal - //936008 is Paint/Materials - //936007 is Shop/Materials + //May be able to use db_ref here to help. + //936012 is Haz Waste Dispoal + //936008 is Paint/Materials + //936007 is Shop/Materials - //Remove paint and shop mat lines. They're calculated under rates. - const isPaintOrShopMat = - jobLine.db_ref === "936008" || jobLine.db_ref === "936007"; + //Remove paint and shop mat lines. They're calculated under rates. + const isPaintOrShopMat = jobLine.db_ref === "936008" || jobLine.db_ref === "936007"; - return ( - (jobLine.lbr_op === "OP13" || //Added to resolve manual job lines coming into other totals because they have no reference. - (jobLine.db_ref && jobLine.db_ref.startsWith("9360"))) && - !isPaintOrShopMat - ); + return ( + (jobLine.lbr_op === "OP13" || //Added to resolve manual job lines coming into other totals because they have no reference. + (jobLine.db_ref && jobLine.db_ref.startsWith("9360"))) && + !isPaintOrShopMat + ); } function CalculateAdditional(job) { - let ret = { - additionalCosts: null, - additionalCostItems: [], - adjustments: null, - towing: null, - shipping: Dinero(), - storage: null, - pvrt: null, - total: null, - }; - ret.towing = Dinero({ - amount: Math.round((job.towing_payable || 0) * 100), - }); + let ret = { + additionalCosts: null, + additionalCostItems: [], + adjustments: null, + towing: null, + shipping: Dinero(), + storage: null, + pvrt: null, + total: null + }; + ret.towing = Dinero({ + amount: Math.round((job.towing_payable || 0) * 100) + }); - ret.additionalCosts = job.joblines - .filter((jl) => !jl.removed && IsAdditionalCost(jl)) - .reduce((acc, val) => { - const lineValue = Dinero({ - amount: Math.round((val.act_price || 0) * 100), - }).multiply(val.part_qty || 1); + ret.additionalCosts = job.joblines + .filter((jl) => !jl.removed && IsAdditionalCost(jl)) + .reduce((acc, val) => { + const lineValue = Dinero({ + amount: Math.round((val.act_price || 0) * 100) + }).multiply(val.part_qty || 1); - if (val.db_ref === "936004") { - //Shipping line IO-1921. - ret.shipping = ret.shipping.add(lineValue); - } + if (val.db_ref === "936004") { + //Shipping line IO-1921. + ret.shipping = ret.shipping.add(lineValue); + } - if (val.line_desc.toLowerCase().includes("towing")) { - ret.towing = lineValue; - return acc; - } else { - ret.additionalCostItems.push({key: val.line_desc, total: lineValue}); - return acc.add(lineValue); - } - }, Dinero()); - ret.adjustments = Dinero({ - amount: Math.round((job.adjustment_bottom_line || 0) * 100), - }); - ret.storage = Dinero({ - amount: Math.round((job.storage_payable || 0) * 100), - }); - ret.pvrt = Dinero({ - amount: Math.round((job.ca_bc_pvrt || 0) * 100), - }); - ret.total = ret.additionalCosts - .add(ret.adjustments) //IO-813 Adjustment takes care of GST & PST at labor rate. - .add(ret.towing) - .add(ret.storage); - //.add(ret.pvrt); + if (val.line_desc.toLowerCase().includes("towing")) { + ret.towing = lineValue; + return acc; + } else { + ret.additionalCostItems.push({ key: val.line_desc, total: lineValue }); + return acc.add(lineValue); + } + }, Dinero()); + ret.adjustments = Dinero({ + amount: Math.round((job.adjustment_bottom_line || 0) * 100) + }); + ret.storage = Dinero({ + amount: Math.round((job.storage_payable || 0) * 100) + }); + ret.pvrt = Dinero({ + amount: Math.round((job.ca_bc_pvrt || 0) * 100) + }); + ret.total = ret.additionalCosts + .add(ret.adjustments) //IO-813 Adjustment takes care of GST & PST at labor rate. + .add(ret.towing) + .add(ret.storage); + //.add(ret.pvrt); - return ret; + return ret; } function CalculateTaxesTotals(job, otherTotals) { - const subtotal = otherTotals.parts.parts.subtotal - .add(otherTotals.parts.sublets.subtotal) - .add(otherTotals.rates.subtotal) //No longer using just rates subtotal to include mapa/mash. - .add(otherTotals.additional.total); + const subtotal = otherTotals.parts.parts.subtotal + .add(otherTotals.parts.sublets.subtotal) + .add(otherTotals.rates.subtotal) //No longer using just rates subtotal to include mapa/mash. + .add(otherTotals.additional.total); - // .add(Dinero({ amount: (job.towing_payable || 0) * 100 })) - // .add(Dinero({ amount: (job.storage_payable || 0) * 100 })); + // .add(Dinero({ amount: (job.towing_payable || 0) * 100 })) + // .add(Dinero({ amount: (job.storage_payable || 0) * 100 })); - //Potential issue here with Sublet Calculation. Sublets are calculated under labor in Mitchell, but it's done in IO - //Under the parts rates. + //Potential issue here with Sublet Calculation. Sublets are calculated under labor in Mitchell, but it's done in IO + //Under the parts rates. - let statePartsTax = Dinero(); - let additionalItemsTax = Dinero(); + let statePartsTax = Dinero(); + let additionalItemsTax = Dinero(); - //Audatex sends additional glass part types. IO-774 - const BackupGlassTax = - job.parts_tax_rates && - (job.parts_tax_rates.PAGD || - job.parts_tax_rates.PAGF || - job.parts_tax_rates.PAGP || - job.parts_tax_rates.PAGQ || - job.parts_tax_rates.PAGR); + //Audatex sends additional glass part types. IO-774 + const BackupGlassTax = + job.parts_tax_rates && + (job.parts_tax_rates.PAGD || + job.parts_tax_rates.PAGF || + job.parts_tax_rates.PAGP || + job.parts_tax_rates.PAGQ || + job.parts_tax_rates.PAGR); - job.joblines - .filter((jl) => !jl.removed) - .forEach((val) => { - if (!val.tax_part) return; - if (!val.part_type && IsAdditionalCost(val)) { - additionalItemsTax = additionalItemsTax.add( - Dinero({amount: Math.round((val.act_price || 0) * 100)}) - .multiply(val.part_qty || 0) - .percentage( - ((job.parts_tax_rates && - job.parts_tax_rates["PAN"] && - job.parts_tax_rates["PAN"].prt_tax_rt) || - 0) * 100 - ) - ); - } else { - statePartsTax = statePartsTax.add( - Dinero({amount: Math.round((val.act_price || 0) * 100)}) - .multiply(val.part_qty || 0) - .add( - val.prt_dsmk_m && - val.prt_dsmk_m !== 0 && - DiscountNotAlreadyCounted(val, job.joblines) - ? Dinero({amount: Math.round(val.prt_dsmk_m * 100)}) - : Dinero({ - amount: Math.round(val.act_price * 100), - }) - .multiply(val.part_qty || 0) - .percentage(Math.abs(val.prt_dsmk_p || 0)) - .multiply(val.prt_dsmk_p > 0 ? 1 : -1) - ) - .percentage( - ((job.parts_tax_rates && - job.parts_tax_rates[val.part_type] && - job.parts_tax_rates[val.part_type].prt_tax_rt) || - (val.part_type && - val.part_type.startsWith("PAG") && - BackupGlassTax && - BackupGlassTax.prt_tax_rt) || - (!val.part_type && - val.db_ref === "900510" && - job.parts_tax_rates["PAN"] && - job.parts_tax_rates["PAN"].prt_tax_rt) || - 0) * 100 - ) - ); - } - }); - - let ret = { - subtotal: subtotal, - federal_tax: subtotal - .percentage((job.federal_tax_rate || 0) * 100) - .add( - otherTotals.additional.pvrt.percentage( - (job.federal_tax_rate || 0) * 100 - ) - ), - statePartsTax, - state_tax: statePartsTax - .add( - otherTotals.rates.subtotal.percentage((job.tax_lbr_rt || 0) * 100) // THis is currently using the lbr tax rate from PFH not PFL. + job.joblines + .filter((jl) => !jl.removed) + .forEach((val) => { + if (!val.tax_part) return; + if (!val.part_type && IsAdditionalCost(val)) { + additionalItemsTax = additionalItemsTax.add( + Dinero({ amount: Math.round((val.act_price || 0) * 100) }) + .multiply(val.part_qty || 0) + .percentage( + ((job.parts_tax_rates && job.parts_tax_rates["PAN"] && job.parts_tax_rates["PAN"].prt_tax_rt) || 0) * 100 ) + ); + } else { + statePartsTax = statePartsTax.add( + Dinero({ amount: Math.round((val.act_price || 0) * 100) }) + .multiply(val.part_qty || 0) .add( - otherTotals.additional.adjustments.percentage( - (job.tax_lbr_rt || 0) * 100 - ) + val.prt_dsmk_m && val.prt_dsmk_m !== 0 && DiscountNotAlreadyCounted(val, job.joblines) + ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(val.act_price * 100) + }) + .multiply(val.part_qty || 0) + .percentage(Math.abs(val.prt_dsmk_p || 0)) + .multiply(val.prt_dsmk_p > 0 ? 1 : -1) ) - .add( - otherTotals.additional.towing.percentage((job.tax_tow_rt || 0) * 100) + .percentage( + ((job.parts_tax_rates && + job.parts_tax_rates[val.part_type] && + job.parts_tax_rates[val.part_type].prt_tax_rt) || + (val.part_type && val.part_type.startsWith("PAG") && BackupGlassTax && BackupGlassTax.prt_tax_rt) || + (!val.part_type && + val.db_ref === "900510" && + job.parts_tax_rates["PAN"] && + job.parts_tax_rates["PAN"].prt_tax_rt) || + 0) * 100 ) - .add( - otherTotals.additional.storage.percentage((job.tax_str_rt || 0) * 100) - ) - .add(additionalItemsTax), - // .add(otherTotals.additional.pvrt), - local_tax: subtotal.percentage((job.local_tax_rate || 0) * 100), - }; - ret.total_repairs = ret.subtotal - .add(ret.federal_tax) - .add(ret.state_tax) - .add(ret.local_tax) - .add(otherTotals.additional.pvrt); + ); + } + }); - ret.custPayable = { - deductible: Dinero({amount: Math.round((job.ded_amt || 0) * 100)}) || 0, - federal_tax: job.ca_gst_registrant - ? job.ca_customer_gst === 0 || job.ca_customer_gst === null - ? ret.federal_tax - : Dinero({amount: Math.round(job.ca_customer_gst * 100)}) - : Dinero(), - other_customer_amount: Dinero({ - amount: Math.round((job.other_amount_payable || 0) * 100), - }), - dep_taxes: Dinero({ - amount: Math.round((job.depreciation_taxes || 0) * 100), - }), - }; + let ret = { + subtotal: subtotal, + federal_tax: subtotal + .percentage((job.federal_tax_rate || 0) * 100) + .add(otherTotals.additional.pvrt.percentage((job.federal_tax_rate || 0) * 100)), + statePartsTax, + state_tax: statePartsTax + .add( + otherTotals.rates.subtotal.percentage((job.tax_lbr_rt || 0) * 100) // THis is currently using the lbr tax rate from PFH not PFL. + ) + .add(otherTotals.additional.adjustments.percentage((job.tax_lbr_rt || 0) * 100)) + .add(otherTotals.additional.towing.percentage((job.tax_tow_rt || 0) * 100)) + .add(otherTotals.additional.storage.percentage((job.tax_str_rt || 0) * 100)) + .add(additionalItemsTax), + // .add(otherTotals.additional.pvrt), + local_tax: subtotal.percentage((job.local_tax_rate || 0) * 100) + }; + ret.total_repairs = ret.subtotal + .add(ret.federal_tax) + .add(ret.state_tax) + .add(ret.local_tax) + .add(otherTotals.additional.pvrt); - ret.custPayable.total = ret.custPayable.deductible - .add(ret.custPayable.federal_tax) - .add(ret.custPayable.other_customer_amount) - .add(ret.custPayable.dep_taxes); + ret.custPayable = { + deductible: Dinero({ amount: Math.round((job.ded_amt || 0) * 100) }) || 0, + federal_tax: job.ca_gst_registrant + ? job.ca_customer_gst === 0 || job.ca_customer_gst === null + ? ret.federal_tax + : Dinero({ amount: Math.round(job.ca_customer_gst * 100) }) + : Dinero(), + other_customer_amount: Dinero({ + amount: Math.round((job.other_amount_payable || 0) * 100) + }), + dep_taxes: Dinero({ + amount: Math.round((job.depreciation_taxes || 0) * 100) + }) + }; - ret.net_repairs = ret.total_repairs.subtract(ret.custPayable.total); + ret.custPayable.total = ret.custPayable.deductible + .add(ret.custPayable.federal_tax) + .add(ret.custPayable.other_customer_amount) + .add(ret.custPayable.dep_taxes); - return ret; + ret.net_repairs = ret.total_repairs.subtract(ret.custPayable.total); + + return ret; } exports.default = Totals; function DiscountNotAlreadyCounted(jobline, joblines) { - if ( - //If it's not a discount line, then it definitely hasn't been counted yet. - jobline.db_ref !== "900510" && - jobline.db_ref !== "900511" - ) - return true; + if ( + //If it's not a discount line, then it definitely hasn't been counted yet. + jobline.db_ref !== "900510" && + jobline.db_ref !== "900511" + ) + return true; - const ParentLine = joblines.find((j) => j.unq_seq === jobline.line_ref); + const ParentLine = joblines.find((j) => j.unq_seq === jobline.line_ref); - return ParentLine && !(ParentLine.prt_dsmk_m && ParentLine.prt_dsmk_m !== 0); + return ParentLine && !(ParentLine.prt_dsmk_m && ParentLine.prt_dsmk_m !== 0); } exports.DiscountNotAlreadyCounted = DiscountNotAlreadyCounted; diff --git a/server/job/job.js b/server/job/job.js index a471baf13..0dc54c041 100644 --- a/server/job/job.js +++ b/server/job/job.js @@ -1,16 +1,16 @@ -const RenderInstanceManager = require('../utils/instanceMgr').default; +const RenderInstanceManager = require("../utils/instanceMgr").default; exports.totals = RenderInstanceManager({ - imex: require('./job-totals').default, - rome: require('./job-totals-USA').default, - promanager: require('./job-totals-USA').default, + imex: require("./job-totals").default, + rome: require("./job-totals-USA").default, + promanager: require("./job-totals-USA").default }); exports.totalsSsu = RenderInstanceManager({ - imex: require('./job-totals').totalsSsu, - rome: require('./job-totals-USA').totalsSsu, - promanager: require('./job-totals-USA').totalsSsu, + imex: require("./job-totals").totalsSsu, + rome: require("./job-totals-USA").totalsSsu, + promanager: require("./job-totals-USA").totalsSsu }); -exports.costing = require('./job-costing').JobCosting; -exports.costingmulti = require('./job-costing').JobCostingMulti; -exports.statustransition = require('./job-status-transition').statustransition; -exports.lifecycle = require('./job-lifecycle'); +exports.costing = require("./job-costing").JobCosting; +exports.costingmulti = require("./job-costing").JobCostingMulti; +exports.statustransition = require("./job-status-transition").statustransition; +exports.lifecycle = require("./job-lifecycle"); diff --git a/server/media/media.js b/server/media/media.js index 6c0b7f121..6706d3392 100644 --- a/server/media/media.js +++ b/server/media/media.js @@ -5,168 +5,155 @@ const client = require("../graphql-client/graphql-client").client; const queries = require("../graphql-client/queries"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); var cloudinary = require("cloudinary").v2; cloudinary.config(process.env.CLOUDINARY_URL); exports.createSignedUploadURL = (req, res) => { - logger.log("media-signed-upload", "DEBUG", req.user.email, null, null); - res.send( - cloudinary.utils.api_sign_request( - req.body, - process.env.CLOUDINARY_API_SECRET - ) - ); + logger.log("media-signed-upload", "DEBUG", req.user.email, null, null); + res.send(cloudinary.utils.api_sign_request(req.body, process.env.CLOUDINARY_API_SECRET)); }; exports.downloadFiles = (req, res) => { - const {ids} = req.body; - logger.log("media-bulk-download", "DEBUG", req.user.email, ids, null); + const { ids } = req.body; + logger.log("media-bulk-download", "DEBUG", req.user.email, ids, null); - const url = cloudinary.utils.download_zip_url({ - public_ids: ids, - flatten_folders: true, - }); - res.send(url); + const url = cloudinary.utils.download_zip_url({ + public_ids: ids, + flatten_folders: true + }); + res.send(url); }; exports.deleteFiles = async (req, res) => { - const {ids} = req.body; - const types = _.groupBy(ids, (x) => DetermineFileType(x.type)); + const { ids } = req.body; + const types = _.groupBy(ids, (x) => DetermineFileType(x.type)); - logger.log("media-bulk-delete", "DEBUG", req.user.email, ids, null); + logger.log("media-bulk-delete", "DEBUG", req.user.email, ids, null); - const returns = []; - if (types.image) { - //delete images + const returns = []; + if (types.image) { + //delete images - returns.push( - await cloudinary.api.delete_resources( - types.image.map((x) => x.key), - {resource_type: "image"} - ) - ); - } - if (types.video) { - //delete images returns.push( - returns.push( - await cloudinary.api.delete_resources( - types.video.map((x) => x.key), - {resource_type: "video"} - ) - ); - } - if (types.raw) { - //delete images returns.push( - returns.push( - await cloudinary.api.delete_resources( - types.raw.map((x) => `${x.key}.${x.extension}`), - {resource_type: "raw"} - ) - ); - } + returns.push( + await cloudinary.api.delete_resources( + types.image.map((x) => x.key), + { resource_type: "image" } + ) + ); + } + if (types.video) { + //delete images returns.push( + returns.push( + await cloudinary.api.delete_resources( + types.video.map((x) => x.key), + { resource_type: "video" } + ) + ); + } + if (types.raw) { + //delete images returns.push( + returns.push( + await cloudinary.api.delete_resources( + types.raw.map((x) => `${x.key}.${x.extension}`), + { resource_type: "raw" } + ) + ); + } - // Delete it on apollo. - const successfulDeletes = []; - returns.forEach((resType) => { - Object.keys(resType.deleted).forEach((key) => { - if ( - resType.deleted[key] === "deleted" || - resType.deleted[key] === "not_found" - ) { - successfulDeletes.push(key.replace(/\.[^/.]+$/, "")); - } - }); + // Delete it on apollo. + const successfulDeletes = []; + returns.forEach((resType) => { + Object.keys(resType.deleted).forEach((key) => { + if (resType.deleted[key] === "deleted" || resType.deleted[key] === "not_found") { + successfulDeletes.push(key.replace(/\.[^/.]+$/, "")); + } + }); + }); + + try { + const result = await client.request(queries.DELETE_MEDIA_DOCUMENTS, { + ids: ids.filter((i) => successfulDeletes.includes(i.key)).map((i) => i.id) }); - try { - const result = await client.request(queries.DELETE_MEDIA_DOCUMENTS, { - ids: ids - .filter((i) => successfulDeletes.includes(i.key)) - .map((i) => i.id), - }); + res.send({ returns, result }); + } catch (error) { + logger.log("media-delete-error", "ERROR", req.user.email, null, [ + { ids, error: error.message || JSON.stringify(error) } + ]); - res.send({returns, result}); - } catch (error) { - logger.log("media-delete-error", "ERROR", req.user.email, null, [ - {ids, error: error.message || JSON.stringify(error)}, - ]); - - res.json({error}); - } + res.json({ error }); + } }; exports.renameKeys = async (req, res) => { - const {documents, tojobid} = req.body; - logger.log("media-bulk-rename", "DEBUG", req.user.email, null, documents); + const { documents, tojobid } = req.body; + logger.log("media-bulk-rename", "DEBUG", req.user.email, null, documents); - const proms = []; - documents.forEach((d) => { - proms.push( - (async () => { - try { - const res = { - id: d.id, - ...(await cloudinary.uploader.rename(d.from, d.to, { - resource_type: DetermineFileType(d.type), - })), - }; - return res; - } catch (error) { - return {id: d.id, from: d.from, error: error}; - } - })() - ); + const proms = []; + documents.forEach((d) => { + proms.push( + (async () => { + try { + const res = { + id: d.id, + ...(await cloudinary.uploader.rename(d.from, d.to, { + resource_type: DetermineFileType(d.type) + })) + }; + return res; + } catch (error) { + return { id: d.id, from: d.from, error: error }; + } + })() + ); + }); + + let result; + + result = await Promise.all(proms); + const errors = []; + result + .filter((d) => d.error) + .forEach((d) => { + errors.push(d); }); - let result; + let mutations = ""; - result = await Promise.all(proms); - const errors = []; - result - .filter((d) => d.error) - .forEach((d) => { - errors.push(d); - }); + result + .filter((d) => !d.error) + .forEach((d, idx) => { + //Create mutation text - let mutations = ""; - - result - .filter((d) => !d.error) - .forEach((d, idx) => { - //Create mutation text - - mutations = - mutations + - ` + mutations = + mutations + + ` update_doc${idx}:update_documents_by_pk(pk_columns: { id: "${d.id}" }, _set: {key: "${d.public_id}", jobid: "${tojobid}"}){ id } `; - }); + }); - if (mutations !== "") { - const mutationResult = await client.request(`mutation { + if (mutations !== "") { + const mutationResult = await client.request(`mutation { ${mutations} }`); - res.json({errors, mutationResult}); - } else { - res.json({errors: "No images were succesfully moved on remote server. "}); - } + res.json({ errors, mutationResult }); + } else { + res.json({ errors: "No images were succesfully moved on remote server. " }); + } }; //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"; + 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"; + return "auto"; } diff --git a/server/middleware/eventAuthorizationMIddleware.js b/server/middleware/eventAuthorizationMIddleware.js index 9dd4dfd3a..87e11c9a2 100644 --- a/server/middleware/eventAuthorizationMIddleware.js +++ b/server/middleware/eventAuthorizationMIddleware.js @@ -9,12 +9,12 @@ const path = require("path"); * @param next */ function eventAuthorizationMiddleware(req, res, next) { - if (req.headers["event-secret"] !== process.env.EVENT_SECRET) { - return res.status(401).send("Unauthorized"); - } + if (req.headers["event-secret"] !== process.env.EVENT_SECRET) { + return res.status(401).send("Unauthorized"); + } - req.isEventAuthorized = true; - next(); + req.isEventAuthorized = true; + next(); } -module.exports = eventAuthorizationMiddleware; \ No newline at end of file +module.exports = eventAuthorizationMiddleware; diff --git a/server/middleware/validateAdminMiddleware.js b/server/middleware/validateAdminMiddleware.js index cfd53b171..97f0335ae 100644 --- a/server/middleware/validateAdminMiddleware.js +++ b/server/middleware/validateAdminMiddleware.js @@ -11,16 +11,16 @@ const adminEmail = require("../utils/adminEmail"); * @returns {*} */ const validateAdminMiddleware = (req, res, next) => { - if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) { - logger.log("admin-validation-failed", "ERROR", req.user.email, null, { - request: req.body, - user: req.user, - }); - return res.sendStatus(404); - } + if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) { + logger.log("admin-validation-failed", "ERROR", req.user.email, null, { + request: req.body, + user: req.user + }); + return res.sendStatus(404); + } - req.isAdmin = true; - next(); + req.isAdmin = true; + next(); }; -module.exports = validateAdminMiddleware; \ No newline at end of file +module.exports = validateAdminMiddleware; diff --git a/server/middleware/validateFirebaseIdTokenMiddleware.js b/server/middleware/validateFirebaseIdTokenMiddleware.js index 0ac8b4e73..d76cc8019 100644 --- a/server/middleware/validateFirebaseIdTokenMiddleware.js +++ b/server/middleware/validateFirebaseIdTokenMiddleware.js @@ -12,58 +12,51 @@ const admin = require("firebase-admin"); * @returns {Promise} */ const validateFirebaseIdTokenMiddleware = async (req, res, next) => { - if ( - ( - !req.headers.authorization || - !req.headers.authorization.startsWith("Bearer ")) && - !(req.cookies && req.cookies.__session - ) - ) { - console.error("Unauthorized attempt. No authorization provided."); - return res.status(403).send("Unauthorized"); - } + if ( + (!req.headers.authorization || !req.headers.authorization.startsWith("Bearer ")) && + !(req.cookies && req.cookies.__session) + ) { + console.error("Unauthorized attempt. No authorization provided."); + return res.status(403).send("Unauthorized"); + } - let idToken; + let idToken; - if ( - req.headers.authorization && - req.headers.authorization.startsWith("Bearer ") - ) { - // console.log('Found "Authorization" header'); - // Read the ID Token from the Authorization header. - idToken = req.headers.authorization.split("Bearer ")[1]; - } else if (req.cookies) { - //console.log('Found "__session" cookie'); - // Read the ID Token from cookie. - idToken = req.cookies.__session; - } else { - // No cookie - console.error("Unauthorized attempt. No cookie provided."); - logger.log("api-unauthorized-call", "WARN", null, null, { - req, - type: "no-cookie", - }); + if (req.headers.authorization && req.headers.authorization.startsWith("Bearer ")) { + // console.log('Found "Authorization" header'); + // Read the ID Token from the Authorization header. + idToken = req.headers.authorization.split("Bearer ")[1]; + } else if (req.cookies) { + //console.log('Found "__session" cookie'); + // Read the ID Token from cookie. + idToken = req.cookies.__session; + } else { + // No cookie + console.error("Unauthorized attempt. No cookie provided."); + logger.log("api-unauthorized-call", "WARN", null, null, { + req, + type: "no-cookie" + }); - return res.status(403).send("Unauthorized"); - } + return res.status(403).send("Unauthorized"); + } - try { - const decodedIdToken = await admin.auth().verifyIdToken(idToken); - //console.log("ID Token correctly decoded", decodedIdToken); - req.user = decodedIdToken; - next(); + try { + const decodedIdToken = await admin.auth().verifyIdToken(idToken); + //console.log("ID Token correctly decoded", decodedIdToken); + req.user = decodedIdToken; + next(); + } catch (error) { + logger.log("api-unauthorized-call", "WARN", null, null, { + path: req.path, + body: req.body, - } catch (error) { - logger.log("api-unauthorized-call", "WARN", null, null, { - path: req.path, - body: req.body, + type: "unauthroized", + ...error + }); - type: "unauthroized", - ...error, - }); - - return res.status(401).send("Unauthorized"); - } + return res.status(401).send("Unauthorized"); + } }; -module.exports = validateFirebaseIdTokenMiddleware; \ No newline at end of file +module.exports = validateFirebaseIdTokenMiddleware; diff --git a/server/middleware/withUserGraphQLClientMiddleware.js b/server/middleware/withUserGraphQLClientMiddleware.js index e55b58c8d..ddff280bf 100644 --- a/server/middleware/withUserGraphQLClientMiddleware.js +++ b/server/middleware/withUserGraphQLClientMiddleware.js @@ -1,4 +1,4 @@ -const {GraphQLClient} = require("graphql-request"); +const { GraphQLClient } = require("graphql-request"); /** * Middleware to add a GraphQL Client to the request object @@ -10,15 +10,15 @@ const {GraphQLClient} = require("graphql-request"); * @param next */ const withUserGraphQLClientMiddleware = (req, res, next) => { - const BearerToken = req.headers.authorization; - req.userGraphQLClient = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); - req.BearerToken = BearerToken; + const BearerToken = req.headers.authorization; + req.userGraphQLClient = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { + headers: { + Authorization: BearerToken + } + }); + req.BearerToken = BearerToken; - next(); + next(); }; -module.exports = withUserGraphQLClientMiddleware; \ No newline at end of file +module.exports = withUserGraphQLClientMiddleware; diff --git a/server/mixdata/mixdata.js b/server/mixdata/mixdata.js index fa0c57b07..0dca3705b 100644 --- a/server/mixdata/mixdata.js +++ b/server/mixdata/mixdata.js @@ -2,142 +2,124 @@ const path = require("path"); const _ = require("lodash"); const xml2js = require("xml2js"); const queries = require("../graphql-client/queries"); -const logger = require('../utils/logger'); +const logger = require("../utils/logger"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); exports.mixdataUpload = async (req, res) => { - const {bodyshopid} = req.body; + const { bodyshopid } = req.body; - const client = req.userGraphQLClient; + const client = req.userGraphQLClient; - logger.log("job-mixdata-upload", "DEBUG", req.user.email, null, null); + logger.log("job-mixdata-upload", "DEBUG", req.user.email, null, null); + try { + for (const element of req.files) { + const b = Buffer.from(element.buffer); - try { - for (const element of req.files) { - const b = Buffer.from(element.buffer); + const inboundRequest = await xml2js.parseStringPromise(b.toString(), { + explicitArray: false + }); - const inboundRequest = await xml2js.parseStringPromise(b.toString(), { - explicitArray: false, - }); + logger.log("job-mixdata-parse", "DEBUG", req.user.email, inboundRequest); - logger.log("job-mixdata-parse", "DEBUG", req.user.email, inboundRequest); + const ScaleType = DetermineScaleType(inboundRequest); + const RoNumbersFromInboundRequest = GetListOfRos(inboundRequest, ScaleType); - const ScaleType = DetermineScaleType(inboundRequest); - const RoNumbersFromInboundRequest = GetListOfRos( - inboundRequest, - ScaleType - ); + if (RoNumbersFromInboundRequest.length > 0) { + //Query the list of ROs based on the RO number. + const { jobs } = await client.request(queries.QUERY_JOB_ID_MIXDATA, { + roNumbers: RoNumbersFromInboundRequest + }); - if (RoNumbersFromInboundRequest.length > 0) { - //Query the list of ROs based on the RO number. - const {jobs} = await client.request(queries.QUERY_JOB_ID_MIXDATA, { - roNumbers: RoNumbersFromInboundRequest, - }); - - //Create the hash for faster processing for inserts/updates. - const jobHash = {}; - jobs.forEach((j) => { - jobHash[j.ro_number] = { - jobid: j.id, - mixdataid: j.mixdata.length > 0 ? j.mixdata[0].id : null, - }; - }); - const MixDataArray = GenerateMixDataArray( - inboundRequest, - ScaleType, - jobHash - ); - const foundJobs = MixDataArray.filter((m) => m.jobid); - const MixDataQuery = ` + //Create the hash for faster processing for inserts/updates. + const jobHash = {}; + jobs.forEach((j) => { + jobHash[j.ro_number] = { + jobid: j.id, + mixdataid: j.mixdata.length > 0 ? j.mixdata[0].id : null + }; + }); + const MixDataArray = GenerateMixDataArray(inboundRequest, ScaleType, jobHash); + const foundJobs = MixDataArray.filter((m) => m.jobid); + const MixDataQuery = ` mutation UPSERT_MIXDATA{ - ${foundJobs - .map((md, idx) => GenerateGqlForMixData(md, idx)) - .join(" ")} + ${foundJobs.map((md, idx) => GenerateGqlForMixData(md, idx)).join(" ")} } `; - if (foundJobs.length > 1) { - const resp = await client.request(MixDataQuery); - } - - //Process the list of ROs and return an object to generate the queries. - } + if (foundJobs.length > 1) { + const resp = await client.request(MixDataQuery); } - res.sendStatus(200); - } catch (error) { - res.status(500).json(error); - logger.log("job-mixdata-upload-error", "ERROR", null, null, { - error: error.message, - ...error, - }); + + //Process the list of ROs and return an object to generate the queries. + } } + res.sendStatus(200); + } catch (error) { + res.status(500).json(error); + logger.log("job-mixdata-upload-error", "ERROR", null, null, { + error: error.message, + ...error + }); + } }; function DetermineScaleType(inboundRequest) { - const ret = {type: "", verson: 0}; + const ret = { type: "", verson: 0 }; - //PPG Mix Data - if (inboundRequest.PPG && inboundRequest.PPG.Header.Protocol.Name === "PPG") { - return { - type: inboundRequest.PPG.Header.Protocol.Name, - company: "PPG", - version: inboundRequest.PPG.Header.Protocol.Version, - }; - } + //PPG Mix Data + if (inboundRequest.PPG && inboundRequest.PPG.Header.Protocol.Name === "PPG") { + return { + type: inboundRequest.PPG.Header.Protocol.Name, + company: "PPG", + version: inboundRequest.PPG.Header.Protocol.Version + }; + } } function GetListOfRos(inboundRequest, ScaleType) { - if (ScaleType.company === "PPG" && ScaleType.version === "1.3.0") { - return inboundRequest.PPG.MixDataInterface.ROData.RepairOrders.RO.map( - (r) => r.RONumber - ); - } + if (ScaleType.company === "PPG" && ScaleType.version === "1.3.0") { + return inboundRequest.PPG.MixDataInterface.ROData.RepairOrders.RO.map((r) => r.RONumber); + } } function GenerateMixDataArray(inboundRequest, ScaleType, jobHash) { - if (ScaleType.company === "PPG" && ScaleType.version === "1.3.0") { - return inboundRequest.PPG.MixDataInterface.ROData.RepairOrders.RO.map( - (r) => { - return { - jobid: jobHash[r.RONumber]?.jobid, - id: jobHash[r.RONumber]?.mixdataid, - mixdata: r, - totalliquidcost: r.TotalLiquidCost, - totalsundrycost: r.TotalSundryCost, - company: ScaleType.company, - version: ScaleType.version, - }; - } - ); - } + if (ScaleType.company === "PPG" && ScaleType.version === "1.3.0") { + return inboundRequest.PPG.MixDataInterface.ROData.RepairOrders.RO.map((r) => { + return { + jobid: jobHash[r.RONumber]?.jobid, + id: jobHash[r.RONumber]?.mixdataid, + mixdata: r, + totalliquidcost: r.TotalLiquidCost, + totalsundrycost: r.TotalSundryCost, + company: ScaleType.company, + version: ScaleType.version + }; + }); + } } function GenerateGqlForMixData(mixdata, key) { - const {id, ...restMixData} = mixdata; + const { id, ...restMixData } = mixdata; - if (id) { - //Update. - return ` - update${key}: update_mixdata_by_pk(pk_columns:{id: "${id}"}, _set: ${JSON.stringify( - restMixData - ).replace(/"(\w+)"\s*:/g, "$1:")}){ + if (id) { + //Update. + return ` + update${key}: update_mixdata_by_pk(pk_columns:{id: "${id}"}, _set: ${JSON.stringify(restMixData).replace( + /"(\w+)"\s*:/g, + "$1:" + )}){ id } `; - } else { - //Insert - return ` - insert${key}: insert_mixdata_one(object: ${JSON.stringify( - restMixData - ).replace(/"(\w+)"\s*:/g, "$1:")}){ + } else { + //Insert + return ` + insert${key}: insert_mixdata_one(object: ${JSON.stringify(restMixData).replace(/"(\w+)"\s*:/g, "$1:")}){ id } `; - } + } } diff --git a/server/opensearch/os-handler.js b/server/opensearch/os-handler.js index 5fe63695c..ba35eebd9 100644 --- a/server/opensearch/os-handler.js +++ b/server/opensearch/os-handler.js @@ -1,84 +1,72 @@ require("dotenv").config({ - path: require("path").resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: require("path").resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); //const client = require("../graphql-client/graphql-client").client; const logger = require("../utils/logger"); const queries = require("../graphql-client/queries"); const client = require("../graphql-client/graphql-client").client; -const {pick, isNil} = require("lodash"); -const {getClient} = require('../../libs/awsUtils'); - +const { pick, isNil } = require("lodash"); +const { getClient } = require("../../libs/awsUtils"); async function OpenSearchUpdateHandler(req, res) { - try { + try { + const osClient = await getClient(); - const osClient = await getClient(); + if (req.body.event.op === "DELETE") { + let response; + response = await osClient.delete({ + id: req.body.event.data.old.id, + index: req.body.table.name + }); + res.status(200).json(response.body); + } else { + let document; - if (req.body.event.op === "DELETE") { - let response; - response = await osClient.delete({ - id: req.body.event.data.old.id, - index: req.body.table.name, - }); - res.status(200).json(response.body); - } else { - let document; - - switch (req.body.table.name) { - case "jobs": - document = pick(req.body.event.data.new, [ - "id", - "bodyshopid", - "clm_no", - "clm_total", - "comment", - "ins_co_nm", - "owner_owing", - "ownr_co_nm", - "ownr_fn", - "ownr_ln", - "ownr_ph1", - "ownr_ph2", - "plate_no", - "ro_number", - "status", - "v_model_yr", - "v_make_desc", - "v_model_desc", - "v_vin", - ]); - document.bodyshopid = req.body.event.data.new.shopid; - break; - case "vehicles": - document = pick(req.body.event.data.new, [ - "id", - "v_model_yr", - "v_model_desc", - "v_make_desc", - "v_color", - "v_vin", - "plate_no", - ]); - document.bodyshopid = req.body.event.data.new.shopid; - break; - case "owners": - document = pick(req.body.event.data.new, [ - "id", - "ownr_fn", - "ownr_ln", - "ownr_co_nm", - "ownr_ph1", - "ownr_ph2", - ]); - document.bodyshopid = req.body.event.data.new.shopid; - break; - case "bills": - const bill = await client.request( - `query ADMIN_GET_BILL_BY_ID($billId: uuid!) { + switch (req.body.table.name) { + case "jobs": + document = pick(req.body.event.data.new, [ + "id", + "bodyshopid", + "clm_no", + "clm_total", + "comment", + "ins_co_nm", + "owner_owing", + "ownr_co_nm", + "ownr_fn", + "ownr_ln", + "ownr_ph1", + "ownr_ph2", + "plate_no", + "ro_number", + "status", + "v_model_yr", + "v_make_desc", + "v_model_desc", + "v_vin" + ]); + document.bodyshopid = req.body.event.data.new.shopid; + break; + case "vehicles": + document = pick(req.body.event.data.new, [ + "id", + "v_model_yr", + "v_model_desc", + "v_make_desc", + "v_color", + "v_vin", + "plate_no" + ]); + document.bodyshopid = req.body.event.data.new.shopid; + break; + case "owners": + document = pick(req.body.event.data.new, ["id", "ownr_fn", "ownr_ln", "ownr_co_nm", "ownr_ph1", "ownr_ph2"]); + document.bodyshopid = req.body.event.data.new.shopid; + break; + case "bills": + const bill = await client.request( + `query ADMIN_GET_BILL_BY_ID($billId: uuid!) { bills_by_pk(id: $billId) { id job { @@ -93,26 +81,26 @@ async function OpenSearchUpdateHandler(req, res) { } } `, - {billId: req.body.event.data.new.id} - ); - document = { - ...pick(req.body.event.data.new, [ - "id", - "date", - "exported", - "exported_at", - "invoice_number", - "is_credit_memo", - "total", - ]), - ...bill.bills_by_pk, - bodyshopid: bill.bills_by_pk.job.shopid, - }; - break; - case "payments": - //Query to get the job and RO number - const payment = await client.request( - `query ADMIN_GET_PAYMENT_BY_ID($paymentId: uuid!) { + { billId: req.body.event.data.new.id } + ); + document = { + ...pick(req.body.event.data.new, [ + "id", + "date", + "exported", + "exported_at", + "invoice_number", + "is_credit_memo", + "total" + ]), + ...bill.bills_by_pk, + bodyshopid: bill.bills_by_pk.job.shopid + }; + break; + case "payments": + //Query to get the job and RO number + const payment = await client.request( + `query ADMIN_GET_PAYMENT_BY_ID($paymentId: uuid!) { payments_by_pk(id: $paymentId) { id job { @@ -133,150 +121,146 @@ async function OpenSearchUpdateHandler(req, res) { } } `, - {paymentId: req.body.event.data.new.id} - ); - document = { - ...pick(req.body.event.data.new, [ - "id", - "amount", - "created_at", - "date", - "exportedat", - "memo", - "payer", - "paymentnum", - "transactionid", - "type", - ]), - ...payment.payments_by_pk, - bodyshopid: payment.payments_by_pk.job.shopid, - }; - break; - } - const payload = { - id: req.body.event.data.new.id, - index: req.body.table.name, - body: document, - }; + { paymentId: req.body.event.data.new.id } + ); + document = { + ...pick(req.body.event.data.new, [ + "id", + "amount", + "created_at", + "date", + "exportedat", + "memo", + "payer", + "paymentnum", + "transactionid", + "type" + ]), + ...payment.payments_by_pk, + bodyshopid: payment.payments_by_pk.job.shopid + }; + break; + } + const payload = { + id: req.body.event.data.new.id, + index: req.body.table.name, + body: document + }; - const response = await osClient.index(payload); - console.log(response.body); - res.status(200).json(response.body); - } - } catch (error) { - res.status(400).json(JSON.stringify(error)); + const response = await osClient.index(payload); + console.log(response.body); + res.status(200).json(response.body); } + } catch (error) { + res.status(400).json(JSON.stringify(error)); + } } async function OpenSearchSearchHandler(req, res) { - try { - const {search, bodyshopid, index} = req.body; + try { + const { search, bodyshopid, index } = req.body; - if (!req.user) { - res.sendStatus(401); - return; - } - - logger.log("os-search", "DEBUG", req.user.email, null, { - search, - }); - - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; - - const assocs = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.ACTIVE_SHOP_BY_USER, { - user: req.user.email, - }); - - if (assocs.length === 0) { - res.sendStatus(401); - } - - const osClient = await getClient(); - - const bodyShopIdMatchOverride = isNil(process.env.BODY_SHOP_ID_MATCH_OVERRIDE) ? assocs.associations[0].shopid : process.env.BODY_SHOP_ID_MATCH_OVERRIDE - - const {body} = await osClient.search({ - ...(index ? {index} : {}), - body: { - size: 100, - query: { - bool: { - must: [ - { - match: { - bodyshopid: bodyShopIdMatchOverride, - }, - }, - { - bool: { - should: [ - { - multi_match: { - query: search, - type: "cross_fields", - fields: ["*ownr_fn", "*ownr_ln"], - }, - }, - { - multi_match: { - query: search, - type: "most_fields", - fields: [ - "*v_model_yr", - "*v_make_desc^2", - "*v_model_desc^3", - ], - }, - }, - { - query_string: { - query: `*${search}*`, - // Weighted Fields - fields: [ - "*ro_number^20", - "*clm_no^14", - "*v_vin^12", - "*plate_no^12", - "*ownr_ln^10", - "transactionid^10", - "paymentnum^10", - "invoice_number^10", - "*ownr_fn^8", - "*ownr_co_nm^8", - "*ownr_ph1^8", - "*ownr_ph2^8", - "*", - ], - }, - }, - ], - minimum_should_match: 1, - }, - }, - ], - }, - }, - sort: [ - { - _score: { - order: "desc", - }, - }, - ], - }, - }); - - res.json(body); - } catch (error) { - console.log(error); - logger.log("os-search-error", "ERROR", req.user.email, null, { - error: JSON.stringify(error), - }); - res.status(400).json(error); + if (!req.user) { + res.sendStatus(401); + return; } + + logger.log("os-search", "DEBUG", req.user.email, null, { + search + }); + + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; + + const assocs = await client.setHeaders({ Authorization: BearerToken }).request(queries.ACTIVE_SHOP_BY_USER, { + user: req.user.email + }); + + if (assocs.length === 0) { + res.sendStatus(401); + } + + const osClient = await getClient(); + + const bodyShopIdMatchOverride = isNil(process.env.BODY_SHOP_ID_MATCH_OVERRIDE) + ? assocs.associations[0].shopid + : process.env.BODY_SHOP_ID_MATCH_OVERRIDE; + + const { body } = await osClient.search({ + ...(index ? { index } : {}), + body: { + size: 100, + query: { + bool: { + must: [ + { + match: { + bodyshopid: bodyShopIdMatchOverride + } + }, + { + bool: { + should: [ + { + multi_match: { + query: search, + type: "cross_fields", + fields: ["*ownr_fn", "*ownr_ln"] + } + }, + { + multi_match: { + query: search, + type: "most_fields", + fields: ["*v_model_yr", "*v_make_desc^2", "*v_model_desc^3"] + } + }, + { + query_string: { + query: `*${search}*`, + // Weighted Fields + fields: [ + "*ro_number^20", + "*clm_no^14", + "*v_vin^12", + "*plate_no^12", + "*ownr_ln^10", + "transactionid^10", + "paymentnum^10", + "invoice_number^10", + "*ownr_fn^8", + "*ownr_co_nm^8", + "*ownr_ph1^8", + "*ownr_ph2^8", + "*" + ] + } + } + ], + minimum_should_match: 1 + } + } + ] + } + }, + sort: [ + { + _score: { + order: "desc" + } + } + ] + } + }); + + res.json(body); + } catch (error) { + console.log(error); + logger.log("os-search-error", "ERROR", req.user.email, null, { + error: JSON.stringify(error) + }); + res.status(400).json(error); + } } exports.handler = OpenSearchUpdateHandler; diff --git a/server/parts-scan/parts-scan.js b/server/parts-scan/parts-scan.js index e68770a4a..7769654ce 100644 --- a/server/parts-scan/parts-scan.js +++ b/server/parts-scan/parts-scan.js @@ -1,56 +1,50 @@ const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); -const logger = require('../utils/logger'); -const {job} = require("../scheduling/scheduling-job"); +const logger = require("../utils/logger"); +const { job } = require("../scheduling/scheduling-job"); const _ = require("lodash"); // Dinero.defaultCurrency = "USD"; // Dinero.globalLocale = "en-CA"; exports.partsScan = async function (req, res) { - const {jobid} = req.body; + const { jobid } = req.body; - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; - logger.log("job-parts-scan", "DEBUG", req.user?.email, jobid, null); + logger.log("job-parts-scan", "DEBUG", req.user?.email, jobid, null); - try { - //Query all jobline data using the user's authorization. - const data = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QUERY_PARTS_SCAN, { - id: jobid, - }); + try { + //Query all jobline data using the user's authorization. + const data = await client.setHeaders({ Authorization: BearerToken }).request(queries.QUERY_PARTS_SCAN, { + id: jobid + }); - //Create RegExps once for better performance. - const IdsToMarkCritical = []; - const RegExpressions = data.jobs_by_pk.bodyshop.md_parts_scan.map( - (r) => new RegExp(r.expression, r.flags) - ); + //Create RegExps once for better performance. + const IdsToMarkCritical = []; + const RegExpressions = data.jobs_by_pk.bodyshop.md_parts_scan.map((r) => new RegExp(r.expression, r.flags)); - //Check each line against each regex rule. - data.jobs_by_pk.joblines.forEach((jobline) => { - RegExpressions.forEach((rExp) => { - if (jobline.line_desc.match(rExp)) { - IdsToMarkCritical.push(jobline); - } - }); - }); + //Check each line against each regex rule. + data.jobs_by_pk.joblines.forEach((jobline) => { + RegExpressions.forEach((rExp) => { + if (jobline.line_desc.match(rExp)) { + IdsToMarkCritical.push(jobline); + } + }); + }); - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.UPDATE_PARTS_CRITICAL, { - IdsToMarkCritical: _.uniqBy(IdsToMarkCritical, "id").map((i) => i.id), - jobid: jobid, - }); + const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.UPDATE_PARTS_CRITICAL, { + IdsToMarkCritical: _.uniqBy(IdsToMarkCritical, "id").map((i) => i.id), + jobid: jobid + }); - res.status(200).json(result); - } catch (error) { - logger.log("job-parts-scan-error", "ERROR", req.user.email, jobid, { - jobid, - error, - }); - res.status(400).json(JSON.stringify(error)); - } + res.status(200).json(result); + } catch (error) { + logger.log("job-parts-scan-error", "ERROR", req.user.email, jobid, { + jobid, + error + }); + res.status(400).json(JSON.stringify(error)); + } }; diff --git a/server/payroll/calculate-totals.js b/server/payroll/calculate-totals.js index 2fcde5f63..ebde2f2d8 100644 --- a/server/payroll/calculate-totals.js +++ b/server/payroll/calculate-totals.js @@ -2,126 +2,106 @@ const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); const GraphQLClient = require("graphql-request").GraphQLClient; const logger = require("../utils/logger"); -const { - CalculateExpectedHoursForJob, - CalculateTicketsHoursForJob, -} = require("./pay-all"); +const { CalculateExpectedHoursForJob, CalculateTicketsHoursForJob } = require("./pay-all"); // Dinero.defaultCurrency = "USD"; // Dinero.globalLocale = "en-CA"; Dinero.globalRoundingMode = "HALF_EVEN"; exports.calculatelabor = async function (req, res) { - const {jobid, calculateOnly} = req.body; - logger.log("job-payroll-calculate-labor", "DEBUG", req.user.email, jobid, null); + const { jobid, calculateOnly } = req.body; + logger.log("job-payroll-calculate-labor", "DEBUG", req.user.email, jobid, null); - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; - try { - const {jobs_by_pk: job} = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QUERY_JOB_PAYROLL_DATA, { - id: jobid, - }); + try { + const { jobs_by_pk: job } = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.QUERY_JOB_PAYROLL_DATA, { + id: jobid + }); - //iterate over each ticket, building a hash of team -> employee to calculate total assigned hours. - const {employeeHash, assignmentHash} = CalculateExpectedHoursForJob(job); - const ticketHash = CalculateTicketsHoursForJob(job); + //iterate over each ticket, building a hash of team -> employee to calculate total assigned hours. + const { employeeHash, assignmentHash } = CalculateExpectedHoursForJob(job); + const ticketHash = CalculateTicketsHoursForJob(job); - const totals = []; + const totals = []; - //Iteratively go through all 4 levels of the object and create an array that can be presented. - // use the employee hash as the golden record (i.e. what they should have), and add what they've claimed. - //While going through, delete items from ticket hash. - //Anything left in ticket hash is an extra entered item. + //Iteratively go through all 4 levels of the object and create an array that can be presented. + // use the employee hash as the golden record (i.e. what they should have), and add what they've claimed. + //While going through, delete items from ticket hash. + //Anything left in ticket hash is an extra entered item. - Object.keys(employeeHash).forEach((employeeIdKey) => { - //At the employee level. - Object.keys(employeeHash[employeeIdKey]).forEach((laborTypeKey) => { - //At the labor level - Object.keys(employeeHash[employeeIdKey][laborTypeKey]).forEach( - (rateKey) => { - //At the rate level. - const expectedHours = - employeeHash[employeeIdKey][laborTypeKey][rateKey]; - //Will the following line fail? Probably if it doesn't exist. - const claimedHours = get( - ticketHash, - `${employeeIdKey}.${laborTypeKey}.${rateKey}` - ); - if (claimedHours) { - delete ticketHash[employeeIdKey][laborTypeKey][rateKey]; - } + Object.keys(employeeHash).forEach((employeeIdKey) => { + //At the employee level. + Object.keys(employeeHash[employeeIdKey]).forEach((laborTypeKey) => { + //At the labor level + Object.keys(employeeHash[employeeIdKey][laborTypeKey]).forEach((rateKey) => { + //At the rate level. + const expectedHours = employeeHash[employeeIdKey][laborTypeKey][rateKey]; + //Will the following line fail? Probably if it doesn't exist. + const claimedHours = get(ticketHash, `${employeeIdKey}.${laborTypeKey}.${rateKey}`); + if (claimedHours) { + delete ticketHash[employeeIdKey][laborTypeKey][rateKey]; + } - totals.push({ - employeeid: employeeIdKey, - rate: rateKey, - mod_lbr_ty: laborTypeKey, - expectedHours, - claimedHours: claimedHours || 0, - }); - } - ); - }); + totals.push({ + employeeid: employeeIdKey, + rate: rateKey, + mod_lbr_ty: laborTypeKey, + expectedHours, + claimedHours: claimedHours || 0 + }); }); + }); + }); - Object.keys(ticketHash).forEach((employeeIdKey) => { - //At the employee level. - Object.keys(ticketHash[employeeIdKey]).forEach((laborTypeKey) => { - //At the labor level - Object.keys(ticketHash[employeeIdKey][laborTypeKey]).forEach( - (rateKey) => { - //At the rate level. - const expectedHours = 0; - //Will the following line fail? Probably if it doesn't exist. - const claimedHours = get( - ticketHash, - `${employeeIdKey}.${laborTypeKey}.${rateKey}` - ); - if (claimedHours) { - delete ticketHash[employeeIdKey][laborTypeKey][rateKey]; - } + Object.keys(ticketHash).forEach((employeeIdKey) => { + //At the employee level. + Object.keys(ticketHash[employeeIdKey]).forEach((laborTypeKey) => { + //At the labor level + Object.keys(ticketHash[employeeIdKey][laborTypeKey]).forEach((rateKey) => { + //At the rate level. + const expectedHours = 0; + //Will the following line fail? Probably if it doesn't exist. + const claimedHours = get(ticketHash, `${employeeIdKey}.${laborTypeKey}.${rateKey}`); + if (claimedHours) { + delete ticketHash[employeeIdKey][laborTypeKey][rateKey]; + } - totals.push({ - employeeid: employeeIdKey, - rate: rateKey, - mod_lbr_ty: laborTypeKey, - expectedHours, - claimedHours: claimedHours || 0, - }); - } - ); - }); + totals.push({ + employeeid: employeeIdKey, + rate: rateKey, + mod_lbr_ty: laborTypeKey, + expectedHours, + claimedHours: claimedHours || 0 + }); }); - if (assignmentHash.unassigned > 0) { - totals.push({ - employeeid: undefined, - //rate: rateKey, - //mod_lbr_ty: laborTypeKey, - expectedHours: assignmentHash.unassigned, - claimedHours: 0, - }); - } - res.json(totals); - //res.json(assignmentHash); - } catch (error) { - logger.log( - "job-payroll-calculate-labor-error", - "ERROR", - req.user.email, - jobid, - { - jobid: jobid, - error, - } - ); - res.status(503).send(); + }); + }); + if (assignmentHash.unassigned > 0) { + totals.push({ + employeeid: undefined, + //rate: rateKey, + //mod_lbr_ty: laborTypeKey, + expectedHours: assignmentHash.unassigned, + claimedHours: 0 + }); } + res.json(totals); + //res.json(assignmentHash); + } catch (error) { + logger.log("job-payroll-calculate-labor-error", "ERROR", req.user.email, jobid, { + jobid: jobid, + error + }); + res.status(503).send(); + } }; get = function (obj, key) { - return key.split(".").reduce(function (o, x) { - return typeof o == "undefined" || o === null ? o : o[x]; - }, obj); + return key.split(".").reduce(function (o, x) { + return typeof o == "undefined" || o === null ? o : o[x]; + }, obj); }; diff --git a/server/payroll/claim-task.js b/server/payroll/claim-task.js index 8a7efe921..ceb05ecc8 100644 --- a/server/payroll/claim-task.js +++ b/server/payroll/claim-task.js @@ -2,106 +2,87 @@ const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); const GraphQLClient = require("graphql-request").GraphQLClient; const logger = require("../utils/logger"); -const { - CalculateExpectedHoursForJob, - CalculateTicketsHoursForJob, -} = require("./pay-all"); +const { CalculateExpectedHoursForJob, CalculateTicketsHoursForJob } = require("./pay-all"); const moment = require("moment"); // Dinero.defaultCurrency = "USD"; // Dinero.globalLocale = "en-CA"; Dinero.globalRoundingMode = "HALF_EVEN"; exports.claimtask = async function (req, res) { - const {jobid, task, calculateOnly, employee} = req.body; - logger.log("job-payroll-pay-all", "DEBUG", req.user.email, jobid, null); + const { jobid, task, calculateOnly, employee } = req.body; + logger.log("job-payroll-pay-all", "DEBUG", req.user.email, jobid, null); - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; - try { - const {jobs_by_pk: job} = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QUERY_JOB_PAYROLL_DATA, { - id: jobid, - }); + try { + const { jobs_by_pk: job } = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.QUERY_JOB_PAYROLL_DATA, { + id: jobid + }); - const theTaskPreset = job.bodyshop.md_tasks_presets.presets.find( - (tp) => tp.name === task - ); - if (!theTaskPreset) { - res - .status(400) - .json({success: false, error: "Provided task preset not found."}); - return; - } - - //Get all of the assignments that are filtered. - const {assignmentHash, employeeHash} = CalculateExpectedHoursForJob( - job, - theTaskPreset.hourstype - ); - const ticketsToInsert = []; - //Then add them in based on a percentage to each employee. - - Object.keys(employeeHash).forEach((employeeIdKey) => { - //At the employee level. - Object.keys(employeeHash[employeeIdKey]).forEach((laborTypeKey) => { - //At the labor level - Object.keys(employeeHash[employeeIdKey][laborTypeKey]).forEach( - (rateKey) => { - //At the rate level. - const expectedHours = - employeeHash[employeeIdKey][laborTypeKey][rateKey] * - (theTaskPreset.percent / 100); - - ticketsToInsert.push({ - task_name: task, - jobid: job.id, - bodyshopid: job.bodyshop.id, - employeeid: employeeIdKey, - productivehrs: expectedHours, - rate: rateKey, - ciecacode: laborTypeKey, - flat_rate: true, - cost_center: - job.bodyshop.md_responsibility_centers.defaults.costs[ - laborTypeKey - ], - memo: `*Flagged Task* ${theTaskPreset.memo}`, - }); - } - ); - }); - }); - if (!calculateOnly) { - //Insert the time ticekts if we're not just calculating them. - const insertResult = await client.request(queries.INSERT_TIME_TICKETS, { - timetickets: ticketsToInsert.filter( - (ticket) => ticket.productivehrs !== 0 - ), - }); - const updateResult = await client.request(queries.UPDATE_JOB, { - jobId: job.id, - job: { - status: theTaskPreset.nextstatus, - completed_tasks: [ - ...job.completed_tasks, - { - name: task, - completedat: moment(), - completed_by: employee, - useremail: req.user.email, - }, - ], - }, - }); - } - res.json({unassignedHours: assignmentHash.unassigned, ticketsToInsert}); - } catch (error) { - logger.log("job-payroll-claim-task-error", "ERROR", req.user.email, jobid, { - jobid: jobid, - error, - }); - res.status(503).send(); + const theTaskPreset = job.bodyshop.md_tasks_presets.presets.find((tp) => tp.name === task); + if (!theTaskPreset) { + res.status(400).json({ success: false, error: "Provided task preset not found." }); + return; } + + //Get all of the assignments that are filtered. + const { assignmentHash, employeeHash } = CalculateExpectedHoursForJob(job, theTaskPreset.hourstype); + const ticketsToInsert = []; + //Then add them in based on a percentage to each employee. + + Object.keys(employeeHash).forEach((employeeIdKey) => { + //At the employee level. + Object.keys(employeeHash[employeeIdKey]).forEach((laborTypeKey) => { + //At the labor level + Object.keys(employeeHash[employeeIdKey][laborTypeKey]).forEach((rateKey) => { + //At the rate level. + const expectedHours = employeeHash[employeeIdKey][laborTypeKey][rateKey] * (theTaskPreset.percent / 100); + + ticketsToInsert.push({ + task_name: task, + jobid: job.id, + bodyshopid: job.bodyshop.id, + employeeid: employeeIdKey, + productivehrs: expectedHours, + rate: rateKey, + ciecacode: laborTypeKey, + flat_rate: true, + cost_center: job.bodyshop.md_responsibility_centers.defaults.costs[laborTypeKey], + memo: `*Flagged Task* ${theTaskPreset.memo}` + }); + }); + }); + }); + if (!calculateOnly) { + //Insert the time ticekts if we're not just calculating them. + const insertResult = await client.request(queries.INSERT_TIME_TICKETS, { + timetickets: ticketsToInsert.filter((ticket) => ticket.productivehrs !== 0) + }); + const updateResult = await client.request(queries.UPDATE_JOB, { + jobId: job.id, + job: { + status: theTaskPreset.nextstatus, + completed_tasks: [ + ...job.completed_tasks, + { + name: task, + completedat: moment(), + completed_by: employee, + useremail: req.user.email + } + ] + } + }); + } + res.json({ unassignedHours: assignmentHash.unassigned, ticketsToInsert }); + } catch (error) { + logger.log("job-payroll-claim-task-error", "ERROR", req.user.email, jobid, { + jobid: jobid, + error + }); + res.status(503).send(); + } }; diff --git a/server/payroll/pay-all.js b/server/payroll/pay-all.js index 60c65ddae..9288e256a 100644 --- a/server/payroll/pay-all.js +++ b/server/payroll/pay-all.js @@ -5,321 +5,271 @@ const _ = require("lodash"); const rdiff = require("recursive-diff"); const logger = require("../utils/logger"); -const {json} = require("body-parser"); +const { json } = require("body-parser"); // Dinero.defaultCurrency = "USD"; // Dinero.globalLocale = "en-CA"; Dinero.globalRoundingMode = "HALF_EVEN"; exports.payall = async function (req, res) { - const {jobid, calculateOnly} = req.body; - logger.log("job-payroll-pay-all", "DEBUG", req.user.email, jobid, null); + const { jobid, calculateOnly } = req.body; + logger.log("job-payroll-pay-all", "DEBUG", req.user.email, jobid, null); - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; - try { - const {jobs_by_pk: job} = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QUERY_JOB_PAYROLL_DATA, { - id: jobid, - }); + try { + const { jobs_by_pk: job } = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.QUERY_JOB_PAYROLL_DATA, { + id: jobid + }); - //iterate over each ticket, building a hash of team -> employee to calculate total assigned hours. + //iterate over each ticket, building a hash of team -> employee to calculate total assigned hours. - const {employeeHash, assignmentHash} = CalculateExpectedHoursForJob(job); - const ticketHash = CalculateTicketsHoursForJob(job); - if (assignmentHash.unassigned > 0) { - res.json({success: false, error: "Not all hours have been assigned."}); - return; - } - - //Calculate how much time each tech should have by labor type. - //Doing this order creates a diff of changes on the ticket hash to make it the same as the employee hash. - const recursiveDiff = rdiff.getDiff(ticketHash, employeeHash, true); - - const ticketsToInsert = []; - - recursiveDiff.forEach((diff) => { - //Every iteration is what we would need to insert into the time ticket hash - //so that it would match the employee hash exactly. - const path = diffParser(diff); - - if (diff.op === "add") { - console.log(Object.keys(diff.val)); - if (typeof diff.val === "object" && Object.keys(diff.val).length > 1) { - //Multiple values to add. - Object.keys(diff.val).forEach((key) => { - console.log("Hours", diff.val[key][Object.keys(diff.val[key])[0]]); - console.log("Rate", Object.keys(diff.val[key])[0]); - ticketsToInsert.push({ - task_name: "Pay All", - jobid: job.id, - bodyshopid: job.bodyshop.id, - employeeid: path.employeeid, - productivehrs: diff.val[key][Object.keys(diff.val[key])[0]], - rate: Object.keys(diff.val[key])[0], - ciecacode: key, - cost_center: - job.bodyshop.md_responsibility_centers.defaults.costs[key], - flat_rate: true, - memo: `Add unflagged hours. (${req.user.email})`, - }); - }); - } else { - //Only the 1 value to add. - ticketsToInsert.push({ - task_name: "Pay All", - jobid: job.id, - bodyshopid: job.bodyshop.id, - employeeid: path.employeeid, - productivehrs: path.hours, - rate: path.rate, - ciecacode: path.mod_lbr_ty, - flat_rate: true, - cost_center: - job.bodyshop.md_responsibility_centers.defaults.costs[ - path.mod_lbr_ty - ], - memo: `Add unflagged hours. (${req.user.email})`, - }); - } - } else if (diff.op === "update") { - //An old ticket amount isn't sufficient - //We can't modify the existing ticket, it might already be committed. So let's add a new one instead. - ticketsToInsert.push({ - task_name: "Pay All", - jobid: job.id, - bodyshopid: job.bodyshop.id, - employeeid: path.employeeid, - productivehrs: diff.val - diff.oldVal, - rate: path.rate, - ciecacode: path.mod_lbr_ty, - flat_rate: true, - cost_center: - job.bodyshop.md_responsibility_centers.defaults.costs[ - path.mod_lbr_ty - ], - memo: `Adjust flagged hours per assignment. (${req.user.email})`, - }); - } else { - //Has to be a delete - if ( - typeof diff.oldVal === "object" && - Object.keys(diff.oldVal).length > 1 - ) { - //Multiple oldValues to add. - Object.keys(diff.oldVal).forEach((key) => { - ticketsToInsert.push({ - task_name: "Pay All", - jobid: job.id, - bodyshopid: job.bodyshop.id, - employeeid: path.employeeid, - productivehrs: - diff.oldVal[key][Object.keys(diff.oldVal[key])[0]] * -1, - rate: Object.keys(diff.oldVal[key])[0], - ciecacode: key, - cost_center: - job.bodyshop.md_responsibility_centers.defaults.costs[key], - flat_rate: true, - memo: `Remove flagged hours per assignment. (${req.user.email})`, - }); - }); - } else { - //Only the 1 value to add. - ticketsToInsert.push({ - task_name: "Pay All", - jobid: job.id, - bodyshopid: job.bodyshop.id, - employeeid: path.employeeid, - productivehrs: path.hours * -1, - rate: path.rate, - ciecacode: path.mod_lbr_ty, - cost_center: - job.bodyshop.md_responsibility_centers.defaults.costs[ - path.mod_lbr_ty - ], - flat_rate: true, - memo: `Remove flagged hours per assignment. (${req.user.email})`, - }); - } - } - }); - - const insertResult = await client.request(queries.INSERT_TIME_TICKETS, { - timetickets: ticketsToInsert.filter( - (ticket) => ticket.productivehrs !== 0 - ), - }); - - res.json(ticketsToInsert.filter((ticket) => ticket.productivehrs !== 0)); - } catch (error) { - logger.log( - "job-payroll-labor-totals-error", - "ERROR", - req.user.email, - jobid, - { - jobid: jobid, - error: JSON.stringify(error), - } - ); - res.status(400).json({error: error.message}); + const { employeeHash, assignmentHash } = CalculateExpectedHoursForJob(job); + const ticketHash = CalculateTicketsHoursForJob(job); + if (assignmentHash.unassigned > 0) { + res.json({ success: false, error: "Not all hours have been assigned." }); + return; } + + //Calculate how much time each tech should have by labor type. + //Doing this order creates a diff of changes on the ticket hash to make it the same as the employee hash. + const recursiveDiff = rdiff.getDiff(ticketHash, employeeHash, true); + + const ticketsToInsert = []; + + recursiveDiff.forEach((diff) => { + //Every iteration is what we would need to insert into the time ticket hash + //so that it would match the employee hash exactly. + const path = diffParser(diff); + + if (diff.op === "add") { + console.log(Object.keys(diff.val)); + if (typeof diff.val === "object" && Object.keys(diff.val).length > 1) { + //Multiple values to add. + Object.keys(diff.val).forEach((key) => { + console.log("Hours", diff.val[key][Object.keys(diff.val[key])[0]]); + console.log("Rate", Object.keys(diff.val[key])[0]); + ticketsToInsert.push({ + task_name: "Pay All", + jobid: job.id, + bodyshopid: job.bodyshop.id, + employeeid: path.employeeid, + productivehrs: diff.val[key][Object.keys(diff.val[key])[0]], + rate: Object.keys(diff.val[key])[0], + ciecacode: key, + cost_center: job.bodyshop.md_responsibility_centers.defaults.costs[key], + flat_rate: true, + memo: `Add unflagged hours. (${req.user.email})` + }); + }); + } else { + //Only the 1 value to add. + ticketsToInsert.push({ + task_name: "Pay All", + jobid: job.id, + bodyshopid: job.bodyshop.id, + employeeid: path.employeeid, + productivehrs: path.hours, + rate: path.rate, + ciecacode: path.mod_lbr_ty, + flat_rate: true, + cost_center: job.bodyshop.md_responsibility_centers.defaults.costs[path.mod_lbr_ty], + memo: `Add unflagged hours. (${req.user.email})` + }); + } + } else if (diff.op === "update") { + //An old ticket amount isn't sufficient + //We can't modify the existing ticket, it might already be committed. So let's add a new one instead. + ticketsToInsert.push({ + task_name: "Pay All", + jobid: job.id, + bodyshopid: job.bodyshop.id, + employeeid: path.employeeid, + productivehrs: diff.val - diff.oldVal, + rate: path.rate, + ciecacode: path.mod_lbr_ty, + flat_rate: true, + cost_center: job.bodyshop.md_responsibility_centers.defaults.costs[path.mod_lbr_ty], + memo: `Adjust flagged hours per assignment. (${req.user.email})` + }); + } else { + //Has to be a delete + if (typeof diff.oldVal === "object" && Object.keys(diff.oldVal).length > 1) { + //Multiple oldValues to add. + Object.keys(diff.oldVal).forEach((key) => { + ticketsToInsert.push({ + task_name: "Pay All", + jobid: job.id, + bodyshopid: job.bodyshop.id, + employeeid: path.employeeid, + productivehrs: diff.oldVal[key][Object.keys(diff.oldVal[key])[0]] * -1, + rate: Object.keys(diff.oldVal[key])[0], + ciecacode: key, + cost_center: job.bodyshop.md_responsibility_centers.defaults.costs[key], + flat_rate: true, + memo: `Remove flagged hours per assignment. (${req.user.email})` + }); + }); + } else { + //Only the 1 value to add. + ticketsToInsert.push({ + task_name: "Pay All", + jobid: job.id, + bodyshopid: job.bodyshop.id, + employeeid: path.employeeid, + productivehrs: path.hours * -1, + rate: path.rate, + ciecacode: path.mod_lbr_ty, + cost_center: job.bodyshop.md_responsibility_centers.defaults.costs[path.mod_lbr_ty], + flat_rate: true, + memo: `Remove flagged hours per assignment. (${req.user.email})` + }); + } + } + }); + + const insertResult = await client.request(queries.INSERT_TIME_TICKETS, { + timetickets: ticketsToInsert.filter((ticket) => ticket.productivehrs !== 0) + }); + + res.json(ticketsToInsert.filter((ticket) => ticket.productivehrs !== 0)); + } catch (error) { + logger.log("job-payroll-labor-totals-error", "ERROR", req.user.email, jobid, { + jobid: jobid, + error: JSON.stringify(error) + }); + res.status(400).json({ error: error.message }); + } }; function diffParser(diff) { - const type = typeof diff.oldVal; - let mod_lbr_ty, rate, hours; + const type = typeof diff.oldVal; + let mod_lbr_ty, rate, hours; - if (diff.path.length === 1) { - if (diff.op === "add") { - mod_lbr_ty = Object.keys(diff.val)[0]; - rate = Object.keys(diff.val[mod_lbr_ty])[0]; - // hours = diff.oldVal[mod_lbr_ty][rate]; - } else { - mod_lbr_ty = Object.keys(diff.oldVal)[0]; - rate = Object.keys(diff.oldVal[mod_lbr_ty])[0]; - // hours = diff.oldVal[mod_lbr_ty][rate]; - } - } else if (diff.path.length === 2) { - mod_lbr_ty = diff.path[1]; - if (diff.op === "add") { - rate = Object.keys(diff.val)[0]; - } else { - rate = Object.keys(diff.oldVal)[0]; - } - } else if (diff.path.length === 3) { - mod_lbr_ty = diff.path[1]; - rate = diff.path[2]; - //hours = 0; - } - - //Set the hours - if ( - typeof diff.val === "number" && - diff.val !== null && - diff.val !== undefined - ) { - hours = diff.val; - } else if (diff.val !== null && diff.val !== undefined) { - if (diff.path.length === 1) { - hours = - diff.val[Object.keys(diff.val)[0]][ - Object.keys(diff.val[Object.keys(diff.val)[0]]) - ]; - } else { - hours = diff.val[Object.keys(diff.val)[0]]; - } - } else if ( - typeof diff.oldVal === "number" && - diff.oldVal !== null && - diff.oldVal !== undefined - ) { - hours = diff.oldVal; + if (diff.path.length === 1) { + if (diff.op === "add") { + mod_lbr_ty = Object.keys(diff.val)[0]; + rate = Object.keys(diff.val[mod_lbr_ty])[0]; + // hours = diff.oldVal[mod_lbr_ty][rate]; } else { - hours = diff.oldVal[Object.keys(diff.oldVal)[0]]; + mod_lbr_ty = Object.keys(diff.oldVal)[0]; + rate = Object.keys(diff.oldVal[mod_lbr_ty])[0]; + // hours = diff.oldVal[mod_lbr_ty][rate]; } + } else if (diff.path.length === 2) { + mod_lbr_ty = diff.path[1]; + if (diff.op === "add") { + rate = Object.keys(diff.val)[0]; + } else { + rate = Object.keys(diff.oldVal)[0]; + } + } else if (diff.path.length === 3) { + mod_lbr_ty = diff.path[1]; + rate = diff.path[2]; + //hours = 0; + } - const ret = { - multiVal: false, - employeeid: diff.path[0], // Always True - mod_lbr_ty, - rate, - hours, - }; - return ret; + //Set the hours + if (typeof diff.val === "number" && diff.val !== null && diff.val !== undefined) { + hours = diff.val; + } else if (diff.val !== null && diff.val !== undefined) { + if (diff.path.length === 1) { + hours = diff.val[Object.keys(diff.val)[0]][Object.keys(diff.val[Object.keys(diff.val)[0]])]; + } else { + hours = diff.val[Object.keys(diff.val)[0]]; + } + } else if (typeof diff.oldVal === "number" && diff.oldVal !== null && diff.oldVal !== undefined) { + hours = diff.oldVal; + } else { + hours = diff.oldVal[Object.keys(diff.oldVal)[0]]; + } + + const ret = { + multiVal: false, + employeeid: diff.path[0], // Always True + mod_lbr_ty, + rate, + hours + }; + return ret; } function CalculateExpectedHoursForJob(job, filterToLbrTypes) { - const assignmentHash = {unassigned: 0}; - const employeeHash = {}; // employeeid => Cieca labor type => rate => hours. Contains how many hours each person should be paid. - job.joblines - .filter((jobline) => { - if (!filterToLbrTypes) return true; - else { - return ( - filterToLbrTypes.includes(jobline.mod_lbr_ty) || - (jobline.convertedtolbr && - filterToLbrTypes.includes(jobline.convertedtolbr_data.mod_lbr_ty)) - ); + const assignmentHash = { unassigned: 0 }; + const employeeHash = {}; // employeeid => Cieca labor type => rate => hours. Contains how many hours each person should be paid. + job.joblines + .filter((jobline) => { + if (!filterToLbrTypes) return true; + else { + return ( + filterToLbrTypes.includes(jobline.mod_lbr_ty) || + (jobline.convertedtolbr && filterToLbrTypes.includes(jobline.convertedtolbr_data.mod_lbr_ty)) + ); + } + }) + .forEach((jobline) => { + if (jobline.convertedtolbr) { + // Line has been converte to labor. Temporarily re-assign the hours. + jobline.mod_lbr_ty = jobline.convertedtolbr_data.mod_lbr_ty; + jobline.mod_lb_hrs += jobline.convertedtolbr_data.mod_lb_hrs; + } + if (jobline.mod_lb_hrs != 0) { + //Check if the line is assigned. If not, keep track of it as an unassigned line by type. + if (jobline.assigned_team === null) { + assignmentHash.unassigned = assignmentHash.unassigned + jobline.mod_lb_hrs; + } else { + //Line is assigned. + if (!assignmentHash[jobline.assigned_team]) { + assignmentHash[jobline.assigned_team] = 0; + } + assignmentHash[jobline.assigned_team] = assignmentHash[jobline.assigned_team] + jobline.mod_lb_hrs; + + //Create the assignment breakdown. + const theTeam = job.bodyshop.employee_teams.find((team) => team.id === jobline.assigned_team); + + theTeam.employee_team_members.forEach((tm) => { + //Figure out how many hours they are owed at this line, and at what rate. + + if (!employeeHash[tm.employee.id]) { + employeeHash[tm.employee.id] = {}; } - }) - .forEach((jobline) => { - if (jobline.convertedtolbr) { - // Line has been converte to labor. Temporarily re-assign the hours. - jobline.mod_lbr_ty = jobline.convertedtolbr_data.mod_lbr_ty; - jobline.mod_lb_hrs += jobline.convertedtolbr_data.mod_lb_hrs; + if (!employeeHash[tm.employee.id][jobline.mod_lbr_ty]) { + employeeHash[tm.employee.id][jobline.mod_lbr_ty] = {}; } - if (jobline.mod_lb_hrs != 0) { - //Check if the line is assigned. If not, keep track of it as an unassigned line by type. - if (jobline.assigned_team === null) { - assignmentHash.unassigned = - assignmentHash.unassigned + jobline.mod_lb_hrs; - } else { - //Line is assigned. - if (!assignmentHash[jobline.assigned_team]) { - assignmentHash[jobline.assigned_team] = 0; - } - assignmentHash[jobline.assigned_team] = - assignmentHash[jobline.assigned_team] + jobline.mod_lb_hrs; - - //Create the assignment breakdown. - const theTeam = job.bodyshop.employee_teams.find( - (team) => team.id === jobline.assigned_team - ); - - theTeam.employee_team_members.forEach((tm) => { - //Figure out how many hours they are owed at this line, and at what rate. - - if (!employeeHash[tm.employee.id]) { - employeeHash[tm.employee.id] = {}; - } - if (!employeeHash[tm.employee.id][jobline.mod_lbr_ty]) { - employeeHash[tm.employee.id][jobline.mod_lbr_ty] = {}; - } - if ( - !employeeHash[tm.employee.id][jobline.mod_lbr_ty][ - tm.labor_rates[jobline.mod_lbr_ty] - ] - ) { - employeeHash[tm.employee.id][jobline.mod_lbr_ty][ - tm.labor_rates[jobline.mod_lbr_ty] - ] = 0; - } - - const hoursOwed = (tm.percentage * jobline.mod_lb_hrs) / 100; - employeeHash[tm.employee.id][jobline.mod_lbr_ty][ - tm.labor_rates[jobline.mod_lbr_ty] - ] = - employeeHash[tm.employee.id][jobline.mod_lbr_ty][ - tm.labor_rates[jobline.mod_lbr_ty] - ] + hoursOwed; - }); - } + if (!employeeHash[tm.employee.id][jobline.mod_lbr_ty][tm.labor_rates[jobline.mod_lbr_ty]]) { + employeeHash[tm.employee.id][jobline.mod_lbr_ty][tm.labor_rates[jobline.mod_lbr_ty]] = 0; } - }); - return {assignmentHash, employeeHash}; + const hoursOwed = (tm.percentage * jobline.mod_lb_hrs) / 100; + employeeHash[tm.employee.id][jobline.mod_lbr_ty][tm.labor_rates[jobline.mod_lbr_ty]] = + employeeHash[tm.employee.id][jobline.mod_lbr_ty][tm.labor_rates[jobline.mod_lbr_ty]] + hoursOwed; + }); + } + } + }); + + return { assignmentHash, employeeHash }; } function CalculateTicketsHoursForJob(job) { - const ticketHash = {}; // employeeid => Cieca labor type => rate => hours. - //Calculate how much each employee has been paid so far. - job.timetickets.forEach((ticket) => { - if (!ticketHash[ticket.employeeid]) { - ticketHash[ticket.employeeid] = {}; - } - if (!ticketHash[ticket.employeeid][ticket.ciecacode]) { - ticketHash[ticket.employeeid][ticket.ciecacode] = {}; - } - if (!ticketHash[ticket.employeeid][ticket.ciecacode][ticket.rate]) { - ticketHash[ticket.employeeid][ticket.ciecacode][ticket.rate] = 0; - } - ticketHash[ticket.employeeid][ticket.ciecacode][ticket.rate] = - ticketHash[ticket.employeeid][ticket.ciecacode][ticket.rate] + - ticket.productivehrs; - }); - return ticketHash; + const ticketHash = {}; // employeeid => Cieca labor type => rate => hours. + //Calculate how much each employee has been paid so far. + job.timetickets.forEach((ticket) => { + if (!ticketHash[ticket.employeeid]) { + ticketHash[ticket.employeeid] = {}; + } + if (!ticketHash[ticket.employeeid][ticket.ciecacode]) { + ticketHash[ticket.employeeid][ticket.ciecacode] = {}; + } + if (!ticketHash[ticket.employeeid][ticket.ciecacode][ticket.rate]) { + ticketHash[ticket.employeeid][ticket.ciecacode][ticket.rate] = 0; + } + ticketHash[ticket.employeeid][ticket.ciecacode][ticket.rate] = + ticketHash[ticket.employeeid][ticket.ciecacode][ticket.rate] + ticket.productivehrs; + }); + return ticketHash; } exports.CalculateExpectedHoursForJob = CalculateExpectedHoursForJob; diff --git a/server/render/inlinecss.js b/server/render/inlinecss.js index cf47d20dc..07fb68052 100644 --- a/server/render/inlinecss.js +++ b/server/render/inlinecss.js @@ -1,29 +1,26 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const logger = require("../utils/logger"); const inlineCssTool = require("inline-css"); exports.inlinecss = (req, res) => { - //Perform request validation + //Perform request validation - logger.log("email-inline-css", "DEBUG", req.user.email, null, null); + logger.log("email-inline-css", "DEBUG", req.user.email, null, null); - const {html, url} = req.body; + const { html, url } = req.body; - inlineCssTool(html, {url: url}) - .then((inlinedHtml) => { - res.send(inlinedHtml); - }) - .catch((error) => { - logger.log("email-inline-css-error", "ERROR", req.user.email, null, { - error, - }); + inlineCssTool(html, { url: url }) + .then((inlinedHtml) => { + res.send(inlinedHtml); + }) + .catch((error) => { + logger.log("email-inline-css-error", "ERROR", req.user.email, null, { + error + }); - res.send(error); - }); + res.send(error); + }); }; diff --git a/server/routes/accountingRoutes.js b/server/routes/accountingRoutes.js index 04576bf01..4d19cf7da 100644 --- a/server/routes/accountingRoutes.js +++ b/server/routes/accountingRoutes.js @@ -1,13 +1,13 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); -const {payments, payables, receivables} = require("../accounting/qbxml/qbxml"); +const { payments, payables, receivables } = require("../accounting/qbxml/qbxml"); const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); router.use(validateFirebaseIdTokenMiddleware); -router.post('/qbxml/receivables', withUserGraphQLClientMiddleware, receivables); -router.post('/qbxml/payables', withUserGraphQLClientMiddleware, payables); -router.post('/qbxml/payments', withUserGraphQLClientMiddleware, payments); +router.post("/qbxml/receivables", withUserGraphQLClientMiddleware, receivables); +router.post("/qbxml/payables", withUserGraphQLClientMiddleware, payables); +router.post("/qbxml/payments", withUserGraphQLClientMiddleware, payments); module.exports = router; diff --git a/server/routes/adminRoutes.js b/server/routes/adminRoutes.js index 617f343b3..c1d3fe85a 100644 --- a/server/routes/adminRoutes.js +++ b/server/routes/adminRoutes.js @@ -1,18 +1,18 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const fb = require('../firebase/firebase-handler'); +const fb = require("../firebase/firebase-handler"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); -const {createAssociation, createShop, updateShop, updateCounter} = require("../admin/adminops"); +const { createAssociation, createShop, updateShop, updateCounter } = require("../admin/adminops"); const validateAdminMiddleware = require("../middleware/validateAdminMiddleware"); router.use(validateFirebaseIdTokenMiddleware); -router.post('/createassociation', validateAdminMiddleware, createAssociation); -router.post('/createshop', validateAdminMiddleware, createShop); -router.post('/updateshop', validateAdminMiddleware, updateShop); -router.post('/updatecounter', validateAdminMiddleware, updateCounter); -router.post('/updateuser', fb.updateUser); -router.post('/getuser', fb.getUser); -router.post('/createuser', fb.createUser); +router.post("/createassociation", validateAdminMiddleware, createAssociation); +router.post("/createshop", validateAdminMiddleware, createShop); +router.post("/updateshop", validateAdminMiddleware, updateShop); +router.post("/updatecounter", validateAdminMiddleware, updateCounter); +router.post("/updateuser", fb.updateUser); +router.post("/getuser", fb.getUser); +router.post("/createuser", fb.createUser); module.exports = router; diff --git a/server/routes/cdkRoutes.js b/server/routes/cdkRoutes.js index 85d2b49d0..5fd0536cb 100644 --- a/server/routes/cdkRoutes.js +++ b/server/routes/cdkRoutes.js @@ -1,11 +1,11 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const cdkGetMake = require('../cdk/cdk-get-makes'); +const cdkGetMake = require("../cdk/cdk-get-makes"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); router.use(validateFirebaseIdTokenMiddleware); -router.post('/getvehicles', withUserGraphQLClientMiddleware, cdkGetMake.default); +router.post("/getvehicles", withUserGraphQLClientMiddleware, cdkGetMake.default); module.exports = router; diff --git a/server/routes/csiRoutes.js b/server/routes/csiRoutes.js index 11993ff8f..8f47a2b2d 100644 --- a/server/routes/csiRoutes.js +++ b/server/routes/csiRoutes.js @@ -1,6 +1,6 @@ const express = require("express"); const router = express.Router(); -const {lookup, submit} = require("../csi/csi"); +const { lookup, submit } = require("../csi/csi"); router.post("/lookup", lookup); router.post("/submit", submit); diff --git a/server/routes/dataRoutes.js b/server/routes/dataRoutes.js index 0240f3388..d7fe6e640 100644 --- a/server/routes/dataRoutes.js +++ b/server/routes/dataRoutes.js @@ -1,9 +1,9 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const {autohouse, claimscorp, kaizen} = require('../data/data'); +const { autohouse, claimscorp, kaizen } = require("../data/data"); -router.post('/ah', autohouse); -router.post('/cc', claimscorp); -router.post('/kaizen', kaizen); +router.post("/ah", autohouse); +router.post("/cc", claimscorp); +router.post("/kaizen", kaizen); module.exports = router; diff --git a/server/routes/intellipayRoutes.js b/server/routes/intellipayRoutes.js index 3952a3450..47f09ffb0 100644 --- a/server/routes/intellipayRoutes.js +++ b/server/routes/intellipayRoutes.js @@ -1,11 +1,11 @@ -const express = require('express'); +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 } = 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('/postback', postback); +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("/postback", postback); module.exports = router; diff --git a/server/routes/jobRoutes.js b/server/routes/jobRoutes.js index 3c5334b2e..1b655aa9f 100644 --- a/server/routes/jobRoutes.js +++ b/server/routes/jobRoutes.js @@ -1,20 +1,20 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const job = require('../job/job'); -const ppc = require('../ccc/partspricechange') -const {partsScan} = require('../parts-scan/parts-scan'); -const eventAuthorizationMiddleware = require('../middleware/eventAuthorizationMIddleware'); +const job = require("../job/job"); +const ppc = require("../ccc/partspricechange"); +const { partsScan } = require("../parts-scan/parts-scan"); +const eventAuthorizationMiddleware = require("../middleware/eventAuthorizationMIddleware"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); -const {totals, statustransition, totalsSsu, costing, lifecycle, costingmulti} = require("../job/job"); +const { totals, statustransition, totalsSsu, costing, lifecycle, costingmulti } = require("../job/job"); const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); -router.post('/totals', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, totals); -router.post('/statustransition', eventAuthorizationMiddleware, statustransition); -router.post('/totalsssu', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, totalsSsu); -router.post('/costing', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, costing); -router.post('/lifecycle', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, lifecycle); -router.post('/costingmulti', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, costingmulti); -router.post('/partsscan', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, partsScan); -router.post('/ppc', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, ppc.generatePpc); +router.post("/totals", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, totals); +router.post("/statustransition", eventAuthorizationMiddleware, statustransition); +router.post("/totalsssu", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, totalsSsu); +router.post("/costing", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, costing); +router.post("/lifecycle", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, lifecycle); +router.post("/costingmulti", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, costingmulti); +router.post("/partsscan", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, partsScan); +router.post("/ppc", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, ppc.generatePpc); module.exports = router; diff --git a/server/routes/mediaRoutes.js b/server/routes/mediaRoutes.js index a44a1a048..699579bb9 100644 --- a/server/routes/mediaRoutes.js +++ b/server/routes/mediaRoutes.js @@ -1,13 +1,13 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const {createSignedUploadURL, downloadFiles, renameKeys, deleteFiles} = require('../media/media'); +const { createSignedUploadURL, downloadFiles, renameKeys, deleteFiles } = require("../media/media"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); router.use(validateFirebaseIdTokenMiddleware); -router.post('/sign', createSignedUploadURL); -router.post('/download', downloadFiles); -router.post('/rename', renameKeys); -router.post('/delete', deleteFiles); +router.post("/sign", createSignedUploadURL); +router.post("/download", downloadFiles); +router.post("/rename", renameKeys); +router.post("/delete", deleteFiles); module.exports = router; diff --git a/server/routes/miscellaneousRoutes.js b/server/routes/miscellaneousRoutes.js index 9d20d9b42..9cf24ba99 100644 --- a/server/routes/miscellaneousRoutes.js +++ b/server/routes/miscellaneousRoutes.js @@ -1,4 +1,4 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); const logger = require("../../server/utils/logger"); const sendEmail = require("../email/sendemail"); @@ -13,39 +13,35 @@ const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLCl //Test route to ensure Express is responding. router.get("/test", async function (req, res) { - const commit = require("child_process").execSync( - "git rev-parse --short HEAD" - ); - // console.log(app.get('trust proxy')); - // console.log("remoteAddress", req.socket.remoteAddress); - // console.log("X-Forwarded-For", req.header('x-forwarded-for')); - logger.log("test-api-status", "DEBUG", "api", {commit}); - // sendEmail.sendServerEmail({ - // subject: `API Check - ${process.env.NODE_ENV}`, - // text: `Server API check has come in. Remote IP: ${req.socket.remoteAddress}, X-Forwarded-For: ${req.header('x-forwarded-for')}`, - // }); - sendEmail.sendServerEmail({ - subject: `API Check - ${process.env.NODE_ENV}`, - text: `Server API check has come in.`, - }); - res.status(200).send(`OK - ${commit}`); + const commit = require("child_process").execSync("git rev-parse --short HEAD"); + // console.log(app.get('trust proxy')); + // console.log("remoteAddress", req.socket.remoteAddress); + // console.log("X-Forwarded-For", req.header('x-forwarded-for')); + logger.log("test-api-status", "DEBUG", "api", { commit }); + // sendEmail.sendServerEmail({ + // subject: `API Check - ${process.env.NODE_ENV}`, + // text: `Server API check has come in. Remote IP: ${req.socket.remoteAddress}, X-Forwarded-For: ${req.header('x-forwarded-for')}`, + // }); + sendEmail.sendServerEmail({ + subject: `API Check - ${process.env.NODE_ENV}`, + text: `Server API check has come in.` + }); + res.status(200).send(`OK - ${commit}`); }); // Search router.post("/search", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, os.search); router.post("/opensearch", eventAuthorizationMiddleware, os.handler); - // IO Events -router.post('/ioevent', ioevent.default); +router.post("/ioevent", ioevent.default); // Email -router.post('/sendemail', validateFirebaseIdTokenMiddleware, sendEmail.sendEmail); -router.post('/emailbounce', bodyParser.text(), sendEmail.emailBounce); +router.post("/sendemail", validateFirebaseIdTokenMiddleware, sendEmail.sendEmail); +router.post("/emailbounce", bodyParser.text(), sendEmail.emailBounce); // Handlers -router.post('/record-handler/arms', data.arms); +router.post("/record-handler/arms", data.arms); router.post("/taskHandler", validateFirebaseIdTokenMiddleware, taskHandler.taskHandler); - module.exports = router; diff --git a/server/routes/mixDataRoutes.js b/server/routes/mixDataRoutes.js index b9ac289e7..1a25c77dc 100644 --- a/server/routes/mixDataRoutes.js +++ b/server/routes/mixDataRoutes.js @@ -1,11 +1,11 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const multer = require('multer'); +const multer = require("multer"); const upload = multer(); -const {mixdataUpload} = require('../mixdata/mixdata'); +const { mixdataUpload } = require("../mixdata/mixdata"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); -router.post('/upload', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, upload.any(), mixdataUpload); +router.post("/upload", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, upload.any(), mixdataUpload); module.exports = router; diff --git a/server/routes/notificationsRoutes.js b/server/routes/notificationsRoutes.js index 1a8e9de7b..641143f0d 100644 --- a/server/routes/notificationsRoutes.js +++ b/server/routes/notificationsRoutes.js @@ -1,11 +1,11 @@ -const express = require('express'); +const express = require("express"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); -const {subscribe, unsubscribe} = require("../firebase/firebase-handler"); +const { subscribe, unsubscribe } = require("../firebase/firebase-handler"); const router = express.Router(); router.use(validateFirebaseIdTokenMiddleware); -router.post('/subscribe', subscribe); -router.post('/unsubscribe', unsubscribe); +router.post("/subscribe", subscribe); +router.post("/unsubscribe", unsubscribe); module.exports = router; diff --git a/server/routes/payrollRoutes.js b/server/routes/payrollRoutes.js index 1666184ec..0a3ce8a71 100644 --- a/server/routes/payrollRoutes.js +++ b/server/routes/payrollRoutes.js @@ -1,6 +1,6 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const payroll = require('../payroll/payroll'); +const payroll = require("../payroll/payroll"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); @@ -12,4 +12,3 @@ router.post("/payall", payroll.payall); router.post("/claimtask", payroll.claimtask); module.exports = router; - diff --git a/server/routes/qboRoutes.js b/server/routes/qboRoutes.js index 22b54e23e..47e4fc220 100644 --- a/server/routes/qboRoutes.js +++ b/server/routes/qboRoutes.js @@ -1,14 +1,14 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const {authorize, callback, receivables, payables, payments} = require('../accounting/qbo/qbo'); +const { authorize, callback, receivables, payables, payments } = require("../accounting/qbo/qbo"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); // Assuming you have a qbo module for handling QuickBooks Online related functionalities // Define the routes for QuickBooks Online -router.post('/authorize', validateFirebaseIdTokenMiddleware, authorize); -router.get('/callback', callback); -router.post('/receivables', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, receivables); -router.post('/payables', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, payables); -router.post('/payments', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, payments); +router.post("/authorize", validateFirebaseIdTokenMiddleware, authorize); +router.get("/callback", callback); +router.post("/receivables", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, receivables); +router.post("/payables", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, payables); +router.post("/payments", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, payments); module.exports = router; diff --git a/server/routes/renderRoutes.js b/server/routes/renderRoutes.js index 7242404e5..5bb91616a 100644 --- a/server/routes/renderRoutes.js +++ b/server/routes/renderRoutes.js @@ -1,9 +1,9 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const {inlinecss} = require('../render/inlinecss'); +const { inlinecss } = require("../render/inlinecss"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); // Define the route for inline CSS rendering -router.post('/inlinecss', validateFirebaseIdTokenMiddleware, inlinecss); +router.post("/inlinecss", validateFirebaseIdTokenMiddleware, inlinecss); module.exports = router; diff --git a/server/routes/schedulingRoutes.js b/server/routes/schedulingRoutes.js index 816114315..368c3f769 100644 --- a/server/routes/schedulingRoutes.js +++ b/server/routes/schedulingRoutes.js @@ -1,9 +1,9 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const {job} = require('../scheduling/scheduling-job'); +const { job } = require("../scheduling/scheduling-job"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); -router.post('/job', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, job); +router.post("/job", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, job); module.exports = router; diff --git a/server/routes/smsRoutes.js b/server/routes/smsRoutes.js index a97b704ba..bb23d24e8 100644 --- a/server/routes/smsRoutes.js +++ b/server/routes/smsRoutes.js @@ -1,17 +1,17 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const twilio = require('twilio'); -const {receive} = require('../sms/receive'); -const {send} = require('../sms/send'); -const {status, markConversationRead} = require('../sms/status'); +const twilio = require("twilio"); +const { receive } = require("../sms/receive"); +const { send } = require("../sms/send"); +const { status, markConversationRead } = require("../sms/status"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); // Twilio Webhook Middleware for production -const twilioWebhookMiddleware = twilio.webhook({validate: process.env.NODE_ENV === "PRODUCTION"}); +const twilioWebhookMiddleware = twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }); -router.post('/receive', twilioWebhookMiddleware, receive); -router.post('/send', validateFirebaseIdTokenMiddleware, send); -router.post('/status', twilioWebhookMiddleware, status); -router.post('/markConversationRead', validateFirebaseIdTokenMiddleware, markConversationRead); +router.post("/receive", twilioWebhookMiddleware, receive); +router.post("/send", validateFirebaseIdTokenMiddleware, send); +router.post("/status", twilioWebhookMiddleware, status); +router.post("/markConversationRead", validateFirebaseIdTokenMiddleware, markConversationRead); module.exports = router; diff --git a/server/routes/techRoutes.js b/server/routes/techRoutes.js index e7594f532..ff0feea28 100644 --- a/server/routes/techRoutes.js +++ b/server/routes/techRoutes.js @@ -1,8 +1,8 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const {techLogin} = require('../tech/tech'); +const { techLogin } = require("../tech/tech"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); -router.post('/login', validateFirebaseIdTokenMiddleware, techLogin); +router.post("/login", validateFirebaseIdTokenMiddleware, techLogin); module.exports = router; diff --git a/server/routes/utilRoutes.js b/server/routes/utilRoutes.js index 938b1e1b1..e54d54561 100644 --- a/server/routes/utilRoutes.js +++ b/server/routes/utilRoutes.js @@ -1,9 +1,9 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const {servertime, jsrAuth} = require('../utils/utils'); +const { servertime, jsrAuth } = require("../utils/utils"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); -router.post('/time', servertime); -router.post('/jsr', validateFirebaseIdTokenMiddleware, jsrAuth); +router.post("/time", servertime); +router.post("/jsr", validateFirebaseIdTokenMiddleware, jsrAuth); module.exports = router; diff --git a/server/scheduling/scheduling-job.js b/server/scheduling/scheduling-job.js index a9154a315..a005a8d72 100644 --- a/server/scheduling/scheduling-job.js +++ b/server/scheduling/scheduling-job.js @@ -4,311 +4,271 @@ const Dinero = require("dinero.js"); const moment = require("moment-timezone"); const logger = require("../utils/logger"); const _ = require("lodash"); -const {filter} = require("lodash"); +const { filter } = require("lodash"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); exports.job = async (req, res) => { - const {jobId} = req.body; + const { jobId } = req.body; - const BearerToken = req.BearerToken; - const client = req.userGraphQLClient; + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; - try { - logger.log("smart-scheduling-start", "DEBUG", req.user.email, jobId, null); + try { + logger.log("smart-scheduling-start", "DEBUG", req.user.email, jobId, null); - const result = await client - .setHeaders({Authorization: BearerToken}) - .request(queries.QUERY_UPCOMING_APPOINTMENTS, { - now: moment().startOf("day"), - jobId: jobId, - }); + const result = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.QUERY_UPCOMING_APPOINTMENTS, { + now: moment().startOf("day"), + jobId: jobId + }); - const {jobs_by_pk, blockedDays, prodJobs, arrJobs, compJobs} = result; - const {ssbuckets, workingdays, timezone, ss_configuration} = - result.jobs_by_pk.bodyshop; - const jobHrs = result.jobs_by_pk.jobhrs.aggregate.sum.mod_lb_hrs; + const { jobs_by_pk, blockedDays, prodJobs, arrJobs, compJobs } = result; + const { ssbuckets, workingdays, timezone, ss_configuration } = result.jobs_by_pk.bodyshop; + const jobHrs = result.jobs_by_pk.jobhrs.aggregate.sum.mod_lb_hrs; - const JobBucket = ssbuckets.filter( - (bucket) => - bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true) - )[0]; - const load = { - productionTotal: {}, - productionHours: 0, - }; - //Set the current load. - ssbuckets.forEach((bucket) => { - load.productionTotal[bucket.id] = {count: 0, label: bucket.label}; - }); + const JobBucket = ssbuckets.filter( + (bucket) => bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true) + )[0]; + const load = { + productionTotal: {}, + productionHours: 0 + }; + //Set the current load. + ssbuckets.forEach((bucket) => { + load.productionTotal[bucket.id] = { count: 0, label: bucket.label }; + }); - const filteredProdJobsList = prodJobs.filter( - (j) => JobBucket.id === CheckJobBucket(ssbuckets, j) - ); + const filteredProdJobsList = prodJobs.filter((j) => JobBucket.id === CheckJobBucket(ssbuckets, j)); - filteredProdJobsList.forEach((item) => { - //Add all of the jobs currently in production to the buckets so that we have a starting point. - const bucketId = CheckJobBucket(ssbuckets, item); - if (bucketId) { - load.productionTotal[bucketId].count = - load.productionTotal[bucketId].count + 1; - } else { - console.log("Uh oh, this job doesn't fit in a bucket!", item); - } - }); + filteredProdJobsList.forEach((item) => { + //Add all of the jobs currently in production to the buckets so that we have a starting point. + const bucketId = CheckJobBucket(ssbuckets, item); + if (bucketId) { + load.productionTotal[bucketId].count = load.productionTotal[bucketId].count + 1; + } else { + console.log("Uh oh, this job doesn't fit in a bucket!", item); + } + }); - // const filteredArrJobs = arrJobs.filter( - // (j) => JobBucket.id === CheckJobBucket(ssbuckets, j) - // ); - const filteredArrJobs = []; + // const filteredArrJobs = arrJobs.filter( + // (j) => JobBucket.id === CheckJobBucket(ssbuckets, j) + // ); + const filteredArrJobs = []; - arrJobs.forEach((item) => { - let isSameBucket = false; - if (JobBucket.id === CheckJobBucket(ssbuckets, item)) { - filteredArrJobs.push(item); - isSameBucket = true; - } + arrJobs.forEach((item) => { + let isSameBucket = false; + if (JobBucket.id === CheckJobBucket(ssbuckets, item)) { + filteredArrJobs.push(item); + isSameBucket = true; + } - let jobHours = - item.labhrs.aggregate.sum.mod_lb_hrs + - item.larhrs.aggregate.sum.mod_lb_hrs; + let jobHours = item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs; - const AddJobForSchedulingCalc = !item.inproduction; + const AddJobForSchedulingCalc = !item.inproduction; - const itemDate = moment(item.actual_in || item.scheduled_in) - .tz(timezone) - .format("yyyy-MM-DD"); - if (isSameBucket) { - if (!!load[itemDate]) { - load[itemDate].hoursIn = - (load[itemDate].hoursIn || 0) + AddJobForSchedulingCalc - ? jobHours - : 0; - if (AddJobForSchedulingCalc) load[itemDate].jobsIn.push(item); - } else { - load[itemDate] = { - jobsIn: AddJobForSchedulingCalc ? [item] : [], - jobsOut: [], - hoursIn: AddJobForSchedulingCalc ? jobHours : 0, - }; - } - } - - if (!load[itemDate]) { - load[itemDate] = { - jobsIn: [], - jobsOut: [], - hoursIn: 0, - hoursInTotal: 0, - }; - } - load[itemDate].hoursInTotal = - (load[itemDate].hoursInTotal || 0) + jobHours; - }); - - //Get the completing jobs. - let problemJobs = []; - const filteredCompJobs = compJobs.filter( - (j) => JobBucket.id === CheckJobBucket(ssbuckets, j) - ); - - filteredCompJobs.forEach((item) => { - const inProdJobs = filteredProdJobsList.find((p) => p.id === item.id); - const inArrJobs = filteredArrJobs.find((p) => p.id === item.id); - - const AddJobForSchedulingCalc = inProdJobs || inArrJobs; - - const itemDate = moment( - item.actual_completion || item.scheduled_completion - ) - .tz(timezone) - .format("yyyy-MM-DD"); - if (!!load[itemDate]) { - load[itemDate].hoursOut = - (load[itemDate].hoursOut || 0) + AddJobForSchedulingCalc - ? item.labhrs.aggregate.sum.mod_lb_hrs + - item.larhrs.aggregate.sum.mod_lb_hrs - : 0; - if (AddJobForSchedulingCalc) load[itemDate].jobsOut.push(item); - } else { - load[itemDate] = { - jobsOut: AddJobForSchedulingCalc ? [item] : [], - hoursOut: AddJobForSchedulingCalc - ? item.labhrs.aggregate.sum.mod_lb_hrs + - item.larhrs.aggregate.sum.mod_lb_hrs - : 0, - }; - } - }); - - //Propagate the expected load to each day. - const yesterday = moment().tz(timezone).subtract(1, "day"); - const today = moment().tz(timezone); - - const end = moment.max([ - ...filteredArrJobs.map((a) => moment(a.scheduled_in).tz(timezone)), - ...filteredCompJobs - .map((p) => - moment(p.actual_completion || p.scheduled_completion).tz(timezone) - ) - .filter((p) => p.isValid() && p.isAfter(yesterday)), - moment().tz(timezone).add(15, "days"), - ]); - const range = Math.round( - moment.duration(end.add(20, "days").diff(today)).asDays() - ); - for (var day = 0; day < range; day++) { - const current = moment(today) - .tz(timezone) - .add(day, "days") - .format("yyyy-MM-DD"); - const prev = moment(today) - .tz(timezone) - .add(day - 1, "days") - .format("yyyy-MM-DD"); - if (!!!load[current]) { - load[current] = {}; - } - if (day === 0) { - //Starting on day 1. The load is current. - load[current].expectedLoad = CalculateLoad( - load.productionTotal, - ssbuckets, - load[current].jobsIn || [], - load[current].jobsOut || [] - ); - } else { - load[current].expectedLoad = CalculateLoad( - load[prev].expectedLoad, - ssbuckets, - load[current].jobsIn || [], - load[current].jobsOut || [] - ); - } - } - - //Add in all of the blocked days. - - blockedDays.forEach((b) => { - //Find it in the load, set it as blocked. - const startIsoFormat = moment(b.start).tz(timezone).format("YYYY-MM-DD"); - if (load[startIsoFormat]) load[startIsoFormat].blocked = true; - else { - load[startIsoFormat] = {blocked: true}; - } - }); - // //Propose the first 10 dates where we are below target. - - const possibleDates = []; - delete load.productionTotal; - const loadKeys = Object.keys(load).sort((a, b) => - moment(a).isAfter(moment(b)) ? 1 : -1 - ); - - loadKeys.forEach((loadKey) => { - const isShopOpen = - (workingdays[dayOfWeekMapper(moment(loadKey).day())] || false) && - !load[loadKey].blocked; - - let isUnderDailyTotalLimit = true; - - if ( - ss_configuration && - ss_configuration.dailyhrslimit && - ss_configuration.dailyhrslimit > 0 && - load[loadKey] && - load[loadKey].hoursInTotal && - load[loadKey].hoursInTotal > ss_configuration.dailyhrslimit - ) { - isUnderDailyTotalLimit = false; - } - - if ( - load[loadKey].expectedLoad && - load[loadKey].expectedLoad[JobBucket.id] && - JobBucket.target > load[loadKey].expectedLoad[JobBucket.id].count && - isShopOpen && - isUnderDailyTotalLimit - ) - possibleDates.push(new Date(loadKey).toISOString().substr(0, 10)); - }); - - if (possibleDates.length < 11) { - res.json(possibleDates); + const itemDate = moment(item.actual_in || item.scheduled_in) + .tz(timezone) + .format("yyyy-MM-DD"); + if (isSameBucket) { + if (!!load[itemDate]) { + load[itemDate].hoursIn = (load[itemDate].hoursIn || 0) + AddJobForSchedulingCalc ? jobHours : 0; + if (AddJobForSchedulingCalc) load[itemDate].jobsIn.push(item); } else { - res.json(possibleDates.slice(0, 10)); + load[itemDate] = { + jobsIn: AddJobForSchedulingCalc ? [item] : [], + jobsOut: [], + hoursIn: AddJobForSchedulingCalc ? jobHours : 0 + }; } - } catch (error) { - logger.log("smart-scheduling-error", "ERROR", req.user.email, jobId, { - error, - }); - res.status(400).send(error); + } + + if (!load[itemDate]) { + load[itemDate] = { + jobsIn: [], + jobsOut: [], + hoursIn: 0, + hoursInTotal: 0 + }; + } + load[itemDate].hoursInTotal = (load[itemDate].hoursInTotal || 0) + jobHours; + }); + + //Get the completing jobs. + let problemJobs = []; + const filteredCompJobs = compJobs.filter((j) => JobBucket.id === CheckJobBucket(ssbuckets, j)); + + filteredCompJobs.forEach((item) => { + const inProdJobs = filteredProdJobsList.find((p) => p.id === item.id); + const inArrJobs = filteredArrJobs.find((p) => p.id === item.id); + + const AddJobForSchedulingCalc = inProdJobs || inArrJobs; + + const itemDate = moment(item.actual_completion || item.scheduled_completion) + .tz(timezone) + .format("yyyy-MM-DD"); + if (!!load[itemDate]) { + load[itemDate].hoursOut = + (load[itemDate].hoursOut || 0) + AddJobForSchedulingCalc + ? item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs + : 0; + if (AddJobForSchedulingCalc) load[itemDate].jobsOut.push(item); + } else { + load[itemDate] = { + jobsOut: AddJobForSchedulingCalc ? [item] : [], + hoursOut: AddJobForSchedulingCalc + ? item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs + : 0 + }; + } + }); + + //Propagate the expected load to each day. + const yesterday = moment().tz(timezone).subtract(1, "day"); + const today = moment().tz(timezone); + + const end = moment.max([ + ...filteredArrJobs.map((a) => moment(a.scheduled_in).tz(timezone)), + ...filteredCompJobs + .map((p) => moment(p.actual_completion || p.scheduled_completion).tz(timezone)) + .filter((p) => p.isValid() && p.isAfter(yesterday)), + moment().tz(timezone).add(15, "days") + ]); + const range = Math.round(moment.duration(end.add(20, "days").diff(today)).asDays()); + for (var day = 0; day < range; day++) { + const current = moment(today).tz(timezone).add(day, "days").format("yyyy-MM-DD"); + const prev = moment(today) + .tz(timezone) + .add(day - 1, "days") + .format("yyyy-MM-DD"); + if (!!!load[current]) { + load[current] = {}; + } + if (day === 0) { + //Starting on day 1. The load is current. + load[current].expectedLoad = CalculateLoad( + load.productionTotal, + ssbuckets, + load[current].jobsIn || [], + load[current].jobsOut || [] + ); + } else { + load[current].expectedLoad = CalculateLoad( + load[prev].expectedLoad, + ssbuckets, + load[current].jobsIn || [], + load[current].jobsOut || [] + ); + } } + + //Add in all of the blocked days. + + blockedDays.forEach((b) => { + //Find it in the load, set it as blocked. + const startIsoFormat = moment(b.start).tz(timezone).format("YYYY-MM-DD"); + if (load[startIsoFormat]) load[startIsoFormat].blocked = true; + else { + load[startIsoFormat] = { blocked: true }; + } + }); + // //Propose the first 10 dates where we are below target. + + const possibleDates = []; + delete load.productionTotal; + const loadKeys = Object.keys(load).sort((a, b) => (moment(a).isAfter(moment(b)) ? 1 : -1)); + + loadKeys.forEach((loadKey) => { + const isShopOpen = (workingdays[dayOfWeekMapper(moment(loadKey).day())] || false) && !load[loadKey].blocked; + + let isUnderDailyTotalLimit = true; + + if ( + ss_configuration && + ss_configuration.dailyhrslimit && + ss_configuration.dailyhrslimit > 0 && + load[loadKey] && + load[loadKey].hoursInTotal && + load[loadKey].hoursInTotal > ss_configuration.dailyhrslimit + ) { + isUnderDailyTotalLimit = false; + } + + if ( + load[loadKey].expectedLoad && + load[loadKey].expectedLoad[JobBucket.id] && + JobBucket.target > load[loadKey].expectedLoad[JobBucket.id].count && + isShopOpen && + isUnderDailyTotalLimit + ) + possibleDates.push(new Date(loadKey).toISOString().substr(0, 10)); + }); + + if (possibleDates.length < 11) { + res.json(possibleDates); + } else { + res.json(possibleDates.slice(0, 10)); + } + } catch (error) { + logger.log("smart-scheduling-error", "ERROR", req.user.email, jobId, { + error + }); + res.status(400).send(error); + } }; const dayOfWeekMapper = (numberOfDay) => { - switch (numberOfDay) { - case 0: - return "sunday"; - case 1: - return "monday"; - case 2: - return "tuesday"; - case 3: - return "wednesday"; - case 4: - return "thursday"; - case 5: - return "friday"; - case 6: - return "saturday"; - } + switch (numberOfDay) { + case 0: + return "sunday"; + case 1: + return "monday"; + case 2: + return "tuesday"; + case 3: + return "wednesday"; + case 4: + return "thursday"; + case 5: + return "friday"; + case 6: + return "saturday"; + } }; const CheckJobBucket = (buckets, job) => { - const jobHours = - job.labhrs.aggregate.sum.mod_lb_hrs + job.larhrs.aggregate.sum.mod_lb_hrs; + const jobHours = job.labhrs.aggregate.sum.mod_lb_hrs + job.larhrs.aggregate.sum.mod_lb_hrs; - const matchingBucket = buckets.filter((b) => - b.gte <= jobHours && b.lt ? b.lt > jobHours : true - ); + const matchingBucket = buckets.filter((b) => (b.gte <= jobHours && b.lt ? b.lt > jobHours : true)); - return matchingBucket[0] && matchingBucket[0].id; + return matchingBucket[0] && matchingBucket[0].id; }; const CalculateLoad = (currentLoad, buckets, jobsIn, jobsOut) => { - //Add the jobs coming - const newLoad = _.cloneDeep(currentLoad); - jobsIn.forEach((job) => { - const bucketId = CheckJobBucket(buckets, job); - if (bucketId) { - newLoad[bucketId].count = newLoad[bucketId].count + 1; - } else { - console.log( - "[Util Arr Job]Uh oh, this job doesn't fit in a bucket!", - job - ); - } - }); + //Add the jobs coming + const newLoad = _.cloneDeep(currentLoad); + jobsIn.forEach((job) => { + const bucketId = CheckJobBucket(buckets, job); + if (bucketId) { + newLoad[bucketId].count = newLoad[bucketId].count + 1; + } else { + console.log("[Util Arr Job]Uh oh, this job doesn't fit in a bucket!", job); + } + }); - jobsOut.forEach((job) => { - const bucketId = CheckJobBucket(buckets, job); - if (bucketId) { - newLoad[bucketId].count = newLoad[bucketId].count - 1; - if (newLoad[bucketId].count < 0) { - console.log("***ERROR: NEGATIVE LOAD Bucket =>", bucketId, job); - } - } else { - console.log( - "[Util Out Job]Uh oh, this job doesn't fit in a bucket!", - job - ); - } - }); + jobsOut.forEach((job) => { + const bucketId = CheckJobBucket(buckets, job); + if (bucketId) { + newLoad[bucketId].count = newLoad[bucketId].count - 1; + if (newLoad[bucketId].count < 0) { + console.log("***ERROR: NEGATIVE LOAD Bucket =>", bucketId, job); + } + } else { + console.log("[Util Out Job]Uh oh, this job doesn't fit in a bucket!", job); + } + }); - return newLoad; + return newLoad; }; diff --git a/server/sms/receive.js b/server/sms/receive.js index fa560e996..ad9291a7f 100644 --- a/server/sms/receive.js +++ b/server/sms/receive.js @@ -1,146 +1,132 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const client = require("../graphql-client/graphql-client").client; const queries = require("../graphql-client/queries"); -const {phone} = require("phone"); -const {admin} = require("../firebase/firebase-handler"); +const { phone } = require("phone"); +const { admin } = require("../firebase/firebase-handler"); const logger = require("../utils/logger"); const InstanceManager = require("../utils/instanceMgr").default; exports.receive = async (req, res) => { - //Perform request validation + //Perform request validation - logger.log("sms-inbound", "DEBUG", "api", null, { + logger.log("sms-inbound", "DEBUG", "api", null, { + msid: req.body.SmsMessageSid, + text: req.body.Body, + image: !!req.body.MediaUrl0, + image_path: generateMediaArray(req.body) + }); + + 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), + 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 + }); + + let newMessage = { msid: req.body.SmsMessageSid, text: req.body.Body, image: !!req.body.MediaUrl0, - image_path: generateMediaArray(req.body), - }); - - if ( - !!!req.body || - !!!req.body.MessagingServiceSid || - !!!req.body.SmsMessageSid - ) { - logger.log("sms-inbound-error", "ERROR", "api", null, { + 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), - 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, - } - ); - - 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, - }); - } - 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, - }); - - logger.log("sms-inbound-success", "DEBUG", "api", null, { - newMessage, - fcmresp, - }); - res.status(200).send(""); - } catch (e2) { - 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, - }); - - res.sendStatus(500).json(e2); - } - } - } catch (e1) { - console.log("e1", e1); - res.sendStatus(500).json(e1); + 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 + }); + } + 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 + }); + + logger.log("sms-inbound-success", "DEBUG", "api", null, { + newMessage, + fcmresp + }); + res.status(200).send(""); + } catch (e2) { + 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 + }); + + res.sendStatus(500).json(e2); + } + } + } catch (e1) { + console.log("e1", e1); + res.sendStatus(500).json(e1); } + } }; // const sampleMessage: { @@ -194,15 +180,15 @@ exports.receive = async (req, res) => { // MediaContentType0: 'video/3gpp', const generateMediaArray = (body) => { - const {NumMedia} = body; - if (parseInt(NumMedia) > 0) { - //stuff - const ret = []; - for (var i = 0; i < parseInt(NumMedia); i++) { - ret.push(body[`MediaUrl${i}`]); - } - return ret; - } else { - return null; + const { NumMedia } = body; + if (parseInt(NumMedia) > 0) { + //stuff + const ret = []; + for (var i = 0; i < parseInt(NumMedia); i++) { + ret.push(body[`MediaUrl${i}`]); } + return ret; + } else { + return null; + } }; diff --git a/server/sms/send.js b/server/sms/send.js index b6019c9b2..e86966342 100644 --- a/server/sms/send.js +++ b/server/sms/send.js @@ -1,127 +1,100 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const twilio = require("twilio"); -const {phone} = require("phone"); +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 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 { to, messagingServiceSid, body, conversationid, selectedMedia, imexshopid } = req.body; - logger.log("sms-outbound", "DEBUG", req.user.email, null, { + logger.log("sms-outbound", "DEBUG", req.user.email, null, { + messagingServiceSid: messagingServiceSid, + to: phone(to).phoneNumber, + mediaUrl: selectedMedia.map((i) => i.src), + 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) : [] + }); + + 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), - 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) : [], - }); - - 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 || "", - }; - - admin.messaging().send({ - topic: `${imexshopid}-messaging`, - data, - }); - - res.sendStatus(200); - }) - .catch((e2) => { - logger.log("sms-outbound-error", "ERROR", req.user.email, null, { - msid: message.sid, - conversationid, - error: e2, - }); - - //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, - }); + 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 }); - } else { + + const data = { + type: "messaging-outbound", + conversationid: newMessage.conversationid || "" + }; + + admin.messaging().send({ + topic: `${imexshopid}-messaging`, + data + }); + + res.sendStatus(200); + }) + .catch((e2) => { + logger.log("sms-outbound-error", "ERROR", req.user.email, null, { + msid: message.sid, + conversationid, + error: e2 + }); + + //res.json({ success: false, message: e2 }); + }); + }) + .catch((e1) => { + //res.json({ success: false, message: error }); logger.log("sms-outbound-error", "ERROR", req.user.email, null, { - type: "missing-parameters", - messagingServiceSid: messagingServiceSid, - to: phone(to).phoneNumber, - 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) - : [], + conversationid, + error: e1 }); - res - .status(400) - .json({success: false, message: "Missing required parameter(s)."}); - } + }); + } else { + logger.log("sms-outbound-error", "ERROR", req.user.email, null, { + type: "missing-parameters", + messagingServiceSid: messagingServiceSid, + to: phone(to).phoneNumber, + 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) : [] + }); + res.status(400).json({ success: false, message: "Missing required parameter(s)." }); + } }; diff --git a/server/sms/status.js b/server/sms/status.js index 2f57c961a..9b5aeb733 100644 --- a/server/sms/status.js +++ b/server/sms/status.js @@ -1,55 +1,52 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const client = require("../graphql-client/graphql-client").client; const queries = require("../graphql-client/queries"); -const {phone} = require("phone"); +const { phone } = require("phone"); const logger = require("../utils/logger"); -const {admin} = require("../firebase/firebase-handler"); +const { admin } = require("../firebase/firebase-handler"); exports.status = (req, res) => { - const {SmsSid, SmsStatus} = req.body; - client - .request(queries.UPDATE_MESSAGE_STATUS, { - msid: SmsSid, - fields: {status: SmsStatus}, - }) - .then((response) => { - logger.log("sms-status-update", "DEBUG", "api", null, { - msid: SmsSid, - fields: {status: SmsStatus}, - }); - }) - .catch((error) => { - logger.log("sms-status-update-error", "ERROR", "api", null, { - msid: SmsSid, - fields: {status: SmsStatus}, - error, - }); - }); - res.sendStatus(200); + const { SmsSid, SmsStatus } = req.body; + client + .request(queries.UPDATE_MESSAGE_STATUS, { + msid: SmsSid, + fields: { status: SmsStatus } + }) + .then((response) => { + logger.log("sms-status-update", "DEBUG", "api", null, { + msid: SmsSid, + fields: { status: SmsStatus } + }); + }) + .catch((error) => { + logger.log("sms-status-update-error", "ERROR", "api", null, { + msid: SmsSid, + fields: { status: SmsStatus }, + error + }); + }); + res.sendStatus(200); }; 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 || "", - }, - }); - res.send(200); + 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 || "" + } + }); + res.send(200); }; // Inbound Sample diff --git a/server/stripe/payment.js b/server/stripe/payment.js index 87deb8b93..097302f34 100644 --- a/server/stripe/payment.js +++ b/server/stripe/payment.js @@ -1,45 +1,42 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); -const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); +const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY); const processor = async (req, res) => { - const {amount, stripe_acct_id} = req.body; + const { amount, stripe_acct_id } = req.body; - try { - await stripe.paymentIntents - .create( - { - payment_method_types: ["card"], - amount: amount, - currency: "cad", - application_fee_amount: 50, - }, - { - stripeAccount: stripe_acct_id, - } - ) - .then(function (paymentIntent) { - try { - return res.send({ - clientSecret: paymentIntent.client_secret, - }); - } catch (err) { - return res.status(500).send({ - error: err.message, - }); - } - }); - } catch (error) { - console.log("error", error); - res.status(400).send(error); - } + try { + await stripe.paymentIntents + .create( + { + payment_method_types: ["card"], + amount: amount, + currency: "cad", + application_fee_amount: 50 + }, + { + stripeAccount: stripe_acct_id + } + ) + .then(function (paymentIntent) { + try { + return res.send({ + clientSecret: paymentIntent.client_secret + }); + } catch (err) { + return res.status(500).send({ + error: err.message + }); + } + }); + } catch (error) { + console.log("error", error); + res.status(400).send(error); + } }; exports.payment = processor; diff --git a/server/tasks/tasks.js b/server/tasks/tasks.js index 9ba237635..d59d834eb 100644 --- a/server/tasks/tasks.js +++ b/server/tasks/tasks.js @@ -1,9 +1,6 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const client = require("../graphql-client/graphql-client").client; const emailer = require("../email/sendemail"); @@ -11,74 +8,76 @@ const moment = require("moment-timezone"); const converter = require("json-2-csv"); exports.taskHandler = async (req, res) => { - try { - const {bodyshopid, query, variables, text, to, subject, timezone} = req.body; + try { + const { bodyshopid, query, variables, text, to, subject, timezone } = req.body; - //Check the variables to see if they are an object. - Object.keys(variables).forEach((key) => { - if (typeof variables[key] === "object") { - if (variables[key].function) { - variables[key] = functionMapper(variables[key].function, timezone); - } - } - }); + //Check the variables to see if they are an object. + Object.keys(variables).forEach((key) => { + if (typeof variables[key] === "object") { + if (variables[key].function) { + variables[key] = functionMapper(variables[key].function, timezone); + } + } + }); - const response = await client.request(query, variables); - const rootElement = response[Object.keys(response)[0]]; //This element should always be an array. + const response = await client.request(query, variables); + const rootElement = response[Object.keys(response)[0]]; //This element should always be an array. - const csv = converter.json2csv(rootElement, {emptyFieldValue: ""}); + const csv = converter.json2csv(rootElement, { emptyFieldValue: "" }); - emailer.sendTaskEmail({ - to, - subject, - text, - attachments: [{filename: "query.csv", content: csv}], - }).catch(err => { - console.error('Errors sending CSV Email.') - }); + emailer + .sendTaskEmail({ + to, + subject, + text, + attachments: [{ filename: "query.csv", content: csv }] + }) + .catch((err) => { + console.error("Errors sending CSV Email."); + }); - return res.status(200).send(csv); - } catch (error) { - res.status(500).json({error: error.message, stack: error.stackTrace}); - } + return res.status(200).send(csv); + } catch (error) { + res.status(500).json({ error: error.message, stack: error.stackTrace }); + } }; const isoFormat = "YYYY-MM-DD"; function functionMapper(f, timezone) { - switch (f) { - case "date.today": - return moment().tz(timezone).format(isoFormat); - case "date.now": - return moment().tz(timezone); - case "date.yesterday": - return moment().tz(timezone).subtract(1, "day").format(isoFormat); - case "date.3daysago": - return moment().tz(timezone).subtract(3, "day").format(isoFormat); - case "date.7daysago": - return moment().tz(timezone).subtract(7, "day").format(isoFormat); - case "date.tomorrow": - return moment().tz(timezone).add(1, "day").format(isoFormat); - case "date.3daysfromnow": - return moment().tz(timezone).add(3, "day").format(isoFormat); - case "date.7daysfromnow": - return moment().tz(timezone).add(7, "day").format(isoFormat); - case "date.yesterdaytz": - return moment().tz(timezone).subtract(1, "day"); - case "date.3daysagotz": - return moment().tz(timezone).subtract(3, "day"); - case "date.7daysagotz": - return moment().tz(timezone).subtract(7, "day"); - case "date.tomorrowtz": - return moment().tz(timezone).add(1, "day"); - case "date.3daysfromnowtz": - return moment().tz(timezone).add(3, "day"); - case "date.7daysfromnowtz": - return moment().tz(timezone).add(7, "day"); + switch (f) { + case "date.today": + return moment().tz(timezone).format(isoFormat); + case "date.now": + return moment().tz(timezone); + case "date.yesterday": + return moment().tz(timezone).subtract(1, "day").format(isoFormat); + case "date.3daysago": + return moment().tz(timezone).subtract(3, "day").format(isoFormat); + case "date.7daysago": + return moment().tz(timezone).subtract(7, "day").format(isoFormat); + case "date.tomorrow": + return moment().tz(timezone).add(1, "day").format(isoFormat); + case "date.3daysfromnow": + return moment().tz(timezone).add(3, "day").format(isoFormat); + case "date.7daysfromnow": + return moment().tz(timezone).add(7, "day").format(isoFormat); + case "date.yesterdaytz": + return moment().tz(timezone).subtract(1, "day"); + case "date.3daysagotz": + return moment().tz(timezone).subtract(3, "day"); + case "date.7daysagotz": + return moment().tz(timezone).subtract(7, "day"); + case "date.tomorrowtz": + return moment().tz(timezone).add(1, "day"); + case "date.3daysfromnowtz": + return moment().tz(timezone).add(3, "day"); + case "date.7daysfromnowtz": + return moment().tz(timezone).add(7, "day"); - case "date.now": - return moment().tz(timezone); - default: - return f; - } + case "date.now": + return moment().tz(timezone); + default: + return f; + } } diff --git a/server/tech/tech.js b/server/tech/tech.js index d233a67ef..794958f5d 100644 --- a/server/tech/tech.js +++ b/server/tech/tech.js @@ -3,48 +3,45 @@ const queries = require("../graphql-client/queries"); const path = require("path"); const logger = require("../utils/logger"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); exports.techLogin = async (req, res) => { - const {shopid, employeeid, pin} = req.body; - logger.log("tech-console-login", "DEBUG", req.user.email, null, null); - try { - const result = await client.request(queries.QUERY_EMPLOYEE_PIN, { - shopId: shopid, - employeeId: employeeid, - }); + const { shopid, employeeid, pin } = req.body; + logger.log("tech-console-login", "DEBUG", req.user.email, null, null); + try { + const result = await client.request(queries.QUERY_EMPLOYEE_PIN, { + shopId: shopid, + employeeId: employeeid + }); - let valid = false; - let error; - let technician; - if (result.employees && result.employees[0]) { - const dbRecord = result.employees[0]; - if (dbRecord.pin === pin && dbRecord.active === true) { - valid = true; - delete dbRecord.pin; - technician = dbRecord; - } else { - logger.log("tech-console-login-error", "DEBUG", req.user.email, null, { - type: "wrong-pin", - }); - - error = "The employee ID and PIN combination are not correct."; - } - } else { - logger.log("tech-console-login-error", "DEBUG", req.user.email, null, { - type: "invalid-employee", - }); - error = "The employee ID does not exist."; - } - res.json({valid, technician, error}); - } catch (error) { + let valid = false; + let error; + let technician; + if (result.employees && result.employees[0]) { + const dbRecord = result.employees[0]; + if (dbRecord.pin === pin && dbRecord.active === true) { + valid = true; + delete dbRecord.pin; + technician = dbRecord; + } else { logger.log("tech-console-login-error", "DEBUG", req.user.email, null, { - error, + type: "wrong-pin" }); - res.status(400).send(error); + + error = "The employee ID and PIN combination are not correct."; + } + } else { + logger.log("tech-console-login-error", "DEBUG", req.user.email, null, { + type: "invalid-employee" + }); + error = "The employee ID does not exist."; } + res.json({ valid, technician, error }); + } catch (error) { + logger.log("tech-console-login-error", "DEBUG", req.user.email, null, { + error + }); + res.status(400).send(error); + } }; diff --git a/server/utils/adminEmail.js b/server/utils/adminEmail.js index 70c44cb47..98013304e 100644 --- a/server/utils/adminEmail.js +++ b/server/utils/adminEmail.js @@ -3,11 +3,11 @@ * @type {string[]} */ const adminEmail = [ - "patrick@imex.dev", - //"patrick@imex.test", - "patrick@imex.prod", - "patrick@imexsystems.ca", - "patrick@thinkimex.com", + "patrick@imex.dev", + //"patrick@imex.test", + "patrick@imex.prod", + "patrick@imexsystems.ca", + "patrick@thinkimex.com" ]; -module.exports = adminEmail; \ No newline at end of file +module.exports = adminEmail; diff --git a/server/utils/calculateStatusDuration.js b/server/utils/calculateStatusDuration.js index 1fcdcc487..b0ceb5b84 100644 --- a/server/utils/calculateStatusDuration.js +++ b/server/utils/calculateStatusDuration.js @@ -4,79 +4,84 @@ const getLifecycleStatusColor = require("./getLifecycleStatusColor"); const _ = require("lodash"); const calculateStatusDuration = (transitions, statuses) => { - let statusDuration = {}; - let totalDuration = 0; - let totalCurrentStatusDuration = null; - let summations = []; + let statusDuration = {}; + let totalDuration = 0; + let totalCurrentStatusDuration = null; + let summations = []; - transitions.forEach((transition, index) => { - let duration = transition.duration; - totalDuration += duration; - if (transition.start && !transition.end) { - const startMoment = moment(transition.start); - const nowMoment = moment(); - const duration = moment.duration(nowMoment.diff(startMoment)); - totalCurrentStatusDuration = { - value: duration.asMilliseconds(), - humanReadable: durationToHumanReadable(duration) - }; - } - - if (!transition.prev_value) { - statusDuration[transition.value] = { - value: duration, - humanReadable: durationToHumanReadable(moment.duration(duration)) - }; - } else { - if (statusDuration[transition.value]) { - statusDuration[transition.value].value += duration; - statusDuration[transition.value].humanReadable = durationToHumanReadable(moment.duration(statusDuration[transition.value].value)); - } else { - statusDuration[transition.value] = { - value: duration, - humanReadable: durationToHumanReadable(moment.duration(duration)) - }; - } - } - }); - - // Calculate the percentage for each status -// Calculate the percentage for each status - let totalPercentage = 0; - const statusKeys = Object.keys(statusDuration); - statusKeys.forEach((status, index) => { - if (index !== statusKeys.length - 1) { - const percentage = (statusDuration[status].value / totalDuration) * 100; - totalPercentage += percentage; - statusDuration[status].percentage = percentage; - } else { - statusDuration[status].percentage = 100 - totalPercentage; - } - }); - - for (let [status, {value, humanReadable}] of Object.entries(statusDuration)) { - if (status !== 'total') { - summations.push({ - status, - value, - humanReadable, - percentage: statusDuration[status].percentage, - color: getLifecycleStatusColor(status), - roundedPercentage: `${Math.round(statusDuration[status].percentage)}%` - }); - } + transitions.forEach((transition, index) => { + let duration = transition.duration; + totalDuration += duration; + if (transition.start && !transition.end) { + const startMoment = moment(transition.start); + const nowMoment = moment(); + const duration = moment.duration(nowMoment.diff(startMoment)); + totalCurrentStatusDuration = { + value: duration.asMilliseconds(), + humanReadable: durationToHumanReadable(duration) + }; } - const humanReadableTotal = durationToHumanReadable(moment.duration(totalDuration)); + if (!transition.prev_value) { + statusDuration[transition.value] = { + value: duration, + humanReadable: durationToHumanReadable(moment.duration(duration)) + }; + } else { + if (statusDuration[transition.value]) { + statusDuration[transition.value].value += duration; + statusDuration[transition.value].humanReadable = durationToHumanReadable( + moment.duration(statusDuration[transition.value].value) + ); + } else { + statusDuration[transition.value] = { + value: duration, + humanReadable: durationToHumanReadable(moment.duration(duration)) + }; + } + } + }); - return { - summations: _.isArray(statuses) && !_.isEmpty(statuses) ? summations.sort((a, b) => { + // Calculate the percentage for each status + // Calculate the percentage for each status + let totalPercentage = 0; + const statusKeys = Object.keys(statusDuration); + statusKeys.forEach((status, index) => { + if (index !== statusKeys.length - 1) { + const percentage = (statusDuration[status].value / totalDuration) * 100; + totalPercentage += percentage; + statusDuration[status].percentage = percentage; + } else { + statusDuration[status].percentage = 100 - totalPercentage; + } + }); + + for (let [status, { value, humanReadable }] of Object.entries(statusDuration)) { + if (status !== "total") { + summations.push({ + status, + value, + humanReadable, + percentage: statusDuration[status].percentage, + color: getLifecycleStatusColor(status), + roundedPercentage: `${Math.round(statusDuration[status].percentage)}%` + }); + } + } + + const humanReadableTotal = durationToHumanReadable(moment.duration(totalDuration)); + + return { + summations: + _.isArray(statuses) && !_.isEmpty(statuses) + ? summations.sort((a, b) => { return statuses.indexOf(a.status) - statuses.indexOf(b.status); - }) : summations, - totalStatuses: summations.length, - total: totalDuration, - totalCurrentStatusDuration, - humanReadableTotal - }; -} -module.exports = calculateStatusDuration; \ No newline at end of file + }) + : summations, + totalStatuses: summations.length, + total: totalDuration, + totalCurrentStatusDuration, + humanReadableTotal + }; +}; +module.exports = calculateStatusDuration; diff --git a/server/utils/durationToHumanReadable.js b/server/utils/durationToHumanReadable.js index e6820c9bf..f6943e08a 100644 --- a/server/utils/durationToHumanReadable.js +++ b/server/utils/durationToHumanReadable.js @@ -1,22 +1,22 @@ const durationToHumanReadable = (duration) => { - if (!duration) return 'N/A'; + if (!duration) return "N/A"; - let parts = []; + let parts = []; - let years = duration.years(); - let months = duration.months(); - let days = duration.days(); - let hours = duration.hours(); - let minutes = duration.minutes(); - let seconds = duration.seconds(); + let years = duration.years(); + let months = duration.months(); + let days = duration.days(); + let hours = duration.hours(); + let minutes = duration.minutes(); + let seconds = duration.seconds(); - if (years) parts.push(years + ' year' + (years > 1 ? 's' : '')); - if (months) parts.push(months + ' month' + (months > 1 ? 's' : '')); - if (days) parts.push(days + ' day' + (days > 1 ? 's' : '')); - if (hours) parts.push(hours + ' hour' + (hours > 1 ? 's' : '')); - if (minutes) parts.push(minutes + ' minute' + (minutes > 1 ? 's' : '')); - if (seconds) parts.push(seconds + ' second' + (seconds > 1 ? 's' : '')); + if (years) parts.push(years + " year" + (years > 1 ? "s" : "")); + if (months) parts.push(months + " month" + (months > 1 ? "s" : "")); + if (days) parts.push(days + " day" + (days > 1 ? "s" : "")); + if (hours) parts.push(hours + " hour" + (hours > 1 ? "s" : "")); + if (minutes) parts.push(minutes + " minute" + (minutes > 1 ? "s" : "")); + if (seconds) parts.push(seconds + " second" + (seconds > 1 ? "s" : "")); - return parts.join(', '); -} -module.exports = durationToHumanReadable; \ No newline at end of file + return parts.join(", "); +}; +module.exports = durationToHumanReadable; diff --git a/server/utils/getLifecycleStatusColor.js b/server/utils/getLifecycleStatusColor.js index d726e1cb3..72bafb654 100644 --- a/server/utils/getLifecycleStatusColor.js +++ b/server/utils/getLifecycleStatusColor.js @@ -1,11 +1,11 @@ -const crypto = require('crypto'); +const crypto = require("crypto"); const getLifecycleStatusColor = (key) => { - const hash = crypto.createHash('sha256'); - hash.update(key); - const hashedKey = hash.digest('hex'); - const num = parseInt(hashedKey, 16); - return '#' + (num % 16777215).toString(16).padStart(6, '0'); + const hash = crypto.createHash("sha256"); + hash.update(key); + const hashedKey = hash.digest("hex"); + const num = parseInt(hashedKey, 16); + return "#" + (num % 16777215).toString(16).padStart(6, "0"); }; -module.exports = getLifecycleStatusColor; \ No newline at end of file +module.exports = getLifecycleStatusColor; diff --git a/server/utils/instanceMgr.js b/server/utils/instanceMgr.js index e60f9e718..e83e32661 100644 --- a/server/utils/instanceMgr.js +++ b/server/utils/instanceMgr.js @@ -12,18 +12,17 @@ function InstanceManager({ args, instance, debug, executeFunction, rome, promana let propToReturn = null; switch (instance || process.env.INSTANCE) { - case 'IMEX': + case "IMEX": propToReturn = imex; break; - case 'ROME': + case "ROME": propToReturn = rome; //TODO:AIO Implement USE_IMEX break; - case 'PROMANAGER': + case "PROMANAGER": //Return the rome prop if USE_ROME. //If not USE_ROME, we want to default back to the rome prop if it's undefined. //If null, we might want to show nothing, so make sure we return null. - propToReturn = - promanager === 'USE_ROME' ? rome : promanager !== undefined ? promanager : rome; + propToReturn = promanager === "USE_ROME" ? rome : promanager !== undefined ? promanager : rome; break; default: propToReturn = imex; @@ -31,17 +30,17 @@ function InstanceManager({ args, instance, debug, executeFunction, rome, promana } if (debug) { - console.log('InstanceRenderManager Debugger'); - console.log('========================='); + console.log("InstanceRenderManager Debugger"); + console.log("========================="); console.log({ executeFunction, rome, promanager, imex, debug, propToReturn }); - console.log('========================='); + console.log("========================="); } //Checking to see if we need to default to another one. - if (propToReturn === 'imex') { + if (propToReturn === "imex") { propToReturn = imex; } - if (executeFunction && typeof propToReturn === 'function') return propToReturn(...args); + if (executeFunction && typeof propToReturn === "function") return propToReturn(...args); return propToReturn === undefined ? null : propToReturn; } diff --git a/server/utils/logger.js b/server/utils/logger.js index 60116eb39..357dcba3d 100644 --- a/server/utils/logger.js +++ b/server/utils/logger.js @@ -1,25 +1,25 @@ const graylog2 = require("graylog2"); const logger = new graylog2.graylog({ - servers: [{host: "logs.bodyshop.app", port: 12201}], + servers: [{ host: "logs.bodyshop.app", port: 12201 }] }); function log(message, type, user, record, object) { - if (type !== "ioevent") - console.log(message, { - type, - env: process.env.NODE_ENV || "development", - user, - record, - ...object, - }); - logger.log(message, message, { - type, - env: process.env.NODE_ENV || "development", - user, - record, - ...object, + if (type !== "ioevent") + console.log(message, { + type, + env: process.env.NODE_ENV || "development", + user, + record, + ...object }); + logger.log(message, message, { + type, + env: process.env.NODE_ENV || "development", + user, + record, + ...object + }); } -module.exports = {log}; +module.exports = { log }; diff --git a/server/utils/utils.js b/server/utils/utils.js index 2843c62de..7b7e61fc2 100644 --- a/server/utils/utils.js +++ b/server/utils/utils.js @@ -1,12 +1,7 @@ exports.servertime = (req, res) => { - res.status(200).send(new Date()); + res.status(200).send(new Date()); }; exports.jsrAuth = async (req, res) => { - res.send( - "Basic " + - Buffer.from( - `${process.env.JSR_USER}:${process.env.JSR_PASSWORD}` - ).toString("base64") - ); + res.send("Basic " + Buffer.from(`${process.env.JSR_USER}:${process.env.JSR_PASSWORD}`).toString("base64")); }; diff --git a/server/web-sockets/web-socket.js b/server/web-sockets/web-socket.js index 7e5aa7a9b..c19f77dcb 100644 --- a/server/web-sockets/web-socket.js +++ b/server/web-sockets/web-socket.js @@ -1,269 +1,224 @@ const path = require("path"); require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); -const {io} = require("../../server"); -const {admin} = require("../firebase/firebase-handler"); -const { - default: CdkJobExport, - CdkSelectedCustomer, -} = require("../cdk/cdk-job-export"); +const { io } = require("../../server"); +const { admin } = require("../firebase/firebase-handler"); +const { default: CdkJobExport, CdkSelectedCustomer } = require("../cdk/cdk-job-export"); const CdkGetMakes = require("../cdk/cdk-get-makes").default; -const CdkCalculateAllocations = - require("../cdk/cdk-calculate-allocations").default; -const {isArray} = require("lodash"); +const CdkCalculateAllocations = require("../cdk/cdk-calculate-allocations").default; +const { isArray } = require("lodash"); const logger = require("../utils/logger"); -const { - default: PbsExportJob, - PbsSelectedCustomer, -} = require("../accounting/pbs/pbs-job-export"); +const { default: PbsExportJob, PbsSelectedCustomer } = require("../accounting/pbs/pbs-job-export"); -const { - PbsCalculateAllocationsAp, - PbsExportAp, -} = require("../accounting/pbs/pbs-ap-allocations"); +const { PbsCalculateAllocationsAp, PbsExportAp } = require("../accounting/pbs/pbs-ap-allocations"); io.use(function (socket, next) { - try { - if (socket.handshake.auth.token) { - admin - .auth() - .verifyIdToken(socket.handshake.auth.token) - .then((user) => { - socket.user = user; - next(); - }) - .catch((error) => { - next(new Error("Authentication error", JSON.stringify(error))); - }); - } else { - next(new Error("Authentication error - no authorization token.")); - } - } catch (error) { - console.log("Uncaught connection error:::", error); - logger.log("websocket-connection-error", "error", null, null, { - token: socket.handshake.auth.token, - ...error, + try { + if (socket.handshake.auth.token) { + admin + .auth() + .verifyIdToken(socket.handshake.auth.token) + .then((user) => { + socket.user = user; + next(); + }) + .catch((error) => { + next(new Error("Authentication error", JSON.stringify(error))); }); - next(new Error(`Authentication error ${error}`)); + } else { + next(new Error("Authentication error - no authorization token.")); } + } catch (error) { + console.log("Uncaught connection error:::", error); + logger.log("websocket-connection-error", "error", null, null, { + token: socket.handshake.auth.token, + ...error + }); + next(new Error(`Authentication error ${error}`)); + } }); io.on("connection", (socket) => { - socket.log_level = "TRACE"; - createLogEvent(socket, "DEBUG", `Connected and Authenticated.`); + socket.log_level = "TRACE"; + createLogEvent(socket, "DEBUG", `Connected and Authenticated.`); - socket.on("set-log-level", (level) => { - socket.log_level = level; - socket.emit("log-event", { - timestamp: new Date(), - level: "INFO", - message: `Updated log level to ${level}`, - }); + socket.on("set-log-level", (level) => { + socket.log_level = level; + socket.emit("log-event", { + timestamp: new Date(), + level: "INFO", + message: `Updated log level to ${level}` }); + }); - ///CDK - socket.on("cdk-export-job", (jobid) => { - CdkJobExport(socket, jobid); - }); - socket.on("cdk-selected-customer", (selectedCustomerId) => { - createLogEvent( - socket, - "DEBUG", - `User selected customer ID ${selectedCustomerId}` - ); - socket.selectedCustomerId = selectedCustomerId; - CdkSelectedCustomer(socket, selectedCustomerId); - }); + ///CDK + socket.on("cdk-export-job", (jobid) => { + CdkJobExport(socket, jobid); + }); + socket.on("cdk-selected-customer", (selectedCustomerId) => { + createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`); + socket.selectedCustomerId = selectedCustomerId; + CdkSelectedCustomer(socket, selectedCustomerId); + }); - socket.on("cdk-get-makes", async (cdk_dealerid, callback) => { - try { - const makes = await CdkGetMakes(socket, cdk_dealerid); - callback(makes); - } catch (error) { - createLogEvent( - socket, - "ERROR", - `Error in cdk-get-makes WS call. ${JSON.stringify(error, null, 2)}` - ); - } - }); + socket.on("cdk-get-makes", async (cdk_dealerid, callback) => { + try { + const makes = await CdkGetMakes(socket, cdk_dealerid); + callback(makes); + } catch (error) { + createLogEvent(socket, "ERROR", `Error in cdk-get-makes WS call. ${JSON.stringify(error, null, 2)}`); + } + }); - socket.on("cdk-calculate-allocations", async (jobid, callback) => { - const allocations = await CdkCalculateAllocations(socket, jobid); - createLogEvent(socket, "DEBUG", `Allocations calculated.`); - createLogEvent( - socket, - "TRACE", - `Allocations calculated. ${JSON.stringify(allocations, null, 2)}` - ); + socket.on("cdk-calculate-allocations", async (jobid, callback) => { + const allocations = await CdkCalculateAllocations(socket, jobid); + createLogEvent(socket, "DEBUG", `Allocations calculated.`); + createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`); - callback(allocations); - }); - //END CDK + callback(allocations); + }); + //END CDK - //PBS AR - socket.on("pbs-calculate-allocations", async (jobid, callback) => { - const allocations = await CdkCalculateAllocations(socket, jobid); - createLogEvent(socket, "DEBUG", `Allocations calculated.`); - createLogEvent( - socket, - "TRACE", - `Allocations calculated. ${JSON.stringify(allocations, null, 2)}` - ); + //PBS AR + socket.on("pbs-calculate-allocations", async (jobid, callback) => { + const allocations = await CdkCalculateAllocations(socket, jobid); + createLogEvent(socket, "DEBUG", `Allocations calculated.`); + createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`); - callback(allocations); - }); - socket.on("pbs-export-job", (jobid) => { - PbsExportJob(socket, jobid); - }); - socket.on("pbs-selected-customer", (selectedCustomerId) => { - createLogEvent( - socket, - "DEBUG", - `User selected customer ID ${selectedCustomerId}` - ); - socket.selectedCustomerId = selectedCustomerId; - PbsSelectedCustomer(socket, selectedCustomerId); - }); - //End PBS AR + callback(allocations); + }); + socket.on("pbs-export-job", (jobid) => { + PbsExportJob(socket, jobid); + }); + socket.on("pbs-selected-customer", (selectedCustomerId) => { + createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`); + socket.selectedCustomerId = selectedCustomerId; + PbsSelectedCustomer(socket, selectedCustomerId); + }); + //End PBS AR - //PBS AP - socket.on("pbs-calculate-allocations-ap", async (billids, callback) => { - const allocations = await PbsCalculateAllocationsAp(socket, billids); - createLogEvent(socket, "DEBUG", `AP Allocations calculated.`); - createLogEvent( - socket, - "TRACE", - `Allocations calculated. ${JSON.stringify(allocations, null, 2)}` - ); - socket.apAllocations = allocations; - callback(allocations); - }); + //PBS AP + socket.on("pbs-calculate-allocations-ap", async (billids, callback) => { + const allocations = await PbsCalculateAllocationsAp(socket, billids); + createLogEvent(socket, "DEBUG", `AP Allocations calculated.`); + createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`); + socket.apAllocations = allocations; + callback(allocations); + }); - socket.on("pbs-export-ap", ({billids, txEnvelope}) => { - socket.txEnvelope = txEnvelope; - PbsExportAp(socket, {billids, txEnvelope}); - }); + socket.on("pbs-export-ap", ({ billids, txEnvelope }) => { + socket.txEnvelope = txEnvelope; + PbsExportAp(socket, { billids, txEnvelope }); + }); - //END PBS AP + //END PBS AP - socket.on("disconnect", () => { - createLogEvent(socket, "DEBUG", `User disconnected.`); - }); + socket.on("disconnect", () => { + createLogEvent(socket, "DEBUG", `User disconnected.`); + }); }); function createLogEvent(socket, level, message) { - if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) { - console.log( - `[WS LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${ - socket.id - } - ${message}` - ); - socket.emit("log-event", { - timestamp: new Date(), - level, - message, - }); + if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) { + console.log(`[WS LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${socket.id} - ${message}`); + socket.emit("log-event", { + timestamp: new Date(), + level, + message + }); - logger.log("ws-log-event", level, socket.user.email, socket.recordid, { - wsmessage: message, - }); - - if (socket.logEvents && isArray(socket.logEvents)) { - socket.logEvents.push({ - timestamp: new Date(), - level, - message, - }); - } - // if (level === "ERROR") { - // throw new Error(message); - // } - } -} - -function createJsonEvent(socket, level, message, json) { - if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) { - console.log( - `[WS LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${ - socket.id - } - ${message}` - ); - socket.emit("log-event", { - timestamp: new Date(), - level, - message, - }); - } - logger.log("ws-log-event-json", level, socket.user.email, socket.recordid, { - wsmessage: message, - json, + logger.log("ws-log-event", level, socket.user.email, socket.recordid, { + wsmessage: message }); if (socket.logEvents && isArray(socket.logEvents)) { - socket.logEvents.push({ - timestamp: new Date(), - level, - message, - }); + socket.logEvents.push({ + timestamp: new Date(), + level, + message + }); } // if (level === "ERROR") { // throw new Error(message); // } + } +} + +function createJsonEvent(socket, level, message, json) { + if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) { + console.log(`[WS LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${socket.id} - ${message}`); + socket.emit("log-event", { + timestamp: new Date(), + level, + message + }); + } + logger.log("ws-log-event-json", level, socket.user.email, socket.recordid, { + wsmessage: message, + json + }); + + if (socket.logEvents && isArray(socket.logEvents)) { + socket.logEvents.push({ + timestamp: new Date(), + level, + message + }); + } + // if (level === "ERROR") { + // throw new Error(message); + // } } function createXmlEvent(socket, xml, message, isError = false) { - if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy("TRACE")) { - socket.emit("log-event", { - timestamp: new Date(), - level: isError ? "ERROR" : "TRACE", - message: `${message}: ${xml}`, - }); - } + if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy("TRACE")) { + socket.emit("log-event", { + timestamp: new Date(), + level: isError ? "ERROR" : "TRACE", + message: `${message}: ${xml}` + }); + } - logger.log( - isError ? "ws-log-event-xml-error" : "ws-log-event-xml", - isError ? "ERROR" : "TRACE", - socket.user.email, - socket.recordid, - { - wsmessage: message, - xml, - } - ); - - if (socket.logEvents && isArray(socket.logEvents)) { - socket.logEvents.push({ - timestamp: new Date(), - level: isError ? "ERROR" : "TRACE", - message, - xml, - }); + logger.log( + isError ? "ws-log-event-xml-error" : "ws-log-event-xml", + isError ? "ERROR" : "TRACE", + socket.user.email, + socket.recordid, + { + wsmessage: message, + xml } + ); + + if (socket.logEvents && isArray(socket.logEvents)) { + socket.logEvents.push({ + timestamp: new Date(), + level: isError ? "ERROR" : "TRACE", + message, + xml + }); + } } function LogLevelHierarchy(level) { - switch (level) { - case "XML": - return 5; - case "TRACE": - return 5; - case "DEBUG": - return 4; - case "INFO": - return 3; - case "WARNING": - return 2; - case "ERROR": - return 1; - default: - return 3; - } + switch (level) { + case "XML": + return 5; + case "TRACE": + return 5; + case "DEBUG": + return 4; + case "INFO": + return 3; + case "WARNING": + return 2; + case "ERROR": + return 1; + default: + return 3; + } } exports.createLogEvent = createLogEvent; diff --git a/setadmin.js b/setadmin.js index 3eab412ff..8d6eaa7f9 100644 --- a/setadmin.js +++ b/setadmin.js @@ -1,23 +1,23 @@ -var {admin} = require("./server/firebase/firebase-handler"); +var { admin } = require("./server/firebase/firebase-handler"); const uidToMakeAdmin = "fIaZcVQQfUR12Fu14I2fyA5vXbp1"; admin - .auth() - .getUser(uidToMakeAdmin) - .then((user) => { - admin - .auth() - .setCustomUserClaims(uidToMakeAdmin, { - ...user.customClaims, - "https://hasura.io/jwt/claims": { - "x-hasura-default-role": "admin", - "x-hasura-allowed-roles": ["admin"], - "x-hasura-user-id": uidToMakeAdmin, - }, - ioadmin: true, - }) - .then(() => console.log("Success.")) - .catch((error) => console.log("Error updating claims.", error)); - }) - .catch((error) => console.log("Error fetching user.", error)); + .auth() + .getUser(uidToMakeAdmin) + .then((user) => { + admin + .auth() + .setCustomUserClaims(uidToMakeAdmin, { + ...user.customClaims, + "https://hasura.io/jwt/claims": { + "x-hasura-default-role": "admin", + "x-hasura-allowed-roles": ["admin"], + "x-hasura-user-id": uidToMakeAdmin + }, + ioadmin: true + }) + .then(() => console.log("Success.")) + .catch((error) => console.log("Error updating claims.", error)); + }) + .catch((error) => console.log("Error fetching user.", error)); From 0a784c687306b71495b9ed17b3f8efcf7ad1b273 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 27 Mar 2024 15:47:00 -0700 Subject: [PATCH 11/12] Resolve gallery issues. --- client/src/index.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/index.jsx b/client/src/index.jsx index 9678b6b69..74797374b 100644 --- a/client/src/index.jsx +++ b/client/src/index.jsx @@ -16,6 +16,8 @@ import { ConfigProvider } from "antd"; import InstanceRenderManager from "./utils/instanceRenderMgr"; import { registerSW } from "virtual:pwa-register"; +window.global ||= window; + registerSW({ immediate: true }); //import { BrowserTracing } from "@sentry/tracing"; //import "antd/dist/antd.css"; From c3fa4ef8afcc6a96bb2f97fd1fabdadca880abd1 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 28 Mar 2024 09:26:24 -0700 Subject: [PATCH 12/12] Minor fixes. --- .../job-totals-table/job-totals.table.labor.component.jsx | 4 +--- server/email/sendemail.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/client/src/components/job-totals-table/job-totals.table.labor.component.jsx b/client/src/components/job-totals-table/job-totals.table.labor.component.jsx index 2e8bb48e9..dedce3809 100644 --- a/client/src/components/job-totals-table/job-totals.table.labor.component.jsx +++ b/client/src/components/job-totals-table/job-totals.table.labor.component.jsx @@ -51,11 +51,9 @@ export default function JobTotalsTableLabor({ job }) { { title: t("joblines.fields.mod_lb_hrs"), dataIndex: "mod_lb_hrs", - key: "mod_lb_hrs", - sorter: (a, b) => a.mod_lb_hrs - b.mod_lb_hrs, + sorter: (a, b) => a.hours - b.hours, sortOrder: state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order, - render: (text, record) => record.hours.toFixed(1) }, { diff --git a/server/email/sendemail.js b/server/email/sendemail.js index fa8a38075..cface107f 100644 --- a/server/email/sendemail.js +++ b/server/email/sendemail.js @@ -36,7 +36,7 @@ exports.sendServerEmail = async function ({ subject, text }) { rome: `Rome Online API - ${process.env.NODE_ENV} `, promanager: `ProManager API - ${process.env.NODE_ENV} ` }), - to: ["patrick@imexsystems.ca"], + to: ["patrick@imexsystems.ca", "support@thinkimex.com"], subject: subject, text: text, ses: {