diff --git a/client/src/components/job-lifecycle/job-lifecycle.component.jsx b/client/src/components/job-lifecycle/job-lifecycle.component.jsx new file mode 100644 index 000000000..0727a6ef1 --- /dev/null +++ b/client/src/components/job-lifecycle/job-lifecycle.component.jsx @@ -0,0 +1,233 @@ +import React, {useCallback, useEffect, useState} from 'react'; +import moment from "moment"; +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"; + +require('./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. + + const {data} = useQuery(gql` + query get_job_test($id: uuid!){ + jobs_by_pk(id:$id){ + id + status + } + } + `, { + 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); + } + } + }, [job, statuses]); + + useEffect(() => { + if (!data) return; + setTimeout(() => { + getLifecycleData().catch(err => console.error(`${t('job_lifecycle.errors.fetch')}: ${err.message}`)); + }, 500); + }, [data, getLifecycleData]); + + 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) => moment(a.start).unix() - moment(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 moment(a.end).unix() - moment(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, + }, + ]; + + 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 > 5 ? + <> +
{key.roundedPercentage}
+
+ {key.status} +
+ + : null} +
+ ); + })} +
+ +
+ {lifecycleData.durations.summations.map((key) => ( + +
+ {key.status} ({key.roundedPercentage}) +
+
+ ))} +
+
+ +
    +
  • + {t('job_lifecycle.content.previous_status_accumulated_time')}: {lifecycleData.durations.humanReadableTotal} +
  • +
  • + {t('job_lifecycle.content.current_status_accumulated_time')} ({lifecycleData.lifecycle[0].value}): {lifecycleData.durations.totalCurrentStatusDuration.humanReadable} +
  • +
+
+
+ + + + {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 diff --git a/client/src/components/job-lifecycle/job-lifecycle.styles.scss b/client/src/components/job-lifecycle/job-lifecycle.styles.scss new file mode 100644 index 000000000..e69de29bb 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 001a18166..89a912242 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx @@ -54,6 +54,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import UndefinedToNull from "../../utils/undefinedtonull"; import { DateTimeFormat } from "./../../utils/DateFormatter"; +import JobLifecycleComponent from "../../components/job-lifecycle/job-lifecycle.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -333,7 +334,15 @@ export function JobsDetailPage({ > - {t('menus.jobsdetail.lifecycle')}} + key="lifecycle" + > + + + + diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 1589e9b5f..becddf9eb 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -113,11 +113,11 @@ "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}}.", - "jobinvoiced": "Job has been invoiced.", "jobconverted": "Job converted and assigned number {{ro_number}}.", "jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.", "jobimported": "Job imported.", "jobinproductionchange": "Job production status set to {{inproduction}}", + "jobinvoiced": "Job has been invoiced.", "jobioucreated": "IOU Created.", "jobmodifylbradj": "Labor adjustments modified {{mod_lbr_ty}} / {{hours}}.", "jobnoteadded": "Note added to Job.", @@ -255,7 +255,6 @@ "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", @@ -332,6 +331,9 @@ "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" @@ -354,9 +356,6 @@ }, "md_payment_types": "Payment Types", "md_referral_sources": "Referral Sources", - "md_functionality_toggles": { - "parts_queue_toggle": "Auto Add Imported/Supplemented Jobs to Parts Queue" - }, "md_tasks_presets": { "hourstype": "", "memo": "", @@ -474,6 +473,7 @@ "editaccess": "Users -> Edit access" } }, + "ReceivableCustomField": "QBO Receivable Custom Field {{number}}", "responsibilitycenter": "Responsibility Center", "responsibilitycenter_accountdesc": "Account Description", "responsibilitycenter_accountitem": "Item", @@ -815,6 +815,10 @@ "usage": "Usage", "vehicle": "Vehicle Description" }, + "readiness": { + "notready": "Not Ready", + "ready": "Ready" + }, "status": { "in": "Available", "inservice": "In Service", @@ -824,10 +828,6 @@ }, "successes": { "saved": "Courtesy Car saved successfully." - }, - "readiness": { - "notready": "Not Ready", - "ready": "Ready" } }, "csi": { @@ -1214,6 +1214,31 @@ "updated": "Inventory line updated." } }, + "job_lifecycle": { + "columns": { + "duration": "Duration", + "end": "End", + "relative_end": "Relative End", + "relative_start": "Relative Start", + "start": "Start", + "value": "Value" + }, + "content": { + "current_status_accumulated_time": "Current Status Accumulated Time", + "data_unavailable": " There is currently no Lifecycle data for this Job.", + "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 Duration's", + "title_loading": "Loading", + "title_transitions": "Transitions" + }, + "errors": { + "fetch": "Error getting Job Lifecycle Data" + } + }, "job_payments": { "buttons": { "goback": "Go Back", @@ -2014,6 +2039,7 @@ "general": "General", "insurance": "Insurance Information", "labor": "Labor", + "lifecycle": "Lifecycle", "partssublet": "Parts & Bills", "rates": "Rates", "repairdata": "Repair Data", @@ -2922,8 +2948,8 @@ "shop-templates": "Shop Templates | $t(titles.app)", "shop_vendors": "Vendors | $t(titles.app)", "techconsole": "Technician Console | $t(titles.app)", - "techjoblookup": "Technician Job Lookup | $t(titles.app)", "techjobclock": "Technician Job Clock | $t(titles.app)", + "techjoblookup": "Technician Job Lookup | $t(titles.app)", "techshiftclock": "Technician Shift Clock | $t(titles.app)", "temporarydocs": "Temporary Documents | $t(titles.app)", "timetickets": "Time Tickets | $t(titles.app)", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index fc977e7eb..0b7722676 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -113,11 +113,11 @@ "jobassignmentchange": "", "jobassignmentremoved": "", "jobchecklist": "", - "jobinvoiced": "", "jobconverted": "", "jobfieldchanged": "", "jobimported": "", "jobinproductionchange": "", + "jobinvoiced": "", "jobioucreated": "", "jobmodifylbradj": "", "jobnoteadded": "", @@ -255,13 +255,9 @@ "saving": "" }, "fields": { - "ReceivableCustomField": "", "address1": "", "address2": "", "appt_alt_transport": "", - "md_functionality_toggles": { - "parts_queue_toggle": "" - }, "appt_colors": { "color": "", "label": "" @@ -335,6 +331,9 @@ "md_ded_notes": "", "md_email_cc": "", "md_from_emails": "", + "md_functionality_toggles": { + "parts_queue_toggle": "" + }, "md_hour_split": { "paint": "", "prep": "" @@ -474,6 +473,7 @@ "editaccess": "" } }, + "ReceivableCustomField": "", "responsibilitycenter": "", "responsibilitycenter_accountdesc": "", "responsibilitycenter_accountitem": "", @@ -815,6 +815,10 @@ "usage": "", "vehicle": "" }, + "readiness": { + "notready": "", + "ready": "" + }, "status": { "in": "", "inservice": "", @@ -824,10 +828,6 @@ }, "successes": { "saved": "" - }, - "readiness": { - "notready": "", - "ready": "" } }, "csi": { @@ -1214,6 +1214,31 @@ "updated": "" } }, + "job_lifecycle": { + "columns": { + "duration": "", + "end": "", + "relative_end": "", + "relative_start": "", + "start": "", + "value": "" + }, + "content": { + "current_status_accumulated_time": "", + "data_unavailable": "", + "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" + } + }, "job_payments": { "buttons": { "goback": "", @@ -2014,6 +2039,7 @@ "general": "", "insurance": "", "labor": "Labor", + "lifecycle": "", "partssublet": "Piezas / Subarrendamiento", "rates": "", "repairdata": "Datos de reparación", @@ -2922,8 +2948,8 @@ "shop-templates": "", "shop_vendors": "Vendedores | $t(titles.app)", "techconsole": "$t(titles.app)", - "techjoblookup": "$t(titles.app)", "techjobclock": "$t(titles.app)", + "techjoblookup": "$t(titles.app)", "techshiftclock": "$t(titles.app)", "temporarydocs": "", "timetickets": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 76e3ce6a9..86ebd85ec 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -113,11 +113,11 @@ "jobassignmentchange": "", "jobassignmentremoved": "", "jobchecklist": "", - "jobinvoiced": "", "jobconverted": "", "jobfieldchanged": "", "jobimported": "", "jobinproductionchange": "", + "jobinvoiced": "", "jobioucreated": "", "jobmodifylbradj": "", "jobnoteadded": "", @@ -255,7 +255,6 @@ "saving": "" }, "fields": { - "ReceivableCustomField": "", "address1": "", "address2": "", "appt_alt_transport": "", @@ -332,13 +331,13 @@ "md_ded_notes": "", "md_email_cc": "", "md_from_emails": "", + "md_functionality_toggles": { + "parts_queue_toggle": "" + }, "md_hour_split": { "paint": "", "prep": "" }, - "md_functionality_toggles": { - "parts_queue_toggle": "" - }, "md_ins_co": { "city": "", "name": "", @@ -474,6 +473,7 @@ "editaccess": "" } }, + "ReceivableCustomField": "", "responsibilitycenter": "", "responsibilitycenter_accountdesc": "", "responsibilitycenter_accountitem": "", @@ -815,6 +815,10 @@ "usage": "", "vehicle": "" }, + "readiness": { + "notready": "", + "ready": "" + }, "status": { "in": "", "inservice": "", @@ -824,10 +828,6 @@ }, "successes": { "saved": "" - }, - "readiness": { - "notready": "", - "ready": "" } }, "csi": { @@ -1214,6 +1214,31 @@ "updated": "" } }, + "job_lifecycle": { + "columns": { + "duration": "", + "end": "", + "relative_end": "", + "relative_start": "", + "start": "", + "value": "" + }, + "content": { + "current_status_accumulated_time": "", + "data_unavailable": "", + "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" + } + }, "job_payments": { "buttons": { "goback": "", @@ -2014,6 +2039,7 @@ "general": "", "insurance": "", "labor": "La main d'oeuvre", + "lifecycle": "", "partssublet": "Pièces / Sous-location", "rates": "", "repairdata": "Données de réparation", @@ -2922,8 +2948,8 @@ "shop-templates": "", "shop_vendors": "Vendeurs | $t(titles.app)", "techconsole": "$t(titles.app)", - "techjoblookup": "$t(titles.app)", "techjobclock": "$t(titles.app)", + "techjoblookup": "$t(titles.app)", "techshiftclock": "$t(titles.app)", "temporarydocs": "", "timetickets": "", diff --git a/client/src/utils/DateFormatter.jsx b/client/src/utils/DateFormatter.jsx index d034266e3..e8137c54d 100644 --- a/client/src/utils/DateFormatter.jsx +++ b/client/src/utils/DateFormatter.jsx @@ -17,6 +17,9 @@ export function DateTimeFormatter(props) { ) : null; } +export function DateTimeFormatterFunction(date) { + return moment(date).format("MM/DD/YYYY hh:mm a"); +} export function TimeFormatter(props) { return props.children ? moment(props.children).format(props.format ? props.format : "hh:mm a") diff --git a/new_bodyshop_translations.babel b/new_bodyshop_translations.babel new file mode 100644 index 000000000..5409f6ce5 --- /dev/null +++ b/new_bodyshop_translations.babel @@ -0,0 +1,47574 @@ + + + + + generic-json + new_bodyshop_translations.babel + client + + + + + main + + + translation + + + allocations + + + actions + + + assign + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + deleting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saving + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + validation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + employee + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + deleted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + save + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + appointments + + + actions + + + block + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + calculate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cancel + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + intake + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + preview + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reschedule + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sendreminder + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + unblock + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + viewjob + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + blocking + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + canceling + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saving + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + alt_transport + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + color + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + end + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + note + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + start + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + time + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + arrivedon + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + arrivingjobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + blocked + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cancelledappointment + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + completingjobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dataconsistency + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + expectedjobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + expectedprodhrs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + history + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + inproduction + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + manualevent + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noarrivingjobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nocompletingjobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nodateselected + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + priorappointments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reminder + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scheduledfor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + severalerrorsfound + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + smartscheduling + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + suggesteddates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + canceled + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + created + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saved + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + associations + + + actions + + + activate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + active + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shopname + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + actions + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + audit + + + fields + + + cc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + contents + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + created + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + operation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + subject + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + to + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + useremail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + values + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + audit_trail + + + messages + + + admin_job_remove_from_ar + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + admin_jobmarkexported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + admin_jobmarkforreexport + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + admin_jobuninvoice + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + admin_jobunvoid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + alerttoggle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + appointmentcancel + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + appointmentinsert + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + billposted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + billupdated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + failedpayment + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobassignmentchange + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobassignmentremoved + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobchecklist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobconverted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobfieldchanged + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobimported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobinproductionchange + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobinvoiced + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobioucreated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobmodifylbradj + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobnoteadded + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobnotedeleted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobnoteupdated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobspartsorder + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobspartsreturn + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobstatuschange + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobsupplement + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + billlines + + + actions + + + newline + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + actual_cost + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + actual_price + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cost_center + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + federal_tax_applicable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobline + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + line_desc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + local_tax_applicable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + location + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + quantity + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + state_tax_applicable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + deductedfromlbr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + entered + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + from + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + other + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reconciled + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + unreconciled + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + validation + + + atleastone + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + bills + + + actions + + + edit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + receive + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + return + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + creating + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + existinginventoryline + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exporting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exporting-partner + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + invalidro + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + invalidvendor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + validation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + allpartslocation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + federal_tax_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + invoice_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + is_credit_memo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + is_credit_memo_short + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + local_tax_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ro_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + state_tax_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + total + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vendor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vendorname + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + actions + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bill_lines + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bill_total + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + billcmtotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bills + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + calculatedcreditsnotreceived + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + creditsnotreceived + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + creditsreceived + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dedfromlbr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleteconfirm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + discrepancy + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + discrepwithcms + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + discrepwithlbradj + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + editadjwarning + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + entered_total + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + enteringcreditmemo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + federal_tax + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + federal_tax_exempt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + generatepartslabel + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + iouexists + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + local_tax + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + markexported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + markforreexport + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noneselected + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + onlycmforinvoiced + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + printlabels + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + retailtotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + savewithdiscrepancy + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + state_tax + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + subtotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + totalreturns + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + created + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + markexported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reexport + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + validation + + + closingperiod + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + inventoryquantity + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + manualinhouse + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + unique_invoice_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + bodyshop + + + actions + + + add_task_preset + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + addapptcolor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + addbucket + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + addpartslocation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + addpartsrule + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + addspeedprint + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + addtemplate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newlaborrate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newsalestaxcode + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newstatus + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + testrender + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + loading + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saving + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + address1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + address2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + appt_alt_transport + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + appt_colors + + + color + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + label + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + appt_length + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + attach_pdf_to_email + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bill_allow_post_to_closed + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bill_federal_tax_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bill_local_tax_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bill_state_tax_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + city + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + closingperiod + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + country + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dailybodytarget + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dailypainttarget + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_adjustment_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deliver + + + templates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + dms + + + apcontrol + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + appostingaccount + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cashierid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_journal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + disablebillwip + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + disablecontactvehiclecreation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms_acctnumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms_control_override + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms_wip_acctnumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + generic_customer_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + itc_federal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + itc_local + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + itc_state + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mappingname + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sendmaterialscosting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + srcco + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + email + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + enforce_class + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + enforce_conversion_category + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + enforce_conversion_csr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + enforce_referral + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + federal_tax_id + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ignoreblockeddays + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + inhousevendorid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + insurance_vendor_id + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + intake + + + next_contact_hours + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + templates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + invoice_federal_tax_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + invoice_local_tax_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + invoice_state_tax_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jc_hourly_rates + + + mapa + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mash + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + last_name_first + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lastnumberworkingdays + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + localmediaserverhttp + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + localmediaservernetwork + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + localmediatoken + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + logo_img_footer_margin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + logo_img_header_margin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + logo_img_path + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + logo_img_path_height + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + logo_img_path_width + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_categories + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_ccc_rates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_classes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_ded_notes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_email_cc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_from_emails + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_functionality_toggles + + + parts_queue_toggle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + md_hour_split + + + paint + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + prep + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + md_ins_co + + + city + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + private + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + state + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + street1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + street2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + zip + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + md_jobline_presets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_lost_sale_reasons + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_parts_order_comment + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_parts_scan + + + expression + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + flags + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + md_payment_types + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_referral_sources + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_tasks_presets + + + hourstype + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + memo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + percent + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + messaginglabel + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + messagingtext + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noteslabel + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notestext + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + partslocation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + phone + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + prodtargethrs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rbac + + + accounting + + + exportlog + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payables + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + receivables + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + bills + + + delete + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + enter + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reexport + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + view + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + contracts + + + create + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + courtesycar + + + create + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + csi + + + export + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + page + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + employee_teams + + + page + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + employees + + + page + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + inventory + + + delete + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + jobs + + + admin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + available-list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + checklist-view + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + close + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + create + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deliver + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + intake + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + list-active + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + list-all + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + list-ready + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + partsqueue + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + void + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + owners + + + detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + payments + + + enter + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + phonebook + + + edit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + view + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + production + + + board + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + schedule + + + view + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + scoreboard + + + view + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + shiftclock + + + view + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + shop + + + config + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dashboard + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rbac + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reportcenter + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + templates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vendors + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + temporarydocs + + + view + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + timetickets + + + edit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + editcommitted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + enter + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shiftedit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + ttapprovals + + + approve + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + view + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + users + + + editaccess + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + ReceivableCustomField + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + responsibilitycenter + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + responsibilitycenter_accountdesc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + responsibilitycenter_accountitem + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + responsibilitycenter_accountname + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + responsibilitycenter_accountnumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + responsibilitycenter_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + responsibilitycenters + + + ap + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ar + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ats + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + federal_tax + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + federal_tax_itc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gst_override + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + invoiceexemptcode + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + itemexemptcode + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + la1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + la2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + la3 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + la4 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + laa + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lab + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lad + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lae + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + laf + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lag + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lam + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lar + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + las + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lau + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + local_tax + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mapa + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mash + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + paa + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pac + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pag + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pam + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pan + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pao + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pap + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + par + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pas + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pasl + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + refund + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sales_tax_codes + + + code + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + description + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + federal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + local + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + state + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + state_tax + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tow + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + schedule_end_time + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + schedule_start_time + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shopname + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + speedprint + + + id + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + label + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + templates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + ss_configuration + + + dailyhrslimit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + ssbuckets + + + color + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gte + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + id + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + label + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + target + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + state + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + state_tax_id + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + statuses + + + active_statuses + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + additional_board_statuses + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + color + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_arrived + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_bo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_canceled + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_completed + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_delivered + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_exported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_imported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_invoiced + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_ordered + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_quote + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_received + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_returned + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_scheduled + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + default_void + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + open_statuses + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + post_production_statuses + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pre_production_statuses + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_colors + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_statuses + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ready_statuses + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + target_touchtime + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + timezone + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tt_allow_post_to_invoiced + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tt_enforce_hours_for_tech_console + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + use_fippa + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + use_paint_scale_data + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + uselocalmediaserver + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + website + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + zip_post + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + 2tiername + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 2tiersetup + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 2tiersource + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + accountingsetup + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + accountingtiers + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + alljobstatuses + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + allopenjobstatuses + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + apptcolors + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + businessinformation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + checklists + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + csiq + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + customtemplates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + defaultcostsmapping + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + defaultprofitsmapping + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deliverchecklist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms + + + cdk + + + controllist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payers + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + cdk_dealerid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pbs_serialnumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + emaillater + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employee_teams + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employees + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + estimators + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + filehandlers + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + insurancecos + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + intakechecklist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobstatuses + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + laborrates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + licensing + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_tasks_presets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_to_emails + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + md_to_emails_emails + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + messagingpresets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notemplatesavailable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notespresets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + orderstatuses + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + partslocations + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + partsscan + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + printlater + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + qbo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + qbo_departmentid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + qbo_usa + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rbac + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + responsibilitycenters + + + costs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + profits + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sales_tax_codes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tax_accounts + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + scheduling + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scoreboardsetup + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shopinfo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + speedprint + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ssbuckets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + systemsettings + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + task-presets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + workingdays + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + save + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + validation + + + centermustexist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + larsplit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + useremailmustexist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + checklist + + + actions + + + printall + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + complete + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nochecklist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + addtoproduction + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + allow_text_message + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + checklist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + printpack + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + removefromproduction + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + completed + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + contracts + + + actions + + + changerate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + convertoro + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + decodelicense + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + find + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + printcontract + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + senddltoform + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + fetchingjobinfo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + returning + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saving + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + selectjobandcar + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + actax + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + actualreturn + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + agreementnumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cc_cardholder + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cc_expiry + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cc_num + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cleanupcharge + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + coverage + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dailyfreekm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dailyrate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + damage + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + damagewaiver + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driver + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driver_addr1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driver_addr2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driver_city + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driver_dlexpiry + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driver_dlnumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driver_dlst + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driver_dob + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driver_fn + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driver_ln + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driver_ph1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driver_state + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driver_zip + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + excesskmrate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + federaltax + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + fuelin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + fuelout + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + kmend + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + kmstart + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + length + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + localtax + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + refuelcharge + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scheduledreturn + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + start + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + statetax + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + agreement + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + availablecars + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cardueforservice + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + convertform + + + applycleanupcharge + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + refuelqty + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + correctdataonform + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dateinpast + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dlexpirebeforereturn + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driverinformation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + findcontract + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + findermodal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noteconvertedfrom + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + populatefromjob + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + time + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + waitingforscan + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + status + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + out + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + returned + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + saved + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + courtesycars + + + actions + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + return + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + saving + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + color + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dailycost + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + damage + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + fleetnumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + fuel + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + insuranceexpires + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + leaseenddate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + make + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mileage + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + model + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nextservicedate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nextservicekm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + plate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchasedate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + readiness + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + registrationexpires + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + serviceenddate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + servicestartdate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + year + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + courtesycar + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + fuel + + + 12 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 14 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 18 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 34 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 38 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 58 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 78 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + empty + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + full + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + outwith + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + return + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + uniquefleet + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + usage + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + readiness + + + notready + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ready + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + status + + + in + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + inservice + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + leasereturn + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + out + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sold + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + saved + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + csi + + + actions + + + activate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + creating + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notconfigured + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notfoundsubtitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notfoundtitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + completedon + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + created_at + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + nologgedinuser + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nologgedinuser_sub + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noneselected + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + created + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + submitted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + submittedsub + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + dashboard + + + actions + + + addcomponent + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + refreshrequired + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updatinglayout + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + bodyhrs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dollarsinproduction + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + prodhrs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + refhrs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + titles + + + labhours + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + larhours + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + monthlyemployeeefficiency + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + monthlyjobcosting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + monthlylaborsales + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + monthlypartssales + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + monthlyrevenuegraph + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + prodhrssummary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + productiondollars + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + productionhours + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + projectedmonthlysales + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scheduledintoday + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scheduledouttoday + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + dms + + + errors + + + alreadyexported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + refreshallocations + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + documents + + + actions + + + delete + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + download + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reassign + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + selectallimages + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + selectallotherdocuments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + deletes3 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleting_cloudinary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + getpresignurl + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + insert + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nodocuments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updating + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + confirmdelete + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + doctype + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newjobid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + openinexplorer + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + optimizedimage + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reassign_limitexceeded + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reassign_limitexceeded_title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + storageexceeded + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + storageexceeded_title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + upload + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + upload_limitexceeded + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + upload_limitexceeded_title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + uploading + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + usage + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + delete + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + edituploaded + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + insert + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + emails + + + errors + + + notsent + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + cc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + from + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + subject + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + to + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + attachments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + documents + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + emailpreview + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + generatingemail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pdfcopywillbeattached + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + preview + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + sent + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + employee_teams + + + actions + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newmember + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + active + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employeeid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + percentage + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + employees + + + actions + + + addvacation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newrate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + delete + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + save + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + validation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + validationtitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + active + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + base_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cost_center + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employee_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + external_id + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + first_name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + flat_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hire_date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + last_name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + termination_date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + user_email + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vacation + + + end + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + length + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + start + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + labels + + + actions + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + active + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + endmustbeafterstart + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + flat_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + inactive + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_type + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + straight_time + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + delete + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + save + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vacationadded + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + validation + + + unique_employee_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + exportlogs + + + fields + + + createdat + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + attempts + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + priorsuccesfulexport + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + general + + + actions + + + add + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + calculate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cancel + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clear + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + close + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + copied + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + copylink + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + create + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + delete + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleteall + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deselectall + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + edit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + login + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + print + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + refresh + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + remove + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reset + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + resetpassword + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + save + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saveandnew + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + selectall + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + send + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sendbysms + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + senderrortosupport + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + submit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tryagain + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + view + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + viewreleasenotes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + fcm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notfound + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sizelimit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + itemtypes + + + contract + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + courtesycar + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + job + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + owner + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + actions + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + areyousure + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + barcode + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cancel + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clear + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + confirmpassword + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + created_at + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + email + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + errors + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + excel + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exceptiontitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + friday + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + globalsearch + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + help + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + in + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + instanceconflictext + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + instanceconflictitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + item + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + label + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + loading + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + loadingapp + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + loadingshop + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + loggingin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + markedexported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + message + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + monday + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + na + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newpassword + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + no + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nointernet + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nointernet_sub + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + none + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + out + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + password + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + passwordresetsuccess + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + passwordresetsuccess_sub + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + passwordresetvalidatesuccess + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + passwordresetvalidatesuccess_sub + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + passwordsdonotmatch + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + print + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + refresh + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reports + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + required + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saturday + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + search + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + searchresults + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + selectdate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sendagain + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sendby + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + signin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sms + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sub_status + + + expired + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successful + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sunday + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + text + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + thursday + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + total + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + totals + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tuesday + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tvmode + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + unknown + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + username + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + view + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + wednesday + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + yes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + languages + + + english + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + french + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + spanish + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + messages + + + exception + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newversionmessage + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newversiontitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noacctfilepath + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nofeatureaccess + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noshop + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notfoundsub + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notfoundtitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + partnernotrunning + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rbacunauth + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + unsavedchanges + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + unsavedchangespopup + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + validation + + + invalidemail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + invalidphone + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + required + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + help + + + actions + + + connect + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + codeplacholder + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rescuedesc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rescuetitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + intake + + + labels + + + printpack + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + inventory + + + actions + + + addtoinventory + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + addtoro + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + consumefrominventory + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + edit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + inserting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + comment + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + manualinvoicenumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + manualvendor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + consumedbyjob + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleteconfirm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + frombillinvoicenumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + fromvendor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + inventory + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + showall + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + showavailable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + deleted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + inserted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + job_lifecycle + + + columns + + + duration + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + end + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + relative_end + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + relative_start + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + start + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + value + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + content + + + current_status_accumulated_time + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + data_unavailable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + legend_title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + loading + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + not_available + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + previous_status_accumulated_time + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + title_durations + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + title_loading + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + title_transitions + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + fetch + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + job_payments + + + buttons + + + goback + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + proceedtopayment + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + refundpayment + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + notifications + + + error + + + description + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + openingip + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + titles + + + amount + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dateOfPayment + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + descriptions + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payer + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payername + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + paymentid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + paymentnum + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + paymenttype + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + refundamount + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + transactionid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + joblines + + + actions + + + converttolabor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + creating + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updating + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + act_price + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ah_detail_line + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + db_price + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lbr_types + + + LA1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + LA2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + LA3 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + LA4 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + LAA + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + LAB + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + LAD + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + LAE + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + LAF + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + LAG + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + LAM + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + LAR + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + LAS + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + LAU + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + line_desc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + line_ind + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + line_no + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + location + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mod_lb_hrs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mod_lbr_ty + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + oem_partno + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + op_code_desc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + part_qty + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + part_type + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + part_types + + + CCC + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + CCD + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + CCDR + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + CCF + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + CCM + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + PAA + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + PAC + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + PAE + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + PAG + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + PAL + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + PAM + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + PAN + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + PAO + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + PAP + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + PAR + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + PAS + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + PASL + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + profitcenter_labor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + profitcenter_part + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + prt_dsmk_m + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + prt_dsmk_p + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tax_part + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + total + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + unq_seq + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + adjustmenttobeadded + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + billref + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + convertedtolabor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + edit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ioucreated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nostatus + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + presets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + created + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saved + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + validations + + + ahdetailonlyonuserdefinedtypes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hrsrequirediflbrtyp + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + requiredifparttype + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + zeropriceexistingpart + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + jobs + + + actions + + + addDocuments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + addNote + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + addtopartsqueue + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + addtoproduction + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + addtoscoreboard + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + allocate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + autoallocate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + changefilehandler + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + changelaborrate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + changestatus + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + changestimator + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + convert + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + createiou + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deliver + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms + + + addpayer + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + createnewcustomer + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + findmakemodelcode + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + getmakes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + labels + + + refreshallocations + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + post + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + refetchmakesmodels + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + usegeneric + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + useselected + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + dmsautoallocate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + export + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exportcustdata + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exportselected + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + filterpartsonly + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + generatecsi + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gotojob + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + intake + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + manualnew + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mark + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + markasexported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + markpstexempt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + markpstexemptconfirm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + postbills + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + printCenter + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + recalculate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reconcile + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + removefromproduction + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + schedule + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sendcsi + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sendpartspricechange + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sendtodms + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sync + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + uninvoice + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + unvoid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + viewchecklist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + viewdetail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + addingtoproduction + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cannotintake + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + closing + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + creating + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exporting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exporting-partner + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + invoicing + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noaccess + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nodamage + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nodates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nofinancial + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nojobselected + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noowner + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + novehicle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + partspricechange + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saving + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scanimport + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + totalscalc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updating + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + validation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + validationtitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + voiding + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + actual_completion + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + actual_delivery + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + actual_in + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + adjustment_bottom_line + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + adjustmenthours + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + alt_transport + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + area_of_damage_impact + + + 01 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 02 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 03 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 04 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 05 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 06 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 07 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 08 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 09 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 10 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 11 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 12 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 13 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 14 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 15 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 16 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 25 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 26 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 27 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 28 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + 34 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + auto_add_ats + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ca_bc_pvrt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ca_customer_gst + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ca_gst_registrant + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + category + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ccc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ccd + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ccdr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ccf + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ccm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cieca_id + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + claim_total + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + class + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clm_no + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clm_total + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + comment + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + customerowing + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date_estimated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date_exported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date_invoiced + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date_last_contacted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date_lost_sale + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date_next_contact + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date_open + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date_rentalresp + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date_repairstarted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date_scheduled + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date_towin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date_void + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ded_amt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ded_note + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ded_status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + depreciation_taxes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms + + + address + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + amount + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + center + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + control_type + + + account_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + cost + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cost_dms_acctnumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms_make + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms_model + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms_model_override + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms_unsold + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms_wip_acctnumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + id + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + inservicedate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + journal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lines + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + name1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payer + + + amount + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + control_type + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + controlnumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms_acctnumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + sale + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sale_dms_acctnumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + story + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vinowner + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + dms_allocation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + driveable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employee_body + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employee_csr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employee_prep + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employee_refinish + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + est_addr1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + est_co_nm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + est_ct_fn + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + est_ct_ln + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + est_ea + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + est_ph1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + federal_tax_payable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + federal_tax_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ins_addr1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ins_city + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ins_co_id + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ins_co_nm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ins_co_nm_short + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ins_ct_fn + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ins_ct_ln + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ins_ea + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ins_ph1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + intake + + + label + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + max + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + min + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + required + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + type + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + invoice_final_note + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + kmin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + kmout + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + la1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + la2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + la3 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + la4 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + laa + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lab + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + labor_rate_desc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lad + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lae + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + laf + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lag + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lam + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lar + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + las + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lau + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + local_tax_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + loss_date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + loss_desc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + loss_of_use + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lost_sale_reason + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ma2s + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ma3s + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mabl + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + macs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mahw + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mapa + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mash + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + matd + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + other_amount_payable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + owner + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + owner_owing + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_ea + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_ph1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_ph2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + paa + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pac + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pae + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pag + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pam + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pan + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pao + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pap + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + par + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_tax_rates + + + prt_discp + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + prt_mktyp + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + prt_mkupp + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + prt_tax_in + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + prt_tax_rt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + prt_type + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + partsstatus + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pas + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pay_date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + phoneshort + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + po_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + policy_no + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ponumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_vars + + + note + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + qb_multiple_payers + + + amount + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + queued_for_parts + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_ats + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_la1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_la2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_la3 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_la4 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_laa + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_lab + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_lad + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_lae + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_laf + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_lag + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_lam + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_lar + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_las + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_lau + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_ma2s + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_ma3s + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_mabl + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_macs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_mahw + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_mapa + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_mash + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate_matd + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + referral_source_extra + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + referral_source_other + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + referralsource + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + regie_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + repairtotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ro_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scheduled_completion + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scheduled_delivery + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scheduled_in + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + selling_dealer + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + selling_dealer_contact + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + servicecar + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + servicing_dealer + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + servicing_dealer_contact + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + special_coverage_policy + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + specialcoveragepolicy + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + state_tax_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + storage_payable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tax_lbr_rt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tax_levies_rt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tax_paint_mat_rt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tax_registration_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tax_shop_mat_rt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tax_str_rt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tax_sub_rt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tax_tow_rt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + towin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + towing_payable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + unitnumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updated_at + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + uploaded_by + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + forms + + + admindates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + appraiserinfo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + claiminfo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + estdates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + laborrates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lossinfo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + other + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + repairdates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scheddates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + act_price_ppc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + actual_completion_inferred + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + actual_delivery_inferred + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + actual_in_inferred + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + additionalpayeroverallocation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + additionaltotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + adjustmentrate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + adjustments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + adminwarning + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + allocations + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + alreadyaddedtoscoreboard + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + alreadyclosed + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + appointmentconfirmation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + associationwarning + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + audit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + available + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + availablejobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ca_bc_pvrt + + + days + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + ca_gst_all_if_null + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + calc_repair_days + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + calc_repair_days_tt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cards + + + customer + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + damage + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + documents + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + estimator + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + filehandler + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + insurance + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + more + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + totals + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + changeclass + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + checklistcompletedby + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + checklistdocuments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + checklists + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + closeconfirm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + closejob + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + closingperiod + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + contracts + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + convertedtolabor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cost + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cost_Additional + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cost_labor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cost_parts + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cost_sublet + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + costs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + create + + + jobinfo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newowner + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newvehicle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + novehicle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownerinfo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicleinfo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + createiouwarning + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + creating_new_job + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deductible + + + stands + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + waived + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + deleteconfirm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deletedelivery + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleteintake + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deliverchecklist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + difference + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + diskscan + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms + + + apexported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + damageto + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + defaultstory + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + disablebillwip + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + invoicedatefuture + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + kmoutnotgreaterthankmin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + logs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notallocated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + postingform + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + totalallocated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + documents + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + documents-images + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + documents-other + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + duplicateconfirm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + emailaudit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employeeassignments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + estimatelines + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + estimator + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + existing_jobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + federal_tax_amt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gpdollars + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gppercent + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hrs_claimed + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hrs_total + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + importnote + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + inproduction + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + intakechecklist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + iou + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + job + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobcosting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobtotals + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + labor_rates_subtotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + laborallocations + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + labortotals + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lines + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + local_tax_amt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mapa + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + markforreexport + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mash + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + multipayers + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + net_repairs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + othertotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + override_header + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownerassociation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_received + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_tax_rates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + partsfilter + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + partssubletstotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + partstotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pimraryamountpayable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + plitooltips + + + billtotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + calculatedcreditsnotreceived + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + creditmemos + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + creditsnotreceived + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + discrep1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + discrep2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + discrep3 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + laboradj + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + partstotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + totalreturns + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + ppc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + profileadjustments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + prt_dsmk_total + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rates_subtotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reconciliation + + + billlinestotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + byassoc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + byprice + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clear + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + discrepancy + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + joblinestotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + multipleactprices + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + multiplebilllines + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + multiplebillsforactprice + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + removedpartsstrikethrough + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + reconciliationheader + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + relatedros + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + remove_from_ar + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + returntotals + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rosaletotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sale_additional + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sale_labor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sale_parts + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sale_sublet + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sales + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + savebeforeconversion + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scheduledinchange + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + specialcoveragepolicy + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + state_tax_amt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + subletstotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + subtotal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + supplementnote + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + suspended + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + suspense + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + threshhold + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + total_cost + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + total_cust_payable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + total_repairs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + total_sales + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + totals + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + unvoidnote + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicle_info + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicleassociation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + viewallocations + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + voidjob + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + voidnote + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + addedtoproduction + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + all_deleted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + closed + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + converted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + created + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + creatednoclick + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + delete + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + duplicated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + invoiced + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ioucreated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + partsqueue + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + save + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + savetitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + supplemented + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + voided + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + landing + + + bigfeature + + + subtitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + footer + + + company + + + about + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + contact + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + disclaimers + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + privacypolicy + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + io + + + help + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + slogan + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + hero + + + button + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + features + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + managemyshop + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pricing + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + pricing + + + basic + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sub + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + essentials + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sub + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + pricingtitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pro + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sub + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + unlimited + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sub + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + + + menus + + + currentuser + + + languageselector + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + profile + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + header + + + accounting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + accounting-payables + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + accounting-payments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + accounting-receivables + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + activejobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + alljobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + allpayments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + availablejobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bills + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + courtesycars + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + courtesycars-all + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + courtesycars-contracts + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + courtesycars-newcontract + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + customers + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dashboard + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + enterbills + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + entercardpayment + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + enterpayment + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + entertimeticket + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + export + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + export-logs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + help + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + home + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + inventory + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newjob + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + owners + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts-queue + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + phonebook + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + productionboard + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + productionlist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + readyjobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + recent + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reportcenter + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rescueme + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + schedule + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scoreboard + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + search + + + bills + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + owners + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + phonebook + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicles + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + shiftclock + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shop + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shop_config + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shop_csi + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shop_templates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shop_vendors + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + temporarydocs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + timetickets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ttapprovals + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicles + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + jobsactions + + + admin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cancelallappointments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + closejob + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deletejob + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + duplicate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + duplicatenolines + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newcccontract + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + void + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + jobsdetail + + + claimdetail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + financials + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + general + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + insurance + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + labor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lifecycle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + partssublet + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + repairdata + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + totals + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + profilesidebar + + + profile + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shops + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + tech + + + home + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobclockin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobclockout + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + joblookup + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + login + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + logout + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + productionboard + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + productionlist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shiftclockin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + messaging + + + actions + + + link + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + invalidphone + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noattachedjobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updatinglabel + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + addlabel + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + archive + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + maxtenimages + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + messaging + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noallowtxt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nojobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + nopush + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + phonenumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + presets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + recentonly + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + selectmedia + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sentby + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + typeamessage + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + unarchive + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + render + + + conversation_list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + notes + + + actions + + + actions + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deletenote + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + edit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + savetojobnotes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + inserting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + createdby + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + critical + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + private + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + text + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + type + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + types + + + customer + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + general + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + office + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + paint + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shop + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + supplement + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + updatedat + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + addtorelatedro + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newnoteplaceholder + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notetoadd + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + systemnotes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + usernotes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + create + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + owner + + + labels + + + noownerinfo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + owners + + + actions + + + update + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + deleting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noaccess + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saving + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + selectexistingornew + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + address + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + allow_text_message + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + note + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_addr1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_addr2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_city + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_co_nm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_ctry + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_ea + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_fn + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_ln + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_ph1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_ph2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_st + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_zip + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + preferred_contact + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + tax_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + forms + + + address + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + contact + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + create_new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleteconfirm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + existing_owners + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + fromclaim + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + fromowner + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + relatedjobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updateowner + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + delete + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + save + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + parts + + + actions + + + order + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + orderinhouse + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + parts_orders + + + actions + + + backordered + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + receive + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + receivebill + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + associatedbills + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + backordering + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + creating + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + oec + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saving + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updating + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + act_price + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + backordered_eta + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + backordered_on + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cm_received + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + comments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cost + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + db_price + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deliver_by + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + job_line_id + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + line_desc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + line_remarks + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lineremarks + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + oem_partno + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + order_date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + order_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + orderedby + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + part_type + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + quantity + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + return + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + allpartsto + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + confirmdelete + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + custompercent + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + discount + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + email + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + inthisorder + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + is_quote + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mark_as_received + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newpartsorder + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notyetordered + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + oec + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + order_type + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + orderhistory + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_order + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_orders + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + print + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + receive + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + removefrompartsqueue + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + returnpartsorder + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sublet_order + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + created + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + line_updated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + received + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + return_created + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + payments + + + actions + + + generatepaymentlink + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + exporting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exporting-partner + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + inserting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + amount + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + created_at + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exportedat + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + memo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payer + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + paymentnum + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + stripeid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + transactionid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + type + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + balance + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ca_bc_etf_table + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + customer + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + edit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + electronicpayment + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + external + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + findermodal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + insurance + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + markexported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + markforreexport + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + signup + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + smspaymentreminder + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + totalpayments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + exported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + markexported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + markreexported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payment + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + stripe + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + phonebook + + + actions + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + adding + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saving + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + address1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + address2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + category + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + city + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + company + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + country + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + email + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + fax + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + firstname + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lastname + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + phone1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + phone2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + state + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + noneselected + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + onenamerequired + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vendorcategory + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + added + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saved + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + printcenter + + + appointments + + + appointment_confirmation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + bills + + + inhouse_invoice + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + courtesycarcontract + + + courtesy_car_contract + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + courtesy_car_impound + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + courtesy_car_inventory + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + courtesy_car_terms + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + nocontexttype + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + jobs + + + 3rdpartyfields + + + addr1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + addr2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + addr3 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + attn + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + city + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + custgst + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ded_amt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + depreciation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + other + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ponumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + refnumber + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sendtype + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + state + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + zip + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + 3rdpartypayer + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ab_proof_of_loss + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + appointment_confirmation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + appointment_reminder + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + casl_authorization + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + coversheet_landscape + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + coversheet_portrait + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + csi_invitation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + csi_invitation_action + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + diagnostic_authorization + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms_posting_sheet + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + envelope_return_address + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + estimate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + estimate_detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + estimate_followup + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + express_repair_checklist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + filing_coversheet_landscape + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + filing_coversheet_portrait + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + final_invoice + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + fippa_authorization + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + folder_label_multiple + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + glass_express_checklist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + guarantee + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + individual_job_note + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + invoice_customer_payable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + invoice_total_payable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + iou_form + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + job_costing_ro + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + job_notes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + key_tag + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + labels + + + count + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + labels + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + position + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + lag_time_ro + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mechanical_authorization + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mpi_animal_checklist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mpi_eglass_auth + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mpi_final_acct_sheet + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + mpi_final_repair_acct_sheet + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + paint_grid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_invoice_label_single + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_label_multiple + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_label_single + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_order + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_order_confirmation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_order_history + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_return_slip + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payment_receipt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payment_request + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payments_by_job + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchases_by_ro_detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchases_by_ro_summary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + qc_sheet + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + rental_reservation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ro_totals + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ro_with_description + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sgi_certificate_of_repairs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sgi_windshield_auth + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + stolen_recovery_checklist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sublet_order + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + supplement_request + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + thank_you_ro + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + thirdpartypayer + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + timetickets_ro + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicle_check_in + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicle_delivery_check + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + window_tag + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + window_tag_sublet + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + work_authorization + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + worksheet_by_line_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + worksheet_sorted_by_operation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + worksheet_sorted_by_operation_no_hours + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + worksheet_sorted_by_operation_part_type + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + worksheet_sorted_by_operation_type + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + groups + + + authorization + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + financial + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + post + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pre + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ro + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + worksheet + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + misc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + repairorder + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + reportcentermodal + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + speedprint + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + title + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + payments + + + ca_bc_etf_table + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exported_payroll + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + special + + + attendance_detail_csv + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + subjects + + + jobs + + + individual_job_note + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_order + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_return_slip + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sublet_order + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + vendors + + + purchases_by_vendor_detailed + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchases_by_vendor_summary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + production + + + actions + + + addcolumns + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bodypriority-clear + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bodypriority-set + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + detailpriority-clear + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + detailpriority-set + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + paintpriority-clear + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + paintpriority-set + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + remove + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + removecolumn + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saveconfig + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + suspend + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + unsuspend + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + boardupdate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + removing + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + settings + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + actual_in + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + alert + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + alertoff + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + alerton + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ats + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bodyhours + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bodypriority + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bodyshop + + + labels + + + qbo_departmentid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + qbo_usa + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + cardcolor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cardsettings + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clm_no + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + comment + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + compact + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + detailpriority + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employeeassignments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employeesearch + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ins_co_nm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobdetail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + laborhrs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + legend + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + note + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ownr_nm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + paintpriority + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + partsstatus + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_note + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + refinishhours + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scheduled_completion + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + selectview + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + stickyheader + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sublets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + totalhours + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + touchtime + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + viewname + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + removed + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + profile + + + errors + + + state + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + activeshop + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + updated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + reportcenter + + + actions + + + generate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + dates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employee + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + filterson + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + generateasemail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + groups + + + customers + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payroll + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchases + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + sales + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + key + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + objects + + + appointments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bills + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + csi + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exportlogs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_orders + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scoreboard + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + timetickets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + vendor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + templates + + + anticipated_revenue + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ar_aging + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + attendance_detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + attendance_employee + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + attendance_summary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + credits_not_received_date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + credits_not_received_date_vendorid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + csi + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + customer_list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cycle_time_analysis + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + estimates_written_converted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + estimator_detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + estimator_summary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + export_payables + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + export_payments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + export_receivables + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exported_gsr_by_ro + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + exported_gsr_by_ro_labor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gsr_by_atp + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gsr_by_ats + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gsr_by_category + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gsr_by_csr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gsr_by_delivery_date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gsr_by_estimator + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gsr_by_exported_date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gsr_by_ins_co + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gsr_by_make + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gsr_by_referral + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gsr_by_ro + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + gsr_labor_only + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_detail_closed + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_detail_closed_csr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_detail_closed_estimator + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_detail_closed_ins_co + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_detail_closed_status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_detail_open + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_detail_open_csr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_detail_open_estimator + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_detail_open_ins_co + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_detail_open_status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_summary_closed + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_summary_closed_csr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_summary_closed_estimator + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_summary_closed_ins_co + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_summary_closed_status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_summary_open + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_summary_open_csr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_summary_open_estimator + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_summary_open_ins_co + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hours_sold_summary_open_status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + job_costing_ro_csr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + job_costing_ro_date_detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + job_costing_ro_date_summary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + job_costing_ro_estimator + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + job_costing_ro_ins_co + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs_completed_not_invoiced + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs_invoiced_not_exported + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs_reconcile + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs_scheduled_completion + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lag_time + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lost_sales + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + open_orders + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + open_orders_csr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + open_orders_estimator + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + open_orders_excel + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + open_orders_ins_co + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + open_orders_referral + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + open_orders_specific_csr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + open_orders_status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_backorder + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_not_recieved + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_not_recieved_vendor + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts_received_not_scheduled + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payments_by_date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payments_by_date_type + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_by_category + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_by_category_one + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_by_csr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_by_last_name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_by_repair_status + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_by_repair_status_one + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_by_ro + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_by_target_date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_by_technician + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_by_technician_one + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + production_over_time + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + psr_by_make + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchase_return_ratio_grouped_by_vendor_detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchase_return_ratio_grouped_by_vendor_summary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchases_by_cost_center_detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchases_by_cost_center_summary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchases_by_date_range_detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchases_by_date_range_summary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchases_by_vendor_detailed_date_range + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchases_by_vendor_summary_date_range + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchases_grouped_by_vendor_detailed + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + purchases_grouped_by_vendor_summary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + returns_grouped_by_vendor_detailed + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + returns_grouped_by_vendor_summary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + schedule + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scheduled_parts_list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scoreboard_detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scoreboard_summary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + supplement_ratio_ins_co + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + thank_you_date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + timetickets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + timetickets_employee + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + timetickets_summary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + unclaimed_hrs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + void_ros + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + work_in_progress_jobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + work_in_progress_labour + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + work_in_progress_payables + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + schedule + + + labels + + + atssummary + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employeevacation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + estimators + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ins_co_nm_filter + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + intake + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + manual + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + manualevent + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + scoreboard + + + actions + + + edit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + adding + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + removing + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updating + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + bodyhrs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + painthrs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + allemployeetimetickets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + asoftodaytarget + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + body + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bodycharttitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + calendarperiod + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + combinedcharttitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dailyactual + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dailytarget + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + efficiencyoverperiod + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + entries + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobscompletednotinvoiced + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lastmonth + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lastweek + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + monthlytarget + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + priorweek + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + productivestatistics + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + productivetimeticketsoverdate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + refinish + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + refinishcharttitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + targets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + thismonth + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + thisweek + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + timetickets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + timeticketsemployee + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + todateactual + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + totalhrs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + totaloverperiod + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + weeklyactual + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + weeklytarget + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + workingdays + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + added + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + removed + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + tech + + + fields + + + employeeid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + loggedin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notloggedin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + templates + + + errors + + + updating + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + updated + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + timetickets + + + actions + + + claimtasks + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clockin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clockout + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + enter + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + printemployee + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + clockingin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clockingout + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + creating + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noemployeeforuser + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noemployeeforuser_sub + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shiftalreadyclockedon + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + actualhrs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ciecacode + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clockhours + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clockoff + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clockon + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + committed + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cost_center + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + created_by + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + efficiency + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employee + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + employee_team + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + flat_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + memo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + productivehrs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ro_number + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + alreadyclockedon + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ambreak + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + amshift + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clockhours + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clockintojob + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleteconfirm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + edit + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + efficiency + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + flat_rate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobhours + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lunch + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pmbreak + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + pmshift + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shift + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shiftalreadyclockedon + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + straight_time + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + timetickets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + zeroactualnegativeprod + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + clockedin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clockedout + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + created + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + deleted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + validation + + + clockoffmustbeafterclockon + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + clockoffwithoutclockon + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + hoursenteredmorethanavailable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + titles + + + accounting-payables + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + accounting-payments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + accounting-receivables + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + app + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bc + + + accounting-payables + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + accounting-payments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + accounting-receivables + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + availablejobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + bills-list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + contracts + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + contracts-create + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + contracts-detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + courtesycars + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + courtesycars-detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + courtesycars-new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dashboard + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + export-logs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + inventory + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-active + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-admin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-all + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-checklist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-close + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-deliver + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-intake + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-ready + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + owner-detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + owners + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts-queue + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payments-all + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + phonebook + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + productionboard + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + productionlist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + profile + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + schedule + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scoreboard + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shop + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shop-csi + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shop-templates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shop-vendors + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + temporarydocs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + timetickets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ttapprovals + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicle-details + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicles + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + bills-list + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + contracts + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + contracts-create + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + contracts-detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + courtesycars + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + courtesycars-create + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + courtesycars-detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dashboard + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dms + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + export-logs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + inventory + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-admin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-all + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-checklist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-close + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-create + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-deliver + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobs-intake + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobsavailable + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobsdetail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + jobsdocuments + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + manageroot + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + owners + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + owners-detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + parts-queue + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + payments-all + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + phonebook + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + productionboard + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + productionlist + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + profile + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + readyjobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + resetpassword + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + resetpasswordvalidate + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + schedule + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + scoreboard + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shop + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shop-csi + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shop-templates + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + shop_vendors + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + techconsole + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + techjobclock + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + techjoblookup + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + techshiftclock + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + temporarydocs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + timetickets + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + ttapprovals + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicledetail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + vehicles + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + tt_approvals + + + actions + + + approveselected + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + user + + + actions + + + changepassword + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + signout + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updateprofile + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + updating + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + authlevel + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + displayname + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + email + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + photourl + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + actions + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + changepassword + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + profileinfo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successess + + + passwordchanged + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + users + + + errors + + + signinerror + + + auth/user-disabled + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + auth/user-not-found + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + auth/wrong-password + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + + + vehicles + + + errors + + + deleting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + noaccess + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + selectexistingornew + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + validation + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + validationtitle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + description + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + notes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + plate_no + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + plate_st + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + trim_color + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_bstyle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_color + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_cond + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_engine + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_make_desc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_makecode + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_mldgcode + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_model_desc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_model_yr + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_options + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_paint_codes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_prod_dt + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_stage + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_tone + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_trimcode + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_type + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + v_vin + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + forms + + + detail + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + misc + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + registration + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + deleteconfirm + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + fromvehicle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + novehinfo + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + relatedjobs + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + updatevehicle + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + delete + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + save + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + vendors + + + actions + + + addtophonebook + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + new + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + newpreferredmake + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + errors + + + deleting + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saving + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + fields + + + active + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + am + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + city + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + cost_center + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + country + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + discount + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + display_name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + dmsid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + due_date + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + email + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + favorite + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + lkq + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + make + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + oem + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + phone + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + prompt_discount + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + state + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + street1 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + street2 + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + taxid + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + terms + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + zip + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + labels + + + noneselected + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + preferredmakes + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + search + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + successes + + + deleted + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + saved + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + validation + + + unique_vendor_name + + + + + en-US + false + + + es-ES + false + + + fr-CA + false + + + + + + + + + + + + + + false + + + en-US + + + es-ES + + + fr-CA + + + + + main + + + client/src/translations/en_us/common.json + en-US + + + client/src/translations/es/common.json + es-ES + + + client/src/translations/fr/common.json + fr-CA + + + + + + true + alphabetically + + '%1' + + + + + + + en-US + + tab + namespaced-json + true + + diff --git a/package-lock.json b/package-lock.json index 6658c405a..889051d11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "node-quickbooks": "^2.0.43", "nodemailer": "^6.9.7", "phone": "^3.1.41", + "rimraf": "^5.0.5", "soap": "^1.0.0", "socket.io": "^4.7.2", "ssh2-sftp-client": "^9.1.0", @@ -939,6 +940,95 @@ "node": ">=6" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@jonkemp/package-utils": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@jonkemp/package-utils/-/package-utils-1.0.8.tgz", @@ -1016,6 +1106,15 @@ "yarn": "^1.22.10" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1897,7 +1996,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "devOptional": true, "engines": { "node": ">=8" } @@ -1906,7 +2004,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2041,8 +2138,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "devOptional": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -2148,7 +2244,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "devOptional": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -2346,7 +2441,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2650,6 +2744,19 @@ "node-fetch": "^2.6.12" } }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/csrf": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz", @@ -2954,6 +3061,11 @@ "stream-shift": "^1.0.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -2994,8 +3106,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "devOptional": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/enabled": { "version": "2.0.0", @@ -3507,6 +3618,21 @@ } } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -4210,7 +4336,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "devOptional": true, "engines": { "node": ">=8" } @@ -4254,11 +4379,33 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { "version": "10.8.7", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", @@ -4842,6 +4989,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -5209,6 +5364,37 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -5670,57 +5856,55 @@ } }, "node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", "dependencies": { - "glob": "^7.1.3" + "glob": "^10.3.7" }, "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rndm": { @@ -5869,6 +6053,25 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, "node_modules/shell-quote": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", @@ -5891,6 +6094,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -6313,7 +6527,20 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "devOptional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6335,7 +6562,18 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "devOptional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -6523,6 +6761,60 @@ "node": ">=6.0.0" } }, + "node_modules/temp/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/temp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/temp/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/text-decoding": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", @@ -6922,6 +7214,20 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/winston": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", @@ -6981,6 +7287,23 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 7d2748e0f..3625e8808 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "scripts": { "setup": "rm -rf node_modules && npm i && cd client && rm -rf node_modules && npm i", + "setup:win": "rimraf node_modules && npm i && cd client && rimraf node_modules && npm i", "admin": "cd admin && npm start", "client": "cd client && npm start", "server": "nodemon server.js", @@ -49,6 +50,7 @@ "node-quickbooks": "^2.0.43", "nodemailer": "^6.9.7", "phone": "^3.1.41", + "rimraf": "^5.0.5", "soap": "^1.0.0", "socket.io": "^4.7.2", "ssh2-sftp-client": "^9.1.0", diff --git a/server.js b/server.js index 5eebba671..1eaa4b8ec 100644 --- a/server.js +++ b/server.js @@ -1,299 +1,94 @@ +// Import core modules const express = require("express"); const cors = require("cors"); const bodyParser = require("body-parser"); const path = require("path"); const compression = require("compression"); -const twilio = require("twilio"); -const logger = require("./server/utils/logger"); -const fb = require("./server/firebase/firebase-handler"); const cookieParser = require("cookie-parser"); -const multer = require("multer"); -const upload = multer(); -//var enforce = require("express-sslify"); +const http = require("http"); +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 +const logger = require("./server/utils/logger"); + +// Express app and server setup const app = express(); const port = process.env.PORT || 5000; -//const port = 5000; - -const http = require("http"); const server = http.createServer(app); -const { Server } = require("socket.io"); 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://beta.test.imex.online", - "https://www.beta.test.imex.online", - "https://beta.imex.online", - "https://www.beta.imex.online", - ], - 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://beta.test.imex.online", + "https://www.beta.test.imex.online", + "https://beta.imex.online", + "https://www.beta.imex.online", + ], + methods: ["GET", "POST"], + credentials: true, + exposedHeaders: ["set-cookie"], + }, }); exports.io = io; + require("./server/web-sockets/web-socket"); -// app.set('trust proxy', true) -// app.use(fb.validateFirebaseIdToken); + + +// Middleware app.use(compression()); app.use(cookieParser()); -app.use(bodyParser.json({ limit: "50mb" })); -app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); -// app.use(enforce.HTTPS({ trustProtoHeader: true })); -app.use( - cors({ credentials: true, exposedHeaders: ["set-cookie"] }) - // cors({ - // credentials: true, - // origin: [ - // "https://test.imex.online", - // "http://localhost:3000", - // "https://imex.online", - // ], - // }) -); +app.use(bodyParser.json({limit: "50mb"})); +app.use(bodyParser.urlencoded({limit: "50mb", extended: true})); +app.use(cors({credentials: true, exposedHeaders: ["set-cookie"]})); -//Email Based Paths. -var sendEmail = require("./server/email/sendemail.js"); -app.post("/sendemail", fb.validateFirebaseIdToken, sendEmail.sendEmail); -app.post("/emailbounce", bodyParser.text(), sendEmail.emailBounce); - -//Test route to ensure Express is responding. -app.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}`); +// Helper middleware +app.use((req, res, next) => { + req.logger = logger; + next(); }); -//Accounting Qbxml -const accountQbxml = require("./server/accounting/qbxml/qbxml"); -app.post( - "/accounting/qbxml/receivables", - fb.validateFirebaseIdToken, - accountQbxml.receivables -); -app.post( - "/accounting/qbxml/payables", - fb.validateFirebaseIdToken, - accountQbxml.payables -); -app.post( - "/accounting/qbxml/payments", - fb.validateFirebaseIdToken, - accountQbxml.payments -); +// Route groupings +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")); -//Cloudinary Media Paths -var media = require("./server/media/media"); -app.post( - "/media/sign", - fb.validateFirebaseIdToken, - media.createSignedUploadURL -); -app.post("/media/download", fb.validateFirebaseIdToken, media.downloadFiles); -app.post("/media/rename", fb.validateFirebaseIdToken, media.renameKeys); -app.post("/media/delete", fb.validateFirebaseIdToken, media.deleteFiles); - -//SMS/Twilio Paths -var smsReceive = require("./server/sms/receive"); -app.post( - "/sms/receive", - twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }), - smsReceive.receive -); -var smsSend = require("./server/sms/send"); -app.post("/sms/send", fb.validateFirebaseIdToken, smsSend.send); -var smsStatus = require("./server/sms/status"); -app.post( - "/sms/status", - twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }), - smsStatus.status -); -app.post( - "/sms/markConversationRead", - fb.validateFirebaseIdToken, - smsStatus.markConversationRead -); - -var job = require("./server/job/job"); -app.post("/job/totals", fb.validateFirebaseIdToken, job.totals); -app.post( - "/job/statustransition", - // fb.validateFirebaseIdToken, - job.statustransition -); -app.post("/job/totalsssu", fb.validateFirebaseIdToken, job.totalsSsu); -app.post("/job/costing", fb.validateFirebaseIdToken, job.costing); -app.post("/job/costingmulti", fb.validateFirebaseIdToken, job.costingmulti); -var partsScan = require("./server/parts-scan/parts-scan"); -app.post("/job/partsscan", fb.validateFirebaseIdToken, partsScan.partsScan); -//Scheduling -var scheduling = require("./server/scheduling/scheduling-job"); -app.post("/scheduling/job", fb.validateFirebaseIdToken, scheduling.job); - -//Handlebars Paths for Email/Report Rendering -// var renderHandlebars = require("./server/render/renderHandlebars"); -// app.post("/render", fb.validateFirebaseIdToken, renderHandlebars.render); -var inlineCss = require("./server/render/inlinecss"); -app.post("/render/inlinecss", fb.validateFirebaseIdToken, inlineCss.inlinecss); - -// app.post( -// "/notifications/send", - -// fb.sendNotification -// ); -app.post("/notifications/subscribe", fb.validateFirebaseIdToken, fb.subscribe); -app.post( - "/notifications/unsubscribe", - fb.validateFirebaseIdToken, - fb.unsubscribe -); -app.post("/adm/updateuser", fb.validateFirebaseIdToken, fb.updateUser); -app.post("/adm/getuser", fb.validateFirebaseIdToken, fb.getUser); -app.post("/adm/createuser", fb.validateFirebaseIdToken, fb.createUser); -const adm = require("./server/admin/adminops"); -app.post( - "/adm/createassociation", - fb.validateFirebaseIdToken, - fb.validateAdmin, - adm.createAssociation -); -app.post( - "/adm/createshop", - fb.validateFirebaseIdToken, - fb.validateAdmin, - adm.createShop -); -app.post( - "/adm/updateshop", - fb.validateFirebaseIdToken, - fb.validateAdmin, - adm.updateShop -); -app.post( - "/adm/updatecounter", - fb.validateFirebaseIdToken, - fb.validateAdmin, - adm.updateCounter -); - -//Stripe Processing -// var stripe = require("./server/stripe/payment"); -// app.post("/stripe/payment", fb.validateFirebaseIdToken, stripe.payment); -// app.post( -// "/stripe/mobilepayment", -// fb.validateFirebaseIdToken, -// stripe.mobile_payment -// ); - -//Tech Console -var tech = require("./server/tech/tech"); -app.post("/tech/login", fb.validateFirebaseIdToken, tech.techLogin); - -var utils = require("./server/utils/utils"); -app.post("/utils/time", utils.servertime); -app.post("/utils/jsr", fb.validateFirebaseIdToken, utils.jsrAuth); -var qbo = require("./server/accounting/qbo/qbo"); -app.post("/qbo/authorize", fb.validateFirebaseIdToken, qbo.authorize); -app.get("/qbo/callback", qbo.callback); -app.post("/qbo/receivables", fb.validateFirebaseIdToken, qbo.receivables); -app.post("/qbo/payables", fb.validateFirebaseIdToken, qbo.payables); -app.post("/qbo/payments", fb.validateFirebaseIdToken, qbo.payments); - -var data = require("./server/data/data"); -app.post("/data/ah", data.autohouse); -app.post("/data/cc", data.claimscorp); -app.post("/data/kaizen", data.kaizen); -app.post("/record-handler/arms", data.arms); - -var taskHandler = require("./server/tasks/tasks"); -app.post("/taskHandler", fb.validateFirebaseIdToken, taskHandler.taskHandler); - -var mixdataUpload = require("./server/mixdata/mixdata"); - -app.post( - "/mixdata/upload", - fb.validateFirebaseIdToken, - upload.any(), - mixdataUpload.mixdataUpload -); - -var intellipay = require("./server/intellipay/intellipay"); -app.post( - "/intellipay/lightbox_credentials", - fb.validateFirebaseIdToken, - intellipay.lightbox_credentials -); - -app.post( - "/intellipay/payment_refund", - fb.validateFirebaseIdToken, - intellipay.payment_refund -); - -app.post( - "/intellipay/generate_payment_url", - fb.validateFirebaseIdToken, - intellipay.generate_payment_url -); - -app.post( - "/intellipay/postback", - // fb.validateFirebaseIdToken, - intellipay.postback -); - -var ioevent = require("./server/ioevent/ioevent"); -app.post("/ioevent", ioevent.default); -// app.post("/newlog", (req, res) => { -// const { message, type, user, record, object } = req.body; -// logger.log(message, type, user, record, object); -// }); - -var os = require("./server/opensearch/os-handler"); -app.post( - "/opensearch", //fb.validateFirebaseIdToken, - os.handler -); -app.post("/search", fb.validateFirebaseIdToken, os.search); - -var cdkGetMake = require("./server/cdk/cdk-get-makes"); -app.post("/cdk/getvehicles", fb.validateFirebaseIdToken, cdkGetMake.default); - -app.get("/", async function (req, res) { - res.status(200).send("Access Forbidden."); +// Default route for forbidden access +app.get("/", (req, res) => { + res.status(200).send("Access Forbidden."); }); -server.listen(port, (error) => { - if (error) throw error; - logger.log( - `[${process.env.NODE_ENV || "DEVELOPMENT"}] Server running on port ${port}`, - "INFO", - "api" - ); -}); +const main = async () => { + 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); + }); \ No newline at end of file diff --git a/server/accounting/pbs/pbs-job-export.js b/server/accounting/pbs/pbs-job-export.js index dd63a975d..08fe9ee14 100644 --- a/server/accounting/pbs/pbs-job-export.js +++ b/server/accounting/pbs/pbs-job-export.js @@ -166,7 +166,7 @@ async function CheckForErrors(socket, response) { CdkBase.createLogEvent( socket, "DEBUG", - `Succesful response from DMS. ${response.Message || ""}` + `Successful response from DMS. ${response.Message || ""}` ); } else { CdkBase.createLogEvent( diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index 18ab2f8f8..3679db86e 100644 --- a/server/accounting/qbo/qbo-payables.js +++ b/server/accounting/qbo/qbo-payables.js @@ -18,10 +18,10 @@ const { } = require("./qbo-callback"); const OAuthClient = require("intuit-oauth"); const moment = require("moment-timezone"); -const GraphQLClient = require("graphql-request").GraphQLClient; 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, @@ -30,29 +30,31 @@ exports.default = async (req, res) => { 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, }); + 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 BearerToken = req.headers.authorization; const { bills: billsToQuery, elgen } = req.body; - //Query Job Info - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); + + 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, { diff --git a/server/accounting/qbo/qbo-payments.js b/server/accounting/qbo/qbo-payments.js index 13f15e2a6..15418606c 100644 --- a/server/accounting/qbo/qbo-payments.js +++ b/server/accounting/qbo/qbo-payments.js @@ -51,15 +51,13 @@ exports.default = async (req, res) => { } await refreshOauthToken(oauthClient, req); - const BearerToken = req.headers.authorization; const { payments: paymentsToQuery, elgen } = req.body; - //Query Job Info - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); + + 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, { diff --git a/server/accounting/qbo/qbo-receivables.js b/server/accounting/qbo/qbo-receivables.js index a09c22d80..fdd7660d8 100644 --- a/server/accounting/qbo/qbo-receivables.js +++ b/server/accounting/qbo/qbo-receivables.js @@ -18,8 +18,6 @@ const { 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"); @@ -46,15 +44,14 @@ exports.default = async (req, res) => { await refreshOauthToken(oauthClient, req); - const BearerToken = req.headers.authorization; const { jobIds, elgen } = req.body; //Query Job Info - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); + + 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, { diff --git a/server/accounting/qbxml/qbxml-payables.js b/server/accounting/qbxml/qbxml-payables.js index 26f0f2d8a..21cc519b5 100644 --- a/server/accounting/qbxml/qbxml-payables.js +++ b/server/accounting/qbxml/qbxml-payables.js @@ -3,10 +3,11 @@ const path = require("path"); const DineroQbFormat = require("../accounting-constants").DineroQbFormat; const queries = require("../../graphql-client/queries"); const Dinero = require("dinero.js"); -var builder = require("xmlbuilder2"); +const builder = require("xmlbuilder2"); const QbXmlUtils = require("./qbxml-utils"); const moment = require("moment-timezone"); -const logger = require("../../utils/logger"); +const logger = require('../../utils/logger'); + require("dotenv").config({ path: path.resolve( process.cwd(), @@ -15,14 +16,10 @@ require("dotenv").config({ }); exports.default = async (req, res) => { - const BearerToken = req.headers.authorization; const { bills: billsToQuery } = req.body; - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; try { logger.log( diff --git a/server/accounting/qbxml/qbxml-payments.js b/server/accounting/qbxml/qbxml-payments.js index 263b1533e..8e563c9ac 100644 --- a/server/accounting/qbxml/qbxml-payments.js +++ b/server/accounting/qbxml/qbxml-payments.js @@ -1,13 +1,12 @@ -const GraphQLClient = require("graphql-request").GraphQLClient; const path = require("path"); const DineroQbFormat = require("../accounting-constants").DineroQbFormat; const queries = require("../../graphql-client/queries"); const Dinero = require("dinero.js"); -var builder = require("xmlbuilder2"); +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( @@ -19,14 +18,10 @@ require("dotenv").config({ const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils; exports.default = async (req, res) => { - const BearerToken = req.headers.authorization; const { payments: paymentsToQuery } = req.body; - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; try { logger.log( diff --git a/server/accounting/qbxml/qbxml-receivables.js b/server/accounting/qbxml/qbxml-receivables.js index 5489f859e..1d55cee4d 100644 --- a/server/accounting/qbxml/qbxml-receivables.js +++ b/server/accounting/qbxml/qbxml-receivables.js @@ -1,13 +1,12 @@ -const GraphQLClient = require("graphql-request").GraphQLClient; const path = require("path"); const DineroQbFormat = require("../accounting-constants").DineroQbFormat; const queries = require("../../graphql-client/queries"); const Dinero = require("dinero.js"); const moment = require("moment-timezone"); -var builder = require("xmlbuilder2"); +const builder = require("xmlbuilder2"); const QbXmlUtils = require("./qbxml-utils"); -const logger = require("../../utils/logger"); const CreateInvoiceLines = require("../qb-receivables-lines").default; +const logger = require('../../utils/logger'); require("dotenv").config({ path: path.resolve( @@ -20,14 +19,10 @@ Dinero.globalRoundingMode = "HALF_EVEN"; const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils; exports.default = async (req, res) => { - const BearerToken = req.headers.authorization; const { jobIds } = req.body; - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; try { logger.log( diff --git a/server/cdk/cdk-get-makes.js b/server/cdk/cdk-get-makes.js index dc5c59801..0ca36f9e0 100644 --- a/server/cdk/cdk-get-makes.js +++ b/server/cdk/cdk-get-makes.js @@ -5,7 +5,6 @@ require("dotenv").config({ `.env.${process.env.NODE_ENV || "development"}` ), }); -const GraphQLClient = require("graphql-request").GraphQLClient; const soap = require("soap"); const queries = require("../graphql-client/queries"); @@ -34,16 +33,11 @@ const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl"); exports.default = async function ReloadCdkMakes(req, res) { const { bodyshopid, cdk_dealerid } = req.body; try { - const BearerToken = req.headers.authorization; //Query all CDK Models const newList = await GetCdkMakes(req, cdk_dealerid); - //Clear out the existing records - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; const deleteResult = await client .setHeaders({ Authorization: BearerToken }) diff --git a/server/email/sendemail.js b/server/email/sendemail.js index 640d24f2e..170504ce2 100644 --- a/server/email/sendemail.js +++ b/server/email/sendemail.js @@ -1,269 +1,269 @@ 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"); let aws = require("@aws-sdk/client-ses"); -let { defaultProvider } = require("@aws-sdk/credential-provider-node"); +let {defaultProvider} = require("@aws-sdk/credential-provider-node"); const logger = require("../utils/logger"); 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", - region: "ca-central-1", - defaultProvider + // The key apiVersion is no longer supported in v3, and can be removed. + // @deprecated The client uses the "latest" apiVersion. + apiVersion: "latest", + region: "ca-central-1", + defaultProvider }); 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( - { - from: `ImEX Online API - ${process.env.NODE_ENV} `, - to: ["patrick@imexsystems.ca", "support@thinkimex.com"], - subject: subject, - text: text, - ses: { - // optional extra arguments for SendRawEmail - Tags: [ +exports.sendServerEmail = async function ({subject, text}) { + if (process.env.NODE_ENV === undefined) return; + try { + transporter.sendMail( { - Name: "tag_name", - Value: "tag_value", + from: `ImEX Online API - ${process.env.NODE_ENV} `, + to: ["patrick@imexsystems.ca", "support@thinkimex.com"], + subject: subject, + text: text, + ses: { + // optional extra arguments for SendRawEmail + Tags: [ + { + Name: "tag_name", + Value: "tag_value", + }, + ], + }, }, - ], - }, - }, - (err, info) => { - console.log(err || info); - } - ); - } 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: `ImEX Online `, - 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.sendTaskEmail = async function ({to, subject, text, attachments}) { + try { + transporter.sendMail( + { + from: `ImEX Online `, + 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, { + 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( + { 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, - }); + 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}); + } } - }) ); - } - - 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, next) { - 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; +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, + }); } - }); - messageId = message.mail.messageId; - if (replyTo === "noreply@imex.online") { - 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: `ImEX Online `, - to: replyTo, - //bcc: "patrick@snapt.ca", - subject: `ImEX Online Bounced Email - RE: ${subject}`, - text: `ImEX Online has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error. + 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 === "noreply@imex.online") { + 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: `ImEX Online `, + to: replyTo, + //bcc: "patrick@snapt.ca", + subject: `ImEX Online Bounced Email - RE: ${subject}`, + text: `ImEX Online 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), + }); } - } catch (error) { - logger.log("sns-error", "ERROR", "api", null, { - error: JSON.stringify(error), - }); - } - res.sendStatus(200); + res.sendStatus(200); }; diff --git a/server/firebase/firebase-handler.js b/server/firebase/firebase-handler.js index e203bcbf3..d8cf63fb6 100644 --- a/server/firebase/firebase-handler.js +++ b/server/firebase/firebase-handler.js @@ -1,287 +1,215 @@ -var admin = require("firebase-admin"); +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; -var serviceAccount = require(process.env.FIREBASE_ADMINSDK_JSON); + +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; -const adminEmail = [ - "patrick@imex.dev", - //"patrick@imex.test", - "patrick@imex.prod", - "patrick@imexsystems.ca", - "patrick@thinkimex.com", -]; - 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, - }); - - if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) { - logger.log( - "admin-update-user-unauthorized", - "ERROR", - req.user.email, - null, - { + logger.log("admin-update-user", "ADMIN", 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. - - 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); }); + + 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. + + 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, - }); - - if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) { - logger.log( - "admin-update-user-unauthorized", - "ERROR", - req.user.email, - null, - { + logger.log("admin-get-user", "ADMIN", 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); + 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; + } + + 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 - +16049992002`, - 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 - +16049992002`, + 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); + } }; -exports.validateFirebaseIdToken = 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."); - res.status(403).send("Unauthorized"); - return; - } - - 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", - }); - res.status(403).send("Unauthorized"); - return; - } - - try { - const decodedIdToken = await admin.auth().verifyIdToken(idToken); - //console.log("ID Token correctly decoded", decodedIdToken); - req.user = decodedIdToken; - next(); - return; - } catch (error) { - logger.log("api-unauthorized-call", "WARN", null, null, { - path: req.path, - body: req.body, - - type: "unauthroized", - ...error, - }); - - res.status(401).send("Unauthorized"); - return; - } -}; - -exports.validateAdmin = async (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, - }); - res.sendStatus(404); - return; - } else { - next(); - return; - } -}; //Admin claims code. // const uid = "JEqqYlsadwPEXIiyRBR55fflfko1"; diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index b9c491de7..2bd83d1ab 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -518,6 +518,21 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = ` } }`; +exports.QUERY_TRANSITIONS_BY_JOBID = `query QUERY_TRANSITIONS_BY_JOBID($jobids: [uuid!]!) { + transitions(where: {jobid: {_in: $jobids}}, order_by: {end: desc}) { + start + end + value + prev_value + next_value + duration + type + created_at + updated_at + jobid + } +}`; + exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) { jobs_by_pk(id: $jobId) { bodyshop { diff --git a/server/job/job-costing.js b/server/job/job-costing.js index e7568fa72..4e7b40dc7 100644 --- a/server/job/job-costing.js +++ b/server/job/job-costing.js @@ -1,83 +1,752 @@ +const _ = require("lodash"); const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); -//const client = require("../graphql-client/graphql-client").client; -const _ = require("lodash"); -const GraphQLClient = require("graphql-request").GraphQLClient; -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.headers.authorization; - logger.log("job-costing-start", "DEBUG", req.user.email, jobid, null); - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; - try { - const resp = await client - .setHeaders({ Authorization: BearerToken }) - .request(queries.QUERY_JOB_COSTING_DETAILS, { - id: jobid, - }); + logger.log("job-costing-start", "DEBUG", req.user.email, jobid, null); - const ret = GenerateCostingData(resp.jobs_by_pk); + try { + const resp = await client + .setHeaders({Authorization: BearerToken}) + .request(queries.QUERY_JOB_COSTING_DETAILS, { + id: jobid, + }); - res.status(200).json(ret); - } catch (error) { - logger.log("job-costing-error", "ERROR", req.user.email, jobid, { - message: error.message, - stack: error.stack, - }); + const ret = GenerateCostingData(resp.jobs_by_pk); - res.status(400).send(JSON.stringify(error)); - } + 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)); + } } async function JobCostingMulti(req, res) { - const { jobids } = req.body; - const BearerToken = req.headers.authorization; - logger.log("job-costing-multi-start", "DEBUG", req.user.email, jobids, null); + const {jobids} = req.body; - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); + const logger = req.logger; + const BearerToken = req.BearerToken + const client = req.userGraphQLClient; - try { - const resp = await client - .setHeaders({ Authorization: BearerToken }) - .request(queries.QUERY_JOB_COSTING_DETAILS_MULTI, { - ids: jobids, - }); + logger.log("job-costing-multi-start", "DEBUG", req.user.email, jobids, null); - 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 }), + + 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}), totalLaborGppercent: null, totalLaborGppercentFormatted: null, totalPartsGppercent: null, @@ -86,894 +755,220 @@ async function JobCostingMulti(req, res) { totalAdditionalGppercentFormatted: null, totalSubletGppercent: null, totalSubletGppercentFormatted: null, - }, + gppercent: null, + gppercentFormatted: null, }; - const ret = {}; - resp.jobs.map((job) => { - const costingData = GenerateCostingData(job); - ret[job.id] = costingData; + 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}); - //Merge on a cost center basis. + 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}); - 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()) * + 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) - ), - }; + ).toFixed(1); + + //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), + }; }); - //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 - ), + //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), }); - } 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) + //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 ); - } - const ticketTotalsByCostCenter = job.timetickets.reduce( - (ticket_acc, ticket_val) => { - //At the invoice level. + 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 + ); - 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 }), - totalLaborGppercent: null, - totalLaborGppercentFormatted: null, - totalPartsGppercent: null, - totalPartsGppercentFormatted: null, - totalAdditionalGppercent: null, - totalAdditionalGppercentFormatted: null, - totalSubletGppercent: null, - totalSubletGppercentFormatted: null, - gppercent: null, - gppercentFormatted: 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 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 }); - - 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 + summaryData.gpdollars = summaryData.totalSales.subtract( + summaryData.totalCost + ); + summaryData.gppercent = ( + (summaryData.gpdollars.getAmount() / + Math.abs(summaryData.totalSales.getAmount())) * + 100 ).toFixed(1); - //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); + if (isNaN(summaryData.gppercent)) summaryData.gppercentFormatted = 0; + else if (!isFinite(summaryData.gppercent)) + summaryData.gppercentFormatted = "- ∞"; + else { + summaryData.gppercentFormatted = `${summaryData.gppercent}%`; + } - 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 }; + 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 { - 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 { + return null; + } } - } }; diff --git a/server/job/job-lifecycle.js b/server/job/job-lifecycle.js new file mode 100644 index 000000000..c2ea72737 --- /dev/null +++ b/server/job/job-lifecycle.js @@ -0,0 +1,68 @@ +const _ = require("lodash"); +const queries = require("../graphql-client/queries"); +const moment = require("moment"); +const durationToHumanReadable = require("../utils/durationToHumanReadable"); +const calculateStatusDuration = require("../utils/calculateStatusDuration"); + +const jobLifecycle = async (req, res) => { + // 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 = {}; + + 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; + }); + + groupedTransitions[jobId] = { + lifecycle: lifecycle, + durations: calculateStatusDuration(lifecycle, statuses), + }; + } + + return res.status(200).json({ + jobIDs, + transition: groupedTransitions, + }); +} + +module.exports = jobLifecycle; \ No newline at end of file diff --git a/server/job/job-status-transition.js b/server/job/job-status-transition.js index fd74a2ec3..960041746 100644 --- a/server/job/job-status-transition.js +++ b/server/job/job-status-transition.js @@ -9,83 +9,84 @@ const logger = require("../utils/logger"); Dinero.globalRoundingMode = "HALF_EVEN"; 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) { - if (req.headers["event-secret"] !== process.env.EVENT_SECRET) { - res.status(401).send("Unauthorized"); - return; - } - res.sendStatus(200); - return; - const { - id: jobid, - status: value, - shopid: bodyshopid, - } = req.body.event.data.new; - try { - const { update_transitions } = await client.request( - queries.UPDATE_OLD_TRANSITION, - { - jobid: jobid, - existingTransition: { - end: new Date(), - next_value: value, + const { + id: jobid, + status: value, + shopid: bodyshopid, + } = req.body.event.data.new; - //duration - }, - } - ); + // 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, - let duration = - update_transitions.affected_rows === 0 - ? 0 - : new Date(update_transitions.returning[0].end) - - new Date(update_transitions.returning[0].start); + //duration + }, + } + ); - const resp2 = await client.request(queries.INSERT_NEW_TRANSITION, { - 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", - }, - }); + let duration = + update_transitions.affected_rows === 0 + ? 0 + : new Date(update_transitions.returning[0].end) - + new Date(update_transitions.returning[0].start); - //Check to see if there is an existing status transition record. - //Query using Job ID, start is not null, end is null. + const resp2 = await client.request(queries.INSERT_NEW_TRANSITION, { + 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", + }, + }); - //If there is no existing record, this is the start of the transition life cycle. - // Create the initial transition record. + //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 a current status transition record, update it with the end date, duration, and next value. + //If there is no existing record, this is the start of the transition life cycle. + // Create the initial transition record. - res.sendStatus(200); //.json(ret); - } catch (error) { - logger.log("job-status-transition-error", "ERROR", req.user?.email, jobid, { - message: error.message, - stack: error.stack, - }); + //If there is a current status transition record, update it with the end date, duration, and next value. - res.status(400).send(JSON.stringify(error)); - } + 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)); + } } exports.statustransition = StatusTransition; diff --git a/server/job/job-totals.js b/server/job/job-totals.js index 35246ec8c..b942871cf 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -1,20 +1,18 @@ const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); -const GraphQLClient = require("graphql-request").GraphQLClient; -const logger = require("../utils/logger"); +const logger = require('../utils/logger'); + // Dinero.defaultCurrency = "USD"; // Dinero.globalLocale = "en-CA"; Dinero.globalRoundingMode = "HALF_EVEN"; exports.totalsSsu = async function (req, res) { - const BearerToken = req.headers.authorization; const { id } = req.body; + + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; + logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null); - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); try { const job = await client @@ -75,21 +73,19 @@ async function TotalsServerSide(req, res) { } async function Totals(req, res) { - const { job } = req.body; + const { job, id } = req.body; + + const logger = req.logger; + const client = req.userGraphQLClient; + logger.log("job-totals", "DEBUG", req.user.email, job.id, { jobid: job.id, }); - const BearerToken = req.headers.authorization; - const { id } = req.body; logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null); - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); await AutoAddAtsIfRequired({ job, client }); + try { let ret = { parts: CalculatePartsTotals(job.joblines), diff --git a/server/job/job.js b/server/job/job.js index e0fff1695..f21cbf4f4 100644 --- a/server/job/job.js +++ b/server/job/job.js @@ -3,3 +3,4 @@ exports.totalsSsu = require("./job-totals").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'); \ No newline at end of file diff --git a/server/middleware/eventAuthorizationMIddleware.js b/server/middleware/eventAuthorizationMIddleware.js new file mode 100644 index 000000000..9dd4dfd3a --- /dev/null +++ b/server/middleware/eventAuthorizationMIddleware.js @@ -0,0 +1,20 @@ +const path = require("path"); + +/** + * Checks if the event secret is correct + * It adds the following properties to the request object: + * - req.isEventAuthorized - Returns true if the event secret is correct + * @param req + * @param res + * @param next + */ +function eventAuthorizationMiddleware(req, res, next) { + if (req.headers["event-secret"] !== process.env.EVENT_SECRET) { + return res.status(401).send("Unauthorized"); + } + + req.isEventAuthorized = true; + next(); +} + +module.exports = eventAuthorizationMiddleware; \ No newline at end of file diff --git a/server/middleware/validateAdminMiddleware.js b/server/middleware/validateAdminMiddleware.js new file mode 100644 index 000000000..cfd53b171 --- /dev/null +++ b/server/middleware/validateAdminMiddleware.js @@ -0,0 +1,26 @@ +const logger = require("../utils/logger"); +const adminEmail = require("../utils/adminEmail"); + +/** + * Validate admin middleware + * It adds the following properties to the request object: + * - req.isAdmin - returns true if the user passed an admin check + * @param req + * @param res + * @param next + * @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); + } + + req.isAdmin = true; + next(); +}; + +module.exports = validateAdminMiddleware; \ No newline at end of file diff --git a/server/middleware/validateFirebaseIdTokenMiddleware.js b/server/middleware/validateFirebaseIdTokenMiddleware.js new file mode 100644 index 000000000..53d1cc775 --- /dev/null +++ b/server/middleware/validateFirebaseIdTokenMiddleware.js @@ -0,0 +1,69 @@ +const logger = require("../utils/logger"); +const admin = require("firebase-admin"); + +/** + * Middleware to validate Firebase ID Tokens. + * This middleware is used to protect API endpoints from unauthorized access. + * It adds the following properties to the request object: + * - req.user - the decoded Firebase ID Token + * @param req + * @param res + * @param next + * @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"); + } + + 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", + }); + + 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(); + + } catch (error) { + logger.log("api-unauthorized-call", "WARN", null, null, { + path: req.path, + body: req.body, + + type: "unauthroized", + ...error, + }); + + return res.status(401).send("Unauthorized"); + } +}; + +module.exports = validateFirebaseIdTokenMiddleware; \ No newline at end of file diff --git a/server/middleware/withUserGraphQLClientMiddleware.js b/server/middleware/withUserGraphQLClientMiddleware.js new file mode 100644 index 000000000..e55b58c8d --- /dev/null +++ b/server/middleware/withUserGraphQLClientMiddleware.js @@ -0,0 +1,24 @@ +const {GraphQLClient} = require("graphql-request"); + +/** + * Middleware to add a GraphQL Client to the request object + * Adds the following to the request object: + * req.userGraphQLClient - GraphQL Client with user Bearer Token + * req.BearerToken - Bearer Token + * @param req + * @param res + * @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; + + next(); +}; + +module.exports = withUserGraphQLClientMiddleware; \ No newline at end of file diff --git a/server/mixdata/mixdata.js b/server/mixdata/mixdata.js index a0d5141f3..41a3b0eb8 100644 --- a/server/mixdata/mixdata.js +++ b/server/mixdata/mixdata.js @@ -1,9 +1,8 @@ const path = require("path"); const _ = require("lodash"); -const logger = require("../utils/logger"); const xml2js = require("xml2js"); -const GraphQLClient = require("graphql-request").GraphQLClient; const queries = require("../graphql-client/queries"); +const logger = require('../utils/logger'); require("dotenv").config({ path: path.resolve( @@ -15,13 +14,10 @@ require("dotenv").config({ exports.mixdataUpload = async (req, res) => { const { bodyshopid } = req.body; - const BearerToken = req.headers.authorization; + const client = req.userGraphQLClient; + logger.log("job-mixdata-upload", "DEBUG", req.user.email, null, null); - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); + try { for (const element of req.files) { diff --git a/server/opensearch/os-handler.js b/server/opensearch/os-handler.js index 7cb544400..5fe63695c 100644 --- a/server/opensearch/os-handler.js +++ b/server/opensearch/os-handler.js @@ -5,7 +5,6 @@ require("dotenv").config({ ), }); -const GraphQLClient = require("graphql-request").GraphQLClient; //const client = require("../graphql-client/graphql-client").client; const logger = require("../utils/logger"); const queries = require("../graphql-client/queries"); @@ -15,10 +14,6 @@ const {getClient} = require('../../libs/awsUtils'); async function OpenSearchUpdateHandler(req, res) { - if (req.headers["event-secret"] !== process.env.EVENT_SECRET) { - res.status(401).send("Unauthorized"); - return; - } try { const osClient = await getClient(); @@ -186,12 +181,8 @@ async function OpenSearchSearchHandler(req, res) { search, }); - const BearerToken = req.headers.authorization; - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; const assocs = await client .setHeaders({Authorization: BearerToken}) diff --git a/server/parts-scan/parts-scan.js b/server/parts-scan/parts-scan.js index c5b619303..b2f51ef3b 100644 --- a/server/parts-scan/parts-scan.js +++ b/server/parts-scan/parts-scan.js @@ -1,21 +1,19 @@ const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); +const logger = require('../utils/logger'); const { job } = require("../scheduling/scheduling-job"); -const GraphQLClient = require("graphql-request").GraphQLClient; -const logger = require("../utils/logger"); const _ = require("lodash"); + // Dinero.defaultCurrency = "USD"; // Dinero.globalLocale = "en-CA"; exports.partsScan = async function (req, res) { - const BearerToken = req.headers.authorization; const { jobid } = req.body; + + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; + logger.log("job-parts-scan", "DEBUG", req.user?.email, jobid, null); - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); try { //Query all jobline data using the user's authorization. diff --git a/server/routes/accountingRoutes.js b/server/routes/accountingRoutes.js new file mode 100644 index 000000000..04576bf01 --- /dev/null +++ b/server/routes/accountingRoutes.js @@ -0,0 +1,13 @@ +const express = require('express'); +const router = express.Router(); +const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); +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); + +module.exports = router; diff --git a/server/routes/adminRoutes.js b/server/routes/adminRoutes.js new file mode 100644 index 000000000..617f343b3 --- /dev/null +++ b/server/routes/adminRoutes.js @@ -0,0 +1,18 @@ +const express = require('express'); +const router = express.Router(); +const fb = require('../firebase/firebase-handler'); +const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); +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); + +module.exports = router; diff --git a/server/routes/cdkRoutes.js b/server/routes/cdkRoutes.js new file mode 100644 index 000000000..85d2b49d0 --- /dev/null +++ b/server/routes/cdkRoutes.js @@ -0,0 +1,11 @@ +const express = require('express'); +const router = express.Router(); +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); + +module.exports = router; diff --git a/server/routes/dataRoutes.js b/server/routes/dataRoutes.js new file mode 100644 index 000000000..0240f3388 --- /dev/null +++ b/server/routes/dataRoutes.js @@ -0,0 +1,9 @@ +const express = require('express'); +const router = express.Router(); +const {autohouse, claimscorp, kaizen} = require('../data/data'); + +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 new file mode 100644 index 000000000..3952a3450 --- /dev/null +++ b/server/routes/intellipayRoutes.js @@ -0,0 +1,11 @@ +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"); + +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 new file mode 100644 index 000000000..9b4a5e9f6 --- /dev/null +++ b/server/routes/jobRoutes.js @@ -0,0 +1,18 @@ +const express = require('express'); +const router = express.Router(); +const job = require('../job/job'); +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 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); + +module.exports = router; diff --git a/server/routes/mediaRoutes.js b/server/routes/mediaRoutes.js new file mode 100644 index 000000000..a44a1a048 --- /dev/null +++ b/server/routes/mediaRoutes.js @@ -0,0 +1,13 @@ +const express = require('express'); +const router = express.Router(); +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); + +module.exports = router; diff --git a/server/routes/miscellaneousRoutes.js b/server/routes/miscellaneousRoutes.js new file mode 100644 index 000000000..9d20d9b42 --- /dev/null +++ b/server/routes/miscellaneousRoutes.js @@ -0,0 +1,51 @@ +const express = require('express'); +const router = express.Router(); +const logger = require("../../server/utils/logger"); +const sendEmail = require("../email/sendemail"); +const data = require("../data/data"); +const bodyParser = require("body-parser"); +const ioevent = require("../ioevent/ioevent"); +const taskHandler = require("../tasks/tasks"); +const os = require("../opensearch/os-handler"); +const eventAuthorizationMiddleware = require("../middleware/eventAuthorizationMIddleware"); +const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); +const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); + +//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}`); +}); + +// Search +router.post("/search", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, os.search); +router.post("/opensearch", eventAuthorizationMiddleware, os.handler); + + +// IO Events +router.post('/ioevent', ioevent.default); + +// Email +router.post('/sendemail', validateFirebaseIdTokenMiddleware, sendEmail.sendEmail); +router.post('/emailbounce', bodyParser.text(), sendEmail.emailBounce); + +// Handlers +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 new file mode 100644 index 000000000..b9ac289e7 --- /dev/null +++ b/server/routes/mixDataRoutes.js @@ -0,0 +1,11 @@ +const express = require('express'); +const router = express.Router(); +const multer = require('multer'); +const upload = multer(); +const {mixdataUpload} = require('../mixdata/mixdata'); +const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); +const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); + +router.post('/upload', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, upload.any(), mixdataUpload); + +module.exports = router; diff --git a/server/routes/notificationsRoutes.js b/server/routes/notificationsRoutes.js new file mode 100644 index 000000000..1a8e9de7b --- /dev/null +++ b/server/routes/notificationsRoutes.js @@ -0,0 +1,11 @@ +const express = require('express'); +const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); +const {subscribe, unsubscribe} = require("../firebase/firebase-handler"); +const router = express.Router(); + +router.use(validateFirebaseIdTokenMiddleware); + +router.post('/subscribe', subscribe); +router.post('/unsubscribe', unsubscribe); + +module.exports = router; diff --git a/server/routes/qboRoutes.js b/server/routes/qboRoutes.js new file mode 100644 index 000000000..22b54e23e --- /dev/null +++ b/server/routes/qboRoutes.js @@ -0,0 +1,14 @@ +const express = require('express'); +const router = express.Router(); +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); + +module.exports = router; diff --git a/server/routes/renderRoutes.js b/server/routes/renderRoutes.js new file mode 100644 index 000000000..7242404e5 --- /dev/null +++ b/server/routes/renderRoutes.js @@ -0,0 +1,9 @@ +const express = require('express'); +const router = express.Router(); +const {inlinecss} = require('../render/inlinecss'); +const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); + +// Define the route for inline CSS rendering +router.post('/inlinecss', validateFirebaseIdTokenMiddleware, inlinecss); + +module.exports = router; diff --git a/server/routes/schedulingRoutes.js b/server/routes/schedulingRoutes.js new file mode 100644 index 000000000..816114315 --- /dev/null +++ b/server/routes/schedulingRoutes.js @@ -0,0 +1,9 @@ +const express = require('express'); +const router = express.Router(); +const {job} = require('../scheduling/scheduling-job'); +const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); +const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); + +router.post('/job', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, job); + +module.exports = router; diff --git a/server/routes/smsRoutes.js b/server/routes/smsRoutes.js new file mode 100644 index 000000000..9952e0d64 --- /dev/null +++ b/server/routes/smsRoutes.js @@ -0,0 +1,17 @@ +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 validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); + +// Twilio Webhook Middleware for 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); + +module.exports = router; diff --git a/server/routes/techRoutes.js b/server/routes/techRoutes.js new file mode 100644 index 000000000..e7594f532 --- /dev/null +++ b/server/routes/techRoutes.js @@ -0,0 +1,8 @@ +const express = require('express'); +const router = express.Router(); +const {techLogin} = require('../tech/tech'); +const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); + +router.post('/login', validateFirebaseIdTokenMiddleware, techLogin); + +module.exports = router; diff --git a/server/routes/utilRoutes.js b/server/routes/utilRoutes.js new file mode 100644 index 000000000..938b1e1b1 --- /dev/null +++ b/server/routes/utilRoutes.js @@ -0,0 +1,9 @@ +const express = require('express'); +const router = express.Router(); +const {servertime, jsrAuth} = require('../utils/utils'); +const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); + +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 e906c866e..178ef3c73 100644 --- a/server/scheduling/scheduling-job.js +++ b/server/scheduling/scheduling-job.js @@ -1,4 +1,3 @@ -const GraphQLClient = require("graphql-request").GraphQLClient; const path = require("path"); const queries = require("../graphql-client/queries"); const Dinero = require("dinero.js"); @@ -14,17 +13,14 @@ require("dotenv").config({ }); exports.job = async (req, res) => { - const BearerToken = req.headers.authorization; const { jobId } = req.body; + + const BearerToken = req.BearerToken; + const client = req.userGraphQLClient; + try { logger.log("smart-scheduling-start", "DEBUG", req.user.email, jobId, null); - const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { - headers: { - Authorization: BearerToken, - }, - }); - const result = await client .setHeaders({ Authorization: BearerToken }) .request(queries.QUERY_UPCOMING_APPOINTMENTS, { diff --git a/server/utils/adminEmail.js b/server/utils/adminEmail.js new file mode 100644 index 000000000..70c44cb47 --- /dev/null +++ b/server/utils/adminEmail.js @@ -0,0 +1,13 @@ +/** + * List of admin email addresses + * @type {string[]} + */ +const adminEmail = [ + "patrick@imex.dev", + //"patrick@imex.test", + "patrick@imex.prod", + "patrick@imexsystems.ca", + "patrick@thinkimex.com", +]; + +module.exports = adminEmail; \ No newline at end of file diff --git a/server/utils/calculateStatusDuration.js b/server/utils/calculateStatusDuration.js new file mode 100644 index 000000000..75b30c54c --- /dev/null +++ b/server/utils/calculateStatusDuration.js @@ -0,0 +1,101 @@ +const durationToHumanReadable = require("./durationToHumanReadable"); +const moment = require("moment"); +const _ = require("lodash"); +const crypto = require('crypto'); + +const getColor = (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 calculateStatusDuration = (transitions, statuses) => { + 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: transition.duration_readable + }; + } else if (!transition.next_value) { + if (statusDuration[transition.value]) { + statusDuration[transition.value].value += duration; + statusDuration[transition.value].humanReadable = transition.duration_readable; + } else { + statusDuration[transition.value] = { + value: duration, + humanReadable: transition.duration_readable + }; + } + } else { + if (statusDuration[transition.value]) { + statusDuration[transition.value].value += duration; + statusDuration[transition.value].humanReadable = transition.duration_readable; + } else { + statusDuration[transition.value] = { + value: duration, + humanReadable: transition.duration_readable + }; + } + } + }); + + // 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: getColor(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 diff --git a/server/utils/durationToHumanReadable.js b/server/utils/durationToHumanReadable.js new file mode 100644 index 000000000..f13e24c98 --- /dev/null +++ b/server/utils/durationToHumanReadable.js @@ -0,0 +1,22 @@ +const durationToHumanReadable = (duration) => { + if (!duration) return 'N/A'; + + 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(); + + 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 (!minutes && !hours && !days && !months && !years && seconds) parts.push(seconds + ' second' + (seconds > 1 ? 's' : '')); + + return parts.join(', '); +} +module.exports = durationToHumanReadable; \ No newline at end of file