187 lines
6.7 KiB
JavaScript
187 lines
6.7 KiB
JavaScript
"use strict";
|
|
/**
|
|
* SubcontractorService - Servicio de gestión de subcontratistas
|
|
*
|
|
* Catálogo de subcontratistas con evaluaciones.
|
|
*
|
|
* @module Contracts
|
|
*/
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.SubcontractorService = void 0;
|
|
class SubcontractorService {
|
|
subcontractorRepository;
|
|
constructor(subcontractorRepository) {
|
|
this.subcontractorRepository = subcontractorRepository;
|
|
}
|
|
generateCode() {
|
|
const now = new Date();
|
|
const year = now.getFullYear().toString().slice(-2);
|
|
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
return `SC-${year}-${random}`;
|
|
}
|
|
async findWithFilters(ctx, filters = {}, page = 1, limit = 20) {
|
|
const skip = (page - 1) * limit;
|
|
const queryBuilder = this.subcontractorRepository
|
|
.createQueryBuilder('sc')
|
|
.leftJoinAndSelect('sc.createdBy', 'createdBy')
|
|
.where('sc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
|
|
.andWhere('sc.deleted_at IS NULL');
|
|
if (filters.specialty) {
|
|
queryBuilder.andWhere('sc.primary_specialty = :specialty', { specialty: filters.specialty });
|
|
}
|
|
if (filters.status) {
|
|
queryBuilder.andWhere('sc.status = :status', { status: filters.status });
|
|
}
|
|
if (filters.search) {
|
|
queryBuilder.andWhere('(sc.business_name ILIKE :search OR sc.trade_name ILIKE :search OR sc.rfc ILIKE :search)', { search: `%${filters.search}%` });
|
|
}
|
|
if (filters.minRating !== undefined) {
|
|
queryBuilder.andWhere('sc.average_rating >= :minRating', { minRating: filters.minRating });
|
|
}
|
|
queryBuilder
|
|
.orderBy('sc.business_name', 'ASC')
|
|
.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.subcontractorRepository.findOne({
|
|
where: {
|
|
id,
|
|
tenantId: ctx.tenantId,
|
|
deletedAt: null,
|
|
},
|
|
});
|
|
}
|
|
async findByRfc(ctx, rfc) {
|
|
return this.subcontractorRepository.findOne({
|
|
where: {
|
|
rfc: rfc.toUpperCase(),
|
|
tenantId: ctx.tenantId,
|
|
deletedAt: null,
|
|
},
|
|
});
|
|
}
|
|
async create(ctx, dto) {
|
|
// Check for existing RFC
|
|
const existing = await this.findByRfc(ctx, dto.rfc);
|
|
if (existing) {
|
|
throw new Error('A subcontractor with this RFC already exists');
|
|
}
|
|
const subcontractor = this.subcontractorRepository.create({
|
|
tenantId: ctx.tenantId,
|
|
createdById: ctx.userId,
|
|
code: this.generateCode(),
|
|
businessName: dto.businessName,
|
|
tradeName: dto.tradeName,
|
|
rfc: dto.rfc.toUpperCase(),
|
|
address: dto.address,
|
|
phone: dto.phone,
|
|
email: dto.email,
|
|
contactName: dto.contactName,
|
|
contactPhone: dto.contactPhone,
|
|
primarySpecialty: dto.primarySpecialty,
|
|
secondarySpecialties: dto.secondarySpecialties,
|
|
bankName: dto.bankName,
|
|
bankAccount: dto.bankAccount,
|
|
clabe: dto.clabe,
|
|
notes: dto.notes,
|
|
status: 'active',
|
|
});
|
|
return this.subcontractorRepository.save(subcontractor);
|
|
}
|
|
async update(ctx, id, dto) {
|
|
const subcontractor = await this.findById(ctx, id);
|
|
if (!subcontractor) {
|
|
return null;
|
|
}
|
|
Object.assign(subcontractor, {
|
|
...dto,
|
|
updatedById: ctx.userId || '',
|
|
});
|
|
return this.subcontractorRepository.save(subcontractor);
|
|
}
|
|
async updateRating(ctx, id, rating) {
|
|
const subcontractor = await this.findById(ctx, id);
|
|
if (!subcontractor) {
|
|
return null;
|
|
}
|
|
// Calculate new average rating
|
|
const totalRatings = subcontractor.completedContracts;
|
|
const currentTotal = subcontractor.averageRating * totalRatings;
|
|
const newTotal = currentTotal + rating;
|
|
subcontractor.averageRating = newTotal / (totalRatings + 1);
|
|
subcontractor.updatedById = ctx.userId || '';
|
|
return this.subcontractorRepository.save(subcontractor);
|
|
}
|
|
async incrementContracts(ctx, id, completed = false) {
|
|
const subcontractor = await this.findById(ctx, id);
|
|
if (!subcontractor) {
|
|
return null;
|
|
}
|
|
subcontractor.totalContracts += 1;
|
|
if (completed) {
|
|
subcontractor.completedContracts += 1;
|
|
}
|
|
subcontractor.updatedById = ctx.userId || '';
|
|
return this.subcontractorRepository.save(subcontractor);
|
|
}
|
|
async incrementIncidents(ctx, id) {
|
|
const subcontractor = await this.findById(ctx, id);
|
|
if (!subcontractor) {
|
|
return null;
|
|
}
|
|
subcontractor.totalIncidents += 1;
|
|
subcontractor.updatedById = ctx.userId || '';
|
|
return this.subcontractorRepository.save(subcontractor);
|
|
}
|
|
async deactivate(ctx, id) {
|
|
const subcontractor = await this.findById(ctx, id);
|
|
if (!subcontractor) {
|
|
return null;
|
|
}
|
|
subcontractor.status = 'inactive';
|
|
subcontractor.updatedById = ctx.userId || '';
|
|
return this.subcontractorRepository.save(subcontractor);
|
|
}
|
|
async blacklist(ctx, id, reason) {
|
|
const subcontractor = await this.findById(ctx, id);
|
|
if (!subcontractor) {
|
|
return null;
|
|
}
|
|
subcontractor.status = 'blacklisted';
|
|
subcontractor.notes = `${subcontractor.notes || ''}\n[BLACKLISTED] ${reason}`;
|
|
subcontractor.updatedById = ctx.userId || '';
|
|
return this.subcontractorRepository.save(subcontractor);
|
|
}
|
|
async softDelete(ctx, id) {
|
|
const subcontractor = await this.findById(ctx, id);
|
|
if (!subcontractor) {
|
|
return false;
|
|
}
|
|
await this.subcontractorRepository.update({ id, tenantId: ctx.tenantId }, { deletedAt: new Date(), deletedById: ctx.userId || '' });
|
|
return true;
|
|
}
|
|
async getBySpecialty(ctx, specialty) {
|
|
return this.subcontractorRepository.find({
|
|
where: {
|
|
tenantId: ctx.tenantId,
|
|
primarySpecialty: specialty,
|
|
status: 'active',
|
|
deletedAt: null,
|
|
},
|
|
order: { averageRating: 'DESC' },
|
|
});
|
|
}
|
|
}
|
|
exports.SubcontractorService = SubcontractorService;
|
|
//# sourceMappingURL=subcontractor.service.js.map
|