421 lines
14 KiB
JavaScript
421 lines
14 KiB
JavaScript
const path = require("path");
|
|
require("dotenv").config({
|
|
path: path.resolve(
|
|
process.cwd(),
|
|
`.env.${process.env.NODE_ENV || "development"}`
|
|
),
|
|
});
|
|
const GraphQLClient = require("graphql-request").GraphQLClient;
|
|
|
|
const queries = require("../graphql-client/queries");
|
|
const CdkBase = require("../web-sockets/web-socket");
|
|
|
|
const Dinero = require("dinero.js");
|
|
const _ = require("lodash");
|
|
const { DiscountNotAlreadyCounted } = require("../job/job-totals");
|
|
|
|
exports.default = async function (socket, jobid) {
|
|
try {
|
|
CdkBase.createLogEvent(
|
|
socket,
|
|
"DEBUG",
|
|
`Received request to calculate allocations for ${jobid}`
|
|
);
|
|
const job = await QueryJobData(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,
|
|
},
|
|
};
|
|
|
|
//Determine if there are MAPA and MASH lines already on the estimate.
|
|
//If there are, don't do anything extra (mitchell estimate)
|
|
//Otherwise, calculate them and add them to the default MAPA and MASH centers.
|
|
let hasMapaLine = false;
|
|
let hasMashLine = false;
|
|
|
|
const profitCenterHash = job.joblines.reduce((acc, val) => {
|
|
//Check the Parts Assignment
|
|
if (val.db_ref === "936008") {
|
|
//If either of these DB REFs change, they also need to change in job-totals/job-costing calculations.
|
|
hasMapaLine = true;
|
|
}
|
|
if (val.db_ref === "936007") {
|
|
hasMashLine = true;
|
|
}
|
|
|
|
if (val.profitcenter_part) {
|
|
if (!acc[val.profitcenter_part]) acc[val.profitcenter_part] = Dinero();
|
|
|
|
let DineroAmount = Dinero({
|
|
amount: Math.round(val.act_price * 100),
|
|
}).multiply(val.part_qty || 1);
|
|
|
|
DineroAmount = DineroAmount.add(
|
|
((val.prt_dsmk_m && val.prt_dsmk_m !== 0) ||
|
|
(val.prt_dsmk_p && val.prt_dsmk_p !== 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()
|
|
);
|
|
|
|
acc[val.profitcenter_part] =
|
|
acc[val.profitcenter_part].add(DineroAmount);
|
|
}
|
|
if (val.profitcenter_labor && val.mod_lbr_ty) {
|
|
//Check the Labor Assignment.
|
|
|
|
if (!acc[val.profitcenter_labor])
|
|
acc[val.profitcenter_labor] = Dinero();
|
|
|
|
acc[val.profitcenter_labor] = acc[val.profitcenter_labor].add(
|
|
Dinero({
|
|
amount: Math.round(
|
|
job[`rate_${val.mod_lbr_ty.toLowerCase()}`] * 100
|
|
),
|
|
}).multiply(val.mod_lb_hrs)
|
|
);
|
|
}
|
|
return acc;
|
|
}, {});
|
|
|
|
const selectedDmsAllocationConfig =
|
|
bodyshop.md_responsibility_centers.dms_defaults.find(
|
|
(d) => d.name === job.dms_allocation
|
|
);
|
|
CdkBase.createLogEvent(
|
|
socket,
|
|
"DEBUG",
|
|
`Using DMS Allocation ${
|
|
selectedDmsAllocationConfig && selectedDmsAllocationConfig.name
|
|
} for cost export.`
|
|
);
|
|
|
|
let costCenterHash = {};
|
|
//Check whether to skip this if PBS and using AP module.
|
|
const disablebillwip = !!bodyshop?.pbs_configuration?.disablebillwip;
|
|
|
|
if (!disablebillwip) {
|
|
costCenterHash = job.bills.reduce((bill_acc, bill_val) => {
|
|
bill_val.billlines.map((line_val) => {
|
|
if (
|
|
!bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]]
|
|
)
|
|
bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] =
|
|
Dinero();
|
|
|
|
let lineDinero = Dinero({
|
|
amount: Math.round((line_val.actual_cost || 0) * 100),
|
|
})
|
|
.multiply(line_val.quantity)
|
|
.multiply(bill_val.is_credit_memo ? -1 : 1);
|
|
|
|
bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] =
|
|
bill_acc[
|
|
selectedDmsAllocationConfig.costs[line_val.cost_center]
|
|
].add(lineDinero);
|
|
return null;
|
|
});
|
|
return bill_acc;
|
|
}, {});
|
|
}
|
|
|
|
job.timetickets.forEach((ticket) => {
|
|
//Get the total amount of the ticket.
|
|
let TicketTotal = Dinero({
|
|
amount: Math.round(
|
|
ticket.rate *
|
|
(ticket.employee && ticket.employee.flat_rate
|
|
? ticket.productivehrs || 0
|
|
: ticket.actualhrs || 0) *
|
|
100
|
|
),
|
|
});
|
|
//Add it to the right cost center.
|
|
if (!costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]])
|
|
costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]] =
|
|
Dinero();
|
|
|
|
costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]] =
|
|
costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]].add(
|
|
TicketTotal
|
|
);
|
|
});
|
|
|
|
if (!hasMapaLine && job.job_totals.rates.mapa.total.amount > 0) {
|
|
// console.log("Adding MAPA Line Manually.");
|
|
const mapaAccountName = selectedDmsAllocationConfig.profits.MAPA;
|
|
|
|
const mapaAccount = bodyshop.md_responsibility_centers.profits.find(
|
|
(c) => c.name === mapaAccountName
|
|
);
|
|
|
|
if (mapaAccount) {
|
|
if (!profitCenterHash[mapaAccountName])
|
|
profitCenterHash[mapaAccountName] = Dinero();
|
|
|
|
profitCenterHash[mapaAccountName] = profitCenterHash[
|
|
mapaAccountName
|
|
].add(Dinero(job.job_totals.rates.mapa.total));
|
|
} else {
|
|
//console.log("NO MAPA ACCOUNT FOUND!!");
|
|
}
|
|
}
|
|
|
|
if (!hasMashLine && job.job_totals.rates.mash.total.amount > 0) {
|
|
// console.log("Adding MASH Line Manually.");
|
|
|
|
const mashAccountName = selectedDmsAllocationConfig.profits.MASH;
|
|
|
|
const mashAccount = bodyshop.md_responsibility_centers.profits.find(
|
|
(c) => c.name === mashAccountName
|
|
);
|
|
|
|
if (mashAccount) {
|
|
if (!profitCenterHash[mashAccountName])
|
|
profitCenterHash[mashAccountName] = Dinero();
|
|
|
|
profitCenterHash[mashAccountName] = profitCenterHash[
|
|
mashAccountName
|
|
].add(Dinero(job.job_totals.rates.mash.total));
|
|
} else {
|
|
// console.log("NO MASH ACCOUNT FOUND!!");
|
|
}
|
|
}
|
|
console.log(
|
|
Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting),
|
|
typeof Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting)
|
|
);
|
|
if (!!bodyshop?.cdk_configuration?.sendmaterialscosting) {
|
|
//Manually send the percentage of the costing.
|
|
|
|
//Paint Mat
|
|
const mapaAccountName = selectedDmsAllocationConfig.costs.MAPA;
|
|
const mapaAccount = bodyshop.md_responsibility_centers.costs.find(
|
|
(c) => c.name === mapaAccountName
|
|
);
|
|
if (mapaAccount) {
|
|
if (!costCenterHash[mapaAccountName])
|
|
costCenterHash[mapaAccountName] = Dinero();
|
|
if (job.bodyshop.use_paint_scale_data === true) {
|
|
if (job.mixdata.length > 0) {
|
|
costCenterHash[mapaAccountName] = costCenterHash[
|
|
mapaAccountName
|
|
].add(
|
|
Dinero({
|
|
amount: Math.round(
|
|
((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) *
|
|
100
|
|
),
|
|
})
|
|
);
|
|
} else {
|
|
costCenterHash[mapaAccountName] = costCenterHash[
|
|
mapaAccountName
|
|
].add(
|
|
Dinero(job.job_totals.rates.mapa.total).percentage(
|
|
bodyshop?.cdk_configuration?.sendmaterialscosting
|
|
)
|
|
);
|
|
}
|
|
} else {
|
|
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
|
|
Dinero(job.job_totals.rates.mapa.total).percentage(
|
|
bodyshop?.cdk_configuration?.sendmaterialscosting
|
|
)
|
|
);
|
|
}
|
|
} else {
|
|
//console.log("NO MAPA ACCOUNT FOUND!!");
|
|
}
|
|
|
|
//Shop Mat
|
|
const mashAccountName = selectedDmsAllocationConfig.costs.MASH;
|
|
const mashAccount = bodyshop.md_responsibility_centers.costs.find(
|
|
(c) => c.name === mashAccountName
|
|
);
|
|
if (mashAccount) {
|
|
if (!costCenterHash[mashAccountName])
|
|
costCenterHash[mashAccountName] = Dinero();
|
|
costCenterHash[mashAccountName] = costCenterHash[mashAccountName].add(
|
|
Dinero(job.job_totals.rates.mash.total).percentage(
|
|
bodyshop?.cdk_configuration?.sendmaterialscosting
|
|
)
|
|
);
|
|
} else {
|
|
// console.log("NO MASH ACCOUNT FOUND!!");
|
|
}
|
|
}
|
|
|
|
const { ca_bc_pvrt } = job;
|
|
if (ca_bc_pvrt) {
|
|
// const pvrtAccount = bodyshop.md_responsibility_centers.profits.find(
|
|
// (c) => c.name === mashAccountName
|
|
// );
|
|
|
|
taxAllocations.state.sale = taxAllocations.state.sale.add(
|
|
Dinero({ amount: Math.round((ca_bc_pvrt || 0) * 100) })
|
|
);
|
|
}
|
|
|
|
if (job.towing_payable && job.towing_payable !== 0) {
|
|
const towAccountName = selectedDmsAllocationConfig.profits.TOW;
|
|
|
|
const towAccount = bodyshop.md_responsibility_centers.profits.find(
|
|
(c) => c.name === towAccountName
|
|
);
|
|
|
|
if (towAccount) {
|
|
if (!profitCenterHash[towAccountName])
|
|
profitCenterHash[towAccountName] = Dinero();
|
|
|
|
profitCenterHash[towAccountName] = profitCenterHash[towAccountName].add(
|
|
Dinero({
|
|
amount: Math.round((job.towing_payable || 0) * 100),
|
|
})
|
|
);
|
|
} else {
|
|
// console.log("NO MASH ACCOUNT FOUND!!");
|
|
}
|
|
}
|
|
if (job.storage_payable && job.storage_payable !== 0) {
|
|
const storageAccountName = selectedDmsAllocationConfig.profits.TOW;
|
|
|
|
const towAccount = bodyshop.md_responsibility_centers.profits.find(
|
|
(c) => c.name === storageAccountName
|
|
);
|
|
|
|
if (towAccount) {
|
|
if (!profitCenterHash[storageAccountName])
|
|
profitCenterHash[storageAccountName] = Dinero();
|
|
|
|
profitCenterHash[storageAccountName] = profitCenterHash[
|
|
storageAccountName
|
|
].add(
|
|
Dinero({
|
|
amount: Math.round((job.storage_payable || 0) * 100),
|
|
})
|
|
);
|
|
} else {
|
|
// console.log("NO MASH ACCOUNT FOUND!!");
|
|
}
|
|
}
|
|
|
|
if (job.adjustment_bottom_line && job.adjustment_bottom_line !== 0) {
|
|
const otherAccountName = selectedDmsAllocationConfig.profits.PAO;
|
|
|
|
const otherAccount = bodyshop.md_responsibility_centers.profits.find(
|
|
(c) => c.name === otherAccountName
|
|
);
|
|
|
|
if (otherAccount) {
|
|
if (!profitCenterHash[otherAccountName])
|
|
profitCenterHash[otherAccountName] = Dinero();
|
|
|
|
profitCenterHash[otherAccountName] = profitCenterHash[
|
|
otherAccountName
|
|
].add(
|
|
Dinero({
|
|
amount: Math.round((job.adjustment_bottom_line || 0) * 100),
|
|
})
|
|
);
|
|
} else {
|
|
// console.log("NO MASH ACCOUNT FOUND!!");
|
|
}
|
|
}
|
|
|
|
const jobAllocations = _.union(
|
|
Object.keys(profitCenterHash),
|
|
Object.keys(costCenterHash)
|
|
).map((key) => {
|
|
const profitCenter = bodyshop.md_responsibility_centers.profits.find(
|
|
(c) => c.name === key
|
|
);
|
|
const costCenter = bodyshop.md_responsibility_centers.costs.find(
|
|
(c) => c.name === key
|
|
);
|
|
|
|
return {
|
|
center: key,
|
|
sale: profitCenterHash[key] ? profitCenterHash[key] : Dinero(),
|
|
cost: costCenterHash[key] ? costCenterHash[key] : Dinero(),
|
|
profitCenter,
|
|
costCenter,
|
|
};
|
|
});
|
|
|
|
return [
|
|
...jobAllocations,
|
|
...Object.keys(taxAllocations)
|
|
.filter(
|
|
(key) =>
|
|
taxAllocations[key].sale.getAmount() > 0 ||
|
|
taxAllocations[key].cost.getAmount() > 0
|
|
)
|
|
.map((key) => {
|
|
if (
|
|
key === "federal" &&
|
|
selectedDmsAllocationConfig.gst_override &&
|
|
selectedDmsAllocationConfig.gst_override !== ""
|
|
) {
|
|
const ret = { ...taxAllocations[key], tax: key };
|
|
ret.costCenter.dms_acctnumber =
|
|
selectedDmsAllocationConfig.gst_override;
|
|
ret.profitCenter.dms_acctnumber =
|
|
selectedDmsAllocationConfig.gst_override;
|
|
return ret;
|
|
} else {
|
|
return { ...taxAllocations[key], tax: key };
|
|
}
|
|
}),
|
|
];
|
|
} catch (error) {
|
|
CdkBase.createLogEvent(
|
|
socket,
|
|
"ERROR",
|
|
`Error encountered in CdkCalculateAllocations. ${error}`
|
|
);
|
|
}
|
|
};
|
|
|
|
async function QueryJobData(socket, jobid) {
|
|
CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`);
|
|
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
|
const result = await client
|
|
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
|
.request(queries.GET_CDK_ALLOCATIONS, { id: jobid });
|
|
CdkBase.createLogEvent(
|
|
socket,
|
|
"TRACE",
|
|
`Job data query result ${JSON.stringify(result, null, 2)}`
|
|
);
|
|
return result.jobs_by_pk;
|
|
}
|