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:
@@ -1,2 +1,12 @@
|
||||
export const DOMAIN_SHARED_PACKAGE_VERSION = "0.1.0";
|
||||
export { onlyDigits, isValidCPF, isValidCNPJ } from "./validators/br-documents.js";
|
||||
export { onlyDigits, isValidCPF, isValidCNPJ, formatCPF, formatCNPJ } from "./validators/br-documents.js";
|
||||
export {
|
||||
SERVICE_TYPES,
|
||||
EXCLUSIVE_SERVICE_TYPE,
|
||||
TARIFF_TYPES,
|
||||
TARIFF_DESCRIPTIONS,
|
||||
TARIFF_EXTRA_NOTES,
|
||||
emptyTariffRates,
|
||||
getTariffTypesToShow,
|
||||
} from "./telecom/service-types.js";
|
||||
export type { ServiceType, TariffType, TariffRate, TariffRates } from "./telecom/service-types.js";
|
||||
|
||||
56
packages/domain-shared/src/telecom/service-types.ts
Normal file
56
packages/domain-shared/src/telecom/service-types.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Recorte regulatório RGC (Resolução Anatel nº 765/2023) — ported
|
||||
* verbatim from the legacy's src/lib/serviceTypes.js e src/lib/
|
||||
* tariffTypes.js. Compartilhado entre packages/database (tipagem de
|
||||
* products.service_type/tariff_rates), apps/api (validação de DTO +
|
||||
* template do PDF de oferta ANATEL) e apps/core-web (formulário de
|
||||
* produto).
|
||||
*/
|
||||
|
||||
/** Únicos 4 valores válidos no legado — sem CHECK adicional além destes. */
|
||||
export const SERVICE_TYPES = ["STFC", "Serviço PABX", "SVA (locação)", "Serviço software"] as const;
|
||||
export type ServiceType = (typeof SERVICE_TYPES)[number];
|
||||
|
||||
/** Usado para separar itens de software (SaaS) do restante de uma oferta — eden.md/legado. */
|
||||
export const EXCLUSIVE_SERVICE_TYPE: ServiceType = "Serviço software";
|
||||
|
||||
export const TARIFF_TYPES = ["LC", "LDN", "VC1", "VC2", "VC3", "LDI"] as const;
|
||||
export type TariffType = (typeof TARIFF_TYPES)[number];
|
||||
|
||||
export interface TariffRate {
|
||||
normal: number;
|
||||
reduced: number;
|
||||
}
|
||||
|
||||
export type TariffRates = Record<TariffType, TariffRate>;
|
||||
|
||||
export function emptyTariffRates(): TariffRates {
|
||||
return {
|
||||
LC: { normal: 0, reduced: 0 },
|
||||
LDN: { normal: 0, reduced: 0 },
|
||||
VC1: { normal: 0, reduced: 0 },
|
||||
VC2: { normal: 0, reduced: 0 },
|
||||
VC3: { normal: 0, reduced: 0 },
|
||||
LDI: { normal: 0, reduced: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
/** LDI sempre existe no objeto (emptyTariffRates), mas só é EXIBIDO quando has_ldi=true. */
|
||||
export function getTariffTypesToShow(hasLdi: boolean): readonly TariffType[] {
|
||||
return hasLdi ? TARIFF_TYPES : TARIFF_TYPES.filter((t) => t !== "LDI");
|
||||
}
|
||||
|
||||
/** Texto de legenda, verbatim do legado (src/lib/tariffTypes.js) — conteúdo regulatório, não parafrasear. */
|
||||
export const TARIFF_DESCRIPTIONS: Record<TariffType, string> = {
|
||||
LC: "Chamadas entre linhas fixas locais que estiverem na mesma área local (mesmo DDD).",
|
||||
LDN: "Chamadas entre linhas fixas de longa distância que estiverem em áreas locais diferentes (DDD diferente).",
|
||||
VC1: "Chamadas para linhas móveis que estiverem na mesma área local (mesmo DDD).",
|
||||
VC2: "Chamadas para linhas móveis que estiverem em áreas locais diferentes, sendo o primeiro dígito do DDD dos dois telefones iguais e o segundo dígito diferente.",
|
||||
VC3: "Chamadas para linhas móveis que estiverem em áreas locais diferentes, sendo o primeiro dígito do DDD dos dois telefones diferentes.",
|
||||
LDI: "Chamadas de longa distância internacional.",
|
||||
};
|
||||
|
||||
export const TARIFF_EXTRA_NOTES: readonly string[] = [
|
||||
"Normal / Reduzido: as tarifas desta OFERTA são idênticas nos horários normal e reduzido.",
|
||||
"Modalidade: Pós-pago.",
|
||||
];
|
||||
@@ -48,3 +48,17 @@ export function isValidCNPJ(value: string): boolean {
|
||||
checkDigit(12, WEIGHTS_12) === digits[12] && checkDigit(13, WEIGHTS_13) === digits[13]
|
||||
);
|
||||
}
|
||||
|
||||
/** Display formatting only (000.000.000-00) — doesn't validate. Returns the raw input if it isn't 11 digits. */
|
||||
export function formatCPF(value: string): string {
|
||||
const digits = onlyDigits(value);
|
||||
if (digits.length !== 11) return value;
|
||||
return digits.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4");
|
||||
}
|
||||
|
||||
/** Display formatting only (00.000.000/0000-00) — doesn't validate. Returns the raw input if it isn't 14 digits. */
|
||||
export function formatCNPJ(value: string): string {
|
||||
const digits = onlyDigits(value);
|
||||
if (digits.length !== 14) return value;
|
||||
return digits.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, "$1.$2.$3/$4-$5");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user