"use strict"; /** * RequisicionService - Servicio de requisiciones de obra * * Gestión de requisiciones de material con workflow: * draft -> submitted -> approved -> partially_served -> served * * @module Inventory */ Object.defineProperty(exports, "__esModule", { value: true }); exports.RequisicionService = void 0; class RequisicionService { requisicionRepository; lineaRepository; constructor(requisicionRepository, lineaRepository) { this.requisicionRepository = requisicionRepository; this.lineaRepository = lineaRepository; } generateNumber() { const now = new Date(); const year = now.getFullYear().toString().slice(-2); const month = (now.getMonth() + 1).toString().padStart(2, '0'); const random = Math.random().toString(36).substring(2, 6).toUpperCase(); return `REQ-${year}${month}-${random}`; } async findWithFilters(ctx, filters = {}, page = 1, limit = 20) { const skip = (page - 1) * limit; const queryBuilder = this.requisicionRepository .createQueryBuilder('req') .leftJoinAndSelect('req.fraccionamiento', 'fraccionamiento') .leftJoinAndSelect('req.requestedBy', 'requestedBy') .where('req.tenant_id = :tenantId', { tenantId: ctx.tenantId }) .andWhere('req.deleted_at IS NULL'); if (filters.fraccionamientoId) { queryBuilder.andWhere('req.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId: filters.fraccionamientoId, }); } if (filters.status) { queryBuilder.andWhere('req.status = :status', { status: filters.status }); } if (filters.priority) { queryBuilder.andWhere('req.priority = :priority', { priority: filters.priority }); } if (filters.dateFrom) { queryBuilder.andWhere('req.requisition_date >= :dateFrom', { dateFrom: filters.dateFrom }); } if (filters.dateTo) { queryBuilder.andWhere('req.requisition_date <= :dateTo', { dateTo: filters.dateTo }); } queryBuilder .orderBy('req.requisition_date', 'DESC') .skip(skip) .take(limit); const [data, total] = await queryBuilder.getManyAndCount(); return { data, meta: { total, page, limit, totalPages: Math.ceil(total / limit), }, }; } async findById(ctx, id) { return this.requisicionRepository.findOne({ where: { id, tenantId: ctx.tenantId, deletedAt: null, }, }); } async findWithDetails(ctx, id) { return this.requisicionRepository.findOne({ where: { id, tenantId: ctx.tenantId, deletedAt: null, }, relations: [ 'fraccionamiento', 'requestedBy', 'approvedBy', 'lineas', 'lineas.concepto', 'lineas.lote', ], }); } async create(ctx, dto) { if (!ctx.userId) { throw new Error('User ID is required to create a requisition'); } const requisicion = this.requisicionRepository.create({ tenantId: ctx.tenantId, createdById: ctx.userId, requestedById: ctx.userId, requisitionNumber: this.generateNumber(), fraccionamientoId: dto.fraccionamientoId, requisitionDate: dto.requisitionDate, requiredDate: dto.requiredDate, priority: dto.priority || 'medium', destinationWarehouseId: dto.destinationWarehouseId, notes: dto.notes, status: 'draft', }); return this.requisicionRepository.save(requisicion); } async addLinea(ctx, requisicionId, dto) { const requisicion = await this.findById(ctx, requisicionId); if (!requisicion) { throw new Error('Requisicion not found'); } if (requisicion.status !== 'draft') { throw new Error('Can only add lines to draft requisitions'); } const linea = this.lineaRepository.create({ tenantId: ctx.tenantId, createdById: ctx.userId, requisicionId, productId: dto.productId, conceptoId: dto.conceptoId, loteId: dto.loteId, quantityRequested: dto.quantityRequested, unitId: dto.unitId, notes: dto.notes, }); return this.lineaRepository.save(linea); } async removeLinea(ctx, requisicionId, lineaId) { const requisicion = await this.findById(ctx, requisicionId); if (!requisicion) { return false; } if (requisicion.status !== 'draft') { throw new Error('Can only remove lines from draft requisitions'); } const result = await this.lineaRepository.delete({ id: lineaId, requisicionId, tenantId: ctx.tenantId, }); return (result.affected ?? 0) > 0; } async submit(ctx, id) { const requisicion = await this.findWithDetails(ctx, id); if (!requisicion) { return null; } if (requisicion.status !== 'draft') { throw new Error('Can only submit draft requisitions'); } if (!requisicion.lineas || requisicion.lineas.length === 0) { throw new Error('Cannot submit requisition without lines'); } requisicion.status = 'submitted'; return this.requisicionRepository.save(requisicion); } async approve(ctx, id) { const requisicion = await this.findWithDetails(ctx, id); if (!requisicion) { return null; } if (requisicion.status !== 'submitted') { throw new Error('Can only approve submitted requisitions'); } requisicion.status = 'approved'; requisicion.approvedById = ctx.userId || ''; requisicion.approvedAt = new Date(); // Set approved quantities to requested quantities by default for (const linea of requisicion.lineas) { linea.quantityApproved = linea.quantityRequested; await this.lineaRepository.save(linea); } return this.requisicionRepository.save(requisicion); } async reject(ctx, id, reason) { const requisicion = await this.findById(ctx, id); if (!requisicion) { return null; } if (requisicion.status !== 'submitted') { throw new Error('Can only reject submitted requisitions'); } requisicion.status = 'cancelled'; requisicion.rejectionReason = reason; return this.requisicionRepository.save(requisicion); } async cancel(ctx, id) { const requisicion = await this.findById(ctx, id); if (!requisicion) { return null; } if (requisicion.status === 'served' || requisicion.status === 'cancelled') { throw new Error('Cannot cancel served or already cancelled requisitions'); } requisicion.status = 'cancelled'; return this.requisicionRepository.save(requisicion); } async updateServedQuantity(ctx, lineaId, quantityServed) { const linea = await this.lineaRepository.findOne({ where: { id: lineaId, tenantId: ctx.tenantId }, relations: ['requisicion'], }); if (!linea || !linea.requisicion) { return null; } if (linea.requisicion.status !== 'approved' && linea.requisicion.status !== 'partially_served') { throw new Error('Can only serve approved or partially served requisitions'); } linea.quantityServed = quantityServed; await this.lineaRepository.save(linea); // Update requisition status based on all lines await this.updateRequisitionStatus(ctx, linea.requisicion.id); return linea; } async updateRequisitionStatus(ctx, requisicionId) { const requisicion = await this.findWithDetails(ctx, requisicionId); if (!requisicion || !requisicion.lineas) { return; } const allServed = requisicion.lineas.every((l) => l.quantityServed >= (l.quantityApproved || l.quantityRequested)); const someServed = requisicion.lineas.some((l) => l.quantityServed > 0); if (allServed) { requisicion.status = 'served'; } else if (someServed) { requisicion.status = 'partially_served'; } await this.requisicionRepository.save(requisicion); } async softDelete(ctx, id) { const requisicion = await this.findById(ctx, id); if (!requisicion) { return false; } if (requisicion.status !== 'draft' && requisicion.status !== 'cancelled') { throw new Error('Can only delete draft or cancelled requisitions'); } await this.requisicionRepository.update({ id, tenantId: ctx.tenantId }, { deletedAt: new Date(), deletedById: ctx.userId }); return true; } } exports.RequisicionService = RequisicionService; //# sourceMappingURL=requisicion.service.js.map