Pular para o conteúdo

Relatórios de serviços — Fluxos

Ator Auth Rota Escopo de dados
Staff (Administrador, Acolhimento, Financeiro, …) system_users + role_pages /app/relatorios/servicos Todos os profissionais / serviços pagos
Profissional system_users com role Profissional /relatorio/servicos (fora do shell staff) Só serviços do próprio professional_code
Camada Comportamento
Admin → páginas por role Role Profissional default: apenas página relatorios-servicos
RBAC API Staff: leitura ampla; Profissional: filtro obrigatório professional_id = session.internal_code
Login redirect Profissional → /relatorio/servicos (não entra no menu staff /app)

Roles staff entram no shell Theme; o profissional não usa sidebar de acolhimento/loja — só o portal do relatório (+ logout).


Só colaboradores que precisam do relatório. Prescritores “de fora” (só receita) não ganham role nem portal.

Criação obrigatória na área de profissionais (/app/profissionais), por registro:

Linha do profissional (is_collaborator)
→ ação "Criar conta" / "Enviar convite"
→ exige e-mail no cadastro do profissional
→ API:
1. Cria (ou reutiliza) system_users:
permissions = ["Profissional"]
internal_code = professional_code
email / name / last_name do profissional
status = pending até concluir /cadastro
2. Gera link assinado (token + expiração) → /cadastro?…
3. Tenta enviar e-mail com o link
→ módulo e-mail indisponível: UI "Copiar link"
→ Profissional abre o link (válido só até expirar)
→ Tela /cadastro: define senha + dados necessários
→ Conta ativa → login no Kunk
→ Redirect SEMPRE /relatorio/servicos
→ Qualquer `/app/*` fora do portal → bloqueado
Campo system_users Valor
permissions somente Profissional (não misturar roles staff)
internal_code professionals.professional_code
email / name / last_name Do profissional

Regras de acesso (hard):

  • Role Profissional nunca acessa páginas operacionais do Kunk (associados, pedidos, serviços, etc.)
  • role_pages default = ["relatorios-servicos"] e guards de rota reforçam
  • Um professional_code → no máximo uma conta portal
  • Reenviar convite: novo token / nova expiração; invalidar o anterior

Envio de e-mail do convite depende do módulo SMTP (email_sent / email_status no response da API).


Abrir página
→ default mês = mês civil anterior
→ GET serviços:
status = "Pagamento Concluído"
consultation_date IS NOT NULL
consultation_date no intervalo do mês (ou meses do ano se "Todos")
→ GET professionals (nomes + contest_reports)
→ montar árvore mês → profissional → linhas

Serviços sem data de consulta são desconsiderados (não listar, não somar).

Controle Comportamento
Mês/Ano Select com meses do ano corrente até o mês atual (histórico getMonthsOfCurrentYear); opção “Todos”
Profissional Autocomplete — esconde quando deep link/internal_code fixa um profissional
Atualizar Refetch
Exportar PDF Tabela agrupada (Data, Associado, Valor pago, Doação, Valor consulta, Valor a receber)
Contexto Exibição
Profissional selecionado Total de registros + Valor a receber (soma dos payables)
Sem profissional Por mês e por profissional: contagem + soma (sempre a fórmula de payable — ver fields)
Ação Efeito
Aprovar PATCH commission_validation = approved
Contestar PATCH commission_validation = contested
Limpar seleção Só UI
Pagamento Ação de pagamento externo — não faz parte desta tela

Staff vê cards vermelhos sob o nome do profissional (mês filtrado). Botão “resolvido” → remove item de contest_reports (PATCH). Sem WhatsApp.


Login (role Profissional)
→ redirect /relatorio/servicos
→ API escopa automaticamente ao internal_code
→ filtro de profissional oculto (só ele)
→ mesmos agrupamentos / colunas / totais
→ botão "Estão faltando dados" → modal contestação
→ logout

Diferenças vs staff:

Item Staff Profissional
Filtro profissional Sim Não (fixo)
Aprovar / contestar linhas Sim Não (só visualiza status)
Contestação “faltam dados” Resolve Cria
Exportar PDF Sim Sim
Shell Theme / sidebar Sim Não — página dedicada

Deep link histórico ?p= no portal: se autenticado e p ≠ internal_code → 403 / não autorizado. Staff no /app pode usar ?p= para pré-selecionar.


Profissional clica "Estão faltando dados"
→ modal com motivo (texto obrigatório)
→ append em professionals.contest_reports:
{ text, date (ISO), month: "março 2026" }
→ staff vê no relatório do mês correspondente
→ staff marca resolvido → remove do array

Contestações são por profissional + mês, não por linha de serviço. Validação por linha (commission_validation) é independente.


fee = association_fee(type) // admin; default 0; sempre aplica
payable = price - fee
if deduct_donation_from_payable: // admin; default false
payable = payable - donation
payable = max(0, payable)
Totais = soma dos payable do conjunto filtrado

Detalhe: fields.


Admin → Tipos / relatório de serviços
→ CRUD tipos (id, label, association_fee, default_consultation_price)
→ Switch: "Descontar doação do valor a pagar ao profissional" (default off)
→ seed: tipos canônicos fee=0, price=null

Ao criar serviço (módulo Serviços):

price default =
se tipo.default_consultation_price != null
→ usar esse valor // anula consultation_price do profissional
senão
→ professional.consultation_price
→ senão 0

Operador ainda pode editar price no serviço depois.

Detalhe UI admin: admin. Impacto no create de serviços: atualizar ../servicos/flow / fields.


┌─────────────┐ login ┌──────────────────┐
│ Profissional│ ─────────────► │ /relatorio/servicos│
│ (role) │ │ escopo próprio │
└─────────────┘ └─────────┬──────────┘
│ contest_reports
▼
┌─────────────┐ ┌──────────────────┐
│ Staff │ ─────────────► │ /app/relatorios/ │
│ │ │ servicos │
└─────────────┘ └─────────┬──────────┘
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
services professionals system_configs
(pagos + validation) (contest_reports) (professional_types)