feature/IO-3587-Comission-Cut - Implement

This commit is contained in:
Dave
2026-03-12 12:19:34 -04:00
parent b9b3e2c2aa
commit c7bb1a9c32
26 changed files with 1110 additions and 600 deletions

View File

@@ -1,11 +1,42 @@
const Dinero = require("dinero.js");
const queries = require("../graphql-client/queries");
const logger = require("../utils/logger");
const { CalculateExpectedHoursForJob } = require("./pay-all");
const { CalculateExpectedHoursForJob, RoundPayrollHours } = require("./pay-all");
const moment = require("moment");
// Dinero.defaultCurrency = "USD";
// Dinero.globalLocale = "en-CA";
Dinero.globalRoundingMode = "HALF_EVEN";
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;
@@ -21,12 +52,25 @@ exports.claimtask = async function (req, res) {
id: jobid
});
const theTaskPreset = job.bodyshop.md_tasks_presets.presets.find((tp) => tp.name === task);
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 = [];
@@ -35,32 +79,37 @@ exports.claimtask = async function (req, res) {
Object.keys(employeeHash).forEach((employeeIdKey) => {
//At the employee level.
Object.keys(employeeHash[employeeIdKey]).forEach((laborTypeKey) => {
//At the labor level
Object.keys(employeeHash[employeeIdKey][laborTypeKey]).forEach((rateKey) => {
//At the rate level.
const expectedHours = employeeHash[employeeIdKey][laborTypeKey][rateKey] * (theTaskPreset.percent / 100);
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: rateKey,
ciecacode: laborTypeKey,
flat_rate: true,
cost_center: job.bodyshop.md_responsibility_centers.defaults.costs[laborTypeKey],
memo: `*Flagged Task* ${theTaskPreset.memo}`
});
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.
const insertResult = await client.request(queries.INSERT_TIME_TICKETS, {
await client.request(queries.INSERT_TIME_TICKETS, {
timetickets: ticketsToInsert.filter((ticket) => ticket.productivehrs !== 0)
});
const updateResult = await client.request(queries.UPDATE_JOB, {
await client.request(queries.UPDATE_JOB, {
jobId: job.id,
job: {
status: theTaskPreset.nextstatus,
@@ -82,6 +131,6 @@ exports.claimtask = async function (req, res) {
jobid: jobid,
error
});
res.status(503).send();
res.status(400).json({ success: false, error: error.message });
}
};