"use strict";
/**
 * EstimacionService - Gestión de Estimaciones de Obra
 *
 * Gestiona estimaciones periódicas con workflow de aprobación.
 * Incluye cálculo de anticipos, retenciones e IVA.
 *
 * @module Estimates
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.EstimacionService = void 0;
const base_service_1 = require("../../../shared/services/base.service");
class EstimacionService extends base_service_1.BaseService {
    conceptoRepository;
    generadorRepository;
    workflowRepository;
    dataSource;
    constructor(repository, conceptoRepository, generadorRepository, workflowRepository, dataSource) {
        super(repository);
        this.conceptoRepository = conceptoRepository;
        this.generadorRepository = generadorRepository;
        this.workflowRepository = workflowRepository;
        this.dataSource = dataSource;
    }
    /**
     * Crear nueva estimación
     */
    async createEstimacion(ctx, data) {
        const sequenceNumber = await this.getNextSequenceNumber(ctx, data.contratoId);
        const estimateNumber = await this.generateEstimateNumber(ctx, data.contratoId, sequenceNumber);
        const estimacion = await this.create(ctx, {
            ...data,
            estimateNumber,
            sequenceNumber,
            status: 'draft',
        });
        // Registrar en workflow
        await this.addWorkflowEntry(ctx, estimacion.id, null, 'draft', 'create', 'Estimación creada');
        return estimacion;
    }
    /**
     * Obtener siguiente número de secuencia
     */
    async getNextSequenceNumber(ctx, contratoId) {
        const result = await this.repository
            .createQueryBuilder('e')
            .select('MAX(e.sequence_number)', 'maxNumber')
            .where('e.tenant_id = :tenantId', { tenantId: ctx.tenantId })
            .andWhere('e.contrato_id = :contratoId', { contratoId })
            .getRawOne();
        return (result?.maxNumber || 0) + 1;
    }
    /**
     * Generar número de estimación
     */
    async generateEstimateNumber(_ctx, contratoId, sequenceNumber) {
        const year = new Date().getFullYear();
        return `EST-${year}-${contratoId.substring(0, 8).toUpperCase()}-${sequenceNumber.toString().padStart(3, '0')}`;
    }
    /**
     * Obtener estimaciones por contrato
     */
    async findByContrato(ctx, contratoId, page = 1, limit = 20) {
        return this.findAll(ctx, {
            page,
            limit,
            where: { contratoId },
        });
    }
    /**
     * Obtener estimaciones con filtros
     */
    async findWithFilters(ctx, filters, page = 1, limit = 20) {
        const qb = this.repository
            .createQueryBuilder('e')
            .where('e.tenant_id = :tenantId', { tenantId: ctx.tenantId })
            .andWhere('e.deleted_at IS NULL');
        if (filters.contratoId) {
            qb.andWhere('e.contrato_id = :contratoId', { contratoId: filters.contratoId });
        }
        if (filters.fraccionamientoId) {
            qb.andWhere('e.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId: filters.fraccionamientoId });
        }
        if (filters.status) {
            qb.andWhere('e.status = :status', { status: filters.status });
        }
        if (filters.periodFrom) {
            qb.andWhere('e.period_start >= :periodFrom', { periodFrom: filters.periodFrom });
        }
        if (filters.periodTo) {
            qb.andWhere('e.period_end <= :periodTo', { periodTo: filters.periodTo });
        }
        const skip = (page - 1) * limit;
        qb.orderBy('e.sequence_number', 'DESC').skip(skip).take(limit);
        const [data, total] = await qb.getManyAndCount();
        return {
            data,
            meta: {
                total,
                page,
                limit,
                totalPages: Math.ceil(total / limit),
            },
        };
    }
    /**
     * Obtener estimación con detalles completos
     */
    async findWithDetails(ctx, id) {
        return this.repository.findOne({
            where: {
                id,
                tenantId: ctx.tenantId,
                deletedAt: null,
            },
            relations: [
                'conceptos',
                'conceptos.concepto',
                'conceptos.generadores',
                'amortizaciones',
                'retenciones',
                'workflow',
            ],
        });
    }
    /**
     * Agregar concepto a estimación
     */
    async addConcepto(ctx, estimacionId, data) {
        const estimacion = await this.findById(ctx, estimacionId);
        if (!estimacion || estimacion.status !== 'draft') {
            throw new Error('Cannot modify non-draft estimation');
        }
        const concepto = this.conceptoRepository.create({
            tenantId: ctx.tenantId,
            estimacionId,
            conceptoId: data.conceptoId,
            contratoPartidaId: data.contratoPartidaId,
            quantityContract: data.quantityContract || 0,
            quantityPrevious: data.quantityPrevious || 0,
            quantityCurrent: data.quantityCurrent,
            unitPrice: data.unitPrice,
            notes: data.notes,
            createdById: ctx.userId,
        });
        const savedConcepto = await this.conceptoRepository.save(concepto);
        await this.recalculateTotals(ctx, estimacionId);
        return savedConcepto;
    }
    /**
     * Agregar generador a concepto de estimación
     */
    async addGenerador(ctx, estimacionConceptoId, data) {
        const concepto = await this.conceptoRepository.findOne({
            where: { id: estimacionConceptoId, tenantId: ctx.tenantId },
            relations: ['estimacion'],
        });
        if (!concepto) {
            throw new Error('Concepto not found');
        }
        const generador = this.generadorRepository.create({
            tenantId: ctx.tenantId,
            estimacionConceptoId,
            generatorNumber: data.generatorNumber,
            description: data.description,
            loteId: data.loteId,
            departamentoId: data.departamentoId,
            locationDescription: data.locationDescription,
            quantity: data.quantity,
            formula: data.formula,
            photoUrl: data.photoUrl,
            sketchUrl: data.sketchUrl,
            status: 'draft',
            capturedById: ctx.userId,
            createdById: ctx.userId,
        });
        return this.generadorRepository.save(generador);
    }
    /**
     * Recalcular totales de estimación
     */
    async recalculateTotals(_ctx, estimacionId) {
        // Ejecutar función de PostgreSQL
        await this.dataSource.query('SELECT estimates.calculate_estimate_totals($1)', [estimacionId]);
    }
    /**
     * Cambiar estado de estimación
     */
    async changeStatus(ctx, estimacionId, newStatus, action, comments) {
        const estimacion = await this.findById(ctx, estimacionId);
        if (!estimacion) {
            return null;
        }
        const validTransitions = {
            draft: ['submitted'],
            submitted: ['reviewed', 'rejected'],
            reviewed: ['approved', 'rejected'],
            approved: ['invoiced'],
            invoiced: ['paid'],
            paid: [],
            rejected: ['draft'],
            cancelled: [],
        };
        if (!validTransitions[estimacion.status]?.includes(newStatus)) {
            throw new Error(`Invalid status transition from ${estimacion.status} to ${newStatus}`);
        }
        const updateData = { status: newStatus };
        switch (newStatus) {
            case 'submitted':
                updateData.submittedAt = new Date();
                updateData.submittedById = ctx.userId;
                break;
            case 'reviewed':
                updateData.reviewedAt = new Date();
                updateData.reviewedById = ctx.userId;
                break;
            case 'approved':
                updateData.approvedAt = new Date();
                updateData.approvedById = ctx.userId;
                break;
            case 'invoiced':
                updateData.invoicedAt = new Date();
                break;
            case 'paid':
                updateData.paidAt = new Date();
                break;
        }
        const updated = await this.update(ctx, estimacionId, updateData);
        // Registrar en workflow
        await this.addWorkflowEntry(ctx, estimacionId, estimacion.status, newStatus, action, comments);
        return updated;
    }
    /**
     * Agregar entrada al workflow
     */
    async addWorkflowEntry(ctx, estimacionId, fromStatus, toStatus, action, comments) {
        await this.workflowRepository.save(this.workflowRepository.create({
            tenantId: ctx.tenantId,
            estimacionId,
            fromStatus,
            toStatus,
            action,
            comments,
            performedById: ctx.userId,
            createdById: ctx.userId,
        }));
    }
    /**
     * Enviar estimación para revisión
     */
    async submit(ctx, estimacionId) {
        return this.changeStatus(ctx, estimacionId, 'submitted', 'submit', 'Enviada para revisión');
    }
    /**
     * Revisar estimación
     */
    async review(ctx, estimacionId) {
        return this.changeStatus(ctx, estimacionId, 'reviewed', 'review', 'Revisión completada');
    }
    /**
     * Aprobar estimación
     */
    async approve(ctx, estimacionId) {
        return this.changeStatus(ctx, estimacionId, 'approved', 'approve', 'Aprobada');
    }
    /**
     * Rechazar estimación
     */
    async reject(ctx, estimacionId, reason) {
        return this.changeStatus(ctx, estimacionId, 'rejected', 'reject', reason);
    }
    /**
     * Obtener resumen de estimaciones por contrato
     */
    async getContractSummary(ctx, contratoId) {
        const result = await this.repository
            .createQueryBuilder('e')
            .select([
            'COUNT(*) as total_estimates',
            'SUM(CASE WHEN e.status = \'approved\' THEN e.total_amount ELSE 0 END) as total_approved',
            'SUM(CASE WHEN e.status = \'paid\' THEN e.total_amount ELSE 0 END) as total_paid',
        ])
            .where('e.tenant_id = :tenantId', { tenantId: ctx.tenantId })
            .andWhere('e.contrato_id = :contratoId', { contratoId })
            .andWhere('e.deleted_at IS NULL')
            .getRawOne();
        return {
            totalEstimates: parseInt(result?.total_estimates || '0'),
            totalApproved: parseFloat(result?.total_approved || '0'),
            totalPaid: parseFloat(result?.total_paid || '0'),
        };
    }
}
exports.EstimacionService = EstimacionService;
//# sourceMappingURL=estimacion.service.js.map