erp-construccion-backend/dist/modules/purchase/services/comparativo.service.js

219 lines
8.3 KiB
JavaScript

"use strict";
/**
* ComparativoService - Servicio de comparativos de cotizaciones
*
* Gestión de cuadros comparativos para selección de proveedores.
*
* @module Purchase
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ComparativoService = void 0;
class ComparativoService {
comparativoRepository;
proveedorRepository;
productoRepository;
constructor(comparativoRepository, proveedorRepository, productoRepository) {
this.comparativoRepository = comparativoRepository;
this.proveedorRepository = proveedorRepository;
this.productoRepository = productoRepository;
}
generateCode() {
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 `CMP-${year}${month}-${random}`;
}
async findWithFilters(ctx, filters = {}, page = 1, limit = 20) {
const skip = (page - 1) * limit;
const queryBuilder = this.comparativoRepository
.createQueryBuilder('comp')
.leftJoinAndSelect('comp.requisicion', 'requisicion')
.leftJoinAndSelect('comp.createdBy', 'createdBy')
.where('comp.tenant_id = :tenantId', { tenantId: ctx.tenantId })
.andWhere('comp.deleted_at IS NULL');
if (filters.requisicionId) {
queryBuilder.andWhere('comp.requisicion_id = :requisicionId', {
requisicionId: filters.requisicionId,
});
}
if (filters.status) {
queryBuilder.andWhere('comp.status = :status', { status: filters.status });
}
if (filters.dateFrom) {
queryBuilder.andWhere('comp.comparison_date >= :dateFrom', { dateFrom: filters.dateFrom });
}
if (filters.dateTo) {
queryBuilder.andWhere('comp.comparison_date <= :dateTo', { dateTo: filters.dateTo });
}
queryBuilder
.orderBy('comp.comparison_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.comparativoRepository.findOne({
where: {
id,
tenantId: ctx.tenantId,
deletedAt: null,
},
});
}
async findWithDetails(ctx, id) {
return this.comparativoRepository.findOne({
where: {
id,
tenantId: ctx.tenantId,
deletedAt: null,
},
relations: [
'requisicion',
'createdBy',
'approvedBy',
'proveedores',
'proveedores.productos',
],
});
}
async create(ctx, dto) {
const comparativo = this.comparativoRepository.create({
tenantId: ctx.tenantId,
createdById: ctx.userId,
code: this.generateCode(),
requisicionId: dto.requisicionId,
name: dto.name,
comparisonDate: dto.comparisonDate,
notes: dto.notes,
status: 'draft',
});
return this.comparativoRepository.save(comparativo);
}
async addProveedor(ctx, comparativoId, dto) {
const comparativo = await this.findById(ctx, comparativoId);
if (!comparativo) {
throw new Error('Comparativo not found');
}
if (comparativo.status !== 'draft' && comparativo.status !== 'in_evaluation') {
throw new Error('Cannot add suppliers to approved or cancelled comparisons');
}
const proveedor = this.proveedorRepository.create({
tenantId: ctx.tenantId,
createdById: ctx.userId,
comparativoId,
supplierId: dto.supplierId,
quotationNumber: dto.quotationNumber,
quotationDate: dto.quotationDate,
deliveryDays: dto.deliveryDays,
paymentConditions: dto.paymentConditions,
evaluationNotes: dto.evaluationNotes,
});
return this.proveedorRepository.save(proveedor);
}
async addProducto(ctx, proveedorId, dto) {
const proveedor = await this.proveedorRepository.findOne({
where: { id: proveedorId, tenantId: ctx.tenantId },
relations: ['comparativo'],
});
if (!proveedor) {
throw new Error('Supplier entry not found');
}
if (proveedor.comparativo.status !== 'draft' && proveedor.comparativo.status !== 'in_evaluation') {
throw new Error('Cannot add products to approved or cancelled comparisons');
}
const producto = this.productoRepository.create({
tenantId: ctx.tenantId,
createdById: ctx.userId,
comparativoProveedorId: proveedorId,
productId: dto.productId,
quantity: dto.quantity,
unitPrice: dto.unitPrice,
notes: dto.notes,
});
return this.productoRepository.save(producto);
}
async startEvaluation(ctx, id) {
const comparativo = await this.findWithDetails(ctx, id);
if (!comparativo) {
return null;
}
if (comparativo.status !== 'draft') {
throw new Error('Can only start evaluation on draft comparisons');
}
if (!comparativo.proveedores || comparativo.proveedores.length < 2) {
throw new Error('Need at least 2 suppliers to start evaluation');
}
comparativo.status = 'in_evaluation';
return this.comparativoRepository.save(comparativo);
}
async selectWinner(ctx, id, supplierId) {
const comparativo = await this.findWithDetails(ctx, id);
if (!comparativo) {
return null;
}
if (comparativo.status !== 'in_evaluation') {
throw new Error('Can only select winner during evaluation');
}
// Verify supplier is in the comparison
const proveedor = comparativo.proveedores?.find((p) => p.supplierId === supplierId);
if (!proveedor) {
throw new Error('Supplier not found in this comparison');
}
// Mark all as not selected, then mark winner
for (const p of comparativo.proveedores || []) {
p.isSelected = p.supplierId === supplierId;
await this.proveedorRepository.save(p);
}
comparativo.winnerSupplierId = supplierId;
comparativo.status = 'approved';
comparativo.approvedById = ctx.userId || '';
comparativo.approvedAt = new Date();
return this.comparativoRepository.save(comparativo);
}
async cancel(ctx, id) {
const comparativo = await this.findById(ctx, id);
if (!comparativo) {
return null;
}
if (comparativo.status === 'approved') {
throw new Error('Cannot cancel approved comparisons');
}
comparativo.status = 'cancelled';
return this.comparativoRepository.save(comparativo);
}
async updateProveedorTotal(ctx, proveedorId) {
const proveedor = await this.proveedorRepository.findOne({
where: { id: proveedorId, tenantId: ctx.tenantId },
relations: ['productos'],
});
if (!proveedor) {
return null;
}
const total = proveedor.productos?.reduce((sum, p) => sum + (p.quantity * p.unitPrice), 0) || 0;
proveedor.totalAmount = total;
return this.proveedorRepository.save(proveedor);
}
async softDelete(ctx, id) {
const comparativo = await this.findById(ctx, id);
if (!comparativo) {
return false;
}
if (comparativo.status === 'approved') {
throw new Error('Cannot delete approved comparisons');
}
await this.comparativoRepository.update({ id, tenantId: ctx.tenantId }, { deletedAt: new Date(), deletedById: ctx.userId });
return true;
}
}
exports.ComparativoService = ComparativoService;
//# sourceMappingURL=comparativo.service.js.map