- V1 Finished

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-08-02 16:33:50 -04:00
14 changed files with 4333 additions and 3477 deletions

View File

@@ -664,7 +664,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
});
}
} else {
//Handle insurance profile adjustments
//Handle insurance profile adjustments for Parts
Object.keys(job_totals.parts.adjustments).forEach((key) => {
if (qbo) {
//Going to always assume that we need to apply GST and PST for labor.
@@ -718,6 +718,67 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
}
});
//Handle insurance profile adjustments for Labor and Materials
Object.keys(job_totals.rate).forEach((key) => {
if (
job_totals.rate[key] &&
job_totals.rate[key].adjustment &&
job_totals.rate[key].adjustment.isZero() === false
) {
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.toUpperCase()]
);
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.rate[key]).adjustment.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.toUpperCase()]
).accountitem
},
Desc: "Storage",
Quantity: 1,
Amount: Dinero(job_totals.rate[key]).adjustment.toFormat(DineroQbFormat),
SalesTaxCodeRef: {
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
}
});
}
}
});
const QboTaxId =
process.env.COUNTRY === "USA"
? CheckQBOUSATaxID({

View File

@@ -360,12 +360,10 @@ function calculateAllocations(connectionData, job) {
}
}
if (InstanceManager({ rome: true })) {
//profile level adjustments
//profile level adjustments for parts
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();
@@ -380,6 +378,24 @@ function calculateAllocations(connectionData, job) {
);
}
});
//profile level adjustments for labor and materials
Object.keys(job.job_totals.rates).forEach((key) => {
if (job.job_totals.rate[key] && job.job_totals.rate[key].adjustment && job.job_totals.rate[key].adjustment.isZero() === false) {
const accountName = selectedDmsAllocationConfig.profits[key.toUpperCase()];
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.rates[key].adjustments));
} else {
CdkBase.createLogEvent(
connectionData,
"ERROR",
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}`
);
}
}
});
}
const jobAllocations = _.union(Object.keys(profitCenterHash), Object.keys(costCenterHash)).map((key) => {

View File

@@ -1527,6 +1527,7 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
ca_bc_pvrt
ca_customer_gst
dms_allocation
cieca_pfl
joblines(where: { removed: { _eq: false } }) {
id
db_ref

View File

@@ -286,9 +286,45 @@ function GenerateCostingData(job) {
const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`;
const laborAmount = Dinero({
let laborAmount = Dinero();
laborAmount = Dinero({
amount: Math.round((job[rateName] || 0) * 100)
}).multiply(val.mod_lb_hrs || 0);
if (
job.cieca_pfl &&
job.cieca_pfl[val.mod_lbr_ty.toUpperCase()] &&
job.cieca_pfl[val.mod_lbr_ty.toUpperCase()].lbr_adjp !== 0
) {
let adjp = 0;
if (
val.mod_lbr_ty === "la1" ||
val.mod_lbr_ty === "la2" ||
val.mod_lbr_ty === "la3" ||
val.mod_lbr_ty === "la4"
) {
adjp =
Math.abs(job.cieca_pfl["LAU"].lbr_adjp) > 1
? job.cieca_pfl["LAU"].lbr_adjp
: job.cieca_pfl["LAU"].lbr_adjp * 100; //Adjust lbr_adjp to whole number
} else {
if (job.cieca_pfl[val.mod_lbr_ty.toUpperCase()]) {
adjp =
Math.abs(job.cieca_pfl[val.mod_lbr_ty.toUpperCase()].lbr_adjp) > 1
? job.cieca_pfl[val.mod_lbr_ty.toUpperCase()].lbr_adjp
: job.cieca_pfl[val.mod_lbr_ty.toUpperCase()].lbr_adjp * 100; //Adjust lbr_adjp to whole number
} else {
adjp =
Math.abs(job.cieca_pfl["LAB"].lbr_adjp) > 1
? job.cieca_pfl["LAB"].lbr_adjp
: job.cieca_pfl["LAB"].lbr_adjp * 100; //Adjust lbr_adjp to whole number
}
}
laborAmount = laborAmount.add(
laborAmount.percentage(adjp < 0 ? adjp * -1 : adjp).multiply(adjp < 0 ? -1 : 1)
);
}
if (!acc.labor[laborProfitCenter]) acc.labor[laborProfitCenter] = Dinero();
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(laborAmount);
@@ -317,7 +353,7 @@ function GenerateCostingData(job) {
if (!partsProfitCenter)
console.log("Unknown cost/profit center mapping for parts.", val.line_desc, val.part_type);
const partsAmount = Dinero({
let partsAmount = Dinero({
amount: val.act_price_before_ppc
? Math.round(val.act_price_before_ppc * 100)
: Math.round(val.act_price * 100)
@@ -338,6 +374,33 @@ function GenerateCostingData(job) {
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
: 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
? parts_tajob.parts_tax_rates_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);
}
@@ -413,6 +476,7 @@ function GenerateCostingData(job) {
if (!hasMapaLine) {
if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]])
jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = Dinero();
jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = jobLineTotalsByProfitCenter.additional[
defaultProfits["MAPA"]
].add(
@@ -420,10 +484,26 @@ function GenerateCostingData(job) {
amount: Math.round((job.rate_mapa || 0) * 100)
}).multiply(materialsHours.mapaHrs || 0)
);
let adjp = 0;
if (job.materials["MAPA"] && job.materials["MAPA"].mat_adjp) {
adjp =
Math.abs(job.materials["MAPA"].mat_adjp) > 1
? job.materials["MAPA"].mat_adjp
: job.materials["MAPA"].mat_adjp * 100; //Adjust mat_adjp to whole number
}
jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = jobLineTotalsByProfitCenter.additional[
defaultProfits["MAPA"]
].add(
jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]]
.percentage(adjp < 0 ? adjp * -1 : adjp)
.multiply(adjp < 0 ? -1 : 1)
);
}
if (!hasMashLine) {
if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]])
jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = Dinero();
jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = jobLineTotalsByProfitCenter.additional[
defaultProfits["MASH"]
].add(
@@ -431,6 +511,21 @@ function GenerateCostingData(job) {
amount: Math.round((job.rate_mash || 0) * 100)
}).multiply(materialsHours.mashHrs || 0)
);
let adjp = 0;
if (job.materials["MASH"] && job.materials["MASH"].mat_adjp) {
adjp =
Math.abs(job.materials["MASH"].mat_adjp) > 1
? job.materials["MASH"].mat_adjp
: job.materials["MASH"].mat_adjp * 100; //Adjust mat_adjp to whole number
}
jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = jobLineTotalsByProfitCenter.additional[
defaultProfits["MASH"]
].add(
jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]]
.percentage(adjp < 0 ? adjp * -1 : adjp)
.multiply(adjp < 0 ? -1 : 1)
);
}
//Is it a DMS Setup?

View File

@@ -331,6 +331,8 @@ async function CalculateRatesTotals({ job, client }) {
//Skip calculating mapa and mash if we got the amounts.
if (!((property === "mapa" && hasMapaLine) || (property === "mash" && hasMashLine))) {
if (!ret[property].total) {
ret[property].base = Dinero();
ret[property].adjustment = Dinero();
ret[property].total = Dinero();
}
let threshold;
@@ -349,13 +351,50 @@ async function CalculateRatesTotals({ job, client }) {
}
}
const total = Dinero({
const base = Dinero({
amount: Math.round((ret[property].rate || 0) * 100)
}).multiply(ret[property].hours);
let adjp = 0;
if (property === "mapa" || property === "mash") {
if (job.materials[property.toUpperCase()] && job.materials[property.toUpperCase()].mat_adjp) {
adjp =
Math.abs(job.materials[property.toUpperCase()].mat_adjp) > 1
? job.materials[property.toUpperCase()].mat_adjp
: job.materials[property.toUpperCase()].mat_adjp * 100; //Adjust mat_adjp to whole number
}
} else {
if (property === "la1" || property === "la2" || property === "la3" || property === "la4") {
if (job.cieca_pfl["LAU"] && job.cieca_pfl["LAU"].lbr_adjp) {
adjp =
Math.abs(job.cieca_pfl["LAU"].lbr_adjp) > 1
? job.cieca_pfl["LAU"].lbr_adjp
: job.cieca_pfl["LAU"].lbr_adjp * 100; //Adjust lbr_adjp to whole number
}
} else {
if (job.cieca_pfl[property.toUpperCase()] && job.cieca_pfl[property.toUpperCase()].lbr_adjp) {
adjp =
Math.abs(job.cieca_pfl[property.toUpperCase()].lbr_adjp) > 1
? job.cieca_pfl[property.toUpperCase()].lbr_adjp
: job.cieca_pfl[property.toUpperCase()].lbr_adjp * 100; //Adjust lbr_adjp to whole number
} else {
if (job.cieca_pfl["LAB"].lbr_adjp) {
adjp =
Math.abs(job.cieca_pfl["LAB"].lbr_adjp) > 1
? job.cieca_pfl["LAB"].lbr_adjp
: job.cieca_pfl["LAB"].lbr_adjp * 100; //Adjust lbr_adjp to whole number
}
}
}
}
const adjustment = base.percentage(adjp < 0 ? adjp * -1 : adjp).multiply(adjp < 0 ? -1 : 1);
const total = base.add(adjustment);
if (threshold && total.greaterThanOrEqual(threshold)) {
ret[property].total = ret[property].total.add(threshold);
} else {
ret[property].base = ret[property].base.add(base);
ret[property].adjustment = ret[property].adjustment.add(adjustment);
ret[property].total = ret[property].total.add(total);
}
}
@@ -703,18 +742,19 @@ function CalculateTaxesTotals(job, otherTotals) {
//Potential issue here with Sublet Calculation. Sublets are calculated under labor in Mitchell, but it's done in IO
//Under the parts rates.
let statePartsTax = Dinero();
let additionalItemsTax = Dinero();
let stateTax = Dinero();
// let additionalItemsTax = Dinero(); //This is not used.
let us_sales_tax_breakdown;
// This is not referenced in the code base.
//Audatex sends additional glass part types. IO-774
const BackupGlassTax =
job.parts_tax_rates &&
(job.parts_tax_rates.PAGD ||
job.parts_tax_rates.PAGF ||
job.parts_tax_rates.PAGP ||
job.parts_tax_rates.PAGQ ||
job.parts_tax_rates.PAGR);
// const BackupGlassTax =
// job.parts_tax_rates &&
// (job.parts_tax_rates.PAGD ||
// job.parts_tax_rates.PAGF ||
// job.parts_tax_rates.PAGP ||
// job.parts_tax_rates.PAGQ ||
// job.parts_tax_rates.PAGR);
const taxableAmounts = {
PAA: Dinero(),
@@ -878,11 +918,27 @@ function CalculateTaxesTotals(job, otherTotals) {
} else if (key.startsWith("LA")) {
//Labor.
for (let tyCounter = 1; tyCounter <= 5; tyCounter++) {
if (IsTrueOrYes(pfl[key][`lbr_tx_in${tyCounter}`])) {
//This amount is taxable for this type.
taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add(
taxableAmounts[key]
);
if (key === "LA1" || key === "LA2" || key === "LA3" || key === "LA4") {
if (IsTrueOrYes(pfl["LAU"][`lbr_tx_in${tyCounter}`])) {
//This amount is taxable for this type.
taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add(
taxableAmounts[key]
);
}
} else if (key === "LAA" && !pfl[key]) {
if (IsTrueOrYes(pfl["LAB"][`lbr_tx_in${tyCounter}`])) {
//This amount is taxable for this type.
taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add(
taxableAmounts[key]
);
}
} else {
if (IsTrueOrYes(pfl[key][`lbr_tx_in${tyCounter}`])) {
//This amount is taxable for this type.
taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add(
taxableAmounts[key]
);
}
}
}
} else if (key === "TOW") {
@@ -919,7 +975,6 @@ function CalculateTaxesTotals(job, otherTotals) {
Object.keys(taxableAmountsByTier).forEach((taxTierKey) => {
taxable_adjustment = taxableAmountsByTier[taxTierKey].multiply(percent_of_adjustment);
console.log("🚀 ~ taxableAmountsByTier ~ taxable_adjustment:", taxable_adjustment);
if (job.adjustment_bottom_line > 0) {
taxableAmountsByTier[taxTierKey] = taxableAmountsByTier[taxTierKey].add(taxable_adjustment);
} else {
@@ -937,8 +992,8 @@ function CalculateTaxesTotals(job, otherTotals) {
let tyCounter = taxTierKey[2]; //Get the number from the key.
//i represents the tax number. If we got here, this type of tax is applicable. Now we need to add based on the thresholds.
for (let threshCounter = 1; threshCounter <= 5; threshCounter++) {
const thresholdAmount = parseFloat(job.cieca_pft[`ty${tyCounter}_thres${threshCounter}`]);
const thresholdTaxRate = parseFloat(job.cieca_pft[`ty${tyCounter}_rate${threshCounter}`]);
const thresholdAmount = parseFloat(job.cieca_pft[`ty${tyCounter}_thres${threshCounter}`]) || 0;
const thresholdTaxRate = parseFloat(job.cieca_pft[`ty${tyCounter}_rate${threshCounter}`]) || 0;
let taxableAmountInThisThreshold;
if (
@@ -946,7 +1001,7 @@ function CalculateTaxesTotals(job, otherTotals) {
InstanceMgr({
imex: false,
rome: thresholdAmount === 0 && parseInt(tyCounter) === 1,
promanager: thresholdAmount === 0 && parseInt(tyCounter) === 1
promanager: "USE_ROME"
})
) {
//
@@ -980,10 +1035,10 @@ function CalculateTaxesTotals(job, otherTotals) {
}
});
// console.log("*** Total Tax by Tier Amounts***");
// console.table(JSON.parse(JSON.stringify(totalTaxByTier)));
console.log("*** Total Tax by Tier Amounts***");
console.table(JSON.parse(JSON.stringify(totalTaxByTier)));
statePartsTax = statePartsTax
stateTax = stateTax
.add(totalTaxByTier.ty1Tax)
.add(totalTaxByTier.ty2Tax)
.add(totalTaxByTier.ty3Tax)
@@ -991,17 +1046,18 @@ function CalculateTaxesTotals(job, otherTotals) {
.add(totalTaxByTier.ty5Tax)
.add(totalTaxByTier.ty6Tax);
us_sales_tax_breakdown = totalTaxByTier;
//console.log("Tiered Taxes Total for Parts/Labor", statePartsTax.toFormat());
//console.log("Tiered Taxes Total for Parts/Labor", stateTax.toFormat());
let laborTaxTotal = Dinero();
// This is not in use as such commented out.
// let laborTaxTotal = Dinero();
if (Object.keys(job.cieca_pfl).length > 0) {
//Ignore it now, we have calculated it above.
//This was previously used for JCS before parts were also calculated at a different rate.
} else {
//We don't have it, just add in how it was before.
laborTaxTotal = otherTotals.rates.subtotal.percentage((job.tax_lbr_rt || 0) * 100); // THis is currently using the lbr tax rate from PFH not PFL.
}
// if (Object.keys(job.cieca_pfl).length > 0) {
// //Ignore it now, we have calculated it above.
// //This was previously used for JCS before parts were also calculated at a different rate.
// } else {
// //We don't have it, just add in how it was before.
// laborTaxTotal = otherTotals.rates.subtotal.percentage((job.tax_lbr_rt || 0) * 100); // THis is currently using the lbr tax rate from PFH not PFL.
// }
//console.log("Labor Tax Total", laborTaxTotal.toFormat());
@@ -1010,9 +1066,9 @@ function CalculateTaxesTotals(job, otherTotals) {
federal_tax: subtotal
.percentage((job.federal_tax_rate || 0) * 100)
.add(otherTotals.additional.pvrt.percentage((job.federal_tax_rate || 0) * 100)),
statePartsTax,
stateTax,
us_sales_tax_breakdown,
state_tax: statePartsTax,
state_tax: stateTax,
local_tax: subtotal.percentage((job.local_tax_rate || 0) * 100)
};
ret.total_repairs = ret.subtotal