const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); const GraphQLClient = require("graphql-request").GraphQLClient; const _ = require("lodash"); const logger = require("../utils/logger"); // Dinero.defaultCurrency = "USD"; // Dinero.globalLocale = "en-CA"; Dinero.globalRoundingMode = "HALF_EVEN"; exports.payall = async function (req, res) { const BearerToken = req.headers.authorization; const { jobid } = req.body; logger.log("job-payroll-pay-all", "DEBUG", req.user.email, jobid, null); const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { headers: { Authorization: BearerToken, }, }); try { const { jobs_by_pk: job } = await client .setHeaders({ Authorization: BearerToken }) .request(queries.QUERY_JOB_PAYROLL_DATA, { id: jobid, }); //iterate over each ticket, building a hash of team -> employee to calculate total assigned hours. const assignmentHash = { unassigned: 0 }; const employeeHash = {}; // employeeid => Cieca labor type => rate => hours. Contains how many hours each person should be paid. job.joblines.forEach((jobline) => { if (jobline.mod_lb_hrs > 0) { //Check if the line is assigned. If not, keep track of it as an unassigned line by type. if (jobline.assigned_team === null) { assignmentHash.unassigned = assignmentHash.unassigned + jobline.mod_lb_hrs; } else { //Line is assigned. if (!assignmentHash[jobline.assigned_team]) { assignmentHash[jobline.assigned_team] = 0; } assignmentHash[jobline.assigned_team] = assignmentHash[jobline.assigned_team] + jobline.mod_lb_hrs; //Create the assignment breakdown. const theTeam = job.bodyshop.employee_teams.find( (team) => team.id === jobline.assigned_team ); theTeam.employee_team_members.forEach((tm) => { //Figure out how many hours they are owed at this line, and at what rate. console.log(tm); if (!employeeHash[tm.employee.id]) { employeeHash[tm.employee.id] = {}; } if (!employeeHash[tm.employee.id][jobline.mod_lbr_ty]) { employeeHash[tm.employee.id][jobline.mod_lbr_ty] = {}; } if ( !employeeHash[tm.employee.id][jobline.mod_lbr_ty][ tm.labor_rates[jobline.mod_lbr_ty] ] ) { employeeHash[tm.employee.id][jobline.mod_lbr_ty][ tm.labor_rates[jobline.mod_lbr_ty] ] = 0; } const hoursOwed = (tm.percentage / 100) * jobline.mod_lb_hrs; employeeHash[tm.employee.id][jobline.mod_lbr_ty][ tm.labor_rates[jobline.mod_lbr_ty] ] = employeeHash[tm.employee.id][jobline.mod_lbr_ty][ tm.labor_rates[jobline.mod_lbr_ty] ] + hoursOwed; }); } } }); const ticketHash = {}; // employeeid => Cieca labor type => rate => hours. //Calculate how much each employee has been paid so far. job.timetickets.forEach((ticket) => { if (!ticketHash[ticket.employeeid]) { ticketHash[ticket.employeeid] = {}; } if (!ticketHash[ticket.employeeid][ticket.ciecacode]) { ticketHash[ticket.employeeid][ticket.ciecacode] = {}; } if (!ticketHash[ticket.employeeid][ticket.ciecacode][ticket.rate]) { ticketHash[ticket.employeeid][ticket.ciecacode][ticket.rate] = 0; } ticketHash[ticket.employeeid][ticket.ciecacode][ticket.rate] = ticketHash[ticket.employeeid][ticket.ciecacode][ticket.rate] + ticket.productivehrs; //Add the rate }); if (assignmentHash.unassigned > 0) { res.json({ success: false, error: "Unassigned hours." }); return; } //Calculate how much time each tech should have by labor type. const comparison = compare(employeeHash, ticketHash); const ticketsToInsert = []; //Check the ones that are different first. Source of truth will be the employee hash. comparison.different.forEach((differentKey) => { const empVal = employeeHash[differentKey]; const ticketVal = ticketHash[differentKey]; ticketsToInsert.push({ jobid: job.id, employeeid: differentKey.split(".")[0], productivehrs: empVal - ticketVal, rate: differentKey.split(".")[2], memo: "Adjustment between expected and entered values. ", }); }); comparison.missing_from_first .filter((differentKey) => differentKey.split(".").length == 3) .forEach((differentKey) => { const empVal = employeeHash[differentKey]; const ticketVal = ticketHash[differentKey]; ticketsToInsert.push({ jobid: job.id, employeeid: differentKey.split(".")[0], productivehrs: empVal - ticketVal * -1, rate: differentKey.split(".")[2], memo: "Entered ticket reversed to match system payroll.", }); }); comparison.missing_from_second .filter((differentKey) => differentKey.split(".").length == 3) .forEach((differentKey) => { const empVal = employeeHash[differentKey]; const ticketVal = ticketHash[differentKey]; ticketsToInsert.push({ jobid: job.id, employeeid: differentKey.split(".")[0], productivehrs: empVal - ticketVal * -1, rate: differentKey.split(".")[2], memo: "Entered ticket reversed to match system payroll.", }); }); res.json({ assignmentHash, employeeHash, diff: getObjectDiff(employeeHash, ticketHash), compare: compare(employeeHash, ticketHash), }); } catch (error) { logger.log( "job-payroll-labor-totals-error", "ERROR", req.user.email, jobid, { jobid: jobid, error, } ); res.status(503).send(); } }; function getObjectDiff(obj1, obj2) { const diff = Object.keys(obj1).reduce((result, key) => { if (!obj2.hasOwnProperty(key)) { result.push(key); } else if (_.isEqual(obj1[key], obj2[key])) { const resultKeyIndex = result.indexOf(key); result.splice(resultKeyIndex, 1); } return result; }, Object.keys(obj2)); return diff; } var compare = function (a, b) { var result = { different: [], missing_from_first: [], missing_from_second: [], }; _.reduce( a, function (result, value, key) { if (b.hasOwnProperty(key)) { if (_.isEqual(value, b[key])) { return result; } else { if (typeof a[key] != typeof {} || typeof b[key] != typeof {}) { //dead end. result.different.push(key); return result; } else { var deeper = compare(a[key], b[key]); result.different = result.different.concat( _.map(deeper.different, (sub_path) => { return key + "." + sub_path; }) ); result.missing_from_second = result.missing_from_second.concat( _.map(deeper.missing_from_second, (sub_path) => { return key + "." + sub_path; }) ); result.missing_from_first = result.missing_from_first.concat( _.map(deeper.missing_from_first, (sub_path) => { return key + "." + sub_path; }) ); return result; } } } else { result.missing_from_second.push(key); return result; } }, result ); _.reduce( b, function (result, value, key) { if (a.hasOwnProperty(key)) { return result; } else { result.missing_from_first.push(key); return result; } }, result ); return result; };