diff --git a/client/src/components/jobs-detail-labor/jobs-detail-labor.component.jsx b/client/src/components/jobs-detail-labor/jobs-detail-labor.component.jsx
index 2b0428772..c03d9d2d4 100644
--- a/client/src/components/jobs-detail-labor/jobs-detail-labor.component.jsx
+++ b/client/src/components/jobs-detail-labor/jobs-detail-labor.component.jsx
@@ -88,6 +88,7 @@ export function JobsDetailLaborContainer({
jobId={jobId}
joblines={joblines}
timetickets={timetickets}
+ refetch={refetch}
adjustments={adjustments}
/>
@@ -97,6 +98,7 @@ export function JobsDetailLaborContainer({
jobId={jobId}
joblines={joblines}
timetickets={timetickets}
+ refetch={refetch}
adjustments={adjustments}
/>
diff --git a/client/src/components/labor-allocations-table/labor-allocations-table.payroll.component.jsx b/client/src/components/labor-allocations-table/labor-allocations-table.payroll.component.jsx
index 7e0add2de..f7989ce83 100644
--- a/client/src/components/labor-allocations-table/labor-allocations-table.payroll.component.jsx
+++ b/client/src/components/labor-allocations-table/labor-allocations-table.payroll.component.jsx
@@ -13,6 +13,7 @@ import LaborAllocationsAdjustmentEdit from "../labor-allocations-adjustment-edit
import "./labor-allocations-table.styles.scss";
import { CalculateAllocationsTotals } from "./labor-allocations-table.utility";
import axios from "axios";
+import { onlyUnique } from "../../utils/arrayHelper";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -26,6 +27,7 @@ export function PayrollLaborAllocationsTable({
bodyshop,
adjustments,
technician,
+ refetch,
}) {
const { t } = useTranslation();
const [totals, setTotals] = useState([]);
@@ -39,11 +41,16 @@ export function PayrollLaborAllocationsTable({
});
useEffect(() => {
- if (!!joblines && !!timetickets && !!bodyshop);
+ async function CalculateTotals() {
+ const { data } = await axios.post("/payroll/calculatelabor", {
+ jobid: jobId,
+ });
+ setTotals(data);
+ }
- setTotals(
- CalculateAllocationsTotals(bodyshop, joblines, timetickets, adjustments)
- );
+ if (!!joblines && !!timetickets && !!bodyshop) {
+ CalculateTotals();
+ }
if (!jobId) setTotals([]);
}, [joblines, timetickets, bodyshop, adjustments, jobId]);
@@ -54,55 +61,46 @@ export function PayrollLaborAllocationsTable({
const columns = [
{
- title: t("timetickets.fields.cost_center"),
- dataIndex: "cost_center",
- key: "cost_center",
- defaultSortOrder: "cost_center",
- sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
- sortOrder:
- state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order,
- render: (text, record) => `${record.cost_center} (${record.mod_lbr_ty})`,
+ title: t("timetickets.fields.employee"),
+ dataIndex: "employeeid",
+ key: "employeeid",
+ render: (text, record) => {
+ if (record.employeeid === undefined) {
+ return "Unassigned";
+ }
+ const emp = bodyshop.employees.find((e) => e.id === record.employeeid);
+ return `${emp?.first_name} ${emp?.last_name}`;
+ },
+ },
+ {
+ title: t("joblines.fields.mod_lbr_ty"),
+ dataIndex: "mod_lbr_ty",
+ key: "mod_lbr_ty",
+ },
+ {
+ title: t("timetickets.fields.rate"),
+ dataIndex: "rate",
+ key: "rate",
},
{
title: t("jobs.labels.hrs_total"),
- dataIndex: "total",
- key: "total",
- sorter: (a, b) => a.total - b.total,
+ dataIndex: "expectedHours",
+ key: "expectedHours",
+ sorter: (a, b) => a.expectedHours - b.expectedHours,
sortOrder:
- state.sortedInfo.columnKey === "total" && state.sortedInfo.order,
- render: (text, record) => record.total.toFixed(2),
+ state.sortedInfo.columnKey === "expectedHours" &&
+ state.sortedInfo.order,
+ render: (text, record) => record.expectedHours.toFixed(5),
},
{
title: t("jobs.labels.hrs_claimed"),
- dataIndex: "hrs_claimed",
- key: "hrs_claimed",
- sorter: (a, b) => a.claimed - b.claimed,
+ dataIndex: "claimedHours",
+ key: "claimedHours",
+ sorter: (a, b) => a.claimedHours - b.claimedHours,
sortOrder:
- state.sortedInfo.columnKey === "claimed" && state.sortedInfo.order,
- render: (text, record) => record.claimed && record.claimed.toFixed(2),
- },
- {
- title: t("jobs.labels.adjustments"),
- dataIndex: "adjustments",
- key: "adjustments",
- sorter: (a, b) => a.adjustments - b.adjustments,
- sortOrder:
- state.sortedInfo.columnKey === "adjustments" && state.sortedInfo.order,
- render: (text, record) => (
-
- {record.adjustments.toFixed(2)}
- {!technician && (
-
-
-
- )}
-
- ),
+ state.sortedInfo.columnKey === "claimedHours" && state.sortedInfo.order,
+ render: (text, record) =>
+ record.claimedHours && record.claimedHours.toFixed(5),
},
{
title: t("jobs.labels.difference"),
@@ -112,15 +110,22 @@ export function PayrollLaborAllocationsTable({
sorter: (a, b) => a.difference - b.difference,
sortOrder:
state.sortedInfo.columnKey === "difference" && state.sortedInfo.order,
- render: (text, record) => (
- = 0 ? "green" : "red",
- }}
- >
- {_.round(record.difference, 1)}
-
- ),
+ render: (text, record) => {
+ const difference = _.round(
+ record.expectedHours - record.claimedHours,
+ 5
+ );
+
+ return (
+ = 0 ? "green" : "red",
+ }}
+ >
+ {difference}
+
+ );
+ },
},
];
const convertedTableCols = [
@@ -182,7 +187,7 @@ export function PayrollLaborAllocationsTable({
render: (text, record) =>
record.convertedtolbr_data &&
record.convertedtolbr_data.mod_lb_hrs &&
- record.convertedtolbr_data.mod_lb_hrs.toFixed(2),
+ record.convertedtolbr_data.mod_lb_hrs.toFixed(5),
},
];
@@ -194,10 +199,10 @@ export function PayrollLaborAllocationsTable({
totals &&
totals.reduce(
(acc, val) => {
- acc.hrs_total += val.total;
- acc.hrs_claimed += val.claimed;
- acc.adjustments += val.adjustments;
- acc.difference += val.difference;
+ acc.hrs_total += val.expectedHours;
+ acc.hrs_claimed += val.claimedHours;
+ // acc.adjustments += val.adjustments;
+ acc.difference += val.expectedHours - val.claimedHours;
return acc;
},
{ hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0 }
@@ -211,18 +216,21 @@ export function PayrollLaborAllocationsTable({
const { data } = await axios.post("/payroll/payall", {
jobid: jobId,
});
-
- setTotals(
- CalculateAllocationsTotals(
- bodyshop,
- joblines,
- [...timetickets, ...data],
- adjustments
- )
- );
+ refetch();
}}
>
- Calc
+ Pay All Test
+
+
+
+
- {summary.hrs_total.toFixed(2)}
+ {summary.hrs_total.toFixed(5)}
- {summary.hrs_claimed.toFixed(2)}
+ {summary.hrs_claimed.toFixed(5)}
+
- {summary.adjustments.toFixed(2)}
-
-
- {summary.difference.toFixed(2)}
+ {summary.difference.toFixed(5)}
)}
diff --git a/client/src/components/shop-teams/shop-employee-teams.form.component.jsx b/client/src/components/shop-teams/shop-employee-teams.form.component.jsx
index 904acda7d..d811eb794 100644
--- a/client/src/components/shop-teams/shop-employee-teams.form.component.jsx
+++ b/client/src/components/shop-teams/shop-employee-teams.form.component.jsx
@@ -199,7 +199,7 @@ export function ShopEmployeeTeamsFormComponent({ bodyshop }) {
},
]}
>
-
+
employee to calculate total assigned hours.
+ const { employeeHash, assignmentHash } = CalculateExpectedHoursForJob(job);
+ const ticketHash = CalculateTicketsHoursForJob(job);
- const assignmentHash = { unassigned: 0 };
- 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[jobline.mod_lbr_ty] =
- assignmentHash.unassigned[jobline.mod_lbr_ty] + jobline.mod_lb_hrs;
- } else {
- //Line is assigned.
- if (!assignmentHash[jobline.assigned_team]) {
- assignmentHash[jobline.assigned_team] = 0;
+ const totals = [];
+
+ //Iteratively go through all 4 levels of the object and create an array that can be presented.
+ // use the employee hash as the golden record (i.e. what they should have), and add what they've claimed.
+ //While going through, delete items from ticket hash.
+ //Anything left in ticket hash is an extra entered item.
+
+ 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];
+ //Will the following line fail? Probably if it doesn't exist.
+ const claimedHours = get(
+ ticketHash,
+ `${employeeIdKey}.${laborTypeKey}.${rateKey}`
+ );
+ if (claimedHours) {
+ delete ticketHash[employeeIdKey][laborTypeKey][rateKey];
+ }
+
+ totals.push({
+ employeeid: employeeIdKey,
+ rate: rateKey,
+ mod_lbr_ty: laborTypeKey,
+ expectedHours,
+ claimedHours: claimedHours || 0,
+ });
}
- assignmentHash[jobline.assigned_team] =
- assignmentHash[jobline.assigned_team] + jobline.mod_lb_hrs;
- }
- }
+ );
+ });
});
- res.json(assignmentHash);
+
+ Object.keys(ticketHash).forEach((employeeIdKey) => {
+ //At the employee level.
+ Object.keys(ticketHash[employeeIdKey]).forEach((laborTypeKey) => {
+ //At the labor level
+ Object.keys(ticketHash[employeeIdKey][laborTypeKey]).forEach(
+ (rateKey) => {
+ //At the rate level.
+ const expectedHours = 0;
+ //Will the following line fail? Probably if it doesn't exist.
+ const claimedHours = get(
+ ticketHash,
+ `${employeeIdKey}.${laborTypeKey}.${rateKey}`
+ );
+ if (claimedHours) {
+ delete ticketHash[employeeIdKey][laborTypeKey][rateKey];
+ }
+
+ totals.push({
+ employeeid: employeeIdKey,
+ rate: rateKey,
+ mod_lbr_ty: laborTypeKey,
+ expectedHours,
+ claimedHours: claimedHours || 0,
+ });
+ }
+ );
+ });
+ });
+ if (assignmentHash.unassigned > 0) {
+ totals.push({
+ employeeid: undefined,
+ //rate: rateKey,
+ //mod_lbr_ty: laborTypeKey,
+ expectedHours: assignmentHash.unassigned,
+ claimedHours: 0,
+ });
+ }
+ res.json(totals);
+ //res.json(assignmentHash);
} catch (error) {
logger.log(
- "job-payroll-labor-totals-error",
+ "job-payroll-calculate-labor-error",
"ERROR",
req.user.email,
jobid,
@@ -57,3 +122,9 @@ exports.calculateLaborTotals = async function (req, res) {
res.status(503).send();
}
};
+
+get = function (obj, key) {
+ return key.split(".").reduce(function (o, x) {
+ return typeof o == "undefined" || o === null ? o : o[x];
+ }, obj);
+};
diff --git a/server/payroll/pay-all.js b/server/payroll/pay-all.js
index 4fbf3f7af..877ad159c 100644
--- a/server/payroll/pay-all.js
+++ b/server/payroll/pay-all.js
@@ -5,13 +5,14 @@ const _ = require("lodash");
const rdiff = require("recursive-diff");
const logger = require("../utils/logger");
+const { json } = require("body-parser");
// 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;
+ const { jobid, calculateOnly } = req.body;
logger.log("job-payroll-pay-all", "DEBUG", req.user.email, jobid, null);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
@@ -28,187 +29,117 @@ exports.payall = async function (req, res) {
//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.
-
- 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 * jobline.mod_lb_hrs) / 100;
- 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
- });
-
+ const { employeeHash, assignmentHash } = CalculateExpectedHoursForJob(job);
+ const ticketHash = CalculateTicketsHoursForJob(job);
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 recursiveDiff = rdiff.getDiff(ticketHash, employeeHash, true); //Doing this order creates a diff of changes on the ticket hash to make it the same as the employee hash.
+ //Doing this order creates a diff of changes on the ticket hash to make it the same as the employee hash.
+ const recursiveDiff = rdiff.getDiff(ticketHash, employeeHash, true);
const ticketsToInsert = [];
recursiveDiff.forEach((diff) => {
//Every iteration is what we would need to insert into the time ticket hash
//so that it would match the employee hash exactly.
-
const path = diffParser(diff);
-
if (diff.op === "add") {
- if (typeof diff.val === "object") {
+ if (typeof diff.val === "object" && Object.keys(diff.val).length > 1) {
//Multiple values to add.
Object.keys(diff.val).forEach((key) => {
+ console.log("Hours", diff.val[key][Object.keys(diff.val[key])[0]]);
+ console.log("Rate", Object.keys(diff.val[key])[0]);
ticketsToInsert.push({
jobid: job.id,
+ bodyshopid: job.bodyshop.id,
employeeid: path.employeeid,
productivehrs: diff.val[key][Object.keys(diff.val[key])[0]],
rate: Object.keys(diff.val[key])[0],
ciecacode: key,
+ cost_center:
+ job.bodyshop.md_responsibility_centers.defaults.costs[key],
flat_rate: true,
- memo: "Add hours.",
+ memo: "*System* Hours added during Pay All function. (multi)",
});
});
} else {
//Only the 1 value to add.
ticketsToInsert.push({
jobid: job.id,
+ bodyshopid: job.bodyshop.id,
employeeid: path.employeeid,
productivehrs: path.hours,
rate: path.rate,
ciecacode: path.mod_lbr_ty,
flat_rate: true,
- memo: "Add hours.",
+ cost_center:
+ job.bodyshop.md_responsibility_centers.defaults.costs[
+ path.mod_lbr_ty
+ ],
+ memo: "*System* Hours added during Pay All function.",
});
}
} else if (diff.op === "update") {
} else {
//Has to be a delete
- ticketsToInsert.push({
- jobid: job.id,
- employeeid: path.employeeid,
- productivehrs: diff.oldVal * -1,
- rate: path.rate,
- ciecacode: path.mod_lbr_ty,
- flat_rate: true,
- memo: "Remove unneeded hours. (Rate/Unassigned).",
- });
+
+ //////
+
+ if (
+ typeof diff.oldVal === "object" &&
+ Object.keys(diff.oldVal).length > 1
+ ) {
+ //Multiple oldValues to add.
+ Object.keys(diff.oldVal).forEach((key) => {
+ console.log(
+ "Hours",
+ diff.oldVal[key][Object.keys(diff.oldVal[key])[0]]
+ );
+ console.log("Rate", Object.keys(diff.oldVal[key])[0]);
+ ticketsToInsert.push({
+ jobid: job.id,
+ bodyshopid: job.bodyshop.id,
+ employeeid: path.employeeid,
+ productivehrs:
+ diff.oldVal[key][Object.keys(diff.oldVal[key])[0]] * -1,
+ rate: Object.keys(diff.oldVal[key])[0],
+ ciecacode: key,
+ cost_center:
+ job.bodyshop.md_responsibility_centers.defaults.costs[key],
+ flat_rate: true,
+ memo: "*System* Hours removed during Pay All function. (Change in rate, unassignment, etc.) *multi*",
+ });
+ });
+ } else {
+ //Only the 1 value to add.
+ ticketsToInsert.push({
+ jobid: job.id,
+ bodyshopid: job.bodyshop.id,
+ employeeid: path.employeeid,
+ productivehrs: path.hours * -1,
+ rate: path.rate,
+ ciecacode: path.mod_lbr_ty,
+ cost_center:
+ job.bodyshop.md_responsibility_centers.defaults.costs[
+ path.mod_lbr_ty
+ ],
+ flat_rate: true,
+ memo: "*System* Hours removed during Pay All function. (Change in rate, unassignment, etc.)",
+ });
+ }
}
});
- // //Check the ones that are different first. Source of truth will be the employee hash.
- // comparison.different.forEach((differentKey) => {
- // const empVal = fetchFromObject(employeeHash, differentKey) || 0;
- // const ticketVal = fetchFromObject(ticketHash, differentKey) || 0;
- // const splitKey = splitEmployeeKey(differentKey);
+ const insertResult = await client.request(queries.INSERT_TIME_TICKETS, {
+ timetickets: ticketsToInsert.filter(
+ (ticket) => ticket.productivehrs !== 0
+ ),
+ });
- // ticketsToInsert.push({
- // jobid: job.id,
- // employeeid: splitKey.employeeid,
- // productivehrs: empVal - ticketVal,
- // rate: splitKey.rate,
- // ciecacode: splitKey.mod_lbr_ty,
- // flat_rate: true,
- // memo: "Adjustment between expected and entered values.",
- // });
- // });
-
- // comparison.missing_from_first //Missing from the employee hash, but present in the ticket hash.
- // .filter((differentKey) => differentKey.split(".").length == 3)
- // .forEach((differentKey) => {
- // const empVal = fetchFromObject(employeeHash, differentKey) || 0;
- // const ticketVal = fetchFromObject(ticketHash, differentKey) || 0;
- // const splitKey = splitEmployeeKey(differentKey);
-
- // ticketsToInsert.push({
- // jobid: job.id,
- // employeeid: differentKey.split(".")[0],
- // productivehrs: empVal - ticketVal * -1,
- // rate: splitKey.rate,
- // ciecacode: splitKey.mod_lbr_ty,
- // flat_rate: true,
- // memo: "Entered ticket reversed to match system payroll.",
- // });
- // });
- // comparison.missing_from_second //Missing from the ticket hash, but present in the employee hash.
- // .filter((differentKey) => differentKey.split(".").length == 3)
- // .forEach((differentKey) => {
- // const empVal = fetchFromObject(employeeHash, differentKey) || 0;
- // const ticketVal = fetchFromObject(ticketHash, differentKey) || 0;
- // const splitKey = splitEmployeeKey(differentKey);
-
- // ticketsToInsert.push({
- // jobid: job.id,
- // employeeid: differentKey.split(".")[0],
- // productivehrs: empVal - ticketVal * -1,
- // rate: splitKey.rate,
- // ciecacode: splitKey.mod_lbr_ty,
- // flat_rate: true,
- // memo: "Entered ticket reversed to match system payroll.",
- // });
- // });
-
- res.json(ticketsToInsert);
+ res.json(ticketsToInsert.filter((ticket) => ticket.productivehrs !== 0));
} catch (error) {
logger.log(
"job-payroll-labor-totals-error",
@@ -224,102 +155,137 @@ exports.payall = async function (req, res) {
}
};
-// 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;
-// };
-
-// function fetchFromObject(obj, prop) {
-// if (typeof obj === "undefined") {
-// return false;
-// }
-
-// var _index = prop.indexOf(".");
-// if (_index > -1) {
-// return fetchFromObject(
-// obj[prop.substring(0, _index)],
-// prop.substr(_index + 1)
-// );
-// }
-
-// return obj[prop];
-// }
-
-// function splitEmployeeKey(key) {
-// const splitArray = differentKey.split(".");
-// // employeeid => Cieca labor type => rate
-// return {
-// employeeid: splitArray[0],
-// mod_lbr_ty: splitArray[1],
-// rate: splitArray[2],
-// hours: splitArray[3],
-// };
-// }
-
function diffParser(diff) {
- return {
- employeeid: diff.path[0],
- mod_lbr_ty: diff.path[1],
- rate: diff.path[2],
- hours: diff.val,
+ const type = typeof diff.oldVal;
+ let mod_lbr_ty, rate, hours;
+
+ if (diff.path.length === 1) {
+ if (diff.op === "add") {
+ mod_lbr_ty = Object.keys(diff.val)[0];
+ rate = Object.keys(diff.val[mod_lbr_ty])[0];
+ // hours = diff.oldVal[mod_lbr_ty][rate];
+ } else {
+ mod_lbr_ty = Object.keys(diff.oldVal)[0];
+ rate = Object.keys(diff.oldVal[mod_lbr_ty])[0];
+ // hours = diff.oldVal[mod_lbr_ty][rate];
+ }
+ } else if (diff.path.length === 2) {
+ mod_lbr_ty = diff.path[1];
+ if (diff.op === "add") {
+ rate = Object.keys(diff.val)[0];
+ } else {
+ rate = Object.keys(diff.oldVal)[0];
+ }
+ } else if (diff.path.length === 3) {
+ mod_lbr_ty = diff.path[1];
+ rate = diff.path[2];
+ //hours = 0;
+ }
+
+ //Set the hours
+ if (
+ typeof diff.val === "number" &&
+ diff.val !== null &&
+ diff.val !== undefined
+ ) {
+ hours = diff.val;
+ } else if (diff.val !== null && diff.val !== undefined) {
+ hours = diff.val[Object.keys(diff.val)[0]];
+ } else if (
+ typeof diff.oldVal === "number" &&
+ diff.oldVal !== null &&
+ diff.oldVal !== undefined
+ ) {
+ hours = diff.oldVal;
+ } else {
+ hours = diff.oldVal[Object.keys(diff.oldVal)[0]];
+ }
+
+ const ret = {
+ multiVal: false,
+ employeeid: diff.path[0], // Always True
+ mod_lbr_ty,
+ rate,
+ hours,
};
+ return ret;
}
+
+function CalculateExpectedHoursForJob(job) {
+ 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.
+
+ 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 * jobline.mod_lb_hrs) / 100;
+ 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;
+ });
+ }
+ }
+ });
+
+ return { assignmentHash, employeeHash };
+}
+
+function CalculateTicketsHoursForJob(job) {
+ 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;
+ });
+ return ticketHash;
+}
+
+exports.CalculateExpectedHoursForJob = CalculateExpectedHoursForJob;
+exports.CalculateTicketsHoursForJob = CalculateTicketsHoursForJob;
diff --git a/server/payroll/payroll.js b/server/payroll/payroll.js
index c069976c9..b7a8b7087 100644
--- a/server/payroll/payroll.js
+++ b/server/payroll/payroll.js
@@ -1,2 +1,2 @@
-exports.calculateLaborTotals = require("./calculate-totals").calculateLaborTotals;
+exports.calculatelabor = require("./calculate-totals").calculatelabor;
exports.payall = require("./pay-all").payall;