changes to auto shift generation
This commit is contained in:
@ -10,7 +10,7 @@ import { bgBG } from '../x-date-pickers/locales/bgBG';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
const common = require('src/helpers/common');
|
||||
//todo import Availability type from prisma schema
|
||||
import { isBefore, addMinutes, isAfter, isEqual, set, getHours, getMinutes, getSeconds } from 'date-fns'; //ToDo obsolete
|
||||
import { isBefore, addMinutes, isAfter, isEqual, set, getHours, getMinutes, getSeconds } from 'date-fns'; //ToDo obsolete?
|
||||
import { stat } from 'fs';
|
||||
|
||||
const { DateTime, FixedOffsetZone } = require('luxon');
|
||||
|
@ -1,9 +1,12 @@
|
||||
//import { getToken } from "next-auth/jwt";
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
import axiosServer from '../../src/axiosServer';
|
||||
import { getToken } from "next-auth/jwt";
|
||||
|
||||
import { set, format, addDays } from 'date-fns';
|
||||
import { set, format, isBefore, addDays, addMinutes, isAfter, isEqual, getHours, getMinutes, getSeconds } from 'date-fns';
|
||||
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { Prisma, PrismaClient, DayOfWeek, Publisher, Shift } from "@prisma/client";
|
||||
@ -58,10 +61,25 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
var forDay = common.parseBool(req.query.forDay);
|
||||
var type = parseInt(req.query.type) || 0;
|
||||
if (type == 2) {
|
||||
var result = await GenerateOptimalSchedule(axios, date, copyFromPreviousMonth, autoFill, forDay, type);
|
||||
// var result = await GenerateOptimalSchedule(axios, date, copyFromPreviousMonth, autoFill, forDay, type);
|
||||
var result = await GenerateScheduleNew(axios, date, copyFromPreviousMonth, autoFill, forDay, type);
|
||||
}
|
||||
else {
|
||||
// Create a new log file with a unique name
|
||||
const logFileName = `shift_generate_log_${Date.now()}.txt`;
|
||||
const logFilePath = path.join(process.cwd(), logFileName);
|
||||
|
||||
// Override console.log to write to the log file
|
||||
const originalConsoleLog = console.log;
|
||||
console.log = function (message) {
|
||||
fs.appendFileSync(logFilePath, message + '\n');
|
||||
originalConsoleLog.apply(console, arguments);
|
||||
};
|
||||
|
||||
var result = await GenerateSchedule(axios, date, copyFromPreviousMonth, autoFill, forDay, type);
|
||||
|
||||
// Restore the original console.log
|
||||
console.log = originalConsoleLog;
|
||||
}
|
||||
res.send(JSON.stringify(result?.error?.toString()));
|
||||
break;
|
||||
@ -136,9 +154,9 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
|
||||
const lastMonthInfo = common.getMonthDatesInfo(new Date(monthInfo.date.getFullYear(), monthInfo.date.getMonth() - 1, 1));
|
||||
|
||||
if (forDay) {
|
||||
await DeleteShiftsForDay(monthInfo.date);
|
||||
await data.DeleteShiftsForDay(monthInfo.date);
|
||||
} else {
|
||||
await DeleteShiftsForMonth(monthInfo);
|
||||
await data.DeleteShiftsForMonth(monthInfo);
|
||||
}
|
||||
|
||||
const events = await prisma.cartEvent.findMany({
|
||||
@ -207,20 +225,37 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
|
||||
if (shiftLastMonthSameDay) {
|
||||
for (let assignment of shiftLastMonthSameDay.assignments) {
|
||||
let publisher = assignment.originalPublisher ?? assignment.publisher;
|
||||
console.log("found publisher from last month: " + publisher.firstName + " " + publisher.lastName + assignment.originalPublisher ? " (original)" : "");
|
||||
let availability = await FindPublisherAvailability(publisher.id, shiftStart, shiftEnd, dayOfWeekEnum, weekNr);
|
||||
console.log("availability " + availability?.id + ": " + common.getDateFormattedShort(availability?.startTime) + " " + common.getTimeFormatted(availability?.startTime) + " - " + common.getTimeFormatted(availability?.endTime));
|
||||
let availability = await data.FindPublisherAvailability(publisher.id, shiftStart, shiftEnd, dayOfWeekEnum, weekNr);
|
||||
|
||||
if (availability && copyFromPreviousMonth && !publishersThisWeek.includes(publisher.id)) {
|
||||
shiftAssignments.push({
|
||||
publisherId: publisher.id,
|
||||
isConfirmed: true,
|
||||
isWithTransport: availability.isWithTransportIn || availability.isWithTransportOut
|
||||
});
|
||||
publishersThisWeek.push(publisher.id);
|
||||
updateRegistry(publisher.id, day, weekNr);
|
||||
if (availability) {
|
||||
console.log("AVAILABLE: " + publisher.firstName + " " + publisher.lastName + (assignment.originalPublisher ? " (original)" : "") + " - av:" + availability?.id + ": " + common.getDateFormattedShort(availability?.startTime) + " " + common.getTimeFormatted(availability?.startTime) + " - " + common.getTimeFormatted(availability?.endTime));
|
||||
} else {
|
||||
console.log(" " + publisher.firstName + " " + publisher.lastName + (assignment.originalPublisher ? " (original)" : "") + " - NOT AVAILABLE");
|
||||
}
|
||||
|
||||
//---------------------------------------------------
|
||||
// // COMMENT TO DISABLE COPY FROM LAST MONTH
|
||||
if (availability && copyFromPreviousMonth && !publishersThisWeek.includes(publisher.id)) {
|
||||
const transportCount = shiftAssignments.filter(a => a.isWithTransport).length;
|
||||
const isWithTransport = availability.isWithTransportIn || availability.isWithTransportOut;
|
||||
|
||||
if (!isWithTransport || transportCount < 2) {
|
||||
shiftAssignments.push({
|
||||
publisherId: publisher.id,
|
||||
isConfirmed: true,
|
||||
isBySystem: true,
|
||||
isWithTransport: isWithTransport
|
||||
});
|
||||
publishersThisWeek.push(publisher.id);
|
||||
updateRegistry(publisher.id, day, weekNr);
|
||||
}
|
||||
else {
|
||||
console.log(" " + publisher.firstName + " " + publisher.lastName + " skipped (transport already assigned)");
|
||||
}
|
||||
}
|
||||
//---------------------------------------------------
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let publishersNeeded = event.numberOfPublishers - shiftAssignments.length;
|
||||
@ -289,7 +324,10 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
|
||||
let rankedPublishers = [];
|
||||
|
||||
// Second pass - prioritize shifts with transport where it is needed
|
||||
console.log(" second pass - fix transports " + monthInfo.monthName + " " + monthInfo.year);
|
||||
|
||||
console.log("\r\n\r\n\r\n" + "# ".repeat(50));
|
||||
console.log("Second pass - fix transports " + monthInfo.monthName + " " + monthInfo.year);
|
||||
|
||||
day = new Date(monthInfo.firstMonday);
|
||||
dayNr = 1;
|
||||
weekNr = 1;
|
||||
@ -348,29 +386,30 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
|
||||
} else if (algType == 1) {
|
||||
rankedPublishers = await RankPublishersForShiftWeighted([...availablePublishers], scheduledPubsPerDayAndWeek, day, weekNr);
|
||||
}
|
||||
if (rankedPublishers.length > 0) {
|
||||
const newAssignment = await prisma.assignment.create({
|
||||
data: {
|
||||
shift: {
|
||||
connect: {
|
||||
id: shift.id,
|
||||
},
|
||||
},
|
||||
publisher: {
|
||||
connect: {
|
||||
id: rankedPublishers[0].id,
|
||||
},
|
||||
},
|
||||
isWithTransport: true,
|
||||
isConfirmed: true,
|
||||
isBySystem: false,
|
||||
},
|
||||
});
|
||||
// if (rankedPublishers.length > 0) {
|
||||
// const newAssignment = await prisma.assignment.create({
|
||||
// data: {
|
||||
// shift: {
|
||||
// connect: {
|
||||
// id: shift.id,
|
||||
// },
|
||||
// },
|
||||
// publisher: {
|
||||
// connect: {
|
||||
// id: rankedPublishers[0].id,
|
||||
// },
|
||||
// },
|
||||
// isWithTransport: true,
|
||||
// isConfirmed: true,
|
||||
// isBySystem: false,
|
||||
// },
|
||||
// });
|
||||
|
||||
shift.assignments.push(newAssignment);
|
||||
publishersToday.push(rankedPublishers[0].id);
|
||||
updateRegistry(rankedPublishers[0].id, day, weekNr);
|
||||
}
|
||||
// shift.assignments.push(newAssignment);
|
||||
// publishersToday.push(rankedPublishers[0].id);
|
||||
// updateRegistry(rankedPublishers[0].id, day, weekNr);
|
||||
// }
|
||||
AddPublisherAssignment(prisma, event, shift, availablePublishers, rankedPublishers, publishersToday, day, weekNr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -381,7 +420,7 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
|
||||
// Fill the rest of the shifts
|
||||
let goal = 1;
|
||||
while (goal <= 4) {
|
||||
console.log("#".repeat(50));
|
||||
console.log("\r\n\r\n\r\n" + "# ".repeat(50));
|
||||
console.log("Filling shifts with " + goal + " publishers " + monthInfo.monthName + " " + monthInfo.year);
|
||||
day = new Date(monthInfo.firstMonday);
|
||||
dayNr = 1;
|
||||
@ -408,7 +447,7 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
|
||||
|
||||
let shiftsToFill = shifts.filter(s => s.assignments.length < goal);
|
||||
console.log("" + day.toLocaleDateString() + " " + shiftsToFill.length + " shifts with less than " + goal + " publishers");
|
||||
|
||||
//when adding new assignment, first check if there are family members available. If yes, olnly proceed adding all available family members at once if there are enough free slots. ignore the main publisher if he/she can]t be with his family and search for the next best candidate
|
||||
for (const shift of shiftsToFill) {
|
||||
console.log("Filling shift " + shift.name + " with " + goal + " publishers");
|
||||
let publishersNeeded = event.numberOfPublishers - shift.assignments.length;
|
||||
@ -417,63 +456,12 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
|
||||
|
||||
let availablePublishers = await FilterInappropriatePublishers([...availablePubsForTheShift], publishersToday, shift);
|
||||
if (algType == 0) {
|
||||
rankedPublishers = await RankPublishersForShiftOld([...availablePublishers], scheduledPubsPerDayAndWeek, day, weekNr);
|
||||
rankedPublishers = await RankPublishersForShiftOld([...availablePublishers], scheduledPubsPerDayAndWeek, day);
|
||||
} else if (algType == 1) {
|
||||
rankedPublishers = await RankPublishersForShiftWeighted([...availablePublishers], scheduledPubsPerDayAndWeek, day, weekNr);
|
||||
}
|
||||
|
||||
if (rankedPublishers.length == 0) {
|
||||
console.log("No available publishers for shift " + shift.name);
|
||||
} else if (rankedPublishers.length > 0) {
|
||||
console.log("Assigning " + rankedPublishers[0].firstName + " " + rankedPublishers[0].lastName + " to " + new Date(shift.startTime).getDate() + " " + shift.name);
|
||||
const newAssignment = await prisma.assignment.create({
|
||||
data: {
|
||||
shift: {
|
||||
connect: {
|
||||
id: shift.id,
|
||||
},
|
||||
},
|
||||
publisher: {
|
||||
connect: {
|
||||
id: rankedPublishers[0].id,
|
||||
},
|
||||
},
|
||||
isConfirmed: true,
|
||||
isBySystem: false,
|
||||
},
|
||||
});
|
||||
shift.assignments.push(newAssignment);
|
||||
publishersToday.push(rankedPublishers[0].id);
|
||||
updateRegistry(rankedPublishers[0].id, day, weekNr);
|
||||
|
||||
let familyMembers = availablePubsForTheShift.filter(p => p.familyHeadId && p.familyHeadId === rankedPublishers[0].familyHeadId);
|
||||
if (familyMembers.length > 0) {
|
||||
familyMembers.forEach(async familyMember => {
|
||||
if (shift.assignments.length < event.numberOfPublishers) {
|
||||
console.log("Assigning " + familyMember.firstName + " " + familyMember.lastName + " to " + shift.startDate.getDate() + " " + shift.name);
|
||||
const newAssignment = await prisma.assignment.create({
|
||||
data: {
|
||||
shift: {
|
||||
connect: {
|
||||
id: shift.id,
|
||||
},
|
||||
},
|
||||
publisher: {
|
||||
connect: {
|
||||
id: familyMember.id,
|
||||
},
|
||||
},
|
||||
isConfirmed: true,
|
||||
isBySystem: false,
|
||||
},
|
||||
});
|
||||
shift.assignments.push(newAssignment);
|
||||
publishersToday.push(familyMember.id);
|
||||
updateRegistry(familyMember.id, day, weekNr);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
AddPublisherAssignment(prisma, event, shift, availablePublishers, rankedPublishers, publishersToday, day, weekNr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -496,12 +484,96 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
|
||||
}
|
||||
}
|
||||
|
||||
async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheShift, rankedPublishers, publishersToday, day, weekNr) {
|
||||
if (rankedPublishers.length == 0) {
|
||||
console.log("No available publishers for shift " + shift.name);
|
||||
} else {
|
||||
for (let i = 0; i < rankedPublishers.length; i++) {
|
||||
let mainPublisher = rankedPublishers[i];
|
||||
let familyMembers = availablePubsForTheShift.filter(p => (p.familyHeadId && p.familyHeadId === mainPublisher.familyHeadId) || (p.familyHeadId === mainPublisher.id));
|
||||
|
||||
if (familyMembers.length > 0 && (shift.assignments.length + familyMembers.length + 1) <= event.numberOfPublishers) {
|
||||
console.log("Assigning " + mainPublisher.firstName + " " + mainPublisher.lastName + " and family members to " + new Date(shift.startTime).getDate() + " " + shift.name);
|
||||
const newAssignment = await prisma.assignment.create({
|
||||
data: {
|
||||
shift: {
|
||||
connect: {
|
||||
id: shift.id,
|
||||
},
|
||||
},
|
||||
publisher: {
|
||||
connect: {
|
||||
id: mainPublisher.id,
|
||||
},
|
||||
},
|
||||
isConfirmed: false,
|
||||
isBySystem: false,
|
||||
isWithTransport: shift.requiresTransport,
|
||||
},
|
||||
});
|
||||
shift.assignments.push(newAssignment);
|
||||
publishersToday.push(mainPublisher.id);
|
||||
updateRegistry(mainPublisher.id, day, weekNr);
|
||||
|
||||
for (const familyMember of familyMembers) {
|
||||
const newFamilyAssignment = await prisma.assignment.create({
|
||||
data: {
|
||||
shift: {
|
||||
connect: {
|
||||
id: shift.id,
|
||||
},
|
||||
},
|
||||
publisher: {
|
||||
connect: {
|
||||
id: familyMember.id,
|
||||
},
|
||||
},
|
||||
isConfirmed: false,
|
||||
isBySystem: false,
|
||||
isWithTransport: shift.requiresTransport,
|
||||
},
|
||||
});
|
||||
shift.assignments.push(newFamilyAssignment);
|
||||
publishersToday.push(familyMember.id);
|
||||
updateRegistry(familyMember.id, day, weekNr);
|
||||
}
|
||||
break;
|
||||
} else if (familyMembers.length == 0) {
|
||||
console.log("Assigning " + mainPublisher.firstName + " " + mainPublisher.lastName + " to " + new Date(shift.startTime).getDate() + " " + shift.name);
|
||||
const newAssignment = await prisma.assignment.create({
|
||||
data: {
|
||||
shift: {
|
||||
connect: {
|
||||
id: shift.id,
|
||||
},
|
||||
},
|
||||
publisher: {
|
||||
connect: {
|
||||
id: mainPublisher.id,
|
||||
},
|
||||
},
|
||||
isConfirmed: false,
|
||||
isBySystem: false,
|
||||
isWithTransport: shift.requiresTransport,
|
||||
},
|
||||
});
|
||||
shift.assignments.push(newAssignment);
|
||||
publishersToday.push(mainPublisher.id);
|
||||
updateRegistry(mainPublisher.id, day, weekNr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function FilterInappropriatePublishers(availablePublishers, pubsToExclude, shift) {
|
||||
//ToDo: Optimization: store number of publishers, so we process the shifts from least to most available publishers later.
|
||||
let goodPublishers = availablePublishers.filter(p => {
|
||||
const isNotAssigned = !shift.assignments.some(a => a.publisher?.id === p.id);
|
||||
const isNotAssignedToday = !pubsToExclude.includes(p.id);
|
||||
const isAssignedEnough = p.currentMonthAssignments >= p.desiredShiftsPerMonth;
|
||||
const isAssignedEnough = p.currentMonthAssignments >= p.desiredShiftsPerMonth
|
||||
|| p.currentMonthAssignments >= 6; // overwrite the desiredShiftsPerMonth to max 6 shifts per month
|
||||
return isNotAssigned && isNotAssignedToday && !isAssignedEnough;
|
||||
});
|
||||
return goodPublishers;
|
||||
@ -517,7 +589,7 @@ async function FilterInappropriatePublishers(availablePublishers, pubsToExclude,
|
||||
// 6. Idealy noone should be more than once a week. disqualify publishers already on a shift this week. only assign them if there are no other options and we have less than 3 publishers on a specific shift.
|
||||
|
||||
//sort publishers to rank the best option for the current shift assignment
|
||||
async function RankPublishersForShiftOld(publishers, scheduledPubsPerDayAndWeek, currentDay) {
|
||||
async function RankPublishersForShiftOld(publishers, scheduledPubsPerDayAndWeek, currentDay: Date) {
|
||||
publishers.forEach(p => {
|
||||
p.DesiredMinusCurrent = p.desiredShiftsPerMonth - p.currentMonthAssignments;
|
||||
});
|
||||
@ -631,11 +703,13 @@ async function RankPublishersForShiftWeighted(publishers, scheduledPubsPerDayAnd
|
||||
|
||||
// Log the scores and penalties of the top publisher
|
||||
if (ranked.length > 0) {
|
||||
console.log(`Top Publisher: ${ranked[0].firstName} ${ranked[0].lastName}`,
|
||||
` Score: ${ranked[0].score}`, "last score: ", ranked[ranked.length - 1].score,
|
||||
` Penalties: `, ranked[0].penalties);
|
||||
const avgScore = (ranked.reduce((acc: number, val: { score: number }) => acc + val.score, 0) / ranked.length).toFixed(2);
|
||||
const topPublisher = ranked[0];
|
||||
const lastPublisher = ranked[ranked.length - 1];
|
||||
console.log(`Top Publisher: ${topPublisher.firstName} ${topPublisher.lastName} (${ranked.length} available)`,
|
||||
` Score: ${topPublisher.score.toFixed(2)}`, "last score: ", lastPublisher.score.toFixed(2), "Avg: ", avgScore,
|
||||
` Penalties: `, topPublisher.penalties);
|
||||
}
|
||||
|
||||
return ranked;
|
||||
}
|
||||
|
||||
@ -644,38 +718,7 @@ async function RankPublishersForShiftWeighted(publishers, scheduledPubsPerDayAnd
|
||||
|
||||
|
||||
|
||||
async function DeleteShiftsForMonth(monthInfo) {
|
||||
try {
|
||||
const prisma = common.getPrismaClient();
|
||||
await prisma.shift.deleteMany({
|
||||
where: {
|
||||
startTime: {
|
||||
gte: monthInfo.firstMonday,
|
||||
lt: monthInfo.lastSunday,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function DeleteShiftsForDay(date) {
|
||||
const prisma = common.getPrismaClient();
|
||||
try {
|
||||
// Assuming shifts do not span multiple days, so equality comparison is used
|
||||
await prisma.shift.deleteMany({
|
||||
where: {
|
||||
startTime: {
|
||||
gte: date,
|
||||
lt: new Date(date.getTime() + 86400000), // +1 day in milliseconds
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function getShiftsFromLastMonth(monthInfo) {
|
||||
const prisma = common.getPrismaClient();
|
||||
@ -746,65 +789,6 @@ async function getAvailablePublishersForShiftNew(startTime, endTime, allPublishe
|
||||
return availablePublishers;
|
||||
}
|
||||
|
||||
async function FindPublisherAvailability(publisherId, startDate, endDate, dayOfWeekEnum, weekNr) {
|
||||
const prisma = common.getPrismaClient();
|
||||
const start = new Date(startDate);
|
||||
const end = new Date(endDate);
|
||||
const hours = start.getHours();
|
||||
const minutes = start.getMinutes();
|
||||
|
||||
const exactAvailabilities = await prisma.availability.findMany({
|
||||
where: {
|
||||
publisherId: publisherId,
|
||||
// type: AvailabilityType.OneTime,
|
||||
AND: [ // Ensure both conditions must be met
|
||||
{ startTime: { lte: start } }, // startTime is less than or equal to the date
|
||||
{ endTime: { gte: end } },// endTime is greater than or equal to the date
|
||||
],
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Query for repeating availabilities, ignoring exact date, focusing on time and day of week/month
|
||||
let repeatingAvailabilities = await prisma.availability.findMany({
|
||||
where: {
|
||||
publisherId: publisherId,
|
||||
dayOfMonth: null, // This signifies a repeating availability
|
||||
OR: [
|
||||
{ dayofweek: dayOfWeekEnum },// Matches the specific day of the week
|
||||
{ weekOfMonth: weekNr } // Matches specific weeks of the month
|
||||
]
|
||||
}
|
||||
});
|
||||
//filter out availabilities that does not match the time
|
||||
// repeatingAvailabilities = repeatingAvailabilities.filter(avail => {
|
||||
// return avail.startTime.getHours() <= hours && avail.endTime.getHours() >= hours
|
||||
// && avail.startTime.getMinutes() <= minutes && avail.endTime.getMinutes() >= minutes
|
||||
// && avail.startTime <= new Date(startDate) && (endDate ? avail.endTime >= new Date(endDate) : true)
|
||||
// });
|
||||
|
||||
repeatingAvailabilities = repeatingAvailabilities.filter(avail => {
|
||||
const availStart = new Date(avail.startTime);
|
||||
const availEnd = new Date(avail.endTime);
|
||||
const availUntil = avail.endDate ? new Date(avail.endDate) : null;
|
||||
|
||||
const availStartTimeInt = common.timeToInteger(availStart.getHours(), availStart.getMinutes());
|
||||
const availEndTimeInt = common.timeToInteger(availEnd.getHours(), availEnd.getMinutes());
|
||||
const startTimeInt = common.timeToInteger(start.getHours(), start.getMinutes());
|
||||
const endTimeInt = common.timeToInteger(end.getHours(), end.getMinutes());
|
||||
|
||||
const isValid = availStartTimeInt <= startTimeInt && availEndTimeInt >= endTimeInt
|
||||
&& availStart <= start
|
||||
&& (!availUntil || availUntil >= end);
|
||||
|
||||
return isValid;
|
||||
});
|
||||
|
||||
// return [...exactAvailabilities, ...repeatingAvailabilities];
|
||||
// Combine the exact and repeating availabilities, return first or null if no availabilities are found
|
||||
return exactAvailabilities.length > 0 ? exactAvailabilities[0] : repeatingAvailabilities.length > 0 ? repeatingAvailabilities[0] : null;
|
||||
}
|
||||
|
||||
// ### COPIED TO shift api (--) ###
|
||||
|
||||
// function addAssignmentToPublisher(shiftAssignments: any[], publisher: Publisher) {
|
||||
@ -874,3 +858,251 @@ async function ImportShiftsFromDocx(axios: Axios) {
|
||||
// *********************************************************************************************************************
|
||||
//region helpers
|
||||
// *********************************************************************************************************************
|
||||
|
||||
|
||||
|
||||
// // *********************************************************************************************************************
|
||||
// // NEW implementation
|
||||
// // *********************************************************************************************************************
|
||||
|
||||
// async function GenerateScheduleNew(axios, date, copyFromPreviousMonth = false, autoFill = false, forDay, algType = 0) {
|
||||
// const prisma = common.getPrismaClient();
|
||||
// const monthInfo = common.getMonthDatesInfo(new Date(date));
|
||||
// const lastMonthInfo = common.getMonthDatesInfo(new Date(monthInfo.date.getFullYear(), monthInfo.date.getMonth() - 1, 1));
|
||||
|
||||
// // 0. Generate empty shifts for the whole period
|
||||
// let shiftPool = await generateEmptyShifts(monthInfo, forDay);
|
||||
|
||||
// // 1. Copy shifts from last month if requested
|
||||
// if (copyFromPreviousMonth) {
|
||||
// shiftPool = await copyShiftsFromLastMonth(shiftPool, lastMonthInfo);
|
||||
// }
|
||||
|
||||
// // 2. Get all publishers
|
||||
// const allPublishers = await getAllPublishers(monthInfo);
|
||||
|
||||
// // 3. Fill shifts by priority
|
||||
// await fillShiftsByPriority(shiftPool, allPublishers);
|
||||
|
||||
// return { message: "Schedule generated successfully", shifts: shiftPool };
|
||||
// }
|
||||
|
||||
// async function generateEmptyShifts(monthInfo, forDay) {
|
||||
// const prisma = common.getPrismaClient();
|
||||
// const startDate = forDay ? monthInfo.date : monthInfo.firstMonday;
|
||||
// const endDate = forDay ? addDays(monthInfo.date, 1) : monthInfo.lastSunday;
|
||||
|
||||
// const events = await prisma.cartEvent.findMany({
|
||||
// where: { isActive: true }
|
||||
// });
|
||||
|
||||
// let shifts = [];
|
||||
|
||||
// for (let day = startDate; day < endDate; day = addDays(day, 1)) {
|
||||
// const dayOfWeek = common.DaysOfWeekArray[day.getDayEuropean()];
|
||||
// const event = events.find(e => e.dayofweek === dayOfWeek);
|
||||
|
||||
// if (event) {
|
||||
// let shiftStart = set(day, { hours: event.startTime.getHours(), minutes: event.startTime.getMinutes() });
|
||||
// const shiftEnd = set(day, { hours: event.endTime.getHours(), minutes: event.endTime.getMinutes() });
|
||||
|
||||
// while (shiftStart < shiftEnd) {
|
||||
// const nextShiftEnd = addMinutes(shiftStart, event.shiftDuration);
|
||||
// const newShift = await prisma.shift.create({
|
||||
// data: {
|
||||
// startTime: shiftStart,
|
||||
// endTime: nextShiftEnd,
|
||||
// requiredPublishers: event.numberOfPublishers,
|
||||
// requiresTransport: shiftStart.getTime() === event.startTime.getTime() || nextShiftEnd.getTime() === event.endTime.getTime(),
|
||||
// assignments: [],
|
||||
// weight: 0
|
||||
// }
|
||||
// });
|
||||
// shifts.push(newShift);
|
||||
// shiftStart = nextShiftEnd;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return shifts;
|
||||
// }
|
||||
|
||||
|
||||
// async function copyShiftsFromLastMonth(shiftPool, lastMonthInfo) {
|
||||
// const prisma = common.getPrismaClient();
|
||||
// const lastMonthShifts = await prisma.shift.findMany({
|
||||
// where: {
|
||||
// startTime: {
|
||||
// gte: lastMonthInfo.firstMonday,
|
||||
// lt: lastMonthInfo.lastSunday
|
||||
// }
|
||||
// },
|
||||
// include: {
|
||||
// assignments: {
|
||||
// include: {
|
||||
// publisher: {
|
||||
// include: {
|
||||
// availabilities: true
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// for (const shift of shiftPool) {
|
||||
// const matchingLastMonthShift = lastMonthShifts.find(s =>
|
||||
// s.startTime.getHours() === shift.startTime.getHours() &&
|
||||
// s.startTime.getMinutes() === shift.startTime.getMinutes() &&
|
||||
// s.startTime.getDay() === shift.startTime.getDay()
|
||||
// );
|
||||
|
||||
// if (matchingLastMonthShift) {
|
||||
// for (const assignment of matchingLastMonthShift.assignments) {
|
||||
// const isStillAvailable = await checkPublisherAvailability(assignment.publisher, shift);
|
||||
// if (isStillAvailable) {
|
||||
// shift.assignments.push({
|
||||
// publisherId: assignment.publisher.id,
|
||||
// isWithTransport: assignment.isWithTransport,
|
||||
// isConfirmed: false,
|
||||
// isBySystem: true
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return shiftPool;
|
||||
// }
|
||||
|
||||
// async function getAllPublishers(monthInfo) {
|
||||
// const prisma = common.getPrismaClient();
|
||||
// const publishers = await prisma.publisher.findMany({
|
||||
// where: { isActive: true },
|
||||
// include: {
|
||||
// availabilities: {
|
||||
// where: {
|
||||
// startTime: {
|
||||
// gte: monthInfo.firstMonday,
|
||||
// lt: monthInfo.lastSunday
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// return publishers.map(p => ({
|
||||
// ...p,
|
||||
// currentMonthAssignments: 0,
|
||||
// lastAssignmentDate: null
|
||||
// }));
|
||||
// }
|
||||
|
||||
// async function fillShiftsByPriority(shiftPool, allPublishers) {
|
||||
// let remainingShifts = [...shiftPool];
|
||||
|
||||
// while (remainingShifts.length > 0) {
|
||||
// updateShiftWeights(remainingShifts);
|
||||
// remainingShifts.sort((a, b) => b.weight - a.weight);
|
||||
|
||||
// const currentShift = remainingShifts[0];
|
||||
// const requireTransport = currentShift.requiresTransport && currentShift.assignments.filter(a => a.isWithTransport).length < 2;
|
||||
|
||||
// const availablePublishers = await getAvailablePublishersForShift(currentShift, allPublishers, requireTransport);
|
||||
// const rankedPublishers = rankPublishersForShift(availablePublishers, currentShift, requireTransport);
|
||||
|
||||
// if (rankedPublishers.length > 0) {
|
||||
// const selectedPublisher = rankedPublishers[0];
|
||||
// await assignPublisherToShift(selectedPublisher, currentShift, requireTransport);
|
||||
// updatePublisherStats(selectedPublisher, currentShift);
|
||||
// }
|
||||
|
||||
// if (currentShift.assignments.length >= currentShift.requiredPublishers) {
|
||||
// remainingShifts.shift();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// function updateShiftWeights(shifts) {
|
||||
// for (const shift of shifts) {
|
||||
// const remainingSlots = shift.requiredPublishers - shift.assignments.length;
|
||||
// const transportWeight = shift.requiresTransport && shift.assignments.filter(a => a.isWithTransport).length < 2 ? 10 : 0;
|
||||
// const timeWeight = shift.startTime.getHours() < 12 ? 5 : 0; // Prioritize morning shifts
|
||||
// shift.weight = remainingSlots * 10 + transportWeight + timeWeight;
|
||||
// }
|
||||
// }
|
||||
|
||||
// async function getAvailablePublishersForShift(shift, allPublishers, requireTransport) {
|
||||
// return allPublishers.filter(publisher => {
|
||||
// const isAvailable = publisher.availabilities.some(avail =>
|
||||
// avail.startTime <= shift.startTime && avail.endTime >= shift.endTime
|
||||
// );
|
||||
// const isNotAssigned = !shift.assignments.some(a => a.publisherId === publisher.id);
|
||||
// const hasTransport = requireTransport ? publisher.canProvideTransport : true;
|
||||
// return isAvailable && isNotAssigned && hasTransport;
|
||||
// });
|
||||
// }
|
||||
|
||||
// function rankPublishersForShift(publishers, shift, requireTransport) {
|
||||
// const weights = {
|
||||
// gender: 2,
|
||||
// desiredCompletion: 3,
|
||||
// currentAssignments: 2,
|
||||
// lastAssignment: 2,
|
||||
// fairness: 3,
|
||||
// transport: requireTransport ? 5 : 0
|
||||
// };
|
||||
|
||||
// return publishers.map(p => ({
|
||||
// ...p,
|
||||
// score: calculatePublisherScore(p, shift, weights)
|
||||
// })).sort((a, b) => b.score - a.score);
|
||||
// }
|
||||
|
||||
// function calculatePublisherScore(publisher, shift, weights) {
|
||||
// const genderScore = publisher.gender === 'male' ? weights.gender : 0;
|
||||
// const desiredCompletionScore = (publisher.desiredShiftsPerMonth - publisher.currentMonthAssignments) * weights.desiredCompletion;
|
||||
// const currentAssignmentsScore = -publisher.currentMonthAssignments * weights.currentAssignments;
|
||||
// const lastAssignmentScore = calculateLastAssignmentScore(publisher, shift) * weights.lastAssignment;
|
||||
// const fairnessScore = calculateFairnessScore(publisher) * weights.fairness;
|
||||
// const transportScore = publisher.canProvideTransport ? weights.transport : 0;
|
||||
|
||||
// return genderScore + desiredCompletionScore + currentAssignmentsScore + lastAssignmentScore + fairnessScore + transportScore;
|
||||
// }
|
||||
|
||||
// function calculateLastAssignmentScore(publisher, shift) {
|
||||
// if (!publisher.lastAssignmentDate) return 1;
|
||||
// const daysSinceLastAssignment = differenceInDays(shift.startTime, publisher.lastAssignmentDate);
|
||||
// return Math.min(daysSinceLastAssignment / 7, 1); // Normalize to 0-1 range, max out at 7 days
|
||||
// }
|
||||
|
||||
// function calculateFairnessScore(publisher) {
|
||||
// const utilizationRate = publisher.currentMonthAssignments / publisher.desiredShiftsPerMonth;
|
||||
// return 1 - utilizationRate; // Higher score for less utilized publishers
|
||||
// }
|
||||
|
||||
// async function assignPublisherToShift(publisher, shift, withTransport) {
|
||||
// const prisma = common.getPrismaClient();
|
||||
// const assignment = await prisma.assignment.create({
|
||||
// data: {
|
||||
// shift: { connect: { id: shift.id } },
|
||||
// publisher: { connect: { id: publisher.id } },
|
||||
// isWithTransport: withTransport,
|
||||
// isConfirmed: false,
|
||||
// isBySystem: true,
|
||||
// },
|
||||
// });
|
||||
|
||||
// shift.assignments.push(assignment);
|
||||
// }
|
||||
|
||||
// function updatePublisherStats(publisher, shift) {
|
||||
// publisher.currentMonthAssignments++;
|
||||
// publisher.lastAssignmentDate = shift.startTime;
|
||||
// }
|
||||
|
||||
// async function checkPublisherAvailability(publisher, shift) {
|
||||
// return publisher.availabilities.some(avail =>
|
||||
// avail.startTime <= shift.startTime && avail.endTime >= shift.endTime
|
||||
// );
|
||||
// }
|
@ -836,246 +836,302 @@ async function getCoverMePublisherEmails(shiftId) {
|
||||
return { shift, availablePublishers: availablePublishers, subscribedPublishers };
|
||||
}
|
||||
|
||||
// ### COPIED TO shift api (++) ###
|
||||
// ### COPIED TO shift api (/shiftgenerate.ts) (++) ###
|
||||
|
||||
/** JSDoc
|
||||
* Generates a schedule.
|
||||
*
|
||||
0. generate shifts and assign publishers from the previous month if still available
|
||||
1. Make sure we always put people only when they are available.
|
||||
2. First provision one male or two females that are available for transport in the first and last shifts.
|
||||
3, Then gradually fill all other shifts with day by day troughout the whole month (monthInfo.firstMonday to .lastSunday) with first one, then two, then 3 and wherever possible more (up to CartEvent.numberOfPublishers number)
|
||||
4. Some publishers are available only at specific time (somoetimes only once) and other are more available. if people are available only for this time, prioritize them so they are not left behind.
|
||||
5. prioritize based on publisher's desiredShiftsPerMonth and previous months assignments.
|
||||
6. Idealy noone should be more than once a week. disqualify publishers already on a shift this week. only assign them if there are no other options and we have less than 3 publishers on a specific shift.
|
||||
*
|
||||
* @param {Axios} axios Axios instance for making requests.
|
||||
* @param {string} date The date for the schedule.
|
||||
* @param {boolean} [copyFromPreviousMonth=false] Whether to copy from the previous month.
|
||||
* @param {boolean} [autoFill=false] Whether to autofill data.
|
||||
* @param {boolean} forDay Specific day flag.
|
||||
*/
|
||||
async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, autoFill = false, forDay) {
|
||||
let missingPublishers = [];
|
||||
let publishersWithChangedPref = [];
|
||||
// /** JSDoc
|
||||
// * Generates a schedule.
|
||||
// *
|
||||
// 0. generate shifts and assign publishers from the previous month if still available
|
||||
// 1. Make sure we always put people only when they are available.
|
||||
// 2. First provision one male or two females that are available for transport in the first and last shifts.
|
||||
// 3, Then gradually fill all other shifts with day by day troughout the whole month (monthInfo.firstMonday to .lastSunday) with first one, then two, then 3 and wherever possible more (up to CartEvent.numberOfPublishers number)
|
||||
// 4. Some publishers are available only at specific time (somoetimes only once) and other are more available. if people are available only for this time, prioritize them so they are not left behind.
|
||||
// 5. prioritize based on publisher's desiredShiftsPerMonth and previous months assignments.
|
||||
// 6. Idealy noone should be more than once a week. disqualify publishers already on a shift this week. only assign them if there are no other options and we have less than 3 publishers on a specific shift.
|
||||
// *
|
||||
// * @param {Axios} axios Axios instance for making requests.
|
||||
// * @param {string} date The date for the schedule.
|
||||
// * @param {boolean} [copyFromPreviousMonth=false] Whether to copy from the previous month.
|
||||
// * @param {boolean} [autoFill=false] Whether to autofill data.
|
||||
// * @param {boolean} forDay Specific day flag.
|
||||
// */
|
||||
// async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, autoFill = false, forDay) {
|
||||
// let missingPublishers = [];
|
||||
// let publishersWithChangedPref = [];
|
||||
|
||||
const prisma = common.getPrismaClient();
|
||||
try {
|
||||
const monthInfo = common.getMonthDatesInfo(new Date(date));
|
||||
const lastMonthInfo = common.getMonthDatesInfo(new Date(monthInfo.date.getFullYear(), monthInfo.date.getMonth() - 1, 1));
|
||||
// const prisma = common.getPrismaClient();
|
||||
// try {
|
||||
// const monthInfo = common.getMonthDatesInfo(new Date(date));
|
||||
// const lastMonthInfo = common.getMonthDatesInfo(new Date(monthInfo.date.getFullYear(), monthInfo.date.getMonth() - 1, 1));
|
||||
|
||||
if (forDay) {
|
||||
await DeleteShiftsForDay(monthInfo.date);
|
||||
} else {
|
||||
await DeleteShiftsForMonth(monthInfo);
|
||||
}
|
||||
// if (forDay) {
|
||||
// await DeleteShiftsForDay(monthInfo.date);
|
||||
// } else {
|
||||
// await DeleteShiftsForMonth(monthInfo);
|
||||
// }
|
||||
|
||||
const events = await prisma.cartEvent.findMany({
|
||||
where: {
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
let shiftsLastMonth = await getShiftsFromLastMonth(lastMonthInfo);
|
||||
let publishers = await getAllPublishersWithStatisticsMonth('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', date, false, true, false, true, true);
|
||||
// const events = await prisma.cartEvent.findMany({
|
||||
// where: {
|
||||
// isActive: true
|
||||
// }
|
||||
// });
|
||||
// let shiftsLastMonth = await getShiftsFromLastMonth(lastMonthInfo);
|
||||
// let publishers = await getAllPublishersWithStatisticsMonth('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', date, false, true, false, true, true);
|
||||
|
||||
let shiftAssignments = [];
|
||||
let day = new Date(monthInfo.firstMonday);
|
||||
let endDate = monthInfo.lastSunday;
|
||||
let dayNr = 1;
|
||||
let weekNr = 1;
|
||||
// let shiftAssignments = [];
|
||||
// let day = new Date(monthInfo.firstMonday);
|
||||
// let endDate = monthInfo.lastSunday;
|
||||
// let dayNr = 1;
|
||||
// let weekNr = 1;
|
||||
|
||||
if (forDay) {
|
||||
day = monthInfo.date;
|
||||
endDate.setDate(monthInfo.date.getDate() + 1);
|
||||
dayNr = monthInfo.date.getDate();
|
||||
weekNr = common.getWeekNumber(monthInfo.date);
|
||||
}
|
||||
// if (forDay) {
|
||||
// day = monthInfo.date;
|
||||
// endDate.setDate(monthInfo.date.getDate() + 1);
|
||||
// dayNr = monthInfo.date.getDate();
|
||||
// weekNr = common.getWeekNumber(monthInfo.date);
|
||||
// }
|
||||
|
||||
let publishersThisWeek = [];
|
||||
// let publishersThisWeek = [];
|
||||
|
||||
// 0. generate shifts and assign publishers from the previous month if still available
|
||||
while (day < endDate) {
|
||||
let availablePubsForTheDay = await filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', day, false, false, false, true, true);
|
||||
console.log("passing schedule generation for " + day.toLocaleDateString());
|
||||
const dayOfM = day.getDate();
|
||||
let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(day);
|
||||
let dayName = common.DaysOfWeekArray[day.getDayEuropean()];
|
||||
const event = events.find((event) => event.dayofweek == dayName && (event.dayOfMonth == null || event.dayOfMonth == dayOfM));
|
||||
// // 0. generate shifts and assign publishers from the previous month if still available
|
||||
// while (day < endDate) {
|
||||
// let availablePubsForTheDay = await filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', day, false, false, false, true, true);
|
||||
// console.log("passing schedule generation for " + day.toLocaleDateString());
|
||||
// const dayOfM = day.getDate();
|
||||
// let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(day);
|
||||
// let dayName = common.DaysOfWeekArray[day.getDayEuropean()];
|
||||
// const event = events.find((event) => event.dayofweek == dayName && (event.dayOfMonth == null || event.dayOfMonth == dayOfM));
|
||||
|
||||
if (!event) {
|
||||
day.setDate(day.getDate() + 1);
|
||||
continue;
|
||||
}
|
||||
// if (!event) {
|
||||
// day.setDate(day.getDate() + 1);
|
||||
// continue;
|
||||
// }
|
||||
|
||||
event.startTime = new Date(event.startTime);
|
||||
event.endTime = new Date(event.endTime);
|
||||
// event.startTime = new Date(event.startTime);
|
||||
// event.endTime = new Date(event.endTime);
|
||||
|
||||
let startTime = new Date(day);
|
||||
startTime.setHours(event.startTime.getHours());
|
||||
startTime.setMinutes(event.startTime.getMinutes());
|
||||
let endTime = new Date(day);
|
||||
endTime.setHours(event.endTime.getHours());
|
||||
endTime.setMinutes(event.endTime.getMinutes());
|
||||
// let startTime = new Date(day);
|
||||
// startTime.setHours(event.startTime.getHours());
|
||||
// startTime.setMinutes(event.startTime.getMinutes());
|
||||
// let endTime = new Date(day);
|
||||
// endTime.setHours(event.endTime.getHours());
|
||||
// endTime.setMinutes(event.endTime.getMinutes());
|
||||
|
||||
let shiftStart = new Date(startTime);
|
||||
let shiftEnd = new Date(startTime);
|
||||
shiftEnd.setMinutes(shiftStart.getMinutes() + event.shiftDuration);
|
||||
// let shiftStart = new Date(startTime);
|
||||
// let shiftEnd = new Date(startTime);
|
||||
// shiftEnd.setMinutes(shiftStart.getMinutes() + event.shiftDuration);
|
||||
|
||||
let shiftNr = 0;
|
||||
while (shiftEnd <= endTime) {
|
||||
shiftNr++;
|
||||
const __shiftName = String(shiftStart.getHours()).padStart(2, "0") + ":" + String(shiftStart.getMinutes()).padStart(2, "0") + " - " + String(shiftEnd.getHours()).padStart(2, "0") + ":" + String(shiftEnd.getMinutes()).padStart(2, "0");
|
||||
shiftAssignments = [];
|
||||
let isTransportRequired = shiftNr == 1 || shiftEnd.getTime() == endTime.getTime();
|
||||
// let shiftNr = 0;
|
||||
// while (shiftEnd <= endTime) {
|
||||
// shiftNr++;
|
||||
// const __shiftName = String(shiftStart.getHours()).padStart(2, "0") + ":" + String(shiftStart.getMinutes()).padStart(2, "0") + " - " + String(shiftEnd.getHours()).padStart(2, "0") + ":" + String(shiftEnd.getMinutes()).padStart(2, "0");
|
||||
// shiftAssignments = [];
|
||||
// let isTransportRequired = shiftNr == 1 || shiftEnd.getTime() == endTime.getTime();
|
||||
|
||||
const shiftLastMonthSameDay = findTheSameShiftFromLastMonth(shiftsLastMonth, day, weekNr, shiftNr);
|
||||
// const shiftLastMonthSameDay = findTheSameShiftFromLastMonth(shiftsLastMonth, day, weekNr, shiftNr);
|
||||
|
||||
if (shiftLastMonthSameDay) {
|
||||
for (let assignment of shiftLastMonthSameDay.assignments) {
|
||||
let publisher = assignment.publisher;
|
||||
console.log("found publisher from last month: " + publisher.firstName + " " + publisher.lastName);
|
||||
let availability = await FindPublisherAvailability(publisher.id, shiftStart, shiftEnd, dayOfWeekEnum, weekNr);
|
||||
console.log("availability " + availability?.id + ": " + common.getDateFormattedShort(availability?.startTime) + " " + common.getTimeFormatted(availability?.startTime) + " - " + common.getTimeFormatted(availability?.endTime));
|
||||
// if (shiftLastMonthSameDay) {
|
||||
// for (let assignment of shiftLastMonthSameDay.assignments) {
|
||||
// let publisher = assignment.publisher;
|
||||
// console.log("found publisher from last month: " + publisher.firstName + " " + publisher.lastName);
|
||||
// let availability = await FindPublisherAvailability(publisher.id, shiftStart, shiftEnd, dayOfWeekEnum, weekNr);
|
||||
// console.log("availability " + availability?.id + ": " + common.getDateFormattedShort(availability?.startTime) + " " + common.getTimeFormatted(availability?.startTime) + " - " + common.getTimeFormatted(availability?.endTime));
|
||||
|
||||
if (availability && copyFromPreviousMonth && !publishersThisWeek.includes(publisher.id)) {
|
||||
shiftAssignments.push({
|
||||
publisherId: publisher.id,
|
||||
isConfirmed: true,
|
||||
isWithTransportIn: availability.isWithTransportIn,
|
||||
isWithTransportOut: availability.isWithTransportOut
|
||||
});
|
||||
publishersThisWeek.push(publisher.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (availability && copyFromPreviousMonth && !publishersThisWeek.includes(publisher.id)) {
|
||||
// shiftAssignments.push({
|
||||
// publisherId: publisher.id,
|
||||
// isConfirmed: true,
|
||||
// isWithTransportIn: availability.isWithTransportIn,
|
||||
// isWithTransportOut: availability.isWithTransportOut
|
||||
// });
|
||||
// publishersThisWeek.push(publisher.id);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
let publishersNeeded = event.numberOfPublishers - shiftAssignments.length;
|
||||
//ToDo: check if getAvailablePublishersForShift is working correctly
|
||||
let availablePublishers = await getAvailablePublishersForShift(shiftStart, shiftEnd, publishers, publishersThisWeek);
|
||||
// let publishersNeeded = event.numberOfPublishers - shiftAssignments.length;
|
||||
// //ToDo: check if getAvailablePublishersForShift is working correctly
|
||||
// let availablePublishers = await getAvailablePublishersForShift(shiftStart, shiftEnd, publishers, publishersThisWeek);
|
||||
|
||||
console.log("shift " + __shiftName + " needs " + publishersNeeded + " publishers, available: " + availablePublishers.length + " for the day: " + availablePubsForTheDay.length);
|
||||
// console.log("shift " + __shiftName + " needs " + publishersNeeded + " publishers, available: " + availablePublishers.length + " for the day: " + availablePubsForTheDay.length);
|
||||
|
||||
// Prioritize publishers with minimal availability
|
||||
// SKIP ADDING PUBLISHERS FOR NOW
|
||||
// availablePublishers = availablePublishers.sort((a, b) => a.currentMonthAvailabilityHoursCount - b.currentMonthAvailabilityHoursCount);
|
||||
// // Prioritize publishers with minimal availability
|
||||
// // SKIP ADDING PUBLISHERS FOR NOW
|
||||
// // availablePublishers = availablePublishers.sort((a, b) => a.currentMonthAvailabilityHoursCount - b.currentMonthAvailabilityHoursCount);
|
||||
|
||||
// for (let i = 0; i < publishersNeeded; i++) {
|
||||
// if (availablePublishers[i]) {
|
||||
// shiftAssignments.push({ publisherId: availablePublishers[i].id });
|
||||
// publishersThisWeek.push(availablePublishers[i].id);
|
||||
// }
|
||||
// }
|
||||
// // for (let i = 0; i < publishersNeeded; i++) {
|
||||
// // if (availablePublishers[i]) {
|
||||
// // shiftAssignments.push({ publisherId: availablePublishers[i].id });
|
||||
// // publishersThisWeek.push(availablePublishers[i].id);
|
||||
// // }
|
||||
// // }
|
||||
|
||||
const createdShift = await prisma.shift.create({
|
||||
data: {
|
||||
startTime: shiftStart,
|
||||
endTime: shiftEnd,
|
||||
name: event.dayofweek + " " + shiftStart.toLocaleTimeString() + " - " + shiftEnd.toLocaleTimeString(),
|
||||
requiresTransport: isTransportRequired,
|
||||
cartEvent: {
|
||||
connect: {
|
||||
id: event.id,
|
||||
},
|
||||
},
|
||||
assignments: {
|
||||
create: shiftAssignments.map((a) => {
|
||||
return {
|
||||
publisher: {
|
||||
connect: { id: a.publisherId }
|
||||
},
|
||||
isConfirmed: a.isConfirmed,
|
||||
isBySystem: true,
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
// const createdShift = await prisma.shift.create({
|
||||
// data: {
|
||||
// startTime: shiftStart,
|
||||
// endTime: shiftEnd,
|
||||
// name: event.dayofweek + " " + shiftStart.toLocaleTimeString() + " - " + shiftEnd.toLocaleTimeString(),
|
||||
// requiresTransport: isTransportRequired,
|
||||
// cartEvent: {
|
||||
// connect: {
|
||||
// id: event.id,
|
||||
// },
|
||||
// },
|
||||
// assignments: {
|
||||
// create: shiftAssignments.map((a) => {
|
||||
// return {
|
||||
// publisher: {
|
||||
// connect: { id: a.publisherId }
|
||||
// },
|
||||
// isConfirmed: a.isConfirmed,
|
||||
// isBySystem: true,
|
||||
// };
|
||||
// }),
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
shiftStart = new Date(shiftEnd);
|
||||
shiftEnd.setMinutes(shiftStart.getMinutes() + event.shiftDuration);
|
||||
}
|
||||
// shiftStart = new Date(shiftEnd);
|
||||
// shiftEnd.setMinutes(shiftStart.getMinutes() + event.shiftDuration);
|
||||
// }
|
||||
|
||||
day.setDate(day.getDate() + 1);
|
||||
dayNr++;
|
||||
if (common.DaysOfWeekArray[day.getDayEuropean()] === DayOfWeek.Sunday) {
|
||||
weekNr++;
|
||||
publishersThisWeek = [];
|
||||
publishers.forEach(p => p.currentWeekAssignments = 0);
|
||||
}
|
||||
if (forDay) break;
|
||||
}
|
||||
// day.setDate(day.getDate() + 1);
|
||||
// dayNr++;
|
||||
// if (common.DaysOfWeekArray[day.getDayEuropean()] === DayOfWeek.Sunday) {
|
||||
// weekNr++;
|
||||
// publishersThisWeek = [];
|
||||
// publishers.forEach(p => p.currentWeekAssignments = 0);
|
||||
// }
|
||||
// if (forDay) break;
|
||||
// }
|
||||
|
||||
let allShifts = await prisma.shift.findMany({
|
||||
where: {
|
||||
startTime: {
|
||||
gte: monthInfo.firstMonday,
|
||||
lt: monthInfo.lastSunday,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
assignments: {
|
||||
include: {
|
||||
publisher: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// let allShifts = await prisma.shift.findMany({
|
||||
// where: {
|
||||
// startTime: {
|
||||
// gte: monthInfo.firstMonday,
|
||||
// lt: monthInfo.lastSunday,
|
||||
// },
|
||||
// },
|
||||
// include: {
|
||||
// assignments: {
|
||||
// include: {
|
||||
// publisher: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
console.log(" second pass " + monthInfo.monthName + " " + monthInfo.year);
|
||||
// 2. First pass - prioritize shifts with transport where it is needed
|
||||
day = monthInfo.firstMonday;
|
||||
dayNr = 1;
|
||||
weekNr = 1;
|
||||
while (day < endDate) {
|
||||
let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(day);
|
||||
let event = events.find((event) => event.dayofweek == common.DaysOfWeekArray[day.getDayEuropean()]);
|
||||
if (event) {
|
||||
let availablePubsForTheDay = await filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', day, false, false, false, true, true);
|
||||
// console.log(" second pass " + monthInfo.monthName + " " + monthInfo.year);
|
||||
// // 2. First pass - prioritize shifts with transport where it is needed
|
||||
// day = monthInfo.firstMonday;
|
||||
// dayNr = 1;
|
||||
// weekNr = 1;
|
||||
// while (day < endDate) {
|
||||
// let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(day);
|
||||
// let event = events.find((event) => event.dayofweek == common.DaysOfWeekArray[day.getDayEuropean()]);
|
||||
// if (event) {
|
||||
// let availablePubsForTheDay = await filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', day, false, false, false, true, true);
|
||||
|
||||
let shifts = allShifts.filter(s => common.getISODateOnly(s.startTime) === common.getISODateOnly(day));
|
||||
let transportShifts = shifts.filter(s => s.requiresTransport);
|
||||
transportShifts.forEach(shift => {
|
||||
let availablePublishers = availablePubsForTheDay.filter(p => !shift.assignments.some(a => a.publisher.id === p.id));
|
||||
availablePublishers = availablePublishers.sort((a, b) => a.currentMonthAvailabilityHoursCount - b.currentMonthAvailabilityHoursCount);
|
||||
let publishersNeeded = event.numberOfPublishers - shift.assignments.length;
|
||||
if (publishersNeeded > 0) {//get the beset match
|
||||
if (availablePublishers[0]) {
|
||||
shift.assignments.push({ publisherId: availablePublishers[i].id });
|
||||
}
|
||||
// let shifts = allShifts.filter(s => common.getISODateOnly(s.startTime) === common.getISODateOnly(day));
|
||||
// let transportShifts = shifts.filter(s => s.requiresTransport);
|
||||
// transportShifts.forEach(shift => {
|
||||
// let availablePublishers = availablePubsForTheDay.filter(p => !shift.assignments.some(a => a.publisher.id === p.id));
|
||||
// availablePublishers = availablePublishers.sort((a, b) => a.currentMonthAvailabilityHoursCount - b.currentMonthAvailabilityHoursCount);
|
||||
// let publishersNeeded = event.numberOfPublishers - shift.assignments.length;
|
||||
// if (publishersNeeded > 0) {//get the beset match
|
||||
// if (availablePublishers[0]) {
|
||||
// shift.assignments.push({ publisherId: availablePublishers[i].id });
|
||||
// }
|
||||
|
||||
}
|
||||
});
|
||||
// 3. Second pass - fill the rest of the shifts
|
||||
let shiftsToFill = shifts.filter(s => !s.requiresTransport);
|
||||
shiftsToFill.forEach(shift => {
|
||||
let availablePublishers = availablePubsForTheDay.filter(p => !shift.assignments.some(a => a.publisher.id === p.id));
|
||||
availablePublishers = availablePublishers.sort((a, b) => a.currentMonthAvailabilityHoursCount - b.currentMonthAvailabilityHoursCount);
|
||||
let publishersNeeded = event.numberOfPublishers - shift.assignments.length;
|
||||
if (publishersNeeded > 0) {//get the beset match
|
||||
if (availablePublishers[0]) {
|
||||
shift.assignments.push({ publisherId: availablePublishers[i].id });
|
||||
}
|
||||
// }
|
||||
// });
|
||||
// // 3. Second pass - fill the rest of the shifts
|
||||
// let shiftsToFill = shifts.filter(s => !s.requiresTransport);
|
||||
// shiftsToFill.forEach(shift => {
|
||||
// let availablePublishers = availablePubsForTheDay.filter(p => !shift.assignments.some(a => a.publisher.id === p.id));
|
||||
// availablePublishers = availablePublishers.sort((a, b) => a.currentMonthAvailabilityHoursCount - b.currentMonthAvailabilityHoursCount);
|
||||
// let publishersNeeded = event.numberOfPublishers - shift.assignments.length;
|
||||
// if (publishersNeeded > 0) {//get the beset match
|
||||
// if (availablePublishers[0]) {
|
||||
// shift.assignments.push({ publisherId: availablePublishers[i].id });
|
||||
// }
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
day.setDate(day.getDate() + 1);
|
||||
}
|
||||
// day.setDate(day.getDate() + 1);
|
||||
// }
|
||||
|
||||
if (!forDay) {
|
||||
console.log("###############################################");
|
||||
console.log(" DONE CREATING SCHEDULE FOR " + monthInfo.monthName + " " + monthInfo.year);
|
||||
console.log("###############################################");
|
||||
}
|
||||
// if (!forDay) {
|
||||
// console.log("###############################################");
|
||||
// console.log(" DONE CREATING SCHEDULE FOR " + monthInfo.monthName + " " + monthInfo.year);
|
||||
// console.log("###############################################");
|
||||
// }
|
||||
|
||||
return {};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return { error: error };
|
||||
}
|
||||
}
|
||||
// return {};
|
||||
// } catch (error) {
|
||||
// console.log(error);
|
||||
// return { error: error };
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// async function getShiftsFromLastMonth(monthInfo) {
|
||||
// const prisma = common.getPrismaClient();
|
||||
// // Fetch shifts for the month
|
||||
// const rawShifts = await prisma.shift.findMany({
|
||||
// where: {
|
||||
// startTime: {
|
||||
// gte: monthInfo.firstMonday,
|
||||
// lte: monthInfo.lastSunday,
|
||||
// },
|
||||
// },
|
||||
// include: {
|
||||
// assignments: {
|
||||
// include: {
|
||||
// publisher: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
// // Process shifts to add weekNr and shiftNr
|
||||
// return rawShifts.map(shift => ({
|
||||
// ...shift,
|
||||
// weekNr: common.getWeekNumber(new Date(shift.startTime)),
|
||||
// shiftNr: rawShifts.filter(s => common.getISODateOnly(s.startTime) === common.getISODateOnly(shift.startTime)).indexOf(shift) + 1,
|
||||
// weekDay: common.DaysOfWeekArray[new Date(shift.startTime).getDayEuropean()],
|
||||
// }));
|
||||
// }
|
||||
// function findTheSameShiftFromLastMonth(shiftsLastMonth, day, weekNr, shiftNr) {
|
||||
// let weekDay = common.DaysOfWeekArray[day.getDayEuropean()];
|
||||
// return shiftsLastMonth.find(s => {
|
||||
// return s.weekNr === weekNr &&
|
||||
// s.shiftNr === shiftNr &&
|
||||
// s.weekDay === weekDay;
|
||||
// });
|
||||
// }
|
||||
|
||||
// //ToDo use bulk find instead of loop
|
||||
// async function getAvailablePublishersForShift(startTime, endTime, allPublishers, publishersThisWeek) {
|
||||
// let availablePublishers = [];
|
||||
|
||||
// for (let publisher of allPublishers) {
|
||||
// let availability = await FindPublisherAvailability(publisher.id, startTime, endTime);
|
||||
|
||||
// if (availability && !publishersThisWeek.includes(publisher.id)) {
|
||||
// availablePublishers.push(publisher);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return availablePublishers;
|
||||
// }
|
||||
|
||||
|
||||
// ### COPIED TO shift api (--) ###
|
||||
|
||||
async function DeleteShiftsForMonth(monthInfo) {
|
||||
try {
|
||||
@ -1110,57 +1166,6 @@ async function DeleteShiftsForDay(date) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getShiftsFromLastMonth(monthInfo) {
|
||||
const prisma = common.getPrismaClient();
|
||||
// Fetch shifts for the month
|
||||
const rawShifts = await prisma.shift.findMany({
|
||||
where: {
|
||||
startTime: {
|
||||
gte: monthInfo.firstMonday,
|
||||
lte: monthInfo.lastSunday,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
assignments: {
|
||||
include: {
|
||||
publisher: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Process shifts to add weekNr and shiftNr
|
||||
return rawShifts.map(shift => ({
|
||||
...shift,
|
||||
weekNr: common.getWeekNumber(new Date(shift.startTime)),
|
||||
shiftNr: rawShifts.filter(s => common.getISODateOnly(s.startTime) === common.getISODateOnly(shift.startTime)).indexOf(shift) + 1,
|
||||
weekDay: common.DaysOfWeekArray[new Date(shift.startTime).getDayEuropean()],
|
||||
}));
|
||||
}
|
||||
function findTheSameShiftFromLastMonth(shiftsLastMonth, day, weekNr, shiftNr) {
|
||||
let weekDay = common.DaysOfWeekArray[day.getDayEuropean()];
|
||||
return shiftsLastMonth.find(s => {
|
||||
return s.weekNr === weekNr &&
|
||||
s.shiftNr === shiftNr &&
|
||||
s.weekDay === weekDay;
|
||||
});
|
||||
}
|
||||
|
||||
//ToDo use bulk find instead of loop
|
||||
async function getAvailablePublishersForShift(startTime, endTime, allPublishers, publishersThisWeek) {
|
||||
let availablePublishers = [];
|
||||
|
||||
for (let publisher of allPublishers) {
|
||||
let availability = await FindPublisherAvailability(publisher.id, startTime, endTime);
|
||||
|
||||
if (availability && !publishersThisWeek.includes(publisher.id)) {
|
||||
availablePublishers.push(publisher);
|
||||
}
|
||||
}
|
||||
|
||||
return availablePublishers;
|
||||
}
|
||||
|
||||
async function FindPublisherAvailability(publisherId, startDate, endDate, dayOfWeekEnum, weekNr) {
|
||||
const prisma = common.getPrismaClient();
|
||||
const start = new Date(startDate);
|
||||
@ -1220,9 +1225,6 @@ async function FindPublisherAvailability(publisherId, startDate, endDate, dayOfW
|
||||
return exactAvailabilities.length > 0 ? exactAvailabilities[0] : repeatingAvailabilities.length > 0 ? repeatingAvailabilities[0] : null;
|
||||
}
|
||||
|
||||
// ### COPIED TO shift api (--) ###
|
||||
|
||||
|
||||
// function matchesAvailability(avail, filterDate) {
|
||||
// // Setting the start and end time of the filterDate
|
||||
// filterDate.setHours(0, 0, 0, 0);
|
||||
@ -1268,7 +1270,7 @@ module.exports = {
|
||||
getCoverMePublisherEmails,
|
||||
getAllPublishersWithStatisticsMonth,
|
||||
getCalendarEvents,
|
||||
GenerateSchedule,
|
||||
// GenerateSchedule,
|
||||
DeleteShiftsForMonth,
|
||||
DeleteShiftsForDay,
|
||||
};
|
Reference in New Issue
Block a user