Aprovação com múltiplas filiações pendentes
TLDR: quando um terapeuta tem mais de uma filiação pendente de aprovação, o sistema deve aprovar manualmente a filiação vigente (A) e auto-aprovar automaticamente as filiações futuras (B, C…), além de marcar
issued_definitive_card = trueno momento da aprovação dos documentos.
Status: proposed Created: 2026-08-11 Owner: @Ruan1800
Contexto
O fluxo de aprovação de documentos (AdminUserProfilesService#admin_approve_user_profile) chama user.active_memberships.first para selecionar qual filiação aprovar. O scope active ordena por created_at DESC e o método encadeia .order(created_at: :asc) — mas em Rails, .order acrescenta ao invés de substituir, então a ordenação efetiva é DESC. O .first retorna a filiação mais recente (B), não a vigente (A).
Caso concreto: - Filiação A — criada 03/02/2026, vigência 2026–2027 - Filiação B — criada 03/07/2026, vigência 2027–2028 - Em 22/07/2026, admin aprova os documentos → sistema aprova B, A fica sem aprovação
Adicionalmente, issued_definitive_card = true hoje só é setado quando a carteira física entra em remessa (admin_issue_card). O requisito é que ele seja setado no momento em que o atendente aprova os documentos e o status vai para :processing.
Relacionado a R-004 — que documenta o fluxo de auto-aprovação via webhook para quem já tem carteira definitiva.
Objetivos
- Sempre aprovar a filiação cuja vigência cobre a data de hoje (
valid_since <= hoje <= valid_until), excluindo reembolsos - Após aprovar a filiação vigente (A), auto-aprovar automaticamente todas as filiações futuras pendentes (B, C…) ancoradas em
valid_since >= A.valid_until - Manter o envio de dois e-mails: aprovação manual para A e
membership_renewal_approved_emailpara cada futura - Setar
issued_definitive_card = trueno momento em queadmin_approve_membershipé chamado (card_status →:processing) - Garantir atomicidade: aprovação de A e auto-aprovação de futuras dentro de uma única transação
Não-goals
- Não altera o fluxo de aprovação via webhook (
WebhookMembershipService) — intocado - Não cria use case, camada nova ou arquivo novo — mudanças cirúrgicas nos arquivos existentes
- Não remove
issued_definitive_card = truedeadmin_issue_card— permanece lá também (idempotente) - Não seta
documentation_status = :okemauto_approve!— segue o padrão do webhook, que não seta
Mudanças
app/models/membership.rb
Adiciona método de estado auto_approve!, seguindo o padrão de suspend!/unsuspend!:
ruby
def auto_approve!(approver)
self.card_status = :auto_issued
self.admin_approved_at = Time.current
self.admin_approved_by = approver
self.card_issued_at = Time.current
self.card_issued_by = approver
save!
end
app/services/admin_memberships_service.rb
Em admin_approve_membership, adiciona 1 linha após setar card_status = :processing:
ruby
resource.user.update!(issued_definitive_card: true)
app/services/admin_user_profiles_service.rb
Refatora apenas admin_approve_user_profile:
Seleção da filiação vigente — substitui active_memberships.try(:first) por current_membership, já existente em User (user.rb:76), que encontra a filiação cuja vigência cobre hoje (between valid_since and valid_until):
ruby
@membership = @profile_user.current_membership
Transação + auto-aprovação da próxima — envolve aprovação de A e auto-aprovação da futura em ActiveRecord::Base.transaction. E-mails ficam fora da transação.
Reutiliza User#next_membership (user.rb:83), já existente, que encontra a filiação que começa após o valid_until da atual. Guard contra o fallback || current do método:
```ruby ActiveRecord::Base.transaction do # aprova UserProfile e membership vigente (A) future_membership = @profile_user.next_membership if future_membership && future_membership != @membership && future_membership.admin_approved_at.nil? && future_membership.not_issued? future_membership.auto_approve!(automatic_approver) end end
e-mails fora da transação
fire_approved_user_profile_email if future_membership && future_membership != @membership MembershipMailer .with(user_id: @profile_user.id, membership_id: future_membership.id) .membership_renewal_approved_email .deliver_later end ```
Adiciona automatic_approver como método privado — mesma lógica já existente no webhook, sem tocar nele:
```ruby private
def automatic_approver User.find_or_create_by!(email: “automatico@citrg.com.br”) do |u| u.name = “Automático” u.admin = true pass = SecureRandom.hex u.password = u.password_confirmation = pass end end ```
Como verificar
bash
make test test=test/services/admin_user_profiles_service_test.rb
make test test=test/models/membership_test.rb
make container.server.lint
Cenário manual (console):
```ruby # usuário com A (vigente) e B (futura), ambas sem aprovação user = User.find_by(email: “…”) user.memberships.order(:valid_since) # => [A (not_issued, admin_approved_at: nil), B (not_issued, admin_approved_at: nil)]
simula aprovação pelo admin
service = AdminUserProfilesService.new service.admin_approve_user_profile(user.user_profile, User.admins.first)
user.memberships.reload.order(:valid_since).map { |m| [m.valid_since, m.card_status, m.admin_approved_at] }
# => A: [:processing,
Guards da query de futuras — casos a verificar:
| Cenário | Esperado |
|---|---|
| Terapeuta com só A (sem futuras) | Loop vazio, sem e-mail extra, sem efeito colateral |
| A + B pendentes | A aprovada manualmente, B auto-aprovada, 2 e-mails |
| A + B + C pendentes | A manual, B e C auto-aprovadas em ordem, 3 e-mails |
| B já aprovada manualmente antes | admin_approved_at não nil → excluída da query |
| B reembolsada | payment_status: refunded → excluída da query |
| B com carteira em processamento | not_issued falso → excluída da query |
Documentação
- R-004 — Renovação de membro com carteira definitiva é aprovada automaticamente — atualizar para refletir que
issued_definitive_card = trueagora também é setado emadmin_approve_membership(aprovação pelo atendente), não apenas emadmin_issue_card(entrada em remessa física).