feat: implement organization insights dashboard with detailed metrics and collaboration data in sysadmin-cli
This commit is contained in:
@@ -264,6 +264,75 @@ export type SysAdminSetCompanyBannedParams = {
|
||||
banned: boolean;
|
||||
};
|
||||
|
||||
export type SysAdminCompanyInsightsParams = {
|
||||
companyId: string;
|
||||
};
|
||||
|
||||
export type SysAdminCompanyInsightsResult = {
|
||||
organization: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
description: string;
|
||||
categoryId: number;
|
||||
banned: boolean;
|
||||
published?: COMPANY_PUBLISHED_STATUS;
|
||||
onboardingStep?: number;
|
||||
onboardingCompleted?: boolean;
|
||||
};
|
||||
owner: {
|
||||
id: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
verificated?: boolean;
|
||||
phoneCountryCode?: string;
|
||||
phoneAreaCode?: string;
|
||||
phoneNumber?: string;
|
||||
} | null;
|
||||
collaborators: {
|
||||
id: string;
|
||||
userId?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
fullName?: string;
|
||||
email?: string;
|
||||
roles?: string[];
|
||||
removed?: boolean;
|
||||
active: boolean;
|
||||
avatar?: string;
|
||||
}[];
|
||||
subscription: {
|
||||
id: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
isActive: boolean;
|
||||
autoRenew: boolean;
|
||||
mpStatus?: string;
|
||||
pendingPaymentType?: "extension" | "upgrade";
|
||||
plan: {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
price: number;
|
||||
limitOrganizations: number;
|
||||
limitEmployees: number;
|
||||
limitServices: number;
|
||||
limitAppointments: number;
|
||||
limitClients: number;
|
||||
} | null;
|
||||
} | null;
|
||||
stats: {
|
||||
employeesCount: number;
|
||||
activeEmployeesCount: number;
|
||||
servicesCount: number;
|
||||
activeServicesCount: number;
|
||||
clientsCount: number;
|
||||
activeClientsCount: number;
|
||||
reservationsLast30Days: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type FixCompanyPostParams = {
|
||||
companyId: string;
|
||||
postId: string;
|
||||
@@ -310,4 +379,5 @@ export interface ICompaniesManager {
|
||||
sysAdminPaginate(data: SysAdminPaginateCompaniesParams): Promise<PaginateCompaniesResults>;
|
||||
sysAdminUpdate(data: SysAdminUpdateCompanyParams): Promise<void>;
|
||||
sysAdminSetBanned(data: SysAdminSetCompanyBannedParams): Promise<void>;
|
||||
sysAdminGetInsights(data: SysAdminCompanyInsightsParams): Promise<SysAdminCompanyInsightsResult>;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import {
|
||||
SysAdminPaginateCompaniesParams,
|
||||
SysAdminUpdateCompanyParams,
|
||||
SysAdminSetCompanyBannedParams,
|
||||
SysAdminCompanyInsightsParams,
|
||||
SysAdminCompanyInsightsResult,
|
||||
} from "./Companies.Interface";
|
||||
import UsersManager from "../Users/Users";
|
||||
import EmployeesList from "../Employees/Employee";
|
||||
@@ -54,11 +56,14 @@ import ClientAccount from "../ClientAccounts/ClientAccount";
|
||||
import Payments from "../Payments/Payments";
|
||||
import Discounts from "../Discounts/Discounts";
|
||||
import Templates from "../Templates/Templates";
|
||||
import PlanSubscriptionsList from "../PlanSubscriptions/PlanSubscriptons";
|
||||
import PlansList from "../Plans/Plans";
|
||||
|
||||
import Posts from "../Posts/Posts";
|
||||
import SchedulesDisabledList from "../SchedulesDisabled/SchedulesDisabled";
|
||||
import DiscountsEmail from "../DiscountsEmail/DiscountsEmail";
|
||||
import { NotificationType } from "../../Models/SystemNotifications/SystemNotification.Interface";
|
||||
import getAvatar from "../../helpers/getAvatar";
|
||||
|
||||
class CompaniesManager implements ICompaniesManager {
|
||||
companies: ICompaniesAdapter;
|
||||
@@ -1085,6 +1090,137 @@ class CompaniesManager implements ICompaniesManager {
|
||||
sessionUser: "" // sysadmin override
|
||||
});
|
||||
}
|
||||
|
||||
public async sysAdminGetInsights(data: SysAdminCompanyInsightsParams): Promise<SysAdminCompanyInsightsResult> {
|
||||
const company = await this.companies.findOne({ _id: data.companyId });
|
||||
if (!company) {
|
||||
throw new Error("La compañia no existe");
|
||||
}
|
||||
|
||||
const [ownerResult, subscriptionResult] = await Promise.allSettled([
|
||||
UsersManager.users.findOne({ _id: String(company.ownerId) }),
|
||||
PlanSubscriptionsList.planSuscriptions.planSuscriptionList
|
||||
.findOne({ userId: String(company.ownerId) })
|
||||
.sort({ endDate: -1 })
|
||||
.exec(),
|
||||
]);
|
||||
const owner = ownerResult.status === "fulfilled" ? ownerResult.value : null;
|
||||
const subscription = subscriptionResult.status === "fulfilled" ? subscriptionResult.value : null;
|
||||
const collaboratorsResult = await Promise.allSettled([
|
||||
EmployeesList.employees.employeeList.find({ companyId: data.companyId }).exec(),
|
||||
]);
|
||||
const employeeDocs = collaboratorsResult[0]?.status === "fulfilled" ? collaboratorsResult[0].value : [];
|
||||
const collaboratorUserResults = await Promise.allSettled(
|
||||
employeeDocs.map((employee) => employee.userId
|
||||
? UsersManager.users.findOne({ _id: String(employee.userId) })
|
||||
: Promise.resolve(null))
|
||||
);
|
||||
const collaborators = employeeDocs.map((employee, index) => {
|
||||
const userResult = collaboratorUserResults[index];
|
||||
const user = userResult?.status === "fulfilled" ? userResult.value : null;
|
||||
const firstName = user?.firstName || employee.profileSnapshot?.firstName;
|
||||
const lastName = user?.lastName || employee.profileSnapshot?.lastName;
|
||||
const fullName = [firstName, lastName].filter(Boolean).join(" ");
|
||||
const avatar = user?.avatar
|
||||
? getAvatar(String(user.id || user._id), user.avatar, fullName)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
id: String(employee.id || employee._id),
|
||||
userId: employee.userId ? String(employee.userId) : undefined,
|
||||
firstName,
|
||||
lastName,
|
||||
fullName: fullName || undefined,
|
||||
email: user?.email || employee.profileSnapshot?.email,
|
||||
roles: employee.roles || [],
|
||||
removed: employee.removed ?? false,
|
||||
active: !(employee.removed ?? false),
|
||||
avatar: avatar !== fullName ? avatar : undefined,
|
||||
};
|
||||
});
|
||||
const planResult = subscription
|
||||
? await Promise.allSettled([PlansList.plans.planList.findOne({ _id: String(subscription.planId) }).exec()])
|
||||
: [];
|
||||
const plan = planResult[0]?.status === "fulfilled" ? planResult[0].value : null;
|
||||
const thirtyDaysAgo = new Date();
|
||||
thirtyDaysAgo.setUTCDate(thirtyDaysAgo.getUTCDate() - 30);
|
||||
thirtyDaysAgo.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
const [
|
||||
employeesCount,
|
||||
activeEmployeesCount,
|
||||
servicesCount,
|
||||
activeServicesCount,
|
||||
clientsCount,
|
||||
activeClientsCount,
|
||||
reservationsLast30Days,
|
||||
] = (await Promise.allSettled([
|
||||
EmployeesList.employees.employeeList.countDocuments({ companyId: data.companyId }).exec(),
|
||||
EmployeesList.employees.employeeList.countDocuments({ companyId: data.companyId, removed: { $ne: true } }).exec(),
|
||||
ServiceList.services.serviceList.countDocuments({ companyId: data.companyId }).exec(),
|
||||
ServiceList.services.serviceList.countDocuments({ companyId: data.companyId, banned: { $ne: true } }).exec(),
|
||||
ClientsList.clients.clientList.countDocuments({ companyId: data.companyId }).exec(),
|
||||
ClientsList.clients.clientList.countDocuments({ companyId: data.companyId, status: true, banned: { $ne: true } }).exec(),
|
||||
AppointmentList.Appointments.AppointmentList.countDocuments({
|
||||
companyId: data.companyId,
|
||||
start: { $gte: thirtyDaysAgo },
|
||||
}).exec(),
|
||||
])).map((result) => result.status === "fulfilled" ? result.value : 0);
|
||||
|
||||
return {
|
||||
organization: {
|
||||
id: String(company.id || company._id),
|
||||
name: company.name,
|
||||
slug: company.slug,
|
||||
description: company.description,
|
||||
categoryId: company.categoryId,
|
||||
banned: company.banned ?? false,
|
||||
published: company.published,
|
||||
onboardingStep: company.onboardingStep,
|
||||
onboardingCompleted: company.onboardingCompleted,
|
||||
},
|
||||
owner: owner ? {
|
||||
id: String(owner.id || owner._id),
|
||||
firstName: owner.firstName,
|
||||
lastName: owner.lastName,
|
||||
email: owner.email,
|
||||
verificated: owner.verificated,
|
||||
phoneCountryCode: owner.phoneCountryCode,
|
||||
phoneAreaCode: owner.phoneAreaCode,
|
||||
phoneNumber: owner.phoneNumber,
|
||||
} : null,
|
||||
collaborators,
|
||||
subscription: subscription ? {
|
||||
id: String(subscription.id || subscription._id),
|
||||
startDate: subscription.startDate,
|
||||
endDate: subscription.endDate,
|
||||
isActive: subscription.isActive,
|
||||
autoRenew: subscription.autoRenew,
|
||||
mpStatus: subscription.mpStatus,
|
||||
pendingPaymentType: subscription.pendingPaymentType,
|
||||
plan: plan ? {
|
||||
id: String(plan.id || plan._id),
|
||||
name: plan.name,
|
||||
code: plan.code,
|
||||
price: plan.price,
|
||||
limitOrganizations: plan.limitOrganizations,
|
||||
limitEmployees: plan.limitEmployees,
|
||||
limitServices: plan.limitServices,
|
||||
limitAppointments: plan.limitAppointments,
|
||||
limitClients: plan.limitClients,
|
||||
} : null,
|
||||
} : null,
|
||||
stats: {
|
||||
employeesCount,
|
||||
activeEmployeesCount,
|
||||
servicesCount,
|
||||
activeServicesCount,
|
||||
clientsCount,
|
||||
activeClientsCount,
|
||||
reservationsLast30Days,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const CompaniesList = new CompaniesManager();
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
SysAdminPaginateCompaniesParams,
|
||||
SysAdminUpdateCompanyParams,
|
||||
SysAdminSetCompanyBannedParams,
|
||||
SysAdminCompanyInsightsParams,
|
||||
SysAdminCompanyInsightsResult,
|
||||
} from "../../../Models/Companies/Companies.Interface";
|
||||
import { ApiValidationError } from "../../../Models/Server.Error.model";
|
||||
import { ApiVoidResult } from "../../../Models/Api.VoidResult.type";
|
||||
@@ -66,4 +68,23 @@ export class SysAdminCompaniesController extends Controller {
|
||||
return new ApiValidationError(500, errorOccurred.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Response<ApiValidationError>(500, "Ha ocurrido un error")
|
||||
@SuccessResponse(200, "Done")
|
||||
@Post("insights")
|
||||
@Middlewares(sysAdminAuthMiddleware)
|
||||
public async sysAdminGetInsights(
|
||||
@Body() requestBody: SysAdminCompanyInsightsParams,
|
||||
): Promise<SysAdminCompanyInsightsResult | ApiValidationError> {
|
||||
try {
|
||||
const { payload, ...cleanRequestBody } = requestBody as any;
|
||||
const result = await new SA_CompaniesService().sysAdminGetInsights(cleanRequestBody);
|
||||
this.setStatus(200);
|
||||
return result;
|
||||
} catch (e) {
|
||||
const errorOccurred: Error = e as Error;
|
||||
this.setStatus(500);
|
||||
return new ApiValidationError(500, errorOccurred.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
SysAdminPaginateCompaniesParams,
|
||||
SysAdminUpdateCompanyParams,
|
||||
SysAdminSetCompanyBannedParams,
|
||||
SysAdminCompanyInsightsParams,
|
||||
SysAdminCompanyInsightsResult,
|
||||
} from "../../../Models/Companies/Companies.Interface";
|
||||
import CompaniesList from "../../../Models/Companies/Companies";
|
||||
|
||||
@@ -22,4 +24,9 @@ export class SA_CompaniesService {
|
||||
await connect(`${process.env.DATABASE_CONNECTION}`);
|
||||
return await CompaniesList.sysAdminSetBanned(data);
|
||||
}
|
||||
|
||||
public async sysAdminGetInsights(data: SysAdminCompanyInsightsParams): Promise<SysAdminCompanyInsightsResult> {
|
||||
await connect(`${process.env.DATABASE_CONNECTION}`);
|
||||
return await CompaniesList.sysAdminGetInsights(data);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user