first commit
This commit is contained in:
@@ -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();
|
||||
Reference in New Issue
Block a user