180 lines
5.8 KiB
JavaScript
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
|