974 lines
36 KiB
TypeScript
974 lines
36 KiB
TypeScript
import { getToken } from "next-auth/jwt";
|
||
import { authOptions } from './auth/[...nextauth]'
|
||
import { getServerSession } from "next-auth/next"
|
||
import { NextApiRequest, NextApiResponse } from 'next'
|
||
import { DayOfWeek, AvailabilityType, UserRole, EventLogType } from '@prisma/client';
|
||
const common = require('../../src/helpers/common');
|
||
const dataHelper = require('../../src/helpers/data');
|
||
const subq = require('../../prisma/bl/subqueries');
|
||
import { set, format, addMinutes, addDays } from 'date-fns';
|
||
|
||
import fs from 'fs';
|
||
import path from 'path';
|
||
import { all } from "axios";
|
||
import { logger } from "src/helpers/common";
|
||
import { generatePublishersExcel } from "src/helpers/excel";
|
||
|
||
/**
|
||
*
|
||
* @param req import { NextApiRequest, NextApiResponse } from 'next'
|
||
* @param res import { NextApiRequest, NextApiResponse } from 'next'
|
||
*/
|
||
export default async function handler(req, res) {
|
||
const prisma = common.getPrismaClient();
|
||
|
||
// Retrieve and validate the JWT token
|
||
const token = await getToken({ req: req });
|
||
if (!token) {
|
||
// If no token or invalid token, return unauthorized status
|
||
return res.status(401).json({ message: "Unauthorized to call this API endpoint" });
|
||
}
|
||
else {
|
||
// If token is valid, log the user
|
||
//console.log("JWT | User: " + token.email);
|
||
}
|
||
|
||
var action = req.query.action;
|
||
var filter = req.query.filter;
|
||
let day: Date;
|
||
let isExactTime;
|
||
if (req.query.date) {
|
||
day = new Date(req.query.date);
|
||
//date.setDate(date.getDate()); // Subtract one day to get the correct date, as calendar sends wrong date (one day ahead)
|
||
//date.setHours(0, 0, 0, 0);
|
||
}
|
||
if (req.query.filterDate) {
|
||
day = new Date(req.query.filterDate);
|
||
isExactTime = true;
|
||
}
|
||
|
||
let monthInfo = common.getMonthDatesInfo(day);
|
||
const searchText = req.query.searchText?.normalize('NFC');
|
||
|
||
const sessionServer = await getServerSession(req, res, authOptions)
|
||
var isAdmin = sessionServer?.user.role == UserRole.ADMIN
|
||
|
||
try {
|
||
switch (action) {
|
||
case "initDb":
|
||
// Read the SQL script from the file
|
||
const sqlFilePath = path.join(process.cwd(), 'prisma', 'data.sql');
|
||
const sql = fs.readFileSync(sqlFilePath, 'utf8');
|
||
|
||
// Execute the SQL script
|
||
await prisma.$executeRawUnsafe(sql);
|
||
res.status(200).json({ message: "SQL script executed successfully" });
|
||
break;
|
||
|
||
case "settings":
|
||
try {
|
||
const key = req.query.key;
|
||
switch (req.method) {
|
||
case "LIST":
|
||
let s1 = await prisma.settings.findMany();
|
||
res.status(200).json(s1.map(setting => setting.key));
|
||
break;
|
||
case "GET":
|
||
const s2 = await prisma.settings.findUnique({
|
||
where: {
|
||
key: key
|
||
}
|
||
});
|
||
res.status(200).json(s2);
|
||
|
||
break;
|
||
case "PUT": //create or update
|
||
const value = req.query.value;
|
||
const results = await prisma.settings.upsert({
|
||
where: {
|
||
key: key
|
||
},
|
||
update: {
|
||
value: value
|
||
},
|
||
create: {
|
||
key: key,
|
||
value: value
|
||
}
|
||
});
|
||
res.status(200).json(results);
|
||
break;
|
||
default:
|
||
res.status(405).json({ message: "Method Not Allowed" });
|
||
break;
|
||
}
|
||
} catch (error) {
|
||
console.error("Error getting settings: " + error);
|
||
res.status(500).json({ error });
|
||
}
|
||
break;
|
||
|
||
|
||
case "deleteAllPublishers":
|
||
//get filter and delete all publishers containing that in first name or last name
|
||
await prisma.publisher.deleteMany({
|
||
where: {
|
||
OR: [
|
||
{ firstName: { contains: filter } },
|
||
{ lastName: { contains: filter } },
|
||
],
|
||
},
|
||
});
|
||
res.status(200).json({ "message": "ok" });
|
||
break;
|
||
case "deleteAllAvailabilities":
|
||
//get filter and delete all publishers containing that in first name or last name
|
||
await prisma.availability.deleteMany({
|
||
where: filter ? {
|
||
OR: [
|
||
// { name: { contains: filter } },
|
||
{ starTime: { lte: day } }
|
||
]
|
||
} : {}
|
||
});
|
||
res.status(200).json({ "message": "ok" });
|
||
break;
|
||
//gets publisher by names with availabilities and assignments
|
||
case "deleteAvailabilityForPublisher":
|
||
let publisherId = req.query.publisherId;
|
||
let dateFor;
|
||
if (req.query.date) {
|
||
dateFor = new Date(req.query.date);
|
||
//get month info from date
|
||
monthInfo = common.getMonthDatesInfo(dateFor);
|
||
}
|
||
const deleteFromPreviousAssignments = common.parseBool(req.query.deleteFromPreviousAssignments);
|
||
// if datefor is not null/undefined, delete availabilities for that month
|
||
try {
|
||
await prisma.availability.deleteMany({
|
||
where: {
|
||
publisherId: publisherId,
|
||
startTime: { gte: monthInfo?.firstMonday },
|
||
endTime: { lte: monthInfo?.lastSunday }
|
||
}
|
||
});
|
||
if (deleteFromPreviousAssignments) {
|
||
await prisma.availability.deleteMany({
|
||
where: {
|
||
publisherId: publisherId,
|
||
isFromPreviousAssignment: true
|
||
}
|
||
});
|
||
}
|
||
// await prisma.availability.deleteMany({
|
||
// where: {
|
||
// publisherId: publisherId
|
||
// }
|
||
// });
|
||
res.status(200).json({ "message": "ok" });
|
||
} catch (error) {
|
||
console.error("Error deleting availability for publisher: " + publisherId + " error: " + error);
|
||
res.status(500).json({ error });
|
||
}
|
||
break;
|
||
|
||
case "createAvailabilities": {
|
||
const availabilities = req.body;
|
||
//! console.log("createAvailabilities: " + JSON.stringify(availabilities));
|
||
try {
|
||
let createResults = await prisma.availability.createMany({
|
||
data: availabilities
|
||
});
|
||
res.status(200).json({ "message": "ok", "results": createResults });
|
||
} catch (error) {
|
||
console.error("Error creating availabilities: " + error);
|
||
res.status(500).json({ error });
|
||
}
|
||
}
|
||
break;
|
||
|
||
case "getCalendarEvents":
|
||
let events = await dataHelper.getCalendarEvents(req.query.publisherId, true, true, isAdmin);
|
||
res.status(200).json(events);
|
||
|
||
case "getPublisherInfo":
|
||
|
||
// let pubs = await filterPublishers("id,firstName,lastName,email".split(","), "", null, req.query.assignments || true, req.query.availabilities || true, false, req.query.id);
|
||
let pubs = await dataHelper.filterPublishersNew("id,firstName,lastName,email,isActive,assignments,availabilities", day, false, true, false, true, false, req.query.id, true);
|
||
let pub = pubs[0] || {};
|
||
if (pub) {
|
||
let dayOfWeekQuery = common.getDayOfWeek(day);
|
||
|
||
pub.availabilities = pub.availabilities.map(avail => {
|
||
if (avail.dayOfMonth == null) {
|
||
let dayOfWeek = common.getDayOfWeekIndex(avail.dayofweek);
|
||
let newStart = new Date(day);
|
||
newStart = addDays(newStart, dayOfWeek - dayOfWeekQuery);
|
||
newStart.setHours(avail.startTime.getHours(), avail.startTime.getMinutes(), 0, 0);
|
||
let newEnd = new Date(newStart);
|
||
newEnd.setHours(avail.endTime.getHours(), avail.endTime.getMinutes(), 0, 0);
|
||
return {
|
||
...avail,
|
||
startTime: newStart,
|
||
endTime: newEnd
|
||
}
|
||
}
|
||
return avail;
|
||
});
|
||
}
|
||
//let pubs = await dataHelper.filterPublishersNew("id,firstName,lastName,email,isActive,assignments,availabilities", day, false, true, false, true, false, req.query.id);
|
||
res.status(200).json(pub);
|
||
break;
|
||
case "filterPublishersNew":
|
||
let includeOldAvailabilities = common.parseBool(req.query.includeOldAvailabilities);
|
||
let results = await filterPublishersNew_Available(req.query.select, day,
|
||
common.parseBool(req.query.isExactTime), common.parseBool(req.query.isForTheMonth), true, includeOldAvailabilities, req.query.id);
|
||
res.status(200).json(results);
|
||
break;
|
||
|
||
case "getMonthlyStatistics":
|
||
let allpubs = await getMonthlyStatistics("id,firstName,lastName,email", day);
|
||
res.status(200).json(allpubs);
|
||
break;
|
||
|
||
case "getUnassignedPublishers":
|
||
//let monthInfo = common.getMonthDatesInfo(date);
|
||
let allPubs = await filterPublishers("id,firstName,lastName,email,isActive".split(","), "", day, true, true, false);
|
||
let unassignedPubs = allPubs.filter(pub => pub.currentMonthAssignments == 0 && pub.availabilities.length > 0);
|
||
res.status(200).json(unassignedPubs);
|
||
break;
|
||
case "filterPublishers":
|
||
const fetchAssignments = common.parseBool(req.query.assignments);
|
||
const fetchAvailabilities = common.parseBool(req.query.availabilities);
|
||
let publishers = await filterPublishers(req.query.select, searchText, day, fetchAssignments, fetchAvailabilities);
|
||
//!console.log("publishers: (" + publishers.length + ") " + JSON.stringify(publishers.map(pub => pub.firstName + " " + pub.lastName)));
|
||
res.status(200).json(publishers);
|
||
break;
|
||
|
||
// find publisher by full name or email
|
||
case "findPublisher":
|
||
const getAll = common.parseBool(req.query.all) || false;
|
||
let publisher = await dataHelper.findPublisher(filter, req.query.email, req.query.select, getAll);
|
||
res.status(200).json(publisher);
|
||
break;
|
||
|
||
case "getShiftsForDay":
|
||
|
||
// Setting the range for a day: starting from the beginning of the date and ending just before the next date.
|
||
let startOfDay = new Date(day.getFullYear(), day.getMonth(), day.getDate());
|
||
let endOfDay = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 23, 59, 59, 999);
|
||
|
||
const pubAvCount = await prisma.publisher.findMany({
|
||
select: {
|
||
id: true,
|
||
firstName: true,
|
||
lastName: true,
|
||
_count: {
|
||
select: {
|
||
availabilities: true,
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
let shiftsForDate = await prisma.shift.findMany({
|
||
where: {
|
||
startTime: {
|
||
gte: startOfDay,
|
||
lt: endOfDay
|
||
},
|
||
},
|
||
include: {
|
||
assignments: {
|
||
include: {
|
||
publisher: subq.publisherSelect
|
||
},
|
||
},
|
||
},
|
||
});
|
||
|
||
shiftsForDate.forEach(shift => {
|
||
shift.assignments.forEach(assignment => {
|
||
assignment.publisher.availabilityCount = pubAvCount.find(pub => pub.id === assignment.publisher.id)?._count?.availabilities || 0;
|
||
});
|
||
}
|
||
);
|
||
|
||
console.log("shiftsForDate(" + day + ") - " + shiftsForDate.length + " : " + JSON.stringify(shiftsForDate.map(shift => shift.id)));
|
||
|
||
res.status(200).json(shiftsForDate);
|
||
break;
|
||
|
||
case "copyOldAvailabilities":
|
||
//get all publishers that don't have availabilities for the current month
|
||
monthInfo = common.getMonthDatesInfo(day);
|
||
// await prisma.availability.deleteMany({
|
||
// where: {
|
||
// startTime: {
|
||
// gte: monthInfo.firstMonday,
|
||
// },
|
||
// isFromPreviousMonth: true
|
||
// }
|
||
// });
|
||
let outdatedPubs = await prisma.publisher.findMany({
|
||
where: {
|
||
availabilities: {
|
||
none: {
|
||
startTime: {
|
||
gte: monthInfo.firstMonday,
|
||
}
|
||
}
|
||
}
|
||
},
|
||
select: {
|
||
id: true,
|
||
firstName: true,
|
||
lastName: true,
|
||
availabilities: true
|
||
}
|
||
});
|
||
outdatedPubs.forEach(async pub => {
|
||
// avail.startTime >= monthInfo.firstMonday
|
||
//get prev month date:
|
||
let prevMonth = new Date(monthInfo.firstMonday);
|
||
prevMonth.setMonth(prevMonth.getMonth() - 1);
|
||
let prevMonthInfo = common.getMonthDatesInfo(prevMonth);
|
||
pub.availabilities = pub.availabilities.filter(avail => avail.startTime > prevMonthInfo.firstMonday);
|
||
console.log("" + pub.firstName + " " + pub.lastName + " copying " + pub.availabilities.length + " availabilities from previous months.");
|
||
pub.availabilities.forEach(async avail => {
|
||
//get the new date based on the day of week and week of month
|
||
if (!avail.weekOfMonth) {
|
||
avail.weekOfMonth = common.getWeekOfMonth(avail.startTime)
|
||
}
|
||
let origMonthInfo = common.getMonthDatesInfo(avail.startTime);
|
||
|
||
let newStart = common.getDateFromWeekNrAndDayOfWeek(monthInfo.firstMonday, avail.weekOfMonth, avail.dayofweek, avail.startTime);
|
||
//ToDo: fix double check. also check if we're in 5th week and the month has 4 weeks
|
||
// const availability = await data.findPublisherAvailability(publisher.id, newStart);
|
||
// if (availability) {
|
||
// return;
|
||
// }
|
||
let newEnd = new Date(newStart.getTime());
|
||
newEnd.setHours(avail.endTime.getHours(), avail.endTime.getMinutes(), 0, 0);
|
||
let data = {
|
||
publisherId: pub.id,
|
||
dayOfMonth: null,
|
||
dayofweek: avail.dayofweek || common.getDayOfWeekNameEnEnumForDate(avail.startTime),
|
||
weekOfMonth: avail.weekofMonth || common.getWeekOfMonth(avail.startTime),
|
||
// null for auto generated availabilities
|
||
//dateOfEntry: new Date(), //avail.dateOfEntry || avail.startTime,
|
||
startTime: newStart,
|
||
endTime: newEnd,
|
||
type: AvailabilityType.Monthly,
|
||
isFromPreviousMonth: true,
|
||
name: avail.name || "старо предпочитание",
|
||
parentAvailabilityId: avail.id,
|
||
// parentAvailability: {
|
||
// connect: {
|
||
// id: avail.id
|
||
// }
|
||
// },
|
||
}
|
||
await prisma.availability.create({ data: data });
|
||
|
||
//if month has 5 weeks and the monthInfo has 4 weeks copy the availabilities also from the 1st week to the 5th week
|
||
if (monthInfo.nrOfWeeks == 5 && avail.weekOfMonth == 1 && origMonthInfo.nrOfWeeks == 4) {
|
||
newStart = common.getDateFromWeekNrAndDayOfWeek(monthInfo.firstMonday, 5, avail.dayofweek, avail.startTime);
|
||
newEnd = new Date(newStart.getTime());
|
||
newEnd.setHours(avail.endTime.getHours(), avail.endTime.getMinutes(), 0, 0);
|
||
data.weekOfMonth = 5;
|
||
data.startTime = newStart;
|
||
data.endTime = newEnd;
|
||
await prisma.availability.create({ data: data });
|
||
}
|
||
|
||
});
|
||
});
|
||
|
||
//convert old assignments to availabilities
|
||
|
||
res.status(200).json({ "message": "ok" });
|
||
break;
|
||
case "deleteCopiedAvailabilities":
|
||
//delete all availabilities that are copied from previous months
|
||
monthInfo = common.getMonthDatesInfo(day);
|
||
await prisma.availability.deleteMany({
|
||
where: {
|
||
startTime: {
|
||
gte: monthInfo.firstMonday,
|
||
},
|
||
isFromPreviousMonth: true
|
||
}
|
||
});
|
||
|
||
case "replaceInAssignment":
|
||
const { oldPublisherId, newPublisherId, shiftId } = req.method === "POST" ? req.body : req.query;
|
||
|
||
const result = await replaceInAssignment(oldPublisherId, newPublisherId, shiftId);
|
||
res.status(200).json(result);
|
||
break;
|
||
|
||
case "updateShifts":
|
||
//get all shifts for the month and publish them (we pass date )
|
||
let isPublished = common.parseBool(req.query.isPublished);
|
||
let updated = await prisma.shift.updateMany({
|
||
where: {
|
||
startTime: {
|
||
gte: new Date(monthInfo.firstMonday.getFullYear(), monthInfo.firstMonday.getMonth(), monthInfo.firstMonday.getDate()),
|
||
lt: new Date(monthInfo.lastSunday.getFullYear(), monthInfo.lastSunday.getMonth() + 1, 1),
|
||
}
|
||
},
|
||
data: {
|
||
isPublished: isPublished
|
||
}
|
||
});
|
||
console.log("Updated shifts: " + updated.count);
|
||
res.status(200).json({ "message": "ok" });
|
||
|
||
break;
|
||
case "getPossibleShiftPublisherEmails":
|
||
let data = await dataHelper.getCoverMePublisherEmails(parseInt(req.query.shiftId));
|
||
res.status(200).json(data);
|
||
break;
|
||
case "getAllPublishersWithStatistics":
|
||
let noEndDate = common.parseBool(req.query.noEndDate);
|
||
res.status(200).json(await dataHelper.getAllPublishersWithStatisticsMonth(day, noEndDate));
|
||
case "exportPublishersExcel":
|
||
try {
|
||
const today = new Date();
|
||
const dateStr = today.toISOString().split('T')[0]; // Gets YYYY-MM-DD format
|
||
|
||
const excelBuffer = await generatePublishersExcel();
|
||
res.setHeader("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||
res.setHeader("Content-Disposition", "attachment; filename=" + encodeURI(`Publishers_${dateStr}.xlsx`));
|
||
res.send(excelBuffer);
|
||
} catch (error) {
|
||
console.error(JSON.stringify(error));
|
||
res.status(500).json({ error: "Failed to generate Excel file" });
|
||
}
|
||
break;
|
||
default:
|
||
res.status(200).json({
|
||
"message": "no action '" + action + "' found"
|
||
});
|
||
break;
|
||
}
|
||
} catch (error) {
|
||
console.error("API: Error executing action: " + action + " with filter: " + filter + " error: " + error);
|
||
res.status(500).json({ error });
|
||
}
|
||
}
|
||
|
||
|
||
|
||
export async function getMonthlyStatistics(selectFields, filterDate) {
|
||
|
||
let publishers = [];
|
||
selectFields = selectFields?.split(",");
|
||
let selectBase = selectFields.reduce((acc, curr) => {
|
||
acc[curr] = true;
|
||
return acc;
|
||
}, {});
|
||
selectBase.assignments = {
|
||
select: {
|
||
id: true,
|
||
shift: {
|
||
select: {
|
||
id: true,
|
||
startTime: true,
|
||
endTime: true
|
||
}
|
||
}
|
||
}
|
||
};
|
||
let currentWeekStart: Date, currentWeekEnd: Date,
|
||
currentMonthStart: Date, currentMonthEnd: Date,
|
||
previousMonthStart: Date, previousMonthEnd: Date;
|
||
|
||
|
||
let date = new Date(filterDate);
|
||
date.setDate(filterDate.getDate());
|
||
currentWeekStart = common.getStartOfWeek(date);
|
||
currentWeekEnd = common.getEndOfWeek(date);
|
||
|
||
var monthInfo = common.getMonthDatesInfo(date);
|
||
currentMonthStart = monthInfo.firstMonday;
|
||
currentMonthEnd = monthInfo.lastSunday;
|
||
date.setMonth(date.getMonth() - 1);
|
||
monthInfo = common.getMonthDatesInfo(date);
|
||
previousMonthStart = monthInfo.firstMonday;
|
||
previousMonthEnd = monthInfo.lastSunday;
|
||
|
||
|
||
const prisma = common.getPrismaClient();
|
||
publishers = await prisma.publisher.findMany({
|
||
select: {
|
||
...selectBase,
|
||
}
|
||
});
|
||
|
||
publishers.forEach(pub => {
|
||
// Debug logs to help identify issues
|
||
pub.currentWeekAssignments = pub.assignments.filter(assignment => {
|
||
return assignment.shift.startTime >= currentWeekStart && assignment.shift.startTime <= currentWeekEnd;
|
||
}).length;
|
||
pub.currentMonthAssignments = pub.assignments.filter(assignment => {
|
||
return assignment.shift.startTime >= currentMonthStart && assignment.shift.startTime <= currentMonthEnd;
|
||
}).length;
|
||
pub.previousMonthAssignments = pub.assignments.filter(assignment => {
|
||
return assignment.shift.startTime >= previousMonthStart && assignment.shift.startTime <= previousMonthEnd;
|
||
}).length;
|
||
});
|
||
return publishers;
|
||
}
|
||
|
||
|
||
export async function filterPublishersNew_Available(selectFields, filterDate, isExactTime = false, isForTheMonth = false, isWithStats = true, includeOldAvailabilities = false) {
|
||
return dataHelper.filterPublishersNew(selectFields, filterDate, isExactTime, isForTheMonth, false, isWithStats, includeOldAvailabilities);
|
||
// async function filterPublishersNew(selectFields, filterDate, isExactTime = false, isForTheMonth = false, noEndDateFilter = false, isWithStats = true, includeOldAvailabilities = false, id = null, filterAvailabilitiesByDate = true)
|
||
}
|
||
|
||
// availabilites filter:
|
||
// 1. if dayOfMonth is null, match by day of week (enum)
|
||
// 2. if dayOfMonth is not null, match by date
|
||
// 3. if date is 00:00:00, match by date only (without time)
|
||
// 4. if date is not 00:00:00, it should be in the range of start and end times
|
||
// this way we distinguish between weekly availabiillities (entered without dayOfMonth) and old availabilities from previous months (entered with dayOfMonth, but we set it to null),
|
||
// (To validate) we use useDateFilter in combination with the filterDate to get publishers without availabilities for the day:
|
||
// 1: useDateFilter = false, filterDate = null - get all publishers with availabilities for the current month
|
||
// 2: useDateFilter = false, filterDate = date - get all publishers with availabilities for the current month
|
||
// 3: useDateFilter = true, filterDate = null - get all publishers with availabilities for the current month
|
||
// 4: useDateFilter = true, filterDate = date - get all publishers with availabilities for the current month and filter by date
|
||
|
||
export async function filterPublishers(selectFields, searchText, filterDate, fetchAssignments: boolean = true, fetchAvailabilities: boolean = true, useDateFilter = true, id = null) {
|
||
|
||
let currentWeekStart: Date, currentWeekEnd: Date,
|
||
currentMonthStart: Date, currentMonthEnd: Date,
|
||
previousMonthStart: Date, previousMonthEnd: Date,
|
||
filterDateEnd: Date,
|
||
publishers = [];
|
||
|
||
if (!filterDate) {
|
||
useDateFilter = false;
|
||
previousMonthStart = new Date();
|
||
previousMonthStart.setDate(1);
|
||
previousMonthStart.setMonth(previousMonthStart.getMonth() - 1);
|
||
}
|
||
else {
|
||
|
||
let date = new Date(filterDate.getTime());
|
||
//date.setDate(filterDate.getDate());
|
||
currentWeekStart = common.getStartOfWeek(date);
|
||
currentWeekEnd = common.getEndOfWeek(date);
|
||
|
||
var monthInfo = common.getMonthDatesInfo(date);
|
||
currentMonthStart = monthInfo.firstMonday;
|
||
currentMonthEnd = monthInfo.lastSunday;
|
||
date.setMonth(date.getMonth() - 1);
|
||
monthInfo = common.getMonthDatesInfo(date);
|
||
previousMonthStart = monthInfo.firstMonday;
|
||
previousMonthEnd = monthInfo.lastSunday;
|
||
|
||
filterDateEnd = new Date(filterDate);
|
||
filterDateEnd.setHours(23, 59, 59, 999);
|
||
}
|
||
|
||
|
||
let whereClause = {};
|
||
if (id) {
|
||
whereClause = {
|
||
id: String(id)
|
||
}
|
||
}
|
||
const searchTextString = String(searchText || "").trim();
|
||
if (searchTextString !== "") {
|
||
whereClause = {
|
||
OR: [
|
||
{ firstName: { contains: searchTextString } },
|
||
{ lastName: { contains: searchTextString } },
|
||
],
|
||
};
|
||
}
|
||
|
||
// Base select fields
|
||
|
||
// Only attempt to split if selectFields is a string; otherwise, use it as it is.
|
||
selectFields = typeof selectFields === 'string' ? selectFields.split(",") : selectFields;
|
||
|
||
let selectBase = selectFields.reduce((acc, curr) => {
|
||
acc[curr] = true;
|
||
return acc;
|
||
}, {});
|
||
|
||
|
||
// If assignments flag is true, fetch assignments
|
||
if (fetchAssignments) {
|
||
|
||
//!! WORKING CODE, but heavy on the DB !!
|
||
selectBase.assignments = {
|
||
select: {
|
||
id: true,
|
||
shift: {
|
||
select: {
|
||
id: true,
|
||
startTime: true,
|
||
endTime: true,
|
||
isPublished: true
|
||
}
|
||
}
|
||
},
|
||
where: {
|
||
shift: {
|
||
OR: [
|
||
// {
|
||
// startTime: {
|
||
// gte: currentWeekStart,
|
||
// lte: currentWeekEnd
|
||
// }
|
||
// },
|
||
// {
|
||
// startTime: {
|
||
// gte: currentMonthStart,
|
||
// lte: currentMonthEnd
|
||
// }
|
||
// },
|
||
{
|
||
startTime: {
|
||
gte: previousMonthStart,
|
||
// lte: previousMonthEnd
|
||
}
|
||
},
|
||
|
||
]
|
||
}
|
||
}
|
||
};
|
||
//selectBase.assignments = true;
|
||
}
|
||
|
||
let dayOfWeekEnum: DayOfWeek
|
||
if (filterDate) {
|
||
// Determine day of week using common function
|
||
dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(filterDate);
|
||
if (filterDate.getHours() > 21 || filterDate.getHours() < 6) {
|
||
filterDate.setHours(0, 0, 0, 0); // Set to midnight
|
||
}
|
||
}
|
||
// console.log(`filterDate: ${filterDate}`);
|
||
// console.log(`filterDateEnd: ${filterDateEnd}`);
|
||
|
||
|
||
if (filterDate && useDateFilter) {
|
||
// Info, description and ToDo:
|
||
// We should distinguish between availabilities with dayOfMonth and without
|
||
// If dayOfMonth is null, we should match by day of week using the enum
|
||
// If dayOfMonth is not null, we should match by date.
|
||
// if date is 00:00:00, we should match by date only (without time)
|
||
// if date is not 00:00:00, it should be in the range of start and end times
|
||
// we should also include availabilities from previous assignments but not with preference - dayOfMonth is null. we shuold include them only if they match the day of week
|
||
// and distinguish between weekly availabiillities (entered without dayOfMonth) and old availabilities from previous months (entered with dayOfMonth, but we set it to null),
|
||
// which we count as weekly availabilities. We can use the type field for that
|
||
//console.log(`filterDate: ${filterDate}. date: ${filterDate.getDate()}. dayOfWeekEnum: ${dayOfWeekEnum}. useDateFilter: ${useDateFilter}`);
|
||
|
||
// we will have 3 cases: up-to date availabilities, old availabilities from previous months and availabilities from previous assignments but not with preference
|
||
// we will use the type field to distinguish between them
|
||
// up-to date availabilities will have type = 1
|
||
// old availabilities from previous months will have type = 2 - we want to drop that function to simplify the code and avoid confusion
|
||
// availabilities from previous assignments but not with preference will have type = 3
|
||
// also, permanent weekly availabilities will have dayOfMonth = null and type = 0
|
||
// for 0 we will match by dayOfWeekEnum and times
|
||
// for 1 we will match by exact date and times
|
||
// for 2 we will match by dayofweek, weekOfMonth and times
|
||
// for 3 we will match by dayofweek, weekOfMonth and times - this is the same as 2, but we will not count them as availabilities for the current month
|
||
|
||
|
||
// generaion of schedule:
|
||
/*
|
||
option 1: fill from blank - first two places per shift, then more if possible
|
||
option 2: fill from previous schedule , remove all assignments where new availabilities are not available
|
||
and permanent availabilities to make room for changes (we want to shuffle if possible??? do we?)
|
||
continue with option 1 from there
|
||
which one depends on if we prioritize empty shifts or making sure everyone has up to date availabilities
|
||
*/
|
||
|
||
//substract the time difference between from ISO string and local time
|
||
const offset = filterDate.getTimezoneOffset() * 60000; // offset in milliseconds
|
||
var dateAsISO = new Date(filterDate.getTime() + offset);
|
||
//if full day, match by date only
|
||
if (filterDate.getHours() == 0 || dateAsISO.getHours() == 0) {
|
||
whereClause["availabilities"] = {
|
||
some: {
|
||
OR: [
|
||
// Check only by date without considering time ( Assignments on specific days without time)
|
||
{
|
||
startTime: { gte: filterDate },
|
||
endTime: { lte: filterDateEnd },
|
||
}
|
||
,
|
||
// Check if dayOfMonth is null and match by day of week using the enum (Assigments every week)
|
||
// This includes availabilities from previous assignments but not with preference
|
||
{
|
||
dayOfMonth: null, // includes monthly and weekly repeats
|
||
dayofweek: dayOfWeekEnum,
|
||
// ToDo: and weekOfMonth
|
||
startTime: { lte: filterDate },
|
||
AND: [
|
||
{
|
||
OR: [ // OR condition for repeatUntil to handle events that either end after filterDate or repeat forever
|
||
{ endDate: { gte: filterDate } },
|
||
{ endDate: null }
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
};
|
||
}
|
||
//if not full day, match by date and time
|
||
else {
|
||
//match exact time (should be same as data.FindPublisherAvailability())
|
||
whereClause["availabilities"] = {
|
||
some: {
|
||
OR: [
|
||
// Check if dayOfMonth is set and filterDate is between start and end dates (Assignments on specific days AND time)
|
||
{
|
||
dayOfMonth: filterDate.getDate(),
|
||
startTime: { lte: filterDate },
|
||
endTime: { gte: filterDate }
|
||
},
|
||
// Check if dayOfMonth is null and match by day of week using the enum (Assigments every week)
|
||
{
|
||
dayOfMonth: null,
|
||
dayofweek: dayOfWeekEnum,
|
||
startTime: { gte: filterDate },
|
||
}
|
||
]
|
||
}
|
||
};
|
||
}
|
||
} else {
|
||
// we use month filter if date is passed and useDateFilter is false to get all publishers with availabilities for the current month
|
||
if (fetchAvailabilities) {
|
||
// If no filter date, return all publishers's availabilities for currentMonthStart
|
||
whereClause["availabilities"] = {
|
||
some: {
|
||
OR: [
|
||
// Check if dayOfMonth is not null and startTime is after currentMonthStart (Assignments on specific days AND time)
|
||
{
|
||
dayOfMonth: { not: null },
|
||
startTime: { gte: currentMonthStart },
|
||
endTime: { lte: currentMonthEnd }
|
||
},
|
||
// Check if dayOfMonth is null and match by day of week using the enum (Assigments every week)
|
||
{
|
||
dayOfMonth: null,
|
||
}
|
||
]
|
||
}
|
||
};
|
||
//try here
|
||
// selectBase.аvailabilities = {
|
||
// select: {
|
||
// dayofweek: true,
|
||
// dayOfMonth: true,
|
||
// startTime: true,
|
||
// endTime: true,
|
||
// weekOfMonth: true,
|
||
// type: true
|
||
// },
|
||
// where: {
|
||
// OR: [
|
||
// {
|
||
// startTime: { gte: currentMonthStart },
|
||
// endTime: { lte: currentMonthEnd }
|
||
// }
|
||
// ]
|
||
// }
|
||
// }
|
||
}
|
||
}
|
||
|
||
//include availabilities if flag is true
|
||
const prisma = common.getPrismaClient(); //why we need to get it again?
|
||
publishers = await prisma.publisher.findMany({
|
||
where: whereClause,
|
||
select: {
|
||
...selectBase,
|
||
...(fetchAvailabilities && { availabilities: true }),
|
||
// ...(fetchAssignments && {
|
||
// assignments: {
|
||
// include: {
|
||
// shift: true,
|
||
// }
|
||
// }
|
||
// }
|
||
// )
|
||
}
|
||
});
|
||
|
||
//console.log(`publishers: ${publishers.length}, WhereClause: ${JSON.stringify(whereClause)}`);
|
||
|
||
if (filterDate) {
|
||
|
||
if (fetchAssignments) {
|
||
//get if publisher has assignments for current weekday, week, current month, previous month
|
||
publishers.forEach(pub => {
|
||
// Filter assignments for current day
|
||
pub.currentDayAssignments = pub.assignments?.filter(assignment => {
|
||
return assignment.shift.startTime >= filterDate && assignment.shift.startTime <= filterDateEnd;
|
||
}).length;
|
||
|
||
// Filter assignments for current week
|
||
pub.currentWeekAssignments = pub.assignments?.filter(assignment => {
|
||
return assignment.shift.startTime >= currentWeekStart && assignment.shift.startTime <= currentWeekEnd;
|
||
}).length;
|
||
|
||
// Filter assignments for current month
|
||
pub.currentMonthAssignments = pub.assignments?.filter(assignment => {
|
||
return assignment.shift.startTime >= currentMonthStart && assignment.shift.startTime <= currentMonthEnd;
|
||
}).length;
|
||
|
||
// Filter assignments for previous month
|
||
pub.previousMonthAssignments = pub.assignments?.filter(assignment => {
|
||
return assignment.shift.startTime >= previousMonthStart && assignment.shift.startTime <= previousMonthEnd;
|
||
}).length;
|
||
});
|
||
}
|
||
}
|
||
|
||
if (fetchAvailabilities) {
|
||
//get the availabilities for the day. Calcullate:
|
||
//1. how many days the publisher is available for the current month - only with dayOfMonth
|
||
//2. how many days the publisher is available without dayOfMonth (previous months count)
|
||
//3. how many hours in total the publisher is available for the current month
|
||
publishers.forEach(pub => {
|
||
pub.currentMonthAvailability = pub.availabilities?.filter(avail => {
|
||
// return avail.dayOfMonth != null && avail.startTime >= currentMonthStart && avail.startTime <= currentMonthEnd;
|
||
return avail.startTime >= currentMonthStart && avail.startTime <= currentMonthEnd;
|
||
})
|
||
pub.currentMonthAvailabilityDaysCount = pub.currentMonthAvailability.length || 0;
|
||
// pub.currentMonthAvailabilityDaysCount += pub.availabilities.filter(avail => {
|
||
// return avail.dayOfMonth == null;
|
||
// }).length;
|
||
pub.currentMonthAvailabilityHoursCount = pub.currentMonthAvailability.reduce((acc, curr) => {
|
||
return acc + (curr.endTime.getTime() - curr.startTime.getTime()) / (1000 * 60 * 60);
|
||
}, 0);
|
||
|
||
//if pub has ever filled the form - if has availabilities which are not from previous assignments
|
||
pub.hasEverFilledForm = pub.availabilities?.some(avail => {
|
||
return avail.isFromPreviousAssignments == false;
|
||
});
|
||
|
||
//if pub has up-to-date availabilities (with dayOfMonth) for the current month
|
||
pub.hasUpToDateAvailabilities = pub.availabilities?.some(avail => {
|
||
return avail.dayOfMonth != null && avail.startTime >= currentMonthStart; // && avail.startTime <= currentMonthEnd;
|
||
});
|
||
|
||
//if pub has availabilities for the current day
|
||
pub.hasAvailabilityForCurrentDay = pub.availabilities?.some(avail => {
|
||
return avail.startTime >= filterDate && avail.startTime <= filterDateEnd;
|
||
});
|
||
|
||
});
|
||
|
||
if (filterDate && useDateFilter) {
|
||
// Post filter for time if dayOfMonth is null
|
||
// Modify the availabilities array of the filtered publishers
|
||
publishers.forEach(pub => {
|
||
pub.availabilities = pub.availabilities?.filter(avail => matchesAvailability(avail, filterDate));
|
||
});
|
||
}
|
||
|
||
}
|
||
|
||
|
||
return publishers;
|
||
}
|
||
|
||
function matchesAvailability(avail, filterDate) {
|
||
// Setting the start and end time of the filterDate
|
||
filterDate.setHours(0, 0, 0, 0);
|
||
const filterDateEnd = new Date(filterDate);
|
||
filterDateEnd.setHours(23, 59, 59, 999);
|
||
|
||
// Return true if avail.startTime is between filterDate and filterDateEnd
|
||
return avail.startTime >= filterDate && avail.startTime <= filterDateEnd;
|
||
}
|
||
|
||
|
||
|
||
async function replaceInAssignment(oldPublisherId, newPublisherId, shiftId) {
|
||
const prisma = common.getPrismaClient();
|
||
const result = await prisma.assignment.updateMany({
|
||
where: {
|
||
publisherId: oldPublisherId,
|
||
shiftId: shiftId
|
||
},
|
||
data: {
|
||
publisherId: newPublisherId,
|
||
originalPublisherId: oldPublisherId,
|
||
isConfirmed: false,
|
||
isBySystem: true,
|
||
isMailSent: false
|
||
}
|
||
});
|
||
|
||
// log the event
|
||
let shift = await prisma.shift.findUnique({
|
||
where: {
|
||
id: shiftId
|
||
},
|
||
|
||
include: {
|
||
cartEvent: {
|
||
select: {
|
||
location: {
|
||
select: {
|
||
name: true
|
||
}
|
||
}
|
||
}
|
||
},
|
||
assignments: {
|
||
include: {
|
||
publisher: {
|
||
select: {
|
||
firstName: true,
|
||
lastName: true,
|
||
email: true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
let publishers = await prisma.publisher.findMany({
|
||
where: {
|
||
id: { in: [oldPublisherId, newPublisherId] }
|
||
},
|
||
select: {
|
||
id: true,
|
||
firstName: true,
|
||
lastName: true,
|
||
email: true
|
||
}
|
||
});
|
||
let originalPublisher = publishers.find(p => p.id == oldPublisherId);
|
||
let newPublisher = publishers.find(p => p.id == newPublisherId);
|
||
let eventLog = await prisma.eventLog.create({
|
||
data: {
|
||
date: new Date(),
|
||
publisher: { connect: { id: oldPublisherId } },
|
||
shift: { connect: { id: shiftId } },
|
||
type: EventLogType.AssignmentReplacementManual,
|
||
content: "Заместване въведено от " + originalPublisher.firstName + " " + originalPublisher.lastName + ". Ще го замества " + newPublisher.firstName + " " + newPublisher.lastName + "."
|
||
|
||
}
|
||
});
|
||
|
||
logger.info("User: " + originalPublisher.email + " replaced his assignment for " + shift.cartEvent.location.name + " " + shift.startTime.toISOString() + " with " + newPublisher.firstName + " " + newPublisher.lastName + "<" + newPublisher.email + ">. EventLogId: " + eventLog.id + "");
|
||
|
||
|
||
return result;
|
||
} |