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
173 lines
6.0 KiB
TypeScript
173 lines
6.0 KiB
TypeScript
import { INestApplication } from "@nestjs/common";
|
|
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
import { agent, bearer, closeTestApp, createTestApp, loginSuperAdmin, type AuthedTokens } from "./setup.js";
|
|
|
|
/**
|
|
* `renderPdf` is mocked (real call path through quotes.service.ts still
|
|
* runs) — what's under test is the ANATEL/RGC annex's computed content
|
|
* (anatel-offer-template.ts), not Chromium itself (covered by the
|
|
* documents-pdf.spec.ts suite + the manual smoke test in progress.md).
|
|
* Business-critical regulatory content: this asserts the actual HTML
|
|
* handed to the renderer contains the right computed fields, not just
|
|
* that the endpoint returns 200.
|
|
*/
|
|
const renderPdfMock = vi.fn(async (_input: { html: string }) => Buffer.from("%PDF-1.4 fake anatel pdf"));
|
|
vi.mock("@eden/integrations", () => ({
|
|
renderPdf: (input: { html: string }) => renderPdfMock(input),
|
|
}));
|
|
|
|
describe("quotes — anexo de conformidade ANATEL/RGC", () => {
|
|
let app: INestApplication;
|
|
let admin: AuthedTokens;
|
|
let resellerId: string;
|
|
let stfcProductId: string;
|
|
let pabxProductId: string;
|
|
|
|
beforeAll(async () => {
|
|
app = await createTestApp();
|
|
admin = await loginSuperAdmin(app);
|
|
|
|
const reseller = await agent(app)
|
|
.post("/api/v1/resellers")
|
|
.set(...bearer(admin.accessToken))
|
|
.send({ name: "qa-test- Revenda ANATEL" })
|
|
.expect(201);
|
|
resellerId = reseller.body.id;
|
|
|
|
const stfcProduct = await agent(app)
|
|
.post("/api/v1/products")
|
|
.set(...bearer(admin.accessToken))
|
|
.send({
|
|
name: "qa-test- Linha STFC",
|
|
productType: "service",
|
|
price0: 200,
|
|
price12: 150,
|
|
serviceType: "STFC",
|
|
metered: true,
|
|
minutesAllowance: 100,
|
|
hasLdi: true,
|
|
tariffRates: {
|
|
LC: { normal: 0.1, reduced: 0.1 },
|
|
LDN: { normal: 0.2, reduced: 0.2 },
|
|
VC1: { normal: 0.15, reduced: 0.15 },
|
|
VC2: { normal: 0.25, reduced: 0.25 },
|
|
VC3: { normal: 0.3, reduced: 0.3 },
|
|
LDI: { normal: 1.5, reduced: 1.5 },
|
|
},
|
|
})
|
|
.expect(201);
|
|
stfcProductId = stfcProduct.body.id;
|
|
expect(stfcProduct.body.service_type).toBe("STFC");
|
|
expect(stfcProduct.body.tariff_rates.LC).toEqual({ normal: 0.1, reduced: 0.1 });
|
|
|
|
const pabxProduct = await agent(app)
|
|
.post("/api/v1/products")
|
|
.set(...bearer(admin.accessToken))
|
|
.send({ name: "qa-test- Ramal PABX", productType: "service", price0: 50, serviceType: "Serviço PABX" })
|
|
.expect(201);
|
|
pabxProductId = pabxProduct.body.id;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await agent(app)
|
|
.patch(`/api/v1/products/${stfcProductId}`)
|
|
.set(...bearer(admin.accessToken))
|
|
.send({ active: false })
|
|
.expect(200);
|
|
await agent(app)
|
|
.patch(`/api/v1/products/${pabxProductId}`)
|
|
.set(...bearer(admin.accessToken))
|
|
.send({ active: false })
|
|
.expect(200);
|
|
await agent(app)
|
|
.patch(`/api/v1/resellers/${resellerId}`)
|
|
.set(...bearer(admin.accessToken))
|
|
.send({ active: false })
|
|
.expect(200);
|
|
await closeTestApp(app);
|
|
});
|
|
|
|
it("snapshots service_type/metered/tariffRates onto the quote item at creation", async () => {
|
|
const quote = await agent(app)
|
|
.post("/api/v1/quotes")
|
|
.set(...bearer(admin.accessToken))
|
|
.send({
|
|
resellerId,
|
|
clientName: "qa-test Cliente ANATEL",
|
|
clientPhone: "11999998888",
|
|
contractPeriod: 12,
|
|
items: [{ productId: stfcProductId, quantity: 2 }],
|
|
salesRepName: "qa-test Vendedor",
|
|
})
|
|
.expect(201);
|
|
|
|
const item = quote.body.items[0];
|
|
expect(item.serviceType).toBe("STFC");
|
|
expect(item.metered).toBe(true);
|
|
expect(item.hasLdi).toBe(true);
|
|
expect(item.tariffRates.LC).toEqual({ normal: 0.1, reduced: 0.1 });
|
|
expect(item.minutesAllowance).toBe(100);
|
|
});
|
|
|
|
it("renders the RGC annex with the right computed fields for an STFC quote", async () => {
|
|
renderPdfMock.mockClear();
|
|
|
|
const quote = await agent(app)
|
|
.post("/api/v1/quotes")
|
|
.set(...bearer(admin.accessToken))
|
|
.send({
|
|
resellerId,
|
|
clientName: "qa-test Cliente ANATEL 2",
|
|
clientPhone: "11999998888",
|
|
contractPeriod: 12,
|
|
items: [{ productId: stfcProductId, quantity: 1 }],
|
|
salesRepName: "qa-test Vendedor",
|
|
})
|
|
.expect(201);
|
|
|
|
const download = await agent(app)
|
|
.get(`/api/v1/quotes/${quote.body.id}/anatel-pdf`)
|
|
.set(...bearer(admin.accessToken))
|
|
.expect(200);
|
|
|
|
expect(download.headers["content-type"]).toBe("application/pdf");
|
|
expect(renderPdfMock).toHaveBeenCalledTimes(1);
|
|
const html = renderPdfMock.mock.calls[0]![0].html;
|
|
|
|
// Legal-basis section only renders because the quote has an STFC item.
|
|
expect(html).toContain("Conformidade RGC — Resolução Anatel nº 765/2023");
|
|
expect(html).toContain("Prazo de Comercialização");
|
|
expect(html).toContain(quote.body.quote_number);
|
|
expect(html).toContain("qa-test Cliente ANATEL 2");
|
|
expect(html).toContain("qa-test Vendedor");
|
|
// Tariff section, since the item is metered.
|
|
expect(html).toContain("TABELA DE TARIFAÇÃO");
|
|
expect(html).toContain("LDI");
|
|
expect(html).toContain("A tecnologia utilizada para entrega das chamadas do serviço STFC ofertado é VoIP.");
|
|
// Fixed regulatory fields.
|
|
expect(html).toContain("Pós-paga");
|
|
expect(html).toContain("Nacional");
|
|
expect(html).toContain("Intervalo mínimo de 12 meses");
|
|
});
|
|
|
|
it("rejects (400) a quote with no STFC item", async () => {
|
|
const quote = await agent(app)
|
|
.post("/api/v1/quotes")
|
|
.set(...bearer(admin.accessToken))
|
|
.send({
|
|
resellerId,
|
|
clientName: "qa-test Cliente Sem STFC",
|
|
clientPhone: "11999998888",
|
|
contractPeriod: 0,
|
|
items: [{ productId: pabxProductId, quantity: 1 }],
|
|
})
|
|
.expect(201);
|
|
|
|
const res = await agent(app)
|
|
.get(`/api/v1/quotes/${quote.body.id}/anatel-pdf`)
|
|
.set(...bearer(admin.accessToken))
|
|
.expect(400);
|
|
expect(res.body.message).toContain("STFC");
|
|
});
|
|
});
|