const queries = require("../graphql-client/queries"); const logger = require("../utils/logger"); const { CalculateExpectedHoursForJob, RoundPayrollHours } = require("./pay-all"); const moment = require("moment"); const normalizePercent = (value) => Math.round((Number(value || 0) + Number.EPSILON) * 10000) / 10000; const getTaskPresetAllocationError = (taskPresets = []) => { const totalsByLaborType = {}; taskPresets.forEach((taskPreset) => { const percent = normalizePercent(taskPreset?.percent); if (!percent) { return; } const laborTypes = Array.isArray(taskPreset?.hourstype) ? taskPreset.hourstype : []; laborTypes.forEach((laborType) => { if (!laborType) { return; } totalsByLaborType[laborType] = normalizePercent((totalsByLaborType[laborType] || 0) + percent); }); }); const overAllocatedType = Object.entries(totalsByLaborType).find(([, total]) => total > 100); if (!overAllocatedType) { return null; } const [laborType, total] = overAllocatedType; return `Task preset percentages for labor type ${laborType} total ${total}% and cannot exceed 100%.`; }; exports.GetTaskPresetAllocationError = getTaskPresetAllocationError; exports.claimtask = async function (req, res) { const { jobid, task, calculateOnly, employee } = req.body; logger.log("job-payroll-pay-all", "DEBUG", req.user.email, jobid, null); const BearerToken = req.BearerToken; const client = req.userGraphQLClient; try { const { jobs_by_pk: job } = await client .setHeaders({ Authorization: BearerToken }) .request(queries.QUERY_JOB_PAYROLL_DATA, { id: jobid }); const taskPresets = job.bodyshop?.md_tasks_presets?.presets || []; const taskPresetAllocationError = getTaskPresetAllocationError(taskPresets); if (taskPresetAllocationError) { res.status(400).json({ success: false, error: taskPresetAllocationError }); return; } const theTaskPreset = taskPresets.find((tp) => tp.name === task); if (!theTaskPreset) { res.status(400).json({ success: false, error: "Provided task preset not found." }); return; } const taskAlreadyCompleted = (job.completed_tasks || []).some((completedTask) => completedTask?.name === task); if (taskAlreadyCompleted) { res.status(400).json({ success: false, error: "Provided task preset has already been completed for this job." }); return; } //Get all of the assignments that are filtered. const { assignmentHash, employeeHash } = CalculateExpectedHoursForJob(job, theTaskPreset.hourstype); const ticketsToInsert = []; //Then add them in based on a percentage to each employee. Object.keys(employeeHash).forEach((employeeIdKey) => { //At the employee level. Object.keys(employeeHash[employeeIdKey]).forEach((laborTypeKey) => { const expected = employeeHash[employeeIdKey][laborTypeKey]; const expectedHours = RoundPayrollHours(expected.hours * (theTaskPreset.percent / 100)); ticketsToInsert.push({ task_name: task, jobid: job.id, bodyshopid: job.bodyshop.id, employeeid: employeeIdKey, productivehrs: expectedHours, rate: expected.rate, ciecacode: laborTypeKey, flat_rate: true, created_by: employee?.name || req.user.email, payout_context: { ...(expected.payoutContext || {}), generated_by: req.user.email, generated_at: new Date().toISOString(), generated_from: "claimtask", task_name: task }, cost_center: job.bodyshop.md_responsibility_centers.defaults.costs[laborTypeKey], memo: `*Flagged Task* ${theTaskPreset.memo}` }); }); }); if (!calculateOnly) { //Insert the time ticekts if we're not just calculating them. await client.request(queries.INSERT_TIME_TICKETS, { timetickets: ticketsToInsert.filter((ticket) => ticket.productivehrs !== 0) }); await client.request(queries.UPDATE_JOB, { jobId: job.id, job: { status: theTaskPreset.nextstatus, completed_tasks: [ ...job.completed_tasks, { name: task, completedat: moment(), completed_by: employee, useremail: req.user.email } ] } }); } res.json({ unassignedHours: assignmentHash.unassigned, ticketsToInsert }); } catch (error) { logger.log("job-payroll-claim-task-error", "ERROR", req.user.email, jobid, { jobid: jobid, error }); res.status(400).json({ success: false, error: error.message }); } };