"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