- the great reformat

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-02-06 18:20:58 -05:00
parent 30c530bcc4
commit e83badb454
912 changed files with 108516 additions and 107493 deletions

View File

@@ -4,311 +4,311 @@ const Dinero = require("dinero.js");
const moment = require("moment-timezone");
const logger = require("../utils/logger");
const _ = require("lodash");
const { filter } = require("lodash");
const {filter} = require("lodash");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
exports.job = async (req, res) => {
const { jobId } = req.body;
const {jobId} = req.body;
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
try {
logger.log("smart-scheduling-start", "DEBUG", req.user.email, jobId, null);
try {
logger.log("smart-scheduling-start", "DEBUG", req.user.email, jobId, null);
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QUERY_UPCOMING_APPOINTMENTS, {
now: moment().startOf("day"),
jobId: jobId,
});
const result = await client
.setHeaders({Authorization: BearerToken})
.request(queries.QUERY_UPCOMING_APPOINTMENTS, {
now: moment().startOf("day"),
jobId: jobId,
});
const { jobs_by_pk, blockedDays, prodJobs, arrJobs, compJobs } = result;
const { ssbuckets, workingdays, timezone, ss_configuration } =
result.jobs_by_pk.bodyshop;
const jobHrs = result.jobs_by_pk.jobhrs.aggregate.sum.mod_lb_hrs;
const {jobs_by_pk, blockedDays, prodJobs, arrJobs, compJobs} = result;
const {ssbuckets, workingdays, timezone, ss_configuration} =
result.jobs_by_pk.bodyshop;
const jobHrs = result.jobs_by_pk.jobhrs.aggregate.sum.mod_lb_hrs;
const JobBucket = ssbuckets.filter(
(bucket) =>
bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true)
)[0];
const load = {
productionTotal: {},
productionHours: 0,
};
//Set the current load.
ssbuckets.forEach((bucket) => {
load.productionTotal[bucket.id] = { count: 0, label: bucket.label };
});
const JobBucket = ssbuckets.filter(
(bucket) =>
bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true)
)[0];
const load = {
productionTotal: {},
productionHours: 0,
};
//Set the current load.
ssbuckets.forEach((bucket) => {
load.productionTotal[bucket.id] = {count: 0, label: bucket.label};
});
const filteredProdJobsList = prodJobs.filter(
(j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
);
const filteredProdJobsList = prodJobs.filter(
(j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
);
filteredProdJobsList.forEach((item) => {
//Add all of the jobs currently in production to the buckets so that we have a starting point.
const bucketId = CheckJobBucket(ssbuckets, item);
if (bucketId) {
load.productionTotal[bucketId].count =
load.productionTotal[bucketId].count + 1;
} else {
console.log("Uh oh, this job doesn't fit in a bucket!", item);
}
});
filteredProdJobsList.forEach((item) => {
//Add all of the jobs currently in production to the buckets so that we have a starting point.
const bucketId = CheckJobBucket(ssbuckets, item);
if (bucketId) {
load.productionTotal[bucketId].count =
load.productionTotal[bucketId].count + 1;
} else {
console.log("Uh oh, this job doesn't fit in a bucket!", item);
}
});
// const filteredArrJobs = arrJobs.filter(
// (j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
// );
const filteredArrJobs = [];
// const filteredArrJobs = arrJobs.filter(
// (j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
// );
const filteredArrJobs = [];
arrJobs.forEach((item) => {
let isSameBucket = false;
if (JobBucket.id === CheckJobBucket(ssbuckets, item)) {
filteredArrJobs.push(item);
isSameBucket = true;
}
arrJobs.forEach((item) => {
let isSameBucket = false;
if (JobBucket.id === CheckJobBucket(ssbuckets, item)) {
filteredArrJobs.push(item);
isSameBucket = true;
}
let jobHours =
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
let jobHours =
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
const AddJobForSchedulingCalc = !item.inproduction;
const AddJobForSchedulingCalc = !item.inproduction;
const itemDate = moment(item.actual_in || item.scheduled_in)
.tz(timezone)
.format("yyyy-MM-DD");
if (isSameBucket) {
if (!!load[itemDate]) {
load[itemDate].hoursIn =
(load[itemDate].hoursIn || 0) + AddJobForSchedulingCalc
? jobHours
: 0;
if (AddJobForSchedulingCalc) load[itemDate].jobsIn.push(item);
} else {
load[itemDate] = {
jobsIn: AddJobForSchedulingCalc ? [item] : [],
jobsOut: [],
hoursIn: AddJobForSchedulingCalc ? jobHours : 0,
};
const itemDate = moment(item.actual_in || item.scheduled_in)
.tz(timezone)
.format("yyyy-MM-DD");
if (isSameBucket) {
if (!!load[itemDate]) {
load[itemDate].hoursIn =
(load[itemDate].hoursIn || 0) + AddJobForSchedulingCalc
? jobHours
: 0;
if (AddJobForSchedulingCalc) load[itemDate].jobsIn.push(item);
} else {
load[itemDate] = {
jobsIn: AddJobForSchedulingCalc ? [item] : [],
jobsOut: [],
hoursIn: AddJobForSchedulingCalc ? jobHours : 0,
};
}
}
if (!load[itemDate]) {
load[itemDate] = {
jobsIn: [],
jobsOut: [],
hoursIn: 0,
hoursInTotal: 0,
};
}
load[itemDate].hoursInTotal =
(load[itemDate].hoursInTotal || 0) + jobHours;
});
//Get the completing jobs.
let problemJobs = [];
const filteredCompJobs = compJobs.filter(
(j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
);
filteredCompJobs.forEach((item) => {
const inProdJobs = filteredProdJobsList.find((p) => p.id === item.id);
const inArrJobs = filteredArrJobs.find((p) => p.id === item.id);
const AddJobForSchedulingCalc = inProdJobs || inArrJobs;
const itemDate = moment(
item.actual_completion || item.scheduled_completion
)
.tz(timezone)
.format("yyyy-MM-DD");
if (!!load[itemDate]) {
load[itemDate].hoursOut =
(load[itemDate].hoursOut || 0) + AddJobForSchedulingCalc
? item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs
: 0;
if (AddJobForSchedulingCalc) load[itemDate].jobsOut.push(item);
} else {
load[itemDate] = {
jobsOut: AddJobForSchedulingCalc ? [item] : [],
hoursOut: AddJobForSchedulingCalc
? item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs
: 0,
};
}
});
//Propagate the expected load to each day.
const yesterday = moment().tz(timezone).subtract(1, "day");
const today = moment().tz(timezone);
const end = moment.max([
...filteredArrJobs.map((a) => moment(a.scheduled_in).tz(timezone)),
...filteredCompJobs
.map((p) =>
moment(p.actual_completion || p.scheduled_completion).tz(timezone)
)
.filter((p) => p.isValid() && p.isAfter(yesterday)),
moment().tz(timezone).add(15, "days"),
]);
const range = Math.round(
moment.duration(end.add(20, "days").diff(today)).asDays()
);
for (var day = 0; day < range; day++) {
const current = moment(today)
.tz(timezone)
.add(day, "days")
.format("yyyy-MM-DD");
const prev = moment(today)
.tz(timezone)
.add(day - 1, "days")
.format("yyyy-MM-DD");
if (!!!load[current]) {
load[current] = {};
}
if (day === 0) {
//Starting on day 1. The load is current.
load[current].expectedLoad = CalculateLoad(
load.productionTotal,
ssbuckets,
load[current].jobsIn || [],
load[current].jobsOut || []
);
} else {
load[current].expectedLoad = CalculateLoad(
load[prev].expectedLoad,
ssbuckets,
load[current].jobsIn || [],
load[current].jobsOut || []
);
}
}
}
if (!load[itemDate]) {
load[itemDate] = {
jobsIn: [],
jobsOut: [],
hoursIn: 0,
hoursInTotal: 0,
};
}
load[itemDate].hoursInTotal =
(load[itemDate].hoursInTotal || 0) + jobHours;
});
//Add in all of the blocked days.
//Get the completing jobs.
let problemJobs = [];
const filteredCompJobs = compJobs.filter(
(j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
);
blockedDays.forEach((b) => {
//Find it in the load, set it as blocked.
const startIsoFormat = moment(b.start).tz(timezone).format("YYYY-MM-DD");
if (load[startIsoFormat]) load[startIsoFormat].blocked = true;
else {
load[startIsoFormat] = {blocked: true};
}
});
// //Propose the first 10 dates where we are below target.
filteredCompJobs.forEach((item) => {
const inProdJobs = filteredProdJobsList.find((p) => p.id === item.id);
const inArrJobs = filteredArrJobs.find((p) => p.id === item.id);
const AddJobForSchedulingCalc = inProdJobs || inArrJobs;
const itemDate = moment(
item.actual_completion || item.scheduled_completion
)
.tz(timezone)
.format("yyyy-MM-DD");
if (!!load[itemDate]) {
load[itemDate].hoursOut =
(load[itemDate].hoursOut || 0) + AddJobForSchedulingCalc
? item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs
: 0;
if (AddJobForSchedulingCalc) load[itemDate].jobsOut.push(item);
} else {
load[itemDate] = {
jobsOut: AddJobForSchedulingCalc ? [item] : [],
hoursOut: AddJobForSchedulingCalc
? item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs
: 0,
};
}
});
//Propagate the expected load to each day.
const yesterday = moment().tz(timezone).subtract(1, "day");
const today = moment().tz(timezone);
const end = moment.max([
...filteredArrJobs.map((a) => moment(a.scheduled_in).tz(timezone)),
...filteredCompJobs
.map((p) =>
moment(p.actual_completion || p.scheduled_completion).tz(timezone)
)
.filter((p) => p.isValid() && p.isAfter(yesterday)),
moment().tz(timezone).add(15, "days"),
]);
const range = Math.round(
moment.duration(end.add(20, "days").diff(today)).asDays()
);
for (var day = 0; day < range; day++) {
const current = moment(today)
.tz(timezone)
.add(day, "days")
.format("yyyy-MM-DD");
const prev = moment(today)
.tz(timezone)
.add(day - 1, "days")
.format("yyyy-MM-DD");
if (!!!load[current]) {
load[current] = {};
}
if (day === 0) {
//Starting on day 1. The load is current.
load[current].expectedLoad = CalculateLoad(
load.productionTotal,
ssbuckets,
load[current].jobsIn || [],
load[current].jobsOut || []
const possibleDates = [];
delete load.productionTotal;
const loadKeys = Object.keys(load).sort((a, b) =>
moment(a).isAfter(moment(b)) ? 1 : -1
);
} else {
load[current].expectedLoad = CalculateLoad(
load[prev].expectedLoad,
ssbuckets,
load[current].jobsIn || [],
load[current].jobsOut || []
);
}
loadKeys.forEach((loadKey) => {
const isShopOpen =
(workingdays[dayOfWeekMapper(moment(loadKey).day())] || false) &&
!load[loadKey].blocked;
let isUnderDailyTotalLimit = true;
if (
ss_configuration &&
ss_configuration.dailyhrslimit &&
ss_configuration.dailyhrslimit > 0 &&
load[loadKey] &&
load[loadKey].hoursInTotal &&
load[loadKey].hoursInTotal > ss_configuration.dailyhrslimit
) {
isUnderDailyTotalLimit = false;
}
if (
load[loadKey].expectedLoad &&
load[loadKey].expectedLoad[JobBucket.id] &&
JobBucket.target > load[loadKey].expectedLoad[JobBucket.id].count &&
isShopOpen &&
isUnderDailyTotalLimit
)
possibleDates.push(new Date(loadKey).toISOString().substr(0, 10));
});
if (possibleDates.length < 11) {
res.json(possibleDates);
} else {
res.json(possibleDates.slice(0, 10));
}
} catch (error) {
logger.log("smart-scheduling-error", "ERROR", req.user.email, jobId, {
error,
});
res.status(400).send(error);
}
//Add in all of the blocked days.
blockedDays.forEach((b) => {
//Find it in the load, set it as blocked.
const startIsoFormat = moment(b.start).tz(timezone).format("YYYY-MM-DD");
if (load[startIsoFormat]) load[startIsoFormat].blocked = true;
else {
load[startIsoFormat] = { blocked: true };
}
});
// //Propose the first 10 dates where we are below target.
const possibleDates = [];
delete load.productionTotal;
const loadKeys = Object.keys(load).sort((a, b) =>
moment(a).isAfter(moment(b)) ? 1 : -1
);
loadKeys.forEach((loadKey) => {
const isShopOpen =
(workingdays[dayOfWeekMapper(moment(loadKey).day())] || false) &&
!load[loadKey].blocked;
let isUnderDailyTotalLimit = true;
if (
ss_configuration &&
ss_configuration.dailyhrslimit &&
ss_configuration.dailyhrslimit > 0 &&
load[loadKey] &&
load[loadKey].hoursInTotal &&
load[loadKey].hoursInTotal > ss_configuration.dailyhrslimit
) {
isUnderDailyTotalLimit = false;
}
if (
load[loadKey].expectedLoad &&
load[loadKey].expectedLoad[JobBucket.id] &&
JobBucket.target > load[loadKey].expectedLoad[JobBucket.id].count &&
isShopOpen &&
isUnderDailyTotalLimit
)
possibleDates.push(new Date(loadKey).toISOString().substr(0, 10));
});
if (possibleDates.length < 11) {
res.json(possibleDates);
} else {
res.json(possibleDates.slice(0, 10));
}
} catch (error) {
logger.log("smart-scheduling-error", "ERROR", req.user.email, jobId, {
error,
});
res.status(400).send(error);
}
};
const dayOfWeekMapper = (numberOfDay) => {
switch (numberOfDay) {
case 0:
return "sunday";
case 1:
return "monday";
case 2:
return "tuesday";
case 3:
return "wednesday";
case 4:
return "thursday";
case 5:
return "friday";
case 6:
return "saturday";
}
switch (numberOfDay) {
case 0:
return "sunday";
case 1:
return "monday";
case 2:
return "tuesday";
case 3:
return "wednesday";
case 4:
return "thursday";
case 5:
return "friday";
case 6:
return "saturday";
}
};
const CheckJobBucket = (buckets, job) => {
const jobHours =
job.labhrs.aggregate.sum.mod_lb_hrs + job.larhrs.aggregate.sum.mod_lb_hrs;
const jobHours =
job.labhrs.aggregate.sum.mod_lb_hrs + job.larhrs.aggregate.sum.mod_lb_hrs;
const matchingBucket = buckets.filter((b) =>
b.gte <= jobHours && b.lt ? b.lt > jobHours : true
);
const matchingBucket = buckets.filter((b) =>
b.gte <= jobHours && b.lt ? b.lt > jobHours : true
);
return matchingBucket[0] && matchingBucket[0].id;
return matchingBucket[0] && matchingBucket[0].id;
};
const CalculateLoad = (currentLoad, buckets, jobsIn, jobsOut) => {
//Add the jobs coming
const newLoad = _.cloneDeep(currentLoad);
jobsIn.forEach((job) => {
const bucketId = CheckJobBucket(buckets, job);
if (bucketId) {
newLoad[bucketId].count = newLoad[bucketId].count + 1;
} else {
console.log(
"[Util Arr Job]Uh oh, this job doesn't fit in a bucket!",
job
);
}
});
//Add the jobs coming
const newLoad = _.cloneDeep(currentLoad);
jobsIn.forEach((job) => {
const bucketId = CheckJobBucket(buckets, job);
if (bucketId) {
newLoad[bucketId].count = newLoad[bucketId].count + 1;
} else {
console.log(
"[Util Arr Job]Uh oh, this job doesn't fit in a bucket!",
job
);
}
});
jobsOut.forEach((job) => {
const bucketId = CheckJobBucket(buckets, job);
if (bucketId) {
newLoad[bucketId].count = newLoad[bucketId].count - 1;
if (newLoad[bucketId].count < 0) {
console.log("***ERROR: NEGATIVE LOAD Bucket =>", bucketId, job);
}
} else {
console.log(
"[Util Out Job]Uh oh, this job doesn't fit in a bucket!",
job
);
}
});
jobsOut.forEach((job) => {
const bucketId = CheckJobBucket(buckets, job);
if (bucketId) {
newLoad[bucketId].count = newLoad[bucketId].count - 1;
if (newLoad[bucketId].count < 0) {
console.log("***ERROR: NEGATIVE LOAD Bucket =>", bucketId, job);
}
} else {
console.log(
"[Util Out Job]Uh oh, this job doesn't fit in a bucket!",
job
);
}
});
return newLoad;
return newLoad;
};