Espelho do routines: NestJS 10 + Fastify + TypeORM, modulos catalog (override por UF), document (versoes + storage GCS), compliance (mesmo score do painel), alerts (regua de vencimento, envio e v1.2) e analise plugavel (KeywordAnalyzer -> OCR/LLM). 38 testes. Deploy aguarda banco 'documents' e secrets no hml2 — por isso o [skip ci] no bootstrap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
88 lines
2.9 KiB
TypeScript
88 lines
2.9 KiB
TypeScript
import { MappedException } from '@clubpetrodev/nestjs-mapped-exception';
|
|
import { Inject, Injectable } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { In, IsNull, Repository } from 'typeorm';
|
|
|
|
import { DocumentTypeException } from './catalog.exception';
|
|
import { DocumentType } from './documentType.entity';
|
|
import { DocumentTypeUfOverride } from './documentTypeUfOverride.entity';
|
|
import { DocumentTypeResponseDto } from './dto/response/documentTypeResponse.dto';
|
|
|
|
@Injectable()
|
|
export class CatalogService {
|
|
constructor(
|
|
@InjectRepository(DocumentType)
|
|
private readonly typeRepository: Repository<DocumentType>,
|
|
@InjectRepository(DocumentTypeUfOverride)
|
|
private readonly overrideRepository: Repository<DocumentTypeUfOverride>,
|
|
@Inject(DocumentTypeException)
|
|
private readonly exception: MappedException<DocumentTypeException>,
|
|
) {}
|
|
|
|
/**
|
|
* Catálogo visível: tipos globais + os do programa, com a variação estadual
|
|
* aplicada quando `uf` vem na chamada. O painel não sabe que overrides
|
|
* existem — recebe o valor certo para a loja e pronto.
|
|
*/
|
|
async getAll(filters: {
|
|
programId?: string;
|
|
uf?: string;
|
|
}): Promise<DocumentTypeResponseDto[]> {
|
|
const query = this.typeRepository
|
|
.createQueryBuilder('type')
|
|
.where('type.active = true')
|
|
.andWhere(
|
|
'(type."programId" IS NULL OR type."programId" = :programId)',
|
|
{ programId: filters.programId ?? null },
|
|
)
|
|
.orderBy('type."sortOrder"', 'ASC')
|
|
.addOrderBy('type.name', 'ASC');
|
|
|
|
const types = await query.getMany();
|
|
|
|
const overrides = filters.uf
|
|
? await this.overrideRepository.find({
|
|
where: {
|
|
uf: filters.uf.toUpperCase(),
|
|
typeCode: In(types.map(type => type.code)),
|
|
},
|
|
})
|
|
: [];
|
|
|
|
return types.map(type => this.applyOverride(type, overrides));
|
|
}
|
|
|
|
async getByCode(code: string): Promise<DocumentType> {
|
|
const type = await this.typeRepository.findOne({
|
|
where: { code, programId: IsNull() },
|
|
});
|
|
if (!type) this.exception.ERRORS.TYPE_NOT_FOUND.throw();
|
|
return type!;
|
|
}
|
|
|
|
async getActiveTypes(): Promise<DocumentType[]> {
|
|
return this.typeRepository.find({ where: { active: true } });
|
|
}
|
|
|
|
applyOverride(
|
|
type: DocumentType,
|
|
overrides: DocumentTypeUfOverride[],
|
|
): DocumentTypeResponseDto {
|
|
const override = overrides.find(item => item.typeCode === type.code);
|
|
|
|
return {
|
|
code: type.code,
|
|
name: type.name,
|
|
issuingBody: type.issuingBody,
|
|
category: type.category,
|
|
criticality: type.criticality,
|
|
legalBasis: type.legalBasis,
|
|
description: type.description,
|
|
defaultValidityMonths:
|
|
override?.validityMonths ?? type.defaultValidityMonths ?? null,
|
|
renewalLeadDays: override?.renewalLeadDays ?? type.renewalLeadDays,
|
|
keywords: type.keywords ?? [],
|
|
};
|
|
}
|
|
}
|