562 lines
20 KiB
TypeScript
562 lines
20 KiB
TypeScript
import fs from "fs";
|
|
import path from "path";
|
|
import { rm } from "fs/promises";
|
|
import { ServicesAdapterMongoose } from "./Service.Adapter.Mongoose";
|
|
import {
|
|
CompanyServiceView,
|
|
CreateServiceParams,
|
|
UpdateServiceParams,
|
|
FindServicesByCompanyParams,
|
|
FindServicesByIdParams,
|
|
IService,
|
|
IServicesManager,
|
|
PaginateServicesParams,
|
|
PaginateServicesResults,
|
|
SetServiceFileParams,
|
|
PublicServicesResults,
|
|
PublicServiceView,
|
|
DeleteServicesByCompany,
|
|
SetPublishedStatusParams,
|
|
SERVICE_PUBLISHED_STATUS,
|
|
FindServicesParams,
|
|
ApplyDiscountParams,
|
|
DeleteDiscountParams,
|
|
SysAdminPaginateServicesParams,
|
|
SysAdminSetServiceBannedParams,
|
|
} from "./Service.Interface";
|
|
import { EmployeeRoles } from "../Employees/Employees.Interface";
|
|
import UsersManager from "../Users/Users";
|
|
import CompaniesManager from "../Companies/Companies";
|
|
import EmployeesList from "../Employees/Employee";
|
|
import { NoPermissionMessage } from "../../helpers/NoPermissionMessage";
|
|
import { isNull } from "../../helpers/IsNull";
|
|
import getServiceImage from "../../helpers/getServiceImage";
|
|
import sharp from "sharp";
|
|
import MetricsList from "../../Models/Metrics/Metrics";
|
|
import { resizeAndCrop } from "../../helpers/resizeAndCrop";
|
|
import { TextObjectFilterResult } from "../TextObjectFilter.model";
|
|
import { validatePermissionsByCompany, validateSessionUser } from "../../helpers/check";
|
|
import Discounts from "../Discounts/Discounts";
|
|
import { DiscountType } from "../Discounts/Discounts.Interface";
|
|
|
|
class ServiceManager implements IServicesManager {
|
|
services: ServicesAdapterMongoose;
|
|
|
|
constructor() {
|
|
this.services = new ServicesAdapterMongoose();
|
|
}
|
|
|
|
private async validatePublicRatingFlagsPlan(ownerId: string): Promise<void> {
|
|
const { default: PlanSubscriptionsList } = await import("../PlanSubscriptions/PlanSubscriptons");
|
|
const subscription = await PlanSubscriptionsList.getSubscriptionByUser({
|
|
sessionUser: ownerId,
|
|
});
|
|
|
|
if (!subscription || subscription.plan.price === 0) {
|
|
throw new Error("Esta configuración está disponible solo para planes pagos.");
|
|
}
|
|
}
|
|
|
|
public async setPublishedStatus(data: SetPublishedStatusParams): 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 service = await this.services.findOne({ _id: data.serviceId });
|
|
|
|
if (!service) {
|
|
throw new Error("El servicio no existe");
|
|
}
|
|
|
|
const company = await CompaniesManager.companies.findOne({
|
|
_id: service.companyId,
|
|
});
|
|
|
|
if (!company) {
|
|
throw new Error("La compañia no existe");
|
|
}
|
|
|
|
if (!(await EmployeesList.checkPermission(company, data.sessionUser, EmployeeRoles.ADMIN))) {
|
|
throw new Error(NoPermissionMessage());
|
|
}
|
|
|
|
service.published = data.published;
|
|
await service.save();
|
|
}
|
|
|
|
public async deleteServicesByCompany(data: DeleteServicesByCompany): Promise<void> {
|
|
const services = await this.services.find({
|
|
companyId: data.companyId,
|
|
});
|
|
|
|
const rootDir =
|
|
process.env.NODE_ENV !== "production"
|
|
? process.env.UPLOAD_SERVICE_FILES_ROOT_DEV
|
|
: process.env.UPLOAD_SERVICE_FILES_ROOT_PRO;
|
|
|
|
if (!rootDir) {
|
|
throw new Error("No se ha encontrado la ruta de los archivos");
|
|
}
|
|
|
|
for (const service of services) {
|
|
if (!service.id) {
|
|
continue;
|
|
}
|
|
const serviceDir = path.join(__dirname, rootDir, service.id);
|
|
|
|
try {
|
|
await rm(serviceDir, { recursive: true, force: true });
|
|
} catch (err) {
|
|
console.error(`Error al eliminar el directorio ${serviceDir}:`, err);
|
|
}
|
|
}
|
|
|
|
await this.services.serviceList.deleteMany({
|
|
companyId: data.companyId,
|
|
});
|
|
}
|
|
|
|
public async createService(data: CreateServiceParams): Promise<IService> {
|
|
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 checkService = await this.services.findOne({
|
|
name: data.name,
|
|
companyId: data.companyId,
|
|
});
|
|
|
|
if (checkService) {
|
|
throw new Error("Ya existe un servicio con ese nombre");
|
|
}
|
|
|
|
const canAdd = await MetricsList.canAddService(companyCheck.ownerId);
|
|
|
|
if (!canAdd) {
|
|
throw new Error("Ha alcanzado el limite de servicios permitidos de acuerdo a su plan.");
|
|
}
|
|
|
|
if (data.showPublicScores !== undefined || data.showPublicOpinions !== undefined) {
|
|
await this.validatePublicRatingFlagsPlan(String(companyCheck.ownerId));
|
|
}
|
|
|
|
const newService = await this.services.create({
|
|
...data,
|
|
...{
|
|
categoryId: companyCheck.categoryId,
|
|
block: companyCheck.block,
|
|
city: companyCheck.city,
|
|
state: companyCheck.state,
|
|
country: companyCheck.country,
|
|
zipCode: companyCheck.zipCode,
|
|
latitude: companyCheck.latitude,
|
|
longitude: companyCheck.longitude,
|
|
originalPrice: data.originalPrice,
|
|
price: data.originalPrice,
|
|
},
|
|
});
|
|
|
|
await MetricsList.addService({
|
|
userId: companyCheck.ownerId,
|
|
quantity: 1,
|
|
});
|
|
|
|
return newService;
|
|
}
|
|
|
|
public async updateService(data: UpdateServiceParams): 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");
|
|
}
|
|
|
|
if (!(await EmployeesList.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) {
|
|
throw new Error(NoPermissionMessage());
|
|
}
|
|
|
|
if (data.showPublicScores !== undefined || data.showPublicOpinions !== undefined) {
|
|
await this.validatePublicRatingFlagsPlan(String(companyCheck.ownerId));
|
|
}
|
|
|
|
await this.services.update(data);
|
|
}
|
|
|
|
public async applyDiscount(data: ApplyDiscountParams): Promise<void> {
|
|
await validateSessionUser({ sessionUser: data.sessionUser });
|
|
await validatePermissionsByCompany({
|
|
companyId: data.companyId,
|
|
sessionUser: data.sessionUser,
|
|
});
|
|
|
|
const service = await this.services.findOne({
|
|
_id: data.serviceId,
|
|
companyId: data.companyId,
|
|
});
|
|
|
|
if (!service) {
|
|
throw new Error("El servicio no existe");
|
|
}
|
|
|
|
const discount = await Discounts.discounts.discountsList.findOne({
|
|
_id: data.discountId,
|
|
companyId: data.companyId,
|
|
});
|
|
|
|
if (!discount) {
|
|
throw new Error("El descuento no existe");
|
|
}
|
|
|
|
if (discount.type == DiscountType.FIXED) {
|
|
service.price = service.originalPrice - discount.value;
|
|
} else if (discount.type == DiscountType.PERCENTAGE) {
|
|
service.price = service.originalPrice - service.originalPrice * (discount.value / 100);
|
|
}
|
|
|
|
service.discountId = data.discountId;
|
|
await service.save();
|
|
}
|
|
|
|
public async deleteDiscount(data: DeleteDiscountParams): Promise<void> {
|
|
await validateSessionUser({ sessionUser: data.sessionUser });
|
|
await validatePermissionsByCompany({
|
|
companyId: data.companyId,
|
|
sessionUser: data.sessionUser,
|
|
});
|
|
|
|
const service = await this.services.findOne({
|
|
_id: data.serviceId,
|
|
companyId: data.companyId,
|
|
});
|
|
|
|
if (!service) {
|
|
throw new Error("El servicio no existe");
|
|
}
|
|
|
|
service.discountId = undefined;
|
|
service.price = service.originalPrice;
|
|
await service.save();
|
|
}
|
|
|
|
public async paginate(filters: PaginateServicesParams): Promise<PaginateServicesResults> {
|
|
return await this.services.paginate(filters);
|
|
}
|
|
|
|
public async findByCompanyId(data: FindServicesByCompanyParams): Promise<CompanyServiceView[]> {
|
|
const returnValue: CompanyServiceView[] = [];
|
|
|
|
const companyServices = await this.services.find({
|
|
companyId: data.companyId,
|
|
});
|
|
|
|
const defColor = "#" + String(process.env.DEFAULT_SERVICE_COLOR);
|
|
const defFontColor = "#" + String(process.env.DEFAULT_SERVICE_FONT_COLOR);
|
|
const defFontShadowColor = "#" + String(process.env.DEFAULT_SERVICE_FONT_SHADOW_COLOR);
|
|
|
|
for (const service of companyServices) {
|
|
let discountId = "";
|
|
let discountName = "";
|
|
let discountCode = "";
|
|
let discountType = "";
|
|
let discountValue = 0;
|
|
|
|
if (service.discountId) {
|
|
const discount = await Discounts.discounts.discountsList.findOne({
|
|
_id: String(service.discountId),
|
|
companyId: data.companyId,
|
|
});
|
|
|
|
if (discount) {
|
|
discountId = discount.id;
|
|
discountName = discount.name;
|
|
discountCode = discount.code;
|
|
discountType = discount.type;
|
|
discountValue = discount.value;
|
|
}
|
|
}
|
|
|
|
returnValue.push({
|
|
id: isNull<string>(service.id, ""),
|
|
name: service.name,
|
|
description: service.description,
|
|
length: service.length,
|
|
price: service.price,
|
|
originalPrice: service.originalPrice,
|
|
discountId: discountId,
|
|
discountName: discountName,
|
|
discountCode: discountCode,
|
|
discountType: discountType,
|
|
discountValue: discountValue,
|
|
limit: service.limit,
|
|
image: getServiceImage(isNull<string>(service.id, ""), isNull<string>(service.image, ""), ""),
|
|
color: isNull<string>(service.color, defColor),
|
|
fontColor: isNull<string>(service.fontColor, defFontColor),
|
|
fontShadowColor: isNull<string>(service.fontShadowColor, defFontShadowColor),
|
|
published: isNull<SERVICE_PUBLISHED_STATUS>(
|
|
service.published,
|
|
SERVICE_PUBLISHED_STATUS.PRIVATE
|
|
),
|
|
showPublicScores: service.showPublicScores ?? true,
|
|
showPublicOpinions: service.showPublicOpinions ?? true,
|
|
});
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
public async findById(data: FindServicesByIdParams): Promise<CompanyServiceView> {
|
|
const service = await this.services.findOne({ _id: data.serviceId });
|
|
|
|
if (!service) {
|
|
throw new Error("El servicio no existe");
|
|
}
|
|
|
|
const defColor = "#" + String(process.env.DEFAULT_SERVICE_COLOR);
|
|
const defFontColor = "#" + String(process.env.DEFAULT_SERVICE_FONT_COLOR);
|
|
const defFontShadowColor = "#" + String(process.env.DEFAULT_SERVICE_FONT_SHADOW_COLOR);
|
|
|
|
let discountId = "";
|
|
let discountName = "";
|
|
let discountCode = "";
|
|
let discountType = "";
|
|
let discountValue = 0;
|
|
|
|
if (service.discountId) {
|
|
const discount = await Discounts.discounts.discountsList.findOne({
|
|
_id: String(service.discountId),
|
|
companyId: String(service.companyId),
|
|
});
|
|
|
|
if (discount) {
|
|
discountId = discount.id;
|
|
discountName = discount.name;
|
|
discountCode = discount.code;
|
|
discountType = discount.type;
|
|
discountValue = discount.value;
|
|
}
|
|
}
|
|
|
|
return {
|
|
id: isNull<string>(service.id, ""),
|
|
name: service.name,
|
|
description: service.description,
|
|
length: service.length,
|
|
price: service.price,
|
|
originalPrice: service.originalPrice,
|
|
discountId: discountId,
|
|
discountName: discountName,
|
|
discountCode: discountCode,
|
|
discountType: discountType,
|
|
discountValue: discountValue,
|
|
limit: service.limit,
|
|
image: getServiceImage(isNull<string>(service.id, ""), isNull<string>(service.image, ""), ""),
|
|
color: isNull<string>(service.color, defColor),
|
|
fontColor: isNull<string>(service.fontColor, defFontColor),
|
|
fontShadowColor: isNull<string>(service.fontShadowColor, defFontShadowColor),
|
|
published: isNull<SERVICE_PUBLISHED_STATUS>(service.published, SERVICE_PUBLISHED_STATUS.PRIVATE),
|
|
showPublicScores: service.showPublicScores ?? true,
|
|
showPublicOpinions: service.showPublicOpinions ?? true,
|
|
};
|
|
}
|
|
|
|
public async setServiceImage(data: SetServiceFileParams): 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");
|
|
}
|
|
|
|
if (!(await EmployeesList.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) {
|
|
throw new Error(NoPermissionMessage());
|
|
}
|
|
|
|
const serviceCheck = await this.services.findOne({
|
|
_id: data.serviceId,
|
|
});
|
|
|
|
if (!serviceCheck) {
|
|
throw new Error("El servicio no existe");
|
|
}
|
|
|
|
if (!data.file) {
|
|
throw new Error("No se ha enviado el archivo");
|
|
}
|
|
|
|
const rootDir =
|
|
process.env.NODE_ENV !== "production"
|
|
? process.env.UPLOAD_SERVICE_FILES_ROOT_DEV
|
|
: process.env.UPLOAD_SERVICE_FILES_ROOT_PRO;
|
|
|
|
const uploadPath = path.join(__dirname, rootDir + data.serviceId);
|
|
|
|
if (!fs.existsSync(uploadPath)) {
|
|
fs.mkdirSync(uploadPath, { recursive: true });
|
|
}
|
|
|
|
const fileExtension = data.file.originalname.split(".").pop();
|
|
const newFileName = "image." + fileExtension;
|
|
|
|
const filePath = path.join(uploadPath, newFileName);
|
|
|
|
const bufferImage = await resizeAndCrop(sharp(data.file.buffer), 378, 378);
|
|
|
|
bufferImage.webp().toFile(filePath);
|
|
|
|
//await sharp(data.file.buffer).resize({ height: 150 }).webp().toFile(filePath);
|
|
|
|
serviceCheck.image = newFileName;
|
|
await serviceCheck.save();
|
|
}
|
|
|
|
public async publicServices(data: PaginateServicesParams): Promise<PublicServicesResults> {
|
|
// Only fetch services that are marked as published
|
|
data.published = SERVICE_PUBLISHED_STATUS.PUBLISHED;
|
|
const services = await this.services.paginateFiltered(data);
|
|
const formattedServices: PublicServiceView[] = [];
|
|
|
|
for (const service of services.data) {
|
|
const organization = await CompaniesManager.companies.findOne({
|
|
_id: service.companyId,
|
|
});
|
|
|
|
// Only include services whose organization is also published
|
|
if (!organization || organization.published !== "published" || organization.banned || service.banned) {
|
|
continue;
|
|
}
|
|
|
|
const defColor = "#" + String(process.env.DEFAULT_HEADER_COLOR);
|
|
const defFontColor = "#" + String(process.env.DEFAULT_HEADER_FONT_COLOR);
|
|
const defFontShadowColor = "#" + String(process.env.DEFAULT_HEADER_FONT_SHADOW_COLOR);
|
|
|
|
let discountId = "";
|
|
let discountName = "";
|
|
let discountCode = "";
|
|
let discountType = "";
|
|
let discountValue = 0;
|
|
|
|
if (service.discountId) {
|
|
const discount = await Discounts.discounts.discountsList.findOne({
|
|
_id: String(service.discountId),
|
|
companyId: String(service.companyId),
|
|
});
|
|
|
|
if (discount) {
|
|
discountId = discount.id;
|
|
discountName = discount.name;
|
|
discountCode = discount.code;
|
|
discountType = discount.type;
|
|
discountValue = discount.value;
|
|
}
|
|
}
|
|
|
|
formattedServices.push({
|
|
id: isNull<string>(service.id, ""),
|
|
name: service.name,
|
|
description: service.description,
|
|
length: service.length,
|
|
price: service.price,
|
|
originalPrice: service.originalPrice,
|
|
discountId: discountId,
|
|
discountName: discountName,
|
|
discountCode: discountCode,
|
|
discountType: discountType,
|
|
discountValue: discountValue,
|
|
limit: service.limit,
|
|
image: getServiceImage(isNull<string>(service.id, ""), isNull<string>(service.image, ""), ""),
|
|
organizationId: isNull<string>(service.companyId, ""),
|
|
organizationName: isNull<string>(organization.name, ""),
|
|
categoryId: service.categoryId,
|
|
color: isNull<string>(service.color, defColor),
|
|
fontColor: isNull<string>(service.fontColor, defFontColor),
|
|
fontShadowColor: isNull<string>(service.fontShadowColor, defFontShadowColor),
|
|
showPublicScores: service.showPublicScores ?? true,
|
|
showPublicOpinions: service.showPublicOpinions ?? true,
|
|
});
|
|
}
|
|
|
|
const results: PublicServicesResults = {
|
|
data: formattedServices,
|
|
page: services.page,
|
|
pages: services.pages,
|
|
};
|
|
|
|
return results;
|
|
}
|
|
|
|
public async textObjectFilter(data: FindServicesParams): Promise<TextObjectFilterResult[]> {
|
|
const services = await this.services.find(data);
|
|
const formattedResult: TextObjectFilterResult[] = [];
|
|
|
|
for (const service of services) {
|
|
formattedResult.push({
|
|
id: isNull<string>(service.id, ""),
|
|
name: service.name,
|
|
image: "",
|
|
});
|
|
}
|
|
|
|
return formattedResult;
|
|
}
|
|
|
|
public async sysAdminPaginate(data: SysAdminPaginateServicesParams): Promise<PaginateServicesResults> {
|
|
return await this.services.sysAdminPaginate(data);
|
|
}
|
|
|
|
public async sysAdminSetBanned(data: SysAdminSetServiceBannedParams): Promise<void> {
|
|
const serviceCheck = await this.services.findOne({ _id: data.id });
|
|
if (!serviceCheck) {
|
|
throw new Error("El servicio no existe");
|
|
}
|
|
await this.services.update({
|
|
id: data.id,
|
|
companyId: serviceCheck.companyId, // needed by UpdateServiceParams
|
|
banned: data.banned,
|
|
sessionUser: "" // sysadmin override
|
|
});
|
|
}
|
|
}
|
|
|
|
const ServiceList = new ServiceManager();
|
|
|
|
export default ServiceList;
|