erp-construccion-backend/dist/modules/progress/services/avance-obra.service.js

195 lines
6.1 KiB
JavaScript

"use strict";
/**
* AvanceObraService - Gestión de Avances de Obra
*
* Gestiona el registro y aprobación de avances físicos de obra.
* Incluye workflow de captura -> revisión -> aprobación.
*
* @module Progress
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AvanceObraService = void 0;
const base_service_1 = require("../../../shared/services/base.service");
class AvanceObraService extends base_service_1.BaseService {
fotoRepository;
constructor(repository, fotoRepository) {
super(repository);
this.fotoRepository = fotoRepository;
}
/**
* Crear nuevo avance (captura)
*/
async createAvance(ctx, data) {
if (!data.loteId && !data.departamentoId) {
throw new Error('Either loteId or departamentoId is required');
}
if (data.loteId && data.departamentoId) {
throw new Error('Cannot specify both loteId and departamentoId');
}
return this.create(ctx, {
...data,
status: 'captured',
capturedById: ctx.userId,
});
}
/**
* Obtener avances por lote
*/
async findByLote(ctx, loteId, page = 1, limit = 20) {
return this.findAll(ctx, {
page,
limit,
where: { loteId },
});
}
/**
* Obtener avances por departamento
*/
async findByDepartamento(ctx, departamentoId, page = 1, limit = 20) {
return this.findAll(ctx, {
page,
limit,
where: { departamentoId },
});
}
/**
* Obtener avances con filtros
*/
async findWithFilters(ctx, filters, page = 1, limit = 20) {
const qb = this.repository
.createQueryBuilder('a')
.where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
.andWhere('a.deleted_at IS NULL');
if (filters.loteId) {
qb.andWhere('a.lote_id = :loteId', { loteId: filters.loteId });
}
if (filters.departamentoId) {
qb.andWhere('a.departamento_id = :departamentoId', { departamentoId: filters.departamentoId });
}
if (filters.conceptoId) {
qb.andWhere('a.concepto_id = :conceptoId', { conceptoId: filters.conceptoId });
}
if (filters.status) {
qb.andWhere('a.status = :status', { status: filters.status });
}
if (filters.dateFrom) {
qb.andWhere('a.capture_date >= :dateFrom', { dateFrom: filters.dateFrom });
}
if (filters.dateTo) {
qb.andWhere('a.capture_date <= :dateTo', { dateTo: filters.dateTo });
}
const skip = (page - 1) * limit;
qb.orderBy('a.capture_date', 'DESC').skip(skip).take(limit);
const [data, total] = await qb.getManyAndCount();
return {
data,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
};
}
/**
* Obtener avance con fotos
*/
async findWithFotos(ctx, id) {
return this.repository.findOne({
where: {
id,
tenantId: ctx.tenantId,
deletedAt: null,
},
relations: ['fotos', 'concepto', 'capturedBy'],
});
}
/**
* Agregar foto al avance
*/
async addFoto(ctx, avanceId, data) {
const avance = await this.findById(ctx, avanceId);
if (!avance) {
throw new Error('Avance not found');
}
const location = data.location
? `POINT(${data.location.lng} ${data.location.lat})`
: null;
const foto = this.fotoRepository.create({
tenantId: ctx.tenantId,
avanceId,
fileUrl: data.fileUrl,
fileName: data.fileName,
fileSize: data.fileSize,
mimeType: data.mimeType,
description: data.description,
location,
createdById: ctx.userId,
});
return this.fotoRepository.save(foto);
}
/**
* Revisar avance
*/
async review(ctx, avanceId) {
const avance = await this.findById(ctx, avanceId);
if (!avance || avance.status !== 'captured') {
return null;
}
return this.update(ctx, avanceId, {
status: 'reviewed',
reviewedById: ctx.userId,
reviewedAt: new Date(),
});
}
/**
* Aprobar avance
*/
async approve(ctx, avanceId) {
const avance = await this.findById(ctx, avanceId);
if (!avance || avance.status !== 'reviewed') {
return null;
}
return this.update(ctx, avanceId, {
status: 'approved',
approvedById: ctx.userId,
approvedAt: new Date(),
});
}
/**
* Rechazar avance
*/
async reject(ctx, avanceId, reason) {
const avance = await this.findById(ctx, avanceId);
if (!avance || !['captured', 'reviewed'].includes(avance.status)) {
return null;
}
return this.update(ctx, avanceId, {
status: 'rejected',
notes: reason,
});
}
/**
* Calcular avance acumulado por concepto
*/
async getAccumulatedProgress(ctx, loteId, departamentoId) {
const qb = this.repository
.createQueryBuilder('a')
.select('a.concepto_id', 'conceptoId')
.addSelect('SUM(a.quantity_executed)', 'totalQuantity')
.addSelect('AVG(a.percentage_executed)', 'avgPercentage')
.where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
.andWhere('a.deleted_at IS NULL')
.andWhere('a.status = :status', { status: 'approved' })
.groupBy('a.concepto_id');
if (loteId) {
qb.andWhere('a.lote_id = :loteId', { loteId });
}
if (departamentoId) {
qb.andWhere('a.departamento_id = :departamentoId', { departamentoId });
}
return qb.getRawMany();
}
}
exports.AvanceObraService = AvanceObraService;
//# sourceMappingURL=avance-obra.service.js.map