first commit

This commit is contained in:
2026-07-16 20:48:43 -03:00
commit 5696cee264
1111 changed files with 322270 additions and 0 deletions
+529
View File
@@ -0,0 +1,529 @@
import { EmployeesAdapterMongoose } from "./Employees.Adapter.Mongoose";
import {
CreateEmployeeParams,
IEmployee,
IEmployeesManager,
EmployeeRoles,
ValidateEmployeeParams,
PaginateEmployeesParams,
PaginateEmployeesResults,
CompanyEmployeesView,
FindEmployeesParams,
FindEmployeesByIdParams,
UpdateEmployeeRolesParams,
UpdateEmployeeParams,
DeleteEmployeesByCompany,
DeleteEmployeeParams,
} from "./Employees.Interface";
import UsersManager from "../Users/Users";
import { ICompanyDocument } from "../Companies/Companies.Adapter.Mongoose";
import CompaniesManager from "../Companies/Companies";
import { NoPermissionMessage } from "../../helpers/NoPermissionMessage";
import { joinStrings } from "../../helpers/String";
import { isNull } from "../../helpers/IsNull";
import getAvatar from "../../helpers/getAvatar";
import { TextObjectFilterResult } from "../../Models/TextObjectFilter.model";
import MetricsList from "../../Models/Metrics/Metrics";
import { NotificationsManager } from "../Notifications/Notifications";
import EmployeesServicesList from "../EmployeesServices/EmployeesServices";
import SchedulesList from "../Schedules/Schedules";
import { IncompleteCollaboratorView } from "./Employees.Interface";
import { NotificationType } from "../../Models/SystemNotifications/SystemNotification.Interface";
class EmployeeManager implements IEmployeesManager {
employees: EmployeesAdapterMongoose;
constructor() {
this.employees = new EmployeesAdapterMongoose();
}
public async deleteEmployee(data: DeleteEmployeeParams): Promise<void> {
await this.employees.employeeList.deleteOne({ _id: data.employeeId });
}
public async deleteEmployeesByCompany(data: DeleteEmployeesByCompany): Promise<void> {
await this.employees.employeeList.deleteMany({ companyId: data.companyId });
}
public async checkRoleById(
companyId: string,
employeeId: string,
role: EmployeeRoles
): Promise<boolean> {
//Check if role exist in roles array.
const check = await this.employees.findOne({ companyId: companyId, userId: employeeId });
if (!check) {
return false;
}
if (check && check.roles) {
return check.roles.includes(role);
}
return false;
}
public async checkPermission(
company: ICompanyDocument,
employeeId: string,
role: EmployeeRoles
): Promise<boolean> {
//Si el data.sessionUser es el owner de la empresa tiene permiso.
if (String(company.ownerId) == String(employeeId)) {
return true;
}
//Si el data.sessionUser tiene el rol admin de la empresa tiene permiso.
return await this.checkRoleById(String(company._id), employeeId, role);
}
public async createEmployee(data: CreateEmployeeParams): Promise<IEmployee> {
//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 this.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) {
throw new Error(NoPermissionMessage());
}
const userCheck = await UsersManager.users.findOne({ _id: data.userId });
if (!userCheck) {
throw new Error("El usuario no existe");
}
const employeeCheck = await this.employees.findOne({
companyId: data.companyId,
userId: data.userId,
});
if (employeeCheck) {
throw new Error("El usuario ya pertenece a la compañia");
}
const canAdd = await MetricsList.canAddEmployee(companyCheck.ownerId);
if (!canAdd) {
throw new Error(
"Ha alcanzado el limite de colaboradores permitidos de acuerdo a su plan."
);
}
const newEmplyee = await this.employees.create(data);
await NotificationsManager.sendSystemNotification({
userId: String(data.userId),
subject: "Su usuario ha sido vinculado a una organización",
message: `La compañía ${companyCheck.name} lo ha vinculado como colaborador. Es necesario que acepte la invitación para poder formar parte de esta organización.`,
type: NotificationType.EMPLOYEE,
code: String((newEmplyee as any)._id)
});
await MetricsList.addEmployee({
userId: companyCheck.ownerId,
quantity: 1,
});
return newEmplyee;
}
public async updateEmployeeRoles(data: UpdateEmployeeRolesParams): 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 companyCheck = await CompaniesManager.companies.findOne({ _id: data.companyId });
if (!companyCheck) {
throw new Error("La compañia no existe");
}
const checkPermission = await this.checkPermission(
companyCheck,
data.sessionUser,
EmployeeRoles.ADMIN
);
if (!checkPermission) {
throw new Error(NoPermissionMessage());
}
const employee = await this.employees.findOne({
_id: data.employeeId,
});
if (!employee) {
throw new Error("Ha ocurrido un error al validar el empleado.");
}
if (String(employee.userId) == data.sessionUser) {
if (String(companyCheck.ownerId) != data.sessionUser) {
throw new Error(NoPermissionMessage());
}
}
employee.roles = data.roles;
await employee.save();
}
//No exponer este metodo.
public async validateFull(data: ValidateEmployeeParams): Promise<void> {
const employee = await this.employees.findOne({
_id: data.employeeId,
});
if (!employee) {
throw new Error("Ha ocurrido un error al validar el empleado.");
}
employee.hostOk = true;
employee.guestOk = true;
await employee.save();
}
public async validateGuest(data: ValidateEmployeeParams): 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 employee = await this.employees.findOne({
_id: data.employeeId,
});
if (!employee) {
throw new Error("Ha ocurrido un error al validar el empleado.");
}
if (String(sessionUser.id) != employee.userId) {
throw new Error("Solo el destinatario puede aceptar la vinculación.");
}
employee.guestOk = true;
const checkCompany = await CompaniesManager.companies.findOne({
_id: employee.companyId,
});
if (!checkCompany) {
throw new Error("La organización no existe");
}
await employee.save();
await NotificationsManager.sendSystemNotification({
userId: String(employee.userId),
subject: "Has aceptado la invitación!",
message: `Ahora que haz aceptado la invitación ya estas habilitado para trabajar con ${checkCompany.name}.`,
type: NotificationType.EMPLOYEE,
code: String(employee._id)
});
}
public async rejectGuest(data: ValidateEmployeeParams): 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 employee = await this.employees.findOne({
_id: data.employeeId,
});
if (!employee) {
throw new Error("Ha ocurrido un error al validar el empleado.");
}
if (String(sessionUser.id) != employee.userId) {
throw new Error("Solo el destinatario puede rechazar la vinculación.");
}
const checkCompany = await CompaniesManager.companies.findOne({
_id: employee.companyId,
});
if (!checkCompany) {
throw new Error("La organización no existe");
}
await this.employees.employeeList.deleteOne({ _id: data.employeeId });
await NotificationsManager.sendSystemNotification({
userId: String(checkCompany.ownerId),
subject: "Invitación rechazada",
message: `El usuario ${UsersManager.getUserFullName(sessionUser)} ha rechazado la invitación para unirse a ${checkCompany.name}.`,
type: NotificationType.EMPLOYEE,
code: String(employee._id)
});
}
public async validateHost(data: ValidateEmployeeParams): 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 employee = await this.employees.findOne({
_id: data.employeeId,
});
if (!employee) {
throw new Error("Ha ocurrido un error al validar el empleado.");
}
//Si el data.sessionUser tiene el rol admin de la empresa tiene permiso.
const checkPermission = await this.checkRoleById(
employee.companyId,
data.sessionUser,
EmployeeRoles.ADMIN
);
if (!checkPermission) {
throw new Error(NoPermissionMessage());
}
const employUserCheck = await UsersManager.users.findOne({
_id: employee.userId,
});
if (!employUserCheck) {
throw new Error("El usuario no existe");
}
const companyCheck = await CompaniesManager.companies.findOne({
_id: employee.companyId,
});
if (!companyCheck) {
throw new Error("La organización no existe");
}
employee.hostOk = true;
await employee.save();
await NotificationsManager.sendSystemNotification({
userId: String(data.sessionUser),
subject: "Has habilitado a un colaborador!",
message: `Se ha enviado una notificación a ${UsersManager.getUserFullName(
employUserCheck
)} para formar parte de ${companyCheck.name}.`,
type: NotificationType.EMPLOYEE,
code: String(employee._id)
});
}
public async paginate(filters: PaginateEmployeesParams): Promise<PaginateEmployeesResults> {
return await this.employees.paginate(filters);
}
public async findByCompanyId(data: FindEmployeesParams): Promise<CompanyEmployeesView[]> {
const employees = await this.employees.find(data);
const view = await Promise.all(
employees.map(async (employee) => {
const userData = await UsersManager.users.findOne({ _id: employee.userId });
const strFirstName = userData ? isNull<string>(userData.firstName, "") : "";
const strLastName = userData ? isNull<string>(userData.lastName, "") : "";
const strFullName = joinStrings([strFirstName, strLastName], " ");
const strAvatar = userData
? getAvatar(userData.id, userData.avatar, strFullName)
: strFullName;
const boolGuestOk = employee.guestOk || false;
const boolHostOk = employee.hostOk || false;
const boolFullOk = boolGuestOk && boolHostOk;
return {
id: isNull<string>(employee.id, ""),
companyId: employee.companyId,
userId: employee.userId,
firstName: strFirstName,
lastName: strLastName,
fullName: strFullName,
avatar: strAvatar,
email: userData ? userData.email : "",
roles: employee.roles ? employee.roles : [],
guestOk: boolGuestOk,
hostOk: boolHostOk,
fullOk: boolFullOk,
calendarColor: isNull<string>(employee.calendarColor, "#ff9900"),
};
})
);
return view;
}
public async findById(data: FindEmployeesByIdParams): Promise<CompanyEmployeesView> {
const employee = await this.employees.findOne({ _id: data.id });
if (!employee) {
throw new Error("El colaborador no existe");
}
const userData = await UsersManager.users.findOne({ _id: employee.userId });
const strFirstName = userData ? isNull<string>(userData.firstName, "") : "";
const strLastName = userData ? isNull<string>(userData.lastName, "") : "";
const strFullName = joinStrings([strFirstName, strLastName], " ");
const strAvatar = userData
? getAvatar(userData.id, userData.avatar, strFullName)
: strFullName;
const boolGuestOk = employee.guestOk || false;
const boolHostOk = employee.hostOk || false;
const boolFullOk = boolGuestOk && boolHostOk;
return {
id: isNull<string>(employee.id, ""),
companyId: employee.companyId,
userId: employee.userId,
firstName: strFirstName,
lastName: strLastName,
fullName: strFullName,
avatar: strAvatar,
email: userData ? userData.email : "",
roles: employee.roles ? employee.roles : [],
guestOk: boolGuestOk,
hostOk: boolHostOk,
fullOk: boolFullOk,
calendarColor: isNull<string>(employee.calendarColor, "#ff9900"),
};
}
public async textObjectFilter(data: FindEmployeesParams): Promise<TextObjectFilterResult[]> {
const employees = await this.employees.find(data);
const formattedResult: TextObjectFilterResult[] = [];
for (const colaborador of employees) {
const userInfo = await UsersManager.users.findOne({ _id: colaborador.userId });
if (!userInfo) {
continue;
}
const userFullName = joinStrings([userInfo.firstName, userInfo.lastName], " ");
const strAvatar = userInfo
? getAvatar(userInfo.id, userInfo.avatar, userFullName)
: userFullName;
formattedResult.push({
id: isNull<string>(colaborador.id, ""),
name: userFullName,
image: strAvatar,
});
}
return formattedResult;
}
public async update(data: UpdateEmployeeParams): 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 employee = await this.employees.findOne({
_id: data.id,
});
if (!employee) {
throw new Error("Ha ocurrido un error al validar el empleado.");
}
const companyCheck = await CompaniesManager.companies.findOne({ _id: employee.companyId });
if (!companyCheck) {
throw new Error("La organización no existe");
}
//Si el data.sessionUser tiene el rol admin de la empresa o es owner tiene permiso.
const checkPermission = await this.checkPermission(
companyCheck,
data.sessionUser,
EmployeeRoles.ADMIN
);
if (!checkPermission) {
throw new Error(NoPermissionMessage());
}
if (data.calendarColor) {
employee.calendarColor = data.calendarColor;
}
if (data.limit) {
employee.limit = data.limit;
}
await employee.save();
}
public async getIncompleteSetup(sessionUser: string): Promise<IncompleteCollaboratorView[]> {
const incompleteCollaborators: IncompleteCollaboratorView[] = [];
const userCompanies = await CompaniesManager.getByUserId({ sessionUser });
for (const company of userCompanies) {
if (company.isAdmin || company.isOwner) {
const employees = await this.findByCompanyId({ companyId: company.id } as any);
for (const employee of employees) {
if (employee.hostOk && employee.guestOk) {
const services = await EmployeesServicesList.find({ employeeId: employee.id } as any);
const schedules = await SchedulesList.find({ employeeId: employee.id, companyId: company.id } as any);
const missingServices = services.length === 0;
const missingSchedules = schedules.length === 0;
if (missingServices || missingSchedules) {
incompleteCollaborators.push({
employeeId: employee.id,
companyId: company.id,
companyName: company.name,
name: employee.fullName,
missingServices,
missingSchedules
});
}
}
}
}
}
return incompleteCollaborators;
}
}
const EmployeesList = new EmployeeManager();
export default EmployeesList;
@@ -0,0 +1,65 @@
import {
FindEmployeesParams,
IEmployeesAdapter,
IEmployee,
CreateEmployeeParams,
PaginateEmployeesParams,
PaginateEmployeesResults,
} from "./Employees.Interface";
import { Document, Model, Schema, model } from "mongoose";
export interface IEmployeeDocument extends Omit<IEmployee, "id">, Document {}
export class EmployeesAdapterMongoose implements IEmployeesAdapter {
schema: Schema;
employeeList: Model<IEmployeeDocument>;
constructor() {
this.schema = new Schema({
userId: { type: Schema.Types.ObjectId, required: true, ref: "User" },
companyId: { type: Schema.Types.ObjectId, required: true, ref: "Companie" },
roles: { type: Array, required: true },
calendarColor: { type: String, required: false },
limit: { type: Number, required: true, default: -1 },
hostOk: { type: Boolean, required: true, default: false },
guestOk: { type: Boolean, required: true, default: false },
creationDate: { type: Date, required: true, default: Date.now },
});
this.employeeList = model<IEmployeeDocument>("Employee", this.schema);
}
public async create(data: CreateEmployeeParams): Promise<IEmployee> {
return await this.employeeList.create(data);
}
public async delete(id: string): Promise<void> {
console.log("delete employee", id);
}
public async find(filters: FindEmployeesParams): Promise<IEmployee[]> {
return this.employeeList.find(filters).exec();
}
public async findOne(filters: FindEmployeesParams): Promise<IEmployeeDocument | null> {
return this.employeeList.findOne(filters).exec();
}
public async paginate(filters: PaginateEmployeesParams): Promise<PaginateEmployeesResults> {
const { page, limit, ...findFilters } = filters; //Extract page and limit from filters
const count = await this.employeeList.countDocuments(findFilters).exec();
const skip = (page - 1) * limit;
const results = await this.employeeList
.find(findFilters)
.skip(skip)
.limit(filters.limit)
.exec();
return {
data: results,
page: filters.page,
pages: Math.ceil(count / filters.limit),
};
}
}
@@ -0,0 +1,128 @@
import { TextObjectFilterResult } from "../../Models/TextObjectFilter.model";
import { IEmployeeDocument } from "./Employees.Adapter.Mongoose";
export type FindEmployeesParams = {
_id?: string;
name?: string;
companyId?: string;
userId?: string;
};
export type PaginateEmployeesParams = FindEmployeesParams & {
page: number;
limit: number;
};
export type PaginateEmployeesResults = {
data: IEmployee[];
page: number;
pages: number;
};
export enum EmployeeRoles {
ADMIN = "admin",
OWNER = "owner",
EMPLOYEE = "employee",
}
export type CreateEmployeeParams = {
companyId: string;
userId: string;
roles?: EmployeeRoles[];
calendarColor?: string;
limit?: number;
hostOk?: boolean;
sessionUser: string;
};
export type UpdateEmployeeParams = {
id: string;
calendarColor?: string;
limit?: number;
sessionUser: string;
};
export type UpdateEmployeeRolesParams = {
employeeId: string;
companyId: string;
roles: EmployeeRoles[];
calendarColor?: string;
limit?: number;
sessionUser: string;
};
export type FindEmployeesByIdParams = {
id?: string;
};
export type ValidateEmployeeParams = {
employeeId: string;
sessionUser: string;
};
export type DeleteEmployeesByCompany = {
companyId: string;
};
export type DeleteEmployeeParams = {
employeeId: string;
};
export interface IEmployee {
id?: string;
companyId: string;
userId: string;
roles?: EmployeeRoles[];
calendarColor?: string;
limit?: number;
guestOk?: boolean;
hostOk?: boolean;
}
export interface CompanyEmployeesView {
id: string;
companyId: string;
userId: string;
firstName: string;
lastName: string;
fullName: string;
avatar: string;
email: string;
roles: EmployeeRoles[];
guestOk: boolean;
hostOk: boolean;
fullOk: boolean;
calendarColor: string;
}
export interface IncompleteCollaboratorView {
employeeId: string;
companyId: string;
companyName: string;
name: string;
missingServices: boolean;
missingSchedules: boolean;
}
export interface IEmployeesAdapter {
create(data: CreateEmployeeParams): Promise<IEmployee>;
delete(id: string): Promise<void>;
find(filters: FindEmployeesParams): Promise<IEmployee[]>;
findOne(filters: FindEmployeesParams): Promise<IEmployeeDocument | null>;
paginate(filters: PaginateEmployeesParams): Promise<PaginateEmployeesResults>;
}
export interface IEmployeesManager {
employees: IEmployeesAdapter;
checkRoleById(companyId: string, employeeId: string, role: EmployeeRoles): Promise<boolean>;
createEmployee(data: CreateEmployeeParams): Promise<IEmployee>;
updateEmployeeRoles(data: UpdateEmployeeRolesParams): Promise<void>;
update(data: UpdateEmployeeParams): Promise<void>;
findByCompanyId(data: FindEmployeesParams): Promise<CompanyEmployeesView[]>;
findById(data: FindEmployeesByIdParams): Promise<CompanyEmployeesView>;
textObjectFilter(data: FindEmployeesParams): Promise<TextObjectFilterResult[]>;
deleteEmployeesByCompany(data: DeleteEmployeesByCompany): Promise<void>;
deleteEmployee(data: DeleteEmployeeParams): Promise<void>;
rejectGuest(data: ValidateEmployeeParams): Promise<void>;
getIncompleteSetup(sessionUser: string): Promise<IncompleteCollaboratorView[]>;
}