Inclusão relatório contábil Diretoria
This commit is contained in:
parent
236b46c65d
commit
543b1171c7
11
CLAUDE.md
11
CLAUDE.md
@ -97,7 +97,7 @@ Ver `docs/identidade-visual/identidade-visual.md` para o inventário de cada arq
|
|||||||
|
|
||||||
### Backend serve o frontend (mesma origem)
|
### Backend serve o frontend (mesma origem)
|
||||||
|
|
||||||
`config/urls.py` registra `path("api/", include("portal_api.urls"))` e, para cada uma das **15 páginas HTML** do frontend (`index.html` mais os 14 shells — ver a tabela em "Páginas" abaixo), uma rota `TemplateView` que resolve o arquivo em `templates/`. Dois templates não têm rota própria: `dashboard-contabil-relatorio.html` e `conciliacao-fornecedores-relatorio.html` são renderizados por uma view da aplicação correspondente (`/api/contabil-apuracoes/{id}/dashboard/` e `/api/conciliacoes-fornecedores/{id}/relatorio/`), não navegáveis pela URL de página. Os estáticos (`static/css`, `static/js`, `static/img`) são servidos por `django.contrib.staticfiles` automaticamente em `DEBUG` (via `STATICFILES_DIRS`) — não há mais nenhum `re_path`/`static_serve` manual em `urls.py`. Cada template usa `{% load static %}` + `{% static 'css/tokens.css' %}` (nunca um caminho hardcoded tipo `assets/css/...`, que não existe mais). Essa escolha (Django servindo o próprio frontend) existe para evitar CORS/cookie cross-origin: autenticação é por **sessão/cookie do Django**, então frontend e API precisam estar na mesma origem.
|
`config/urls.py` registra `path("api/", include("portal_api.urls"))` e, para cada uma das **16 páginas HTML** do frontend (`index.html` mais os 15 shells — ver a tabela em "Páginas" abaixo), uma rota `TemplateView` que resolve o arquivo em `templates/`. Dois templates não têm rota própria: `dashboard-contabil-relatorio.html` e `conciliacao-fornecedores-relatorio.html` são renderizados por uma view da aplicação correspondente (`/api/contabil-apuracoes/{id}/dashboard/` e `/api/conciliacoes-fornecedores/{id}/relatorio/`), não navegáveis pela URL de página. Os estáticos (`static/css`, `static/js`, `static/img`) são servidos por `django.contrib.staticfiles` automaticamente em `DEBUG` (via `STATICFILES_DIRS`) — não há mais nenhum `re_path`/`static_serve` manual em `urls.py`. Cada template usa `{% load static %}` + `{% static 'css/tokens.css' %}` (nunca um caminho hardcoded tipo `assets/css/...`, que não existe mais). Essa escolha (Django servindo o próprio frontend) existe para evitar CORS/cookie cross-origin: autenticação é por **sessão/cookie do Django**, então frontend e API precisam estar na mesma origem.
|
||||||
|
|
||||||
Em produção, rodar `python manage.py collectstatic` (junta tudo em `STATIC_ROOT = BASE_DIR / "staticfiles"`) e servir esse diretório via whitenoise/nginx — `django.contrib.staticfiles` só serve automaticamente quando `DEBUG=True`. Uploads de usuário (ícones de `LinkFerramenta`) são um mecanismo separado: `MEDIA_URL`/`MEDIA_ROOT` em `settings.py`, servidos por `config/urls.py` via `static()` só quando `DEBUG=True` (em produção, servir `media/` também por whitenoise/nginx, igual ao `STATIC_ROOT`).
|
Em produção, rodar `python manage.py collectstatic` (junta tudo em `STATIC_ROOT = BASE_DIR / "staticfiles"`) e servir esse diretório via whitenoise/nginx — `django.contrib.staticfiles` só serve automaticamente quando `DEBUG=True`. Uploads de usuário (ícones de `LinkFerramenta`) são um mecanismo separado: `MEDIA_URL`/`MEDIA_ROOT` em `settings.py`, servidos por `config/urls.py` via `static()` só quando `DEBUG=True` (em produção, servir `media/` também por whitenoise/nginx, igual ao `STATIC_ROOT`).
|
||||||
|
|
||||||
@ -185,6 +185,7 @@ Pacotes Python puros (sem ORM), um por ferramenta — cada um com seu próprio `
|
|||||||
| `nao-conformidades.html` | Aplicação de Relatórios > Qualidade: gestão contínua das ocorrências/ações do Sigsistem, com dashboard e status interno de tratativa da Qualidade (ver `portal_api/nao_conformidades/CLAUDE.md`). |
|
| `nao-conformidades.html` | Aplicação de Relatórios > Qualidade: gestão contínua das ocorrências/ações do Sigsistem, com dashboard e status interno de tratativa da Qualidade (ver `portal_api/nao_conformidades/CLAUDE.md`). |
|
||||||
| `conciliacao-fornecedores.html` | Ferramenta de Utilitários: histórico + nova conciliação (código da empresa com nome buscado no Questor + razão de fornecedores .xlsx/.csv) + detalhe com cards de alerta e tabela de fornecedores expansível, vínculo manual, validação e exportação XLSX (ver `portal_api/conciliacao_fornecedores/CLAUDE.md`). |
|
| `conciliacao-fornecedores.html` | Ferramenta de Utilitários: histórico + nova conciliação (código da empresa com nome buscado no Questor + razão de fornecedores .xlsx/.csv) + detalhe com cards de alerta e tabela de fornecedores expansível, vínculo manual, validação e exportação XLSX (ver `portal_api/conciliacao_fornecedores/CLAUDE.md`). |
|
||||||
| `dashboard-contabil.html` | Aplicação de Relatórios > Contabilidade ("Relatório Contábil" na UI): histórico de análises + nova análise (upload do PDF de Balancete + DRE) + revisão de achados de auditoria (com observações por conta) + botão "Gerar Relatório" (relatório HTML autocontido, ver `portal_api/dashboard_contabil/CLAUDE.md`). |
|
| `dashboard-contabil.html` | Aplicação de Relatórios > Contabilidade ("Relatório Contábil" na UI): histórico de análises + nova análise (upload do PDF de Balancete + DRE) + revisão de achados de auditoria (com observações por conta) + botão "Gerar Relatório" (relatório HTML autocontido, ver `portal_api/dashboard_contabil/CLAUDE.md`). |
|
||||||
|
| `dashboard-contabil-de-paula.html` | Aplicação da seção Diretoria ("Relatório Contábil - De Paula"): a mesma tela de `dashboard-contabil.html`, para as empresas do próprio escritório, com tabelas, endpoints e permissão próprios. Mesmo JS/CSS, parametrizados por `window.PID_DC_CONFIG` (ver "Relatório Contábil - De Paula" em `portal_api/dashboard_contabil/CLAUDE.md`). |
|
||||||
|
|
||||||
O item "Calendário De Paula" no menu **não é uma página local** — é um `<a href="#" id="calendario-depaula-btn">` (continua favoritável, já que ainda é um `<a class="nav-item">` — ver `docs/favoritos/favoritos.md`) cujo clique é interceptado em `sidebar.js` (`PID_CALENDARIO_DEPAULA_URL`) pra abrir `https://depaula-tvcorporativa.lovable.app/calendario` num modal com `<iframe>` (`#calendario-depaula-modal`, presente em todo shell) em vez de navegar — mesmo padrão do modal "Novo Chamado" de Ramais (`.ram-chamado-*` em `ramais.css`/`ramais.js`), só que genérico o bastante (`.iframe-modal-*` em `components.css`) pra existir em todo shell, não só em `ramais.html`. Funciona porque esse host (mesmo domínio do "Novo Chamado") não bloqueia ser embutido via `X-Frame-Options`/CSP, ao contrário do Asana (ver `docs/solicitacoes/solicitacoes.md`) — só foi possível confirmar isso testando de fato, não é garantia geral por domínio. Não recriar um `calendario.html` interno sem confirmar com o usuário; o antigo foi removido de propósito.
|
O item "Calendário De Paula" no menu **não é uma página local** — é um `<a href="#" id="calendario-depaula-btn">` (continua favoritável, já que ainda é um `<a class="nav-item">` — ver `docs/favoritos/favoritos.md`) cujo clique é interceptado em `sidebar.js` (`PID_CALENDARIO_DEPAULA_URL`) pra abrir `https://depaula-tvcorporativa.lovable.app/calendario` num modal com `<iframe>` (`#calendario-depaula-modal`, presente em todo shell) em vez de navegar — mesmo padrão do modal "Novo Chamado" de Ramais (`.ram-chamado-*` em `ramais.css`/`ramais.js`), só que genérico o bastante (`.iframe-modal-*` em `components.css`) pra existir em todo shell, não só em `ramais.html`. Funciona porque esse host (mesmo domínio do "Novo Chamado") não bloqueia ser embutido via `X-Frame-Options`/CSP, ao contrário do Asana (ver `docs/solicitacoes/solicitacoes.md`) — só foi possível confirmar isso testando de fato, não é garantia geral por domínio. Não recriar um `calendario.html` interno sem confirmar com o usuário; o antigo foi removido de propósito.
|
||||||
|
|
||||||
@ -213,7 +214,7 @@ Padrão de guarda por página: `profiles.js`, `users-admin.js` e `widgets.js` ve
|
|||||||
|
|
||||||
## Modelo de permissões (Perfis de Acesso)
|
## Modelo de permissões (Perfis de Acesso)
|
||||||
|
|
||||||
O catálogo de módulos/aplicações do menu vive só no backend agora (`portal_api/catalogo.py`) e é buscado uma vez por `profiles.js` via `GET /api/catalogo/` — **não editar mais um `PID_MODULES`/`PID_MODULE_APPS` hardcoded no frontend**; a edição correta é em `catalogo.py`. 11 das 14 seções do menu têm sub-aplicações configuráveis individualmente (`portais`, `geradoc`, `relatorios`, `relatorios-gerenciais`, `utilitarios`, `integracoes`, `auditorias`, `solicitacoes`, `links-ferramentas`, `ramais`, `calendario-individual`); as outras 3 (`principal`, `calendario`, `administracao`) só têm um toggle de módulo, sem filhos. Em `auditorias`, cada entrada pode ser um subgrupo com `tools` aninhadas (ex.: "Consultoria Tributária" → "Controle Simples Nacional") — categorias reais que agrupam ferramentas reais. Em `ramais` e em `links-ferramentas`, o mesmo formato de subgrupo é reaproveitado por outro motivo: cada subgrupo ali **é** uma subtela/aplicação real da seção (ex.: "Ramais"/"Telefones Externos" dentro de `ramais`; "Links & Ferramentas"/"Acessos Gerais" dentro de `links-ferramentas`), e as `tools` dentro de cada subgrupo não são aplicações de verdade — são os dois níveis de acesso (visualizar/editar) daquela subtela (ver "Padrão visualizar/editar" abaixo). Em `calendario-individual`, o único subgrupo (`calendario-individual-eventos`) tem uma **única** `tool` (`calendario-individual-criar-evento`) em vez de um par visualizar/editar — a visualização do módulo em si já é liberada a todo perfil (está em `BASE_KEYS`), só a criação/gestão de eventos corporativos e categorias é restrita (ver "Eventos Corporativos" abaixo). Em `geradoc`, `simulacao-custo-contratacao` e `indicador-desempenho` são dois apps flat (sem subgrupo), cada um com permissão de **toggle único** — mesmo espírito de `importacao-plano-saude` em Utilitários (quem tem acesso faz o fluxo inteiro, sem par visualizar/editar).
|
O catálogo de módulos/aplicações do menu vive só no backend agora (`portal_api/catalogo.py`) e é buscado uma vez por `profiles.js` via `GET /api/catalogo/` — **não editar mais um `PID_MODULES`/`PID_MODULE_APPS` hardcoded no frontend**; a edição correta é em `catalogo.py`. 12 das 15 seções do menu têm sub-aplicações configuráveis individualmente (`portais`, `geradoc`, `relatorios`, `diretoria`, `relatorios-gerenciais`, `utilitarios`, `integracoes`, `auditorias`, `solicitacoes`, `links-ferramentas`, `ramais`, `calendario-individual`); as outras 3 (`principal`, `calendario`, `administracao`) só têm um toggle de módulo, sem filhos. Em `auditorias`, cada entrada pode ser um subgrupo com `tools` aninhadas (ex.: "Consultoria Tributária" → "Controle Simples Nacional") — categorias reais que agrupam ferramentas reais. Em `ramais` e em `links-ferramentas`, o mesmo formato de subgrupo é reaproveitado por outro motivo: cada subgrupo ali **é** uma subtela/aplicação real da seção (ex.: "Ramais"/"Telefones Externos" dentro de `ramais`; "Links & Ferramentas"/"Acessos Gerais" dentro de `links-ferramentas`), e as `tools` dentro de cada subgrupo não são aplicações de verdade — são os dois níveis de acesso (visualizar/editar) daquela subtela (ver "Padrão visualizar/editar" abaixo). Em `calendario-individual`, o único subgrupo (`calendario-individual-eventos`) tem uma **única** `tool` (`calendario-individual-criar-evento`) em vez de um par visualizar/editar — a visualização do módulo em si já é liberada a todo perfil (está em `BASE_KEYS`), só a criação/gestão de eventos corporativos e categorias é restrita (ver "Eventos Corporativos" abaixo). Em `geradoc`, `simulacao-custo-contratacao` e `indicador-desempenho` são dois apps flat (sem subgrupo), cada um com permissão de **toggle único** — mesmo espírito de `importacao-plano-saude` em Utilitários (quem tem acesso faz o fluxo inteiro, sem par visualizar/editar).
|
||||||
|
|
||||||
Formato de perfil no banco (`PerfilAcesso.permissoes`, `JSONField`):
|
Formato de perfil no banco (`PerfilAcesso.permissoes`, `JSONField`):
|
||||||
```json
|
```json
|
||||||
@ -258,6 +259,8 @@ Isso reaproveita 100% a árvore de permissões que já existe (`renderTree()`/`r
|
|||||||
|
|
||||||
**Cuidado com `seed_portal.py`**: o módulo continua tendo seu próprio `enabled` (visibilidade no menu) e, se estiver em `BASE_KEYS`/`SECTORAL_KEYS` etc., `catalogo.permissions_from_keys()` habilita **todos** os apps de um módulo enabled de uma vez — incluindo todo `*-editar`. Por isso `seed_portal.py` força `permissoes["links-ferramentas"]["apps"]["links-ferramentas-editar"] = False`, `permissoes["links-ferramentas"]["apps"]["acessos-gerais-editar"] = False` e `permissoes["ramais"]["apps"]["ramais-editar"] = False` (+ as outras duas subtelas administráveis de Ramais) explicitamente pra todo perfil que não seja "Integração e Inovação" (código 8, `chaves=None` → todos os apps `True` de propósito), depois de gerar o dict — sem esse override, qualquer perfil com o módulo habilitado nasceria com poder de editar tudo. Ao adicionar uma aplicação nova nesse padrão (dentro de um módulo existente ou não), replicar esse mesmo cuidado no seed.
|
**Cuidado com `seed_portal.py`**: o módulo continua tendo seu próprio `enabled` (visibilidade no menu) e, se estiver em `BASE_KEYS`/`SECTORAL_KEYS` etc., `catalogo.permissions_from_keys()` habilita **todos** os apps de um módulo enabled de uma vez — incluindo todo `*-editar`. Por isso `seed_portal.py` força `permissoes["links-ferramentas"]["apps"]["links-ferramentas-editar"] = False`, `permissoes["links-ferramentas"]["apps"]["acessos-gerais-editar"] = False` e `permissoes["ramais"]["apps"]["ramais-editar"] = False` (+ as outras duas subtelas administráveis de Ramais) explicitamente pra todo perfil que não seja "Integração e Inovação" (código 8, `chaves=None` → todos os apps `True` de propósito), depois de gerar o dict — sem esse override, qualquer perfil com o módulo habilitado nasceria com poder de editar tudo. Ao adicionar uma aplicação nova nesse padrão (dentro de um módulo existente ou não), replicar esse mesmo cuidado no seed.
|
||||||
|
|
||||||
|
**Módulo novo no catálogo e perfis já gravados**: como `seed_portal` não roda em produção, um módulo (seção do menu) novo em `catalogo.MODULES` não entra sozinho no JSON dos perfis existentes. O servidor já trata chave ausente como desabilitada (`permissoes_efetivas()`, `Usuario.permissao_app()`), então ninguém ganha acesso por engano. Na tela de Perfis de Acesso, `pidCompletaPermissoes()` (`profiles.js`) completa módulo/app ausente como desmarcado ao carregar cada perfil, sem alterar valor existente; sem isso, abrir um perfil quebrava a tela ao ler `permissions[modulo].enabled`. A chave só passa a ser gravada quando o perfil é salvo. Primeiro caso: a seção "Diretoria".
|
||||||
|
|
||||||
**Convenção pra aplicação nova que lide com dado sensível/pessoal (não dado de cliente comum)**: decisão explícita do usuário (rodada em que "Importação de Plano de Saúde - De Paula" foi criada, ver `portal_api/planos_saude/CLAUDE.md`) — daqui pra frente, toda aplicação nova adicionada ao catálogo que trate dado pessoal de colaborador do próprio escritório (em vez de dado de cliente, que é o caso comum do resto do Portal) deve **nascer restrita só ao perfil "Inovação"** (código 8), mesmo padrão já usado por "Não Conformidades" (`permissoes["relatorios"]["apps"]["nao-conformidades"] = False` no bloco de overrides) — nunca herdar a visibilidade ampla padrão de `BASE_KEYS`/`SECTORAL_KEYS`. Outros perfis que precisarem ganham acesso depois, manualmente, pela tela de Perfis de Acesso. **Não retroagir** essa convenção nas aplicações de toggle único que já existiam antes desta decisão (Importação de Plano de Saúde original, Simulação de Custo de Contratação, Indicador de Desempenho) — essas continuam com a visibilidade ampla de sempre, decisão explícita do usuário de não mexer no que já está em uso.
|
**Convenção pra aplicação nova que lide com dado sensível/pessoal (não dado de cliente comum)**: decisão explícita do usuário (rodada em que "Importação de Plano de Saúde - De Paula" foi criada, ver `portal_api/planos_saude/CLAUDE.md`) — daqui pra frente, toda aplicação nova adicionada ao catálogo que trate dado pessoal de colaborador do próprio escritório (em vez de dado de cliente, que é o caso comum do resto do Portal) deve **nascer restrita só ao perfil "Inovação"** (código 8), mesmo padrão já usado por "Não Conformidades" (`permissoes["relatorios"]["apps"]["nao-conformidades"] = False` no bloco de overrides) — nunca herdar a visibilidade ampla padrão de `BASE_KEYS`/`SECTORAL_KEYS`. Outros perfis que precisarem ganham acesso depois, manualmente, pela tela de Perfis de Acesso. **Não retroagir** essa convenção nas aplicações de toggle único que já existiam antes desta decisão (Importação de Plano de Saúde original, Simulação de Custo de Contratação, Indicador de Desempenho) — essas continuam com a visibilidade ampla de sempre, decisão explícita do usuário de não mexer no que já está em uso.
|
||||||
|
|
||||||
**Bug real (rodada 69) — `IntegrityError: duplicate key value violates unique constraint "portal_api_perfilacesso_pkey"` ao criar um perfil pela tela**: `seed_portal.py` semeia `PerfilAcesso` com `codigo` **explícito** (`update_or_create(codigo=dado["codigo"], ...)`, já que os 8 códigos 1–8 são referenciados por número fixo em vários lugares do código — ex.: `codigo == 8` = "Integração e Inovação"). No Postgres, um `INSERT` com PK explícita **nunca avança a sequence** por trás do `AutoField` — então a sequence ficava parada em 1 (seu valor inicial), e o primeiro perfil criado pela tela (`POST /api/perfis/`, sem PK explícita) recebia `codigo=1` do `nextval()`, colidindo com um código já usado pelo seed. `_reset_sequence(model)` (função módulo-level em `seed_portal.py`, roda `SELECT setval(pg_get_serial_sequence(...), MAX(pk))` via SQL puro do Postgres) corrige isso, chamada logo depois do loop de `PERFIS_SEED` — toda vez que `seed_portal` roda, a sequence é realinhada de novo. Se esse erro voltar a aparecer no futuro (ex.: um `loaddata`/`RunPython` de migração também inserindo `PerfilAcesso` com PK explícita sem passar por `seed_portal.py` depois), o comando pra corrigir manualmente é `python manage.py seed_portal` (idempotente, seguro rodar de novo) — não precisa de acesso direto ao banco.
|
**Bug real (rodada 69) — `IntegrityError: duplicate key value violates unique constraint "portal_api_perfilacesso_pkey"` ao criar um perfil pela tela**: `seed_portal.py` semeia `PerfilAcesso` com `codigo` **explícito** (`update_or_create(codigo=dado["codigo"], ...)`, já que os 8 códigos 1–8 são referenciados por número fixo em vários lugares do código — ex.: `codigo == 8` = "Integração e Inovação"). No Postgres, um `INSERT` com PK explícita **nunca avança a sequence** por trás do `AutoField` — então a sequence ficava parada em 1 (seu valor inicial), e o primeiro perfil criado pela tela (`POST /api/perfis/`, sem PK explícita) recebia `codigo=1` do `nextval()`, colidindo com um código já usado pelo seed. `_reset_sequence(model)` (função módulo-level em `seed_portal.py`, roda `SELECT setval(pg_get_serial_sequence(...), MAX(pk))` via SQL puro do Postgres) corrige isso, chamada logo depois do loop de `PERFIS_SEED` — toda vez que `seed_portal` roda, a sequence é realinhada de novo. Se esse erro voltar a aparecer no futuro (ex.: um `loaddata`/`RunPython` de migração também inserindo `PerfilAcesso` com PK explícita sem passar por `seed_portal.py` depois), o comando pra corrigir manualmente é `python manage.py seed_portal` (idempotente, seguro rodar de novo) — não precisa de acesso direto ao banco.
|
||||||
@ -355,6 +358,8 @@ Chamado de "Dashboard Contábil" até uma rodada anterior — renomeado pra "Rel
|
|||||||
|
|
||||||
Otimiza a conferência de balancetes hoje feita manualmente pelo Fisco/Contábil (`ITD-FISCO-7513`): o contador anexa o PDF de Balancete + DRE (modelo Questor ou Contabit, detectado sozinho, mesmo relatório hoje enviado ao cliente; no Contabit o código da empresa vem do nome do arquivo e a seção de indicadores fica oculta), a ferramenta extrai as contas e roda um motor de regras de auditoria (saldos negativos, contas transitórias/genéricas com saldo, contas que deveriam ficar zeradas, débito ≠ crédito, variações atípicas mês a mês contra o histórico já processado no Portal), com revisão de achados e observações por conta antes da conclusão (a observação fica no histórico da empresa + conta e reaparece assinada nas competências seguintes, até ser encerrada) — cada conta/linha também ganha um checkbox de "validado" (marcador informativo de que o contador já conferiu aquele item) e um editor de observação inline na própria tabela (não mais um popup), com um toggle "mostrar ao cliente" que nasce desmarcado por padrão. O botão "Gerar Relatório" gera um relatório HTML autocontido (indicadores financeiros, gráfico de evolução, DRE/Balancete agrupados, observações do contador — com a marca do escritório, não a "P.I.D." do Portal), com exportação de Balancete/DRE em XLSX a partir dele. **Ainda fora de escopo**: consolidação entre várias empresas/competências ao mesmo tempo (substituiria o BI Contábil por completo).
|
Otimiza a conferência de balancetes hoje feita manualmente pelo Fisco/Contábil (`ITD-FISCO-7513`): o contador anexa o PDF de Balancete + DRE (modelo Questor ou Contabit, detectado sozinho, mesmo relatório hoje enviado ao cliente; no Contabit o código da empresa vem do nome do arquivo e a seção de indicadores fica oculta), a ferramenta extrai as contas e roda um motor de regras de auditoria (saldos negativos, contas transitórias/genéricas com saldo, contas que deveriam ficar zeradas, débito ≠ crédito, variações atípicas mês a mês contra o histórico já processado no Portal), com revisão de achados e observações por conta antes da conclusão (a observação fica no histórico da empresa + conta e reaparece assinada nas competências seguintes, até ser encerrada) — cada conta/linha também ganha um checkbox de "validado" (marcador informativo de que o contador já conferiu aquele item) e um editor de observação inline na própria tabela (não mais um popup), com um toggle "mostrar ao cliente" que nasce desmarcado por padrão. O botão "Gerar Relatório" gera um relatório HTML autocontido (indicadores financeiros, gráfico de evolução, DRE/Balancete agrupados, observações do contador — com a marca do escritório, não a "P.I.D." do Portal), com exportação de Balancete/DRE em XLSX a partir dele. **Ainda fora de escopo**: consolidação entre várias empresas/competências ao mesmo tempo (substituiria o BI Contábil por completo).
|
||||||
|
|
||||||
|
**Relatório Contábil - De Paula** (seção **Diretoria** do menu, `diretoria` > `dashboard-contabil-de-paula`): segunda instância para as empresas do próprio escritório. Regras, cadastro de indicadores e código compartilhados com a original (models como base abstrata + duas concretas, ViewSets e JS parametrizados); análises, histórico, observações, endpoints (`-de-paula`) e permissão separados, sem FK cruzando. Requisito do usuário: quem tem acesso só ao Relatório Contábil não acessa a De Paula em hipótese alguma. O módulo `diretoria` fica fora de `BASE_KEYS`/`SECTORAL_KEYS`, então nasce fechado para todo perfil e é liberado manualmente pela tela.
|
||||||
|
|
||||||
Ver `portal_api/dashboard_contabil/CLAUDE.md`.
|
Ver `portal_api/dashboard_contabil/CLAUDE.md`.
|
||||||
|
|
||||||
## CSS — organização entre arquivos
|
## CSS — organização entre arquivos
|
||||||
@ -363,7 +368,7 @@ Ver `portal_api/dashboard_contabil/CLAUDE.md`.
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `tokens.css` | Variáveis (`:root`, tema claro em `:root[data-theme="light"]`). |
|
| `tokens.css` | Variáveis (`:root`, tema claro em `:root[data-theme="light"]`). |
|
||||||
| `base.css` | Reset global, incluindo `[hidden] { display: none !important; }` — necessário porque vários componentes (`.no-access`, `.app-card`, `.notif-badge`) definem seu próprio `display`, o que sem o `!important` sobrescreveria o comportamento nativo de `hidden`. Também os `@keyframes` globais de animação (`pidFadeIn`/`pidFadeSlideUp`/`pidScaleIn`, ver "Animações" abaixo) e `.pid-icon-eye`/`pidIconBlink` (piscar de olho do ícone "P.I.D.", reaproveitado pela sidebar e pelo login — ver "Ícone do login e da sidebar são clicáveis" abaixo), já que é o único CSS carregado por **todas** as páginas sem exceção (inclusive `index.html`). |
|
| `base.css` | Reset global, incluindo `[hidden] { display: none !important; }` — necessário porque vários componentes (`.no-access`, `.app-card`, `.notif-badge`) definem seu próprio `display`, o que sem o `!important` sobrescreveria o comportamento nativo de `hidden`. Também os `@keyframes` globais de animação (`pidFadeIn`/`pidFadeSlideUp`/`pidScaleIn`, ver "Animações" abaixo) e `.pid-icon-eye`/`pidIconBlink` (piscar de olho do ícone "P.I.D.", reaproveitado pela sidebar e pelo login — ver "Ícone do login e da sidebar são clicáveis" abaixo), já que é o único CSS carregado por **todas** as páginas sem exceção (inclusive `index.html`). |
|
||||||
| `layout.css` | Casca do shell: `.app-shell`, `.sidebar*`, `.nav-*`, `.fav-toggle`, `.topbar*`. A sidebar usa tokens **congelados**, independentes de tema (fundo sempre escuro em claro/escuro) — não trocar por variáveis que espelham `:root[data-theme="light"]`. Exceção deliberada: `--sidebar-text-primary`/`--sidebar-text-secondary`/`--sidebar-text-muted` (texto/ícone do menu) *são* sobrescritas em `:root[data-theme="light"]` (`tokens.css`) pra branco puro — pedido explícito do usuário pra melhorar a legibilidade; só o fundo/borda da sidebar continuam frozen. Também `.page-content` (largura do conteúdo de cada página, `max-width:1200px` centralizado por padrão) + o modificador `.page-content--wide` (`max-width:1600px`) — `portal.html` ("Principal") é a única página que usa só `.page-content` puro (grade de favoritos fica mais confortável de leitura mais estreita); os outros 12 shells (`perfis-acesso.html`, `usuarios.html`, `links-ferramentas.html`, `acessos-gerais.html`, `ramais.html`, `calendario-individual.html`, `importacao-plano-saude.html`, `importacao-plano-saude-de-paula.html`, `custo-contratacao.html`, `indicador-desempenho.html`, `nao-conformidades.html`, `dashboard-contabil.html`) usam `class="page-content page-content--wide"` no `<main>`, decisão explícita do usuário pra aproveitar melhor o espaço entre a sidebar e a borda da tela em telas de tabela/formulário. Uma página nova que seja mais "aplicação" (tabela, formulário, CRUD) do que "dashboard" deve nascer já com `page-content--wide`. |
|
| `layout.css` | Casca do shell: `.app-shell`, `.sidebar*`, `.nav-*`, `.fav-toggle`, `.topbar*`. A sidebar usa tokens **congelados**, independentes de tema (fundo sempre escuro em claro/escuro) — não trocar por variáveis que espelham `:root[data-theme="light"]`. Exceção deliberada: `--sidebar-text-primary`/`--sidebar-text-secondary`/`--sidebar-text-muted` (texto/ícone do menu) *são* sobrescritas em `:root[data-theme="light"]` (`tokens.css`) pra branco puro — pedido explícito do usuário pra melhorar a legibilidade; só o fundo/borda da sidebar continuam frozen. Também `.page-content` (largura do conteúdo de cada página, `max-width:1200px` centralizado por padrão) + o modificador `.page-content--wide` (`max-width:1600px`) — `portal.html` ("Principal") é a única página que usa só `.page-content` puro (grade de favoritos fica mais confortável de leitura mais estreita); os outros 12 shells (`perfis-acesso.html`, `usuarios.html`, `links-ferramentas.html`, `acessos-gerais.html`, `ramais.html`, `calendario-individual.html`, `importacao-plano-saude.html`, `importacao-plano-saude-de-paula.html`, `custo-contratacao.html`, `indicador-desempenho.html`, `nao-conformidades.html`, `dashboard-contabil.html`, `dashboard-contabil-de-paula.html`) usam `class="page-content page-content--wide"` no `<main>`, decisão explícita do usuário pra aproveitar melhor o espaço entre a sidebar e a borda da tela em telas de tabela/formulário. Uma página nova que seja mais "aplicação" (tabela, formulário, CRUD) do que "dashboard" deve nascer já com `page-content--wide`. |
|
||||||
| `components.css` | UI genérica reutilizável: `.btn-solid/.btn-outline/.btn-ghost/.btn-danger-outline`, **`.modal-overlay`/`.modal-card`** (moldura genérica de modal, + o modificador `.modal-card--wide` pra quando precisa de mais espaço horizontal) **e também** `.modal-field`/`.modal-field-row`/`.modal-checkbox`/`.modal-error`/`.modal-actions` (campos internos do modal — moldura e campos vivem juntos aqui, apesar do nome sugerir só a moldura, incluindo `select`/`textarea` dentro de `.modal-field`, com seta customizada via `background-image` porque o nativo do browser destoa do tema escuro), `.app-card*`, `.no-access`, `.checklist-box`/`.checklist-item`/`.checklist-item__info`/`.checklist-item__nome`/`.checklist-item__departamento`/`.checklist-empty`/`.checklist-search`/`.checklist-select-all` (lista com checkbox, segunda linha de detalhe e busca — usada nos checklists de Perfis de Acesso/Departamento em `usuarios.html`), `.dual-select`/`.dual-select__*` (vinculação em duas tabelas — não vinculados/vinculados, ver `docs/perfis-usuarios/perfis-usuarios.md` — usada em `usuarios.html` e no modal "Gerenciar Usuário" de todo shell), `.info-tooltip`/`.info-tooltip__*`/`.ajuda-modal__*` (botão "?" + tooltip + modal de "Mais informações", ver seção própria abaixo — usados por `static/js/ajuda-aplicacao.js`) e `.modal-overlay--top` (empilha um modal por cima de outro já aberto — usado só pelo modal de confirmação genérico, `static/js/confirm-modal.js`, ver "Modal de confirmação genérico" abaixo). |
|
| `components.css` | UI genérica reutilizável: `.btn-solid/.btn-outline/.btn-ghost/.btn-danger-outline`, **`.modal-overlay`/`.modal-card`** (moldura genérica de modal, + o modificador `.modal-card--wide` pra quando precisa de mais espaço horizontal) **e também** `.modal-field`/`.modal-field-row`/`.modal-checkbox`/`.modal-error`/`.modal-actions` (campos internos do modal — moldura e campos vivem juntos aqui, apesar do nome sugerir só a moldura, incluindo `select`/`textarea` dentro de `.modal-field`, com seta customizada via `background-image` porque o nativo do browser destoa do tema escuro), `.app-card*`, `.no-access`, `.checklist-box`/`.checklist-item`/`.checklist-item__info`/`.checklist-item__nome`/`.checklist-item__departamento`/`.checklist-empty`/`.checklist-search`/`.checklist-select-all` (lista com checkbox, segunda linha de detalhe e busca — usada nos checklists de Perfis de Acesso/Departamento em `usuarios.html`), `.dual-select`/`.dual-select__*` (vinculação em duas tabelas — não vinculados/vinculados, ver `docs/perfis-usuarios/perfis-usuarios.md` — usada em `usuarios.html` e no modal "Gerenciar Usuário" de todo shell), `.info-tooltip`/`.info-tooltip__*`/`.ajuda-modal__*` (botão "?" + tooltip + modal de "Mais informações", ver seção própria abaixo — usados por `static/js/ajuda-aplicacao.js`) e `.modal-overlay--top` (empilha um modal por cima de outro já aberto — usado só pelo modal de confirmação genérico, `static/js/confirm-modal.js`, ver "Modal de confirmação genérico" abaixo). |
|
||||||
| `perfis-acesso.css` | `.pa-*` (tela de Perfis de Acesso), incluindo as seções (`.ua-section*`) e campos específicos (`.ua-inline-add`/`.ua-departamento-item`/`.ua-active-toggle`/`.ua-liderados-field`) do formulário de edição de `usuarios.html`. |
|
| `perfis-acesso.css` | `.pa-*` (tela de Perfis de Acesso), incluindo as seções (`.ua-section*`) e campos específicos (`.ua-inline-add`/`.ua-departamento-item`/`.ua-active-toggle`/`.ua-liderados-field`) do formulário de edição de `usuarios.html`. |
|
||||||
| `calendario.css` | Só `.calendar-*` (grade mensal, células de dia, nav do mês) — os campos do modal de compromisso usam as classes genéricas `.modal-field`/`.modal-checkbox`/`.modal-error`/`.modal-actions` de `components.css`. |
|
| `calendario.css` | Só `.calendar-*` (grade mensal, células de dia, nav do mês) — os campos do modal de compromisso usam as classes genéricas `.modal-field`/`.modal-checkbox`/`.modal-error`/`.modal-actions` de `components.css`. |
|
||||||
|
|||||||
@ -44,6 +44,7 @@ Não há suíte de testes, lint ou build configurados neste projeto.
|
|||||||
| Indicador de Desempenho | Geradoc | [portal_api/indicadores/](portal_api/indicadores/README.md) |
|
| Indicador de Desempenho | Geradoc | [portal_api/indicadores/](portal_api/indicadores/README.md) |
|
||||||
| Não Conformidades | Relatórios > Qualidade | [portal_api/nao_conformidades/](portal_api/nao_conformidades/README.md) |
|
| Não Conformidades | Relatórios > Qualidade | [portal_api/nao_conformidades/](portal_api/nao_conformidades/README.md) |
|
||||||
| Relatório Contábil | Relatórios > Contabilidade | [portal_api/dashboard_contabil/](portal_api/dashboard_contabil/README.md) |
|
| Relatório Contábil | Relatórios > Contabilidade | [portal_api/dashboard_contabil/](portal_api/dashboard_contabil/README.md) |
|
||||||
|
| Relatório Contábil - De Paula (mesma ferramenta, para as empresas do próprio escritório) | Diretoria | [portal_api/dashboard_contabil/](portal_api/dashboard_contabil/README.md) |
|
||||||
| Temas Sazonais (marca P.I.D. sazonal, hoje só Halloween) | — (recurso visual, não é item de menu) | [docs/temas-sazonais/](docs/temas-sazonais/README.md) |
|
| Temas Sazonais (marca P.I.D. sazonal, hoje só Halloween) | — (recurso visual, não é item de menu) | [docs/temas-sazonais/](docs/temas-sazonais/README.md) |
|
||||||
| Identidade visual (logos, marca P.I.D. na UI, animações, intro pós-login) | — (camada transversal, não é item de menu) | [docs/identidade-visual/](docs/identidade-visual/README.md) |
|
| Identidade visual (logos, marca P.I.D. na UI, animações, intro pós-login) | — (camada transversal, não é item de menu) | [docs/identidade-visual/](docs/identidade-visual/README.md) |
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,7 @@ PAGINAS_FRONTEND = [
|
|||||||
"indicador-desempenho.html",
|
"indicador-desempenho.html",
|
||||||
"nao-conformidades.html",
|
"nao-conformidades.html",
|
||||||
"dashboard-contabil.html",
|
"dashboard-contabil.html",
|
||||||
|
"dashboard-contabil-de-paula.html",
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|||||||
22
plano.md
22
plano.md
@ -599,6 +599,28 @@ Nada de acessibilidade de teclado/leitor de tela nem `prefers-reduced-motion`.
|
|||||||
`runserver`. Todos os JS alterados passaram em `node --check`. **Não testado no
|
`runserver`. Todos os JS alterados passaram em `node --check`. **Não testado no
|
||||||
navegador** (sem renderização neste ambiente).
|
navegador** (sem renderização neste ambiente).
|
||||||
|
|
||||||
|
### 152. Seção "Diretoria" no menu e módulo novo em perfis já gravados (2026-10-02)
|
||||||
|
|
||||||
|
Parte estrutural da criação do Relatório Contábil - De Paula (detalhe da
|
||||||
|
aplicação na rodada 157 de `portal_api/dashboard_contabil/CHANGELOG.md`).
|
||||||
|
|
||||||
|
- **Seção nova "Diretoria"** (`diretoria` em `catalogo.MODULES`, entre
|
||||||
|
Relatórios e Relatórios Gerenciais, pedido do usuário), para ferramentas das
|
||||||
|
empresas do próprio escritório. Fora de `BASE_KEYS`/`SECTORAL_KEYS`: nasce
|
||||||
|
fechada para todo perfil, liberação manual pela tela. Bloco do menu
|
||||||
|
acrescentado nos 15 shells (14 existentes + `dashboard-contabil-de-paula.html`).
|
||||||
|
- **Correção em Perfis de Acesso**: a tela lia `permissions[modulo].enabled`
|
||||||
|
direto do JSON salvo, e um módulo novo não existe no JSON dos perfis já
|
||||||
|
gravados (o `seed_portal`, que acrescentava, não roda mais em produção).
|
||||||
|
Abrir qualquer perfil quebraria a tela. `pidCompletaPermissoes()`
|
||||||
|
(`profiles.js`) completa módulo/app ausente como desmarcado ao carregar, sem
|
||||||
|
alterar valor existente. Registrado no `CLAUDE.md` ("Módulo novo no catálogo
|
||||||
|
e perfis já gravados").
|
||||||
|
|
||||||
|
**Validação**: catálogo e `/api/me/` conferidos pelo cliente de teste do
|
||||||
|
Django; normalização conferida contra o JSON real do perfil 8 (nenhum valor
|
||||||
|
alterado, `diretoria` acrescentada desmarcada). **Não testado no navegador.**
|
||||||
|
|
||||||
## Limitações conhecidas / decisões assumidas
|
## Limitações conhecidas / decisões assumidas
|
||||||
|
|
||||||
- Calendário De Paula (empresa) não tem mais versão interna — depende
|
- Calendário De Paula (empresa) não tem mais versão interna — depende
|
||||||
|
|||||||
@ -13,13 +13,21 @@ from .models import (
|
|||||||
ConciliacaoFornecedorVinculo,
|
ConciliacaoFornecedorVinculo,
|
||||||
ConciliacaoFornecedorVinculoHistorico,
|
ConciliacaoFornecedorVinculoHistorico,
|
||||||
ContabilAchado,
|
ContabilAchado,
|
||||||
|
ContabilAchadoDePaula,
|
||||||
ContabilApuracao,
|
ContabilApuracao,
|
||||||
|
ContabilApuracaoDePaula,
|
||||||
ContabilApuracaoReprocessamento,
|
ContabilApuracaoReprocessamento,
|
||||||
|
ContabilApuracaoReprocessamentoDePaula,
|
||||||
ContabilConta,
|
ContabilConta,
|
||||||
|
ContabilContaDePaula,
|
||||||
ContabilLinhaAnaliseVertical,
|
ContabilLinhaAnaliseVertical,
|
||||||
|
ContabilLinhaAnaliseVerticalDePaula,
|
||||||
ContabilLinhaDre,
|
ContabilLinhaDre,
|
||||||
|
ContabilLinhaDreDePaula,
|
||||||
ContabilObservacao,
|
ContabilObservacao,
|
||||||
|
ContabilObservacaoDePaula,
|
||||||
ContabilObservacaoEdicao,
|
ContabilObservacaoEdicao,
|
||||||
|
ContabilObservacaoEdicaoDePaula,
|
||||||
Departamento,
|
Departamento,
|
||||||
EmpresaQuestor,
|
EmpresaQuestor,
|
||||||
Favorito,
|
Favorito,
|
||||||
@ -316,6 +324,17 @@ class ContabilAchadoAdmin(admin.ModelAdmin):
|
|||||||
search_fields = ("titulo", "mensagem")
|
search_fields = ("titulo", "mensagem")
|
||||||
|
|
||||||
|
|
||||||
|
# Relatório Contábil - De Paula (Diretoria): mesmas telas de admin da original.
|
||||||
|
admin.site.register(ContabilApuracaoDePaula, ContabilApuracaoAdmin)
|
||||||
|
admin.site.register(ContabilApuracaoReprocessamentoDePaula, ContabilApuracaoReprocessamentoAdmin)
|
||||||
|
admin.site.register(ContabilContaDePaula, ContabilContaAdmin)
|
||||||
|
admin.site.register(ContabilLinhaDreDePaula, ContabilLinhaDreAdmin)
|
||||||
|
admin.site.register(ContabilLinhaAnaliseVerticalDePaula, ContabilLinhaAnaliseVerticalAdmin)
|
||||||
|
admin.site.register(ContabilObservacaoDePaula, ContabilObservacaoAdmin)
|
||||||
|
admin.site.register(ContabilObservacaoEdicaoDePaula, ContabilObservacaoEdicaoAdmin)
|
||||||
|
admin.site.register(ContabilAchadoDePaula, ContabilAchadoAdmin)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ConciliacaoFornecedor)
|
@admin.register(ConciliacaoFornecedor)
|
||||||
class ConciliacaoFornecedorAdmin(admin.ModelAdmin):
|
class ConciliacaoFornecedorAdmin(admin.ModelAdmin):
|
||||||
list_display = ("codigo_empresa", "nome_empresa", "data_base", "criado_por", "criado_em")
|
list_display = ("codigo_empresa", "nome_empresa", "data_base", "criado_por", "criado_em")
|
||||||
|
|||||||
@ -16,6 +16,7 @@ MODULES = [
|
|||||||
{"key": "portais", "label": "Portais"},
|
{"key": "portais", "label": "Portais"},
|
||||||
{"key": "geradoc", "label": "Geradoc"},
|
{"key": "geradoc", "label": "Geradoc"},
|
||||||
{"key": "relatorios", "label": "Relatórios"},
|
{"key": "relatorios", "label": "Relatórios"},
|
||||||
|
{"key": "diretoria", "label": "Diretoria"},
|
||||||
{"key": "relatorios-gerenciais", "label": "Relatórios Gerenciais"},
|
{"key": "relatorios-gerenciais", "label": "Relatórios Gerenciais"},
|
||||||
{"key": "utilitarios", "label": "Utilitários"},
|
{"key": "utilitarios", "label": "Utilitários"},
|
||||||
{"key": "integracoes", "label": "Integrações"},
|
{"key": "integracoes", "label": "Integrações"},
|
||||||
@ -63,6 +64,14 @@ MODULE_APPS = {
|
|||||||
"tools": [{"key": "dashboard-contabil", "label": "Relatório Contábil"}],
|
"tools": [{"key": "dashboard-contabil", "label": "Relatório Contábil"}],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
# Ferramentas para as empresas do próprio escritório. Fora de BASE_KEYS e
|
||||||
|
# SECTORAL_KEYS de propósito: nenhum perfil ganha acesso automático, a
|
||||||
|
# liberação é sempre manual pela tela de Perfis de Acesso. O Relatório
|
||||||
|
# Contábil - De Paula tem permissão própria, independente da
|
||||||
|
# `dashboard-contabil` de Relatórios.
|
||||||
|
"diretoria": [
|
||||||
|
{"key": "dashboard-contabil-de-paula", "label": "Relatório Contábil - De Paula"},
|
||||||
|
],
|
||||||
"relatorios-gerenciais": [
|
"relatorios-gerenciais": [
|
||||||
{"key": "visao-diretoria", "label": "Visão Diretoria"},
|
{"key": "visao-diretoria", "label": "Visão Diretoria"},
|
||||||
{"key": "visao-gerencial", "label": "Visão Gerencial"},
|
{"key": "visao-gerencial", "label": "Visão Gerencial"},
|
||||||
|
|||||||
@ -546,3 +546,17 @@ Pedido do usuário, na mesma linha da Conciliação de Fornecedores (rodada 59 d
|
|||||||
- Os PDFs já guardados ficam em `media/contabil/apuracoes/` (decisão do usuário: não excluir); sem a coluna, nenhum registro aponta mais para eles. Foram apagados por engano nesta rodada e restaurados do git só os versionados (1 de 12); os demais têm cópia em `Projetos\Balancetes`/Downloads. As 12 análises continuam abrindo normalmente.
|
- Os PDFs já guardados ficam em `media/contabil/apuracoes/` (decisão do usuário: não excluir); sem a coluna, nenhum registro aponta mais para eles. Foram apagados por engano nesta rodada e restaurados do git só os versionados (1 de 12); os demais têm cópia em `Projetos\Balancetes`/Downloads. As 12 análises continuam abrindo normalmente.
|
||||||
|
|
||||||
Mexe em models.py/views.py/serializers.py e migração: exige reiniciar o `runserver`.
|
Mexe em models.py/views.py/serializers.py e migração: exige reiniciar o `runserver`.
|
||||||
|
|
||||||
|
### Rodada 157 — Relatório Contábil - De Paula, na seção nova Diretoria (2026-10-02)
|
||||||
|
|
||||||
|
Pedido: duplicar o Relatório Contábil para as empresas do próprio escritório, numa seção nova do menu chamada "Diretoria", compartilhando as regras com a original, e sem que quem tem acesso só ao Relatório Contábil possa, em hipótese alguma, acessar a versão De Paula. Plano aprovado antes da execução.
|
||||||
|
|
||||||
|
Decisões confirmadas com o usuário: cadastro de indicadores **compartilhado** entre as duas (editar numa reflete na outra); seção Diretoria entre Relatórios e Relatórios Gerenciais; sem notificação de "nova ferramenta" no sino.
|
||||||
|
|
||||||
|
Mesmo modelo de isolamento da "Importação de Plano de Saúde - De Paula" (tabelas, endpoints e permissão próprios, sem FK cruzando), mas com o código **parametrizado** em vez de copiado: models viraram base abstrata + duas concretas (a original sem nenhuma alteração de banco, confirmada por `makemigrations --dry-run`), ViewSets ganharam atributos de classe e os `*DePaulaViewSet` só os trocam, helpers passaram a descobrir o model pela própria apuração, e `dashboard-contabil.js` lê os endpoints de `PID_DC_CONFIG`. Migração `0090` só cria as 8 tabelas novas. Módulo `diretoria` novo em `catalogo.py`, fora de `BASE_KEYS`/`SECTORAL_KEYS`: nasce fechado para todo perfil, liberação manual pela tela.
|
||||||
|
|
||||||
|
Validado antes de entregar: leitura da original idêntica entre o código anterior e o refatorado nas análises reais; fluxo completo de escrita idêntico nos dois (em transação desfeita no fim); mesmo fluxo na De Paula e testes de isolamento (403 cruzado por permissão, 404 para id da outra aplicação, 400 para observação apontando apuração da outra). Detalhe em "Relatório Contábil - De Paula" no `CLAUDE.md` desta pasta.
|
||||||
|
|
||||||
|
**Correção encontrada no caminho, em `profiles.js`**: a tela de Perfis de Acesso lia `permissions[modulo].enabled` direto do JSON salvo do perfil. Um módulo novo no catálogo não existe no JSON dos perfis já gravados (antes, o `seed_portal` acrescentava; em produção ele não roda mais), então abrir qualquer perfil quebraria a tela. `pidCompletaPermissoes()` completa módulo/app ausente como desmarcado ao carregar, sem alterar nenhum valor existente (conferido contra o perfil 8 real).
|
||||||
|
|
||||||
|
Mexe em models.py/views.py/serializers.py/urls.py e migração: exige reiniciar o `runserver`.
|
||||||
|
|||||||
@ -10,6 +10,33 @@ Ferramenta que otimiza a conferência de balancetes hoje feita manualmente pelo
|
|||||||
|
|
||||||
**Permissão**: toggle único `apps["dashboard-contabil"]` em `permissoes["relatorios"]` (subgrupo "Contabilidade"), checado via `PermissaoApp("relatorios", "dashboard-contabil")` em todos os ViewSets. **Nasce restrita ao perfil "Inovação"** (override em `seed_portal.py`), já que expõe balancete/DRE completos dos clientes.
|
**Permissão**: toggle único `apps["dashboard-contabil"]` em `permissoes["relatorios"]` (subgrupo "Contabilidade"), checado via `PermissaoApp("relatorios", "dashboard-contabil")` em todos os ViewSets. **Nasce restrita ao perfil "Inovação"** (override em `seed_portal.py`), já que expõe balancete/DRE completos dos clientes.
|
||||||
|
|
||||||
|
## Relatório Contábil - De Paula (segunda instância, seção Diretoria)
|
||||||
|
|
||||||
|
Existe uma **segunda aplicação**, `dashboard-contabil-de-paula` ("Relatório Contábil - De Paula"), na seção nova **Diretoria** do menu (entre Relatórios e Relatórios Gerenciais). É a mesma ferramenta, para as **empresas do próprio escritório**. Mesmo modelo de "Importação de Plano de Saúde - De Paula" (`portal_api/planos_saude/CLAUDE.md`): regras compartilhadas, dados e permissão separados.
|
||||||
|
|
||||||
|
**Requisito do usuário, que orienta tudo abaixo: quem tem acesso só ao Relatório Contábil não pode, em hipótese alguma, acessar o da De Paula.** São três camadas independentes, cada uma suficiente sozinha:
|
||||||
|
|
||||||
|
1. **Tabelas próprias.** Cada model tem uma versão `DePaula` (`ContabilApuracaoDePaula`, `ContabilContaDePaula`, `ContabilLinhaDreDePaula`, `ContabilLinhaAnaliseVerticalDePaula`, `ContabilAchadoDePaula`, `ContabilObservacaoDePaula`, `ContabilObservacaoEdicaoDePaula`, `ContabilApuracaoReprocessamentoDePaula`, migração `0090`), sem nenhuma FK cruzando com as originais. O histórico mês a mês e as observações por empresa + conta também ficam separados: a mesma empresa analisada nas duas aplicações tem dois históricos independentes.
|
||||||
|
2. **Endpoints próprios**, os mesmos da tabela "API" abaixo com sufixo `-de-paula` (`/api/contabil-apuracoes-de-paula/`, `/api/contabil-contas-de-paula/`, `/api/contabil-linhas-dre-de-paula/`, `/api/contabil-linhas-analise-vertical-de-paula/`, `/api/contabil-observacoes-de-paula/`, `/api/contabil-achados-de-paula/`). Cada queryset só enxerga as tabelas da De Paula, então um id da original devolve 404 ali. Os `PrimaryKeyRelatedField` de apuração dos serializers de observação (`ContabilObservacaoCreateDePaulaSerializer`/`ContabilObservacaoApuracaoDePaulaSerializer`) também usam o queryset da De Paula: apontar uma observação para uma apuração da original devolve 400.
|
||||||
|
3. **Permissão própria**: `PermissaoApp("diretoria", "dashboard-contabil-de-paula")` (`_CONTABIL_DE_PAULA_PERMISSAO` em `views.py`). Ter `relatorios > dashboard-contabil` não dá acesso, e o inverso também vale.
|
||||||
|
|
||||||
|
**Nasce fechada para todos.** O módulo `diretoria` está fora de `BASE_KEYS`/`SECTORAL_KEYS`, e nenhum perfil já gravado em produção tem a chave, que `permissoes_efetivas()` trata como desabilitada. A liberação é sempre manual pela tela de Perfis de Acesso. `seed_portal.py` força `False` em todo perfil que não seja o 8 (só vale para ambiente novo, o seed não roda em produção).
|
||||||
|
|
||||||
|
**O que é compartilhado** (nada disso foi copiado):
|
||||||
|
- todo este pacote (parsers, regras, fórmula, chaves, exportação, PDF do Resumo);
|
||||||
|
- o **cadastro de indicadores** (`IndicadorContabilDefinicao`, `/api/contabil-indicadores-definicoes/`), decisão explícita do usuário: quem tem qualquer uma das duas permissões gerencia os mesmos indicadores (`OR` das duas `PermissaoApp` em `IndicadorContabilDefinicaoViewSet`), e editar um indicador numa aplicação reflete na outra. Ao excluir um indicador, a limpeza de `indicadores_selecionados`/`indicadores_ocultos` varre as apurações das duas;
|
||||||
|
- as views, os serializers e o frontend, **parametrizados** (abaixo);
|
||||||
|
- o template do relatório do cliente (`dashboard-contabil-relatorio.html`), com os links de XLSX/PDF montados a partir de `prefixo_api` no contexto.
|
||||||
|
|
||||||
|
**Como a parametrização funciona** (diferente do Plano de Saúde, que duplicou as views: aqui são cerca de 1.000 linhas e duas cópias divergiriam):
|
||||||
|
- **Models**: cada um é uma base abstrata (`ContabilApuracaoBase`, `ContabilContaBase`, ...) com campos, constantes e métodos, mais duas concretas. As FKs ficam só nas concretas, porque cada variante aponta para os próprios models. Os `related_name` das FKs para a apuração são os mesmos nas duas (`contas`, `linhas_dre`, `linhas_analise_vertical`, `achados`, `observacoes`, `reprocessamentos`); os de `Usuario` têm sufixo `_de_paula`.
|
||||||
|
- **Views**: os ViewSets originais têm atributos de classe (`permissao_app`, `queryset`, serializers, `prefixo_api`), e os `*DePaulaViewSet` são subclasses que só trocam esses atributos. Os helpers descobrem o model pela própria apuração: `apuracao.contas.create(...)`, `apuracao.achados.model`, `_contabil_modelo_observacao(apuracao)` (= `apuracao.observacoes.model`), `type(apuracao)` para o histórico. `_contabil_monta_historico(modelo_apuracao, cabecalho)` recebe o model fixado por `functools.partial`, porque o pipeline o chama só com o cabeçalho.
|
||||||
|
- **Frontend**: `dashboard-contabil.js` lê `PID_DC_CONFIG` (`moduleKey`, `appKey` e os 6 prefixos de endpoint); `dashboard-contabil-de-paula.html` define `window.PID_DC_CONFIG` num `<script>` inline antes do JS, e a página original não define nada (cai nos defaults). O template da De Paula é uma cópia da original com título, item ativo do menu, textos e esse bloco de configuração trocados.
|
||||||
|
|
||||||
|
> **Regra para qualquer mudança futura nesta aplicação**: nenhum código de view pode citar `ContabilApuracao`/`ContabilConta`/`ContabilObservacao`/... direto para consultar ou gravar (constantes como `STATUS_CONCLUIDA`/`ALVO_CONTA` e o estático `chave_conta()` podem, são iguais nas duas). E nenhuma rota de dados pode ser escrita à mão no JS: sempre `PID_DC_CONFIG.endpointX`. Uma referência direta faria a De Paula ler ou gravar nas tabelas da original. Mudança no markup de `dashboard-contabil.html` precisa ser replicada em `dashboard-contabil-de-paula.html`.
|
||||||
|
|
||||||
|
**Validação feita antes de liberar**: (a) comparação dos endpoints de leitura da original (lista, detalhe, observações, indicadores, relatório HTML) entre o código anterior à refatoração e o atual, nas análises reais do banco: respostas idênticas; (b) fluxo completo de escrita da original (criar, duplicada, reprocessar, validar, observação com edição/encerrar/reativar, achado, indicadores, resumo, relatório, XLSX, PDF, concluir, excluir concluída, Contabit) nos dois códigos, dentro de transação desfeita no fim: resultado idêntico; (c) o mesmo fluxo na De Paula, mais os testes de isolamento: usuário só da original recebe 403 em todo endpoint da De Paula e vice-versa; id da original nos endpoints da De Paula dá 404; observação da De Paula apontando para apuração da original dá 400; observações da mesma empresa não cruzam.
|
||||||
|
|
||||||
## Decisões de escopo (confirmadas com o usuário)
|
## Decisões de escopo (confirmadas com o usuário)
|
||||||
|
|
||||||
- **Dois modelos de PDF, detectados sozinhos**: Questor e Contabit. O usuário nunca escolhe; `ContabilApuracao.leiaute` guarda o detectado. Ver "Leiaute Contabit" abaixo para o que muda.
|
- **Dois modelos de PDF, detectados sozinhos**: Questor e Contabit. O usuário nunca escolhe; `ContabilApuracao.leiaute` guarda o detectado. Ver "Leiaute Contabit" abaixo para o que muda.
|
||||||
@ -113,7 +140,7 @@ Validado de duas formas: árvore sintética reproduzindo o caso do usuário (con
|
|||||||
|
|
||||||
## Models (`portal_api/models.py`)
|
## Models (`portal_api/models.py`)
|
||||||
|
|
||||||
Padrão cabeçalho → linhas de detalhe → apontamentos, mesma filosofia de `IndicadorApuracao`/`IndicadorApuracaoColaborador`.
|
Padrão cabeçalho → linhas de detalhe → apontamentos, mesma filosofia de `IndicadorApuracao`/`IndicadorApuracaoColaborador`. Cada model abaixo é, no código, uma base abstrata (`*Base`) mais duas concretas: a da original e a `*DePaula` (ver "Relatório Contábil - De Paula"). Os campos descritos aqui valem para as duas.
|
||||||
|
|
||||||
### Apuração e linhas
|
### Apuração e linhas
|
||||||
|
|
||||||
@ -251,7 +278,7 @@ O miolo `<svg>` de cada um dos 11 ícones existe em `_CONTABIL_ICONES_SVG` (`vie
|
|||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
Todos os endpoints usam `PermissaoApp("relatorios", "dashboard-contabil")`. Nenhum deles aparece na tabela de API do `CLAUDE.md` da raiz de propósito — endpoint de aplicação mora na doc da aplicação.
|
Todos os endpoints usam `PermissaoApp("relatorios", "dashboard-contabil")`, exceto `/api/contabil-indicadores-definicoes/`, que aceita essa permissão **ou** a do Relatório Contábil - De Paula (cadastro compartilhado). Os mesmos endpoints com sufixo `-de-paula` servem a De Paula, com `PermissaoApp("diretoria", "dashboard-contabil-de-paula")` (ver "Relatório Contábil - De Paula" acima). Nenhum deles aparece na tabela de API do `CLAUDE.md` da raiz de propósito — endpoint de aplicação mora na doc da aplicação.
|
||||||
|
|
||||||
| Endpoint | Método | Uso |
|
| Endpoint | Método | Uso |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@ -522,6 +549,8 @@ Cor por sinal só onde é seguro: `sign` (ROA/ROE/EBIT/EBITDA — positivo verde
|
|||||||
|
|
||||||
## Riscos conhecidos e armadilhas
|
## Riscos conhecidos e armadilhas
|
||||||
|
|
||||||
|
- **Duas aplicações sobre o mesmo código**: referência direta a um model `Contabil*` numa view, ou rota literal no JS, faz o Relatório Contábil - De Paula ler ou gravar nas tabelas da original. Ver a regra em "Relatório Contábil - De Paula".
|
||||||
|
|
||||||
- **Leiaute Contabit calibrado contra um único arquivo** — ver "Leiaute Contabit": códigos das regras, passos de nível e a premissa do sinal negativo precisam ser conferidos a cada arquivo novo de outro cliente.
|
- **Leiaute Contabit calibrado contra um único arquivo** — ver "Leiaute Contabit": códigos das regras, passos de nível e a premissa do sinal negativo precisam ser conferidos a cada arquivo novo de outro cliente.
|
||||||
- **Códigos de classificação fixos** — se um cliente usar plano de contas com numeração diferente, indicadores e a regra de caixa saem errados **silenciosamente**. Revisar contra mais balancetes reais de empresas diferentes antes de confiar cegamente num valor exibido ao cliente.
|
- **Códigos de classificação fixos** — se um cliente usar plano de contas com numeração diferente, indicadores e a regra de caixa saem errados **silenciosamente**. Revisar contra mais balancetes reais de empresas diferentes antes de confiar cegamente num valor exibido ao cliente.
|
||||||
- **Descrições coladas em PDF de fonte atípica** — não têm correção segura; a ferramenta avisa por badge. Valores monetários nunca são afetados.
|
- **Descrições coladas em PDF de fonte atípica** — não têm correção segura; a ferramenta avisa por badge. Valores monetários nunca são afetados.
|
||||||
|
|||||||
@ -4,11 +4,14 @@
|
|||||||
|
|
||||||
Otimiza a conferência de balancetes hoje feita manualmente pelo Fisco/Contábil: o contador anexa o PDF de Balancete + DRE de uma empresa (mesmo relatório hoje enviado ao cliente, gerado pelo Questor ou pelo Contabit; o modelo é detectado sozinho, e no Contabit o código da empresa vem do nome do arquivo, ex. "1512 - Balancete 082026.pdf"), a ferramenta extrai as contas automaticamente e roda um conjunto de checagens de auditoria (saldos negativos de caixa, contas transitórias/genéricas com saldo, contas que deveriam estar zeradas, diferença débito/crédito, lucro do balancete diferente da DRE, variações atípicas na DRE) — o contador revisa os achados, marca contas/linhas como validadas, registra observações por conta/linha (thread inline na própria tabela, com um toggle "mostrar ao cliente" que nasce desmarcado), escreve um resumo livre do fechamento (considerações/análises, texto rico com imagem) e conclui a análise. As observações não morrem com a competência: cada uma fica no histórico daquela empresa + conta, reaparecendo na análise do mês seguinte assinada por quem escreveu e com a data, travada pra edição, até o contador encerrá-la para as próximas execuções. Quando o PDF traz a seção "Demonstração Mensal (Análise Vertical)" (histórico de 3 meses, valor+variação percentual por linha), ela também é extraída e ganha sua própria aba de revisão (mesmos recursos de observação/validado/ocultar do Balancete/D.R.E.). Se o arquivo original estava errado/incompleto, o botão "Reprocessar" reanexa um PDF novo pra mesma empresa/competência sem perder nada já registrado — conta/linha sem mudança mantém observação e validação, a que mudou volta pra "não validada" com um alerta, e achado já tratado nunca é apagado ou tem sua justificativa sobrescrita. O botão "Gerar Relatório" gera um relatório HTML autocontido (resumo do fechamento, indicadores financeiros, DRE/Balancete/Análise Vertical agrupados, observações do contador) com a marca do escritório, pronto pra enviar ao administrador da empresa — a aba "Resumo" desse relatório também pode ser exportada avulsa em PDF (resumo do fechamento + indicadores + observações, sem o resto do relatório).
|
Otimiza a conferência de balancetes hoje feita manualmente pelo Fisco/Contábil: o contador anexa o PDF de Balancete + DRE de uma empresa (mesmo relatório hoje enviado ao cliente, gerado pelo Questor ou pelo Contabit; o modelo é detectado sozinho, e no Contabit o código da empresa vem do nome do arquivo, ex. "1512 - Balancete 082026.pdf"), a ferramenta extrai as contas automaticamente e roda um conjunto de checagens de auditoria (saldos negativos de caixa, contas transitórias/genéricas com saldo, contas que deveriam estar zeradas, diferença débito/crédito, lucro do balancete diferente da DRE, variações atípicas na DRE) — o contador revisa os achados, marca contas/linhas como validadas, registra observações por conta/linha (thread inline na própria tabela, com um toggle "mostrar ao cliente" que nasce desmarcado), escreve um resumo livre do fechamento (considerações/análises, texto rico com imagem) e conclui a análise. As observações não morrem com a competência: cada uma fica no histórico daquela empresa + conta, reaparecendo na análise do mês seguinte assinada por quem escreveu e com a data, travada pra edição, até o contador encerrá-la para as próximas execuções. Quando o PDF traz a seção "Demonstração Mensal (Análise Vertical)" (histórico de 3 meses, valor+variação percentual por linha), ela também é extraída e ganha sua própria aba de revisão (mesmos recursos de observação/validado/ocultar do Balancete/D.R.E.). Se o arquivo original estava errado/incompleto, o botão "Reprocessar" reanexa um PDF novo pra mesma empresa/competência sem perder nada já registrado — conta/linha sem mudança mantém observação e validação, a que mudou volta pra "não validada" com um alerta, e achado já tratado nunca é apagado ou tem sua justificativa sobrescrita. O botão "Gerar Relatório" gera um relatório HTML autocontido (resumo do fechamento, indicadores financeiros, DRE/Balancete/Análise Vertical agrupados, observações do contador) com a marca do escritório, pronto pra enviar ao administrador da empresa — a aba "Resumo" desse relatório também pode ser exportada avulsa em PDF (resumo do fechamento + indicadores + observações, sem o resto do relatório).
|
||||||
|
|
||||||
|
**Relatório Contábil - De Paula** (seção Diretoria do menu): a mesma ferramenta para as empresas do próprio escritório. Regras e cadastro de indicadores compartilhados com a original; análises, histórico, observações e permissão totalmente separados (quem tem acesso só ao Relatório Contábil não acessa esta). Liberada manualmente por perfil em Perfis de Acesso.
|
||||||
|
|
||||||
**Ainda fora de escopo**: indicadores para empresas do Contabit (a seção fica oculta nesse modelo); consolidação entre várias empresas/competências ao mesmo tempo (é sempre uma apuração por vez).
|
**Ainda fora de escopo**: indicadores para empresas do Contabit (a seção fica oculta nesse modelo); consolidação entre várias empresas/competências ao mesmo tempo (é sempre uma apuração por vez).
|
||||||
|
|
||||||
## Onde mexer
|
## Onde mexer
|
||||||
|
|
||||||
- `portal_api/dashboard_contabil/` — `parser.py` (extração do PDF Questor via `pdfplumber` e detecção do modelo), `parser_contabit.py` (extração do PDF Contabit), `regras.py` (motor de regras de auditoria), `indicadores.py` (indicadores financeiros do relatório), `pipeline.py` (orquestração), `modelos.py` (dataclasses), `chaves.py` (chave natural de conta/linha), `exportacao.py` (Balancete/DRE em XLSX), `resumo_pdf.py` (Resumo do Fechamento + Indicadores + Observações em PDF, avulso).
|
- `portal_api/dashboard_contabil/` — `parser.py` (extração do PDF Questor via `pdfplumber` e detecção do modelo), `parser_contabit.py` (extração do PDF Contabit), `regras.py` (motor de regras de auditoria), `indicadores.py` (indicadores financeiros do relatório), `pipeline.py` (orquestração), `modelos.py` (dataclasses), `chaves.py` (chave natural de conta/linha), `exportacao.py` (Balancete/DRE em XLSX), `resumo_pdf.py` (Resumo do Fechamento + Indicadores + Observações em PDF, avulso).
|
||||||
- `ContabilApuracao`/`ContabilConta`/`ContabilLinhaDre`/`ContabilLinhaAnaliseVertical`/`ContabilAchado`/`ContabilObservacao`/`ContabilObservacaoEdicao` (`portal_api/models.py`).
|
- `ContabilApuracao`/`ContabilConta`/`ContabilLinhaDre`/`ContabilLinhaAnaliseVertical`/`ContabilAchado`/`ContabilObservacao`/`ContabilObservacaoEdicao` (`portal_api/models.py`).
|
||||||
- `dashboard-contabil.html` / `static/js/dashboard-contabil.js` / `static/css/dashboard-contabil.css`.
|
- As mesmas classes com sufixo `DePaula` para o Relatório Contábil - De Paula (cada model é uma base abstrata + duas concretas).
|
||||||
|
- `dashboard-contabil.html` / `dashboard-contabil-de-paula.html` / `static/js/dashboard-contabil.js` (parametrizado por `PID_DC_CONFIG`) / `static/css/dashboard-contabil.css`.
|
||||||
- `dashboard-contabil-relatorio.html` (relatório em si) / `portal_api/templatetags/contabil_extras.py` (filtros de formatação).
|
- `dashboard-contabil-relatorio.html` (relatório em si) / `portal_api/templatetags/contabil_extras.py` (filtros de formatação).
|
||||||
|
|||||||
@ -196,6 +196,12 @@ class Command(BaseCommand):
|
|||||||
# decisão explícita do usuário (dado de cliente, mas ferramenta
|
# decisão explícita do usuário (dado de cliente, mas ferramenta
|
||||||
# ainda em validação), liberada depois pela tela.
|
# ainda em validação), liberada depois pela tela.
|
||||||
permissoes["utilitarios"]["apps"]["conciliacao-fornecedores"] = False
|
permissoes["utilitarios"]["apps"]["conciliacao-fornecedores"] = False
|
||||||
|
# "diretoria" (Relatório Contábil - De Paula, empresas do próprio
|
||||||
|
# escritório) já fica de fora por não estar em nenhuma lista de
|
||||||
|
# chaves acima; o override é só para continuar restrita mesmo se
|
||||||
|
# o módulo um dia entrar na lista de algum perfil.
|
||||||
|
permissoes["diretoria"]["enabled"] = False
|
||||||
|
permissoes["diretoria"]["apps"]["dashboard-contabil-de-paula"] = False
|
||||||
# `nome` só é gravado na criação — reexecuções não devem reverter uma
|
# `nome` só é gravado na criação — reexecuções não devem reverter uma
|
||||||
# renomeação feita depois pela própria tela de Perfis de Acesso (mesmo
|
# renomeação feita depois pela própria tela de Perfis de Acesso (mesmo
|
||||||
# espírito de "só inicializa na primeira criação" já usado pra
|
# espírito de "só inicializa na primeira criação" já usado pra
|
||||||
|
|||||||
202
portal_api/migrations/0090_relatorio_contabil_de_paula.py
Normal file
202
portal_api/migrations/0090_relatorio_contabil_de_paula.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
# Generated by Django 6.0.7 on 2026-10-02 12:20
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import portal_api.models
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('portal_api', '0089_contabil_indicador_sem_arquivo'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ContabilApuracaoDePaula',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('codigo_empresa', models.CharField(max_length=20, verbose_name='Código da empresa')),
|
||||||
|
('nome_empresa', models.CharField(max_length=255, verbose_name='Nome da empresa')),
|
||||||
|
('cnpj', models.CharField(blank=True, max_length=20, verbose_name='CNPJ')),
|
||||||
|
('competencia', models.DateField(verbose_name='Competência')),
|
||||||
|
('periodo_inicio', models.DateField(verbose_name='Início do período')),
|
||||||
|
('periodo_fim', models.DateField(verbose_name='Fim do período')),
|
||||||
|
('status', models.CharField(choices=[('revisao', 'Em revisão'), ('concluida', 'Concluída')], default='revisao', max_length=20, verbose_name='Status')),
|
||||||
|
('criado_em', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('concluida_em', models.DateTimeField(blank=True, null=True, verbose_name='Concluída em')),
|
||||||
|
('indicadores_ocultos', models.JSONField(blank=True, default=list, verbose_name='Indicadores ocultos no relatório')),
|
||||||
|
('indicadores_selecionados', models.JSONField(blank=True, default=list, verbose_name='Indicadores não padrão selecionados')),
|
||||||
|
('resumo_fechamento', models.TextField(blank=True, validators=[portal_api.models.validar_tamanho_resumo_fechamento_contabil], verbose_name='Resumo do Fechamento')),
|
||||||
|
('analise_vertical_meses', models.JSONField(blank=True, default=list, verbose_name='Meses da Análise Vertical')),
|
||||||
|
('fonte_pdf_atipica', models.BooleanField(default=False, verbose_name='PDF de fonte atípica (risco de nomenclatura)')),
|
||||||
|
('leiaute', models.CharField(choices=[('questor', 'Questor'), ('contabit', 'Contabit')], db_default='questor', max_length=20, verbose_name='Modelo do arquivo')),
|
||||||
|
('criado_por', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='apuracoes_contabeis_de_paula', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Apuração do Relatório Contábil - De Paula',
|
||||||
|
'verbose_name_plural': 'Apurações do Relatório Contábil - De Paula',
|
||||||
|
'ordering': ['-competencia', '-criado_em'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ContabilApuracaoReprocessamentoDePaula',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('reprocessado_em', models.DateTimeField(auto_now_add=True, verbose_name='Reprocessado em')),
|
||||||
|
('apuracao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reprocessamentos', to='portal_api.contabilapuracaodepaula')),
|
||||||
|
('reprocessado_por', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='reprocessamentos_contabeis_de_paula', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Reprocessamento do Relatório Contábil - De Paula',
|
||||||
|
'verbose_name_plural': 'Reprocessamentos do Relatório Contábil - De Paula',
|
||||||
|
'ordering': ['-reprocessado_em', '-id'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ContabilContaDePaula',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('ordem', models.IntegerField(default=0, verbose_name='Ordem')),
|
||||||
|
('conta_numero', models.IntegerField(blank=True, null=True, verbose_name='Número da conta')),
|
||||||
|
('codigo', models.CharField(max_length=60, verbose_name='Classificação')),
|
||||||
|
('descricao', models.CharField(max_length=255, verbose_name='Descrição')),
|
||||||
|
('tipo', models.CharField(max_length=1, verbose_name='Tipo')),
|
||||||
|
('saldo_anterior', models.DecimalField(decimal_places=2, max_digits=16)),
|
||||||
|
('debito', models.DecimalField(decimal_places=2, max_digits=16)),
|
||||||
|
('credito', models.DecimalField(decimal_places=2, max_digits=16)),
|
||||||
|
('saldo_atual', models.DecimalField(decimal_places=2, max_digits=16)),
|
||||||
|
('validado', models.BooleanField(default=False, verbose_name='Validado pelo contador')),
|
||||||
|
('alterada_reprocessamento', models.BooleanField(default=False, verbose_name='Alterada no último reprocessamento')),
|
||||||
|
('valor_anterior_reprocessamento', models.DecimalField(blank=True, decimal_places=2, max_digits=16, null=True, verbose_name='Saldo atual antes do último reprocessamento')),
|
||||||
|
('apuracao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contas', to='portal_api.contabilapuracaodepaula')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Conta do Balancete (Relatório Contábil - De Paula)',
|
||||||
|
'verbose_name_plural': 'Contas do Balancete (Relatório Contábil - De Paula)',
|
||||||
|
'ordering': ['ordem', 'id'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ContabilLinhaAnaliseVerticalDePaula',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('ordem', models.IntegerField(verbose_name='Ordem')),
|
||||||
|
('descricao', models.CharField(max_length=255, verbose_name='Descrição')),
|
||||||
|
('nivel', models.IntegerField(default=0, verbose_name='Nível de indentação')),
|
||||||
|
('totalizador', models.BooleanField(default=False, verbose_name='Linha totalizadora')),
|
||||||
|
('valores', models.JSONField(default=list, verbose_name='Valores mensais')),
|
||||||
|
('validado', models.BooleanField(default=False, verbose_name='Validado pelo contador')),
|
||||||
|
('alterada_reprocessamento', models.BooleanField(default=False, verbose_name='Alterada no último reprocessamento')),
|
||||||
|
('valores_anterior_reprocessamento', models.JSONField(blank=True, default=list, verbose_name='Valores mensais antes do último reprocessamento')),
|
||||||
|
('apuracao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='linhas_analise_vertical', to='portal_api.contabilapuracaodepaula')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Linha da Análise Vertical (Relatório Contábil - De Paula)',
|
||||||
|
'verbose_name_plural': 'Linhas da Análise Vertical (Relatório Contábil - De Paula)',
|
||||||
|
'ordering': ['ordem', 'id'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ContabilAchadoDePaula',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('regra', models.CharField(max_length=60, verbose_name='Regra')),
|
||||||
|
('severidade', models.CharField(choices=[('alta', 'Alta'), ('media', 'Média'), ('baixa', 'Baixa')], max_length=10, verbose_name='Severidade')),
|
||||||
|
('titulo', models.CharField(max_length=255, verbose_name='Título')),
|
||||||
|
('mensagem', models.TextField(verbose_name='Mensagem')),
|
||||||
|
('valor_referencia', models.DecimalField(blank=True, decimal_places=2, max_digits=16, null=True)),
|
||||||
|
('status', models.CharField(choices=[('pendente', 'Pendente'), ('tratado', 'Tratado'), ('ignorado', 'Ignorado')], default='pendente', max_length=10, verbose_name='Status')),
|
||||||
|
('observacao_contador', models.TextField(blank=True, verbose_name='Observação do contador')),
|
||||||
|
('tratado_em', models.DateTimeField(blank=True, null=True, verbose_name='Tratado em')),
|
||||||
|
('oculto_no_relatorio', models.BooleanField(default=False, verbose_name='Observação oculta no relatório')),
|
||||||
|
('tratado_por', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='achados_contabeis_tratados_de_paula', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('apuracao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='achados', to='portal_api.contabilapuracaodepaula')),
|
||||||
|
('conta', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='achados', to='portal_api.contabilcontadepaula')),
|
||||||
|
('linha_analise_vertical', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='achados', to='portal_api.contabillinhaanaliseverticaldepaula')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Achado do Relatório Contábil - De Paula',
|
||||||
|
'verbose_name_plural': 'Achados do Relatório Contábil - De Paula',
|
||||||
|
'ordering': ['-severidade', 'id'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ContabilLinhaDreDePaula',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('ordem', models.IntegerField(verbose_name='Ordem')),
|
||||||
|
('descricao', models.CharField(max_length=255, verbose_name='Descrição')),
|
||||||
|
('nivel', models.IntegerField(default=0, verbose_name='Nível de indentação')),
|
||||||
|
('valor', models.DecimalField(decimal_places=2, max_digits=16)),
|
||||||
|
('totalizador', models.BooleanField(default=False, verbose_name='Linha totalizadora')),
|
||||||
|
('validado', models.BooleanField(default=False, verbose_name='Validado pelo contador')),
|
||||||
|
('alterada_reprocessamento', models.BooleanField(default=False, verbose_name='Alterada no último reprocessamento')),
|
||||||
|
('valor_anterior_reprocessamento', models.DecimalField(blank=True, decimal_places=2, max_digits=16, null=True, verbose_name='Valor antes do último reprocessamento')),
|
||||||
|
('apuracao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='linhas_dre', to='portal_api.contabilapuracaodepaula')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Linha da DRE (Relatório Contábil - De Paula)',
|
||||||
|
'verbose_name_plural': 'Linhas da DRE (Relatório Contábil - De Paula)',
|
||||||
|
'ordering': ['ordem', 'id'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ContabilObservacaoDePaula',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('codigo_empresa', models.CharField(max_length=20, verbose_name='Código da empresa')),
|
||||||
|
('alvo_tipo', models.CharField(choices=[('conta', 'Conta do Balancete'), ('dre', 'Linha da DRE'), ('analise_vertical', 'Linha da Análise Vertical')], max_length=20, verbose_name='Tipo do alvo')),
|
||||||
|
('alvo_chave', models.CharField(max_length=1000, verbose_name='Chave natural do alvo')),
|
||||||
|
('alvo_rotulo', models.CharField(blank=True, max_length=320, verbose_name='Rótulo do alvo')),
|
||||||
|
('competencia_origem', models.DateField(verbose_name='Competência de origem')),
|
||||||
|
('texto', models.TextField(verbose_name='Observação')),
|
||||||
|
('mostrar_ao_cliente', models.BooleanField(default=False, verbose_name='Mostrar ao cliente no relatório')),
|
||||||
|
('criado_em', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('atualizado_em', models.DateTimeField(auto_now=True)),
|
||||||
|
('encerrada_em_competencia', models.DateField(blank=True, null=True, verbose_name='Encerrada na competência')),
|
||||||
|
('encerrada_em', models.DateTimeField(blank=True, null=True, verbose_name='Encerrada em')),
|
||||||
|
('apuracao_origem', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='observacoes', to='portal_api.contabilapuracaodepaula')),
|
||||||
|
('criado_por', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='observacoes_contabeis_de_paula', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('encerrada_por', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='observacoes_contabeis_encerradas_de_paula', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Observação do Relatório Contábil - De Paula',
|
||||||
|
'verbose_name_plural': 'Observações do Relatório Contábil - De Paula',
|
||||||
|
'ordering': ['competencia_origem', 'criado_em', 'id'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ContabilObservacaoEdicaoDePaula',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('texto_anterior', models.TextField(verbose_name='Texto anterior')),
|
||||||
|
('texto_novo', models.TextField(verbose_name='Texto novo')),
|
||||||
|
('editado_em', models.DateTimeField(auto_now_add=True, verbose_name='Editado em')),
|
||||||
|
('editado_por', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='edicoes_observacoes_contabeis_de_paula', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('observacao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='edicoes', to='portal_api.contabilobservacaodepaula')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Edição de observação do Relatório Contábil - De Paula',
|
||||||
|
'verbose_name_plural': 'Edições de observação do Relatório Contábil - De Paula',
|
||||||
|
'ordering': ['-editado_em', '-id'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='contabilapuracaodepaula',
|
||||||
|
constraint=models.UniqueConstraint(fields=('codigo_empresa', 'competencia'), name='contabil_apuracao_de_paula_unica'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='contabilobservacaodepaula',
|
||||||
|
index=models.Index(fields=['codigo_empresa', 'alvo_tipo', 'alvo_chave'], name='contabil_obs_dp_alvo_idx'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -1972,7 +1972,15 @@ def validar_tamanho_arquivo_contabil(arquivo: File) -> None:
|
|||||||
raise ValidationError("O arquivo deve ter no máximo 15MB.")
|
raise ValidationError("O arquivo deve ter no máximo 15MB.")
|
||||||
|
|
||||||
|
|
||||||
class ContabilApuracao(models.Model):
|
# Relatório Contábil e Relatório Contábil - De Paula (Diretoria): cada model
|
||||||
|
# é uma base abstrata (campos e métodos) mais duas classes concretas, uma por
|
||||||
|
# aplicação, com tabelas separadas e sem nenhuma FK cruzando entre as duas. As
|
||||||
|
# FKs ficam só nas concretas, porque cada variante aponta para os próprios
|
||||||
|
# models. Ver "Relatório Contábil - De Paula" no CLAUDE.md do pacote
|
||||||
|
# `dashboard_contabil`.
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilApuracaoBase(models.Model):
|
||||||
"""Uma análise do Relatório Contábil (Relatórios > Contabilidade): o
|
"""Uma análise do Relatório Contábil (Relatórios > Contabilidade): o
|
||||||
contador anexa o PDF de Balancete + DRE (leiaute Questor, mesmo relatório
|
contador anexa o PDF de Balancete + DRE (leiaute Questor, mesmo relatório
|
||||||
hoje enviado ao cliente) e o pipeline (`portal_api.dashboard_contabil.
|
hoje enviado ao cliente) e o pipeline (`portal_api.dashboard_contabil.
|
||||||
@ -1999,7 +2007,6 @@ class ContabilApuracao(models.Model):
|
|||||||
# O PDF anexado não é gravado: é lido na criação/reprocessamento e tudo o
|
# O PDF anexado não é gravado: é lido na criação/reprocessamento e tudo o
|
||||||
# que se usa dele fica em contas, linhas e achados.
|
# que se usa dele fica em contas, linhas e achados.
|
||||||
status = models.CharField("Status", max_length=20, choices=STATUS_CHOICES, default=STATUS_REVISAO)
|
status = models.CharField("Status", max_length=20, choices=STATUS_CHOICES, default=STATUS_REVISAO)
|
||||||
criado_por = models.ForeignKey(Usuario, on_delete=models.SET_NULL, null=True, related_name="apuracoes_contabeis")
|
|
||||||
criado_em = models.DateTimeField(auto_now_add=True)
|
criado_em = models.DateTimeField(auto_now_add=True)
|
||||||
concluida_em = models.DateTimeField("Concluída em", null=True, blank=True)
|
concluida_em = models.DateTimeField("Concluída em", null=True, blank=True)
|
||||||
# Chaves de IndicadoresFinanceiros (dashboard_contabil/indicadores.py,
|
# Chaves de IndicadoresFinanceiros (dashboard_contabil/indicadores.py,
|
||||||
@ -2065,18 +2072,43 @@ class ContabilApuracao(models.Model):
|
|||||||
return self.leiaute != self.LEIAUTE_CONTABIT
|
return self.leiaute != self.LEIAUTE_CONTABIT
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Apuração do Relatório Contábil"
|
abstract = True
|
||||||
verbose_name_plural = "Apurações do Relatório Contábil"
|
|
||||||
ordering = ["-competencia", "-criado_em"]
|
ordering = ["-competencia", "-criado_em"]
|
||||||
constraints = [
|
|
||||||
models.UniqueConstraint(fields=["codigo_empresa", "competencia"], name="contabil_apuracao_unica")
|
|
||||||
]
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.codigo_empresa} — {self.competencia:%m/%Y}"
|
return f"{self.codigo_empresa} — {self.competencia:%m/%Y}"
|
||||||
|
|
||||||
|
|
||||||
class ContabilApuracaoReprocessamento(models.Model):
|
class ContabilApuracao(ContabilApuracaoBase):
|
||||||
|
criado_por = models.ForeignKey(Usuario, on_delete=models.SET_NULL, null=True, related_name="apuracoes_contabeis")
|
||||||
|
|
||||||
|
class Meta(ContabilApuracaoBase.Meta):
|
||||||
|
verbose_name = "Apuração do Relatório Contábil"
|
||||||
|
verbose_name_plural = "Apurações do Relatório Contábil"
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(fields=["codigo_empresa", "competencia"], name="contabil_apuracao_unica")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilApuracaoDePaula(ContabilApuracaoBase):
|
||||||
|
"""Apuração do Relatório Contábil - De Paula (Diretoria), para as empresas
|
||||||
|
do próprio escritório. Mesma estrutura da original, em tabela própria."""
|
||||||
|
|
||||||
|
criado_por = models.ForeignKey(
|
||||||
|
Usuario, on_delete=models.SET_NULL, null=True, related_name="apuracoes_contabeis_de_paula"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(ContabilApuracaoBase.Meta):
|
||||||
|
verbose_name = "Apuração do Relatório Contábil - De Paula"
|
||||||
|
verbose_name_plural = "Apurações do Relatório Contábil - De Paula"
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["codigo_empresa", "competencia"], name="contabil_apuracao_de_paula_unica"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilApuracaoReprocessamentoBase(models.Model):
|
||||||
"""Log de cada reprocessamento de uma `ContabilApuracao` (pedido explícito
|
"""Log de cada reprocessamento de uma `ContabilApuracao` (pedido explícito
|
||||||
do usuário — "identificar quem reprocessou e quantos reprocessamentos já
|
do usuário — "identificar quem reprocessou e quantos reprocessamentos já
|
||||||
ocorreu"), mesmo espírito de `ContabilObservacaoEdicao`. Um registro por
|
ocorreu"), mesmo espírito de `ContabilObservacaoEdicao`. Um registro por
|
||||||
@ -2084,29 +2116,45 @@ class ContabilApuracaoReprocessamento(models.Model):
|
|||||||
dentro da mesma transação da resincronização, então uma tentativa que
|
dentro da mesma transação da resincronização, então uma tentativa que
|
||||||
falhar no meio do caminho não deixa um registro órfão aqui."""
|
falhar no meio do caminho não deixa um registro órfão aqui."""
|
||||||
|
|
||||||
apuracao = models.ForeignKey(ContabilApuracao, on_delete=models.CASCADE, related_name="reprocessamentos")
|
|
||||||
reprocessado_por = models.ForeignKey(
|
|
||||||
Usuario, on_delete=models.SET_NULL, null=True, blank=True, related_name="reprocessamentos_contabeis"
|
|
||||||
)
|
|
||||||
reprocessado_em = models.DateTimeField("Reprocessado em", auto_now_add=True)
|
reprocessado_em = models.DateTimeField("Reprocessado em", auto_now_add=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Reprocessamento do Relatório Contábil"
|
abstract = True
|
||||||
verbose_name_plural = "Reprocessamentos do Relatório Contábil"
|
|
||||||
ordering = ["-reprocessado_em", "-id"]
|
ordering = ["-reprocessado_em", "-id"]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"Reprocessamento de {self.apuracao_id} em {self.reprocessado_em:%d/%m/%Y %H:%M}"
|
return f"Reprocessamento de {self.apuracao_id} em {self.reprocessado_em:%d/%m/%Y %H:%M}"
|
||||||
|
|
||||||
|
|
||||||
class ContabilConta(models.Model):
|
class ContabilApuracaoReprocessamento(ContabilApuracaoReprocessamentoBase):
|
||||||
|
apuracao = models.ForeignKey(ContabilApuracao, on_delete=models.CASCADE, related_name="reprocessamentos")
|
||||||
|
reprocessado_por = models.ForeignKey(
|
||||||
|
Usuario, on_delete=models.SET_NULL, null=True, blank=True, related_name="reprocessamentos_contabeis"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(ContabilApuracaoReprocessamentoBase.Meta):
|
||||||
|
verbose_name = "Reprocessamento do Relatório Contábil"
|
||||||
|
verbose_name_plural = "Reprocessamentos do Relatório Contábil"
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilApuracaoReprocessamentoDePaula(ContabilApuracaoReprocessamentoBase):
|
||||||
|
apuracao = models.ForeignKey(ContabilApuracaoDePaula, on_delete=models.CASCADE, related_name="reprocessamentos")
|
||||||
|
reprocessado_por = models.ForeignKey(
|
||||||
|
Usuario, on_delete=models.SET_NULL, null=True, blank=True, related_name="reprocessamentos_contabeis_de_paula"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(ContabilApuracaoReprocessamentoBase.Meta):
|
||||||
|
verbose_name = "Reprocessamento do Relatório Contábil - De Paula"
|
||||||
|
verbose_name_plural = "Reprocessamentos do Relatório Contábil - De Paula"
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilContaBase(models.Model):
|
||||||
"""Uma linha do Balancete extraída do PDF (grupo Ativo/Passivo, sintética
|
"""Uma linha do Balancete extraída do PDF (grupo Ativo/Passivo, sintética
|
||||||
ou analítica). `observacao` é livre e editável pelo contador via PATCH em
|
ou analítica). `observacao` é livre e editável pelo contador via PATCH em
|
||||||
qualquer conta, tenha ela gerado um `ContabilAchado` ou não — é o espaço
|
qualquer conta, tenha ela gerado um `ContabilAchado` ou não — é o espaço
|
||||||
de "análise" pedido pelo usuário, independente do que a auditoria
|
de "análise" pedido pelo usuário, independente do que a auditoria
|
||||||
automática encontrou."""
|
automática encontrou."""
|
||||||
|
|
||||||
apuracao = models.ForeignKey(ContabilApuracao, on_delete=models.CASCADE, related_name="contas")
|
|
||||||
# `conta_numero` (a numeração interna do Questor, coluna "Conta" do PDF)
|
# `conta_numero` (a numeração interna do Questor, coluna "Conta" do PDF)
|
||||||
# NÃO corresponde à ordem real de impressão do balancete — o Questor
|
# NÃO corresponde à ordem real de impressão do balancete — o Questor
|
||||||
# reaproveita numeração de contas antigas/canceladas, então o mesmo
|
# reaproveita numeração de contas antigas/canceladas, então o mesmo
|
||||||
@ -2158,8 +2206,7 @@ class ContabilConta(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Conta do Balancete (Relatório Contábil)"
|
abstract = True
|
||||||
verbose_name_plural = "Contas do Balancete (Relatório Contábil)"
|
|
||||||
# `id` desempata: `ordem` sozinha deixa a ordenação indefinida quando
|
# `id` desempata: `ordem` sozinha deixa a ordenação indefinida quando
|
||||||
# duas linhas empatam, e a árvore (nível de cada linha em relação à
|
# duas linhas empatam, e a árvore (nível de cada linha em relação à
|
||||||
# anterior) e a chave natural da DRE/Análise Vertical dependem da
|
# anterior) e a chave natural da DRE/Análise Vertical dependem da
|
||||||
@ -2171,12 +2218,27 @@ class ContabilConta(models.Model):
|
|||||||
return f"{self.codigo} {self.descricao}"
|
return f"{self.codigo} {self.descricao}"
|
||||||
|
|
||||||
|
|
||||||
class ContabilLinhaDre(models.Model):
|
class ContabilConta(ContabilContaBase):
|
||||||
|
apuracao = models.ForeignKey(ContabilApuracao, on_delete=models.CASCADE, related_name="contas")
|
||||||
|
|
||||||
|
class Meta(ContabilContaBase.Meta):
|
||||||
|
verbose_name = "Conta do Balancete (Relatório Contábil)"
|
||||||
|
verbose_name_plural = "Contas do Balancete (Relatório Contábil)"
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilContaDePaula(ContabilContaBase):
|
||||||
|
apuracao = models.ForeignKey(ContabilApuracaoDePaula, on_delete=models.CASCADE, related_name="contas")
|
||||||
|
|
||||||
|
class Meta(ContabilContaBase.Meta):
|
||||||
|
verbose_name = "Conta do Balancete (Relatório Contábil - De Paula)"
|
||||||
|
verbose_name_plural = "Contas do Balancete (Relatório Contábil - De Paula)"
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilLinhaDreBase(models.Model):
|
||||||
"""Uma linha da Demonstração do Resultado do Exercício extraída do PDF —
|
"""Uma linha da Demonstração do Resultado do Exercício extraída do PDF —
|
||||||
sem código de classificação (o relatório da DRE não traz, diferente do
|
sem código de classificação (o relatório da DRE não traz, diferente do
|
||||||
Balancete), só descrição/nível/valor na ordem em que aparecem."""
|
Balancete), só descrição/nível/valor na ordem em que aparecem."""
|
||||||
|
|
||||||
apuracao = models.ForeignKey(ContabilApuracao, on_delete=models.CASCADE, related_name="linhas_dre")
|
|
||||||
ordem = models.IntegerField("Ordem")
|
ordem = models.IntegerField("Ordem")
|
||||||
descricao = models.CharField("Descrição", max_length=255)
|
descricao = models.CharField("Descrição", max_length=255)
|
||||||
nivel = models.IntegerField("Nível de indentação", default=0)
|
nivel = models.IntegerField("Nível de indentação", default=0)
|
||||||
@ -2194,16 +2256,31 @@ class ContabilLinhaDre(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Linha da DRE (Relatório Contábil)"
|
abstract = True
|
||||||
verbose_name_plural = "Linhas da DRE (Relatório Contábil)"
|
# Ver a nota em `ContabilContaBase.Meta`.
|
||||||
# Ver a nota em `ContabilConta.Meta`.
|
|
||||||
ordering = ["ordem", "id"]
|
ordering = ["ordem", "id"]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.descricao}"
|
return f"{self.descricao}"
|
||||||
|
|
||||||
|
|
||||||
class ContabilLinhaAnaliseVertical(models.Model):
|
class ContabilLinhaDre(ContabilLinhaDreBase):
|
||||||
|
apuracao = models.ForeignKey(ContabilApuracao, on_delete=models.CASCADE, related_name="linhas_dre")
|
||||||
|
|
||||||
|
class Meta(ContabilLinhaDreBase.Meta):
|
||||||
|
verbose_name = "Linha da DRE (Relatório Contábil)"
|
||||||
|
verbose_name_plural = "Linhas da DRE (Relatório Contábil)"
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilLinhaDreDePaula(ContabilLinhaDreBase):
|
||||||
|
apuracao = models.ForeignKey(ContabilApuracaoDePaula, on_delete=models.CASCADE, related_name="linhas_dre")
|
||||||
|
|
||||||
|
class Meta(ContabilLinhaDreBase.Meta):
|
||||||
|
verbose_name = "Linha da DRE (Relatório Contábil - De Paula)"
|
||||||
|
verbose_name_plural = "Linhas da DRE (Relatório Contábil - De Paula)"
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilLinhaAnaliseVerticalBase(models.Model):
|
||||||
"""Uma linha da seção "Demonstração Mensal (Análise Vertical)" do mesmo
|
"""Uma linha da seção "Demonstração Mensal (Análise Vertical)" do mesmo
|
||||||
PDF — mesma árvore/descrição/nível da DRE (`ContabilLinhaDre`), só que
|
PDF — mesma árvore/descrição/nível da DRE (`ContabilLinhaDre`), só que
|
||||||
com um valor+percentual por mês em vez de um valor único, por isso
|
com um valor+percentual por mês em vez de um valor único, por isso
|
||||||
@ -2213,7 +2290,6 @@ class ContabilLinhaAnaliseVertical(models.Model):
|
|||||||
texto (não float), pra não perder precisão decimal em nenhuma conversão
|
texto (não float), pra não perder precisão decimal em nenhuma conversão
|
||||||
— `[{"valor": "1234.56", "percentual": "12.34"}, ...]`."""
|
— `[{"valor": "1234.56", "percentual": "12.34"}, ...]`."""
|
||||||
|
|
||||||
apuracao = models.ForeignKey(ContabilApuracao, on_delete=models.CASCADE, related_name="linhas_analise_vertical")
|
|
||||||
ordem = models.IntegerField("Ordem")
|
ordem = models.IntegerField("Ordem")
|
||||||
descricao = models.CharField("Descrição", max_length=255)
|
descricao = models.CharField("Descrição", max_length=255)
|
||||||
nivel = models.IntegerField("Nível de indentação", default=0)
|
nivel = models.IntegerField("Nível de indentação", default=0)
|
||||||
@ -2230,16 +2306,33 @@ class ContabilLinhaAnaliseVertical(models.Model):
|
|||||||
valores_anterior_reprocessamento = models.JSONField("Valores mensais antes do último reprocessamento", default=list, blank=True)
|
valores_anterior_reprocessamento = models.JSONField("Valores mensais antes do último reprocessamento", default=list, blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Linha da Análise Vertical (Relatório Contábil)"
|
abstract = True
|
||||||
verbose_name_plural = "Linhas da Análise Vertical (Relatório Contábil)"
|
# Ver a nota em `ContabilContaBase.Meta`.
|
||||||
# Ver a nota em `ContabilConta.Meta`.
|
|
||||||
ordering = ["ordem", "id"]
|
ordering = ["ordem", "id"]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.descricao}"
|
return f"{self.descricao}"
|
||||||
|
|
||||||
|
|
||||||
class ContabilAchado(models.Model):
|
class ContabilLinhaAnaliseVertical(ContabilLinhaAnaliseVerticalBase):
|
||||||
|
apuracao = models.ForeignKey(ContabilApuracao, on_delete=models.CASCADE, related_name="linhas_analise_vertical")
|
||||||
|
|
||||||
|
class Meta(ContabilLinhaAnaliseVerticalBase.Meta):
|
||||||
|
verbose_name = "Linha da Análise Vertical (Relatório Contábil)"
|
||||||
|
verbose_name_plural = "Linhas da Análise Vertical (Relatório Contábil)"
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilLinhaAnaliseVerticalDePaula(ContabilLinhaAnaliseVerticalBase):
|
||||||
|
apuracao = models.ForeignKey(
|
||||||
|
ContabilApuracaoDePaula, on_delete=models.CASCADE, related_name="linhas_analise_vertical"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(ContabilLinhaAnaliseVerticalBase.Meta):
|
||||||
|
verbose_name = "Linha da Análise Vertical (Relatório Contábil - De Paula)"
|
||||||
|
verbose_name_plural = "Linhas da Análise Vertical (Relatório Contábil - De Paula)"
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilAchadoBase(models.Model):
|
||||||
"""Um achado gerado automaticamente pelo motor de regras
|
"""Um achado gerado automaticamente pelo motor de regras
|
||||||
(`dashboard_contabil.regras`) ao criar a apuração, só muda de `status`
|
(`dashboard_contabil.regras`) ao criar a apuração, só muda de `status`
|
||||||
conforme o contador revisa (mesmo espírito de `ImportacaoPlanoSaudeAuditoria`).
|
conforme o contador revisa (mesmo espírito de `ImportacaoPlanoSaudeAuditoria`).
|
||||||
@ -2275,19 +2368,14 @@ class ContabilAchado(models.Model):
|
|||||||
(STATUS_IGNORADO, "Ignorado"),
|
(STATUS_IGNORADO, "Ignorado"),
|
||||||
]
|
]
|
||||||
|
|
||||||
apuracao = models.ForeignKey(ContabilApuracao, on_delete=models.CASCADE, related_name="achados")
|
# `apuracao`, `conta`, `linha_analise_vertical` e `tratado_por` ficam nas
|
||||||
conta = models.ForeignKey(
|
# classes concretas. `linha_analise_vertical` é preenchido só por achados
|
||||||
ContabilConta, on_delete=models.SET_NULL, null=True, blank=True, related_name="achados"
|
# sem uma conta do Balancete por trás (hoje, só
|
||||||
)
|
|
||||||
# Preenchido só por achados sem uma conta do Balancete por trás (hoje, só
|
|
||||||
# `regra_variacao_atipica_dre`, que lê a Análise Vertical) — mesmo
|
# `regra_variacao_atipica_dre`, que lê a Análise Vertical) — mesmo
|
||||||
# espírito de `conta` acima, alimenta o botão "Ver na tabela" da tela de
|
# espírito de `conta`, alimenta o botão "Ver na tabela" da tela de
|
||||||
# revisão (navega até a Análise Vertical e destaca esta linha em vez do
|
# revisão (navega até a Análise Vertical e destaca esta linha em vez do
|
||||||
# Balancete). Casado por `ordem` na view, ver `AchadoDetectado.
|
# Balancete). Casado por `ordem` na view, ver `AchadoDetectado.
|
||||||
# ordem_linha_analise_vertical`/`_contabil_recria_achados()`.
|
# ordem_linha_analise_vertical`/`_contabil_recria_achados()`.
|
||||||
linha_analise_vertical = models.ForeignKey(
|
|
||||||
ContabilLinhaAnaliseVertical, on_delete=models.SET_NULL, null=True, blank=True, related_name="achados"
|
|
||||||
)
|
|
||||||
regra = models.CharField("Regra", max_length=60)
|
regra = models.CharField("Regra", max_length=60)
|
||||||
severidade = models.CharField("Severidade", max_length=10, choices=SEVERIDADE_CHOICES)
|
severidade = models.CharField("Severidade", max_length=10, choices=SEVERIDADE_CHOICES)
|
||||||
titulo = models.CharField("Título", max_length=255)
|
titulo = models.CharField("Título", max_length=255)
|
||||||
@ -2295,9 +2383,6 @@ class ContabilAchado(models.Model):
|
|||||||
valor_referencia = models.DecimalField(max_digits=16, decimal_places=2, null=True, blank=True)
|
valor_referencia = models.DecimalField(max_digits=16, decimal_places=2, null=True, blank=True)
|
||||||
status = models.CharField("Status", max_length=10, choices=STATUS_CHOICES, default=STATUS_PENDENTE)
|
status = models.CharField("Status", max_length=10, choices=STATUS_CHOICES, default=STATUS_PENDENTE)
|
||||||
observacao_contador = models.TextField("Observação do contador", blank=True)
|
observacao_contador = models.TextField("Observação do contador", blank=True)
|
||||||
tratado_por = models.ForeignKey(
|
|
||||||
Usuario, on_delete=models.SET_NULL, null=True, blank=True, related_name="achados_contabeis_tratados"
|
|
||||||
)
|
|
||||||
tratado_em = models.DateTimeField("Tratado em", null=True, blank=True)
|
tratado_em = models.DateTimeField("Tratado em", null=True, blank=True)
|
||||||
# Mesmo espírito de ContabilConta.oculta_no_relatorio — só afeta a
|
# Mesmo espírito de ContabilConta.oculta_no_relatorio — só afeta a
|
||||||
# seção "Observações" do relatório "Gerar Dashboard" (relevante só pra
|
# seção "Observações" do relatório "Gerar Dashboard" (relevante só pra
|
||||||
@ -2306,15 +2391,48 @@ class ContabilAchado(models.Model):
|
|||||||
oculto_no_relatorio = models.BooleanField("Observação oculta no relatório", default=False)
|
oculto_no_relatorio = models.BooleanField("Observação oculta no relatório", default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Achado do Relatório Contábil"
|
abstract = True
|
||||||
verbose_name_plural = "Achados do Relatório Contábil"
|
|
||||||
ordering = ["-severidade", "id"]
|
ordering = ["-severidade", "id"]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.titulo} ({self.get_status_display()})"
|
return f"{self.titulo} ({self.get_status_display()})"
|
||||||
|
|
||||||
|
|
||||||
class ContabilObservacao(models.Model):
|
class ContabilAchado(ContabilAchadoBase):
|
||||||
|
apuracao = models.ForeignKey(ContabilApuracao, on_delete=models.CASCADE, related_name="achados")
|
||||||
|
conta = models.ForeignKey(
|
||||||
|
ContabilConta, on_delete=models.SET_NULL, null=True, blank=True, related_name="achados"
|
||||||
|
)
|
||||||
|
linha_analise_vertical = models.ForeignKey(
|
||||||
|
ContabilLinhaAnaliseVertical, on_delete=models.SET_NULL, null=True, blank=True, related_name="achados"
|
||||||
|
)
|
||||||
|
tratado_por = models.ForeignKey(
|
||||||
|
Usuario, on_delete=models.SET_NULL, null=True, blank=True, related_name="achados_contabeis_tratados"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(ContabilAchadoBase.Meta):
|
||||||
|
verbose_name = "Achado do Relatório Contábil"
|
||||||
|
verbose_name_plural = "Achados do Relatório Contábil"
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilAchadoDePaula(ContabilAchadoBase):
|
||||||
|
apuracao = models.ForeignKey(ContabilApuracaoDePaula, on_delete=models.CASCADE, related_name="achados")
|
||||||
|
conta = models.ForeignKey(
|
||||||
|
ContabilContaDePaula, on_delete=models.SET_NULL, null=True, blank=True, related_name="achados"
|
||||||
|
)
|
||||||
|
linha_analise_vertical = models.ForeignKey(
|
||||||
|
ContabilLinhaAnaliseVerticalDePaula, on_delete=models.SET_NULL, null=True, blank=True, related_name="achados"
|
||||||
|
)
|
||||||
|
tratado_por = models.ForeignKey(
|
||||||
|
Usuario, on_delete=models.SET_NULL, null=True, blank=True, related_name="achados_contabeis_tratados_de_paula"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(ContabilAchadoBase.Meta):
|
||||||
|
verbose_name = "Achado do Relatório Contábil - De Paula"
|
||||||
|
verbose_name_plural = "Achados do Relatório Contábil - De Paula"
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilObservacaoBase(models.Model):
|
||||||
"""Uma observação escrita pelo contador sobre uma conta do Balancete, uma
|
"""Uma observação escrita pelo contador sobre uma conta do Balancete, uma
|
||||||
linha da DRE ou uma linha da Análise Vertical — **não** pertence à
|
linha da DRE ou uma linha da Análise Vertical — **não** pertence à
|
||||||
apuração, e sim à empresa + conta, atravessando competências (pedido
|
apuração, e sim à empresa + conta, atravessando competências (pedido
|
||||||
@ -2373,13 +2491,11 @@ class ContabilObservacao(models.Model):
|
|||||||
# só pra exibir o histórico quando aquela conta não existir mais na
|
# só pra exibir o histórico quando aquela conta não existir mais na
|
||||||
# apuração aberta (plano de contas mudou, linha saiu do relatório).
|
# apuração aberta (plano de contas mudou, linha saiu do relatório).
|
||||||
alvo_rotulo = models.CharField("Rótulo do alvo", max_length=320, blank=True)
|
alvo_rotulo = models.CharField("Rótulo do alvo", max_length=320, blank=True)
|
||||||
# `SET_NULL` (não CASCADE) de propósito: o histórico é do cliente, não da
|
# `apuracao_origem` (nas classes concretas) é `SET_NULL`, não CASCADE, de
|
||||||
# apuração — excluir a análise de um mês não pode apagar o que o contador
|
# propósito: o histórico é do cliente, não da apuração — excluir a análise
|
||||||
# registrou sobre aquela conta. `competencia_origem` é uma cópia
|
# de um mês não pode apagar o que o contador registrou sobre aquela conta.
|
||||||
# justamente pra vigência continuar resolvendo sem a apuração original.
|
# `competencia_origem` é uma cópia justamente pra vigência continuar
|
||||||
apuracao_origem = models.ForeignKey(
|
# resolvendo sem a apuração original.
|
||||||
ContabilApuracao, on_delete=models.SET_NULL, null=True, blank=True, related_name="observacoes"
|
|
||||||
)
|
|
||||||
competencia_origem = models.DateField("Competência de origem")
|
competencia_origem = models.DateField("Competência de origem")
|
||||||
texto = models.TextField("Observação")
|
texto = models.TextField("Observação")
|
||||||
# Substitui o antigo `oculta_no_relatorio` das linhas, com o sinal
|
# Substitui o antigo `oculta_no_relatorio` das linhas, com o sinal
|
||||||
@ -2388,27 +2504,18 @@ class ContabilObservacao(models.Model):
|
|||||||
# mesma decisão de sempre: uma observação nova só vai pro relatório do
|
# mesma decisão de sempre: uma observação nova só vai pro relatório do
|
||||||
# cliente quando o contador confirmar explicitamente.
|
# cliente quando o contador confirmar explicitamente.
|
||||||
mostrar_ao_cliente = models.BooleanField("Mostrar ao cliente no relatório", default=False)
|
mostrar_ao_cliente = models.BooleanField("Mostrar ao cliente no relatório", default=False)
|
||||||
criado_por = models.ForeignKey(
|
|
||||||
Usuario, on_delete=models.SET_NULL, null=True, blank=True, related_name="observacoes_contabeis"
|
|
||||||
)
|
|
||||||
criado_em = models.DateTimeField(auto_now_add=True)
|
criado_em = models.DateTimeField(auto_now_add=True)
|
||||||
atualizado_em = models.DateTimeField(auto_now=True)
|
atualizado_em = models.DateTimeField(auto_now=True)
|
||||||
# "Ocultar das próximas execuções": a observação continua visível na
|
# "Ocultar das próximas execuções": a observação continua visível na
|
||||||
# competência em que foi encerrada (e em todas as anteriores, o histórico
|
# competência em que foi encerrada (e em todas as anteriores, o histórico
|
||||||
# nunca é reescrito) e some a partir da seguinte.
|
# nunca é reescrito) e some a partir da seguinte. `encerrada_por` fica
|
||||||
|
# nas classes concretas.
|
||||||
encerrada_em_competencia = models.DateField("Encerrada na competência", null=True, blank=True)
|
encerrada_em_competencia = models.DateField("Encerrada na competência", null=True, blank=True)
|
||||||
encerrada_por = models.ForeignKey(
|
|
||||||
Usuario, on_delete=models.SET_NULL, null=True, blank=True, related_name="observacoes_contabeis_encerradas"
|
|
||||||
)
|
|
||||||
encerrada_em = models.DateTimeField("Encerrada em", null=True, blank=True)
|
encerrada_em = models.DateTimeField("Encerrada em", null=True, blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Observação do Relatório Contábil"
|
abstract = True
|
||||||
verbose_name_plural = "Observações do Relatório Contábil"
|
|
||||||
ordering = ["competencia_origem", "criado_em", "id"]
|
ordering = ["competencia_origem", "criado_em", "id"]
|
||||||
indexes = [
|
|
||||||
models.Index(fields=["codigo_empresa", "alvo_tipo", "alvo_chave"], name="contabil_obs_alvo_idx"),
|
|
||||||
]
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.codigo_empresa} {self.alvo_chave} ({self.competencia_origem:%m/%Y})"
|
return f"{self.codigo_empresa} {self.alvo_chave} ({self.competencia_origem:%m/%Y})"
|
||||||
@ -2456,7 +2563,49 @@ class ContabilObservacao(models.Model):
|
|||||||
return self.competencia_origem < competencia
|
return self.competencia_origem < competencia
|
||||||
|
|
||||||
|
|
||||||
class ContabilObservacaoEdicao(models.Model):
|
class ContabilObservacao(ContabilObservacaoBase):
|
||||||
|
apuracao_origem = models.ForeignKey(
|
||||||
|
ContabilApuracao, on_delete=models.SET_NULL, null=True, blank=True, related_name="observacoes"
|
||||||
|
)
|
||||||
|
criado_por = models.ForeignKey(
|
||||||
|
Usuario, on_delete=models.SET_NULL, null=True, blank=True, related_name="observacoes_contabeis"
|
||||||
|
)
|
||||||
|
encerrada_por = models.ForeignKey(
|
||||||
|
Usuario, on_delete=models.SET_NULL, null=True, blank=True, related_name="observacoes_contabeis_encerradas"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(ContabilObservacaoBase.Meta):
|
||||||
|
verbose_name = "Observação do Relatório Contábil"
|
||||||
|
verbose_name_plural = "Observações do Relatório Contábil"
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["codigo_empresa", "alvo_tipo", "alvo_chave"], name="contabil_obs_alvo_idx"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilObservacaoDePaula(ContabilObservacaoBase):
|
||||||
|
apuracao_origem = models.ForeignKey(
|
||||||
|
ContabilApuracaoDePaula, on_delete=models.SET_NULL, null=True, blank=True, related_name="observacoes"
|
||||||
|
)
|
||||||
|
criado_por = models.ForeignKey(
|
||||||
|
Usuario, on_delete=models.SET_NULL, null=True, blank=True, related_name="observacoes_contabeis_de_paula"
|
||||||
|
)
|
||||||
|
encerrada_por = models.ForeignKey(
|
||||||
|
Usuario,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="observacoes_contabeis_encerradas_de_paula",
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(ContabilObservacaoBase.Meta):
|
||||||
|
verbose_name = "Observação do Relatório Contábil - De Paula"
|
||||||
|
verbose_name_plural = "Observações do Relatório Contábil - De Paula"
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["codigo_empresa", "alvo_tipo", "alvo_chave"], name="contabil_obs_dp_alvo_idx"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilObservacaoEdicaoBase(models.Model):
|
||||||
"""Log de cada edição de texto de uma `ContabilObservacao` (pedido
|
"""Log de cada edição de texto de uma `ContabilObservacao` (pedido
|
||||||
explícito do usuário, depois que o botão de excluir observação foi
|
explícito do usuário, depois que o botão de excluir observação foi
|
||||||
removido da tela — "evitar a ocultação de observações importantes" exige
|
removido da tela — "evitar a ocultação de observações importantes" exige
|
||||||
@ -2470,23 +2619,44 @@ class ContabilObservacaoEdicao(models.Model):
|
|||||||
relatório HTML gerado pro cliente (`dashboard()`) não usa esse
|
relatório HTML gerado pro cliente (`dashboard()`) não usa esse
|
||||||
serializer, então o selo/histórico nunca vaza pra lá."""
|
serializer, então o selo/histórico nunca vaza pra lá."""
|
||||||
|
|
||||||
observacao = models.ForeignKey(ContabilObservacao, on_delete=models.CASCADE, related_name="edicoes")
|
|
||||||
texto_anterior = models.TextField("Texto anterior")
|
texto_anterior = models.TextField("Texto anterior")
|
||||||
texto_novo = models.TextField("Texto novo")
|
texto_novo = models.TextField("Texto novo")
|
||||||
editado_por = models.ForeignKey(
|
|
||||||
Usuario, on_delete=models.SET_NULL, null=True, blank=True, related_name="edicoes_observacoes_contabeis"
|
|
||||||
)
|
|
||||||
editado_em = models.DateTimeField("Editado em", auto_now_add=True)
|
editado_em = models.DateTimeField("Editado em", auto_now_add=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Edição de observação do Relatório Contábil"
|
abstract = True
|
||||||
verbose_name_plural = "Edições de observação do Relatório Contábil"
|
|
||||||
ordering = ["-editado_em", "-id"]
|
ordering = ["-editado_em", "-id"]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"Edição de {self.observacao_id} em {self.editado_em:%d/%m/%Y %H:%M}"
|
return f"Edição de {self.observacao_id} em {self.editado_em:%d/%m/%Y %H:%M}"
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilObservacaoEdicao(ContabilObservacaoEdicaoBase):
|
||||||
|
observacao = models.ForeignKey(ContabilObservacao, on_delete=models.CASCADE, related_name="edicoes")
|
||||||
|
editado_por = models.ForeignKey(
|
||||||
|
Usuario, on_delete=models.SET_NULL, null=True, blank=True, related_name="edicoes_observacoes_contabeis"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(ContabilObservacaoEdicaoBase.Meta):
|
||||||
|
verbose_name = "Edição de observação do Relatório Contábil"
|
||||||
|
verbose_name_plural = "Edições de observação do Relatório Contábil"
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilObservacaoEdicaoDePaula(ContabilObservacaoEdicaoBase):
|
||||||
|
observacao = models.ForeignKey(ContabilObservacaoDePaula, on_delete=models.CASCADE, related_name="edicoes")
|
||||||
|
editado_por = models.ForeignKey(
|
||||||
|
Usuario,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="edicoes_observacoes_contabeis_de_paula",
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(ContabilObservacaoEdicaoBase.Meta):
|
||||||
|
verbose_name = "Edição de observação do Relatório Contábil - De Paula"
|
||||||
|
verbose_name_plural = "Edições de observação do Relatório Contábil - De Paula"
|
||||||
|
|
||||||
|
|
||||||
class IndicadorContabilDefinicao(models.Model):
|
class IndicadorContabilDefinicao(models.Model):
|
||||||
"""Um indicador financeiro do Relatório Contábil — todo indicador é um
|
"""Um indicador financeiro do Relatório Contábil — todo indicador é um
|
||||||
registro aqui agora, inclusive os antigos 11 "de sistema" (ROA/ROE/
|
registro aqui agora, inclusive os antigos 11 "de sistema" (ROA/ROE/
|
||||||
|
|||||||
@ -22,13 +22,21 @@ from .models import (
|
|||||||
CompromissoAgenda,
|
CompromissoAgenda,
|
||||||
ConciliacaoFornecedor,
|
ConciliacaoFornecedor,
|
||||||
ContabilAchado,
|
ContabilAchado,
|
||||||
|
ContabilAchadoDePaula,
|
||||||
ContabilApuracao,
|
ContabilApuracao,
|
||||||
|
ContabilApuracaoDePaula,
|
||||||
ContabilApuracaoReprocessamento,
|
ContabilApuracaoReprocessamento,
|
||||||
|
ContabilApuracaoReprocessamentoDePaula,
|
||||||
ContabilConta,
|
ContabilConta,
|
||||||
|
ContabilContaDePaula,
|
||||||
ContabilLinhaAnaliseVertical,
|
ContabilLinhaAnaliseVertical,
|
||||||
|
ContabilLinhaAnaliseVerticalDePaula,
|
||||||
ContabilLinhaDre,
|
ContabilLinhaDre,
|
||||||
|
ContabilLinhaDreDePaula,
|
||||||
ContabilObservacao,
|
ContabilObservacao,
|
||||||
|
ContabilObservacaoDePaula,
|
||||||
ContabilObservacaoEdicao,
|
ContabilObservacaoEdicao,
|
||||||
|
ContabilObservacaoEdicaoDePaula,
|
||||||
Departamento,
|
Departamento,
|
||||||
EmpresaQuestor,
|
EmpresaQuestor,
|
||||||
Favorito,
|
Favorito,
|
||||||
@ -2793,6 +2801,75 @@ class ContabilApuracaoDetailSerializer(serializers.ModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# --- Relatório Contábil - De Paula (Diretoria) ------------------------------
|
||||||
|
# Mesmos serializers da original, apontados para as tabelas da De Paula. Os
|
||||||
|
# `PrimaryKeyRelatedField` de apuração são o ponto que importa para o
|
||||||
|
# isolamento: com o queryset da De Paula, uma requisição nos endpoints dela
|
||||||
|
# nunca resolve o id de uma apuração da original, nem o contrário.
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilContaDePaulaSerializer(ContabilContaSerializer):
|
||||||
|
class Meta(ContabilContaSerializer.Meta):
|
||||||
|
model = ContabilContaDePaula
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilLinhaDreDePaulaSerializer(ContabilLinhaDreSerializer):
|
||||||
|
class Meta(ContabilLinhaDreSerializer.Meta):
|
||||||
|
model = ContabilLinhaDreDePaula
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilLinhaAnaliseVerticalDePaulaSerializer(ContabilLinhaAnaliseVerticalSerializer):
|
||||||
|
class Meta(ContabilLinhaAnaliseVerticalSerializer.Meta):
|
||||||
|
model = ContabilLinhaAnaliseVerticalDePaula
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilObservacaoEdicaoDePaulaSerializer(ContabilObservacaoEdicaoSerializer):
|
||||||
|
class Meta(ContabilObservacaoEdicaoSerializer.Meta):
|
||||||
|
model = ContabilObservacaoEdicaoDePaula
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilObservacaoDePaulaSerializer(ContabilObservacaoSerializer):
|
||||||
|
edicoes = ContabilObservacaoEdicaoDePaulaSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta(ContabilObservacaoSerializer.Meta):
|
||||||
|
model = ContabilObservacaoDePaula
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilObservacaoCreateDePaulaSerializer(ContabilObservacaoCreateSerializer):
|
||||||
|
apuracao = serializers.PrimaryKeyRelatedField(queryset=ContabilApuracaoDePaula.objects.all())
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilObservacaoApuracaoDePaulaSerializer(ContabilObservacaoApuracaoSerializer):
|
||||||
|
apuracao = serializers.PrimaryKeyRelatedField(queryset=ContabilApuracaoDePaula.objects.all())
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilAchadoDePaulaSerializer(ContabilAchadoSerializer):
|
||||||
|
class Meta(ContabilAchadoSerializer.Meta):
|
||||||
|
model = ContabilAchadoDePaula
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilApuracaoReprocessamentoDePaulaSerializer(ContabilApuracaoReprocessamentoSerializer):
|
||||||
|
class Meta(ContabilApuracaoReprocessamentoSerializer.Meta):
|
||||||
|
model = ContabilApuracaoReprocessamentoDePaula
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilApuracaoListDePaulaSerializer(ContabilApuracaoListSerializer):
|
||||||
|
reprocessamentos = ContabilApuracaoReprocessamentoDePaulaSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta(ContabilApuracaoListSerializer.Meta):
|
||||||
|
model = ContabilApuracaoDePaula
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilApuracaoDetailDePaulaSerializer(ContabilApuracaoDetailSerializer):
|
||||||
|
contas = ContabilContaDePaulaSerializer(many=True, read_only=True)
|
||||||
|
linhas_dre = ContabilLinhaDreDePaulaSerializer(many=True, read_only=True)
|
||||||
|
linhas_analise_vertical = ContabilLinhaAnaliseVerticalDePaulaSerializer(many=True, read_only=True)
|
||||||
|
achados = ContabilAchadoDePaulaSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta(ContabilApuracaoDetailSerializer.Meta):
|
||||||
|
model = ContabilApuracaoDePaula
|
||||||
|
|
||||||
|
|
||||||
# --- Conciliação de Fornecedores (Utilitários) -----------------------------
|
# --- Conciliação de Fornecedores (Utilitários) -----------------------------
|
||||||
# O detalhe (contas + lançamentos + análise recalculada) é montado em
|
# O detalhe (contas + lançamentos + análise recalculada) é montado em
|
||||||
# `_conciliacao_detalhe()` em views.py, porque depende da análise de
|
# `_conciliacao_detalhe()` em views.py, porque depende da análise de
|
||||||
|
|||||||
@ -110,6 +110,25 @@ router.register(
|
|||||||
views.IndicadorContabilDefinicaoViewSet,
|
views.IndicadorContabilDefinicaoViewSet,
|
||||||
basename="contabil-indicador-definicao",
|
basename="contabil-indicador-definicao",
|
||||||
)
|
)
|
||||||
|
# Relatório Contábil - De Paula (Diretoria): mesmos endpoints com sufixo
|
||||||
|
# "-de-paula", tabelas e permissão próprias. O cadastro de indicadores
|
||||||
|
# (`contabil-indicadores-definicoes`) é o mesmo das duas aplicações.
|
||||||
|
router.register(
|
||||||
|
"contabil-apuracoes-de-paula", views.ContabilApuracaoDePaulaViewSet, basename="contabil-apuracao-de-paula"
|
||||||
|
)
|
||||||
|
router.register("contabil-contas-de-paula", views.ContabilContaDePaulaViewSet, basename="contabil-conta-de-paula")
|
||||||
|
router.register(
|
||||||
|
"contabil-linhas-dre-de-paula", views.ContabilLinhaDreDePaulaViewSet, basename="contabil-linha-dre-de-paula"
|
||||||
|
)
|
||||||
|
router.register(
|
||||||
|
"contabil-linhas-analise-vertical-de-paula",
|
||||||
|
views.ContabilLinhaAnaliseVerticalDePaulaViewSet,
|
||||||
|
basename="contabil-linha-analise-vertical-de-paula",
|
||||||
|
)
|
||||||
|
router.register(
|
||||||
|
"contabil-observacoes-de-paula", views.ContabilObservacaoDePaulaViewSet, basename="contabil-observacao-de-paula"
|
||||||
|
)
|
||||||
|
router.register("contabil-achados-de-paula", views.ContabilAchadoDePaulaViewSet, basename="contabil-achado-de-paula")
|
||||||
|
|
||||||
router.register(
|
router.register(
|
||||||
"conciliacoes-fornecedores", views.ConciliacaoFornecedorViewSet, basename="conciliacao-fornecedor"
|
"conciliacoes-fornecedores", views.ConciliacaoFornecedorViewSet, basename="conciliacao-fornecedor"
|
||||||
|
|||||||
@ -34,7 +34,7 @@ from django.views.decorators.csrf import ensure_csrf_cookie
|
|||||||
from rest_framework import status, viewsets
|
from rest_framework import status, viewsets
|
||||||
from rest_framework.decorators import action, api_view, permission_classes
|
from rest_framework.decorators import action, api_view, permission_classes
|
||||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||||
from rest_framework.permissions import SAFE_METHODS, AllowAny, BasePermission, IsAuthenticated
|
from rest_framework.permissions import OR, SAFE_METHODS, AllowAny, BasePermission, IsAuthenticated
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import ModelSerializer
|
from rest_framework.serializers import ModelSerializer
|
||||||
@ -74,13 +74,20 @@ from .models import (
|
|||||||
ConciliacaoFornecedorVinculo,
|
ConciliacaoFornecedorVinculo,
|
||||||
ConciliacaoFornecedorVinculoHistorico,
|
ConciliacaoFornecedorVinculoHistorico,
|
||||||
ContabilAchado,
|
ContabilAchado,
|
||||||
|
ContabilAchadoBase,
|
||||||
|
ContabilAchadoDePaula,
|
||||||
ContabilApuracao,
|
ContabilApuracao,
|
||||||
ContabilApuracaoReprocessamento,
|
ContabilApuracaoBase,
|
||||||
|
ContabilApuracaoDePaula,
|
||||||
ContabilConta,
|
ContabilConta,
|
||||||
|
ContabilContaDePaula,
|
||||||
ContabilLinhaAnaliseVertical,
|
ContabilLinhaAnaliseVertical,
|
||||||
|
ContabilLinhaAnaliseVerticalDePaula,
|
||||||
ContabilLinhaDre,
|
ContabilLinhaDre,
|
||||||
|
ContabilLinhaDreDePaula,
|
||||||
ContabilObservacao,
|
ContabilObservacao,
|
||||||
ContabilObservacaoEdicao,
|
ContabilObservacaoBase,
|
||||||
|
ContabilObservacaoDePaula,
|
||||||
Departamento,
|
Departamento,
|
||||||
Favorito,
|
Favorito,
|
||||||
FuncaoTelefonia,
|
FuncaoTelefonia,
|
||||||
@ -154,20 +161,29 @@ from .serializers import (
|
|||||||
ConciliacaoFornecedorValidarSerializer,
|
ConciliacaoFornecedorValidarSerializer,
|
||||||
ConciliacaoFornecedorVincularSerializer,
|
ConciliacaoFornecedorVincularSerializer,
|
||||||
ContabilAchadoAjusteSerializer,
|
ContabilAchadoAjusteSerializer,
|
||||||
|
ContabilAchadoDePaulaSerializer,
|
||||||
ContabilAchadoOcultoSerializer,
|
ContabilAchadoOcultoSerializer,
|
||||||
ContabilAchadoValidarSerializer,
|
ContabilAchadoValidarSerializer,
|
||||||
ContabilAchadoSerializer,
|
ContabilAchadoSerializer,
|
||||||
ContabilApuracaoCreateSerializer,
|
ContabilApuracaoCreateSerializer,
|
||||||
|
ContabilApuracaoDetailDePaulaSerializer,
|
||||||
ContabilApuracaoDetailSerializer,
|
ContabilApuracaoDetailSerializer,
|
||||||
|
ContabilApuracaoListDePaulaSerializer,
|
||||||
ContabilApuracaoListSerializer,
|
ContabilApuracaoListSerializer,
|
||||||
|
ContabilContaDePaulaSerializer,
|
||||||
ContabilContaSerializer,
|
ContabilContaSerializer,
|
||||||
ContabilIndicadoresOcultosSerializer,
|
ContabilIndicadoresOcultosSerializer,
|
||||||
ContabilIndicadoresSelecionadosSerializer,
|
ContabilIndicadoresSelecionadosSerializer,
|
||||||
ContabilResumoFechamentoSerializer,
|
ContabilResumoFechamentoSerializer,
|
||||||
|
ContabilLinhaAnaliseVerticalDePaulaSerializer,
|
||||||
ContabilLinhaAnaliseVerticalSerializer,
|
ContabilLinhaAnaliseVerticalSerializer,
|
||||||
|
ContabilLinhaDreDePaulaSerializer,
|
||||||
ContabilLinhaDreSerializer,
|
ContabilLinhaDreSerializer,
|
||||||
|
ContabilObservacaoApuracaoDePaulaSerializer,
|
||||||
ContabilObservacaoApuracaoSerializer,
|
ContabilObservacaoApuracaoSerializer,
|
||||||
|
ContabilObservacaoCreateDePaulaSerializer,
|
||||||
ContabilObservacaoCreateSerializer,
|
ContabilObservacaoCreateSerializer,
|
||||||
|
ContabilObservacaoDePaulaSerializer,
|
||||||
ContabilObservacaoSerializer,
|
ContabilObservacaoSerializer,
|
||||||
ContabilObservacaoUpdateSerializer,
|
ContabilObservacaoUpdateSerializer,
|
||||||
DepartamentoSerializer,
|
DepartamentoSerializer,
|
||||||
@ -3428,15 +3444,24 @@ def nao_conformidades_dashboard_view(request: Request) -> Response:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _contabil_monta_historico(cabecalho: ContabilCabecalhoExtraido) -> list[ContabilSnapshotHistorico]:
|
_CONTABIL_PERMISSAO = ("relatorios", "dashboard-contabil")
|
||||||
|
_CONTABIL_DE_PAULA_PERMISSAO = ("diretoria", "dashboard-contabil-de-paula")
|
||||||
|
|
||||||
|
|
||||||
|
def _contabil_monta_historico(
|
||||||
|
modelo_apuracao: type[ContabilApuracaoBase], cabecalho: ContabilCabecalhoExtraido
|
||||||
|
) -> list[ContabilSnapshotHistorico]:
|
||||||
"""Histórico da mesma empresa (até 2 apurações anteriores, mais recente
|
"""Histórico da mesma empresa (até 2 apurações anteriores, mais recente
|
||||||
primeiro) já persistidas — usado pelas regras de variação mês a mês
|
primeiro) já persistidas — usado pelas regras de variação mês a mês
|
||||||
(`dashboard_contabil.regras`). Só uma consulta simples ao ORM, sem
|
(`dashboard_contabil.regras`). Só uma consulta simples ao ORM, sem
|
||||||
depender de nenhum relatório complementar anexado (ver CLAUDE.md do
|
depender de nenhum relatório complementar anexado (ver CLAUDE.md do
|
||||||
pacote)."""
|
pacote). `modelo_apuracao` é a tabela da aplicação que está processando
|
||||||
|
(original ou De Paula): o histórico de uma nunca entra na outra. O
|
||||||
|
pipeline chama com só o cabeçalho, então o chamador fixa o model com
|
||||||
|
`functools.partial`."""
|
||||||
competencia_atual = cabecalho.periodo_fim.replace(day=1)
|
competencia_atual = cabecalho.periodo_fim.replace(day=1)
|
||||||
anteriores = (
|
anteriores = (
|
||||||
ContabilApuracao.objects.filter(codigo_empresa=cabecalho.codigo_empresa, competencia__lt=competencia_atual)
|
modelo_apuracao.objects.filter(codigo_empresa=cabecalho.codigo_empresa, competencia__lt=competencia_atual)
|
||||||
.order_by("-competencia")
|
.order_by("-competencia")
|
||||||
.prefetch_related("contas", "linhas_dre")[:2]
|
.prefetch_related("contas", "linhas_dre")[:2]
|
||||||
)
|
)
|
||||||
@ -3450,7 +3475,9 @@ def _contabil_monta_historico(cabecalho: ContabilCabecalhoExtraido) -> list[Cont
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def _contabil_monta_historico_completo(codigo_empresa: str, competencia_atual: date) -> list[ContabilSnapshotHistorico]:
|
def _contabil_monta_historico_completo(
|
||||||
|
modelo_apuracao: type[ContabilApuracaoBase], codigo_empresa: str, competencia_atual: date
|
||||||
|
) -> list[ContabilSnapshotHistorico]:
|
||||||
"""Mesma ideia de `_contabil_monta_historico`, mas **sem** o `[:2]` —
|
"""Mesma ideia de `_contabil_monta_historico`, mas **sem** o `[:2]` —
|
||||||
histórico completo da empresa (mais recente primeiro), usado só pelo
|
histórico completo da empresa (mais recente primeiro), usado só pelo
|
||||||
relatório "Gerar Dashboard" (`ContabilApuracaoViewSet.dashboard`), pro
|
relatório "Gerar Dashboard" (`ContabilApuracaoViewSet.dashboard`), pro
|
||||||
@ -3458,7 +3485,7 @@ def _contabil_monta_historico_completo(codigo_empresa: str, competencia_atual: d
|
|||||||
(`dashboard_contabil.indicadores`). As regras de auditoria continuam
|
(`dashboard_contabil.indicadores`). As regras de auditoria continuam
|
||||||
usando `_contabil_monta_historico` (capado em 2), sem mudar."""
|
usando `_contabil_monta_historico` (capado em 2), sem mudar."""
|
||||||
anteriores = (
|
anteriores = (
|
||||||
ContabilApuracao.objects.filter(codigo_empresa=codigo_empresa, competencia__lt=competencia_atual)
|
modelo_apuracao.objects.filter(codigo_empresa=codigo_empresa, competencia__lt=competencia_atual)
|
||||||
.order_by("-competencia")
|
.order_by("-competencia")
|
||||||
.prefetch_related("contas", "linhas_dre")
|
.prefetch_related("contas", "linhas_dre")
|
||||||
)
|
)
|
||||||
@ -3488,13 +3515,15 @@ class _ContabilDadosIndicadores:
|
|||||||
historico_completo: list[ContabilSnapshotHistorico]
|
historico_completo: list[ContabilSnapshotHistorico]
|
||||||
|
|
||||||
|
|
||||||
def _contabil_coleta_dados_indicadores(apuracao: ContabilApuracao) -> _ContabilDadosIndicadores:
|
def _contabil_coleta_dados_indicadores(apuracao: ContabilApuracaoBase) -> _ContabilDadosIndicadores:
|
||||||
contas = list(apuracao.contas.order_by("ordem", "id"))
|
contas = list(apuracao.contas.order_by("ordem", "id"))
|
||||||
linhas_dre = list(apuracao.linhas_dre.order_by("ordem", "id"))
|
linhas_dre = list(apuracao.linhas_dre.order_by("ordem", "id"))
|
||||||
contas_atuais = {conta.codigo: conta.saldo_atual for conta in contas}
|
contas_atuais = {conta.codigo: conta.saldo_atual for conta in contas}
|
||||||
dre_atual = {linha.descricao: linha.valor for linha in linhas_dre}
|
dre_atual = {linha.descricao: linha.valor for linha in linhas_dre}
|
||||||
resultado_liquido = linhas_dre[-1].valor if linhas_dre else Decimal(0)
|
resultado_liquido = linhas_dre[-1].valor if linhas_dre else Decimal(0)
|
||||||
historico_completo = _contabil_monta_historico_completo(apuracao.codigo_empresa, apuracao.competencia)
|
historico_completo = _contabil_monta_historico_completo(
|
||||||
|
type(apuracao), apuracao.codigo_empresa, apuracao.competencia
|
||||||
|
)
|
||||||
# `variacao_conta` (componente de indicador personalizado, ver abaixo)
|
# `variacao_conta` (componente de indicador personalizado, ver abaixo)
|
||||||
# só compara contra a apuração anterior imediata — mesma convenção já
|
# só compara contra a apuração anterior imediata — mesma convenção já
|
||||||
# usada pra Depreciação/Amortização em `indicadores.calcula_indicadores`
|
# usada pra Depreciação/Amortização em `indicadores.calcula_indicadores`
|
||||||
@ -3770,7 +3799,15 @@ def _contabil_agrupa_indicadores_cards(cards: list[dict[str, Any]]) -> list[dict
|
|||||||
return [{"nome": nome, "cards": grupos[nome]} for nome in sorted(grupos, key=ordenacao)]
|
return [{"nome": nome, "cards": grupos[nome]} for nome in sorted(grupos, key=ordenacao)]
|
||||||
|
|
||||||
|
|
||||||
def _contabil_dados_resumo(apuracao: ContabilApuracao) -> dict[str, Any]:
|
def _contabil_modelo_observacao(apuracao: ContabilApuracaoBase) -> type[ContabilObservacaoBase]:
|
||||||
|
"""Tabela de observações da mesma aplicação da apuração (original ou De
|
||||||
|
Paula), pelo reverse de `apuracao_origem`. Observação é casada por
|
||||||
|
empresa + chave natural, não por FK, então a consulta precisa partir do
|
||||||
|
model certo para o histórico de uma aplicação nunca aparecer na outra."""
|
||||||
|
return apuracao.observacoes.model
|
||||||
|
|
||||||
|
|
||||||
|
def _contabil_dados_resumo(apuracao: ContabilApuracaoBase) -> dict[str, Any]:
|
||||||
"""Indicadores (já agrupados em cards) + observações visíveis ao cliente
|
"""Indicadores (já agrupados em cards) + observações visíveis ao cliente
|
||||||
+ achados com observação do contador — o conteúdo da aba "Resumo" do
|
+ achados com observação do contador — o conteúdo da aba "Resumo" do
|
||||||
relatório "Gerar Dashboard", reaproveitado tal e qual pelo PDF do Resumo
|
relatório "Gerar Dashboard", reaproveitado tal e qual pelo PDF do Resumo
|
||||||
@ -3794,7 +3831,7 @@ def _contabil_dados_resumo(apuracao: ContabilApuracao) -> dict[str, Any]:
|
|||||||
# justamente o ponto do histórico.
|
# justamente o ponto do histórico.
|
||||||
observacoes_visiveis = [
|
observacoes_visiveis = [
|
||||||
observacao
|
observacao
|
||||||
for observacao in ContabilObservacao.vigentes_para(
|
for observacao in _contabil_modelo_observacao(apuracao).vigentes_para(
|
||||||
apuracao.codigo_empresa, apuracao.competencia
|
apuracao.codigo_empresa, apuracao.competencia
|
||||||
).select_related("criado_por")
|
).select_related("criado_por")
|
||||||
if observacao.mostrar_ao_cliente
|
if observacao.mostrar_ao_cliente
|
||||||
@ -3901,7 +3938,7 @@ def _contabil_observacoes_por_chave(apuracao: ContabilApuracao, alvo_tipo: str)
|
|||||||
por linha."""
|
por linha."""
|
||||||
agrupadas: dict[str, list[ContabilObservacao]] = {}
|
agrupadas: dict[str, list[ContabilObservacao]] = {}
|
||||||
consulta = (
|
consulta = (
|
||||||
ContabilObservacao.vigentes_para(apuracao.codigo_empresa, apuracao.competencia)
|
_contabil_modelo_observacao(apuracao).vigentes_para(apuracao.codigo_empresa, apuracao.competencia)
|
||||||
.filter(alvo_tipo=alvo_tipo)
|
.filter(alvo_tipo=alvo_tipo)
|
||||||
.select_related("criado_por", "encerrada_por")
|
.select_related("criado_por", "encerrada_por")
|
||||||
)
|
)
|
||||||
@ -4014,8 +4051,7 @@ def _contabil_sincroniza_contas(
|
|||||||
antiga.alterada_reprocessamento = alterou
|
antiga.alterada_reprocessamento = alterou
|
||||||
antiga.save()
|
antiga.save()
|
||||||
else:
|
else:
|
||||||
ContabilConta.objects.create(
|
apuracao.contas.create(
|
||||||
apuracao=apuracao,
|
|
||||||
ordem=indice,
|
ordem=indice,
|
||||||
conta_numero=extraida.conta_numero,
|
conta_numero=extraida.conta_numero,
|
||||||
codigo=extraida.codigo,
|
codigo=extraida.codigo,
|
||||||
@ -4058,8 +4094,7 @@ def _contabil_sincroniza_linhas_dre(
|
|||||||
antiga.alterada_reprocessamento = alterou
|
antiga.alterada_reprocessamento = alterou
|
||||||
antiga.save()
|
antiga.save()
|
||||||
else:
|
else:
|
||||||
ContabilLinhaDre.objects.create(
|
apuracao.linhas_dre.create(
|
||||||
apuracao=apuracao,
|
|
||||||
ordem=linha.ordem,
|
ordem=linha.ordem,
|
||||||
descricao=linha.descricao,
|
descricao=linha.descricao,
|
||||||
nivel=linha.nivel,
|
nivel=linha.nivel,
|
||||||
@ -4111,8 +4146,7 @@ def _contabil_sincroniza_linhas_analise_vertical(
|
|||||||
antiga.alterada_reprocessamento = alterou
|
antiga.alterada_reprocessamento = alterou
|
||||||
antiga.save()
|
antiga.save()
|
||||||
else:
|
else:
|
||||||
ContabilLinhaAnaliseVertical.objects.create(
|
apuracao.linhas_analise_vertical.create(
|
||||||
apuracao=apuracao,
|
|
||||||
ordem=linha.ordem,
|
ordem=linha.ordem,
|
||||||
descricao=linha.descricao,
|
descricao=linha.descricao,
|
||||||
nivel=linha.nivel,
|
nivel=linha.nivel,
|
||||||
@ -4150,12 +4184,22 @@ def _contabil_recria_achados(
|
|||||||
(`ContabilObservacao`, ver o model) não vivem aqui, são casadas por chave
|
(`ContabilObservacao`, ver o model) não vivem aqui, são casadas por chave
|
||||||
natural (empresa+conta) e continuam intactas através de qualquer
|
natural (empresa+conta) e continuam intactas através de qualquer
|
||||||
reprocessamento — só o apontamento automático é recriado."""
|
reprocessamento — só o apontamento automático é recriado."""
|
||||||
|
apuracao.achados.all().delete()
|
||||||
|
_contabil_cria_achados(apuracao, achados_detectados)
|
||||||
|
|
||||||
|
|
||||||
|
def _contabil_cria_achados(
|
||||||
|
apuracao: ContabilApuracaoBase, achados_detectados: list[dashboard_contabil_modelos.AchadoDetectado]
|
||||||
|
) -> None:
|
||||||
|
"""`bulk_create` dos achados de uma apuração, usado na criação e no
|
||||||
|
reprocessamento. O model do achado vem do reverse `apuracao.achados`, então
|
||||||
|
serve às duas aplicações (original e De Paula) sem citar a classe."""
|
||||||
|
modelo_achado = apuracao.achados.model
|
||||||
contas_por_codigo = {conta.codigo: conta for conta in apuracao.contas.all()}
|
contas_por_codigo = {conta.codigo: conta for conta in apuracao.contas.all()}
|
||||||
av_por_ordem = {linha.ordem: linha for linha in apuracao.linhas_analise_vertical.all()}
|
av_por_ordem = {linha.ordem: linha for linha in apuracao.linhas_analise_vertical.all()}
|
||||||
apuracao.achados.all().delete()
|
modelo_achado.objects.bulk_create(
|
||||||
ContabilAchado.objects.bulk_create(
|
|
||||||
[
|
[
|
||||||
ContabilAchado(
|
modelo_achado(
|
||||||
apuracao=apuracao,
|
apuracao=apuracao,
|
||||||
conta=contas_por_codigo.get(achado.codigo_conta) if achado.codigo_conta else None,
|
conta=contas_por_codigo.get(achado.codigo_conta) if achado.codigo_conta else None,
|
||||||
linha_analise_vertical=(
|
linha_analise_vertical=(
|
||||||
@ -4182,15 +4226,28 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
|
|||||||
persistido da mesma empresa e roda o motor de regras — tudo isso **antes**
|
persistido da mesma empresa e roda o motor de regras — tudo isso **antes**
|
||||||
de gravar qualquer coisa no banco, já que `codigo_empresa`/`competencia`
|
de gravar qualquer coisa no banco, já que `codigo_empresa`/`competencia`
|
||||||
(a chave natural da apuração) só são conhecidos depois de ler o PDF, não
|
(a chave natural da apuração) só são conhecidos depois de ler o PDF, não
|
||||||
informados pelo usuário no upload (diferente de IndicadorApuracao)."""
|
informados pelo usuário no upload (diferente de IndicadorApuracao).
|
||||||
|
|
||||||
|
Serve também ao Relatório Contábil - De Paula (Diretoria), via
|
||||||
|
`ContabilApuracaoDePaulaViewSet`, que só troca os atributos de classe
|
||||||
|
abaixo (permissão, tabela, serializers e prefixo da API). Nada aqui pode
|
||||||
|
citar `ContabilApuracao`/`ContabilConta`/... direto: o model sai de
|
||||||
|
`self.queryset.model` ou dos reverses da própria apuração."""
|
||||||
|
|
||||||
http_method_names = ["get", "post", "delete", "head", "options"]
|
http_method_names = ["get", "post", "delete", "head", "options"]
|
||||||
queryset = ContabilApuracao.objects.all()
|
queryset = ContabilApuracao.objects.all()
|
||||||
|
permissao_app = _CONTABIL_PERMISSAO
|
||||||
|
serializer_lista = ContabilApuracaoListSerializer
|
||||||
|
serializer_detalhe = ContabilApuracaoDetailSerializer
|
||||||
|
serializer_observacao = ContabilObservacaoSerializer
|
||||||
|
# Prefixo da API usado pelos links do relatório HTML (exportar XLSX, PDF
|
||||||
|
# do Resumo), que precisam apontar para os endpoints da mesma aplicação.
|
||||||
|
prefixo_api = "/api/contabil-apuracoes"
|
||||||
|
|
||||||
def get_permissions(self) -> list[BasePermission]:
|
def get_permissions(self) -> list[BasePermission]:
|
||||||
return [PermissaoApp("relatorios", "dashboard-contabil")]
|
return [PermissaoApp(*self.permissao_app)]
|
||||||
|
|
||||||
def get_queryset(self) -> QuerySet[ContabilApuracao]:
|
def get_queryset(self) -> QuerySet[ContabilApuracaoBase]:
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()
|
||||||
if self.action == "retrieve":
|
if self.action == "retrieve":
|
||||||
queryset = queryset.prefetch_related(
|
queryset = queryset.prefetch_related(
|
||||||
@ -4202,10 +4259,10 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
def get_serializer_class(self) -> type[ModelSerializer]:
|
def get_serializer_class(self) -> type[ModelSerializer]:
|
||||||
if self.action == "list":
|
if self.action == "list":
|
||||||
return ContabilApuracaoListSerializer
|
return self.serializer_lista
|
||||||
return ContabilApuracaoDetailSerializer
|
return self.serializer_detalhe
|
||||||
|
|
||||||
def perform_destroy(self, instance: ContabilApuracao) -> None:
|
def perform_destroy(self, instance: ContabilApuracaoBase) -> None:
|
||||||
"""Análise **Concluída** não pode mais ser excluída — pedido explícito
|
"""Análise **Concluída** não pode mais ser excluída — pedido explícito
|
||||||
do usuário, mesma trava de "concluiu, não se mexe mais" que já valia
|
do usuário, mesma trava de "concluiu, não se mexe mais" que já valia
|
||||||
pra reprocessar e pra editar observação/achado
|
pra reprocessar e pra editar observação/achado
|
||||||
@ -4225,17 +4282,18 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
|
|||||||
entrada.is_valid(raise_exception=True)
|
entrada.is_valid(raise_exception=True)
|
||||||
arquivo = entrada.validated_data["arquivo"]
|
arquivo = entrada.validated_data["arquivo"]
|
||||||
conteudo = arquivo.read()
|
conteudo = arquivo.read()
|
||||||
|
modelo_apuracao = self.queryset.model
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resultado = dashboard_contabil_pipeline.processa_apuracao(
|
resultado = dashboard_contabil_pipeline.processa_apuracao(
|
||||||
io.BytesIO(conteudo), _contabil_monta_historico, arquivo.name
|
io.BytesIO(conteudo), functools.partial(_contabil_monta_historico, modelo_apuracao), arquivo.name
|
||||||
)
|
)
|
||||||
except ContabilExtracaoInvalidaError as exc:
|
except ContabilExtracaoInvalidaError as exc:
|
||||||
return _contabil_resposta_extracao_invalida(exc)
|
return _contabil_resposta_extracao_invalida(exc)
|
||||||
|
|
||||||
cabecalho = resultado.extracao.cabecalho
|
cabecalho = resultado.extracao.cabecalho
|
||||||
competencia = cabecalho.periodo_fim.replace(day=1)
|
competencia = cabecalho.periodo_fim.replace(day=1)
|
||||||
if ContabilApuracao.objects.filter(codigo_empresa=cabecalho.codigo_empresa, competencia=competencia).exists():
|
if modelo_apuracao.objects.filter(codigo_empresa=cabecalho.codigo_empresa, competencia=competencia).exists():
|
||||||
return Response(
|
return Response(
|
||||||
{"detail": f"Já existe uma análise para {cabecalho.nome_empresa} na competência {competencia:%m/%Y}."},
|
{"detail": f"Já existe uma análise para {cabecalho.nome_empresa} na competência {competencia:%m/%Y}."},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
@ -4244,7 +4302,7 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
|
|||||||
# O PDF não é gravado: depois de extraído, nada mais o usa.
|
# O PDF não é gravado: depois de extraído, nada mais o usa.
|
||||||
try:
|
try:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
apuracao = ContabilApuracao.objects.create(
|
apuracao = modelo_apuracao.objects.create(
|
||||||
codigo_empresa=cabecalho.codigo_empresa,
|
codigo_empresa=cabecalho.codigo_empresa,
|
||||||
nome_empresa=cabecalho.nome_empresa,
|
nome_empresa=cabecalho.nome_empresa,
|
||||||
cnpj=cabecalho.cnpj,
|
cnpj=cabecalho.cnpj,
|
||||||
@ -4256,9 +4314,12 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
|
|||||||
fonte_pdf_atipica=resultado.extracao.fonte_pdf_atipica,
|
fonte_pdf_atipica=resultado.extracao.fonte_pdf_atipica,
|
||||||
leiaute=resultado.extracao.leiaute,
|
leiaute=resultado.extracao.leiaute,
|
||||||
)
|
)
|
||||||
ContabilConta.objects.bulk_create(
|
modelo_conta = apuracao.contas.model
|
||||||
|
modelo_linha_dre = apuracao.linhas_dre.model
|
||||||
|
modelo_linha_av = apuracao.linhas_analise_vertical.model
|
||||||
|
modelo_conta.objects.bulk_create(
|
||||||
[
|
[
|
||||||
ContabilConta(
|
modelo_conta(
|
||||||
apuracao=apuracao,
|
apuracao=apuracao,
|
||||||
ordem=indice,
|
ordem=indice,
|
||||||
conta_numero=conta.conta_numero,
|
conta_numero=conta.conta_numero,
|
||||||
@ -4273,9 +4334,9 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
|
|||||||
for indice, conta in enumerate(resultado.extracao.contas)
|
for indice, conta in enumerate(resultado.extracao.contas)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
ContabilLinhaDre.objects.bulk_create(
|
modelo_linha_dre.objects.bulk_create(
|
||||||
[
|
[
|
||||||
ContabilLinhaDre(
|
modelo_linha_dre(
|
||||||
apuracao=apuracao,
|
apuracao=apuracao,
|
||||||
ordem=linha.ordem,
|
ordem=linha.ordem,
|
||||||
descricao=linha.descricao,
|
descricao=linha.descricao,
|
||||||
@ -4286,9 +4347,9 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
|
|||||||
for linha in resultado.extracao.linhas_dre
|
for linha in resultado.extracao.linhas_dre
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
ContabilLinhaAnaliseVertical.objects.bulk_create(
|
modelo_linha_av.objects.bulk_create(
|
||||||
[
|
[
|
||||||
ContabilLinhaAnaliseVertical(
|
modelo_linha_av(
|
||||||
apuracao=apuracao,
|
apuracao=apuracao,
|
||||||
ordem=linha.ordem,
|
ordem=linha.ordem,
|
||||||
descricao=linha.descricao,
|
descricao=linha.descricao,
|
||||||
@ -4302,34 +4363,14 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
|
|||||||
for linha in resultado.extracao.linhas_analise_vertical
|
for linha in resultado.extracao.linhas_analise_vertical
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
contas_por_codigo = {conta.codigo: conta for conta in apuracao.contas.all()}
|
_contabil_cria_achados(apuracao, resultado.achados)
|
||||||
av_por_ordem = {linha.ordem: linha for linha in apuracao.linhas_analise_vertical.all()}
|
|
||||||
ContabilAchado.objects.bulk_create(
|
|
||||||
[
|
|
||||||
ContabilAchado(
|
|
||||||
apuracao=apuracao,
|
|
||||||
conta=contas_por_codigo.get(achado.codigo_conta) if achado.codigo_conta else None,
|
|
||||||
linha_analise_vertical=(
|
|
||||||
av_por_ordem.get(achado.ordem_linha_analise_vertical)
|
|
||||||
if achado.ordem_linha_analise_vertical is not None
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
regra=achado.regra,
|
|
||||||
severidade=achado.severidade,
|
|
||||||
titulo=achado.titulo,
|
|
||||||
mensagem=achado.mensagem,
|
|
||||||
valor_referencia=achado.valor_referencia,
|
|
||||||
)
|
|
||||||
for achado in resultado.achados
|
|
||||||
]
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return Response(
|
return Response(
|
||||||
{"detail": "Não foi possível salvar a análise. Contate o setor de Inovação."},
|
{"detail": "Não foi possível salvar a análise. Contate o setor de Inovação."},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
serializer = ContabilApuracaoDetailSerializer(apuracao)
|
serializer = self.serializer_detalhe(apuracao)
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
@action(detail=True, methods=["post"])
|
@action(detail=True, methods=["post"])
|
||||||
@ -4381,7 +4422,7 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
resultado = dashboard_contabil_pipeline.processa_apuracao(
|
resultado = dashboard_contabil_pipeline.processa_apuracao(
|
||||||
io.BytesIO(conteudo), _contabil_monta_historico, arquivo.name
|
io.BytesIO(conteudo), functools.partial(_contabil_monta_historico, type(apuracao)), arquivo.name
|
||||||
)
|
)
|
||||||
except ContabilExtracaoInvalidaError as exc:
|
except ContabilExtracaoInvalidaError as exc:
|
||||||
return _contabil_resposta_extracao_invalida(exc)
|
return _contabil_resposta_extracao_invalida(exc)
|
||||||
@ -4428,17 +4469,17 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
|
|||||||
_contabil_recria_achados(
|
_contabil_recria_achados(
|
||||||
apuracao, resultado.achados + dashboard_contabil_regras.achados_itens_removidos(removidos)
|
apuracao, resultado.achados + dashboard_contabil_regras.achados_itens_removidos(removidos)
|
||||||
)
|
)
|
||||||
ContabilApuracaoReprocessamento.objects.create(apuracao=apuracao, reprocessado_por=request.user)
|
apuracao.reprocessamentos.create(reprocessado_por=request.user)
|
||||||
except Exception:
|
except Exception:
|
||||||
return Response(
|
return Response(
|
||||||
{"detail": "Não foi possível reprocessar a análise. Contate o setor de Inovação."},
|
{"detail": "Não foi possível reprocessar a análise. Contate o setor de Inovação."},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
apuracao =ContabilApuracao.objects.prefetch_related(
|
apuracao = self.queryset.model.objects.prefetch_related(
|
||||||
"contas", "linhas_dre", "linhas_analise_vertical", "achados__conta"
|
"contas", "linhas_dre", "linhas_analise_vertical", "achados__conta"
|
||||||
).get(pk=apuracao.pk)
|
).get(pk=apuracao.pk)
|
||||||
return Response(ContabilApuracaoDetailSerializer(apuracao).data)
|
return Response(self.serializer_detalhe(apuracao).data)
|
||||||
|
|
||||||
@action(detail=True, methods=["post"])
|
@action(detail=True, methods=["post"])
|
||||||
def concluir(self, request: Request, pk: str | None = None) -> Response:
|
def concluir(self, request: Request, pk: str | None = None) -> Response:
|
||||||
@ -4451,7 +4492,7 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
|
|||||||
apuracao.status = ContabilApuracao.STATUS_CONCLUIDA
|
apuracao.status = ContabilApuracao.STATUS_CONCLUIDA
|
||||||
apuracao.concluida_em = timezone.now()
|
apuracao.concluida_em = timezone.now()
|
||||||
apuracao.save(update_fields=["status", "concluida_em"])
|
apuracao.save(update_fields=["status", "concluida_em"])
|
||||||
return Response(ContabilApuracaoDetailSerializer(apuracao).data)
|
return Response(self.serializer_detalhe(apuracao).data)
|
||||||
|
|
||||||
@action(detail=True, methods=["get"])
|
@action(detail=True, methods=["get"])
|
||||||
def observacoes(self, request: Request, pk: str | None = None) -> Response:
|
def observacoes(self, request: Request, pk: str | None = None) -> Response:
|
||||||
@ -4462,11 +4503,12 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
|
|||||||
linha (a linha é recriada a cada apuração, a observação não)."""
|
linha (a linha é recriada a cada apuração, a observação não)."""
|
||||||
apuracao = self.get_object()
|
apuracao = self.get_object()
|
||||||
consulta = (
|
consulta = (
|
||||||
ContabilObservacao.vigentes_para(apuracao.codigo_empresa, apuracao.competencia)
|
_contabil_modelo_observacao(apuracao)
|
||||||
|
.vigentes_para(apuracao.codigo_empresa, apuracao.competencia)
|
||||||
.select_related("criado_por", "encerrada_por")
|
.select_related("criado_por", "encerrada_por")
|
||||||
.prefetch_related("edicoes__editado_por")
|
.prefetch_related("edicoes__editado_por")
|
||||||
)
|
)
|
||||||
serializada = ContabilObservacaoSerializer(
|
serializada = self.serializer_observacao(
|
||||||
consulta, many=True, context={"competencia": apuracao.competencia}
|
consulta, many=True, context={"competencia": apuracao.competencia}
|
||||||
)
|
)
|
||||||
return Response(serializada.data)
|
return Response(serializada.data)
|
||||||
@ -4574,6 +4616,7 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
|
|||||||
"achados_com_observacao": dados_resumo["achados_com_observacao"],
|
"achados_com_observacao": dados_resumo["achados_com_observacao"],
|
||||||
"gerado_em": timezone.now(),
|
"gerado_em": timezone.now(),
|
||||||
"logo_data_uri": _logo_relatorio_data_uri(),
|
"logo_data_uri": _logo_relatorio_data_uri(),
|
||||||
|
"prefixo_api": self.prefixo_api,
|
||||||
}
|
}
|
||||||
html = render_to_string("dashboard-contabil-relatorio.html", contexto)
|
html = render_to_string("dashboard-contabil-relatorio.html", contexto)
|
||||||
return HttpResponse(html, content_type="text/html; charset=utf-8")
|
return HttpResponse(html, content_type="text/html; charset=utf-8")
|
||||||
@ -4839,9 +4882,10 @@ class ContabilContaViewSet(viewsets.ModelViewSet):
|
|||||||
http_method_names = ["get", "patch", "head", "options"]
|
http_method_names = ["get", "patch", "head", "options"]
|
||||||
queryset = ContabilConta.objects.all()
|
queryset = ContabilConta.objects.all()
|
||||||
serializer_class = ContabilContaSerializer
|
serializer_class = ContabilContaSerializer
|
||||||
|
permissao_app = _CONTABIL_PERMISSAO
|
||||||
|
|
||||||
def get_permissions(self) -> list[BasePermission]:
|
def get_permissions(self) -> list[BasePermission]:
|
||||||
return [PermissaoApp("relatorios", "dashboard-contabil")]
|
return [PermissaoApp(*self.permissao_app)]
|
||||||
|
|
||||||
def perform_update(self, serializer: ContabilContaSerializer) -> None:
|
def perform_update(self, serializer: ContabilContaSerializer) -> None:
|
||||||
_contabil_garante_em_revisao(serializer.instance.apuracao)
|
_contabil_garante_em_revisao(serializer.instance.apuracao)
|
||||||
@ -4856,9 +4900,10 @@ class ContabilLinhaDreViewSet(viewsets.ModelViewSet):
|
|||||||
http_method_names = ["get", "patch", "head", "options"]
|
http_method_names = ["get", "patch", "head", "options"]
|
||||||
queryset = ContabilLinhaDre.objects.all()
|
queryset = ContabilLinhaDre.objects.all()
|
||||||
serializer_class = ContabilLinhaDreSerializer
|
serializer_class = ContabilLinhaDreSerializer
|
||||||
|
permissao_app = _CONTABIL_PERMISSAO
|
||||||
|
|
||||||
def get_permissions(self) -> list[BasePermission]:
|
def get_permissions(self) -> list[BasePermission]:
|
||||||
return [PermissaoApp("relatorios", "dashboard-contabil")]
|
return [PermissaoApp(*self.permissao_app)]
|
||||||
|
|
||||||
def perform_update(self, serializer: ContabilLinhaDreSerializer) -> None:
|
def perform_update(self, serializer: ContabilLinhaDreSerializer) -> None:
|
||||||
_contabil_garante_em_revisao(serializer.instance.apuracao)
|
_contabil_garante_em_revisao(serializer.instance.apuracao)
|
||||||
@ -4873,9 +4918,10 @@ class ContabilLinhaAnaliseVerticalViewSet(viewsets.ModelViewSet):
|
|||||||
http_method_names = ["get", "patch", "head", "options"]
|
http_method_names = ["get", "patch", "head", "options"]
|
||||||
queryset = ContabilLinhaAnaliseVertical.objects.all()
|
queryset = ContabilLinhaAnaliseVertical.objects.all()
|
||||||
serializer_class = ContabilLinhaAnaliseVerticalSerializer
|
serializer_class = ContabilLinhaAnaliseVerticalSerializer
|
||||||
|
permissao_app = _CONTABIL_PERMISSAO
|
||||||
|
|
||||||
def get_permissions(self) -> list[BasePermission]:
|
def get_permissions(self) -> list[BasePermission]:
|
||||||
return [PermissaoApp("relatorios", "dashboard-contabil")]
|
return [PermissaoApp(*self.permissao_app)]
|
||||||
|
|
||||||
def perform_update(self, serializer: ContabilLinhaAnaliseVerticalSerializer) -> None:
|
def perform_update(self, serializer: ContabilLinhaAnaliseVerticalSerializer) -> None:
|
||||||
_contabil_garante_em_revisao(serializer.instance.apuracao)
|
_contabil_garante_em_revisao(serializer.instance.apuracao)
|
||||||
@ -4920,21 +4966,24 @@ class ContabilObservacaoViewSet(viewsets.ModelViewSet):
|
|||||||
http_method_names = ["post", "patch", "delete", "head", "options"]
|
http_method_names = ["post", "patch", "delete", "head", "options"]
|
||||||
queryset = ContabilObservacao.objects.select_related("criado_por", "encerrada_por", "apuracao_origem")
|
queryset = ContabilObservacao.objects.select_related("criado_por", "encerrada_por", "apuracao_origem")
|
||||||
serializer_class = ContabilObservacaoSerializer
|
serializer_class = ContabilObservacaoSerializer
|
||||||
|
serializer_criacao = ContabilObservacaoCreateSerializer
|
||||||
|
serializer_apuracao = ContabilObservacaoApuracaoSerializer
|
||||||
|
permissao_app = _CONTABIL_PERMISSAO
|
||||||
|
|
||||||
def get_permissions(self) -> list[BasePermission]:
|
def get_permissions(self) -> list[BasePermission]:
|
||||||
return [PermissaoApp("relatorios", "dashboard-contabil")]
|
return [PermissaoApp(*self.permissao_app)]
|
||||||
|
|
||||||
def _resolve_alvo(self, apuracao: ContabilApuracao, alvo_tipo: str, alvo_id: int) -> Any:
|
def _resolve_alvo(self, apuracao: ContabilApuracaoBase, alvo_tipo: str, alvo_id: int) -> Any:
|
||||||
"""Conta/linha **desta** apuração que recebeu a observação — pegar o
|
"""Conta/linha **desta** apuração que recebeu a observação — pegar o
|
||||||
objeto (em vez de confiar numa chave enviada pelo cliente) garante
|
objeto (em vez de confiar numa chave enviada pelo cliente) garante
|
||||||
que a chave natural gravada é sempre a real e que o alvo pertence
|
que a chave natural gravada é sempre a real e que o alvo pertence
|
||||||
mesmo à apuração informada."""
|
mesmo à apuração informada."""
|
||||||
modelos = {
|
relacoes = {
|
||||||
ContabilObservacao.ALVO_CONTA: ContabilConta,
|
ContabilObservacao.ALVO_CONTA: apuracao.contas,
|
||||||
ContabilObservacao.ALVO_DRE: ContabilLinhaDre,
|
ContabilObservacao.ALVO_DRE: apuracao.linhas_dre,
|
||||||
ContabilObservacao.ALVO_ANALISE_VERTICAL: ContabilLinhaAnaliseVertical,
|
ContabilObservacao.ALVO_ANALISE_VERTICAL: apuracao.linhas_analise_vertical,
|
||||||
}
|
}
|
||||||
alvo = modelos[alvo_tipo].objects.filter(pk=alvo_id, apuracao=apuracao).first()
|
alvo = relacoes[alvo_tipo].filter(pk=alvo_id).first()
|
||||||
if alvo is None:
|
if alvo is None:
|
||||||
raise ValidationError({"alvo_id": "Conta ou linha não encontrada nesta análise."})
|
raise ValidationError({"alvo_id": "Conta ou linha não encontrada nesta análise."})
|
||||||
return alvo
|
return alvo
|
||||||
@ -4952,13 +5001,13 @@ class ContabilObservacaoViewSet(viewsets.ModelViewSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def create(self, request: Request, *args: Any, **kwargs: Any) -> Response:
|
def create(self, request: Request, *args: Any, **kwargs: Any) -> Response:
|
||||||
entrada = ContabilObservacaoCreateSerializer(data=request.data)
|
entrada = self.serializer_criacao(data=request.data)
|
||||||
entrada.is_valid(raise_exception=True)
|
entrada.is_valid(raise_exception=True)
|
||||||
dados = entrada.validated_data
|
dados = entrada.validated_data
|
||||||
apuracao: ContabilApuracao = dados["apuracao"]
|
apuracao: ContabilApuracaoBase = dados["apuracao"]
|
||||||
_contabil_garante_em_revisao(apuracao)
|
_contabil_garante_em_revisao(apuracao)
|
||||||
alvo = self._resolve_alvo(apuracao, dados["alvo_tipo"], dados["alvo_id"])
|
alvo = self._resolve_alvo(apuracao, dados["alvo_tipo"], dados["alvo_id"])
|
||||||
observacao = ContabilObservacao.objects.create(
|
observacao = self.queryset.model.objects.create(
|
||||||
codigo_empresa=apuracao.codigo_empresa,
|
codigo_empresa=apuracao.codigo_empresa,
|
||||||
alvo_tipo=dados["alvo_tipo"],
|
alvo_tipo=dados["alvo_tipo"],
|
||||||
alvo_chave=_contabil_chave_alvo(apuracao, dados["alvo_tipo"], alvo),
|
alvo_chave=_contabil_chave_alvo(apuracao, dados["alvo_tipo"], alvo),
|
||||||
@ -4969,11 +5018,11 @@ class ContabilObservacaoViewSet(viewsets.ModelViewSet):
|
|||||||
mostrar_ao_cliente=dados["mostrar_ao_cliente"],
|
mostrar_ao_cliente=dados["mostrar_ao_cliente"],
|
||||||
criado_por=request.user,
|
criado_por=request.user,
|
||||||
)
|
)
|
||||||
serializada = ContabilObservacaoSerializer(observacao, context={"competencia": apuracao.competencia})
|
serializada = self.serializer_class(observacao, context={"competencia": apuracao.competencia})
|
||||||
return Response(serializada.data, status=status.HTTP_201_CREATED)
|
return Response(serializada.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
def partial_update(self, request: Request, *args: Any, **kwargs: Any) -> Response:
|
def partial_update(self, request: Request, *args: Any, **kwargs: Any) -> Response:
|
||||||
observacao: ContabilObservacao = self.get_object()
|
observacao: ContabilObservacaoBase = self.get_object()
|
||||||
entrada = ContabilObservacaoUpdateSerializer(data=request.data)
|
entrada = ContabilObservacaoUpdateSerializer(data=request.data)
|
||||||
entrada.is_valid(raise_exception=True)
|
entrada.is_valid(raise_exception=True)
|
||||||
dados = entrada.validated_data
|
dados = entrada.validated_data
|
||||||
@ -4982,8 +5031,7 @@ class ContabilObservacaoViewSet(viewsets.ModelViewSet):
|
|||||||
self._garante_texto_editavel(observacao)
|
self._garante_texto_editavel(observacao)
|
||||||
texto_novo = dados["texto"].strip()
|
texto_novo = dados["texto"].strip()
|
||||||
if texto_novo != observacao.texto:
|
if texto_novo != observacao.texto:
|
||||||
ContabilObservacaoEdicao.objects.create(
|
observacao.edicoes.create(
|
||||||
observacao=observacao,
|
|
||||||
texto_anterior=observacao.texto,
|
texto_anterior=observacao.texto,
|
||||||
texto_novo=texto_novo,
|
texto_novo=texto_novo,
|
||||||
editado_por=request.user,
|
editado_por=request.user,
|
||||||
@ -4995,9 +5043,9 @@ class ContabilObservacaoViewSet(viewsets.ModelViewSet):
|
|||||||
campos.append("mostrar_ao_cliente")
|
campos.append("mostrar_ao_cliente")
|
||||||
observacao.save(update_fields=[*campos, "atualizado_em"])
|
observacao.save(update_fields=[*campos, "atualizado_em"])
|
||||||
competencia = observacao.apuracao_origem.competencia if observacao.apuracao_origem else None
|
competencia = observacao.apuracao_origem.competencia if observacao.apuracao_origem else None
|
||||||
return Response(ContabilObservacaoSerializer(observacao, context={"competencia": competencia}).data)
|
return Response(self.serializer_class(observacao, context={"competencia": competencia}).data)
|
||||||
|
|
||||||
def perform_destroy(self, instance: ContabilObservacao) -> None:
|
def perform_destroy(self, instance: ContabilObservacaoBase) -> None:
|
||||||
self._garante_texto_editavel(instance)
|
self._garante_texto_editavel(instance)
|
||||||
if instance.criado_por_id != self.request.user.id:
|
if instance.criado_por_id != self.request.user.id:
|
||||||
raise PermissionDenied("Só quem registrou esta observação pode excluí-la.")
|
raise PermissionDenied("Só quem registrou esta observação pode excluí-la.")
|
||||||
@ -5009,15 +5057,15 @@ class ContabilObservacaoViewSet(viewsets.ModelViewSet):
|
|||||||
competência informada (e em todas as anteriores — o histórico nunca é
|
competência informada (e em todas as anteriores — o histórico nunca é
|
||||||
reescrito) e some a partir da seguinte."""
|
reescrito) e some a partir da seguinte."""
|
||||||
observacao = self.get_object()
|
observacao = self.get_object()
|
||||||
entrada = ContabilObservacaoApuracaoSerializer(data=request.data)
|
entrada = self.serializer_apuracao(data=request.data)
|
||||||
entrada.is_valid(raise_exception=True)
|
entrada.is_valid(raise_exception=True)
|
||||||
apuracao: ContabilApuracao = entrada.validated_data["apuracao"]
|
apuracao: ContabilApuracaoBase = entrada.validated_data["apuracao"]
|
||||||
_contabil_garante_em_revisao(apuracao)
|
_contabil_garante_em_revisao(apuracao)
|
||||||
observacao.encerrada_em_competencia = apuracao.competencia
|
observacao.encerrada_em_competencia = apuracao.competencia
|
||||||
observacao.encerrada_por = request.user
|
observacao.encerrada_por = request.user
|
||||||
observacao.encerrada_em = timezone.now()
|
observacao.encerrada_em = timezone.now()
|
||||||
observacao.save(update_fields=["encerrada_em_competencia", "encerrada_por", "encerrada_em", "atualizado_em"])
|
observacao.save(update_fields=["encerrada_em_competencia", "encerrada_por", "encerrada_em", "atualizado_em"])
|
||||||
serializada = ContabilObservacaoSerializer(observacao, context={"competencia": apuracao.competencia})
|
serializada = self.serializer_class(observacao, context={"competencia": apuracao.competencia})
|
||||||
return Response(serializada.data)
|
return Response(serializada.data)
|
||||||
|
|
||||||
@action(detail=True, methods=["post"])
|
@action(detail=True, methods=["post"])
|
||||||
@ -5026,15 +5074,15 @@ class ContabilObservacaoViewSet(viewsets.ModelViewSet):
|
|||||||
frente. Existe pro contador não ficar preso a um clique errado; o
|
frente. Existe pro contador não ficar preso a um clique errado; o
|
||||||
texto continua intocado nos dois caminhos."""
|
texto continua intocado nos dois caminhos."""
|
||||||
observacao = self.get_object()
|
observacao = self.get_object()
|
||||||
entrada = ContabilObservacaoApuracaoSerializer(data=request.data)
|
entrada = self.serializer_apuracao(data=request.data)
|
||||||
entrada.is_valid(raise_exception=True)
|
entrada.is_valid(raise_exception=True)
|
||||||
apuracao: ContabilApuracao = entrada.validated_data["apuracao"]
|
apuracao: ContabilApuracaoBase = entrada.validated_data["apuracao"]
|
||||||
_contabil_garante_em_revisao(apuracao)
|
_contabil_garante_em_revisao(apuracao)
|
||||||
observacao.encerrada_em_competencia = None
|
observacao.encerrada_em_competencia = None
|
||||||
observacao.encerrada_por = None
|
observacao.encerrada_por = None
|
||||||
observacao.encerrada_em = None
|
observacao.encerrada_em = None
|
||||||
observacao.save(update_fields=["encerrada_em_competencia", "encerrada_por", "encerrada_em", "atualizado_em"])
|
observacao.save(update_fields=["encerrada_em_competencia", "encerrada_por", "encerrada_em", "atualizado_em"])
|
||||||
serializada = ContabilObservacaoSerializer(observacao, context={"competencia": apuracao.competencia})
|
serializada = self.serializer_class(observacao, context={"competencia": apuracao.competencia})
|
||||||
return Response(serializada.data)
|
return Response(serializada.data)
|
||||||
|
|
||||||
|
|
||||||
@ -5046,17 +5094,19 @@ class ContabilAchadoViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
http_method_names = ["get", "patch", "post", "head", "options"]
|
http_method_names = ["get", "patch", "post", "head", "options"]
|
||||||
queryset = ContabilAchado.objects.all()
|
queryset = ContabilAchado.objects.all()
|
||||||
|
serializer_leitura = ContabilAchadoSerializer
|
||||||
|
permissao_app = _CONTABIL_PERMISSAO
|
||||||
|
|
||||||
def get_permissions(self) -> list[BasePermission]:
|
def get_permissions(self) -> list[BasePermission]:
|
||||||
return [PermissaoApp("relatorios", "dashboard-contabil")]
|
return [PermissaoApp(*self.permissao_app)]
|
||||||
|
|
||||||
def get_serializer_class(self) -> type[ModelSerializer]:
|
def get_serializer_class(self) -> type[ModelSerializer]:
|
||||||
if self.action in ("update", "partial_update"):
|
if self.action in ("update", "partial_update"):
|
||||||
return ContabilAchadoAjusteSerializer
|
return ContabilAchadoAjusteSerializer
|
||||||
return ContabilAchadoSerializer
|
return self.serializer_leitura
|
||||||
|
|
||||||
def perform_update(self, serializer: ContabilAchadoAjusteSerializer) -> None:
|
def perform_update(self, serializer: ContabilAchadoAjusteSerializer) -> None:
|
||||||
achado: ContabilAchado = self.get_object()
|
achado: ContabilAchadoBase = self.get_object()
|
||||||
_contabil_garante_em_revisao(achado.apuracao)
|
_contabil_garante_em_revisao(achado.apuracao)
|
||||||
achado.status = serializer.validated_data["status"]
|
achado.status = serializer.validated_data["status"]
|
||||||
achado.observacao_contador = serializer.validated_data["observacao_contador"]
|
achado.observacao_contador = serializer.validated_data["observacao_contador"]
|
||||||
@ -5067,7 +5117,7 @@ class ContabilAchadoViewSet(viewsets.ModelViewSet):
|
|||||||
def update(self, request: Request, *args: Any, **kwargs: Any) -> Response:
|
def update(self, request: Request, *args: Any, **kwargs: Any) -> Response:
|
||||||
super().update(request, *args, **kwargs)
|
super().update(request, *args, **kwargs)
|
||||||
achado = self.get_object()
|
achado = self.get_object()
|
||||||
return Response(ContabilAchadoSerializer(achado).data)
|
return Response(self.serializer_leitura(achado).data)
|
||||||
|
|
||||||
@action(detail=True, methods=["post"], url_path="alternar-oculto")
|
@action(detail=True, methods=["post"], url_path="alternar-oculto")
|
||||||
def alternar_oculto(self, request: Request, pk: str | None = None) -> Response:
|
def alternar_oculto(self, request: Request, pk: str | None = None) -> Response:
|
||||||
@ -5083,7 +5133,7 @@ class ContabilAchadoViewSet(viewsets.ModelViewSet):
|
|||||||
entrada.is_valid(raise_exception=True)
|
entrada.is_valid(raise_exception=True)
|
||||||
achado.oculto_no_relatorio = entrada.validated_data["oculto_no_relatorio"]
|
achado.oculto_no_relatorio = entrada.validated_data["oculto_no_relatorio"]
|
||||||
achado.save(update_fields=["oculto_no_relatorio"])
|
achado.save(update_fields=["oculto_no_relatorio"])
|
||||||
return Response(ContabilAchadoSerializer(achado).data)
|
return Response(self.serializer_leitura(achado).data)
|
||||||
|
|
||||||
@action(detail=True, methods=["post"])
|
@action(detail=True, methods=["post"])
|
||||||
def validar(self, request: Request, pk: str | None = None) -> Response:
|
def validar(self, request: Request, pk: str | None = None) -> Response:
|
||||||
@ -5110,7 +5160,7 @@ class ContabilAchadoViewSet(viewsets.ModelViewSet):
|
|||||||
achado.tratado_por = None
|
achado.tratado_por = None
|
||||||
achado.tratado_em = None
|
achado.tratado_em = None
|
||||||
achado.save(update_fields=["status", "tratado_por", "tratado_em"])
|
achado.save(update_fields=["status", "tratado_por", "tratado_em"])
|
||||||
return Response(ContabilAchadoSerializer(achado).data)
|
return Response(self.serializer_leitura(achado).data)
|
||||||
|
|
||||||
|
|
||||||
def _gera_chave_indicador_contabil(nome: str) -> str:
|
def _gera_chave_indicador_contabil(nome: str) -> str:
|
||||||
@ -5150,7 +5200,11 @@ class IndicadorContabilDefinicaoViewSet(viewsets.ModelViewSet):
|
|||||||
queryset = IndicadorContabilDefinicao.objects.prefetch_related("componentes").all()
|
queryset = IndicadorContabilDefinicao.objects.prefetch_related("componentes").all()
|
||||||
|
|
||||||
def get_permissions(self) -> list[BasePermission]:
|
def get_permissions(self) -> list[BasePermission]:
|
||||||
return [PermissaoApp("relatorios", "dashboard-contabil")]
|
# O cadastro de indicadores é compartilhado com o Relatório Contábil -
|
||||||
|
# De Paula (decisão do usuário): quem tem qualquer uma das duas
|
||||||
|
# aplicações gerencia os mesmos indicadores. Só o cadastro é
|
||||||
|
# compartilhado; nenhum dado de apuração passa por aqui.
|
||||||
|
return [OR(PermissaoApp(*_CONTABIL_PERMISSAO), PermissaoApp(*_CONTABIL_DE_PAULA_PERMISSAO))]
|
||||||
|
|
||||||
def get_serializer_class(self) -> type[ModelSerializer]:
|
def get_serializer_class(self) -> type[ModelSerializer]:
|
||||||
if self.action in ("create", "update", "partial_update"):
|
if self.action in ("create", "update", "partial_update"):
|
||||||
@ -5246,21 +5300,73 @@ class IndicadorContabilDefinicaoViewSet(viewsets.ModelViewSet):
|
|||||||
# em `ContabilApuracao.indicadores_selecionados`/`indicadores_ocultos`
|
# em `ContabilApuracao.indicadores_selecionados`/`indicadores_ocultos`
|
||||||
# e a próxima tentativa de alternar QUALQUER indicador no hub daquela
|
# e a próxima tentativa de alternar QUALQUER indicador no hub daquela
|
||||||
# apuração falharia (a validação reenvia a lista inteira a cada
|
# apuração falharia (a validação reenvia a lista inteira a cada
|
||||||
# alternância). Ver bug real, rodada 106.
|
# alternância). Ver bug real, rodada 106. Varre as duas aplicações,
|
||||||
for apuracao in ContabilApuracao.objects.filter(
|
# já que o cadastro de indicadores é compartilhado entre elas.
|
||||||
Q(indicadores_selecionados__contains=[chave]) | Q(indicadores_ocultos__contains=[chave])
|
for modelo_apuracao in (ContabilApuracao, ContabilApuracaoDePaula):
|
||||||
):
|
for apuracao in modelo_apuracao.objects.filter(
|
||||||
campos_alterados = []
|
Q(indicadores_selecionados__contains=[chave]) | Q(indicadores_ocultos__contains=[chave])
|
||||||
if chave in apuracao.indicadores_selecionados:
|
):
|
||||||
apuracao.indicadores_selecionados = [
|
campos_alterados = []
|
||||||
c for c in apuracao.indicadores_selecionados if c != chave
|
if chave in apuracao.indicadores_selecionados:
|
||||||
]
|
apuracao.indicadores_selecionados = [
|
||||||
campos_alterados.append("indicadores_selecionados")
|
c for c in apuracao.indicadores_selecionados if c != chave
|
||||||
if chave in apuracao.indicadores_ocultos:
|
]
|
||||||
apuracao.indicadores_ocultos = [c for c in apuracao.indicadores_ocultos if c != chave]
|
campos_alterados.append("indicadores_selecionados")
|
||||||
campos_alterados.append("indicadores_ocultos")
|
if chave in apuracao.indicadores_ocultos:
|
||||||
if campos_alterados:
|
apuracao.indicadores_ocultos = [c for c in apuracao.indicadores_ocultos if c != chave]
|
||||||
apuracao.save(update_fields=campos_alterados)
|
campos_alterados.append("indicadores_ocultos")
|
||||||
|
if campos_alterados:
|
||||||
|
apuracao.save(update_fields=campos_alterados)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Relatório Contábil - De Paula (Diretoria) ------------------------------
|
||||||
|
# Mesmas views da original, só trocando permissão, tabelas e serializers
|
||||||
|
# (ver docstring de `ContabilApuracaoViewSet`). A permissão é própria
|
||||||
|
# (`diretoria` > `dashboard-contabil-de-paula`): ter acesso ao Relatório
|
||||||
|
# Contábil não dá acesso a nenhum destes endpoints, e cada queryset só
|
||||||
|
# enxerga as tabelas da De Paula. Ver "Relatório Contábil - De Paula" no
|
||||||
|
# CLAUDE.md do pacote `dashboard_contabil`.
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilApuracaoDePaulaViewSet(ContabilApuracaoViewSet):
|
||||||
|
queryset = ContabilApuracaoDePaula.objects.all()
|
||||||
|
permissao_app = _CONTABIL_DE_PAULA_PERMISSAO
|
||||||
|
serializer_lista = ContabilApuracaoListDePaulaSerializer
|
||||||
|
serializer_detalhe = ContabilApuracaoDetailDePaulaSerializer
|
||||||
|
serializer_observacao = ContabilObservacaoDePaulaSerializer
|
||||||
|
prefixo_api = "/api/contabil-apuracoes-de-paula"
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilContaDePaulaViewSet(ContabilContaViewSet):
|
||||||
|
queryset = ContabilContaDePaula.objects.all()
|
||||||
|
serializer_class = ContabilContaDePaulaSerializer
|
||||||
|
permissao_app = _CONTABIL_DE_PAULA_PERMISSAO
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilLinhaDreDePaulaViewSet(ContabilLinhaDreViewSet):
|
||||||
|
queryset = ContabilLinhaDreDePaula.objects.all()
|
||||||
|
serializer_class = ContabilLinhaDreDePaulaSerializer
|
||||||
|
permissao_app = _CONTABIL_DE_PAULA_PERMISSAO
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilLinhaAnaliseVerticalDePaulaViewSet(ContabilLinhaAnaliseVerticalViewSet):
|
||||||
|
queryset = ContabilLinhaAnaliseVerticalDePaula.objects.all()
|
||||||
|
serializer_class = ContabilLinhaAnaliseVerticalDePaulaSerializer
|
||||||
|
permissao_app = _CONTABIL_DE_PAULA_PERMISSAO
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilObservacaoDePaulaViewSet(ContabilObservacaoViewSet):
|
||||||
|
queryset = ContabilObservacaoDePaula.objects.select_related("criado_por", "encerrada_por", "apuracao_origem")
|
||||||
|
serializer_class = ContabilObservacaoDePaulaSerializer
|
||||||
|
serializer_criacao = ContabilObservacaoCreateDePaulaSerializer
|
||||||
|
serializer_apuracao = ContabilObservacaoApuracaoDePaulaSerializer
|
||||||
|
permissao_app = _CONTABIL_DE_PAULA_PERMISSAO
|
||||||
|
|
||||||
|
|
||||||
|
class ContabilAchadoDePaulaViewSet(ContabilAchadoViewSet):
|
||||||
|
queryset = ContabilAchadoDePaula.objects.all()
|
||||||
|
serializer_leitura = ContabilAchadoDePaulaSerializer
|
||||||
|
permissao_app = _CONTABIL_DE_PAULA_PERMISSAO
|
||||||
|
|
||||||
|
|
||||||
# --- Conciliação de Fornecedores (Utilitários) -----------------------------
|
# --- Conciliação de Fornecedores (Utilitários) -----------------------------
|
||||||
|
|||||||
1
prd.md
1
prd.md
@ -35,6 +35,7 @@ Colaboradores do escritório, autenticados por login/senha (sessão Django). O a
|
|||||||
### Relatórios (perfis com acesso liberado)
|
### Relatórios (perfis com acesso liberado)
|
||||||
- **Não Conformidades** (Relatórios > Qualidade) — gestão contínua das ocorrências e ações do Sistema de Gestão da Qualidade (Sigsistem, ISO 9001), com reabertura automática quando algo muda desde o último tratamento e um dashboard de motivos de abertura e maior incidência. Acesso restrito por padrão.
|
- **Não Conformidades** (Relatórios > Qualidade) — gestão contínua das ocorrências e ações do Sistema de Gestão da Qualidade (Sigsistem, ISO 9001), com reabertura automática quando algo muda desde o último tratamento e um dashboard de motivos de abertura e maior incidência. Acesso restrito por padrão.
|
||||||
- **Relatório Contábil** (Relatórios > Contabilidade) — o contador anexa o PDF de Balancete + DRE e a ferramenta roda um motor de regras de auditoria, com revisão das observações antes de gerar um relatório pronto para o cliente. Acesso restrito por padrão. Fora de escopo hoje: consolidar várias empresas/competências ao mesmo tempo.
|
- **Relatório Contábil** (Relatórios > Contabilidade) — o contador anexa o PDF de Balancete + DRE e a ferramenta roda um motor de regras de auditoria, com revisão das observações antes de gerar um relatório pronto para o cliente. Acesso restrito por padrão. Fora de escopo hoje: consolidar várias empresas/competências ao mesmo tempo.
|
||||||
|
- **Relatório Contábil - De Paula** (Diretoria) — a mesma ferramenta, para as empresas do próprio escritório. Mesmas regras e indicadores; análises, histórico e permissão totalmente separados da versão de clientes, de propósito: quem tem acesso só ao Relatório Contábil não acessa esta. Nasce sem acesso para nenhum perfil, liberada manualmente.
|
||||||
|
|
||||||
### Recursos visuais (todo usuário, sem item de menu)
|
### Recursos visuais (todo usuário, sem item de menu)
|
||||||
- **Temas Sazonais** — troca a marca P.I.D. por uma variante temática durante uma janela de datas (hoje só o Halloween), com um toggle no menu da conta para o usuário desligar por preferência ou acessibilidade. Fora da janela, tudo volta sozinho.
|
- **Temas Sazonais** — troca a marca P.I.D. por uma variante temática durante uma janela de datas (hoje só o Halloween), com um toggle no menu da conta para o usuário desligar por preferência ou acessibilidade. Fora da janela, tudo volta sozinho.
|
||||||
|
|||||||
@ -1,3 +1,26 @@
|
|||||||
|
// Este arquivo serve duas páginas: o Relatório Contábil (dashboard-contabil.html)
|
||||||
|
// e o Relatório Contábil - De Paula (dashboard-contabil-de-paula.html, seção
|
||||||
|
// Diretoria). A variante define window.PID_DC_CONFIG num <script> inline antes
|
||||||
|
// deste arquivo, com a permissão e os prefixos de endpoint dela; na original
|
||||||
|
// window.PID_DC_CONFIG não existe e os defaults abaixo valem. Toda rota de
|
||||||
|
// apuração/conta/linha/observação/achado passa por aqui, nunca escrita à mão:
|
||||||
|
// uma rota literal faria a variante gravar nas tabelas da outra aplicação. O
|
||||||
|
// cadastro de indicadores (/contabil-indicadores-definicoes/) é o mesmo das
|
||||||
|
// duas, por decisão do usuário.
|
||||||
|
const PID_DC_CONFIG = Object.assign(
|
||||||
|
{
|
||||||
|
moduleKey: "relatorios",
|
||||||
|
appKey: "dashboard-contabil",
|
||||||
|
endpointApuracoes: "/contabil-apuracoes",
|
||||||
|
endpointContas: "/contabil-contas",
|
||||||
|
endpointLinhasDre: "/contabil-linhas-dre",
|
||||||
|
endpointLinhasAnaliseVertical: "/contabil-linhas-analise-vertical",
|
||||||
|
endpointObservacoes: "/contabil-observacoes",
|
||||||
|
endpointAchados: "/contabil-achados",
|
||||||
|
},
|
||||||
|
window.PID_DC_CONFIG || {}
|
||||||
|
);
|
||||||
|
|
||||||
const PID_DC_STATUS_LABELS = { revisao: "Em revisão", concluida: "Concluída" };
|
const PID_DC_STATUS_LABELS = { revisao: "Em revisão", concluida: "Concluída" };
|
||||||
const PID_DC_SEVERIDADE_LABELS = { alta: "Alta", media: "Média", baixa: "Baixa" };
|
const PID_DC_SEVERIDADE_LABELS = { alta: "Alta", media: "Média", baixa: "Baixa" };
|
||||||
const PID_DC_ACHADO_STATUS_LABELS = { pendente: "Pendente", tratado: "Tratado", ignorado: "Ignorado" };
|
const PID_DC_ACHADO_STATUS_LABELS = { pendente: "Pendente", tratado: "Tratado", ignorado: "Ignorado" };
|
||||||
@ -315,27 +338,27 @@ document.addEventListener("click", pidDcEscondeDica, true);
|
|||||||
window.addEventListener("scroll", pidDcEscondeDica, true);
|
window.addEventListener("scroll", pidDcEscondeDica, true);
|
||||||
|
|
||||||
async function pidFetchApuracoesContabil() {
|
async function pidFetchApuracoesContabil() {
|
||||||
return pidApiRequest("/contabil-apuracoes/");
|
return pidApiRequest(`${PID_DC_CONFIG.endpointApuracoes}/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pidFetchApuracaoContabil(id) {
|
async function pidFetchApuracaoContabil(id) {
|
||||||
return pidApiRequest(`/contabil-apuracoes/${id}/`);
|
return pidApiRequest(`${PID_DC_CONFIG.endpointApuracoes}/${id}/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pidCriarApuracaoContabil(formData) {
|
async function pidCriarApuracaoContabil(formData) {
|
||||||
return pidApiRequest("/contabil-apuracoes/", { method: "POST", body: formData });
|
return pidApiRequest(`${PID_DC_CONFIG.endpointApuracoes}/`, { method: "POST", body: formData });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pidExcluirApuracaoContabil(id) {
|
async function pidExcluirApuracaoContabil(id) {
|
||||||
return pidApiRequest(`/contabil-apuracoes/${id}/`, { method: "DELETE" });
|
return pidApiRequest(`${PID_DC_CONFIG.endpointApuracoes}/${id}/`, { method: "DELETE" });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pidConcluirApuracaoContabil(id) {
|
async function pidConcluirApuracaoContabil(id) {
|
||||||
return pidApiRequest(`/contabil-apuracoes/${id}/concluir/`, { method: "POST" });
|
return pidApiRequest(`${PID_DC_CONFIG.endpointApuracoes}/${id}/concluir/`, { method: "POST" });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pidReprocessarApuracaoContabil(id, formData) {
|
async function pidReprocessarApuracaoContabil(id, formData) {
|
||||||
return pidApiRequest(`/contabil-apuracoes/${id}/reprocessar/`, { method: "POST", body: formData });
|
return pidApiRequest(`${PID_DC_CONFIG.endpointApuracoes}/${id}/reprocessar/`, { method: "POST", body: formData });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -349,7 +372,7 @@ async function pidReprocessarApuracaoContabil(id, formData) {
|
|||||||
* navegação de verdade pra URL da API, isso deixa de ser um problema.
|
* navegação de verdade pra URL da API, isso deixa de ser um problema.
|
||||||
*/
|
*/
|
||||||
function pidGerarDashboardContabil(id) {
|
function pidGerarDashboardContabil(id) {
|
||||||
window.open(`/api/contabil-apuracoes/${id}/dashboard/`, "_blank");
|
window.open(`/api${PID_DC_CONFIG.endpointApuracoes}/${id}/dashboard/`, "_blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Observações (histórico por empresa+conta, atravessa competências) ----
|
// ---- Observações (histórico por empresa+conta, atravessa competências) ----
|
||||||
@ -361,14 +384,14 @@ function pidGerarDashboardContabil(id) {
|
|||||||
// `/contabil-observacoes/`, nunca contra a linha.
|
// `/contabil-observacoes/`, nunca contra a linha.
|
||||||
|
|
||||||
async function pidBuscarObservacoesContabil(apuracaoId) {
|
async function pidBuscarObservacoesContabil(apuracaoId) {
|
||||||
return pidApiRequest(`/contabil-apuracoes/${apuracaoId}/observacoes/`);
|
return pidApiRequest(`${PID_DC_CONFIG.endpointApuracoes}/${apuracaoId}/observacoes/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// `alvoId` é o id da conta/linha DESTA apuração — o servidor deriva empresa,
|
// `alvoId` é o id da conta/linha DESTA apuração — o servidor deriva empresa,
|
||||||
// competência, chave natural e rótulo a partir dele (ver
|
// competência, chave natural e rótulo a partir dele (ver
|
||||||
// ContabilObservacaoViewSet.create).
|
// ContabilObservacaoViewSet.create).
|
||||||
async function pidCriarObservacaoContabil(apuracaoId, alvoTipo, alvoId, texto, mostrarAoCliente) {
|
async function pidCriarObservacaoContabil(apuracaoId, alvoTipo, alvoId, texto, mostrarAoCliente) {
|
||||||
return pidApiRequest("/contabil-observacoes/", {
|
return pidApiRequest(`${PID_DC_CONFIG.endpointObservacoes}/`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: {
|
body: {
|
||||||
apuracao: apuracaoId,
|
apuracao: apuracaoId,
|
||||||
@ -384,17 +407,17 @@ async function pidCriarObservacaoContabil(apuracaoId, alvoTipo, alvoId, texto, m
|
|||||||
// enquanto a observação ainda for da competência aberta; a visibilidade ao
|
// enquanto a observação ainda for da competência aberta; a visibilidade ao
|
||||||
// cliente é sempre alternável, inclusive numa observação histórica.
|
// cliente é sempre alternável, inclusive numa observação histórica.
|
||||||
async function pidAtualizarObservacaoContabil(id, payload) {
|
async function pidAtualizarObservacaoContabil(id, payload) {
|
||||||
return pidApiRequest(`/contabil-observacoes/${id}/`, { method: "PATCH", body: payload });
|
return pidApiRequest(`${PID_DC_CONFIG.endpointObservacoes}/${id}/`, { method: "PATCH", body: payload });
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Ocultar das próximas execuções": continua visível nesta competência e
|
// "Ocultar das próximas execuções": continua visível nesta competência e
|
||||||
// some a partir da seguinte. `reativar` desfaz.
|
// some a partir da seguinte. `reativar` desfaz.
|
||||||
async function pidEncerrarObservacaoContabil(id, apuracaoId) {
|
async function pidEncerrarObservacaoContabil(id, apuracaoId) {
|
||||||
return pidApiRequest(`/contabil-observacoes/${id}/encerrar/`, { method: "POST", body: { apuracao: apuracaoId } });
|
return pidApiRequest(`${PID_DC_CONFIG.endpointObservacoes}/${id}/encerrar/`, { method: "POST", body: { apuracao: apuracaoId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pidReativarObservacaoContabil(id, apuracaoId) {
|
async function pidReativarObservacaoContabil(id, apuracaoId) {
|
||||||
return pidApiRequest(`/contabil-observacoes/${id}/reativar/`, { method: "POST", body: { apuracao: apuracaoId } });
|
return pidApiRequest(`${PID_DC_CONFIG.endpointObservacoes}/${id}/reativar/`, { method: "POST", body: { apuracao: apuracaoId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Excluir de verdade — botão voltou a existir na tela (rodada 145), restrito
|
// Excluir de verdade — botão voltou a existir na tela (rodada 145), restrito
|
||||||
@ -402,7 +425,7 @@ async function pidReativarObservacaoContabil(id, apuracaoId) {
|
|||||||
// revisão (mesmas duas regras checadas de novo no servidor, ver
|
// revisão (mesmas duas regras checadas de novo no servidor, ver
|
||||||
// ContabilObservacaoViewSet.perform_destroy()).
|
// ContabilObservacaoViewSet.perform_destroy()).
|
||||||
async function pidExcluirObservacaoContabil(id) {
|
async function pidExcluirObservacaoContabil(id) {
|
||||||
return pidApiRequest(`/contabil-observacoes/${id}/`, { method: "DELETE" });
|
return pidApiRequest(`${PID_DC_CONFIG.endpointObservacoes}/${id}/`, { method: "DELETE" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// `alterada_reprocessamento` nunca é tocado por aqui (pedido explícito do
|
// `alterada_reprocessamento` nunca é tocado por aqui (pedido explícito do
|
||||||
@ -412,19 +435,19 @@ async function pidExcluirObservacaoContabil(id) {
|
|||||||
// um reprocessamento novo (sem mudança daquela conta/linha desta vez) limpa
|
// um reprocessamento novo (sem mudança daquela conta/linha desta vez) limpa
|
||||||
// o campo de verdade, em `_contabil_sincroniza_*` (views.py).
|
// o campo de verdade, em `_contabil_sincroniza_*` (views.py).
|
||||||
async function pidAtualizarValidadoContaContabil(id, validado) {
|
async function pidAtualizarValidadoContaContabil(id, validado) {
|
||||||
return pidApiRequest(`/contabil-contas/${id}/`, { method: "PATCH", body: { validado } });
|
return pidApiRequest(`${PID_DC_CONFIG.endpointContas}/${id}/`, { method: "PATCH", body: { validado } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pidAtualizarValidadoLinhaDreContabil(id, validado) {
|
async function pidAtualizarValidadoLinhaDreContabil(id, validado) {
|
||||||
return pidApiRequest(`/contabil-linhas-dre/${id}/`, { method: "PATCH", body: { validado } });
|
return pidApiRequest(`${PID_DC_CONFIG.endpointLinhasDre}/${id}/`, { method: "PATCH", body: { validado } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pidAtualizarValidadoLinhaAnaliseVerticalContabil(id, validado) {
|
async function pidAtualizarValidadoLinhaAnaliseVerticalContabil(id, validado) {
|
||||||
return pidApiRequest(`/contabil-linhas-analise-vertical/${id}/`, { method: "PATCH", body: { validado } });
|
return pidApiRequest(`${PID_DC_CONFIG.endpointLinhasAnaliseVertical}/${id}/`, { method: "PATCH", body: { validado } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pidAtualizarAchadoContabil(id, status, observacaoContador) {
|
async function pidAtualizarAchadoContabil(id, status, observacaoContador) {
|
||||||
return pidApiRequest(`/contabil-achados/${id}/`, {
|
return pidApiRequest(`${PID_DC_CONFIG.endpointAchados}/${id}/`, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: { status, observacao_contador: observacaoContador },
|
body: { status, observacao_contador: observacaoContador },
|
||||||
});
|
});
|
||||||
@ -434,24 +457,24 @@ async function pidAtualizarAchadoContabil(id, status, observacaoContador) {
|
|||||||
// justificativa escrita (o texto vai na observação da linha), desvalidar
|
// justificativa escrita (o texto vai na observação da linha), desvalidar
|
||||||
// volta a "pendente" — ver ContabilAchadoViewSet.validar().
|
// volta a "pendente" — ver ContabilAchadoViewSet.validar().
|
||||||
async function pidValidarAchadoContabil(id, validado) {
|
async function pidValidarAchadoContabil(id, validado) {
|
||||||
return pidApiRequest(`/contabil-achados/${id}/validar/`, { method: "POST", body: { validado } });
|
return pidApiRequest(`${PID_DC_CONFIG.endpointAchados}/${id}/validar/`, { method: "POST", body: { validado } });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Aba "Dashboard" (pré-visualização do relatório "Gerar Dashboard") ----
|
// ---- Aba "Dashboard" (pré-visualização do relatório "Gerar Dashboard") ----
|
||||||
|
|
||||||
async function pidBuscarIndicadoresContabil(id) {
|
async function pidBuscarIndicadoresContabil(id) {
|
||||||
return pidApiRequest(`/contabil-apuracoes/${id}/indicadores/`);
|
return pidApiRequest(`${PID_DC_CONFIG.endpointApuracoes}/${id}/indicadores/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pidAtualizarIndicadoresOcultosContabil(id, indicadoresOcultos) {
|
async function pidAtualizarIndicadoresOcultosContabil(id, indicadoresOcultos) {
|
||||||
return pidApiRequest(`/contabil-apuracoes/${id}/indicadores-ocultos/`, {
|
return pidApiRequest(`${PID_DC_CONFIG.endpointApuracoes}/${id}/indicadores-ocultos/`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: { indicadores_ocultos: indicadoresOcultos },
|
body: { indicadores_ocultos: indicadoresOcultos },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pidAlternarOcultoAchadoContabil(id, oculto) {
|
async function pidAlternarOcultoAchadoContabil(id, oculto) {
|
||||||
return pidApiRequest(`/contabil-achados/${id}/alternar-oculto/`, {
|
return pidApiRequest(`${PID_DC_CONFIG.endpointAchados}/${id}/alternar-oculto/`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: { oculto_no_relatorio: oculto },
|
body: { oculto_no_relatorio: oculto },
|
||||||
});
|
});
|
||||||
@ -478,21 +501,21 @@ async function pidBuscarDefinicoesIndicadoresContabil() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function pidPreVisualizarIndicadorContabil(apuracaoId, payload) {
|
async function pidPreVisualizarIndicadorContabil(apuracaoId, payload) {
|
||||||
return pidApiRequest(`/contabil-apuracoes/${apuracaoId}/pre-visualizar-indicador/`, {
|
return pidApiRequest(`${PID_DC_CONFIG.endpointApuracoes}/${apuracaoId}/pre-visualizar-indicador/`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: payload,
|
body: payload,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pidAtualizarIndicadoresSelecionadosContabil(id, indicadoresSelecionados) {
|
async function pidAtualizarIndicadoresSelecionadosContabil(id, indicadoresSelecionados) {
|
||||||
return pidApiRequest(`/contabil-apuracoes/${id}/indicadores-selecionados/`, {
|
return pidApiRequest(`${PID_DC_CONFIG.endpointApuracoes}/${id}/indicadores-selecionados/`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: { indicadores_selecionados: indicadoresSelecionados },
|
body: { indicadores_selecionados: indicadoresSelecionados },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pidAtualizarResumoFechamentoContabil(id, resumoFechamento) {
|
async function pidAtualizarResumoFechamentoContabil(id, resumoFechamento) {
|
||||||
return pidApiRequest(`/contabil-apuracoes/${id}/resumo-fechamento/`, {
|
return pidApiRequest(`${PID_DC_CONFIG.endpointApuracoes}/${id}/resumo-fechamento/`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: { resumo_fechamento: resumoFechamento },
|
body: { resumo_fechamento: resumoFechamento },
|
||||||
});
|
});
|
||||||
@ -505,8 +528,8 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const me = await pidGetMe();
|
const me = await pidGetMe();
|
||||||
if (!me) return;
|
if (!me) return;
|
||||||
|
|
||||||
const relatoriosPerms = (me.permissoes_efetivas && me.permissoes_efetivas["relatorios"]) || {};
|
const moduloPerms = (me.permissoes_efetivas && me.permissoes_efetivas[PID_DC_CONFIG.moduleKey]) || {};
|
||||||
const canUse = !!(relatoriosPerms.enabled && relatoriosPerms.apps && relatoriosPerms.apps["dashboard-contabil"]);
|
const canUse = !!(moduloPerms.enabled && moduloPerms.apps && moduloPerms.apps[PID_DC_CONFIG.appKey]);
|
||||||
|
|
||||||
const noAccessEl = document.getElementById("dc-no-access");
|
const noAccessEl = document.getElementById("dc-no-access");
|
||||||
const contentEl = document.getElementById("dc-content");
|
const contentEl = document.getElementById("dc-content");
|
||||||
|
|||||||
@ -57,10 +57,27 @@ function pidPerfilFromApi(p) {
|
|||||||
nome: p.nome,
|
nome: p.nome,
|
||||||
ativo: p.ativo,
|
ativo: p.ativo,
|
||||||
gerenciaPermissoes: p.gerencia_permissoes,
|
gerenciaPermissoes: p.gerencia_permissoes,
|
||||||
permissions: p.permissoes,
|
permissions: pidCompletaPermissoes(p.permissoes),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Completa o JSON salvo de um perfil com todo módulo/app do catálogo que
|
||||||
|
* ainda não existe nele, desmarcado. Um módulo novo no catálogo (ex.:
|
||||||
|
* "Diretoria") não está no JSON dos perfis já gravados, e como o seed não
|
||||||
|
* roda em produção ninguém o acrescenta — sem isto, abrir um perfil quebra a
|
||||||
|
* tela ao ler `permissions[modulo].enabled`. Desmarcado é o mesmo que o
|
||||||
|
* servidor já assume para chave ausente (`permissoes_efetivas()`), então
|
||||||
|
* nada muda de acesso; o perfil só passa a gravar a chave quando for salvo. */
|
||||||
|
function pidCompletaPermissoes(permissoes) {
|
||||||
|
const completo = JSON.parse(JSON.stringify(permissoes || {}));
|
||||||
|
PID_MODULES.forEach((m) => {
|
||||||
|
const atual = completo[m.key] || {};
|
||||||
|
const apps = Object.assign(pidAppsFor(m.key, false), atual.apps || {});
|
||||||
|
completo[m.key] = Object.assign({}, atual, { enabled: !!atual.enabled, apps });
|
||||||
|
});
|
||||||
|
return completo;
|
||||||
|
}
|
||||||
|
|
||||||
function pidPerfilToApi(p) {
|
function pidPerfilToApi(p) {
|
||||||
return {
|
return {
|
||||||
nome: p.nome,
|
nome: p.nome,
|
||||||
|
|||||||
@ -143,6 +143,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li data-section="relatorios-gerenciais" class="nav-group">
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
@ -144,6 +144,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li data-section="relatorios-gerenciais" class="nav-group">
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
@ -144,6 +144,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li data-section="relatorios-gerenciais" class="nav-group">
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
@ -143,6 +143,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li data-section="relatorios-gerenciais" class="nav-group">
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
949
templates/dashboard-contabil-de-paula.html
Normal file
949
templates/dashboard-contabil-de-paula.html
Normal file
@ -0,0 +1,949 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pt-BR" data-theme="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>P.I.D. — Relatório Contábil - De Paula</title>
|
||||||
|
<link rel="icon" type="image/svg+xml" href="{% static 'img/pid-icone-escuro.svg' %}" />
|
||||||
|
<link rel="stylesheet" href="{% static 'css/tokens.css' %}" />
|
||||||
|
<link rel="stylesheet" href="{% static 'css/base.css' %}" />
|
||||||
|
<link rel="stylesheet" href="{% static 'css/layout.css' %}" />
|
||||||
|
<link rel="stylesheet" href="{% static 'css/components.css' %}" />
|
||||||
|
<link rel="stylesheet" href="{% static 'css/perfis-acesso.css' %}" />
|
||||||
|
<link rel="stylesheet" href="{% static 'css/dashboard-contabil.css' %}" />
|
||||||
|
<script src="{% static 'js/theme.js' %}" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app-shell" id="app-shell">
|
||||||
|
<aside class="sidebar">
|
||||||
|
<a class="sidebar__brand" href="portal.html" id="sidebar-brand-link" aria-label="Ir para a tela Principal">
|
||||||
|
<svg class="sidebar__logo sidebar__logo--full" viewBox="0 0 300 80" aria-hidden="true" focusable="false">
|
||||||
|
<g transform="translate(8,8) scale(1)">
|
||||||
|
<path d="M10 4 h34 l14 14 v40 a2 2 0 0 1 -2 2 H8 a2 2 0 0 1 -2 -2 V6 a2 2 0 0 1 2 -2 z" fill="#7B5BA8"></path>
|
||||||
|
<rect x="22" y="4" width="20" height="16" fill="#C6A24A"></rect>
|
||||||
|
<rect x="34" y="7" width="5" height="10" fill="#7B5BA8"></rect>
|
||||||
|
<rect x="12" y="26" width="40" height="30" rx="2" fill="#241C33"></rect>
|
||||||
|
<g class="pid-icon-eye">
|
||||||
|
<circle cx="24" cy="38" r="5" fill="#F2EDE3"></circle>
|
||||||
|
<circle cx="26" cy="36.2" r="1.6" fill="#241C33"></circle>
|
||||||
|
</g>
|
||||||
|
<g class="pid-icon-eye">
|
||||||
|
<circle cx="40" cy="38" r="5" fill="#F2EDE3"></circle>
|
||||||
|
<circle cx="42" cy="36.2" r="1.6" fill="#241C33"></circle>
|
||||||
|
</g>
|
||||||
|
<path d="M25 47 q7 6 14 0" stroke="#C6A24A" stroke-width="3" fill="none" stroke-linecap="round"></path>
|
||||||
|
</g>
|
||||||
|
<text x="86" y="42" font-family="'Space Grotesk',Helvetica,Arial,sans-serif" font-size="34" font-weight="700" letter-spacing="1.4" fill="#F2EDE3">P.I.D.</text>
|
||||||
|
<text x="88" y="61" font-family="'DM Mono',ui-monospace,monospace" font-size="9.5" letter-spacing="2.6" fill="#C6A24A">PORTAL INTERNO DA DE PAULA</text>
|
||||||
|
</svg>
|
||||||
|
<svg class="sidebar__logo sidebar__logo--icon" viewBox="0 0 64 64" aria-hidden="true" focusable="false">
|
||||||
|
<path d="M10 4 h34 l14 14 v40 a2 2 0 0 1 -2 2 H8 a2 2 0 0 1 -2 -2 V6 a2 2 0 0 1 2 -2 z" fill="#7B5BA8"></path>
|
||||||
|
<rect x="22" y="4" width="20" height="16" fill="#C6A24A"></rect>
|
||||||
|
<rect x="34" y="7" width="5" height="10" fill="#7B5BA8"></rect>
|
||||||
|
<rect x="12" y="26" width="40" height="30" rx="2" fill="#241C33"></rect>
|
||||||
|
<g class="pid-icon-eye">
|
||||||
|
<circle cx="24" cy="38" r="5" fill="#F2EDE3"></circle>
|
||||||
|
<circle cx="26" cy="36.2" r="1.6" fill="#241C33"></circle>
|
||||||
|
</g>
|
||||||
|
<g class="pid-icon-eye">
|
||||||
|
<circle cx="40" cy="38" r="5" fill="#F2EDE3"></circle>
|
||||||
|
<circle cx="42" cy="36.2" r="1.6" fill="#241C33"></circle>
|
||||||
|
</g>
|
||||||
|
<path d="M25 47 q7 6 14 0" stroke="#C6A24A" stroke-width="3" fill="none" stroke-linecap="round"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<nav class="sidebar__nav">
|
||||||
|
<ul class="nav-list">
|
||||||
|
<li data-section="principal">
|
||||||
|
<a href="portal.html" class="nav-item">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 11l9-7 9 7v9a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Principal</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li data-section="ramais">
|
||||||
|
<a href="ramais.html" class="nav-item">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4c0 9 7 16 16 16l3-4-6-3-2 2c-3-1.5-5-3.5-6-6l2-2-3-6z" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Ramais</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li data-section="calendario">
|
||||||
|
<a href="#" class="nav-item" id="calendario-depaula-btn">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 9h18M8 3v4M16 3v4" stroke-linecap="round"/></svg>
|
||||||
|
<span class="nav-item__label">Calendário De Paula</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li data-section="calendario-individual">
|
||||||
|
<a href="calendario-individual.html" class="nav-item">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 9h18M8 3v4M16 3v4" stroke-linecap="round"/><path d="M9 15l2 2 4-4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Calendário Individual</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-section="portais" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
|
||||||
|
<span class="nav-item__label">Portais</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="portal-do-cliente"><a href="#" class="nav-subitem">Portal do Cliente</a></li>
|
||||||
|
<li data-app="portal-fiscal"><a href="#" class="nav-subitem">Portal Fiscal</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-section="geradoc" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 3h9l3 3v15a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/><path d="M14 3v4h4" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Geradoc</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="simulacao-custo-contratacao"><a href="custo-contratacao.html" class="nav-subitem">Simulação de Custo de Contratação</a></li>
|
||||||
|
<li data-app="indicador-desempenho"><a href="indicador-desempenho.html" class="nav-subitem">Indicador de Desempenho</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-section="relatorios" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 20V10M12 20V4M20 20v-7" stroke-linecap="round"/></svg>
|
||||||
|
<span class="nav-item__label">Relatórios</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="indicadores"><a href="#" class="nav-subitem">Indicadores</a></li>
|
||||||
|
<li class="nav-subgroup" data-subgroup="qualidade">
|
||||||
|
<button type="button" class="nav-subitem nav-subgroup__toggle" data-subgroup-toggle>
|
||||||
|
<span>Qualidade</span>
|
||||||
|
<svg class="nav-item__chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-subgroup__list">
|
||||||
|
<li data-app="nao-conformidades">
|
||||||
|
<a href="nao-conformidades.html" class="nav-subitem nav-subitem--tool">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 2h9l3 3v15a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1z"/><path d="M14 2v4h4" stroke-linejoin="round"/></svg>
|
||||||
|
Não Conformidades
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="nav-subgroup" data-subgroup="contabilidade">
|
||||||
|
<button type="button" class="nav-subitem nav-subgroup__toggle" data-subgroup-toggle>
|
||||||
|
<span>Contabilidade</span>
|
||||||
|
<svg class="nav-item__chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-subgroup__list">
|
||||||
|
<li data-app="dashboard-contabil">
|
||||||
|
<a href="dashboard-contabil.html" class="nav-subitem nav-subitem--tool">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 2h9l3 3v15a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1z"/><path d="M14 2v4h4" stroke-linejoin="round"/></svg>
|
||||||
|
Relatório Contábil
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem is-active">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Relatórios Gerenciais</span>
|
||||||
|
<span class="badge-restricted" data-restricted-badge hidden>Restrito</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="visao-diretoria"><a href="#" class="nav-subitem">Visão Diretoria</a></li>
|
||||||
|
<li data-app="visao-gerencial"><a href="#" class="nav-subitem">Visão Gerencial</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-section="utilitarios" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 7l3 3-8 8-4 1 1-4z" stroke-linejoin="round"/><path d="M12 9l3 3" stroke-linecap="round"/></svg>
|
||||||
|
<span class="nav-item__label">Utilitários</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="importacao-plano-saude"><a href="importacao-plano-saude.html" class="nav-subitem">Importação de Plano de Saúde</a></li>
|
||||||
|
<li data-app="importacao-plano-saude-de-paula"><a href="importacao-plano-saude-de-paula.html" class="nav-subitem">Importação de Plano de Saúde - De Paula</a></li>
|
||||||
|
<li data-app="conciliacao-fornecedores"><a href="conciliacao-fornecedores.html" class="nav-subitem">Conciliação de Fornecedores</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-section="integracoes" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="6" cy="12" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M8.6 10.6L15.4 7.4M8.6 13.4l6.8 3.2" stroke-linecap="round"/></svg>
|
||||||
|
<span class="nav-item__label">Integrações</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="questor"><a href="#" class="nav-subitem">Questor</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-section="auditorias" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l7 4v5c0 5-3.5 8-7 9-3.5-1-7-4-7-9V7z" stroke-linejoin="round"/><path d="M9 12l2 2 4-4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Auditorias</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li class="nav-subgroup" data-subgroup="consultoria-tributaria">
|
||||||
|
<button type="button" class="nav-subitem nav-subgroup__toggle" data-subgroup-toggle>
|
||||||
|
<span>Consultoria Tributária</span>
|
||||||
|
<svg class="nav-item__chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-subgroup__list">
|
||||||
|
<li data-app="controle-simples-nacional">
|
||||||
|
<a href="#" class="nav-subitem nav-subitem--tool">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 2h9l3 3v15a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1z"/><path d="M14 2v4h4" stroke-linejoin="round"/></svg>
|
||||||
|
Controle Simples Nacional
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="nav-subgroup" data-subgroup="fisco-contabil">
|
||||||
|
<button type="button" class="nav-subitem nav-subgroup__toggle" data-subgroup-toggle>
|
||||||
|
<span>Fisco/Contábil</span>
|
||||||
|
<svg class="nav-item__chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-subgroup__list">
|
||||||
|
<li data-app="auditoria-de-balancetes">
|
||||||
|
<a href="#" class="nav-subitem nav-subitem--tool">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 2h9l3 3v15a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1z"/><path d="M14 2v4h4" stroke-linejoin="round"/></svg>
|
||||||
|
Auditoria de Balancetes
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-section="links-ferramentas" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1"/><path d="M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1"/></svg>
|
||||||
|
<span class="nav-item__label">Links & Ferramentas</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="links-ferramentas-visualizar"><a href="links-ferramentas.html" class="nav-subitem">Links & Ferramentas</a></li>
|
||||||
|
<li data-app="acessos-gerais-visualizar"><a href="acessos-gerais.html" class="nav-subitem">Acessos Gerais</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-section="solicitacoes" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 10h.01M12 10h.01M16 10h.01M21 12a9 9 0 1 1-4.4-7.7L21 3l-1.2 4.5A8.96 8.96 0 0 1 21 12z"/></svg>
|
||||||
|
<span class="nav-item__label">Solicitações</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="controle-notificacoes"><a href="https://form.asana.com/?k=PcELrDLzN969CwpiRBLtFA&d=1207308016523218" target="_blank" rel="noopener noreferrer" class="nav-subitem">Controle de Notificações</a></li>
|
||||||
|
<li data-app="ordem-servico"><a href="#" class="nav-subitem">Ordem de Serviço</a></li>
|
||||||
|
<li data-app="pimi"><a href="https://form.asana.com/?k=XfBww25lGUyPVrInNcLyuQ&d=1207308016523218" target="_blank" rel="noopener noreferrer" class="nav-subitem">P.I.M.I.</a></li>
|
||||||
|
<li data-app="programa-indicacoes"><a href="https://form.asana.com/?k=y0mTeaRTOAoC0r0XP8ee0Q&d=1207308016523218" target="_blank" rel="noopener noreferrer" class="nav-subitem">Programa de Indicações</a></li>
|
||||||
|
<li data-app="pos-vendas"><a href="https://form.asana.com/?k=jRAztLip-_BknWPsGmEcZA&d=1207308016523218" target="_blank" rel="noopener noreferrer" class="nav-subitem">Pós-Vendas</a></li>
|
||||||
|
<li data-app="sugestao-inovacao"><a href="https://form.asana.com/?k=Zn8cLeT97R7FTUrsNB8Yaw&d=1207308016523218" target="_blank" rel="noopener noreferrer" class="nav-subitem">Sugestão de Inovação</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-section="administracao" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1-1.6 1.7 1.7 0 0 0-1.9.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.9 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1 1.7 1.7 0 0 0-.3-1.9l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.9.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.9-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.9V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Administração</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-section="perfis-acesso"><a href="perfis-acesso.html" class="nav-subitem">Perfis de Acesso</a></li>
|
||||||
|
<li data-section="usuarios"><a href="usuarios.html" class="nav-subitem">Usuários</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<header class="topbar">
|
||||||
|
<button type="button" class="icon-btn" id="sidebar-toggle" aria-label="Alternar menu">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16M4 12h16M4 18h16" stroke-linecap="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<h1 class="topbar__title">Relatório Contábil - De Paula</h1>
|
||||||
|
|
||||||
|
<div class="topbar__actions">
|
||||||
|
<button type="button" class="icon-btn" id="theme-toggle" aria-label="Ativar modo claro">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" stroke-linecap="round"/></svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="notifications">
|
||||||
|
<button type="button" class="icon-btn" id="notif-bell" aria-label="Notificações">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 8a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6" stroke-linejoin="round"/><path d="M9.5 20a2.5 2.5 0 0 0 5 0"/></svg>
|
||||||
|
<span class="notif-badge" id="notif-badge" hidden>0</span>
|
||||||
|
</button>
|
||||||
|
<div class="notif-dropdown" id="notif-dropdown" hidden>
|
||||||
|
<div class="notif-dropdown__header">
|
||||||
|
<span>Notificações</span>
|
||||||
|
<div class="notif-dropdown__header-actions">
|
||||||
|
<button type="button" id="notif-history-btn">Histórico</button>
|
||||||
|
<button type="button" id="notif-clear-all">Limpar tudo</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="notif-list" id="notif-list"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="account-menu">
|
||||||
|
<button type="button" class="persona-chip" id="persona-chip">
|
||||||
|
<span class="persona-chip__icon">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 20v-1a6 6 0 0 1 6-6h4a6 6 0 0 1 6 6v1" stroke-linecap="round" stroke-linejoin="round"/><circle cx="12" cy="7" r="4"/></svg>
|
||||||
|
</span>
|
||||||
|
<span id="persona-chip-label">Usuário</span>
|
||||||
|
</button>
|
||||||
|
<div class="account-dropdown" id="account-dropdown" hidden>
|
||||||
|
<div class="account-dropdown__header">
|
||||||
|
<p class="account-dropdown__name" id="account-name"></p>
|
||||||
|
<p class="account-dropdown__profile" id="account-profile"></p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="account-dropdown__item" id="account-manage-account-btn">Gerenciar Usuário</button>
|
||||||
|
<a href="perfis-acesso.html" class="account-dropdown__item" id="account-manage-link" hidden>Gerenciar Perfis de Acesso</a>
|
||||||
|
<div class="account-dropdown__section">
|
||||||
|
<p class="account-dropdown__section-title">Tema de cores</p>
|
||||||
|
<div class="theme-swatches" id="theme-swatches"></div>
|
||||||
|
</div>
|
||||||
|
<div class="account-dropdown__section">
|
||||||
|
<p class="account-dropdown__section-title">Temas sazonais</p>
|
||||||
|
<button type="button" class="seasonal-theme-toggle" id="seasonal-theme-toggle" aria-pressed="true">
|
||||||
|
<span class="seasonal-theme-toggle__dot"></span>
|
||||||
|
<span id="seasonal-theme-toggle-label">Ativo</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="account-dropdown__item account-dropdown__logout" data-logout>Sair</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="page-content page-content--wide">
|
||||||
|
<div id="dc-no-access" class="no-access" hidden>
|
||||||
|
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><rect x="4" y="10" width="16" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<h2>Sem acesso ao Relatório Contábil - De Paula</h2>
|
||||||
|
<p>Seu perfil de acesso não tem permissão para visualizar esta tela. Solicite à Inovação a liberação em Perfis de Acesso.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="dc-content">
|
||||||
|
<div id="dc-list-view">
|
||||||
|
<div class="dc-header">
|
||||||
|
<div>
|
||||||
|
<h2>Relatório Contábil - De Paula</h2>
|
||||||
|
<p class="dc-subtitle">Anexe o balancete + DRE em PDF (modelo Questor ou Contabit) de uma empresa do próprio escritório — a ferramenta extrai as contas, roda um conjunto de checagens automáticas e apresenta as observações para análise antes da conclusão. No modelo Contabit, o nome do arquivo precisa começar pelo código da empresa (ex.: "1512 - Balancete 082026.pdf").</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-solid" id="dc-new-btn">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14" stroke-linecap="round"/></svg>
|
||||||
|
Nova Análise
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pa-table-wrap dc-list-table-wrap">
|
||||||
|
<table class="pa-table" id="dc-list-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-sort="empresa">Empresa <span class="dc-sort-icon">↕</span></th>
|
||||||
|
<th data-sort="competencia">Competência <span class="dc-sort-icon">↕</span></th>
|
||||||
|
<th data-sort="observacoes">Observações pendentes <span class="dc-sort-icon">↕</span></th>
|
||||||
|
<th data-sort="status">Status <span class="dc-sort-icon">↕</span></th>
|
||||||
|
<th data-sort="criado_por">Criado por <span class="dc-sort-icon">↕</span></th>
|
||||||
|
<th data-sort="criado_em">Data <span class="dc-sort-icon">↕</span></th>
|
||||||
|
<th>
|
||||||
|
<button type="button" class="dc-list-reset-btn" id="dc-list-reset-btn" data-dc-dica="Limpar filtros e ordenação" aria-label="Limpar filtros e ordenação">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 20H8l-5.5-5.5a2 2 0 0 1 0-2.8l9-9a2 2 0 0 1 2.8 0l6.2 6.2a2 2 0 0 1 0 2.8L13 19" stroke-linejoin="round"/><path d="M8.5 12.5l6 6" stroke-linecap="round"/></svg>
|
||||||
|
</button>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="dc-list-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<p class="dc-empty" id="dc-list-empty" hidden>Nenhuma análise realizada ainda.</p>
|
||||||
|
<div class="dc-list-pagination" id="dc-list-pagination"></div>
|
||||||
|
<p class="dc-list-count" id="dc-list-count"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="dc-form-view" hidden>
|
||||||
|
<div class="dc-header">
|
||||||
|
<div>
|
||||||
|
<h2>Nova Análise</h2>
|
||||||
|
<p class="dc-subtitle">Anexe o PDF de Balancete + DRE (mesmo relatório hoje enviado ao cliente) — empresa, CNPJ e competência são identificados automaticamente a partir do próprio arquivo.</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-outline" id="dc-form-back-btn">Voltar</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dc-form">
|
||||||
|
<div class="modal-field">
|
||||||
|
<label>Balancete + DRE (PDF)</label>
|
||||||
|
<div class="dc-file-field">
|
||||||
|
<label class="btn-outline dc-file-field__btn" for="dc-form-arquivo">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 16V4M12 4l4 4M12 4L8 8" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 16v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
Selecionar arquivo
|
||||||
|
</label>
|
||||||
|
<input type="file" id="dc-form-arquivo" accept=".pdf" class="dc-file-field__input" />
|
||||||
|
<span class="dc-file-field__name" id="dc-form-arquivo-name">Nenhum arquivo selecionado</span>
|
||||||
|
<button type="button" class="icon-btn dc-file-field__remove" id="dc-form-arquivo-remove" aria-label="Remover arquivo" data-dc-dica="Remover arquivo" hidden>
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12" stroke-linecap="round"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="modal-error" id="dc-form-error"></p>
|
||||||
|
<div class="dc-progress" id="dc-form-progress" hidden>
|
||||||
|
<div class="dc-progress__bar"><div class="dc-progress__fill"></div></div>
|
||||||
|
<p class="dc-progress__label">Processando o arquivo, aguarde…</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button type="button" class="btn-outline" id="dc-form-cancel-btn">Cancelar</button>
|
||||||
|
<button type="button" class="btn-solid" id="dc-form-submit-btn">Processar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="dc-review-view" hidden>
|
||||||
|
<div class="dc-header">
|
||||||
|
<div>
|
||||||
|
<h2>Revisão <span class="dc-review-empresa" id="dc-review-empresa"></span></h2>
|
||||||
|
<p class="dc-subtitle" id="dc-review-subtitle"></p>
|
||||||
|
</div>
|
||||||
|
<div class="dc-header-actions">
|
||||||
|
<button type="button" class="btn-outline" id="dc-review-back-btn">Voltar</button>
|
||||||
|
<button type="button" class="btn-outline" id="dc-gerar-dashboard-btn">Gerar Relatório</button>
|
||||||
|
<button type="button" class="btn-solid" id="dc-concluir-btn">Concluir Análise</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="dc-concluida-aviso" id="dc-concluida-aviso" hidden>Esta análise já foi concluída — as observações não podem mais ser editadas.</p>
|
||||||
|
|
||||||
|
<div class="pa-tabs" id="dc-tabs">
|
||||||
|
<button type="button" class="pa-tab is-active" data-dc-tab="achados">Observações</button>
|
||||||
|
<button type="button" class="pa-tab" data-dc-tab="balancete">Balancete</button>
|
||||||
|
<button type="button" class="pa-tab" data-dc-tab="dre">DRE</button>
|
||||||
|
<button type="button" class="pa-tab" data-dc-tab="analise-vertical" id="dc-av-tab-btn" hidden>Análise Vertical</button>
|
||||||
|
<button type="button" class="pa-tab" data-dc-tab="dashboard">Dashboard</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pa-tab-panel" data-dc-panel="achados">
|
||||||
|
<div class="dc-achados-resumo">
|
||||||
|
<div class="dc-achados-resumo__chart">
|
||||||
|
<div class="dc-donut" id="dc-achados-donut">
|
||||||
|
<svg class="dc-donut-svg" id="dc-achados-donut-svg" viewBox="0 0 36 36"></svg>
|
||||||
|
<div class="dc-donut__hole">
|
||||||
|
<span class="dc-donut__total" id="dc-achados-donut-total">0</span>
|
||||||
|
<span class="dc-donut__label">observações</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="dc-donut-legend" id="dc-achados-donut-legend"></ul>
|
||||||
|
</div>
|
||||||
|
<div class="dc-achados-categorias" id="dc-achados-categorias"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dc-achados-toolbar">
|
||||||
|
<div class="dc-chip-row" id="dc-achados-severidade-chips">
|
||||||
|
<button type="button" class="dc-chip is-active" data-dc-severidade="todas">Todas</button>
|
||||||
|
<button type="button" class="dc-chip" data-dc-severidade="alta">Alta</button>
|
||||||
|
<button type="button" class="dc-chip" data-dc-severidade="media">Média</button>
|
||||||
|
<button type="button" class="dc-chip" data-dc-severidade="baixa">Baixa</button>
|
||||||
|
</div>
|
||||||
|
<div class="dc-chip-row" id="dc-achados-status-chips">
|
||||||
|
<button type="button" class="dc-chip is-active" data-dc-status="pendente">Pendentes</button>
|
||||||
|
<button type="button" class="dc-chip" data-dc-status="todos">Todos</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dc-regra-filtro-ativo" id="dc-regra-filtro-ativo" hidden>
|
||||||
|
Filtrando por categoria: <strong id="dc-regra-filtro-ativo-label"></strong>
|
||||||
|
<button type="button" class="dc-regra-filtro-limpar" id="dc-regra-filtro-limpar-btn">Limpar</button>
|
||||||
|
</div>
|
||||||
|
<div class="dc-achados-list" id="dc-achados-list"></div>
|
||||||
|
<p class="dc-empty" id="dc-achados-empty" hidden>Nenhuma observação para este filtro.</p>
|
||||||
|
<button type="button" class="dc-av-aviso" id="dc-av-aviso" hidden>
|
||||||
|
<span class="dc-av-aviso__texto" id="dc-av-aviso-texto"></span>
|
||||||
|
<span class="dc-av-aviso__link">Ver na Análise Vertical →</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pa-tab-panel" data-dc-panel="balancete" hidden>
|
||||||
|
<div class="pa-table-wrap">
|
||||||
|
<table class="pa-table dc-contas-table">
|
||||||
|
<thead id="dc-contas-thead">
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<span class="dc-th-linha dc-th-linha--inicio">
|
||||||
|
<button type="button" class="icon-btn dc-recolher-grupos-btn" data-dc-recolher-grupos="balancete" aria-label="Recolher os grupos do Balancete" data-dc-dica="Recolher os grupos (mostrar só os totais de cada nível)">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M5 12h14"/></svg>
|
||||||
|
</button>
|
||||||
|
Conta
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th>Classificação</th>
|
||||||
|
<th>Descrição</th>
|
||||||
|
<th>Saldo Anterior</th>
|
||||||
|
<th>Débito</th>
|
||||||
|
<th>Crédito</th>
|
||||||
|
<th>Saldo Atual</th>
|
||||||
|
<th>
|
||||||
|
<span class="dc-th-linha">Observação
|
||||||
|
<button type="button" class="icon-btn dc-reset-formatacao-btn" data-dc-reset-formatacao="balancete" aria-label="Restaurar formatação padrão do Balancete" data-dc-dica="Restaurar formatação padrão (tudo expandido, como ao abrir)">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 2.64-6.36"/><path d="M3 3v6h6"/></svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="dc-contas-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<section class="dc-obs-resumo">
|
||||||
|
<h3 class="dc-obs-resumo__titulo">Observações do Balancete <span class="dc-obs-resumo__count" id="dc-contas-obs-count">0</span></h3>
|
||||||
|
<div class="dc-chip-row dc-obs-filtros" data-dc-obs-filtros>
|
||||||
|
<button type="button" class="dc-chip is-active" data-dc-obs-filtro="todas">Todas</button>
|
||||||
|
<button type="button" class="dc-chip" data-dc-obs-filtro="cliente">Visíveis ao cliente</button>
|
||||||
|
<button type="button" class="dc-chip" data-dc-obs-filtro="internas">Internas</button>
|
||||||
|
</div>
|
||||||
|
<div class="dc-dash-obs-list" id="dc-contas-obs-list"></div>
|
||||||
|
<p class="dc-empty" id="dc-contas-obs-empty" hidden>Nenhuma observação registrada no Balancete ainda.</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pa-tab-panel" data-dc-panel="dre" hidden>
|
||||||
|
<div class="pa-table-wrap">
|
||||||
|
<table class="pa-table dc-dre-table">
|
||||||
|
<thead id="dc-dre-thead">
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<span class="dc-th-linha dc-th-linha--inicio">
|
||||||
|
<button type="button" class="icon-btn dc-recolher-grupos-btn" data-dc-recolher-grupos="dre" aria-label="Recolher os grupos da DRE" data-dc-dica="Recolher os grupos (mostrar só os totais de cada nível)">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M5 12h14"/></svg>
|
||||||
|
</button>
|
||||||
|
Descrição
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th>Valor</th>
|
||||||
|
<th>
|
||||||
|
<span class="dc-th-linha">Observação
|
||||||
|
<button type="button" class="icon-btn dc-reset-formatacao-btn" data-dc-reset-formatacao="dre" aria-label="Restaurar formatação padrão da DRE" data-dc-dica="Restaurar formatação padrão (tudo expandido, como ao abrir)">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 2.64-6.36"/><path d="M3 3v6h6"/></svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="dc-dre-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<section class="dc-obs-resumo">
|
||||||
|
<h3 class="dc-obs-resumo__titulo">Observações da DRE <span class="dc-obs-resumo__count" id="dc-dre-obs-count">0</span></h3>
|
||||||
|
<div class="dc-chip-row dc-obs-filtros" data-dc-obs-filtros>
|
||||||
|
<button type="button" class="dc-chip is-active" data-dc-obs-filtro="todas">Todas</button>
|
||||||
|
<button type="button" class="dc-chip" data-dc-obs-filtro="cliente">Visíveis ao cliente</button>
|
||||||
|
<button type="button" class="dc-chip" data-dc-obs-filtro="internas">Internas</button>
|
||||||
|
</div>
|
||||||
|
<div class="dc-dash-obs-list" id="dc-dre-obs-list"></div>
|
||||||
|
<p class="dc-empty" id="dc-dre-obs-empty" hidden>Nenhuma observação registrada na DRE ainda.</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pa-tab-panel" data-dc-panel="analise-vertical" hidden>
|
||||||
|
<div class="pa-table-wrap">
|
||||||
|
<table class="pa-table dc-dre-table dc-av-table">
|
||||||
|
<thead id="dc-av-thead">
|
||||||
|
<tr id="dc-av-head-row">
|
||||||
|
<th>Descrição</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="dc-av-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<section class="dc-obs-resumo">
|
||||||
|
<h3 class="dc-obs-resumo__titulo">Observações da Análise Vertical <span class="dc-obs-resumo__count" id="dc-av-obs-count">0</span></h3>
|
||||||
|
<div class="dc-chip-row dc-obs-filtros" data-dc-obs-filtros>
|
||||||
|
<button type="button" class="dc-chip is-active" data-dc-obs-filtro="todas">Todas</button>
|
||||||
|
<button type="button" class="dc-chip" data-dc-obs-filtro="cliente">Visíveis ao cliente</button>
|
||||||
|
<button type="button" class="dc-chip" data-dc-obs-filtro="internas">Internas</button>
|
||||||
|
</div>
|
||||||
|
<div class="dc-dash-obs-list" id="dc-av-obs-list"></div>
|
||||||
|
<p class="dc-empty" id="dc-av-obs-empty" hidden>Nenhuma observação registrada na Análise Vertical ainda.</p>
|
||||||
|
</section>
|
||||||
|
<section class="dc-obs-resumo dc-variacoes" id="dc-av-variacoes">
|
||||||
|
<h3 class="dc-obs-resumo__titulo">Variação atípica na DRE - Análise Vertical <span class="dc-obs-resumo__count" id="dc-av-variacoes-count">0</span></h3>
|
||||||
|
<p class="dc-variacoes__dica">Confira cada variação e clique em "Validar". A observação escrita aqui é a mesma da linha na tabela: fica no histórico da empresa e pode ser mostrada ao cliente.</p>
|
||||||
|
<div class="dc-variacoes__lista" id="dc-av-variacoes-list"></div>
|
||||||
|
<p class="dc-empty" id="dc-av-variacoes-empty" hidden>Nenhuma variação atípica nesta análise.</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pa-tab-panel" data-dc-panel="dashboard" hidden>
|
||||||
|
<p class="dc-dash-intro">
|
||||||
|
Pré-visualização do que vai para o relatório gerado pelo botão "Gerar Relatório". Use o botão de olho pra ocultar um card de indicador ou uma observação — o que estiver oculto aqui não aparece no relatório.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<section class="dc-dash-section">
|
||||||
|
<div class="dc-dash-section__header">
|
||||||
|
<h3 class="dc-dash-section__titulo">Resumo</h3>
|
||||||
|
<button type="button" class="btn-outline" id="dc-indicadores-gerenciar-btn">Gerenciar Indicadores</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dc-dash-resumo-fechamento">
|
||||||
|
<h4 class="dc-dash-resumo-fechamento__titulo">Resumo do Fechamento</h4>
|
||||||
|
<p class="dc-dash-resumo-fechamento__dica">Escreva aqui as considerações e análises deste fechamento — cole (Ctrl+V) ou arraste uma imagem pra incluí-la no texto. Aparece no relatório "Gerar Relatório", antes dos indicadores.</p>
|
||||||
|
<div id="dc-dash-resumo-fechamento-editor" class="dc-dash-richtext" contenteditable="true" data-placeholder="Ex.: A escrituração contábil da empresa foi atualizada até …/…/…. Considerações: o resultado do exercício foi…"></div>
|
||||||
|
<div class="dc-dash-resumo-fechamento__acoes">
|
||||||
|
<button type="button" class="btn-solid" id="dc-dash-resumo-fechamento-salvar-btn">Salvar</button>
|
||||||
|
<span class="dc-dash-resumo-fechamento__status" id="dc-dash-resumo-fechamento-status" hidden></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="dc-dash-section__dica" id="dc-dash-indicadores-dica">Clique num card pra ver a fórmula usada.</p>
|
||||||
|
<div id="dc-dash-indicadores"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="dc-dash-section">
|
||||||
|
<h3 class="dc-dash-section__titulo">Observações</h3>
|
||||||
|
<div class="dc-chip-row dc-obs-filtros" data-dc-obs-filtros>
|
||||||
|
<button type="button" class="dc-chip is-active" data-dc-obs-filtro="todas">Todas</button>
|
||||||
|
<button type="button" class="dc-chip" data-dc-obs-filtro="cliente">Visíveis ao cliente</button>
|
||||||
|
<button type="button" class="dc-chip" data-dc-obs-filtro="internas">Internas</button>
|
||||||
|
</div>
|
||||||
|
<div class="dc-dash-obs-list" id="dc-dash-observacoes-list"></div>
|
||||||
|
<p class="dc-empty" id="dc-dash-observacoes-empty" hidden>Nenhuma observação registrada ainda no Balancete, na DRE ou na auditoria.</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-overlay" id="dc-reprocessar-modal" hidden>
|
||||||
|
<div class="modal-card">
|
||||||
|
<h2 class="modal-card__title">Reprocessar análise</h2>
|
||||||
|
<p class="modal-card__subtitle">Anexe um PDF novo pra mesma empresa/competência (ex.: o arquivo original estava incompleto ou com erro). Contas/linhas sem mudança mantêm observação e validação como estavam; o que mudou volta pra "não validado" e ganha um alerta.</p>
|
||||||
|
<div class="modal-field">
|
||||||
|
<label>Balancete + DRE (PDF)</label>
|
||||||
|
<div class="dc-file-field">
|
||||||
|
<label class="btn-outline dc-file-field__btn" for="dc-reprocessar-arquivo">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 16V4M12 4l4 4M12 4L8 8" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 16v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
Selecionar arquivo
|
||||||
|
</label>
|
||||||
|
<input type="file" id="dc-reprocessar-arquivo" accept=".pdf" class="dc-file-field__input" />
|
||||||
|
<span class="dc-file-field__name" id="dc-reprocessar-arquivo-name">Nenhum arquivo selecionado</span>
|
||||||
|
<button type="button" class="icon-btn dc-file-field__remove" id="dc-reprocessar-arquivo-remove" aria-label="Remover arquivo" data-dc-dica="Remover arquivo" hidden>
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12" stroke-linecap="round"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<section class="dc-reprocessar-log" id="dc-reprocessar-log" hidden>
|
||||||
|
<h3 class="dc-reprocessar-log__titulo">Reprocessamentos anteriores <span class="dc-reprocessar-log__count" id="dc-reprocessar-log-count">0</span></h3>
|
||||||
|
<ul class="dc-reprocessar-log__lista" id="dc-reprocessar-log-lista"></ul>
|
||||||
|
</section>
|
||||||
|
<p class="modal-error" id="dc-reprocessar-error"></p>
|
||||||
|
<div class="dc-progress" id="dc-reprocessar-progress" hidden>
|
||||||
|
<div class="dc-progress__bar"><div class="dc-progress__fill"></div></div>
|
||||||
|
<p class="dc-progress__label">Processando o arquivo, aguarde…</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button type="button" class="btn-outline" id="dc-reprocessar-cancel-btn">Cancelar</button>
|
||||||
|
<button type="button" class="btn-solid" id="dc-reprocessar-confirmar-btn">Reprocessar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-overlay" id="dc-achado-modal" hidden>
|
||||||
|
<div class="modal-card">
|
||||||
|
<h2 class="modal-card__title" id="dc-achado-modal-titulo"></h2>
|
||||||
|
<p class="modal-card__subtitle" id="dc-achado-modal-mensagem"></p>
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="dc-achado-modal-texto">Observação (obrigatória)</label>
|
||||||
|
<textarea id="dc-achado-modal-texto" rows="5" placeholder="Justifique o tratamento desta observação…"></textarea>
|
||||||
|
</div>
|
||||||
|
<p class="modal-error" id="dc-achado-modal-error"></p>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button type="button" class="btn-outline" id="dc-achado-modal-cancel-btn">Cancelar</button>
|
||||||
|
<button type="button" class="btn-danger-outline" id="dc-achado-modal-ignorar-btn">Ignorar</button>
|
||||||
|
<button type="button" class="btn-solid" id="dc-achado-modal-tratar-btn">Marcar como tratado</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-overlay" id="dc-obs-historico-modal" hidden>
|
||||||
|
<div class="modal-card">
|
||||||
|
<h2 class="modal-card__title">Histórico de edições</h2>
|
||||||
|
<p class="modal-card__subtitle">Toda mudança de texto desta observação, da mais recente para a mais antiga.</p>
|
||||||
|
<div class="dc-obs-historico-lista" id="dc-obs-historico-lista"></div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button type="button" class="btn-solid" id="dc-obs-historico-modal-fechar-btn">Fechar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-overlay" id="dc-indicador-formula-modal" hidden>
|
||||||
|
<div class="modal-card">
|
||||||
|
<h2 class="modal-card__title" id="dc-indicador-formula-modal-titulo"></h2>
|
||||||
|
<p class="modal-card__subtitle" id="dc-indicador-formula-modal-descricao"></p>
|
||||||
|
<div class="dc-ind-formula-box">
|
||||||
|
<span class="dc-ind-formula-box__label">Fórmula</span>
|
||||||
|
<p id="dc-indicador-formula-modal-formula"></p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button type="button" class="btn-solid" id="dc-indicador-formula-modal-fechar-btn">Fechar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-overlay" id="dc-indicadores-hub-modal" hidden>
|
||||||
|
<div class="modal-card modal-card--wide">
|
||||||
|
<h2 class="modal-card__title">Gerenciar Indicadores</h2>
|
||||||
|
<p class="modal-card__subtitle">Indicadores padrão aparecem automaticamente em toda apuração. Indicadores não padrão ficam salvos aqui e só entram nesta apuração quando marcados.</p>
|
||||||
|
|
||||||
|
<div class="dc-ind-hub-secao">
|
||||||
|
<h3 class="dc-ind-hub-secao__titulo">Padrão</h3>
|
||||||
|
<div class="dc-ind-hub-lista" id="dc-ind-hub-lista-padrao"></div>
|
||||||
|
<p class="dc-empty" id="dc-ind-hub-vazio-padrao" hidden>Nenhum indicador padrão personalizado cadastrado.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dc-ind-hub-secao">
|
||||||
|
<h3 class="dc-ind-hub-secao__titulo">Não padrão</h3>
|
||||||
|
<div class="dc-ind-hub-lista" id="dc-ind-hub-lista-nao-padrao"></div>
|
||||||
|
<p class="dc-empty" id="dc-ind-hub-vazio-nao-padrao" hidden>Nenhum indicador não padrão cadastrado.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button type="button" class="btn-outline" id="dc-indicadores-hub-fechar-btn">Fechar</button>
|
||||||
|
<button type="button" class="btn-solid" id="dc-indicadores-hub-novo-btn">+ Novo Indicador</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-overlay modal-overlay--top" id="dc-indicador-modal" hidden>
|
||||||
|
<div class="modal-card modal-card--wide">
|
||||||
|
<h2 class="modal-card__title" id="dc-indicador-modal-titulo">Novo Indicador</h2>
|
||||||
|
<p class="modal-card__subtitle">Monte a fórmula a partir de contas do Balancete, linhas da DRE, variação entre apurações ou outros indicadores já existentes.</p>
|
||||||
|
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="dc-indicador-modal-nome">Nome</label>
|
||||||
|
<input type="text" id="dc-indicador-modal-nome" name="indicador_nome" autocomplete="off" placeholder="ex.: Margem Líquida…" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-field-row">
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="dc-indicador-modal-descricao">Descrição</label>
|
||||||
|
<textarea id="dc-indicador-modal-descricao" rows="3" placeholder="O que este indicador mede, como interpretar o resultado…"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="dc-indicador-modal-formato">Formato de exibição</label>
|
||||||
|
<select id="dc-indicador-modal-formato">
|
||||||
|
<option value="indice">Índice</option>
|
||||||
|
<option value="percentual">Percentual (%)</option>
|
||||||
|
<option value="moeda">Moeda (R$)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="modal-checkbox">
|
||||||
|
<input type="checkbox" id="dc-indicador-modal-padrao" checked />
|
||||||
|
Indicador padrão (aparece automaticamente em todas as apurações — desmarque pra deixar disponível só quando selecionado em "Gerenciar Indicadores")
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="modal-field">
|
||||||
|
<label>Ícone (aparece no card do relatório "Gerar Relatório")</label>
|
||||||
|
<div class="dc-ind-icone-grid" id="dc-indicador-icones"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dc-ind-componentes-header">
|
||||||
|
<span>Componentes</span>
|
||||||
|
<button type="button" class="btn-outline" id="dc-indicador-add-componente-btn">+ Adicionar componente</button>
|
||||||
|
</div>
|
||||||
|
<div id="dc-indicador-componentes" class="dc-ind-componentes-lista"></div>
|
||||||
|
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="dc-indicador-modal-formula">Fórmula (cálculo interno)</label>
|
||||||
|
<input type="text" id="dc-indicador-modal-formula" name="indicador_formula" autocomplete="off" placeholder="ex.: (receita - custo) / receita…" />
|
||||||
|
<p class="dc-ind-formula-hint" id="dc-indicador-modal-formula-hint">Use a chave de cada componente acima, combinadas com +, -, * , / e parênteses.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="dc-indicador-modal-formula-exibicao">Fórmula (como aparece ao cliente)</label>
|
||||||
|
<input type="text" id="dc-indicador-modal-formula-exibicao" name="indicador_formula_exibicao" autocomplete="off" placeholder="ex.: Lucro Líquido − Despesas Financeiras…" />
|
||||||
|
<p class="dc-ind-formula-hint">Texto livre, só pra exibição no relatório "Gerar Relatório" — não precisa (e não deve) usar as chaves técnicas dos componentes. Se deixar em branco, o relatório mostra a fórmula de cálculo interno acima.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="btn-outline" id="dc-indicador-calcular-btn">Calcular com esta apuração</button>
|
||||||
|
<div class="dc-ind-preview" id="dc-indicador-preview" hidden></div>
|
||||||
|
|
||||||
|
<p class="modal-error" id="dc-indicador-modal-error"></p>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button type="button" class="btn-danger-outline" id="dc-indicador-modal-excluir-btn" hidden>Excluir</button>
|
||||||
|
<button type="button" class="btn-outline" id="dc-indicador-modal-cancel-btn">Cancelar</button>
|
||||||
|
<button type="button" class="btn-solid" id="dc-indicador-modal-save-btn">Salvar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-overlay" id="manage-account-modal" hidden>
|
||||||
|
<div class="modal-card" id="manage-account-modal-card">
|
||||||
|
<h2 class="modal-card__title">Gerenciar Usuário</h2>
|
||||||
|
<div class="modal-field">
|
||||||
|
<button type="button" class="btn-outline" id="manage-account-change-password-btn">Alterar senha</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-field" id="manage-account-liderados-field" hidden>
|
||||||
|
<label>Usuários sob sua liderança</label>
|
||||||
|
<div class="dual-select">
|
||||||
|
<div class="dual-select__panel">
|
||||||
|
<div class="dual-select__panel-header">
|
||||||
|
<button type="button" class="btn-solid" id="manage-account-liderados-available-link-btn" disabled>Vincular</button>
|
||||||
|
</div>
|
||||||
|
<div class="dual-select__table-wrap">
|
||||||
|
<table class="dual-select__table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><input type="checkbox" id="manage-account-liderados-available-select-all" aria-label="Selecionar todos" /></th>
|
||||||
|
<th class="dual-select__sort" id="manage-account-liderados-available-sort-nome">Nome <span class="dual-select__sort-icon">↕</span></th>
|
||||||
|
<th>Departamento</th>
|
||||||
|
</tr>
|
||||||
|
<tr class="dual-select__filters">
|
||||||
|
<th></th>
|
||||||
|
<th><input type="text" id="manage-account-liderados-available-search-nome" placeholder="Buscar por nome..." autocomplete="off" /></th>
|
||||||
|
<th><input type="text" id="manage-account-liderados-available-search-departamento" placeholder="Buscar por departamento..." autocomplete="off" /></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="manage-account-liderados-available-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="dual-select__footer" id="manage-account-liderados-available-footer"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dual-select__panel">
|
||||||
|
<div class="dual-select__panel-header">
|
||||||
|
<button type="button" class="btn-danger-outline" id="manage-account-liderados-linked-unlink-btn" disabled>Desvincular</button>
|
||||||
|
</div>
|
||||||
|
<div class="dual-select__table-wrap">
|
||||||
|
<table class="dual-select__table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><input type="checkbox" id="manage-account-liderados-linked-select-all" aria-label="Selecionar todos" /></th>
|
||||||
|
<th class="dual-select__sort" id="manage-account-liderados-linked-sort-nome">Nome <span class="dual-select__sort-icon">↕</span></th>
|
||||||
|
<th>Departamento</th>
|
||||||
|
</tr>
|
||||||
|
<tr class="dual-select__filters">
|
||||||
|
<th></th>
|
||||||
|
<th><input type="text" id="manage-account-liderados-linked-search-nome" placeholder="Buscar por nome..." autocomplete="off" /></th>
|
||||||
|
<th><input type="text" id="manage-account-liderados-linked-search-departamento" placeholder="Buscar por departamento..." autocomplete="off" /></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="manage-account-liderados-linked-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="dual-select__footer" id="manage-account-liderados-linked-footer"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="modal-error" id="manage-account-liderados-error"></p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button type="button" class="btn-outline" id="manage-account-close-btn">Fechar</button>
|
||||||
|
<button type="button" class="btn-solid" id="manage-account-save-btn" hidden>Salvar liderados</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-overlay" id="password-modal" hidden>
|
||||||
|
<div class="modal-card">
|
||||||
|
<h2 class="modal-card__title">Alterar senha</h2>
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="pwd-current">Senha atual</label>
|
||||||
|
<div class="password-field">
|
||||||
|
<input type="password" id="pwd-current" autocomplete="current-password" />
|
||||||
|
<button type="button" class="password-field__toggle" id="pwd-current-toggle" aria-label="Mostrar senha"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7Z" stroke-linecap="round" stroke-linejoin="round"/><circle cx="12" cy="12" r="3"/></svg></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="pwd-new">Nova senha</label>
|
||||||
|
<div class="password-field">
|
||||||
|
<input type="password" id="pwd-new" autocomplete="new-password" />
|
||||||
|
<button type="button" class="password-field__toggle" id="pwd-new-toggle" aria-label="Mostrar senha"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7Z" stroke-linecap="round" stroke-linejoin="round"/><circle cx="12" cy="12" r="3"/></svg></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="pwd-confirm">Confirmar nova senha</label>
|
||||||
|
<div class="password-field">
|
||||||
|
<input type="password" id="pwd-confirm" autocomplete="new-password" />
|
||||||
|
<button type="button" class="password-field__toggle" id="pwd-confirm-toggle" aria-label="Mostrar senha"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7Z" stroke-linecap="round" stroke-linejoin="round"/><circle cx="12" cy="12" r="3"/></svg></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="modal-error" id="pwd-error"></p>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button type="button" class="btn-outline" id="pwd-cancel-btn">Cancelar</button>
|
||||||
|
<button type="button" class="btn-solid" id="pwd-save-btn">Salvar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-overlay" id="notif-history-modal" hidden>
|
||||||
|
<div class="modal-card">
|
||||||
|
<h2 class="modal-card__title">Histórico de Notificações</h2>
|
||||||
|
<ul class="notif-list" id="notif-history-list"></ul>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button type="button" class="btn-outline" id="notif-history-close-btn">Fechar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="dc-back-to-top-btn" id="dc-back-to-top-btn" aria-label="Voltar ao início da página" data-dc-dica="Voltar ao início da página" hidden>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5"/><path d="M5 12l7-7 7 7"/></svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="modal-overlay" id="calendario-depaula-modal" hidden>
|
||||||
|
<div class="modal-card iframe-modal-card">
|
||||||
|
<div class="iframe-modal-header">
|
||||||
|
<h2 class="modal-card__title">Calendário De Paula</h2>
|
||||||
|
<button type="button" class="icon-btn" id="calendario-depaula-close-btn" aria-label="Fechar">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12" stroke-linecap="round"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<iframe id="calendario-depaula-iframe" class="iframe-modal-iframe" title="Calendário De Paula"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="{% static 'js/api.js' %}"></script>
|
||||||
|
<script src="{% static 'js/confirm-modal.js' %}"></script>
|
||||||
|
<script src="{% static 'js/seasonal-theme.js' %}"></script>
|
||||||
|
<script src="{% static 'js/sidebar-brand.js' %}"></script>
|
||||||
|
<script src="{% static 'js/dual-select.js' %}"></script>
|
||||||
|
<script src="{% static 'js/profiles.js' %}"></script>
|
||||||
|
<script src="{% static 'js/auth.js' %}"></script>
|
||||||
|
<script src="{% static 'js/access.js' %}"></script>
|
||||||
|
<script src="{% static 'js/account.js' %}"></script>
|
||||||
|
<script src="{% static 'js/favorites.js' %}"></script>
|
||||||
|
<script src="{% static 'js/events.js' %}"></script>
|
||||||
|
<script src="{% static 'js/sidebar.js' %}"></script>
|
||||||
|
<script src="{% static 'js/notifications.js' %}"></script>
|
||||||
|
<script>
|
||||||
|
// Mesma tela do Relatório Contábil, apontada para as tabelas e a
|
||||||
|
// permissão da De Paula (ver PID_DC_CONFIG em dashboard-contabil.js).
|
||||||
|
window.PID_DC_CONFIG = {
|
||||||
|
moduleKey: "diretoria",
|
||||||
|
appKey: "dashboard-contabil-de-paula",
|
||||||
|
endpointApuracoes: "/contabil-apuracoes-de-paula",
|
||||||
|
endpointContas: "/contabil-contas-de-paula",
|
||||||
|
endpointLinhasDre: "/contabil-linhas-dre-de-paula",
|
||||||
|
endpointLinhasAnaliseVertical: "/contabil-linhas-analise-vertical-de-paula",
|
||||||
|
endpointObservacoes: "/contabil-observacoes-de-paula",
|
||||||
|
endpointAchados: "/contabil-achados-de-paula",
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="{% static 'js/dashboard-contabil.js' %}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -738,7 +738,7 @@
|
|||||||
<h2>
|
<h2>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
|
||||||
Balancete
|
Balancete
|
||||||
<a class="dcr-export-btn" href="/api/contabil-apuracoes/{{ apuracao.id }}/exportar-xlsx/?parte=balancete">
|
<a class="dcr-export-btn" href="{{ prefixo_api }}/{{ apuracao.id }}/exportar-xlsx/?parte=balancete">
|
||||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
|
||||||
Exportar XLSX
|
Exportar XLSX
|
||||||
</a>
|
</a>
|
||||||
@ -837,7 +837,7 @@
|
|||||||
<h2>
|
<h2>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19V5a2 2 0 0 1 2-2h8l6 6v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2Z"/><path d="M14 3v6h6M9 13h6M9 17h6"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19V5a2 2 0 0 1 2-2h8l6 6v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2Z"/><path d="M14 3v6h6M9 13h6M9 17h6"/></svg>
|
||||||
D.R.E. — Demonstração do Resultado do Exercício
|
D.R.E. — Demonstração do Resultado do Exercício
|
||||||
<a class="dcr-export-btn" href="/api/contabil-apuracoes/{{ apuracao.id }}/exportar-xlsx/?parte=dre">
|
<a class="dcr-export-btn" href="{{ prefixo_api }}/{{ apuracao.id }}/exportar-xlsx/?parte=dre">
|
||||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
|
||||||
Exportar XLSX
|
Exportar XLSX
|
||||||
</a>
|
</a>
|
||||||
@ -991,7 +991,7 @@
|
|||||||
|
|
||||||
<div class="dcr-tab-panel" data-dcr-panel="indicadores" hidden>
|
<div class="dcr-tab-panel" data-dcr-panel="indicadores" hidden>
|
||||||
<div class="dcr-resumo-toolbar">
|
<div class="dcr-resumo-toolbar">
|
||||||
<a class="dcr-export-btn" href="/api/contabil-apuracoes/{{ apuracao.id }}/resumo-pdf/">
|
<a class="dcr-export-btn" href="{{ prefixo_api }}/{{ apuracao.id }}/resumo-pdf/">
|
||||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><path d="M12 18v-6M9 15l3 3 3-3"/></svg>
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><path d="M12 18v-6M9 15l3 3 3-3"/></svg>
|
||||||
Exportar PDF
|
Exportar PDF
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -144,6 +144,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li data-section="relatorios-gerenciais" class="nav-group">
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
@ -144,6 +144,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li data-section="relatorios-gerenciais" class="nav-group">
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
@ -144,6 +144,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li data-section="relatorios-gerenciais" class="nav-group">
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
@ -144,6 +144,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li data-section="relatorios-gerenciais" class="nav-group">
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
@ -144,6 +144,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li data-section="relatorios-gerenciais" class="nav-group">
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
@ -144,6 +144,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li data-section="relatorios-gerenciais" class="nav-group">
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
@ -143,6 +143,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li data-section="relatorios-gerenciais" class="nav-group">
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
@ -147,6 +147,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li data-section="relatorios-gerenciais" class="nav-group">
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
@ -144,6 +144,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li data-section="relatorios-gerenciais" class="nav-group">
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
@ -143,6 +143,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li data-section="diretoria" class="nav-group">
|
||||||
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 13h18" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
<span class="nav-item__label">Diretoria</span>
|
||||||
|
<svg class="nav-item__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-group__list">
|
||||||
|
<li data-app="dashboard-contabil-de-paula"><a href="dashboard-contabil-de-paula.html" class="nav-subitem">Relatório Contábil - De Paula</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li data-section="relatorios-gerenciais" class="nav-group">
|
<li data-section="relatorios-gerenciais" class="nav-group">
|
||||||
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
<button type="button" class="nav-item nav-group__toggle" data-group-toggle>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18M7 15l4-5 3 3 5-7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user