Extensions to job totals calculation.

This commit is contained in:
Patrick Fic
2021-03-03 17:25:54 -08:00
parent 4ee71c69d8
commit 5be34a445d
11 changed files with 364 additions and 66 deletions

View File

@@ -1,12 +1,21 @@
{
"env": {
"browser": false,
"commonjs": true,
"es2021": true
env: {
es6: true,
node: true,
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 12
extends: "eslint:recommended",
globals: {
Atomics: "readonly",
SharedArrayBuffer: "readonly",
},
"rules": {}
}
parserOptions: {
ecmaVersion: 2018,
sourceType: "module",
},
// plugins: [],
rules: {
"no-console": "off"
},
settings: {},
};

View File

@@ -14754,6 +14754,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>totalscalc</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>updating</name>
<definition_loaded>false</definition_loaded>

View File

@@ -4,7 +4,7 @@ import React, { useState } from "react";
import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import Dinero from "dinero.js";
export default function JobCalculateTotals({ job, disabled }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
@@ -25,6 +25,10 @@ export default function JobCalculateTotals({ job, disabled }) {
jobId: job.id,
job: {
job_totals: newTotals,
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat(
"0.00"
),
},
},
});

View File

@@ -1,4 +1,5 @@
import {
gql,
useApolloClient,
useLazyQuery,
useMutation,
@@ -7,9 +8,10 @@ import {
import { notification } from "antd";
import Axios from "axios";
import Dinero from "dinero.js";
import { gql } from "@apollo/client";
import _ from "lodash";
import React, { useState, useEffect, useCallback } from "react";
import moment from "moment";
import queryString from "query-string";
import React, { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
@@ -21,6 +23,7 @@ import {
QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK,
} from "../../graphql/available-jobs.queries";
import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries";
import { INSERT_NEW_NOTE } from "../../graphql/notes.queries";
import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries";
import {
selectBodyshop,
@@ -33,9 +36,6 @@ import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.contai
import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util";
import HeaderFields from "./jobs-available-supplement.headerfields";
import JobsAvailableTableComponent from "./jobs-available-table.component";
import moment from "moment";
import { INSERT_NEW_NOTE } from "../../graphql/notes.queries";
import queryString from "query-string";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -71,6 +71,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
const importOptions = importOptionsState[0];
const modalSearchState = useState("");
//Import Scenario
const onOwnerFindModalOk = async () => {
logImEXEvent("job_import_new");
@@ -171,6 +172,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
});
};
//Suplement scenario
const onJobFindModalOk = async () => {
logImEXEvent("job_import_supplement");
@@ -199,15 +201,6 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
HeaderFields.forEach((item) => delete supp[item]);
}
const newTotals = (
await Axios.post("/job/totals", {
job: {
...estData.data.available_jobs_by_pk.est_data,
joblines: estData.data.available_jobs_by_pk.est_data.joblines.data,
},
})
).data;
let suppDelta = await GetSupplementDelta(
client,
selectedJob,
@@ -220,46 +213,65 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
${suppDelta}
`,
});
updateJob({
const updateResult = await updateJob({
variables: {
jobId: selectedJob,
job: {
...supp,
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat(
"0.00"
),
job_totals: newTotals,
queued_for_parts: true,
// clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
// owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat(
// "0.00"
// ),
// job_totals: newTotals,
// queued_for_parts: true,
},
},
})
.then((r) => {
notification["success"]({
message: t("jobs.successes.supplemented"),
onClick: () => {
history.push(
`/manage/jobs/${r.data.update_jobs.returning[0].id}`
);
},
});
//Job has been inserted. Clean up the available jobs record.
});
deleteJob({
variables: { id: estData.data.available_jobs_by_pk.id },
}).then((r) => {
refetch();
setInsertLoading(false);
});
})
.catch((r) => {
//error while inserting
notification["error"]({
message: t("jobs.errors.creating", { error: r.message }),
});
refetch();
setInsertLoading(false);
if (updateResult.errors) {
//error while inserting
notification["error"]({
message: t("jobs.errors.creating", {
error: JSON.stringify(updateResult.errors),
}),
});
refetch();
setInsertLoading(false);
return;
}
const newTotals = await Axios.post("/job/totalsssu", {
id: selectedJob,
});
console.log(
"🚀 ~ file: jobs-available-table.container.jsx ~ line 247 ~ newTotals",
newTotals
);
if (newTotals.status !== 200) {
notification["error"]({
message: t("jobs.errors.totalscalc"),
});
setInsertLoading(false);
return;
}
notification["success"]({
message: t("jobs.successes.supplemented"),
onClick: () => {
history.push(
`/manage/jobs/${updateResult.data.update_jobs.returning[0].id}`
);
},
});
//Job has been inserted. Clean up the available jobs record.
deleteJob({
variables: { id: estData.data.available_jobs_by_pk.id },
}).then((r) => {
refetch();
setInsertLoading(false);
});
await insertNote({
variables: {

View File

@@ -936,6 +936,7 @@
"noowner": "No owner associated.",
"novehicle": "No vehicle associated.",
"saving": "Error encountered while saving record.",
"totalscalc": "Error while calculating new job totals.",
"updating": "Error while updating job(s). {{error}}",
"validation": "Please ensure all fields are entered correctly.",
"validationtitle": "Validation Error",

View File

@@ -936,6 +936,7 @@
"noowner": "Ningún propietario asociado.",
"novehicle": "No hay vehículo asociado.",
"saving": "Se encontró un error al guardar el registro.",
"totalscalc": "",
"updating": "",
"validation": "Asegúrese de que todos los campos se ingresen correctamente.",
"validationtitle": "Error de validacion",

View File

@@ -936,6 +936,7 @@
"noowner": "Aucun propriétaire associé.",
"novehicle": "Aucun véhicule associé.",
"saving": "Erreur rencontrée lors de la sauvegarde de l'enregistrement.",
"totalscalc": "",
"updating": "",
"validation": "Veuillez vous assurer que tous les champs sont correctement entrés.",
"validationtitle": "Erreur de validation",

View File

@@ -16,9 +16,6 @@ require("dotenv").config({
),
});
const https = require("https");
const fs = require("fs");
const app = express();
const port = process.env.PORT || 5000;
//const port = 5000;
@@ -96,6 +93,7 @@ app.post(
var job = require("./server/job/job");
app.post("/job/totals", fb.validateFirebaseIdToken, job.totals);
app.post("/job/totalsssu", fb.validateFirebaseIdToken, job.totalsSsu);
//Scheduling
var scheduling = require("./server/scheduling/scheduling-job");

View File

@@ -382,3 +382,190 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) {
}
}
`;
exports.UPDATE_JOB = `
mutation UPDATE_JOB($jobId: uuid!, $job: jobs_set_input!) {
update_jobs(where: { id: { _eq: $jobId } }, _set: $job) {
returning {
id
date_exported
status
alt_transport
ro_number
production_vars
lbr_adjustments
}
}
}
`;
exports.GET_JOB_BY_PK = ` query GET_JOB_BY_PK($id: uuid!) {
jobs_by_pk(id: $id) {
updated_at
alt_transport
intakechecklist
csr
loss_desc
kmin
kmout
referral_source
unit_number
po_number
special_coverage_policy
scheduled_delivery
converted
lbr_adjustments
ro_number
clm_total
inproduction
vehicleid
plate_no
v_vin
v_model_yr
v_model_desc
v_make_desc
v_color
vehicleid
driveable
towin
ins_co_id
policy_no
loss_date
clm_no
area_of_damage
ins_co_nm
ins_addr1
ins_city
ins_ct_ln
ins_ct_fn
ins_ea
ins_ph1
est_co_nm
est_ct_fn
est_ct_ln
est_ph1
est_ea
selling_dealer
servicing_dealer
selling_dealer_contact
servicing_dealer_contact
regie_number
scheduled_completion
id
ded_amt
ded_status
depreciation_taxes
other_amount_payable
towing_payable
storage_payable
adjustment_bottom_line
federal_tax_rate
state_tax_rate
local_tax_rate
tax_tow_rt
tax_str_rt
tax_paint_mat_rt
tax_shop_mat_rt
tax_sub_rt
tax_lbr_rt
tax_levies_rt
parts_tax_rates
job_totals
ownr_fn
ownr_ln
ownr_ea
ownr_addr1
ownr_addr2
ownr_city
ownr_st
ownr_zip
ownr_ctry
ownr_ph1
production_vars
ca_gst_registrant
labor_rate_desc
rate_la1
rate_la2
rate_la3
rate_la4
rate_laa
rate_lab
rate_lad
rate_lae
rate_laf
rate_lag
rate_lam
rate_lar
rate_las
rate_lau
rate_ma2s
rate_ma2t
rate_ma3s
rate_mabl
rate_macs
rate_mahw
rate_mapa
rate_mash
rate_matd
actual_in
federal_tax_rate
local_tax_rate
state_tax_rate
scheduled_completion
scheduled_in
actual_completion
scheduled_delivery
actual_delivery
date_estimated
date_open
date_scheduled
date_invoiced
date_exported
status
owner_owing
tax_registration_number
class
category
deliverchecklist
voided
ca_bc_pvrt
joblines{
id
line_no
unq_seq
line_ind
line_desc
part_type
oem_partno
db_price
act_price
part_qty
mod_lbr_ty
db_hrs
mod_lb_hrs
lbr_op
lbr_amt
op_code_desc
status
notes
location
tax_part
db_ref
manual_line
parts_order_lines {
id
parts_order {
id
order_number
order_date
user_email
vendor {
id
name
}
}
}
}
}
}`;

View File

@@ -1,10 +1,75 @@
const Dinero = require("dinero.js");
const queries = require("../graphql-client/queries");
const GraphQLClient = require("graphql-request").GraphQLClient;
Dinero.defaultCurrency = "USD";
Dinero.globalLocale = "en-CA";
Dinero.globalRoundingMode = "HALF_UP";
exports.default = async function (req, res) {
exports.totalsSsu = async function (req, res) {
const BearerToken = req.headers.authorization;
const { id } = req.body;
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
Authorization: BearerToken,
},
});
try {
const job = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.GET_JOB_BY_PK, {
id: id,
});
const newTotals = await TotalsServerSide(
{ body: { job: job.jobs_by_pk } },
res,
true
);
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.UPDATE_JOB, {
jobId: id,
job: {
clm_total: newTotals.totals.total_repairs.toFormat("0.00"),
owner_owing: newTotals.totals.custPayable.total.toFormat("0.00"),
job_totals: newTotals,
queued_for_parts: true,
},
});
res.status(200);
} catch (error) {
console.log(error);
res.status(503);
}
};
//IMPORTANT*** These two functions MUST be mirrrored.
async function TotalsServerSide(req, res) {
const { job } = req.body;
console.log(
`Calculating Job Totals on the server side for ${job.id} - ${job.ro_number}`
);
try {
let ret = {
parts: CalculatePartsTotals(job.joblines),
rates: CalculateRatesTotals(job),
additional: CalculateAdditional(job),
};
ret.totals = CalculateTaxesTotals(job, ret);
return ret;
} catch (error) {
console.log("error", error);
res.status(400).send(JSON.stringify(error));
}
}
async function Totals(req, res) {
const { job } = req.body;
console.log(`Calculating Job Totals for ${job.id} - ${job.ro_number}`);
try {
@@ -14,12 +79,13 @@ exports.default = async function (req, res) {
additional: CalculateAdditional(job),
};
ret.totals = CalculateTaxesTotals(job, ret);
res.status(200).json(ret);
} catch (error) {
console.log("error", error);
res.status(400).send(JSON.stringify(error));
}
};
}
function CalculateRatesTotals(ratesList, shoprates) {
const jobLines = ratesList.joblines.filter((jl) => !jl.removed);
@@ -237,11 +303,6 @@ function CalculateAdditional(job) {
}
function CalculateTaxesTotals(job, otherTotals) {
console.log(
"🚀 ~ file: job-totals.js ~ line 240 ~ otherTotals",
JSON.stringify(otherTotals)
);
const subtotal = otherTotals.parts.parts.subtotal
.add(otherTotals.parts.sublets.subtotal)
.add(otherTotals.rates.rates_subtotal)
@@ -327,3 +388,5 @@ function CalculateTaxesTotals(job, otherTotals) {
return ret;
}
exports.default = Totals;

View File

@@ -1 +1,2 @@
exports.totals = require("./job-totals").default;
exports.totalsSsu = require("./job-totals").totalsSsu;