erp-construccion-backend/dist/modules/quality/services/inspection.service.js

243 lines
9.4 KiB
JavaScript

"use strict";
/**
* InspectionService - Servicio de inspecciones de calidad
*
* Gestión de inspecciones con checklists y resultados.
*
* @module Quality
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.InspectionService = void 0;
class InspectionService {
inspectionRepository;
resultRepository;
nonConformityRepository;
checklistRepository;
constructor(inspectionRepository, resultRepository, nonConformityRepository, checklistRepository) {
this.inspectionRepository = inspectionRepository;
this.resultRepository = resultRepository;
this.nonConformityRepository = nonConformityRepository;
this.checklistRepository = checklistRepository;
}
generateInspectionNumber() {
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, 8).toUpperCase();
return `INS-${year}${month}-${random}`;
}
generateNCNumber() {
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 `NC-${year}${month}-${random}`;
}
async findWithFilters(ctx, filters = {}, page = 1, limit = 20) {
const skip = (page - 1) * limit;
const queryBuilder = this.inspectionRepository
.createQueryBuilder('insp')
.leftJoinAndSelect('insp.checklist', 'checklist')
.leftJoinAndSelect('insp.inspector', 'inspector')
.where('insp.tenant_id = :tenantId', { tenantId: ctx.tenantId });
if (filters.checklistId) {
queryBuilder.andWhere('insp.checklist_id = :checklistId', { checklistId: filters.checklistId });
}
if (filters.loteId) {
queryBuilder.andWhere('insp.lote_id = :loteId', { loteId: filters.loteId });
}
if (filters.inspectorId) {
queryBuilder.andWhere('insp.inspector_id = :inspectorId', { inspectorId: filters.inspectorId });
}
if (filters.status) {
queryBuilder.andWhere('insp.status = :status', { status: filters.status });
}
if (filters.dateFrom) {
queryBuilder.andWhere('insp.inspection_date >= :dateFrom', { dateFrom: filters.dateFrom });
}
if (filters.dateTo) {
queryBuilder.andWhere('insp.inspection_date <= :dateTo', { dateTo: filters.dateTo });
}
queryBuilder
.orderBy('insp.inspection_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.inspectionRepository.findOne({
where: {
id,
tenantId: ctx.tenantId,
},
});
}
async findWithDetails(ctx, id) {
return this.inspectionRepository.findOne({
where: {
id,
tenantId: ctx.tenantId,
},
relations: ['checklist', 'checklist.items', 'inspector', 'results', 'results.checklistItem', 'nonConformities'],
});
}
async create(ctx, dto) {
// Validate checklist exists
const checklist = await this.checklistRepository.findOne({
where: {
id: dto.checklistId,
tenantId: ctx.tenantId,
isActive: true,
deletedAt: null,
},
relations: ['items'],
});
if (!checklist) {
throw new Error('Checklist not found or inactive');
}
const inspection = this.inspectionRepository.create({
tenantId: ctx.tenantId,
createdById: ctx.userId,
checklistId: dto.checklistId,
loteId: dto.loteId,
inspectionNumber: this.generateInspectionNumber(),
inspectionDate: dto.inspectionDate,
inspectorId: dto.inspectorId,
notes: dto.notes,
status: 'pending',
totalItems: checklist.items?.filter(i => i.isActive).length || 0,
});
return this.inspectionRepository.save(inspection);
}
async startInspection(ctx, id) {
const inspection = await this.findById(ctx, id);
if (!inspection) {
return null;
}
if (inspection.status !== 'pending') {
throw new Error('Can only start pending inspections');
}
inspection.status = 'in_progress';
inspection.updatedById = ctx.userId || '';
return this.inspectionRepository.save(inspection);
}
async recordResult(ctx, inspectionId, dto) {
const inspection = await this.findById(ctx, inspectionId);
if (!inspection) {
throw new Error('Inspection not found');
}
if (inspection.status !== 'in_progress') {
throw new Error('Can only record results for in-progress inspections');
}
// Check if result already exists
let result = await this.resultRepository.findOne({
where: {
inspectionId,
checklistItemId: dto.checklistItemId,
tenantId: ctx.tenantId,
},
});
if (result) {
// Update existing
result.result = dto.result;
result.observations = dto.observations || result.observations;
result.photoUrl = dto.photoUrl || result.photoUrl;
result.inspectedAt = new Date();
}
else {
// Create new
result = this.resultRepository.create({
tenantId: ctx.tenantId,
inspectionId,
checklistItemId: dto.checklistItemId,
result: dto.result,
observations: dto.observations,
photoUrl: dto.photoUrl,
inspectedAt: new Date(),
});
}
const savedResult = await this.resultRepository.save(result);
// If failed, create non-conformity automatically
if (dto.result === 'failed') {
const nc = this.nonConformityRepository.create({
tenantId: ctx.tenantId,
createdById: ctx.userId,
inspectionId,
loteId: inspection.loteId,
ncNumber: this.generateNCNumber(),
detectionDate: new Date(),
category: 'Inspection Finding',
severity: 'minor',
description: dto.observations || 'Failed inspection item',
photoUrl: dto.photoUrl,
status: 'open',
});
await this.nonConformityRepository.save(nc);
}
return savedResult;
}
async completeInspection(ctx, id) {
const inspection = await this.findWithDetails(ctx, id);
if (!inspection) {
return null;
}
if (inspection.status !== 'in_progress') {
throw new Error('Can only complete in-progress inspections');
}
// Calculate pass/fail counts
const results = inspection.results || [];
const passed = results.filter(r => r.result === 'passed').length;
const failed = results.filter(r => r.result === 'failed').length;
const total = results.filter(r => r.result !== 'not_applicable').length;
inspection.passedItems = passed;
inspection.failedItems = failed;
inspection.passRate = total > 0 ? (passed / total) * 100 : 0;
inspection.status = 'completed';
inspection.completedAt = new Date();
inspection.updatedById = ctx.userId || '';
return this.inspectionRepository.save(inspection);
}
async approveInspection(ctx, id) {
const inspection = await this.findWithDetails(ctx, id);
if (!inspection) {
return null;
}
if (inspection.status !== 'completed') {
throw new Error('Can only approve completed inspections');
}
// Check for open critical non-conformities
const criticalNCs = inspection.nonConformities?.filter(nc => nc.severity === 'critical' && nc.status !== 'verified');
if (criticalNCs && criticalNCs.length > 0) {
throw new Error('Cannot approve inspection with open critical non-conformities');
}
inspection.status = 'approved';
inspection.approvedById = ctx.userId || '';
inspection.approvedAt = new Date();
inspection.updatedById = ctx.userId || '';
return this.inspectionRepository.save(inspection);
}
async rejectInspection(ctx, id, reason) {
const inspection = await this.findById(ctx, id);
if (!inspection) {
return null;
}
if (inspection.status !== 'completed') {
throw new Error('Can only reject completed inspections');
}
inspection.status = 'rejected';
inspection.rejectionReason = reason;
inspection.updatedById = ctx.userId || '';
return this.inspectionRepository.save(inspection);
}
}
exports.InspectionService = InspectionService;
//# sourceMappingURL=inspection.service.js.map