Tela admin de impostos passa a exportar no layout eNotas
TLDR:
/admin/taxesdeixa de listarprofessional_payment_invoicepor ano/status e passa a listar sessões (Meeting) elegíveis para nota fiscal, uma linha por sessão, nas colunas exigidas pela importação de notas do eNotas — substituindo a query SQL manual rodada hoje para gerar esse export.
Contexto
A emissão de notas fiscais dos terapeutas é feita hoje rodando manualmente uma query SQL direto no banco, que monta um CSV no layout de importação do eNotas: uma linha por sessão paga, com identificador único, dados do terapeuta (nome, documento, e-mail, endereço com cidade/UF/CEP/país) e o valor da taxa retida.
A tela /admin/taxes e o endpoint GET /api/v1/admin/taxes já existiam (spec 20260727165953_admin_taxes_endpoint), mas listavam professional_payment_invoice (uma linha por invoice mensal, com status/subtotal/taxas separadas) filtrando por year. Esse formato não corresponde ao que o eNotas espera para importar notas, exigindo que a query manual continue sendo rodada à parte.
Esta entrega faz a tela e o endpoint produzirem diretamente as colunas do layout eNotas, eliminando a necessidade da query manual.
Objetivos
- Trocar a base da listagem de
ProfessionalPaymentInvoiceparaMeeting: uma linha por sessão elegível para nota, não por invoice mensal - Trocar o filtro
yearpor um intervalostart_period/end_period(formatoMM/AAAA), filtrando pela data de atualização do pedido (orders.updated_at) — o eNotas exporta por período arbitrário, não só por ano - Reescrever
TaxInvoiceSerializerpara expor as colunas do layout eNotas:chave_unica,cliente_nome_razao_social,cliente_documento,cliente_email,cliente_endereco(cidade/UF, CEP, país concatenados),produto_nome(fixo,"ATENDIMENTO TERAPEUTICO") evenda_valor_total/venda_data - Restringir a listagem a sessões realmente faturáveis:
finished, com pedido pago (orders.payments.status = paid), pagamento não gratuito (payment_method != free) e com taxa de invoice (professional_payment_invoice_meetings.total) maior que zero - Manter a tela
/admin/taxese o endpoint JSON consumindo a mesma lógica (FetchTaxSummary/TaxSummaryQuery), como já era antes
Fora de escopo
- Gerar o CSV de fato (export/download) — esta entrega só ajusta a listagem em tela/JSON para as colunas corretas; a geração do arquivo para upload no eNotas continua manual por ora
- Automatizar ou versionar a query SQL manual usada como referência de layout — continua fora do código, roda direto no banco quando necessário
- Autenticação, paginação e estrutura de resposta do endpoint — inalteradas nesta entrega
Mudanças
app/queries/professional_payment_invoices/tax_summary_query.rb—initializepassa a partir deMeeting.finished.joins(:professional_payment_invoice_meeting).joins(user_client: :professional).joins(order: :payment).where(orders: {payments: {status: :paid}}).where.not(orders: {payment_method: :free}).where("professional_payment_invoice_meetings.total > 0"), compreloaddeorder,professional_payment_invoice_meetingeuser_client: {professional: [:user_profile, :address]}, ordenando porusers.name.#by_year(year)é substituído por#by_period(start_period, end_period), que fazDate.strptime(period, "%m/%Y")e filtraorders.updated_atentre o início do primeiro mês e o fim do último; retornaselfsem filtrar se qualquer um dos dois forblank?app/use_cases/professional_payment_invoices/fetch_tax_summary.rb— chamaTaxSummaryQuery#by_period(context.start_period, context.end_period)em vez de#by_year(context.year)app/controllers/api/v1/admin/taxes_controller.rbeapp/controllers/admin/taxes_controller.rb— passamparams[:start_period]/params[:end_period]para o use case em vez deparams[:year]app/serializers/tax_invoice_serializer.rb— reescrito para o layout eNotas.objectagora é umMeeting. Novos atributos:chave_unica(object.id),cliente_nome_razao_social/cliente_documento/cliente_email(do terapeuta, viauser_client.professional),cliente_endereco(monta"rua, número, bairro, cidade - UF, CEP, país"a partir doaddressdo terapeuta, pulando partes ausentes),produto_nome(constante"ATENDIMENTO TERAPEUTICO"),venda_valor_total(ProfessionalPaymentInvoices::CalculateFee.call(meeting.professional_payment_invoice_meeting.total), taxa de 10%) evenda_data(order.updated_atformatadodd/mm/aaaa). UF é abreviada via mapaSTATE_ABBREVIATIONScom remoção de acentos (tr); CEP é normalizado para 8 dígitos viaStripNonNumbers, tratando"00000000"como ausente; país usa"Brasil"como padrão quandocountry_codeforBRou ausenteapp/views/admin/taxes/index.html.erb— troca o campo único “Ano” por dois campos “De”/”Até” (start_period/end_period,maxlength="7", formatoMM/AAAA); tabela passa a ter uma coluna por atributo do serializer (Sessão, Terapeuta, Documento, Email, Endereço, Produto, Valor da taxa, Data), lendo deTaxInvoiceSerializer.new(meeting)por linha; subtítulo passa a mencionar o formato eNotasspec/queries/professional_payment_invoices/tax_summary_query_spec.rb,spec/use_cases/professional_payment_invoices/fetch_tax_summary_spec.rb,spec/requests/api/v1/admin/taxes_spec.rb,spec/features/admin/taxes_index_spec.rb— reescritos para a nova base (Meetingem vez deProfessionalPaymentInvoice), com um helper localcreate_exportable_meetingrepetido em cada arquivo (sessão:finished, pedido:paid_order,professional_payment_invoice_meetingcomtotal) e casos novos para as exclusões (sessão não finalizada, pagamento não pago, pagamento gratuito, taxa zerada)
Sem spec dedicada para o serializer
O repo não tem spec/serializers/ — nenhum dos 35 serializers em app/serializers/ tem spec unitária; a cobertura é sempre indireta via request/feature spec. Esta entrega manteve o padrão: a lógica nova do serializer (documento, endereço com UF/CEP/país, produto, valor, data) não ganhou spec própria. A feature/request spec cobre nome, email e valor da taxa; UF, CEP, país e formatação de endereço ficaram sem asserção direta.
Como verificar
make test test=spec/queries/professional_payment_invoices/tax_summary_query_spec.rbmake test test=spec/use_cases/professional_payment_invoices/fetch_tax_summary_spec.rbmake test test=spec/requests/api/v1/admin/taxes_spec.rbmake test test=spec/features/admin/taxes_index_spec.rb- Manual: acessar
/admin/taxes, filtrar porstart_period/end_period(MM/AAAA) e comparar as linhas retornadas com o resultado da query manual de referência rodada para o mesmo período - Manual: conferir que terapeutas com endereço em estado abreviado (
"SP") e em nome completo ("São Paulo") resolvem para a mesma UF na coluna Endereço
Documentação
Nenhuma mudança em rules/ ou reference/ — não introduz regra de negócio nova (o cálculo da taxa de 10% já existe em ProfessionalPaymentInvoices::CalculateFee), só reformata como um dado existente é listado. O fluxo de repasse/taxas continua descrito em invoice_payment_flow.