feat(quotes): anexo de conformidade ANATEL/RGC no PDF de oferta

Porta o anexo regulatório do legado (Resolução Anatel 765/2023, RGC,
recorte STFC) com fidelidade total ao conteúdo legal, incluindo a
seção de tarifação — em vez do html2canvas+jsPDF client-side do
legado, usa o Chromium server-side já construído para Documents.

Schema: products ganha service_type/metered/tariff_rates/has_ldi
(migration 1788525270647) — campos que a migration original de
products tinha deliberadamente deferido. Snapshot no item da oferta
na criação (nunca referência viva ao produto). Novo pacote
compartilhado packages/domain-shared/src/telecom para o catálogo de
tipos e legendas de tarifação.

Endpoint GET /v1/quotes/:id/anatel-pdf (400 sem item STFC), sempre
renderizado ao vivo (nunca cacheado, já que o conteúdo deriva de
updated_at). Frontend: campos de produto/oferta necessários +
"Baixar PDF ANATEL" na tela de detalhe da oferta. Inclui também o
botão de download de PDF em Documentos (endpoint já existia, faltava
o link na UI).

Verificado ao vivo através do container real: PDF gerado com
Chromium de verdade, texto extraído confere byte a byte com os
valores computados (multa, datas, tarifas, resumo financeiro).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcWWuKfykGtUweXmHD6cKx
This commit is contained in:
2026-09-04 10:33:33 -03:00
parent 29430d0d96
commit ccac936372
26 changed files with 1321 additions and 20 deletions

View File

@@ -1,4 +1,5 @@
import { Body, Controller, Get, Param, Patch, Post, Query, UseGuards } from "@nestjs/common";
import { Body, Controller, Get, Header, Param, Patch, Post, Query, Res, StreamableFile, UseGuards } from "@nestjs/common";
import type { Response } from "express";
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
import { QuotesService } from "./quotes.service.js";
import { CreateQuoteDto } from "./dto/create-quote.dto.js";
@@ -43,6 +44,20 @@ export class QuotesController {
return this.quotes.create(dto, user);
}
/** Anexo de conformidade ANATEL/RGC (Resolução 765/2023) — 400 se a oferta não tiver item STFC. Renderizado ao vivo, nunca cacheado (ver doc comment em QuotesService.generateAnatelPdf). */
@Get(":id/anatel-pdf")
@RequirePermission("quotes", "view")
@Header("Content-Type", "application/pdf")
async downloadAnatelPdf(
@Param("id") id: string,
@CurrentUser() user: EdenAuthenticatedUser,
@Res({ passthrough: true }) res: Response,
) {
const pdf = await this.quotes.generateAnatelPdf(id, user);
res.set({ "Content-Disposition": `inline; filename="anatel-${id}.pdf"` });
return new StreamableFile(pdf);
}
@Patch(":id")
@RequirePermission("quotes", "edit")
update(