first commit
This commit is contained in:
@@ -0,0 +1,584 @@
|
||||
import UsersManager from "../Users/Users";
|
||||
import CompaniesManager from "../Companies/Companies";
|
||||
import { NoPermissionMessage } from "../../helpers/NoPermissionMessage";
|
||||
import { NotificationsManager } from "../Notifications/Notifications";
|
||||
import { RepeatsAdapterMongoose } from "./Repeats.Adapter.Mongoose";
|
||||
import {
|
||||
AddRepeatDisabledDateParams,
|
||||
CountRepeatsParams,
|
||||
CreateRepeatsParams,
|
||||
DeleteRepeatsByClientParams,
|
||||
DeleteRepeatsByCompanyParams,
|
||||
DeleteRepeatsByUserParams,
|
||||
DeleteRepeatsParams,
|
||||
FindRepeatsParams,
|
||||
IRepeat,
|
||||
IRepeatsManager,
|
||||
RepeatView,
|
||||
GetPendingRepeatsParams,
|
||||
PendingRepeatView,
|
||||
} from "./Repeats.Interface";
|
||||
import { EmployeeRoles } from "../Employees/Employees.Interface";
|
||||
import ServiceList from "../Services/Service";
|
||||
import ClientsList from "../Clients/Clients";
|
||||
import EmployeesList from "../Employees/Employee";
|
||||
import UserList from "../Users/Users";
|
||||
import dayjs from "dayjs";
|
||||
import localeData from "dayjs/plugin/localeData";
|
||||
import "dayjs/locale/es";
|
||||
import weekday from "dayjs/plugin/weekday";
|
||||
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
|
||||
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
|
||||
import { isNull } from "../../helpers/IsNull";
|
||||
import getAvatar from "../../helpers/getAvatar";
|
||||
import MetricsList from "../Metrics/Metrics";
|
||||
import AppointmentList from "../Appointments/Appointments";
|
||||
import Discounts from "../Discounts/Discounts";
|
||||
import { NotificationType } from "../../Models/SystemNotifications/SystemNotification.Interface";
|
||||
|
||||
dayjs.locale("es");
|
||||
dayjs.extend(localeData);
|
||||
dayjs.extend(weekday);
|
||||
dayjs.extend(isSameOrAfter);
|
||||
dayjs.extend(isSameOrBefore);
|
||||
|
||||
class RepeatsManager implements IRepeatsManager {
|
||||
repeats: RepeatsAdapterMongoose;
|
||||
|
||||
constructor() {
|
||||
this.repeats = new RepeatsAdapterMongoose();
|
||||
}
|
||||
|
||||
private numberToTime(decimal: number): string {
|
||||
const hours = Math.floor(decimal);
|
||||
const minutes = Math.round((decimal - hours) * 60);
|
||||
const paddedMinutes = minutes.toString().padStart(2, "0");
|
||||
return `${hours}:${paddedMinutes}hs`;
|
||||
}
|
||||
|
||||
public async createRepeat(data: CreateRepeatsParams): Promise<IRepeat> {
|
||||
//Primero tengo que verificar que el usuario que intenta registrar un empleado
|
||||
//tenga permisos para hacerlo y que sea un usuario valido. Este dato viene de
|
||||
//el campo data.sessionUser.
|
||||
|
||||
const sessionUser = await UsersManager.users.findOne({ _id: data.sessionUser });
|
||||
|
||||
if (!sessionUser) {
|
||||
throw new Error(
|
||||
"No se ha encontrado el usuario o no tiene permisos para realizar esta acción"
|
||||
);
|
||||
}
|
||||
|
||||
const companyCheck = await CompaniesManager.companies.findOne({ _id: data.companyId });
|
||||
|
||||
if (!companyCheck) {
|
||||
throw new Error("La compañia no existe");
|
||||
}
|
||||
|
||||
if (
|
||||
!(await EmployeesList.checkPermission(
|
||||
companyCheck,
|
||||
data.sessionUser,
|
||||
EmployeeRoles.ADMIN
|
||||
))
|
||||
) {
|
||||
throw new Error(NoPermissionMessage());
|
||||
}
|
||||
|
||||
const canAdd = await MetricsList.canAddRepeat(companyCheck.ownerId);
|
||||
|
||||
if (!canAdd) {
|
||||
throw new Error(
|
||||
"Ha alcanzado el limite de repeticiones permitidas de acuerdo a su plan."
|
||||
);
|
||||
}
|
||||
|
||||
const checkEmployee = await EmployeesList.employees.findOne({
|
||||
_id: data.employeeId,
|
||||
});
|
||||
|
||||
if (!checkEmployee) {
|
||||
throw new Error("El empleado no existe");
|
||||
}
|
||||
|
||||
const employeeUser = await UsersManager.users.findOne({
|
||||
_id: checkEmployee.userId,
|
||||
});
|
||||
|
||||
if (!employeeUser) {
|
||||
throw new Error("El empleado no tiene un usuario asociado");
|
||||
}
|
||||
|
||||
const checkService = await ServiceList.services.findOne({
|
||||
_id: data.serviceId,
|
||||
});
|
||||
|
||||
if (!checkService) {
|
||||
throw new Error("El servicio no existe");
|
||||
}
|
||||
|
||||
const checkClient = await ClientsList.clients.findOne({
|
||||
_id: data.clientId,
|
||||
});
|
||||
|
||||
if (!checkClient) {
|
||||
throw new Error("El cliente no existe");
|
||||
}
|
||||
|
||||
const checkRepeat = await this.repeats.findOne({
|
||||
companyId: data.companyId,
|
||||
clientId: data.clientId,
|
||||
weekDay: data.weekDay,
|
||||
startHour: data.startHour,
|
||||
});
|
||||
|
||||
if (checkRepeat) {
|
||||
throw new Error("Ya existe una reserva previa.");
|
||||
}
|
||||
|
||||
const newRepeat = await this.repeats.create({
|
||||
...data,
|
||||
...{ length: checkService.length },
|
||||
});
|
||||
|
||||
await MetricsList.addRepeat({
|
||||
userId: companyCheck.ownerId,
|
||||
quantity: 1,
|
||||
});
|
||||
|
||||
const humanDay = dayjs().localeData().weekdays()[data.weekDay];
|
||||
const humanHour = this.numberToTime(data.startHour);
|
||||
|
||||
if (checkClient.userId) {
|
||||
await NotificationsManager.sendSystemNotification({
|
||||
userId: String(checkClient.userId),
|
||||
subject: "Se ha creado una nueva reserva con renovación automática",
|
||||
message: `La organización ${
|
||||
companyCheck.name
|
||||
} ha asignado una reserva con renovación automática todos los ${humanDay} a las ${humanHour} para ${
|
||||
checkService.name
|
||||
} con ${UserList.getUserFullName(employeeUser)}.`,
|
||||
type: NotificationType.SYSTEM
|
||||
});
|
||||
}
|
||||
|
||||
await NotificationsManager.sendSystemNotification({
|
||||
userId: String(checkEmployee.userId),
|
||||
subject: "Se ha creado una nueva reserva con renovación automática",
|
||||
message: `${ClientsList.getClientFullName(
|
||||
checkClient
|
||||
)} tiene una reservación con renovación automática todos los ${humanDay} a las ${humanHour} para ${
|
||||
checkService.name
|
||||
} con vos.`,
|
||||
type: NotificationType.SYSTEM
|
||||
});
|
||||
|
||||
return newRepeat;
|
||||
}
|
||||
|
||||
public async deleteRepeat(data: DeleteRepeatsParams): Promise<void> {
|
||||
const sessionUser = await UsersManager.users.findOne({ _id: data.sessionUser });
|
||||
|
||||
if (!sessionUser) {
|
||||
throw new Error(
|
||||
"No se ha encontrado el usuario o no tiene permisos para realizar esta acción"
|
||||
);
|
||||
}
|
||||
|
||||
const checkRepeat = await this.repeats.findOne({
|
||||
_id: data.repeatId,
|
||||
});
|
||||
|
||||
if (!checkRepeat) {
|
||||
throw new Error("La repetición no existe");
|
||||
}
|
||||
|
||||
const companyCheck = await CompaniesManager.companies.findOne({
|
||||
_id: String(checkRepeat.companyId),
|
||||
});
|
||||
|
||||
if (!companyCheck) {
|
||||
throw new Error("La compañia no existe");
|
||||
}
|
||||
|
||||
if (
|
||||
!(await EmployeesList.checkPermission(
|
||||
companyCheck,
|
||||
data.sessionUser,
|
||||
EmployeeRoles.ADMIN
|
||||
))
|
||||
) {
|
||||
throw new Error(NoPermissionMessage());
|
||||
}
|
||||
|
||||
await this.repeats.delete(data.repeatId);
|
||||
}
|
||||
|
||||
public async deleteRepeatsByCompany(data: DeleteRepeatsByCompanyParams): Promise<void> {
|
||||
await this.repeats.repeatsList.deleteMany({ companyId: data.companyId });
|
||||
}
|
||||
|
||||
public async deleteRepeatsByClient(data: DeleteRepeatsByClientParams): Promise<void> {
|
||||
await this.repeats.repeatsList.deleteMany({ clientId: data.clientId });
|
||||
}
|
||||
|
||||
public async deleteRepeatsByUser(data: DeleteRepeatsByUserParams): Promise<void> {
|
||||
//obtengo los clientes asociados al usuario.
|
||||
const clients = await ClientsList.clients.find({
|
||||
userId: data.userId,
|
||||
});
|
||||
|
||||
for (const client of clients) {
|
||||
await this.deleteRepeatsByClient({
|
||||
clientId: String(client.id),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async getRepeatsByClient(data: FindRepeatsParams): Promise<RepeatView[]> {
|
||||
const returnValue: RepeatView[] = [];
|
||||
|
||||
const repeats = await this.repeats.find(data);
|
||||
|
||||
for (const repeat of repeats) {
|
||||
if (!repeat.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const companyCheck = await CompaniesManager.companies.findOne({
|
||||
_id: repeat.companyId,
|
||||
});
|
||||
|
||||
if (!companyCheck) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const employeeCheck = await EmployeesList.employees.findOne({
|
||||
_id: repeat.employeeId,
|
||||
});
|
||||
|
||||
if (!employeeCheck) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const employeeUser = await UsersManager.users.findOne({
|
||||
_id: employeeCheck.userId,
|
||||
});
|
||||
|
||||
if (!employeeUser) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const serviceCheck = await ServiceList.services.findOne({
|
||||
_id: repeat.serviceId,
|
||||
});
|
||||
|
||||
if (!serviceCheck) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const clientCheck = await ClientsList.clients.findOne({
|
||||
_id: repeat.clientId,
|
||||
});
|
||||
|
||||
if (!clientCheck) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const discountData = await Discounts.getDiscountData(
|
||||
repeat.discountId,
|
||||
repeat.companyId
|
||||
);
|
||||
|
||||
returnValue.push({
|
||||
id: repeat.id,
|
||||
weekDay: repeat.weekDay,
|
||||
weekDayName: dayjs().localeData().weekdays()[repeat.weekDay],
|
||||
startHour: repeat.startHour,
|
||||
startHourFormatted: this.numberToTime(repeat.startHour),
|
||||
length: repeat.length,
|
||||
companyId: repeat.companyId,
|
||||
companyName: companyCheck.name,
|
||||
employeeId: repeat.employeeId,
|
||||
employeeName: UserList.getUserFullName(employeeUser),
|
||||
employeeCalendarColor: isNull<string>(employeeCheck.calendarColor, "#000000"),
|
||||
employeeAvatar: getAvatar(
|
||||
employeeCheck.userId,
|
||||
employeeUser.avatar,
|
||||
UserList.getUserFullName(employeeUser)
|
||||
),
|
||||
serviceId: repeat.serviceId,
|
||||
serviceName: serviceCheck.name,
|
||||
discountId: isNull<string>(repeat.discountId, ""),
|
||||
discountName: discountData.name,
|
||||
discountType: discountData.type,
|
||||
discountValue: discountData.value,
|
||||
discountCode: discountData.code,
|
||||
clientId: repeat.clientId,
|
||||
clientName: ClientsList.getClientFullName(clientCheck),
|
||||
});
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
public async countRepeats(data: CountRepeatsParams): Promise<number> {
|
||||
let count = 0;
|
||||
const countDateStart = dayjs(data.countDate);
|
||||
const countDateEnd = countDateStart.clone().add(data.heatMapFraction, "minutes");
|
||||
const weekDay = dayjs(data.countDate).weekday() + 1;
|
||||
const weekStart = dayjs(data.countDate).startOf("week");
|
||||
|
||||
const repeats = await this.repeats.find({
|
||||
companyId: data.companyId,
|
||||
employeeId: data.employeeId,
|
||||
weekDay: weekDay,
|
||||
});
|
||||
|
||||
for (const repeat of repeats) {
|
||||
if (!repeat.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const repeatHour = Math.floor(repeat.startHour);
|
||||
const repeatMinutes = (repeat.startHour - repeatHour) * 60;
|
||||
const repeatWeekDay = weekDay - 1;
|
||||
|
||||
if (repeatWeekDay < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const repeatDateStart = weekStart
|
||||
.add(repeatWeekDay, "d")
|
||||
.startOf("day")
|
||||
.hour(repeatHour)
|
||||
.minute(repeatMinutes)
|
||||
.second(0)
|
||||
.millisecond(0);
|
||||
|
||||
//Si la fecha esta deshabilitada no tengo que contarla.
|
||||
if (repeat.disabledDates) {
|
||||
if (this.existDisabledDate(repeatDateStart.toDate(), repeat.disabledDates)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Si el turno ya esta creado no tengo que contarlo...
|
||||
const searchDate = repeatDateStart.clone().toDate();
|
||||
|
||||
//Solo hago push si la repeticion no tiene el turno creado.
|
||||
const appointmentCreated = await AppointmentList.Appointments.findOne({
|
||||
companyId: data.companyId,
|
||||
clientId: String(repeat.clientId),
|
||||
serviceId: String(repeat.serviceId),
|
||||
employeeId: String(repeat.employeeId),
|
||||
start: searchDate,
|
||||
});
|
||||
|
||||
if (appointmentCreated) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const repeatDateEnd = repeatDateStart.clone().add(repeat.length, "minutes");
|
||||
|
||||
if (
|
||||
repeatDateStart.isSameOrBefore(countDateStart) &&
|
||||
repeatDateEnd.isSameOrAfter(countDateEnd)
|
||||
) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public async getPendingRepeatsCurrentWeek(data: GetPendingRepeatsParams): Promise<PendingRepeatView[]> {
|
||||
const returnValue: PendingRepeatView[] = [];
|
||||
|
||||
const weekStart = dayjs().startOf("week");
|
||||
const filters: any = {};
|
||||
if (data.companyId) {
|
||||
const companyCheck = await CompaniesManager.companies.findOne({ _id: data.companyId });
|
||||
if (!companyCheck) {
|
||||
throw new Error("La compañia no existe");
|
||||
}
|
||||
filters.companyId = data.companyId;
|
||||
}
|
||||
|
||||
if (data.filterBySessionUser) {
|
||||
const employeeQuery: any = { userId: data.sessionUser };
|
||||
if (data.companyId) {
|
||||
employeeQuery.companyId = data.companyId;
|
||||
}
|
||||
const sessionEmployees = await EmployeesList.employees.find(employeeQuery);
|
||||
if (sessionEmployees && sessionEmployees.length > 0) {
|
||||
filters.employeeId = { $in: sessionEmployees.map(e => e.id) };
|
||||
} else {
|
||||
// If user is not an employee, they can't have personal repeats
|
||||
return [];
|
||||
}
|
||||
} else if (data.employeeId) {
|
||||
filters.employeeId = data.employeeId;
|
||||
}
|
||||
|
||||
const repeats = await this.repeats.find(filters);
|
||||
|
||||
for (const repeat of repeats) {
|
||||
if (!repeat.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const repeatHour = Math.floor(repeat.startHour);
|
||||
const repeatMinutes = Math.round((repeat.startHour - repeatHour) * 60);
|
||||
|
||||
// dayjs() .weekday() logic: 0 to 6
|
||||
// repeat.weekDay is likely 1-7 or 0-6?
|
||||
// In countRepeats: const weekDay = dayjs(data.countDate).weekday() + 1;
|
||||
// and repeat.weekDay is checked against it. So repeat.weekDay corresponds to weekday()+1
|
||||
|
||||
const repeatWeekDay = repeat.weekDay - 1;
|
||||
|
||||
if (repeatWeekDay < 0 || repeatWeekDay > 6) {
|
||||
continue; // Should not happen if data is correct
|
||||
}
|
||||
|
||||
const repeatDateStart = weekStart
|
||||
.clone()
|
||||
.add(repeatWeekDay, "d")
|
||||
.startOf("day")
|
||||
.hour(repeatHour)
|
||||
.minute(repeatMinutes)
|
||||
.second(0)
|
||||
.millisecond(0);
|
||||
|
||||
// Si la fecha es en el pasado, no la mostramos como pendiente para crear
|
||||
if (repeatDateStart.isBefore(dayjs().startOf("day"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const searchDate = repeatDateStart.toDate();
|
||||
|
||||
// Si la fecha esta deshabilitada no tengo que devolverla
|
||||
if (repeat.disabledDates && this.existDisabledDate(searchDate, repeat.disabledDates)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if appointment is already created
|
||||
const appointmentCreated = await AppointmentList.Appointments.findOne({
|
||||
companyId: String(repeat.companyId),
|
||||
clientId: String(repeat.clientId),
|
||||
serviceId: String(repeat.serviceId),
|
||||
employeeId: String(repeat.employeeId),
|
||||
start: searchDate,
|
||||
});
|
||||
|
||||
if (appointmentCreated) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prepare the view
|
||||
const employeeCheck = await EmployeesList.employees.findOne({ _id: repeat.employeeId });
|
||||
if (!employeeCheck) continue;
|
||||
|
||||
const employeeUser = await UsersManager.users.findOne({ _id: employeeCheck.userId });
|
||||
if (!employeeUser) continue;
|
||||
|
||||
const serviceCheck = await ServiceList.services.findOne({ _id: repeat.serviceId });
|
||||
if (!serviceCheck) continue;
|
||||
|
||||
const clientCheck = await ClientsList.clients.findOne({ _id: repeat.clientId });
|
||||
if (!clientCheck) continue;
|
||||
|
||||
const discountData = await Discounts.getDiscountData(repeat.discountId, repeat.companyId);
|
||||
|
||||
const companyCheck = await CompaniesManager.companies.findOne({ _id: repeat.companyId });
|
||||
|
||||
returnValue.push({
|
||||
id: repeat.id,
|
||||
weekDay: repeat.weekDay,
|
||||
weekDayName: dayjs().localeData().weekdays()[repeat.weekDay],
|
||||
startHour: repeat.startHour,
|
||||
startHourFormatted: this.numberToTime(repeat.startHour),
|
||||
length: repeat.length,
|
||||
companyId: repeat.companyId,
|
||||
companyName: companyCheck ? companyCheck.name : "",
|
||||
employeeId: repeat.employeeId,
|
||||
employeeName: UserList.getUserFullName(employeeUser),
|
||||
employeeCalendarColor: isNull<string>(employeeCheck.calendarColor, "#000000"),
|
||||
employeeAvatar: getAvatar(employeeCheck.userId, employeeUser.avatar, UserList.getUserFullName(employeeUser)),
|
||||
serviceId: repeat.serviceId,
|
||||
serviceName: serviceCheck.name,
|
||||
discountId: isNull<string>(repeat.discountId, ""),
|
||||
discountName: discountData.name,
|
||||
discountType: discountData.type,
|
||||
discountValue: discountData.value,
|
||||
discountCode: discountData.code,
|
||||
clientId: repeat.clientId,
|
||||
clientName: ClientsList.getClientFullName(clientCheck),
|
||||
pendingDate: searchDate
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by pendingDate
|
||||
returnValue.sort((a, b) => a.pendingDate.getTime() - b.pendingDate.getTime());
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
public existDisabledDate(checkDate: Date, disabledDates: Date[]): boolean {
|
||||
return disabledDates.some((fecha) => fecha.getTime() === checkDate.getTime());
|
||||
}
|
||||
|
||||
public async addDisabledDate(data: AddRepeatDisabledDateParams): Promise<void> {
|
||||
const repeat = await this.repeats.findOne({
|
||||
_id: data.repeatId,
|
||||
});
|
||||
|
||||
if (!repeat) {
|
||||
throw new Error("La repetición no existe");
|
||||
}
|
||||
|
||||
const sessionUser = await UsersManager.users.findOne({ _id: data.sessionUser });
|
||||
|
||||
if (!sessionUser) {
|
||||
throw new Error(
|
||||
"No se ha encontrado el usuario o no tiene permisos para realizar esta acción"
|
||||
);
|
||||
}
|
||||
|
||||
const companyCheck = await CompaniesManager.companies.findOne({
|
||||
_id: String(repeat.companyId),
|
||||
});
|
||||
|
||||
if (!companyCheck) {
|
||||
throw new Error("La compañia no existe");
|
||||
}
|
||||
|
||||
if (
|
||||
!(await EmployeesList.checkPermission(
|
||||
companyCheck,
|
||||
data.sessionUser,
|
||||
EmployeeRoles.ADMIN
|
||||
))
|
||||
) {
|
||||
throw new Error(NoPermissionMessage());
|
||||
}
|
||||
|
||||
if (!repeat.disabledDates) {
|
||||
repeat.disabledDates = [];
|
||||
}
|
||||
|
||||
if (this.existDisabledDate(data.disableDate, repeat.disabledDates)) {
|
||||
console.log("La fecha ya estaba deshabilitada");
|
||||
return;
|
||||
}
|
||||
|
||||
repeat.disabledDates.push(new Date(data.disableDate));
|
||||
|
||||
await repeat.save();
|
||||
}
|
||||
}
|
||||
|
||||
const RepeatsList = new RepeatsManager();
|
||||
|
||||
export default RepeatsList;
|
||||
Reference in New Issue
Block a user