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
@@ -0,0 +1,71 @@
import { Document, Model, Schema, model, FilterQuery } from "mongoose";
import {
CreateCashFlowBalanceParams,
FindCashFlowBalanceParams,
ICashFlowBalance,
ICashFlowBalanceAdapter,
} from "./CashFlowBalance.Interface";
export interface ICashFlowBalanceDocument extends Omit<ICashFlowBalance, "id">, Document {}
export class CashFlowBalanceAdapterMongoose implements ICashFlowBalanceAdapter {
schema: Schema;
balanceList: Model<ICashFlowBalanceDocument>;
constructor() {
this.schema = new Schema({
companyId: {
type: Schema.Types.ObjectId,
required: true,
ref: "Company",
},
amount: { type: Number, required: true, default: 0 },
year: { type: Number, required: true, default: 0 },
month: { type: Number, required: true, default: 0 },
updatedAt: { type: Date, required: true, default: Date.now },
});
this.balanceList = model<ICashFlowBalanceDocument>("CashFlowBalance", this.schema);
}
public async create(data: CreateCashFlowBalanceParams): Promise<ICashFlowBalance> {
return await this.balanceList.create(data);
}
public async delete(id: string): Promise<void> {
await this.balanceList.deleteOne({ _id: id });
}
private buildSearchCriteria(
filters: Omit<FindCashFlowBalanceParams, "sessionUser">
): FilterQuery<ICashFlowBalance> {
const searchCriteria: FilterQuery<ICashFlowBalance> = {};
if (filters.companyId) {
searchCriteria.companyId = filters.companyId;
}
if (filters.month) {
searchCriteria.month = filters.month;
}
if (filters.year) {
searchCriteria.year = filters.year;
}
return searchCriteria;
}
public async find(
filters: Omit<FindCashFlowBalanceParams, "sessionUser">
): Promise<ICashFlowBalance[]> {
const searchCriteria = this.buildSearchCriteria(filters);
return this.balanceList.find(searchCriteria).exec();
}
public async findOne(
filters: Omit<FindCashFlowBalanceParams, "sessionUser">
): Promise<ICashFlowBalanceDocument | null> {
return this.balanceList.findOne(filters).exec();
}
}
@@ -0,0 +1,67 @@
import {
CreateCashFlowParams,
DeleteByClientAccountMovementParams,
DeleteMovementByPaymentParams,
DeleteMovementParams,
} from "../CashFlow/CashFlow.Interface";
import { ICashFlowBalanceDocument } from "./CashFlowBalance.Adapter.Mongoose";
export type FindCashFlowBalanceParams = {
companyId: string;
year: number;
month: number;
sessionUser: string;
};
export type CreateCashFlowBalanceParams = {
companyId: string;
amount: number;
year: number;
month: number;
updatedAt: Date;
};
export type CalculateCashFlowBalanceParams = {
companyId: string;
year: number;
month: number;
sessionUser: string;
};
export interface ICashFlowBalance {
id?: string;
companyId: string;
amount: number;
year: number;
month: number;
updatedAt: Date;
}
export interface CashFlowHistoryItem {
amount: number;
month: number;
year: number;
}
export interface CashFlowHistoryView {
companyId: string;
data: CashFlowHistoryItem[];
total: number;
}
export interface ICashFlowBalanceAdapter {
create(data: CreateCashFlowBalanceParams): Promise<ICashFlowBalance>;
delete(id: string): Promise<void>;
find(filters: FindCashFlowBalanceParams): Promise<ICashFlowBalance[]>;
findOne(filters: FindCashFlowBalanceParams): Promise<ICashFlowBalanceDocument | null>;
}
export interface ICashFlowBalanceManager {
balance: ICashFlowBalanceAdapter;
createCashFlowMovement(data: CreateCashFlowParams): Promise<void>;
calculateBalance(data: CalculateCashFlowBalanceParams): Promise<ICashFlowBalance>;
delete(data: DeleteMovementParams): Promise<void>;
deleteByPayment(data: DeleteMovementByPaymentParams): Promise<void>;
deleteByClientAccountMovement(data: DeleteByClientAccountMovementParams): Promise<void>;
history(data: FindCashFlowBalanceParams): Promise<CashFlowHistoryView>;
}
@@ -0,0 +1,261 @@
import UsersManager from "../Users/Users";
import { IUserDocument } from "../Users/Users.Adapter.Mongoose";
import CompaniesList from "../Companies/Companies";
import { EmployeeRoles } from "../Employees/Employees.Interface";
import EmployeeManager from "../Employees/Employee";
import { NoPermissionMessage } from "../../helpers/NoPermissionMessage";
import {
CalculateCashFlowBalanceParams,
CashFlowHistoryView,
FindCashFlowBalanceParams,
ICashFlowBalance,
ICashFlowBalanceManager,
} from "./CashFlowBalance.Interface";
import { CashFlowBalanceAdapterMongoose } from "./CashFlowBalance.Adapter.Mongoose";
import dayjs from "dayjs";
import CashFlow from "../CashFlow/CashFlow";
import {
CreateCashFlowParams,
DeleteByClientAccountMovementParams,
DeleteMovementByPaymentParams,
DeleteMovementParams,
} from "../CashFlow/CashFlow.Interface";
import Payments from "../Payments/Payments";
import ClientAccountMovements from "../ClientAccountMovements/ClientAccountMovements";
import { CLIENT_ACCOUNT_MOVEMENT_TYPES } from "../ClientAccountMovements/ClientAccountMovements.Interface";
import PlanSubscriptionsList from "../PlanSubscriptions/PlanSubscriptons";
import { PlanFeatures } from "../Plans/Plans.interface";
class CashFlowBalanceManager implements ICashFlowBalanceManager {
balance: CashFlowBalanceAdapterMongoose;
constructor() {
this.balance = new CashFlowBalanceAdapterMongoose();
}
private async validateSessionUser(sessionUserId: string): Promise<IUserDocument> {
const sessionUser = await UsersManager.users.findOne({
_id: sessionUserId,
});
if (!sessionUser) {
throw new Error(
"No se ha encontrado el usuario o no tiene permisos para realizar esta acción"
);
}
return sessionUser;
}
private async validatePermissions(sessionUserId: string, companyId: string): Promise<void> {
const company = await CompaniesList.companies.findOne({
_id: companyId,
});
if (!company) {
throw new Error("No se ha encontrado la organización");
}
// Verificar si el usuario de sesión es un empleado con permisos de administrador
const hasAdminPermission = await EmployeeManager.checkRoleById(
companyId,
sessionUserId,
EmployeeRoles.ADMIN
);
if (!hasAdminPermission) {
throw new Error(NoPermissionMessage());
}
const canAccess = await PlanSubscriptionsList.checkFeature({
userId: String(company.ownerId),
feature: PlanFeatures.PAYMENTS,
});
if (!canAccess) {
throw new Error("La organizacion no tiene un plan que soporte el módulo de pagos");
}
}
public async createCashFlowMovement(data: CreateCashFlowParams): Promise<void> {
const userSession = await this.validateSessionUser(data.sessionUser);
await this.validatePermissions(String(userSession.id), data.companyId);
if (data.type === CLIENT_ACCOUNT_MOVEMENT_TYPES.DEPOSIT) {
data.amount = Math.abs(data.amount);
} else {
data.amount = -Math.abs(data.amount);
}
const movement = await CashFlow.movements.create(data);
const movDate = dayjs(movement.createdAt);
const movDateYear = movDate.year();
const movDateMonth = movDate.month();
console.log("calculando balance:", {
year: movDateYear,
month: movDateMonth,
companyId: data.companyId,
sessionUser: data.sessionUser,
});
await this.calculateBalance({
year: movDateYear,
month: movDateMonth,
companyId: data.companyId,
sessionUser: data.sessionUser,
});
}
private async getBalanceByYearMonth(
companyId: string,
year: number,
month: number
): Promise<number> {
const movements = await CashFlow.movements.find({
companyId: companyId,
month: month,
year: year,
});
return movements.reduce((acc, movement) => acc + movement.amount, 0);
}
public async calculateBalance(data: CalculateCashFlowBalanceParams): Promise<ICashFlowBalance> {
const userSession = await this.validateSessionUser(data.sessionUser);
await this.validatePermissions(String(userSession.id), data.companyId);
const balance = await this.balance.findOne({
companyId: data.companyId,
year: data.year,
month: data.month,
});
if (!balance) {
const amount = await this.getBalanceByYearMonth(data.companyId, data.year, data.month);
return await this.balance.balanceList.create({
amount,
companyId: data.companyId,
year: data.year,
month: data.month,
updatedAt: new Date(),
});
}
const amount = await this.getBalanceByYearMonth(data.companyId, data.year, data.month);
balance.amount = amount;
balance.updatedAt = new Date();
await balance.save();
return balance as ICashFlowBalance;
}
public async delete(data: DeleteMovementParams): Promise<void> {
const movement = await CashFlow.movements.movementList.findOne({
_id: data.id,
});
if (!movement) {
throw new Error("No se encontró el movimiento");
}
const companyId = movement.companyId;
const movementDate = dayjs(movement.createdAt);
const movementDateYear = movementDate.year();
const movementDateMonth = movementDate.month();
await CashFlow.movements.delete(data.id);
await this.calculateBalance({
companyId: companyId,
year: movementDateYear,
month: movementDateMonth,
sessionUser: data.sessionUser,
});
}
public async deleteByPayment(data: DeleteMovementByPaymentParams): Promise<void> {
const payment = await Payments.payments.paymentsList.findOne({
_id: data.paymentId,
});
if (!payment) {
throw new Error("No se encontró el pago");
}
const companyId = payment.companyId;
const paymentDate = dayjs(payment.createdAt);
const paymentYear = paymentDate.year();
const paymentMonth = paymentDate.month();
await CashFlow.deleteByPayment({
companyId: companyId,
paymentId: data.paymentId,
sessionUser: data.sessionUser,
});
await this.calculateBalance({
companyId: companyId,
year: paymentYear,
month: paymentMonth,
sessionUser: data.sessionUser,
});
}
public async deleteByClientAccountMovement(
data: DeleteByClientAccountMovementParams
): Promise<void> {
const clientAccountMovement = await ClientAccountMovements.movements.movementList.findOne({
_id: data.clientAccountMovementId,
});
if (!clientAccountMovement) {
throw new Error("No se encontró el movimiento");
}
const companyId = clientAccountMovement.clientId;
const movementDate = dayjs(clientAccountMovement.createdAt);
const movementDateYear = movementDate.year();
const movementDateMonth = movementDate.month();
await ClientAccountMovements.movements.delete(data.clientAccountMovementId);
await this.calculateBalance({
companyId: companyId,
year: movementDateYear,
month: movementDateMonth,
sessionUser: data.sessionUser,
});
}
public async history(data: FindCashFlowBalanceParams): Promise<CashFlowHistoryView> {
const returnData: CashFlowHistoryView = {
companyId: data.companyId,
data: [],
total: 0,
};
const userSession = await this.validateSessionUser(data.sessionUser);
await this.validatePermissions(String(userSession.id), data.companyId);
const months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
const year = data.year;
const companyId = data.companyId;
let totalBalance = 0;
for (const month of months) {
//obtenemos los balances previamente calculados para cada mes/anio de una organizacion.
const balance = await this.balance.findOne({
year: year,
month: month,
companyId: companyId,
});
returnData.data.push({
amount: balance ? balance.amount : 0,
month: month,
year: year,
});
totalBalance += balance ? balance.amount : 0;
}
return { ...returnData, ...{ total: totalBalance } };
}
}
export default new CashFlowBalanceManager();