feat(api,ui): MFA/TOTP, Inventory, Support, HR-Timeclock, Fiscal, Billing e Design System
Leva grande, pedida como "pelo menos mais 20 etapas, use subagentes":
fecha a pendência de MFA/TOTP da Fase 1 (feito diretamente, autenticação é
sensível demais para delegar) e lança 5 subagentes em paralelo em 2 ondas
(eden-inventory, eden-support, eden-hr-timeclock; depois eden-finance,
eden-fiscal, eden-frontend), cada um em worktree isolado, com integração
seletiva manual (barrel, app.module.ts, migrations renomeadas/aplicadas,
build/typecheck/teste ao vivo antes de cada merge).
## Backend
- MFA/TOTP (`apps/api/src/auth/mfa.service.ts`, `crypto/field-encryption.service.ts`):
login em duas etapas quando `mfa_enabled=true`, `FieldEncryptionService`
genérico (AES-256-GCM versionado, ADR-0005) reaproveitado por outros
segredos reversíveis (ex. credencial de equipamento Control iD).
- Inventory: warehouses, stock_movements (ledger append-only), serialized_assets,
asset_assignments, stock_reservations — invariante "serial/patrimônio/MAC
nunca duplicado entre ativos ativos" reforçada por índices únicos parciais
no banco (Master Prompt §15.3).
- Support/Service Desk: tickets com máquina de estados, SLA com pausa/retomada
em `waiting_customer`/`waiting_third_party` (funções puras testáveis).
- HR-Timeclock (fundação): dispositivos Control iD (senha cifrada), import de
AFD com parser + CRC-16/KERMIT, `afd_records` imutável sem exceção nem
para super_admin (sem válvula de escape, diferente de audit_log).
- Fiscal: NCM/CFOP/municípios/naturezas de operação (eden.md módulo 5, MVP de
5 das ~19 tabelas), product_fiscal_profiles com invariante de único
profile ativo por billing_component.
- Billing: subscription→billing_run→invoice idempotente (regenerar em draft
substitui, nunca duplica) e imutável após consolidar (ADR-0008: nunca
confunde faturamento/documento fiscal/recebimento).
- FKs cruzadas fechadas: quotes/client_registrations/contracts.
## Frontend — primeira leva
- `packages/ui`: Design System real extraído do tema DreamsERP comprado —
achado que mudou o escopo: o tema já vem em React+Tailwind (não só
Angular), tokens visuais reais portados (cores, Geist como fonte real,
divergindo da doc antiga que citava Nunito/Poppins).
- `apps/core-web`: login real (com suporte a MFA), sessão, shell autenticado
via `AppShell` — substitui a tela estática "Bootstrap da Fase 1".
## Bugs reais encontrados e corrigidos (todos via teste ao vivo, não leitura)
1. **Segurança**: o token de desafio MFA era aceito como access token normal
pelo `JwtAuthGuard` (mesma assinatura, nenhuma checagem da claim
`purpose`) — bypass total do segundo fator. Explorado e fechado ao vivo
contra o container real; teste de regressão em `test/mfa.spec.ts`.
2. Crase solta dentro de comentário SQL na migration de hr-timeclock quebrava
o template literal JS (`SyntaxError` ao rodar a migration).
3. CORS ausente por completo na API.
4. `turbo.json` não declarava `VITE_API_BASE_URL` como input de build — o
Turborepo cacheava o bundle do frontend ignorando mudança na env var.
5. `compose.yaml` usava `${VITE_API_BASE_URL:-/api/v1}`, que colidia com a
variável de mesmo nome do `.env` local — o default nunca era aplicado.
6. Não existia `.dockerignore` — todo build copiava node_modules/.git/
worktrees de subagentes (665MB) para dentro do contexto Docker.
Decisão do operador incorporada: a API nunca tem domínio público próprio
(só IP interno) — os 3 frontends chamam por caminho relativo `/api/v1`,
proxy externo roteia same-origin; CORS só é relevante para dev local.
Verificado: `pnpm turbo run typecheck build` (28/28), `pnpm --filter
@eden/api test` (30/30, cresceu de 15), todo o lote testado ao vivo via
curl contra os containers reais reconstruídos, incluindo os 6 bugs acima
já fechados e confirmados, e a trilha de auditoria conferida no Postgres.
Claude-Session: https://claude.ai/code/session_01BcWWuKfykGtUweXmHD6cKx
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
231
packages/database/migrations/1788457368744_fiscal.cjs
Normal file
231
packages/database/migrations/1788457368744_fiscal.cjs
Normal file
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* Fiscal bounded context (Master Prompt §6.11, ADR-0011, eden.md módulo 5
|
||||
* "Módulo Fiscal (NCM, CFOP, Municípios)"). The legacy OrçaFácil has a
|
||||
* mature ~19-table catalog layer + automatic (Siscomex/IBGE fetch) and
|
||||
* manual (CSV/XLSX/JSON upload) importers + a sync log (`fiscal_catalog_sync`)
|
||||
* + `product_fiscal_profiles` linking `products` to fiscal classification.
|
||||
*
|
||||
* This MVP builds a coherent, faithfully-shaped SUBSET, end-to-end:
|
||||
* - fiscal_ncm (this migration) — "simple catalog" shape, eden.md §2.2
|
||||
* - fiscal_cfop (this migration) — simple catalog + direction/scope, eden.md §2.3/§4
|
||||
* - fiscal_municipality (this migration) — distinct shape, eden.md §2.3
|
||||
* - fiscal_operation_nature (this migration) — internal enum-like catalog, eden.md §2.3, seeded
|
||||
* - product_fiscal_profiles (this migration) — eden.md §2.5, reduced to the
|
||||
* 4 catalogs actually created here (ncm/cfop/municipality/operation_nature)
|
||||
*
|
||||
* Explicitly DEFERRED, not forgotten:
|
||||
* - The other ~9 "simple catalog" tables (eden.md §2.2): fiscal_product_origin,
|
||||
* fiscal_icms_cst, fiscal_icms_csosn, fiscal_pis_cst, fiscal_cofins_cst,
|
||||
* fiscal_ipi_cst, fiscal_lc116_service, fiscal_nbs, fiscal_ibs_cbs_cst,
|
||||
* fiscal_ibs_cbs_credit_class — same shape as fiscal_ncm/fiscal_cfop, can
|
||||
* be added later without a redesign.
|
||||
* - The "extended catalog" tables (eden.md §2.3): fiscal_cest, fiscal_cbenef,
|
||||
* fiscal_nfcom_cclass, fiscal_nfse_trib_nacional, fiscal_nfse_trib_municipal,
|
||||
* fiscal_ibs_cbs_classification, fiscal_ibs_cbs_standard_rate.
|
||||
* - `fiscal_catalog_sync` (append-only sync log, eden.md §2.4) and the
|
||||
* automatic importers (NCM/Siscomex, Municipality/IBGE — eden.md §3/§5)
|
||||
* + the generic manual file importer with dry-run and the mass-
|
||||
* inactivation safety threshold (eden.md §7) — substantial, isolated
|
||||
* work (network fetch adapters, checksum/threshold logic, file parsing),
|
||||
* deferred to its own leva.
|
||||
* - `fiscal_rules` (eden.md §2.6) — schema-ready-but-unconsumed even in the
|
||||
* legacy itself ("estrutura pronta, sem UI/consumo ainda"); not recreated
|
||||
* here, stays future work until the fiscal resolution engine is built.
|
||||
* - `products.ixc_product_code` / `products.product_code` (eden.md §6) —
|
||||
* belong to the Products migration, not this one.
|
||||
* - product_fiscal_profiles columns for catalogs not yet created here:
|
||||
* nfcom_cclass_id, nfse_trib_nacional_id, nfse_trib_municipal_id,
|
||||
* lc116_service_id, nbs_id, cest_id, product_origin_id, ibs_cbs_cst_id,
|
||||
* ibs_cbs_classification_id, ibs_cbs_credit_class_id (eden.md §2.5) —
|
||||
* added when their respective catalogs land.
|
||||
*
|
||||
* `product_fiscal_profiles.product_id` is deliberately nullable-FK-free:
|
||||
* NO `REFERENCES products(id)`, same cross-module "FK cruzada adiada"
|
||||
* pattern used by `contracts.quote_id`/`contracts.client_registration_id`
|
||||
* (see 1788447802216_contracts.cjs header) and
|
||||
* `support_tickets.client_registration_id`/`.contract_id`
|
||||
* (see 1788451374900_support.cjs header) — this worktree may not always
|
||||
* carry every other module's migration in the same merge order; wiring the
|
||||
* FK is integration-step work at merge time, not this migration's.
|
||||
*
|
||||
* Naming convention: all fiscal tables use `created_at`/`updated_at`
|
||||
* (not `created_date`/`updated_date`) — this is already EDEN's own
|
||||
* repo-wide convention (see every other migration in this directory), so
|
||||
* there is no deviation to reconcile here. eden.md §2.1 calls this "a
|
||||
* deliberate deviation" only relative to the *legacy* OrçaFácil schema,
|
||||
* not relative to EDEN.
|
||||
*/
|
||||
|
||||
exports.shorthands = undefined;
|
||||
|
||||
exports.up = (pgm) => {
|
||||
pgm.sql(`
|
||||
-- =====================================================================
|
||||
-- fiscal_ncm — Nomenclatura Comum do Mercosul. "Simple catalog" shape,
|
||||
-- eden.md §2.2. Source: Receita Federal / Siscomex (import deferred).
|
||||
-- =====================================================================
|
||||
CREATE TABLE fiscal_ncm (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
code TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
valid_from DATE,
|
||||
valid_until DATE,
|
||||
active BOOLEAN NOT NULL DEFAULT true,
|
||||
source TEXT,
|
||||
source_version TEXT,
|
||||
source_updated_at TIMESTAMPTZ,
|
||||
metadata JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
COMMENT ON TABLE fiscal_ncm IS
|
||||
'Nomenclatura Comum do Mercosul (eden.md §2.2, §3). "Catálogo simples" — mesmo shape reutilizável para as ~9 outras tabelas de catálogo simples ainda não portadas (fiscal_product_origin, ICMS CST/CSOSN, PIS/COFINS/IPI CST, LC116, NBS, IBS-CBS CST/credit_class). Importação automática (Siscomex, checksum, trava de volume mínimo, upsert com inactivateMissing=true) é trabalho DEFERIDO — ver cabeçalho da migration.';
|
||||
|
||||
CREATE UNIQUE INDEX idx_fiscal_ncm_code ON fiscal_ncm(code);
|
||||
CREATE INDEX idx_fiscal_ncm_active ON fiscal_ncm(active);
|
||||
|
||||
CREATE TRIGGER trg_fiscal_ncm_updated_at BEFORE UPDATE ON fiscal_ncm
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =====================================================================
|
||||
-- fiscal_cfop — Código Fiscal de Operações e Prestações. "Simple
|
||||
-- catalog" shape + direction/scope (eden.md §2.3, §4). Fonte MANUAL
|
||||
-- (sem endpoint estruturado confiável testado no legado).
|
||||
-- =====================================================================
|
||||
CREATE TABLE fiscal_cfop (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
code TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
direction TEXT,
|
||||
scope TEXT,
|
||||
valid_from DATE,
|
||||
valid_until DATE,
|
||||
active BOOLEAN NOT NULL DEFAULT true,
|
||||
source TEXT,
|
||||
source_version TEXT,
|
||||
source_updated_at TIMESTAMPTZ,
|
||||
metadata JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
COMMENT ON TABLE fiscal_cfop IS
|
||||
'Código Fiscal de Operações e Prestações (eden.md §2.3, §4). "Catálogo simples" + direction/scope. Fonte declarada "Portal Nacional NF-e" mas tipo MANUAL no legado (sem endpoint estruturado confiável testado) — cadastro por upload de arquivo ou tela, ambos DEFERIDOS nesta leva (ver cabeçalho da migration).';
|
||||
COMMENT ON COLUMN fiscal_cfop.direction IS
|
||||
'Direção da operação (entrada/saída) — TEXT livre, sem CHECK/enum, exatamente como o legado (eden.md §4). O 1º dígito do código CFOP codifica direção+âmbito na prática brasileira, mas isso NUNCA é derivado automaticamente do código no legado nem aqui — direction/scope são só campos preenchidos manualmente/via importação.';
|
||||
COMMENT ON COLUMN fiscal_cfop.scope IS
|
||||
'Âmbito da operação (estadual/interestadual/exterior) — TEXT livre, sem CHECK/enum, mesma razão de direction (eden.md §4).';
|
||||
|
||||
CREATE UNIQUE INDEX idx_fiscal_cfop_code ON fiscal_cfop(code);
|
||||
CREATE INDEX idx_fiscal_cfop_active ON fiscal_cfop(active);
|
||||
|
||||
CREATE TRIGGER trg_fiscal_cfop_updated_at BEFORE UPDATE ON fiscal_cfop
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =====================================================================
|
||||
-- fiscal_municipality — shape DIFERENTE do catálogo simples: sem
|
||||
-- valid_from/valid_until/metadata ("não faz sentido um município
|
||||
-- expirar", eden.md §2.3). Fonte: IBGE (import deferido).
|
||||
-- =====================================================================
|
||||
CREATE TABLE fiscal_municipality (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
code TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
state TEXT NOT NULL,
|
||||
active BOOLEAN NOT NULL DEFAULT true,
|
||||
source TEXT,
|
||||
source_version TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
COMMENT ON TABLE fiscal_municipality IS
|
||||
'Municípios/IBGE (eden.md §2.3, §5) — base para ISS/NFS-e. Shape deliberadamente distinto do catálogo simples: sem valid_from/valid_until/metadata ("não faz sentido um município expirar"). code = código IBGE, state = UF. Importação automática (IBGE, checksum, trava de volume mínimo de 5000 linhas) é DEFERIDA — ver cabeçalho da migration.';
|
||||
|
||||
CREATE UNIQUE INDEX idx_fiscal_municipality_code ON fiscal_municipality(code);
|
||||
CREATE INDEX idx_fiscal_municipality_state ON fiscal_municipality(state);
|
||||
|
||||
CREATE TRIGGER trg_fiscal_municipality_updated_at BEFORE UPDATE ON fiscal_municipality
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =====================================================================
|
||||
-- fiscal_operation_nature — cadastro interno, sem fonte externa
|
||||
-- (eden.md §2.3). Seed fixo via ON CONFLICT DO NOTHING.
|
||||
-- =====================================================================
|
||||
CREATE TABLE fiscal_operation_nature (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
code TEXT UNIQUE NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
COMMENT ON TABLE fiscal_operation_nature IS
|
||||
'Naturezas de operação (eden.md §2.3) — cadastro interno de negócio, não uma fonte externa sincronizável. Seed fixo abaixo, único endpoint da API para este catálogo é leitura (GET /v1/fiscal/operation-natures) — sem criação via API nesta leva.';
|
||||
|
||||
CREATE TRIGGER trg_fiscal_operation_nature_updated_at BEFORE UPDATE ON fiscal_operation_nature
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
INSERT INTO fiscal_operation_nature (code, name) VALUES
|
||||
('VENDA', 'Venda'),
|
||||
('REMESSA_LOCACAO', 'Remessa em locação'),
|
||||
('RETORNO_LOCACAO', 'Retorno de locação'),
|
||||
('REMESSA_MANUTENCAO', 'Remessa para manutenção'),
|
||||
('RETORNO_MANUTENCAO', 'Retorno de manutenção'),
|
||||
('STFC_RECORRENTE', 'STFC recorrente'),
|
||||
('SVA_RECORRENTE', 'SVA recorrente'),
|
||||
('PABX_RECORRENTE', 'PABX recorrente'),
|
||||
('PABX_IMPLANTACAO', 'PABX implantação'),
|
||||
('SOFTWARE_RECORRENTE', 'Software recorrente'),
|
||||
('SOFTWARE_IMPLANTACAO', 'Software implantação')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- =====================================================================
|
||||
-- product_fiscal_profiles — liga products à classificação fiscal
|
||||
-- (eden.md §2.5). Reduzida aos 4 catálogos criados nesta leva.
|
||||
-- =====================================================================
|
||||
CREATE TABLE product_fiscal_profiles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
product_id UUID NOT NULL,
|
||||
billing_component TEXT NOT NULL CHECK (billing_component IN ('RECURRING', 'IMPLEMENTATION')),
|
||||
document_type TEXT NOT NULL DEFAULT 'NONE' CHECK (document_type IN ('NFCOM', 'NFSE', 'NFE', 'NONE')),
|
||||
ncm_id UUID REFERENCES fiscal_ncm(id),
|
||||
cfop_id UUID REFERENCES fiscal_cfop(id),
|
||||
municipality_id UUID REFERENCES fiscal_municipality(id),
|
||||
operation_nature_id UUID REFERENCES fiscal_operation_nature(id),
|
||||
valid_from DATE,
|
||||
valid_until DATE,
|
||||
active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
created_by UUID REFERENCES users(id),
|
||||
updated_by UUID REFERENCES users(id)
|
||||
);
|
||||
COMMENT ON TABLE product_fiscal_profiles IS
|
||||
'Configuração fiscal de produto (eden.md §2.5) — deliberadamente separada de products, nunca duplica campos comerciais (price_*, impl_unit_price, metered, tariff_rates, has_ldi continuam só em products). product_id SEM FK (products pode não existir neste worktree isolado — ver cabeçalho da migration, padrão "FK cruzada adiada" de contracts.cjs/support.cjs). Colunas de referência a catálogos ainda não portados (nfcom_cclass_id, nfse_trib_nacional_id, nfse_trib_municipal_id, lc116_service_id, nbs_id, cest_id, product_origin_id, ibs_cbs_cst_id, ibs_cbs_classification_id, ibs_cbs_credit_class_id) ficam para quando os catálogos correspondentes existirem. Sem backfill automático nesta leva (eden.md §2.5 backfill dependia de products.service_type/impl_unit_price já populados em produção real) — profiles são criados sob demanda via API.';
|
||||
COMMENT ON COLUMN product_fiscal_profiles.product_id IS
|
||||
'Sem REFERENCES products(id) nesta leva — products pode não existir neste worktree isolado. FK cruzada é trabalho de integração no merge (mesmo padrão de contracts.quote_id/client_registration_id e support_tickets.client_registration_id/contract_id).';
|
||||
|
||||
-- Invariante central (eden.md §2.5): um produto tem no máximo um
|
||||
-- profile ATIVO por billing_component. Nunca DELETE/UPDATE destrutivo
|
||||
-- de um profile antigo para "trocar" a classificação — cria-se um novo
|
||||
-- profile ativo e inativa-se o anterior (active=false), preservando
|
||||
-- histórico.
|
||||
CREATE UNIQUE INDEX idx_product_fiscal_profiles_active_component
|
||||
ON product_fiscal_profiles(product_id, billing_component) WHERE active = true;
|
||||
|
||||
CREATE INDEX idx_product_fiscal_profiles_product ON product_fiscal_profiles(product_id);
|
||||
|
||||
CREATE TRIGGER trg_product_fiscal_profiles_updated_at BEFORE UPDATE ON product_fiscal_profiles
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
`);
|
||||
};
|
||||
|
||||
exports.down = (pgm) => {
|
||||
pgm.sql(`
|
||||
DROP TABLE IF EXISTS product_fiscal_profiles;
|
||||
DROP TABLE IF EXISTS fiscal_operation_nature;
|
||||
DROP TABLE IF EXISTS fiscal_municipality;
|
||||
DROP TABLE IF EXISTS fiscal_cfop;
|
||||
DROP TABLE IF EXISTS fiscal_ncm;
|
||||
`);
|
||||
};
|
||||
Reference in New Issue
Block a user