Files
turnosxpress/server/src/api/Dashboard/Dashboard.Service.ts
T

221 lines
9.2 KiB
TypeScript

import { connect } from "mongoose";
import EmployeesList from "../../Models/Employees/Employee";
import { EmployeeRoles } from "../../Models/Employees/Employees.Interface";
import AppointmentList from "../../Models/Appointments/Appointments";
import { DashboardFinancialsResult, DashboardSummaryResult, GetDashboardFinancialsParams, IncomeDataPoint } from "./Dashboard.Interface";
import dayjs from "dayjs";
import { AppointmentEventByClient } from "../../Models/Appointments/Appointments.Interface";
import { validatePermissionsByCompany, validateSessionUser } from "../../helpers/check";
import CashFlowList from "../../Models/CashFlow/CashFlow";
import CompaniesList from "../../Models/Companies/Companies";
import PlanSubscriptionsList from "../../Models/PlanSubscriptions/PlanSubscriptons";
import MetricsList from "../../Models/Metrics/Metrics";
import PlanUsageCycleList from "../../Models/PlanUsageCycle/PlanUsageCycle";
export class DashboardService {
public async getDashboardSummary(sessionUser: string): Promise<DashboardSummaryResult> {
await connect(`${process.env.DATABASE_CONNECTION}`);
await validateSessionUser({ sessionUser });
// 1. Encontrar todas las relaciones de empleado para este usuario
const employeeRecords = await EmployeesList.employees.find({ userId: sessionUser });
let finalRole: "ADMIN" | "EMPLOYEE" | "NONE" = "NONE";
let allPersonalAgenda: AppointmentEventByClient[] = [];
for (const emp of employeeRecords) {
const isAdmin = emp.roles?.includes(EmployeeRoles.ADMIN) || false;
if (isAdmin && finalRole !== "ADMIN") {
finalRole = "ADMIN";
} else if (!isAdmin && finalRole === "NONE") {
finalRole = "EMPLOYEE";
}
const todayStart = dayjs().startOf("day").toDate();
const todayEnd = dayjs().endOf("day").toDate();
if (isAdmin) {
// If ADMIN, fetch all appointments for this company for today
const apps = await AppointmentList.Appointments.AppointmentList.find({
companyId: emp.companyId,
start: { $gte: todayStart, $lte: todayEnd }
});
for (const a of apps) {
try {
const evt = await AppointmentList.getAppointmentEvent({
appointmentId: String(a._id),
sessionUser: sessionUser
});
allPersonalAgenda.push(evt);
} catch (e) {
console.error(e);
}
}
} else {
// If EMPLOYEE, fetch only their appointments for today
const apps = await AppointmentList.Appointments.AppointmentList.find({
companyId: emp.companyId,
employeeId: String(emp.id),
start: { $gte: todayStart, $lte: todayEnd }
});
for (const a of apps) {
try {
const evt = await AppointmentList.getAppointmentEvent({
appointmentId: String(a._id),
sessionUser: sessionUser
});
allPersonalAgenda.push(evt);
} catch (e) {
console.error(e);
}
}
}
}
// Sort agenda by start time
allPersonalAgenda.sort((a, b) => a.startTime - b.startTime);
// Note: globalMetrics is handled by the frontend useSessionStore
// we just return the role and the personalAgenda
return {
role: finalRole,
personalAgenda: allPersonalAgenda
};
}
public async getDashboardFinancials(params: GetDashboardFinancialsParams): Promise<DashboardFinancialsResult> {
await connect(`${process.env.DATABASE_CONNECTION}`);
await validateSessionUser({ sessionUser: params.sessionUser });
await validatePermissionsByCompany({ companyId: params.companyId, sessionUser: params.sessionUser });
const now = dayjs();
let startDate: Date;
let prevStartDate: Date;
let endDate = now.toDate();
let prevEndDate: Date;
if (params.timeframe === "week") {
startDate = now.subtract(7, "day").startOf("day").toDate();
prevStartDate = now.subtract(14, "day").startOf("day").toDate();
prevEndDate = now.subtract(7, "day").endOf("day").toDate();
} else if (params.timeframe === "month") {
startDate = now.subtract(1, "month").startOf("day").toDate();
prevStartDate = now.subtract(2, "month").startOf("day").toDate();
prevEndDate = now.subtract(1, "month").endOf("day").toDate();
} else { // year
startDate = now.subtract(1, "year").startOf("month").toDate();
prevStartDate = now.subtract(2, "year").startOf("month").toDate();
prevEndDate = now.subtract(1, "year").endOf("month").toDate();
}
// Fetch current period cashflow
const currentMatch: any = {
companyId: params.companyId,
type: "deposit",
createdAt: { $gte: startDate, $lte: endDate }
};
if (params.employeeId) currentMatch.employeeId = params.employeeId;
const currentMovements = await CashFlowList.movements.movementList.find(currentMatch);
// Fetch previous period cashflow
const prevMatch: any = {
companyId: params.companyId,
type: "deposit",
createdAt: { $gte: prevStartDate, $lte: prevEndDate }
};
if (params.employeeId) prevMatch.employeeId = params.employeeId;
const prevMovements = await CashFlowList.movements.movementList.find(prevMatch);
const currentPeriodTotal = currentMovements.reduce((sum, m) => sum + m.amount, 0);
const previousPeriodTotal = prevMovements.reduce((sum, m) => sum + m.amount, 0);
// Group data points
const dataPointsMap = new Map<string, number>();
currentMovements.forEach(m => {
let key = "";
if (params.timeframe === "year") {
key = dayjs(m.createdAt).format("YYYY-MM");
} else {
key = dayjs(m.createdAt).format("YYYY-MM-DD");
}
dataPointsMap.set(key, (dataPointsMap.get(key) || 0) + m.amount);
});
// Fill empty dates for the chart
const currentPeriodData: IncomeDataPoint[] = [];
if (params.timeframe === "year") {
for (let i = 0; i < 12; i++) {
const k = dayjs(startDate).add(i, "month").format("YYYY-MM");
currentPeriodData.push({ date: k, amount: dataPointsMap.get(k) || 0 });
}
} else {
const days = params.timeframe === "week" ? 7 : 30; // Approx
for (let i = 0; i <= days; i++) {
const k = dayjs(startDate).add(i, "day").format("YYYY-MM-DD");
if (dayjs(k).isAfter(now)) break;
currentPeriodData.push({ date: k, amount: dataPointsMap.get(k) || 0 });
}
}
// Calculate unpaid today
const todayStart = now.startOf("day").toDate();
const todayEnd = now.endOf("day").toDate();
const unpaidMatch: any = {
companyId: params.companyId,
payment: false,
start: { $gte: todayStart, $lte: todayEnd }
};
if (params.employeeId) unpaidMatch.employeeId = params.employeeId;
const unpaidApps = await AppointmentList.Appointments.AppointmentList.find(unpaidMatch);
const unpaidToday = unpaidApps.reduce((sum, a) => sum + (a.price || 0), 0);
return {
currentPeriodTotal,
previousPeriodTotal,
currentPeriodData,
unpaidToday
};
}
public async getCompanyMetrics(params: { sessionUser: string; companyId: string }): Promise<any> {
await connect(`${process.env.DATABASE_CONNECTION}`);
await validateSessionUser({ sessionUser: params.sessionUser });
await validatePermissionsByCompany({ companyId: params.companyId, sessionUser: params.sessionUser });
const company = await CompaniesList.companies.findOne({ _id: params.companyId });
if (!company) {
throw new Error("No se encontro la compañia.");
}
const ownerId = company.ownerId;
const isOwner = String(ownerId) === String(params.sessionUser);
// Fetch metrics for owner
const metricsDocument = await MetricsList.getMetrics(String(ownerId));
const metrics = metricsDocument.toObject ? metricsDocument.toObject() : { ...metricsDocument };
// Fetch subscription for owner
const subscription = await PlanSubscriptionsList.getSubscriptionByUser({ sessionUser: String(ownerId) });
if (subscription) {
metrics.appointmentsCount = await PlanUsageCycleList.getAppointmentsCount({
userId: String(ownerId),
});
}
return {
metrics,
subscription,
isOwner
};
}
}