changes to auto shift generation
This commit is contained in:
@ -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
|
||||
// );
|
||||
// }
|
Reference in New Issue
Block a user