erp-construccion-backend/dist/modules/budgets/services/presupuesto.service.js

180 lines
5.8 KiB
JavaScript

"use strict";
/**
* PresupuestoService - Gestión de Presupuestos de Obra
*
* Gestiona presupuestos de obra con sus partidas.
* Soporta versionamiento y aprobación.
*
* @module Budgets
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PresupuestoService = void 0;
const base_service_1 = require("../../../shared/services/base.service");
class PresupuestoService extends base_service_1.BaseService {
partidaRepository;
constructor(repository, partidaRepository) {
super(repository);
this.partidaRepository = partidaRepository;
}
/**
* Crear nuevo presupuesto
*/
async createPresupuesto(ctx, data) {
return this.create(ctx, {
...data,
version: 1,
isActive: true,
totalAmount: 0,
});
}
/**
* Obtener presupuestos por fraccionamiento
*/
async findByFraccionamiento(ctx, fraccionamientoId, page = 1, limit = 20) {
return this.findAll(ctx, {
page,
limit,
where: { fraccionamientoId, isActive: true },
});
}
/**
* Obtener presupuesto con sus partidas
*/
async findWithPartidas(ctx, id) {
return this.repository.findOne({
where: {
id,
tenantId: ctx.tenantId,
deletedAt: null,
},
relations: ['partidas', 'partidas.concepto'],
});
}
/**
* Agregar partida al presupuesto
*/
async addPartida(ctx, presupuestoId, data) {
const presupuesto = await this.findById(ctx, presupuestoId);
if (!presupuesto) {
throw new Error('Presupuesto not found');
}
const partida = this.partidaRepository.create({
tenantId: ctx.tenantId,
presupuestoId,
conceptoId: data.conceptoId,
quantity: data.quantity,
unitPrice: data.unitPrice,
sequence: data.sequence || 0,
createdById: ctx.userId,
});
const savedPartida = await this.partidaRepository.save(partida);
await this.recalculateTotal(ctx, presupuestoId);
return savedPartida;
}
/**
* Actualizar partida
*/
async updatePartida(ctx, partidaId, data) {
const partida = await this.partidaRepository.findOne({
where: {
id: partidaId,
tenantId: ctx.tenantId,
deletedAt: null,
},
});
if (!partida) {
return null;
}
const updated = this.partidaRepository.merge(partida, {
...data,
updatedById: ctx.userId,
});
const saved = await this.partidaRepository.save(updated);
await this.recalculateTotal(ctx, partida.presupuestoId);
return saved;
}
/**
* Eliminar partida
*/
async removePartida(ctx, partidaId) {
const partida = await this.partidaRepository.findOne({
where: {
id: partidaId,
tenantId: ctx.tenantId,
},
});
if (!partida) {
return false;
}
await this.partidaRepository.update({ id: partidaId }, {
deletedAt: new Date(),
deletedById: ctx.userId,
});
await this.recalculateTotal(ctx, partida.presupuestoId);
return true;
}
/**
* Recalcular total del presupuesto
*/
async recalculateTotal(ctx, presupuestoId) {
const result = await this.partidaRepository
.createQueryBuilder('p')
.select('SUM(p.quantity * p.unit_price)', 'total')
.where('p.presupuesto_id = :presupuestoId', { presupuestoId })
.andWhere('p.deleted_at IS NULL')
.getRawOne();
const total = parseFloat(result?.total || '0');
await this.repository.update({ id: presupuestoId }, { totalAmount: total, updatedById: ctx.userId });
}
/**
* Crear nueva versión del presupuesto
*/
async createNewVersion(ctx, presupuestoId) {
const original = await this.findWithPartidas(ctx, presupuestoId);
if (!original) {
throw new Error('Presupuesto not found');
}
// Desactivar versión anterior
await this.repository.update({ id: presupuestoId }, { isActive: false, updatedById: ctx.userId });
// Crear nueva versión
const newVersion = await this.create(ctx, {
code: original.code,
name: original.name,
description: original.description,
fraccionamientoId: original.fraccionamientoId,
prototipoId: original.prototipoId,
currencyId: original.currencyId,
version: original.version + 1,
isActive: true,
totalAmount: original.totalAmount,
});
// Copiar partidas
for (const partida of original.partidas) {
await this.partidaRepository.save(this.partidaRepository.create({
tenantId: ctx.tenantId,
presupuestoId: newVersion.id,
conceptoId: partida.conceptoId,
quantity: partida.quantity,
unitPrice: partida.unitPrice,
sequence: partida.sequence,
createdById: ctx.userId,
}));
}
return newVersion;
}
/**
* Aprobar presupuesto
*/
async approve(ctx, presupuestoId) {
const presupuesto = await this.findById(ctx, presupuestoId);
if (!presupuesto) {
return null;
}
return this.update(ctx, presupuestoId, {
approvedAt: new Date(),
approvedById: ctx.userId,
});
}
}
exports.PresupuestoService = PresupuestoService;
//# sourceMappingURL=presupuesto.service.js.map