documents/src/modules/catalog/catalog.service.ts
Breno Pires 9165f1e9ab feat: microservico documents — catalogo por UF, documentos com versoes, compliance e analise [skip ci]
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>
2026-08-07 01:42:24 -03:00

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 ?? [],
};
}
}