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 InstanceManager = require("../utils/instanceMgr").default; const { DiscountNotAlreadyCounted } = InstanceManager({ imex: require("../job/job-totals"), rome: require("../job/job-totals-USA") }); exports.defaultRoute = async function (req, res) { try { CdkBase.createLogEvent(req, "DEBUG", `Received request to calculate allocations for ${req.body.jobid}`); const jobData = await QueryJobData(req, req.BearerToken, req.body.jobid); return res.status(200).json({ data: calculateAllocations(req, jobData) }); } catch (error) { ////console.log(error); CdkBase.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`); res.status(500).json({ error: `Error encountered in CdkCalculateAllocations. ${error}` }); } }; exports.default = async function (socket, jobid) { try { const jobData = await QueryJobData(socket, "Bearer " + socket.handshake.auth.token, jobid); return calculateAllocations(socket, jobData); } catch (error) { ////console.log(error); CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`); } }; async function QueryJobData(connectionData, token, jobid) { CdkBase.createLogEvent(connectionData, "DEBUG", `Querying job data for id ${jobid}`); const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); const result = await client.setHeaders({ Authorization: token }).request(queries.GET_CDK_ALLOCATIONS, { id: jobid }); CdkBase.createLogEvent(connectionData, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`); return result.jobs_by_pk; } function calculateAllocations(connectionData, job) { const { bodyshop } = job; const taxAllocations = InstanceManager({ executeFunction: true, deubg: true, args: [], imex: () => ({ 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 } }), rome: () => ({ tax_ty1: { center: bodyshop.md_responsibility_centers.taxes[`tax_ty1`].name, 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.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.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.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.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`] } }) }); //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( connectionData, "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!!"); } } if (InstanceManager({ rome: true })) { //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(); profitCenterHash[accountName] = profitCenterHash[accountName].add( Dinero(job.job_totals.parts.adjustments[key]) ); } else { CdkBase.createLogEvent( connectionData, "ERROR", `Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}` ); } }); //profile level adjustments for labor and materials Object.keys(job.job_totals.rates).forEach((key) => { if ( job.job_totals.rates[key] && job.job_totals.rates[key].adjustment && Dinero(job.job_totals.rates[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) => { 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 }; } }), ...(job.job_totals.totals.ttl_adjustment ? [ { center: "SUB ADJ", sale: Dinero(job.job_totals.totals.ttl_adjustment), cost: Dinero(), profitCenter: { name: "SUB ADJ", accountdesc: "SUB ADJ", accountitem: "SUB ADJ", accountname: "SUB ADJ", dms_acctnumber: bodyshop.md_responsibility_centers.ttl_adjustment.dms_acctnumber }, costCenter: {} } ] : []), ...(job.job_totals.totals.ttl_tax_adjustment ? [ { center: "TAX ADJ", sale: Dinero(job.job_totals.totals.ttl_tax_adjustment), cost: Dinero(), profitCenter: { name: "TAX ADJ", accountdesc: "TAX ADJ", accountitem: "TAX ADJ", accountname: "TAX ADJ", dms_acctnumber: bodyshop.md_responsibility_centers.ttl_tax_adjustment.dms_acctnumber }, costCenter: {} } ] : []) ]; }