From b12ad405c3a0aa31856f3bb24bc7bfda5665761b Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Tue, 15 Jun 2021 13:32:17 -0700 Subject: [PATCH] IO-594 additional autohouse improvements. --- server/data/autohouse.js | 141 ++++++++++++++++++++++++++++--- server/graphql-client/queries.js | 30 ++++++- 2 files changed, 159 insertions(+), 12 deletions(-) diff --git a/server/data/autohouse.js b/server/data/autohouse.js index 3d4b70abf..765e787cf 100644 --- a/server/data/autohouse.js +++ b/server/data/autohouse.js @@ -31,13 +31,17 @@ exports.default = async (req, res) => { }, }; - console.log("***Number of Failed jobs***: ", erroredJobs.length); + console.log( + "***Number of Failed jobs***: ", + erroredJobs.length, + JSON.stringify(erroredJobs.map((x) => x.error)) + ); var ret = builder .create(autoHouseObject, { version: "1.0", encoding: "UTF-8", }) - .end({ pretty: true }); + .end({ pretty: true, allowEmptyTags: true }); //***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml res.type("application/xml"); @@ -48,6 +52,8 @@ exports.default = async (req, res) => { const CreateRepairOrderTag = (job, errorCallback) => { //Level 2 + const repairCosts = CreateCosts(job); + try { const ret = { RepairOrderInformation: { @@ -63,8 +69,8 @@ const CreateRepairOrderTag = (job, errorCallback) => { ShopState: job.bodyshop.state, ShopZip: job.bodyshop.zip_post, ShopPhone: job.bodyshop.phone, - EstimatorID: `${job.est_ct_fn} ${job.est_ct_ln}`, - EstimatorName: `${job.est_ct_fn} ${job.est_ct_ln}`, + EstimatorID: `${job.est_ct_fn || ""} ${job.est_ct_ln || ""}`, + EstimatorName: `${job.est_ct_fn || ""} ${job.est_ct_ln || ""}`, }, CustomerInformation: { FirstName: job.ownr_fn, @@ -97,7 +103,7 @@ const CreateRepairOrderTag = (job, errorCallback) => { VehiclePaintCode: null, VehicleTrimCode: null, VehicleBodyStyle: null, - DriveableFlag: job.tlos_ind ? "Y" : "N", + DriveableFlag: job.driveable ? "Y" : "N", }, InsuranceInformation: { @@ -251,25 +257,39 @@ const CreateRepairOrderTag = (job, errorCallback) => { }, RevisedTotals: { BodyHours: job.job_totals.rates.lab.hours, + BodyRepairHours: job.joblines + .filter((line) => repairOpCodes.includes(line.lbr_op)) + .reduce((acc, val) => acc + val.mod_lb_hrs, 0), + BodyReplaceHours: job.joblines + .filter((line) => replaceOpCodes.includes(line.lbr_op)) + .reduce((acc, val) => acc + val.mod_lb_hrs, 0), RefinishHours: job.job_totals.rates.lar.hours, MechanicalHours: job.job_totals.rates.lam.hours, StructuralHours: job.job_totals.rates.las.hours, PartsTotal: Dinero(job.job_totals.parts.parts.total).toFormat( AHDineroFormat ), - PartsTotalCost: 0, + PartsTotalCost: repairCosts.PartsTotalCost.toFormat(AHDineroFormat), PartsOEM: Dinero( job.job_totals.parts.parts.list.PAN && job.job_totals.parts.parts.list.PAN.total - ).toFormat(AHDineroFormat), - PartsOEMCost: 0, + ) + .add( + Dinero( + job.job_totals.parts.parts.list.PAP && + job.job_totals.parts.parts.list.PAP.total + ) + ) + .toFormat(AHDineroFormat), + PartsOEMCost: repairCosts.PartsOemCost.toFormat(AHDineroFormat), PartsAM: Dinero( job.job_totals.parts.parts.list.PAA && job.job_totals.parts.parts.list.PAA.total ).toFormat(AHDineroFormat), - PartsAMCost: 0, + PartsAMCost: repairCosts.PartsAMCost.toFormat(AHDineroFormat), PartsReconditioned: null, - PartsReconditionedCost: null, + PartsReconditionedCost: + repairCosts.PartsReconditionedCost.toFormat(AHDineroFormat), PartsRecycled: Dinero( job.job_totals.parts.parts.list.PAR && job.job_totals.parts.parts.list.PAR.total @@ -389,10 +409,108 @@ const CreateRepairOrderTag = (job, errorCallback) => { }; return ret; } catch (error) { + console.log("Error calculating job", error); errorCallback(job, error); } }; +const CreateCosts = (job) => { + //Create a mapping based on AH Requirements + + const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => { + //At the bill level. + bill_val.billlines.map((line_val) => { + //At the bill line level. + //console.log("JobCostingPartsTable -> line_val", line_val); + if (!bill_acc[line_val.cost_center]) + bill_acc[line_val.cost_center] = Dinero(); + + bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add( + Dinero({ + amount: Math.round((line_val.actual_cost || 0) * 100), + }) + .multiply(line_val.quantity) + .multiply(bill_val.is_credit_memo ? -1 : 1) + ); + + return null; + }); + return bill_acc; + }, {}); + const materialsHours = { mapaHrs: 0, mashHrs: 0 }; + //If the hourly rates for job costing are set, add them in. + if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa) { + if ( + !billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ] + ) + billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ] = Dinero(); + billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ] = billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ].add( + Dinero({ + amount: + (job.bodyshop.jc_hourly_rates && + job.bodyshop.jc_hourly_rates.mapa * 100) || + 0, + }).multiply(materialsHours.mapaHrs) + ); + } + const ticketTotalsByCostCenter = job.timetickets.reduce( + (ticket_acc, ticket_val) => { + //At the invoice level. + if (!ticket_acc[ticket_val.cost_center]) + ticket_acc[ticket_val.cost_center] = Dinero(); + + ticket_acc[ticket_val.cost_center] = ticket_acc[ + ticket_val.cost_center + ].add( + Dinero({ + amount: Math.round((ticket_val.rate || 0) * 100), + }).multiply(ticket_val.actualhrs || ticket_val.productivehrs || 0) + ); + + return ticket_acc; + }, + {} + ); + const defaultCosts = job.bodyshop.md_responsibility_centers.defaults.costs; + + return { + PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => { + return acc.add(billTotalsByCostCenters[key]); + }, Dinero()), + PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add( + billTotalsByCostCenters[defaultCosts.PAP] || Dinero() + ), + PartsAMCost: billTotalsByCostCenters[defaultCosts.PAA] || Dinero(), + PartsReconditionedCost: Dinero(), + PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAR] || Dinero(), + PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), + SubletTotalCost: billTotalsByCostCenters[defaultCosts.PAS] || Dinero(), + BodyLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(), + RefinishLaborTotalCost: + ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(), + MechanicalLaborTotalCost: + ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(), + StructuralLaborTotalCost: + ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(), + PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(), + BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(), + MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), + TowingTotalCost: billTotalsByCostCenters[defaultCosts.TOW] || Dinero(), + StorageTotalCost: Dinero(), + DetailTotal: Dinero(), + DetailTotalCost: Dinero(), + SalesTaxTotalCost: Dinero(), + }; +}; + const StatusMapping = (status, md_ro_statuses) => { //EST, SCH, ARR, IPR, RDY, DEL, CLO, CAN, UNDEFINED. const { @@ -493,3 +611,6 @@ const generateNullDetailLine = () => { EstimateAmount: null, }; }; + +const repairOpCodes = ["OP4", "OP9", "OP10"]; +const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"]; diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 6ee17551a..f5f4e837d 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -338,6 +338,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) { rate_mapa rate_mash job_totals + driveable bodyshop { id shopname @@ -350,6 +351,8 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) { md_ro_statuses md_order_statuses autohouseid + md_responsibility_centers + jc_hourly_rates } joblines (where:{removed: {_eq:false}}){ id @@ -366,7 +369,10 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) { part_qty part_type oem_partno - billlines (order_by:{bill:{date:desc_nulls_last}}) { + lbr_op + profitcenter_part + profitcenter_labor + billlines (order_by:{bill:{date:desc_nulls_last}}) { actual_cost actual_price quantity @@ -377,7 +383,27 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) { invoice_number } } - } + + } bills { + id + federal_tax_rate + local_tax_rate + state_tax_rate + is_credit_memo + billlines { + actual_cost + cost_center + id + quantity + } + } + timetickets { + id + rate + cost_center + actualhrs + productivehrs + } area_of_damage employee_prep_rel { first_name