- {`ImEX Online V.${process.env.NODE_ENV} - ${
+ {`ImEX Online ${
process.env.REACT_APP_GIT_SHA
} - ${preval`module.exports = new Date().toLocaleString("en-US", {timeZone: "America/Los_Angeles"});`}`}
diff --git a/client/src/redux/application/application.actions.js b/client/src/redux/application/application.actions.js
index cffcaa4fc..3f3fb4012 100644
--- a/client/src/redux/application/application.actions.js
+++ b/client/src/redux/application/application.actions.js
@@ -48,3 +48,8 @@ export const setPartnerVersion = (version) => ({
type: ApplicationActionTypes.SET_PARTNER_VERSION,
payload: version,
});
+
+export const setOnline = (isOnline) => ({
+ type: ApplicationActionTypes.SET_ONLINE_STATUS,
+ payload: isOnline,
+});
diff --git a/client/src/redux/application/application.reducer.js b/client/src/redux/application/application.reducer.js
index 40fece7d7..60685e2cc 100644
--- a/client/src/redux/application/application.reducer.js
+++ b/client/src/redux/application/application.reducer.js
@@ -2,6 +2,7 @@ import ApplicationActionTypes from "./application.types";
const INITIAL_STATE = {
loading: false,
+ online: true,
breadcrumbs: [],
recentItems: [],
selectedHeader: "home",
@@ -21,6 +22,11 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
...state,
selectedHeader: action.payload,
};
+ case ApplicationActionTypes.SET_ONLINE_STATUS:
+ return {
+ ...state,
+ online: action.payload,
+ };
case ApplicationActionTypes.ADD_RECENT_ITEM:
return {
...state,
diff --git a/client/src/redux/application/application.selectors.js b/client/src/redux/application/application.selectors.js
index 2dba7b8ea..5c48a17d1 100644
--- a/client/src/redux/application/application.selectors.js
+++ b/client/src/redux/application/application.selectors.js
@@ -40,3 +40,7 @@ export const selectJobReadOnly = createSelector(
[selectApplication],
(application) => application.jobReadOnly
);
+export const selectOnline = createSelector(
+ [selectApplication],
+ (application) => application.online
+);
diff --git a/client/src/redux/application/application.types.js b/client/src/redux/application/application.types.js
index 2de24c05d..8714692cb 100644
--- a/client/src/redux/application/application.types.js
+++ b/client/src/redux/application/application.types.js
@@ -9,5 +9,6 @@ const ApplicationActionTypes = {
SET_SELECTED_HEADER: "SET_SELECTED_HEADER",
SET_JOB_READONLY: "SET_JOB_READONLY",
SET_PARTNER_VERSION: "SET_PARTNER_VERSION",
+ SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
};
export default ApplicationActionTypes;
diff --git a/client/src/redux/user/user.sagas.js b/client/src/redux/user/user.sagas.js
index a70964387..cbeba03af 100644
--- a/client/src/redux/user/user.sagas.js
+++ b/client/src/redux/user/user.sagas.js
@@ -1,4 +1,6 @@
import Fingerprint2 from "@fingerprintjs/fingerprintjs";
+import { notification } from "antd";
+import i18next from "i18next";
import LogRocket from "logrocket";
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
import {
@@ -100,6 +102,10 @@ export function* updateUserDetails(userDetails) {
try {
yield updateCurrentUser(userDetails.payload);
yield put(updateUserDetailsSuccess(userDetails.payload));
+ notification.open({
+ type: "success",
+ message: i18next.t("profile.successes.updated"),
+ });
} catch (error) {
//yield put(signOutFailure(error.message));
}
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index 97730f5e4..164bdd128 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -218,6 +218,10 @@
"invoice_federal_tax_rate": "Invoices - Federal Tax Rate",
"invoice_local_tax_rate": "Invoices - Local Tax Rate",
"invoice_state_tax_rate": "Invoices - Provincial/State Tax Rate",
+ "jc_hourly_rates": {
+ "mapa": "Job Costing - Paint Materials Hourly Cost Rate",
+ "mash": "Job Costing - Shop Materials Hourly Cost Rate"
+ },
"lastnumberworkingdays": "Scoreboard - Last Number of Working Days",
"logo_img_path": "Shop Logo",
"logo_img_path_height": "Logo Image Height",
@@ -555,7 +559,7 @@
"driverinformation": "Driver's Information",
"findcontract": "Find Contract",
"findermodal": "Contract Finder",
- "noteconvertedfrom": "R.O. created from converted Courtesy Car Contract {{agreementnumber}}.",
+ "noteconvertedfrom": "R.O. created from converted Courtesy Car Contract {{agreementnumber}}.",
"populatefromjob": "Populate from Job",
"rates": "Contract Rates",
"time": "Time",
@@ -683,7 +687,8 @@
"deleting_cloudinary": "Error deleting document from storage. {{message}}",
"getpresignurl": "Error obtaining presigned URL for document. {{message}}",
"insert": "Unable to upload file. {{message}}",
- "nodocuments": "There are no documents."
+ "nodocuments": "There are no documents.",
+ "updating": "Error updating document. {{error}}"
},
"labels": {
"confirmdelete": "Are you sure you want to delete these documents. This CANNOT be undone.",
@@ -820,6 +825,8 @@
"monday": "Monday",
"na": "N/A",
"no": "No",
+ "nointernet": "It looks like you're not connected to the internet.",
+ "nointernet_sub": "Please check your connection and try again. ",
"none": "None",
"out": "Out",
"password": "Password",
@@ -1281,7 +1288,7 @@
"gppercent": "% G.P.",
"hrs_claimed": "Hours Claimed",
"hrs_total": "Hours Total",
- "importnote": "The job was initially imported on {{date}} at {{time}}.",
+ "importnote": "The job was initially imported.",
"inproduction": "In Production",
"intakechecklist": "Intake Checklist",
"job": "Job Details",
@@ -1316,6 +1323,7 @@
"partstotal": "This is the total of all parts and sublet amounts on the vehicle (some of these may require an in-house invoice).
\nItems such as shop and paint materials, labor online lines, etc. are not included in this total.",
"totalreturns": "The total amount of returns created for this job."
},
+ "prt_dsmk_total": "Line Item Markup",
"rates": "Rates",
"rates_subtotal": "All Rates Subtotal",
"reconciliation": {
@@ -1341,19 +1349,19 @@
"state_tax_amt": "Provincial/State Taxes",
"subletstotal": "Sublets Total",
"subtotal": "Subtotal",
- "supplementnote": "The job had a supplement imported on {{date}} at {{time}}.",
+ "supplementnote": "The job had a supplement imported.",
"suspense": "Suspense",
"total_cost": "Total Cost",
"total_cust_payable": "Total Customer Amount Payable",
"total_repairs": "Total Repairs",
"total_sales": "Total Sales",
"totals": "Totals",
- "unvoidnote": "This job was unvoided by {{email}}.",
+ "unvoidnote": "This job was unvoided.",
"vehicle_info": "Vehicle",
"vehicleassociation": "Vehicle Association",
"viewallocations": "View Allocations",
"voidjob": "Are you sure you want to void this job? This cannot be easily undone. ",
- "voidnote": "This repair order was voided on {{date}} at {{time}}."
+ "voidnote": "This job was voided."
},
"successes": {
"addedtoproduction": "Job added to production board.",
@@ -1664,6 +1672,8 @@
"state": "Province/State"
},
"labels": {
+ "noneselected": "No phone book entry selected. ",
+ "onenamerequired": "At least one name related field is required.",
"vendorcategory": "Vendor"
},
"successes": {
@@ -1815,6 +1825,9 @@
},
"labels": {
"activeshop": "Active Shop"
+ },
+ "successes": {
+ "updated": "Profile updated successfully."
}
},
"reportcenter": {
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index ccbcc56ad..4fae2a134 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -218,6 +218,10 @@
"invoice_federal_tax_rate": "",
"invoice_local_tax_rate": "",
"invoice_state_tax_rate": "",
+ "jc_hourly_rates": {
+ "mapa": "",
+ "mash": ""
+ },
"lastnumberworkingdays": "",
"logo_img_path": "",
"logo_img_path_height": "",
@@ -683,7 +687,8 @@
"deleting_cloudinary": "",
"getpresignurl": "Error al obtener la URL prescrita para el documento. {{message}}",
"insert": "Incapaz de cargar el archivo. {{message}}",
- "nodocuments": "No hay documentos"
+ "nodocuments": "No hay documentos",
+ "updating": ""
},
"labels": {
"confirmdelete": "",
@@ -820,6 +825,8 @@
"monday": "",
"na": "N / A",
"no": "",
+ "nointernet": "",
+ "nointernet_sub": "",
"none": "",
"out": "Afuera",
"password": "",
@@ -1316,6 +1323,7 @@
"partstotal": "",
"totalreturns": ""
},
+ "prt_dsmk_total": "",
"rates": "Tarifas",
"rates_subtotal": "",
"reconciliation": {
@@ -1664,6 +1672,8 @@
"state": ""
},
"labels": {
+ "noneselected": "",
+ "onenamerequired": "",
"vendorcategory": ""
},
"successes": {
@@ -1815,6 +1825,9 @@
},
"labels": {
"activeshop": ""
+ },
+ "successes": {
+ "updated": ""
}
},
"reportcenter": {
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index b34210452..63fde29b6 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -218,6 +218,10 @@
"invoice_federal_tax_rate": "",
"invoice_local_tax_rate": "",
"invoice_state_tax_rate": "",
+ "jc_hourly_rates": {
+ "mapa": "",
+ "mash": ""
+ },
"lastnumberworkingdays": "",
"logo_img_path": "",
"logo_img_path_height": "",
@@ -683,7 +687,8 @@
"deleting_cloudinary": "",
"getpresignurl": "Erreur lors de l'obtention de l'URL présignée pour le document. {{message}}",
"insert": "Incapable de télécharger le fichier. {{message}}",
- "nodocuments": "Il n'y a pas de documents."
+ "nodocuments": "Il n'y a pas de documents.",
+ "updating": ""
},
"labels": {
"confirmdelete": "",
@@ -820,6 +825,8 @@
"monday": "",
"na": "N / A",
"no": "",
+ "nointernet": "",
+ "nointernet_sub": "",
"none": "",
"out": "En dehors",
"password": "",
@@ -1316,6 +1323,7 @@
"partstotal": "",
"totalreturns": ""
},
+ "prt_dsmk_total": "",
"rates": "Les taux",
"rates_subtotal": "",
"reconciliation": {
@@ -1664,6 +1672,8 @@
"state": ""
},
"labels": {
+ "noneselected": "",
+ "onenamerequired": "",
"vendorcategory": ""
},
"successes": {
@@ -1815,6 +1825,9 @@
},
"labels": {
"activeshop": ""
+ },
+ "successes": {
+ "updated": ""
}
},
"reportcenter": {
diff --git a/hasura/migrations/1622147830428_alter_table_public_bodyshops_alter_column_autohouseid/down.yaml b/hasura/migrations/1622147830428_alter_table_public_bodyshops_alter_column_autohouseid/down.yaml
new file mode 100644
index 000000000..6393d9f29
--- /dev/null
+++ b/hasura/migrations/1622147830428_alter_table_public_bodyshops_alter_column_autohouseid/down.yaml
@@ -0,0 +1,6 @@
+- args:
+ cascade: false
+ read_only: false
+ sql: ALTER TABLE "public"."bodyshops" ADD CONSTRAINT "bodyshops_autohouseid_key"
+ UNIQUE ("autohouseid");
+ type: run_sql
diff --git a/hasura/migrations/1622147830428_alter_table_public_bodyshops_alter_column_autohouseid/up.yaml b/hasura/migrations/1622147830428_alter_table_public_bodyshops_alter_column_autohouseid/up.yaml
new file mode 100644
index 000000000..13be53f1d
--- /dev/null
+++ b/hasura/migrations/1622147830428_alter_table_public_bodyshops_alter_column_autohouseid/up.yaml
@@ -0,0 +1,5 @@
+- args:
+ cascade: false
+ read_only: false
+ sql: ALTER TABLE "public"."bodyshops" DROP CONSTRAINT "bodyshops_autohouseid_key";
+ type: run_sql
diff --git a/hasura/migrations/1622147892235_alter_table_public_bodyshops_add_column_jc_hourly_rates/down.yaml b/hasura/migrations/1622147892235_alter_table_public_bodyshops_add_column_jc_hourly_rates/down.yaml
new file mode 100644
index 000000000..40520bed4
--- /dev/null
+++ b/hasura/migrations/1622147892235_alter_table_public_bodyshops_add_column_jc_hourly_rates/down.yaml
@@ -0,0 +1,5 @@
+- args:
+ cascade: false
+ read_only: false
+ sql: ALTER TABLE "public"."bodyshops" DROP COLUMN "jc_hourly_rates";
+ type: run_sql
diff --git a/hasura/migrations/1622147892235_alter_table_public_bodyshops_add_column_jc_hourly_rates/up.yaml b/hasura/migrations/1622147892235_alter_table_public_bodyshops_add_column_jc_hourly_rates/up.yaml
new file mode 100644
index 000000000..b943423ad
--- /dev/null
+++ b/hasura/migrations/1622147892235_alter_table_public_bodyshops_add_column_jc_hourly_rates/up.yaml
@@ -0,0 +1,6 @@
+- args:
+ cascade: false
+ read_only: false
+ sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "jc_hourly_rates" jsonb NULL
+ DEFAULT jsonb_build_object();
+ type: run_sql
diff --git a/hasura/migrations/1622147902848_update_permission_user_public_table_bodyshops/down.yaml b/hasura/migrations/1622147902848_update_permission_user_public_table_bodyshops/down.yaml
new file mode 100644
index 000000000..f17e43eb2
--- /dev/null
+++ b/hasura/migrations/1622147902848_update_permission_user_public_table_bodyshops/down.yaml
@@ -0,0 +1,83 @@
+- args:
+ role: user
+ table:
+ name: bodyshops
+ schema: public
+ type: drop_select_permission
+- args:
+ permission:
+ allow_aggregations: false
+ columns:
+ - accountingconfig
+ - address1
+ - address2
+ - appt_alt_transport
+ - appt_colors
+ - appt_length
+ - bill_tax_rates
+ - city
+ - country
+ - created_at
+ - default_adjustment_rate
+ - deliverchecklist
+ - email
+ - enforce_class
+ - enforce_referral
+ - federal_tax_id
+ - id
+ - imexshopid
+ - inhousevendorid
+ - insurance_vendor_id
+ - intakechecklist
+ - jobsizelimit
+ - logo_img_path
+ - md_categories
+ - md_ccc_rates
+ - md_classes
+ - md_hour_split
+ - md_ins_cos
+ - md_labor_rates
+ - md_messaging_presets
+ - md_notes_presets
+ - md_order_statuses
+ - md_parts_locations
+ - md_payment_types
+ - md_rbac
+ - md_referral_sources
+ - md_responsibility_centers
+ - md_ro_statuses
+ - messagingservicesid
+ - phone
+ - prodtargethrs
+ - production_config
+ - region_config
+ - schedule_end_time
+ - schedule_start_time
+ - scoreboard_target
+ - shopname
+ - shoprates
+ - speedprint
+ - ssbuckets
+ - state
+ - state_tax_id
+ - stripe_acct_id
+ - sub_status
+ - target_touchtime
+ - template_header
+ - textid
+ - updated_at
+ - use_fippa
+ - website
+ - workingdays
+ - zip_post
+ computed_fields: []
+ filter:
+ associations:
+ user:
+ authid:
+ _eq: X-Hasura-User-Id
+ role: user
+ table:
+ name: bodyshops
+ schema: public
+ type: create_select_permission
diff --git a/hasura/migrations/1622147902848_update_permission_user_public_table_bodyshops/up.yaml b/hasura/migrations/1622147902848_update_permission_user_public_table_bodyshops/up.yaml
new file mode 100644
index 000000000..a1885496e
--- /dev/null
+++ b/hasura/migrations/1622147902848_update_permission_user_public_table_bodyshops/up.yaml
@@ -0,0 +1,84 @@
+- args:
+ role: user
+ table:
+ name: bodyshops
+ schema: public
+ type: drop_select_permission
+- args:
+ permission:
+ allow_aggregations: false
+ columns:
+ - accountingconfig
+ - address1
+ - address2
+ - appt_alt_transport
+ - appt_colors
+ - appt_length
+ - bill_tax_rates
+ - city
+ - country
+ - created_at
+ - default_adjustment_rate
+ - deliverchecklist
+ - email
+ - enforce_class
+ - enforce_referral
+ - federal_tax_id
+ - id
+ - imexshopid
+ - inhousevendorid
+ - insurance_vendor_id
+ - intakechecklist
+ - jc_hourly_rates
+ - jobsizelimit
+ - logo_img_path
+ - md_categories
+ - md_ccc_rates
+ - md_classes
+ - md_hour_split
+ - md_ins_cos
+ - md_labor_rates
+ - md_messaging_presets
+ - md_notes_presets
+ - md_order_statuses
+ - md_parts_locations
+ - md_payment_types
+ - md_rbac
+ - md_referral_sources
+ - md_responsibility_centers
+ - md_ro_statuses
+ - messagingservicesid
+ - phone
+ - prodtargethrs
+ - production_config
+ - region_config
+ - schedule_end_time
+ - schedule_start_time
+ - scoreboard_target
+ - shopname
+ - shoprates
+ - speedprint
+ - ssbuckets
+ - state
+ - state_tax_id
+ - stripe_acct_id
+ - sub_status
+ - target_touchtime
+ - template_header
+ - textid
+ - updated_at
+ - use_fippa
+ - website
+ - workingdays
+ - zip_post
+ computed_fields: []
+ filter:
+ associations:
+ user:
+ authid:
+ _eq: X-Hasura-User-Id
+ role: user
+ table:
+ name: bodyshops
+ schema: public
+ type: create_select_permission
diff --git a/hasura/migrations/1622147914381_update_permission_user_public_table_bodyshops/down.yaml b/hasura/migrations/1622147914381_update_permission_user_public_table_bodyshops/down.yaml
new file mode 100644
index 000000000..cf88f05a1
--- /dev/null
+++ b/hasura/migrations/1622147914381_update_permission_user_public_table_bodyshops/down.yaml
@@ -0,0 +1,77 @@
+- args:
+ role: user
+ table:
+ name: bodyshops
+ schema: public
+ type: drop_update_permission
+- args:
+ permission:
+ columns:
+ - accountingconfig
+ - address1
+ - address2
+ - appt_alt_transport
+ - appt_colors
+ - appt_length
+ - bill_tax_rates
+ - city
+ - country
+ - created_at
+ - default_adjustment_rate
+ - deliverchecklist
+ - email
+ - enforce_class
+ - enforce_referral
+ - federal_tax_id
+ - id
+ - inhousevendorid
+ - insurance_vendor_id
+ - intakechecklist
+ - logo_img_path
+ - md_categories
+ - md_ccc_rates
+ - md_classes
+ - md_hour_split
+ - md_ins_cos
+ - md_labor_rates
+ - md_messaging_presets
+ - md_notes_presets
+ - md_order_statuses
+ - md_parts_locations
+ - md_payment_types
+ - md_rbac
+ - md_referral_sources
+ - md_responsibility_centers
+ - md_ro_statuses
+ - phone
+ - prodtargethrs
+ - production_config
+ - schedule_end_time
+ - schedule_start_time
+ - scoreboard_target
+ - shopname
+ - shoprates
+ - speedprint
+ - ssbuckets
+ - state
+ - state_tax_id
+ - target_touchtime
+ - updated_at
+ - use_fippa
+ - website
+ - workingdays
+ - zip_post
+ filter:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ set: {}
+ role: user
+ table:
+ name: bodyshops
+ schema: public
+ type: create_update_permission
diff --git a/hasura/migrations/1622147914381_update_permission_user_public_table_bodyshops/up.yaml b/hasura/migrations/1622147914381_update_permission_user_public_table_bodyshops/up.yaml
new file mode 100644
index 000000000..e8583e4ca
--- /dev/null
+++ b/hasura/migrations/1622147914381_update_permission_user_public_table_bodyshops/up.yaml
@@ -0,0 +1,78 @@
+- args:
+ role: user
+ table:
+ name: bodyshops
+ schema: public
+ type: drop_update_permission
+- args:
+ permission:
+ columns:
+ - accountingconfig
+ - address1
+ - address2
+ - appt_alt_transport
+ - appt_colors
+ - appt_length
+ - bill_tax_rates
+ - city
+ - country
+ - created_at
+ - default_adjustment_rate
+ - deliverchecklist
+ - email
+ - enforce_class
+ - enforce_referral
+ - federal_tax_id
+ - id
+ - inhousevendorid
+ - insurance_vendor_id
+ - intakechecklist
+ - jc_hourly_rates
+ - logo_img_path
+ - md_categories
+ - md_ccc_rates
+ - md_classes
+ - md_hour_split
+ - md_ins_cos
+ - md_labor_rates
+ - md_messaging_presets
+ - md_notes_presets
+ - md_order_statuses
+ - md_parts_locations
+ - md_payment_types
+ - md_rbac
+ - md_referral_sources
+ - md_responsibility_centers
+ - md_ro_statuses
+ - phone
+ - prodtargethrs
+ - production_config
+ - schedule_end_time
+ - schedule_start_time
+ - scoreboard_target
+ - shopname
+ - shoprates
+ - speedprint
+ - ssbuckets
+ - state
+ - state_tax_id
+ - target_touchtime
+ - updated_at
+ - use_fippa
+ - website
+ - workingdays
+ - zip_post
+ filter:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ set: {}
+ role: user
+ table:
+ name: bodyshops
+ schema: public
+ type: create_update_permission
diff --git a/hasura/migrations/metadata.yaml b/hasura/migrations/metadata.yaml
index 3aef96e54..02291ea5a 100644
--- a/hasura/migrations/metadata.yaml
+++ b/hasura/migrations/metadata.yaml
@@ -771,6 +771,7 @@ tables:
- inhousevendorid
- insurance_vendor_id
- intakechecklist
+ - jc_hourly_rates
- jobsizelimit
- logo_img_path
- md_categories
@@ -841,6 +842,7 @@ tables:
- inhousevendorid
- insurance_vendor_id
- intakechecklist
+ - jc_hourly_rates
- logo_img_path
- md_categories
- md_ccc_rates
diff --git a/server/accounting/qbxml/qbxml-receivables.js b/server/accounting/qbxml/qbxml-receivables.js
index ac24f4408..49f67f397 100644
--- a/server/accounting/qbxml/qbxml-receivables.js
+++ b/server/accounting/qbxml/qbxml-receivables.js
@@ -203,7 +203,8 @@ const generateInvoiceQbxml = (
//Create the invoice lines mapping.
jobs_by_pk.joblines.map((jobline) => {
//Parts Lines
- if (jobline.db_ref === "936008") { //If either of these DB REFs change, they also need to change in job-totals calculations.
+ if (jobline.db_ref === "936008") {
+ //If either of these DB REFs change, they also need to change in job-totals calculations.
hasMapaLine = true;
}
if (jobline.db_ref === "936007") {
@@ -213,7 +214,15 @@ const generateInvoiceQbxml = (
if (jobline.profitcenter_part && jobline.act_price) {
const DineroAmount = Dinero({
amount: Math.round(jobline.act_price * 100),
- }).multiply(jobline.part_qty || 1);
+ })
+ .multiply(jobline.part_qty || 1)
+ .add(
+ Dinero({
+ amount: Math.round((jobline.act_price || 0) * 100),
+ })
+ .multiply(jobline.part_qty || 0)
+ .percentage(jobline.prt_dsmk_p)
+ );
const account = responsibilityCenters.profits.find(
(i) => jobline.profitcenter_part.toLowerCase() === i.name.toLowerCase()
);
diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js
index c9d7f3173..9b7b3677c 100644
--- a/server/graphql-client/queries.js
+++ b/server/graphql-client/queries.js
@@ -566,6 +566,7 @@ exports.GET_JOB_BY_PK = ` query GET_JOB_BY_PK($id: uuid!) {
tax_part
db_ref
manual_line
+ prt_dsmk_p
parts_order_lines {
id
parts_order {
@@ -679,6 +680,7 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
bodyshop{
id
md_responsibility_centers
+ jc_hourly_rates
}
}
}`;
@@ -779,6 +781,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
bodyshop {
id
md_responsibility_centers
+ jc_hourly_rates
}
}
}
diff --git a/server/job/job-costing.js b/server/job/job-costing.js
index 7a15b4f29..24d1d6382 100644
--- a/server/job/job-costing.js
+++ b/server/job/job-costing.js
@@ -10,8 +10,7 @@ Dinero.globalRoundingMode = "HALF_EVEN";
async function JobCosting(req, res) {
const { jobid } = req.body;
- console.log("🚀 ~ file: job-costing.js ~ line 13 ~ jobid", jobid);
- console.time("querydata");
+ console.time("Query for Data");
const BearerToken = req.headers.authorization;
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
@@ -122,27 +121,34 @@ async function JobCostingMulti(req, res) {
});
//Add all summary data.
- multiSummary.summaryData.totalPartsSales = multiSummary.summaryData.totalPartsSales.add(
- costingData.summaryData.totalPartsSales
- );
- multiSummary.summaryData.totalSales = multiSummary.summaryData.totalSales.add(
- costingData.summaryData.totalSales
- );
- multiSummary.summaryData.totalLaborCost = multiSummary.summaryData.totalLaborCost.add(
- costingData.summaryData.totalLaborCost
- );
- multiSummary.summaryData.totalLaborSales = multiSummary.summaryData.totalLaborSales.add(
- costingData.summaryData.totalLaborSales
- );
- multiSummary.summaryData.totalPartsCost = multiSummary.summaryData.totalPartsCost.add(
- costingData.summaryData.totalPartsCost
- );
- multiSummary.summaryData.totalCost = multiSummary.summaryData.totalCost.add(
- costingData.summaryData.totalCost
- );
- multiSummary.summaryData.gpdollars = multiSummary.summaryData.gpdollars.add(
- costingData.summaryData.gpdollars
- );
+ multiSummary.summaryData.totalPartsSales =
+ multiSummary.summaryData.totalPartsSales.add(
+ costingData.summaryData.totalPartsSales
+ );
+ multiSummary.summaryData.totalSales =
+ multiSummary.summaryData.totalSales.add(
+ costingData.summaryData.totalSales
+ );
+ multiSummary.summaryData.totalLaborCost =
+ multiSummary.summaryData.totalLaborCost.add(
+ costingData.summaryData.totalLaborCost
+ );
+ multiSummary.summaryData.totalLaborSales =
+ multiSummary.summaryData.totalLaborSales.add(
+ costingData.summaryData.totalLaborSales
+ );
+ multiSummary.summaryData.totalPartsCost =
+ multiSummary.summaryData.totalPartsCost.add(
+ costingData.summaryData.totalPartsCost
+ );
+ multiSummary.summaryData.totalCost =
+ multiSummary.summaryData.totalCost.add(
+ costingData.summaryData.totalCost
+ );
+ multiSummary.summaryData.gpdollars =
+ multiSummary.summaryData.gpdollars.add(
+ costingData.summaryData.gpdollars
+ );
console.timeEnd(`SummaryOfCostingData-${job.id}`);
//Take the summary data & add it to total summary data.
});
@@ -202,6 +208,8 @@ function GenerateCostingData(job) {
job.bodyshop.md_responsibility_centers.costs.map((p) => p.name)
);
+ const materialsHours = { mapaHrs: 0, mashHrs: 0 };
+
//Massage the data.
const jobLineTotalsByProfitCenter =
job &&
@@ -220,14 +228,14 @@ function GenerateCostingData(job) {
}).multiply(val.mod_lb_hrs || 0);
if (!acc.labor[laborProfitCenter])
acc.labor[laborProfitCenter] = Dinero();
- acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(
- laborAmount
- );
+ acc.labor[laborProfitCenter] =
+ acc.labor[laborProfitCenter].add(laborAmount);
if (val.mod_lbr_ty === "LAR") {
if (!acc.labor[defaultProfits["MAPA"]])
acc.labor[defaultProfits["MAPA"]] = Dinero();
+ materialsHours.mapaHrs += val.mod_lb_hrs || 0;
acc.labor[defaultProfits["MAPA"]] = acc.labor[
defaultProfits["MAPA"]
].add(
@@ -247,6 +255,7 @@ function GenerateCostingData(job) {
amount: Math.round((job.rate_mash || 0) * 100),
}).multiply(val.mod_lb_hrs || 0)
);
+ materialsHours.mashHrs += val.mod_lb_hrs || 0;
}
//If labor line, add to paint and shop materials.
}
@@ -265,12 +274,19 @@ function GenerateCostingData(job) {
);
const partsAmount = Dinero({
amount: Math.round((val.act_price || 0) * 100),
- }).multiply(val.part_qty || 1);
+ })
+ .multiply(val.part_qty || 1)
+ .add(
+ Dinero({
+ amount: Math.round((val.act_price || 0) * 100),
+ })
+ .multiply(val.part_qty || 0)
+ .percentage(val.prt_dsmk_p)
+ );
if (!acc.parts[partsProfitCenter])
acc.parts[partsProfitCenter] = Dinero();
- acc.parts[partsProfitCenter] = acc.parts[partsProfitCenter].add(
- partsAmount
- );
+ acc.parts[partsProfitCenter] =
+ acc.parts[partsProfitCenter].add(partsAmount);
}
//To deal with additional costs.
@@ -287,18 +303,20 @@ function GenerateCostingData(job) {
} else {
const partsAmount = Dinero({
amount: Math.round((val.act_price || 0) * 100),
- }).multiply(val.part_qty || 1);
- console.log(
- `*** partsAmount`,
- val.line_desc,
- partsProfitCenter,
- partsAmount.toJSON()
- );
+ })
+ .multiply(val.part_qty || 1)
+ .add(
+ Dinero({
+ amount: Math.round((val.act_price || 0) * 100),
+ })
+ .multiply(val.part_qty || 0)
+ .percentage(val.prt_dsmk_p)
+ );
+
if (!acc.parts[partsProfitCenter])
acc.parts[partsProfitCenter] = Dinero();
- acc.parts[partsProfitCenter] = acc.parts[partsProfitCenter].add(
- partsAmount
- );
+ acc.parts[partsProfitCenter] =
+ acc.parts[partsProfitCenter].add(partsAmount);
}
}
@@ -328,6 +346,52 @@ function GenerateCostingData(job) {
return bill_acc;
}, {});
+ //If the hourly rates for job costing are set, add them in.
+ if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa) {
+ if (
+ !billTotalsByCostCenters[
+ job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
+ ]
+ )
+ billTotalsByCostCenters[
+ job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
+ ] = Dinero();
+ billTotalsByCostCenters[
+ job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
+ ] = billTotalsByCostCenters[
+ job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
+ ].add(
+ Dinero({
+ amount:
+ (job.bodyshop.jc_hourly_rates &&
+ job.bodyshop.jc_hourly_rates.mapa * 100) ||
+ 0,
+ }).multiply(materialsHours.mapaHrs)
+ );
+ }
+ if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) {
+ if (
+ !billTotalsByCostCenters[
+ job.bodyshop.md_responsibility_centers.defaults.costs.MASH
+ ]
+ )
+ billTotalsByCostCenters[
+ job.bodyshop.md_responsibility_centers.defaults.costs.MASH
+ ] = Dinero();
+ billTotalsByCostCenters[
+ job.bodyshop.md_responsibility_centers.defaults.costs.MASH
+ ] = billTotalsByCostCenters[
+ job.bodyshop.md_responsibility_centers.defaults.costs.MASH
+ ].add(
+ Dinero({
+ amount:
+ (job.bodyshop.jc_hourly_rates &&
+ job.bodyshop.jc_hourly_rates.mash * 100) ||
+ 0,
+ }).multiply(materialsHours.mashHrs)
+ );
+ }
+
const ticketTotalsByCostCenter = job.timetickets.reduce(
(ticket_acc, ticket_val) => {
//At the invoice level.
diff --git a/server/job/job-totals.js b/server/job/job-totals.js
index b0798c62d..dea522a89 100644
--- a/server/job/job-totals.js
+++ b/server/job/job-totals.js
@@ -86,6 +86,7 @@ async function Totals(req, res) {
res.status(400).send(JSON.stringify(error));
}
}
+
function CalculateRatesTotals(ratesList) {
const jobLines = ratesList.joblines.filter((jl) => !jl.removed);
@@ -211,6 +212,13 @@ function CalculatePartsTotals(jobLines) {
...acc,
parts: {
...acc.parts,
+ prt_dsmk_total: acc.parts.prt_dsmk_total.add(
+ Dinero({
+ amount: Math.round((value.act_price || 0) * 100),
+ })
+ .multiply(value.part_qty || 0)
+ .percentage(value.prt_dsmk_p)
+ ),
list: {
...acc.parts.list,
[value.part_type]:
@@ -229,11 +237,19 @@ function CalculatePartsTotals(jobLines) {
}).multiply(value.part_qty || 0),
},
},
- subtotal: acc.parts.subtotal.add(
- Dinero({
- amount: Math.round(value.act_price * 100),
- }).multiply(value.part_qty || 0)
- ),
+ subtotal: acc.parts.subtotal
+ .add(
+ Dinero({
+ amount: Math.round(value.act_price * 100),
+ }).multiply(value.part_qty || 0)
+ )
+ .add(
+ Dinero({
+ amount: Math.round((value.act_price || 0) * 100),
+ })
+ .multiply(value.part_qty || 0)
+ .percentage(value.prt_dsmk_p)
+ ),
},
};
}
@@ -241,6 +257,7 @@ function CalculatePartsTotals(jobLines) {
{
parts: {
list: {},
+ prt_dsmk_total: Dinero(),
subtotal: Dinero({ amount: 0 }),
total: Dinero({ amount: 0 }),
},
@@ -360,6 +377,13 @@ function CalculateTaxesTotals(job, otherTotals) {
statePartsTax = statePartsTax.add(
Dinero({ amount: Math.round((val.act_price || 0) * 100) })
.multiply(val.part_qty || 1)
+ .add(
+ Dinero({
+ amount: Math.round((val.act_price || 0) * 100),
+ })
+ .multiply(val.part_qty || 0)
+ .percentage(val.prt_dsmk_p)
+ )
.percentage(
((job.parts_tax_rates &&
job.parts_tax_rates[val.part_type] &&
diff --git a/server/media/media.js b/server/media/media.js
index 9aa60663e..e02beeac5 100644
--- a/server/media/media.js
+++ b/server/media/media.js
@@ -40,7 +40,9 @@ exports.renameKeys = async (req, res) => {
try {
const res = {
id: d.id,
- ...(await cloudinary.uploader.rename(d.from, d.to)),
+ ...(await cloudinary.uploader.rename(d.from, d.to, {
+ resource_type: DetermineFileType(d.type),
+ })),
};
return res;
} catch (error) {
@@ -56,3 +58,14 @@ exports.renameKeys = async (req, res) => {
res.send(result);
};
+
+//Also needs to be updated in upload utility and mobile app.
+function DetermineFileType(filetype) {
+ if (!filetype) return "auto";
+ else if (filetype.startsWith("image")) return "image";
+ else if (filetype.startsWith("video")) return "video";
+ else if (filetype.startsWith("application/pdf")) return "image";
+ else if (filetype.startsWith("application")) return "raw";
+
+ return "auto";
+}