diff --git a/pages/api/shiftgenerate.ts b/pages/api/shiftgenerate.ts index 214ad13..2a35b75 100644 --- a/pages/api/shiftgenerate.ts +++ b/pages/api/shiftgenerate.ts @@ -123,25 +123,65 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { // ### COPIED TO shift api (++) ### - -let scheduledPubsPerDayAndWeek = {}; +let publisherMonthlyAssignments = new Map(); +let scheduledPubsCacheStatistics = {}; // Function to flatten the registry // Function to update the registry function updateRegistry(publisherId, day, weekNr) { // Registry schema: {day: {weekNr: [publisherIds]}} const dayKey = common.getISODateOnly(day); - if (!scheduledPubsPerDayAndWeek[dayKey]) { - scheduledPubsPerDayAndWeek[dayKey] = {}; + if (!scheduledPubsCacheStatistics[dayKey]) { + scheduledPubsCacheStatistics[dayKey] = {}; } - if (!scheduledPubsPerDayAndWeek[dayKey][weekNr]) { - scheduledPubsPerDayAndWeek[dayKey][weekNr] = []; + if (!scheduledPubsCacheStatistics[dayKey][weekNr]) { + scheduledPubsCacheStatistics[dayKey][weekNr] = []; } - scheduledPubsPerDayAndWeek[dayKey][weekNr].push(publisherId); + scheduledPubsCacheStatistics[dayKey][weekNr].push(publisherId); + // Update monthly assignments + const currentCount = publisherMonthlyAssignments.get(publisherId) || 0; + publisherMonthlyAssignments.set(publisherId, currentCount + 1); } function flattenRegistry(dayKey) { - const weekEntries = scheduledPubsPerDayAndWeek[dayKey] || {}; + const weekEntries = scheduledPubsCacheStatistics[dayKey] || {}; return Object.values(weekEntries).flat(); } +// Function to initialize monthly counts from database +async function initializeMonthlyAssignments(prisma, startOfMonth, endOfMonth) { + const monthlyAssignments = await prisma.assignment.groupBy({ + by: ['publisherId'], + where: { + shift: { + startTime: { + gte: startOfMonth, + lte: endOfMonth + } + } + }, + _count: { + publisherId: true + } + }); + + publisherMonthlyAssignments = new Map( + monthlyAssignments.map(count => [ + count.publisherId, + count._count.publisherId + ]) + ); +} +// Function to get current assignment count for a publisher +function getPublisherAssignmentCount(publisherId) { + return publisherMonthlyAssignments.get(publisherId) || 0; +} + +// Function to update publishers with their current counts +function updatePublishersWithCurrentCounts(publishers) { + return publishers.map(pub => ({ + ...pub, + currentMonthAssignments: getPublisherAssignmentCount(pub.id) + })); +} + async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, autoFill = false, forDay, algType = 0, until) { @@ -167,7 +207,7 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto let shiftsLastMonth = await getShiftsFromLastMonth(lastMonthInfo); let publishers = await data.getAllPublishersWithStatisticsMonth(date, false, false); - let shiftAssignments = []; + let shiftAssignments: any[] = []; let day = new Date(monthInfo.firstMonday); let endDate = monthInfo.lastSunday; let dayNr = 1; @@ -188,7 +228,12 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto weekNr = common.getWeekNumber(monthInfo.date); } - let publishersThisWeek = []; + /** + * An array to store the publishers for the current week. + * + * @type {never[]} + */ + let publishersThisWeek: never[] = []; // # # # # # # # # # # # @@ -242,25 +287,25 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto //--------------------------------------------------- // // 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 (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); - publisher.currentMonthAssignments += 1; - } - else { - console.log(" " + publisher.firstName + " " + publisher.lastName + " skipped (transport already assigned)"); - } - } + // if (!isWithTransport || transportCount < 2) { + // shiftAssignments.push({ + // publisherId: publisher.id, + // isConfirmed: true, + // isBySystem: true, + // isWithTransport: isWithTransport + // }); + // publishersThisWeek.push(publisher.id); + // updateRegistry(publisher.id, day, weekNr); + // publisher.currentMonthAssignments += 1; + // } + // else { + // console.log(" " + publisher.firstName + " " + publisher.lastName + " skipped (transport already assigned)"); + // } + // } //--------------------------------------------------- } @@ -359,7 +404,7 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(day); let event = events.find((event) => event.dayofweek == common.DaysOfWeekArray[day.getDayEuropean()]); if (event) { - let availablePubsForTheDay = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', day, false, false, false, true, false); + // let availablePubsForTheDay = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', day, false, false, false, true, false); let shifts = allShifts.filter(s => common.getISODateOnly(s.startTime) === common.getISODateOnly(day)); let publishersToday = await prisma.assignment.findMany({ @@ -395,20 +440,22 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto else if (publishersNeeded > 0) { console.log("shift " + shift.name + " requires transport (" + transportCapable.length + " transport capable)"); - let availablePubsForTheShift = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type,familyHeadId', shift.startTime, true, false, false, true, false); + const availablePubsForTheShift = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type,familyHeadId', shift.startTime, true, false, false, true, false); + const availablePubsWithCounts = updatePublishersWithCurrentCounts(availablePubsForTheShift); - let availablePublishers = availablePubsForTheShift.filter(p => { + + const availablePublishers = availablePubsWithCounts.filter(p => { const hasTransportInAvailability = shift.transportIn && p.availabilities.some(avail => avail.isWithTransportIn); const hasTransportOutAvailability = shift.transportOut && p.availabilities.some(avail => avail.isWithTransportOut); return (hasTransportInAvailability || hasTransportOutAvailability); }); - availablePublishers = await FilterInappropriatePublishers([...availablePublishers], publishersToday, shift); + const appropriatePublishers = await FilterInappropriatePublishers([...availablePublishers], publishersToday, shift, 10); if (algType == 0) { - rankedPublishers = await RankPublishersForShiftOld([...availablePublishers], scheduledPubsPerDayAndWeek, day, weekNr); + rankedPublishers = await RankPublishersForShiftOld([...appropriatePublishers], scheduledPubsCacheStatistics, day); } else if (algType == 1) { - rankedPublishers = await RankPublishersForShiftWeighted([...availablePublishers], scheduledPubsPerDayAndWeek, day, weekNr); + rankedPublishers = await RankPublishersForShiftWeighted([...appropriatePublishers], scheduledPubsCacheStatistics, day, weekNr); } AddPublisherAssignment(prisma, event, shift, availablePublishers, rankedPublishers, publishersToday, day, weekNr); @@ -443,7 +490,7 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(day); let event = events.find((event) => event.dayofweek == common.DaysOfWeekArray[day.getDayEuropean()]); if (event) { - let availablePubsForTheDay = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', day, false, false, false, true, false); + //let availablePubsForTheDay = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', day, false, false, false, true, false); let shifts = allShifts.filter(s => common.getISODateOnly(s.startTime) === common.getISODateOnly(day)); let publishersToday = await prisma.assignment.findMany({ where: { @@ -468,11 +515,11 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto if (publishersNeeded > 0 && shift.assignments.length < goal) { let availablePubsForTheShift = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type,familyHeadId', shift.startTime, true, false, false, true, false); - let availablePublishers = await FilterInappropriatePublishers([...availablePubsForTheShift], publishersToday, shift); + let availablePublishers = await FilterInappropriatePublishers([...availablePubsForTheShift], publishersToday, shift, 10); if (algType == 0) { - rankedPublishers = await RankPublishersForShiftOld([...availablePublishers], scheduledPubsPerDayAndWeek, day); + rankedPublishers = await RankPublishersForShiftOld([...availablePublishers], scheduledPubsCacheStatistics, day); } else if (algType == 1) { - rankedPublishers = await RankPublishersForShiftWeighted([...availablePublishers], scheduledPubsPerDayAndWeek, day, weekNr); + rankedPublishers = await RankPublishersForShiftWeighted([...availablePublishers], scheduledPubsCacheStatistics, day, weekNr); } await AddPublisherAssignment(prisma, event, shift, availablePublishers, rankedPublishers, publishersToday, day, weekNr); @@ -509,10 +556,16 @@ async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheS } else { for (let i = 0; i < rankedPublishers.length; i++) { let mainPublisher = rankedPublishers[i]; - let familyMembers = availablePubsForTheShift.filter(p => (p.id !== mainPublisher.id && (p.id === mainPublisher.familyHeadId) || (p.familyHeadId === mainPublisher.id))); + let familyMembers = availablePubsForTheShift.filter(p => { + const isNotSelf = p.id !== mainPublisher.id; + const isMyFamilyHead = p.id === mainPublisher.familyHeadId; + const isMyFamilyMember = p.familyHeadId === mainPublisher.id; + + return isNotSelf && (isMyFamilyHead || isMyFamilyMember); + }); if (familyMembers.length > 0 && (shift.assignments.length + familyMembers.length + 1) <= event.numberOfPublishers) { - console.log("Assigning " + mainPublisher.firstName + " " + mainPublisher.lastName + " and " + familyMembers.length + " available family members to " + new Date(shift.startTime).getDate() + " " + shift.name); + console.log("Assigning " + mainPublisher.firstName + " " + mainPublisher.lastName + " and family members: '" + familyMembers.map(fm => `${fm.firstName} ${fm.lastName}`).join(", ") + `' to ${new Date(shift.startTime).getDate()} ${shift.name}`); const hasTransportInAvailability = shift.transportIn && mainPublisher.availabilities.some(avail => avail.isWithTransportIn); const hasTransportOutAvailability = shift.transportOut && mainPublisher.availabilities.some(avail => avail.isWithTransportOut); @@ -537,7 +590,6 @@ async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheS shift.assignments.push(newAssignment); publishersToday.push(mainPublisher.id); updateRegistry(mainPublisher.id, day, weekNr); - mainPublisher.currentMonthAssignments += 1; for (const familyMember of familyMembers) { const newFamilyAssignment = await prisma.assignment.create({ @@ -560,7 +612,6 @@ async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheS shift.assignments.push(newFamilyAssignment); publishersToday.push(familyMember.id); updateRegistry(familyMember.id, day, weekNr); - familyMember.currentMonthAssignments += 1; } break; @@ -590,7 +641,6 @@ async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheS shift.assignments.push(newAssignment); publishersToday.push(mainPublisher.id); updateRegistry(mainPublisher.id, day, weekNr); - mainPublisher.currentMonthAssignments += 1; break; } } @@ -619,7 +669,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: Date) { +async function RankPublishersForShiftOld(publishers, stats, currentDay: Date) { publishers.forEach(p => { p.DesiredMinusCurrent = p.desiredShiftsPerMonth - p.currentMonthAssignments; }); @@ -672,7 +722,7 @@ async function RankPublishersForShiftOld(publishers, scheduledPubsPerDayAndWeek, // ToDo: add negative weights for currentweekAssignments, so we avoid assigning the same publishers multiple times in a week. having in mind the days difference between shifts. -async function RankPublishersForShiftWeighted(publishers, scheduledPubsPerDayAndWeek, currentDay, currentWeekNr) { +async function RankPublishersForShiftWeighted(publishers, stats, currentDay, currentWeekNr) { // Define weights for each criterion const weights = { gender: 2, @@ -694,6 +744,9 @@ async function RankPublishersForShiftWeighted(publishers, scheduledPubsPerDayAnd }); const calculateScoreAndPenalties = (p) => { + + // apply for reaching desired shifts per month + let score = (p.isMale ? weights.gender : 0) - (p.desiredCompletion * weights.desiredCompletion) + ((1 - p.currentMonthAvailabilityHoursCount / 24) * weights.availability) + diff --git a/pages/cart/calendar/index.tsx b/pages/cart/calendar/index.tsx index b0d2df0..70fb116 100644 --- a/pages/cart/calendar/index.tsx +++ b/pages/cart/calendar/index.tsx @@ -730,10 +730,12 @@ export default function CalendarPage({ initialEvents, initialShifts }) { Генерирай смени - + {/* ✧✨ */} + {/* + Генерирай смени 3 */} +