Resolved calculations for profile markups and discounts.

This commit is contained in:
Patrick Fic
2023-10-06 07:57:26 -07:00
parent 4bd139f93b
commit ef146032df
5 changed files with 328 additions and 377 deletions

View File

@@ -43,10 +43,10 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
export const socket = SocketIO(
process.env.NODE_ENV === "production"
? process.env.REACT_APP_AXIOS_BASE_API_URL
: window.location.origin,
// "http://localhost:4000", // for dev testing,
// process.env.NODE_ENV === "production"
// ? process.env.REACT_APP_AXIOS_BASE_API_URL
// : window.location.origin,
"http://localhost:4000", // for dev testing,
{
path: "/ws",
withCredentials: true,

View File

@@ -20,7 +20,7 @@ require("dotenv").config({
async function RunTheTest() {
const bodyshopids = ["a7ee1503-ee05-4a02-b80e-bdb11d1cc8ac"];
const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImFkNWM1ZTlmNTdjOWI2NDYzYzg1ODQ1YTA4OTlhOWQ0MTI5MmM4YzMiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUm9tZSBEZXZlbG9wbWVudCIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciJdLCJ4LWhhc3VyYS11c2VyLWlkIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiJ9LCJpb2FkbWluIjp0cnVlLCJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vcm9tZS1wcm9kLTEiLCJhdWQiOiJyb21lLXByb2QtMSIsImF1dGhfdGltZSI6MTY5NTE1MDU0NywidXNlcl9pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIiLCJzdWIiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwiaWF0IjoxNjk1ODUzNTI5LCJleHAiOjE2OTU4NTcxMjksImVtYWlsIjoicGF0cmlja0Byb21lLmRldiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJwYXRyaWNrQHJvbWUuZGV2Il19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.cLDqjTy7dyo3MYwRuWaDxFz0faqDqK5elCFy78qgr4IMhWQEZKdO1FIJoBmxuGn7qbJUfgSRettkYx5YcY3AzrzlTu7UbL6yz7yMyCUjIOF189OLhN-IZH8sHbyb4xvpP4GvYLkaEoBjTCvMaSW-9ycpM3uvYbjgCO81p2gGjo56E2TGoT8tfWE-NGO2nGv_-UacTrZWh_8CGijeZrC9QXeY3DSXTykRV1_xWA7UQNi8IeKphgXsVkOsQI6xC5fXCBWThOfx2RN5af36fU-b3aVXCq21M5y3tJg1IZbzlcYyyBw8Gc71wfp5bjKU92EPH8yCnbic7B5c5Yzk2ikWZg`;
const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjlhNTE5MDc0NmU5M2JhZTI0OWIyYWE3YzJhYTRlMzA2M2UzNDFlYzciLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUm9tZSBEZXZlbG9wbWVudCIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciJdLCJ4LWhhc3VyYS11c2VyLWlkIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiJ9LCJpb2FkbWluIjp0cnVlLCJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vcm9tZS1wcm9kLTEiLCJhdWQiOiJyb21lLXByb2QtMSIsImF1dGhfdGltZSI6MTY5NTkxNDQ5NywidXNlcl9pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIiLCJzdWIiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwiaWF0IjoxNjk2NTQzMzgxLCJleHAiOjE2OTY1NDY5ODEsImVtYWlsIjoicGF0cmlja0Byb21lLmRldiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJwYXRyaWNrQHJvbWUuZGV2Il19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.nLjEuD_KOTbO2iEoTuweulEtQeZeu5NC7uG0QyaleSxrJ1AXt7r-qT6TcECfXxSpZunzdkl4Tiz6wXqSEcOEPOOv1TZBSCNogTNOF_LCJCn6e8jHWE1ry5m4qQ4wUb_DELauFdZcmNJBHShwcGbMMbApjp6YZ9g7aJNYpP3LtRXi_zBHX_Pmf2sB9RuMyDOSfHGBlt-g-5c6TtOeRXWLY92MOfV_X-1bqKV-honnpZwi3Ht_g6z3nUY6p2VQsD2oy7jjuRPis3P9E_rym5UVIIpF5zEiLb3RaOnHcI_gmX6LFXx5roLIwBjwALQfeE5iRakeylxkgIeuwcjCiRJbhA`;
const { jobs } = await client.request(
gql`
query GET_JOBS($bodyshopids: [uuid!]!) {
@@ -44,7 +44,11 @@ async function RunTheTest() {
for (const [index, job] of jobs.entries()) {
process.stdout.cursorTo(0);
process.stdout.write(`Processing job ${index + 1} of ${jobs.length}`);
process.stdout.write(
`Processing job ${index + 1} of ${jobs.length}. Failed jobs: ${
results.filter((r) => r.result !== "PASS").length
}`
);
try {
await axios.post(

View File

@@ -420,6 +420,7 @@ exports.default = function ({
Amount: Dinero(jobs_by_pk.job_totals.additional.towing).toFormat(
DineroQbFormat
),
SalesItemLineDetail: {
...(jobs_by_pk.class
? { ClassRef: { value: classes[jobs_by_pk.class] } }
@@ -573,6 +574,68 @@ exports.default = function ({
//Add tax lines
const job_totals = jobs_by_pk.job_totals;
//Handle insurance profile adjustments
Object.keys(job_totals.parts.adjustments).forEach((key) => {
if (qbo) {
//Going to always assume that we need to apply GST and PST for labor.
const taxAccountCode = findTaxCode(
{
local: false,
federal: process.env.COUNTRY === "USA" ? false : true,
state: jobs_by_pk.state_tax_rate === 0 ? false : true,
},
bodyshop.md_responsibility_centers.sales_tax_codes
);
const account = responsibilityCenters.profits.find(
(c) => c.name === responsibilityCenters.defaults.profits[key]
);
const QboTaxId =
process.env.COUNTRY === "USA"
? CheckQBOUSATaxID({
// jobline: jobline,
job: jobs_by_pk,
type: "storage",
})
: taxCodes[taxAccountCode];
InvoiceLineAdd.push({
DetailType: "SalesItemLineDetail",
Amount: Dinero(job_totals.parts.adjustments[key]).toFormat(
DineroQbFormat
),
Description: `${account.accountdesc} - Adjustment`,
SalesItemLineDetail: {
...(jobs_by_pk.class
? { ClassRef: { value: classes[jobs_by_pk.class] } }
: {}),
ItemRef: {
value: items[account.accountitem],
},
TaxCodeRef: {
value: QboTaxId,
},
Qty: 1,
},
});
} else {
InvoiceLineAdd.push({
ItemRef: {
FullName: responsibilityCenters.profits.find(
(c) => c.name === responsibilityCenters.defaults.profits[key]
).accountitem,
},
Desc: "Storage",
Quantity: 1,
Amount: Dinero(job_totals.parts.adjustments[key]).toFormat(
DineroQbFormat
),
SalesTaxCodeRef: {
FullName:
bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON",
},
});
}
});
const federal_tax = Dinero(job_totals.totals.federal_tax);
const QboTaxId =
process.env.COUNTRY === "USA"

View File

@@ -25,58 +25,37 @@ exports.default = async function (socket, jobid) {
const { bodyshop } = job;
const taxAllocations = {
// local: {
// center: bodyshop.md_responsibility_centers.taxes.local.name,
// sale: Dinero(job.job_totals.totals.local_tax),
// cost: Dinero(),
// profitCenter: bodyshop.md_responsibility_centers.taxes.local,
// costCenter: bodyshop.md_responsibility_centers.taxes.local,
// },
// state: {
// center: bodyshop.md_responsibility_centers.taxes.state.name,
// sale: Dinero(job.job_totals.totals.state_tax),
// cost: Dinero(),
// profitCenter: bodyshop.md_responsibility_centers.taxes.state,
// costCenter: bodyshop.md_responsibility_centers.taxes.state,
// },
// federal: {
// center: bodyshop.md_responsibility_centers.taxes.federal.name,
// sale: Dinero(job.job_totals.totals.federal_tax),
// cost: Dinero(),
// profitCenter: bodyshop.md_responsibility_centers.taxes.federal,
// costCenter: bodyshop.md_responsibility_centers.taxes.federal,
// },
tax_ty1: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty1`].name,
sale: Dinero(job_totals.totals.us_sales_tax_breakdown[`ty1Tax`]),
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty1Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty1`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty1`],
},
tax_ty2: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty2`].name,
sale: Dinero(job_totals.totals.us_sales_tax_breakdown[`ty2Tax`]),
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty2Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty2`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty2`],
},
tax_ty3: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty3`].name,
sale: Dinero(job_totals.totals.us_sales_tax_breakdown[`ty3Tax`]),
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty3Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty3`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty3`],
},
tax_ty4: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty4`].name,
sale: Dinero(job_totals.totals.us_sales_tax_breakdown[`ty4Tax`]),
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty4Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty4`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty4`],
},
tax_ty5: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty5`].name,
sale: Dinero(job_totals.totals.us_sales_tax_breakdown[`ty5Tax`]),
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty5Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`],
@@ -363,6 +342,30 @@ exports.default = async function (socket, jobid) {
}
}
//profile level adjustments
Object.keys(job.job_totals.parts.adjustments).forEach((key) => {
const accountName = selectedDmsAllocationConfig.profits[key];
const otherAccount = bodyshop.md_responsibility_centers.profits.find(
(c) => c.name === accountName
);
if (otherAccount) {
if (!profitCenterHash[accountName])
profitCenterHash[accountName] = Dinero();
profitCenterHash[accountName] = profitCenterHash[accountName].add(
Dinero(job.job_totals.parts.adjustments[key])
);
} else {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}`
);
}
});
const jobAllocations = _.union(
Object.keys(profitCenterHash),
Object.keys(costCenterHash)

View File

@@ -521,6 +521,7 @@ function CalculatePartsTotals(jobLines, parts_tax_rates, job) {
.percentage(Math.abs(value.prt_dsmk_p || 0))
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
: Dinero();
return {
...acc,
parts: {
@@ -595,19 +596,8 @@ function CalculatePartsTotals(jobLines, parts_tax_rates, job) {
}
);
//Apply insurance based parts discuounts/markups.
let adjustments = {
PAA: Dinero(),
PAC: Dinero(),
PAG: Dinero(),
PAL: Dinero(),
PAN: Dinero(),
PAO: Dinero(),
PAP: Dinero(),
PAR: Dinero(),
PAS: Dinero(),
PAT: Dinero(),
};
//Apply insurance based parts discounts/markups.
let adjustments = {};
//Track all adjustments that need to be made.
const linesToAdjustForDiscount = [];
@@ -645,9 +635,6 @@ function CalculatePartsTotals(jobLines, parts_tax_rates, job) {
markup = ret.parts.list[key].total.percentage(markupRate);
}
}
let adjustment = disc.add(markup);
adjustments[key] = adjustment;
const correspondingCiecaStlTotalLine = job.cieca_stl?.data.find(
(c) => c.ttl_typecd === key
);
@@ -661,46 +648,12 @@ function CalculatePartsTotals(jobLines, parts_tax_rates, job) {
correspondingCiecaStlTotalLine.ttl_amt * 100
) > 1
) {
// Update the total.
console.log(
key,
ret.parts.list[key]?.total.getAmount(),
correspondingCiecaStlTotalLine?.ttl_amt
);
//Find the corresponding lines. Update the discount/markup for them.
console.warn("There's a difference! Type: ", key);
let totalDiscountToAdjustBy = Dinero();
job.joblines.forEach((jobline) => {
//Modify the line in place to add the mark up/discount.
if (jobline.part_type === key) {
const discountAmountDinero = Dinero({
amount: Math.round(jobline.act_price * 100),
}).percentage(discountRate);
const discountAmount = parseFloat(
discountAmountDinero.toFormat("0.00")
);
totalDiscountToAdjustBy =
totalDiscountToAdjustBy.add(discountAmountDinero);
jobline.prt_dsmk_m = discountAmount * -1;
jobline.prt_dsmk_p = discountRate * -1;
linesToAdjustForDiscount.push(jobline);
let adjustment = disc.add(markup);
adjustments[key] = adjustment;
ret.parts.subtotal = ret.parts.subtotal.add(adjustment);
ret.parts.total = ret.parts.total.add(adjustment);
}
});
// ret.parts.list[key].total = ret.parts.list[key]?.total.subtract(
// totalDiscountToAdjustBy
// );
ret.parts.prt_dsmk_total = ret.parts.prt_dsmk_total.subtract(
totalDiscountToAdjustBy
);
ret.parts.subtotal = ret.parts.subtotal.subtract(totalDiscountToAdjustBy);
ret.parts.total = ret.parts.total.subtract(totalDiscountToAdjustBy);
}
});
UpdateJobLines(linesToAdjustForDiscount.filter((l) => l.prt_dsmk_m !== 0));
return {
adjustments,
@@ -847,10 +800,6 @@ function CalculateTaxesTotals(job, otherTotals) {
STOR: Dinero(),
};
if (
job.parts_tax_rates.PAN.prt_tx_ty1 &&
job.parts_tax_rates.PAN.prt_tx_ty1 !== ""
) {
//For each line, determine if it's taxable, and if it is, add the line amount to the taxable amounts total.
job.joblines
.filter((jl) => !jl.removed)
@@ -866,10 +815,8 @@ function CalculateTaxesTotals(job, otherTotals) {
//Do nothing for now.
} else {
const typeOfPart = val.part_type;
taxableAmounts[typeOfPart] = taxableAmounts[typeOfPart].add(
Dinero({ amount: Math.round((val.act_price || 0) * 100) })
.multiply(val.part_qty || 0)
.add(
const discMarkupAmount =
val.prt_dsmk_m &&
val.prt_dsmk_m !== 0 &&
DiscountNotAlreadyCounted(val, job.joblines) // DO WE NEED TO COUNT PFP DISCOUNT HERE?
@@ -881,9 +828,14 @@ function CalculateTaxesTotals(job, otherTotals) {
.multiply(val.part_qty || 0)
.percentage(Math.abs(val.prt_dsmk_p || 0))
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
: Dinero()
)
);
: Dinero();
const partAmount = Dinero({
amount: Math.round((val.act_price || 0) * 100),
})
.multiply(val.part_qty || 0)
.add(discMarkupAmount);
taxableAmounts[typeOfPart] = taxableAmounts[typeOfPart].add(partAmount);
}
});
@@ -922,6 +874,16 @@ function CalculateTaxesTotals(job, otherTotals) {
amount: Math.round(stlStorage.t_amt * 100),
});
const pfp = job.parts_tax_rates;
//For any profile level markups/discounts, add them in now as well.
Object.keys(otherTotals.parts.adjustments).forEach((key) => {
const adjustmentAmount = otherTotals.parts.adjustments[key];
if (adjustmentAmount.getAmount() !== 0 && pfp[key]?.prt_tax_in) {
taxableAmounts[key] = taxableAmounts[key].add(adjustmentAmount);
}
});
// console.log("*** Taxable Amounts***");
// console.table(JSON.parse(JSON.stringify(taxableAmounts)));
@@ -945,7 +907,6 @@ function CalculateTaxesTotals(job, otherTotals) {
ty6Tax: Dinero(),
};
const pfp = job.parts_tax_rates;
const pfl = job.cieca_pfl;
const pfm = job.materials;
const pfo = job.cieca_pfo;
@@ -1033,8 +994,7 @@ function CalculateTaxesTotals(job, otherTotals) {
remainingTaxableAmounts[taxTierKey].getAmount() / 100
) {
//This threshold is bigger than the remaining taxable balance. Add it all.
taxableAmountInThisThreshold =
remainingTaxableAmounts[taxTierKey];
taxableAmountInThisThreshold = remainingTaxableAmounts[taxTierKey];
remainingTaxableAmounts[taxTierKey] = Dinero();
} else {
//Take the size of the threshold from the remaining amount, tax it, and do it all over.
@@ -1074,59 +1034,6 @@ function CalculateTaxesTotals(job, otherTotals) {
.add(totalTaxByTier.ty6Tax);
us_sales_tax_breakdown = totalTaxByTier;
//console.log("Tiered Taxes Total for Parts/Labor", statePartsTax.toFormat());
} else {
//Use the old thing.
job.joblines
.filter((jl) => !jl.removed)
.forEach((val) => {
if (!val.tax_part) return;
if (!val.part_type && IsAdditionalCost(val)) {
additionalItemsTax = additionalItemsTax.add(
Dinero({ amount: Math.round((val.act_price || 0) * 100) })
.multiply(val.part_qty || 0)
.percentage(
((job.parts_tax_rates &&
job.parts_tax_rates["PAN"] &&
job.parts_tax_rates["PAN"].prt_tax_rt) ||
0) * 100
)
);
} else {
statePartsTax = statePartsTax.add(
Dinero({ amount: Math.round((val.act_price || 0) * 100) })
.multiply(val.part_qty || 0)
.add(
val.prt_dsmk_m &&
val.prt_dsmk_m !== 0 &&
DiscountNotAlreadyCounted(val, job.joblines)
? 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()
)
.percentage(
((job.parts_tax_rates &&
job.parts_tax_rates[val.part_type] &&
job.parts_tax_rates[val.part_type].prt_tax_rt) ||
(val.part_type &&
val.part_type.startsWith("PAG") &&
BackupGlassTax &&
BackupGlassTax.prt_tax_rt) ||
(!val.part_type &&
val.db_ref === "900510" &&
job.parts_tax_rates["PAN"] &&
job.parts_tax_rates["PAN"].prt_tax_rt) ||
0) * 100
)
);
}
});
}
let laborTaxTotal = Dinero();
@@ -1191,32 +1098,6 @@ exports.default = Totals;
function DiscountNotAlreadyCounted(jobline, joblines) {
return false;
//CCC already factors in the discount. If the difference between the 2 is exactly the discount, it's all good.
if (
Math.round(
(jobline.prt_dsmk_m / (jobline.act_price - jobline.prt_dsmk_m)) * 100
) === Math.abs(jobline.prt_dsmk_p)
) {
return false;
}
if (
jobline.db_price !== jobline.act_price &&
jobline.db_price - jobline.act_price - Math.abs(jobline.prt_dsmk_m) < 0.02
) {
return false;
}
if (
//If it's not a discount line, then it definitely hasn't been counted yet.
jobline.db_ref !== "900510" &&
jobline.db_ref !== "900511"
)
return true;
const ParentLine = joblines.find((j) => j.unq_seq === jobline.line_ref);
return ParentLine && !(ParentLine.prt_dsmk_m && ParentLine.prt_dsmk_m !== 0);
}
exports.DiscountNotAlreadyCounted = DiscountNotAlreadyCounted;