From 1716c3e6b2f87b59c582fedce91fbc775310180b Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 20 Jan 2026 11:55:12 -0800 Subject: [PATCH] IO-3503 Job Costing Fixes Signed-off-by: Allan Carr --- server/job/job-costing.js | 135 ++++++++++++++++++++++++++--------- server/job/job-totals-USA.js | 2 + 2 files changed, 102 insertions(+), 35 deletions(-) diff --git a/server/job/job-costing.js b/server/job/job-costing.js index a8688f002..674d6346e 100644 --- a/server/job/job-costing.js +++ b/server/job/job-costing.js @@ -2,6 +2,7 @@ const _ = require("lodash"); const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); const logger = require("../utils/logger"); +const { ParseCalopCode } = require("../job/job-totals-USA"); const InstanceManager = require("../utils/instanceMgr").default; const { DiscountNotAlreadyCounted } = InstanceManager({ imex: require("../job/job-totals"), @@ -265,6 +266,10 @@ function GenerateCostingData(job) { ); const materialsHours = { mapaHrs: 0, mashHrs: 0 }; + let mashOpCodes = InstanceManager({ + imex: [], + rome: ParseCalopCode(job.materials["MASH"]?.cal_opcode) + }); let hasMapaLine = false; let hasMashLine = false; @@ -351,7 +356,7 @@ function GenerateCostingData(job) { if (val.mod_lbr_ty === "LAR") { materialsHours.mapaHrs += val.mod_lb_hrs || 0; } - if (val.mod_lbr_ty !== "LAR") { + if (val.mod_lbr_ty !== "LAR" && mashOpCodes.includes(val.lbr_op)) { materialsHours.mashHrs += val.mod_lb_hrs || 0; } } @@ -398,32 +403,6 @@ function GenerateCostingData(job) { : Dinero() ); - // Profile Discount for Parts - if (job.parts_tax_rates && job.parts_tax_rates[val.part_type.toUpperCase()]) { - if ( - job.parts_tax_rates[val.part_type.toUpperCase()].prt_discp !== undefined && - job.parts_tax_rates[val.part_type.toUpperCase()].prt_discp >= 0 - ) { - const discountRate = - Math.abs(job.parts_tax_rates[val.part_type.toUpperCase()].prt_discp) > 1 - ? job.parts_tax_rates[val.part_type.toUpperCase()].prt_discp - : job.parts_tax_rates[val.part_type.toUpperCase()].prt_discp * 100; - const disc = partsAmount.percentage(discountRate).multiply(-1); - partsAmount = partsAmount.add(disc); - } - if ( - job.parts_tax_rates[val.part_type.toUpperCase()].prt_mkupp !== undefined && - job.parts_tax_rates[val.part_type.toUpperCase()].prt_mkupp >= 0 - ) { - const markupRate = - Math.abs(job.parts_tax_rates[val.part_type.toUpperCase()].prt_mkupp) > 1 - ? job.parts_tax_rates[val.part_type.toUpperCase()].prt_mkupp - : job.parts_tax_rates[val.part_type.toUpperCase()].prt_mkupp * 100; - const markup = partsAmount.percentage(markupRate); - partsAmount = partsAmount.add(markup); - } - } - if (!acc.parts[partsProfitCenter]) acc.parts[partsProfitCenter] = Dinero(); acc.parts[partsProfitCenter] = acc.parts[partsProfitCenter].add(partsAmount); } @@ -510,10 +489,64 @@ function GenerateCostingData(job) { { parts: {}, labor: {}, additional: {}, sublet: {} } ); + // Profile Discount for Parts + Object.keys(jobLineTotalsByProfitCenter.parts).forEach((key) => { + let disc = Dinero(), + markup = Dinero(); + const convertedKey = Object.keys(defaultProfits).find((k) => defaultProfits[k] === key); + if (job.parts_tax_rates && job.parts_tax_rates[convertedKey.toUpperCase()]) { + if ( + job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp !== undefined && + job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp >= 0 + ) { + const discountRate = + Math.abs(job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp) > 1 + ? job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp + : job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp * 100; + disc = jobLineTotalsByProfitCenter.parts[key].percentage(discountRate).multiply(-1); + } + if ( + job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp !== undefined && + job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp >= 0 + ) { + const markupRate = + Math.abs(job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp) > 1 + ? job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp + : job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp * 100; + markup = jobLineTotalsByProfitCenter.parts[key].percentage(markupRate); + } + } + + if (InstanceManager({ imex: true, rome: false })) { + //this might need to be removed for ImEX + jobLineTotalsByProfitCenter.parts[key] = jobLineTotalsByProfitCenter.parts[key].add(disc).add(markup); + } else { + const correspondingCiecaStlTotalLine = job.cieca_stl?.data.find( + (c) => c.ttl_typecd === convertedKey.toUpperCase() + ); + if ( + correspondingCiecaStlTotalLine && + Math.abs(jobLineTotalsByProfitCenter.parts[key].getAmount() - correspondingCiecaStlTotalLine.ttl_amt * 100) > 1 + ) { + jobLineTotalsByProfitCenter.parts[key] = jobLineTotalsByProfitCenter.parts[key].add(disc).add(markup); + } + } + }); + if (!hasMapaLine) { + let threshold; + if (job.materials["MAPA"].cal_maxdlr !== undefined && job.materials["MAPA"].cal_maxdlr >= 0) { + //It has an upper threshhold. + threshold = Dinero({ + amount: Math.round(job.materials["MAPA"].cal_maxdlr * 100) + }); + } + if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]]) jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = Dinero(); + const origMAPAAmount = jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]]; + jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = jobLineTotalsByProfitCenter.additional[ defaultProfits["MAPA"] ].add( @@ -541,11 +574,25 @@ function GenerateCostingData(job) { .percentage(adjp < 0 ? adjp * -1 : adjp) .multiply(adjp < 0 ? -1 : 1) ); + + if (threshold && jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]].greaterThanOrEqual(threshold)) { + jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = threshold.add(origMAPAAmount); + } } if (!hasMashLine) { + let threshold; + if (job.materials["MASH"].cal_maxdlr !== undefined && job.materials["MASH"].cal_maxdlr >= 0) { + //It has an upper threshhold. + threshold = Dinero({ + amount: Math.round(job.materials["MASH"].cal_maxdlr * 100) + }); + } + if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]]) jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = Dinero(); + const origMASHAmount = jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]]; + jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = jobLineTotalsByProfitCenter.additional[ defaultProfits["MASH"] ].add( @@ -573,6 +620,10 @@ function GenerateCostingData(job) { .percentage(adjp < 0 ? adjp * -1 : adjp) .multiply(adjp < 0 ? -1 : 1) ); + + if (threshold && jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]].greaterThanOrEqual(threshold)) { + jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = threshold.add(origMASHAmount); + } } if (InstanceManager({ imex: false, rome: true })) { @@ -582,20 +633,34 @@ function GenerateCostingData(job) { if (!jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]]) jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = Dinero(); - jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = stlTowing - ? Dinero({ amount: Math.round((stlTowing.ttl_amt || 0) * 100) }) - : Dinero({ + if (stlTowing) + jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = Dinero({ + amount: Math.round((stlTowing.ttl_amt || 0) * 100) + }); + if (!stlTowing && job.towing_payable && job.towing_payable > 0) + jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = jobLineTotalsByProfitCenter.additional[ + defaultProfits["TOW"] + ].add( + Dinero({ amount: Math.round((job.towing_payable || 0) * 100) - }); + }) + ); if (!jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]]) jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]] = Dinero(); - jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]] = stlStorage - ? Dinero({ amount: Math.round((stlStorage.ttl_amt || 0) * 100) }) - : Dinero({ + if (stlStorage) + jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = Dinero({ + amount: Math.round((stlStorage.ttl_amt || 0) * 100) + }); + if (!stlStorage && job.storage_payable && job.storage_payable > 0) + jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]] = jobLineTotalsByProfitCenter.additional[ + defaultProfits["STO"] + ].add( + Dinero({ amount: Math.round((job.storage_payable || 0) * 100) - }); + }) + ); } //Is it a DMS Setup? diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index 6d5c7c8b4..27fced83e 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -1227,6 +1227,8 @@ function ParseCalopCode(opcode) { return opcode.trim().split(" "); } +exports.ParseCalopCode = ParseCalopCode; + function IsTrueOrYes(value) { return value === true || value === "Y" || value === "y"; }