Foto na carteira digital auto-emitida e resultado único na busca pública
TLDR:
Membership#automatic_approve!passa a anexar a foto do perfil na carteira — comomanual_approve!já faz — e as filiações auto-aprovadas sem foto são corrigidas no dado;Membership.searchpassa a devolver uma filiação por terapeuta, a devalid_untilmais distante, refletindo a continuidade da vigência após a renovação.
Contexto
Depois que a renovação automática entrou (PR #217), duas regressões apareceram na área pública do site do CITRG:
-
Carteira digital sem foto.
automatic_approve!nasceu sem ocard_picture.attachque todos os caminhos de aprovação humana têm (manual_approve!,AdminMembershipsService#admin_approve_membership, o form do Admin). ComoApi::V2::TherapistController#cardresolve a filiação porMembership.active_current— que pega a mais nova — o card servido é sempre a renovaçãoauto_issued, justamente a que não tem foto. OTherapistCardSerializerdevolvephoto_url: null. -
Duplicidade na busca pública.
Membership.searchdevolve uma linha por filiação, não por terapeuta, e o scopeactivefiltra sóvalid_until >= hoje— nuncavalid_since. Como a renovação nasce comvalid_sinceigual aovalid_untilda vigente, as duas filiações são “ativas” hoje ao mesmo tempo. Antes da aprovação automática a renovação ficavanot_issuedcom o perfil desaprovado e não passava emwith_profile_approved_or_card_issued; com a carteira definitiva ela nasceauto_issuede o perfil auto-aprovado, então as duas passam. As duas compartilham o mesmoregister_number, e o token do card éBase64(register_number)— por isso os dois resultados abrem o mesmo card e exibem o mesmo vencimento.
O apolo_membership já resolveu o mesmo problema de “duas filiações em continuidade” no R-001. A busca pública nunca recebeu esse tratamento.
Objetivos
- Filiações auto-aprovadas a partir de agora nascem com
card_pictureanexado, igual às aprovadas manualmente. - As filiações já auto-aprovadas desde 12/08/2026, que estão sem
card_picture, recebem o anexo por correção de dado — cerca de 100 registros. - A busca pública devolve um resultado por terapeuta, mesmo com filiações consecutivas.
- O resultado devolvido é o da filiação de
valid_untilmais distante — a nova vigência após a renovação. Membership.active_currentdeixa de escolher a filiação porcreated_ate passa a escolher pela vigência.
Fora de escopo
- Fallback de leitura na carteira digital.
TherapistCardSerializercontinua exibindo só ocard_pictureda própria filiação: o passivo é corrigido no dado, não escondido na leitura. Filiação sem foto no perfil continua sem foto — é caso de pedir a foto à terapeuta. - Rake task para o backfill: são ~100 registros, corrigidos uma vez pelo console. Nada de arquivo one-off no repositório.
- Composição de campos entre duas filiações no card, ao estilo do R-001 — o card continua vindo de uma filiação, agora a certa.
- Alterar o
register_numbercompartilhado entre filiações consecutivas, ou o formato do token do card. MembershipApoloSerializereMembership#public_serialize, que já tratamcard_pictureausente comattached?.
Mudanças
A entrega é em dois passos: primeiro a foto da carteira (modelo + correção de dado), depois o resultado único na busca (scope :search).
app/models/membership.rb
automatic_approve!anexa a foto do perfil na carteira antes doupdate!, via um método privadoattach_profile_picture_to_cardextraído demanual_approve!— os dois caminhos passam a usá-lo.scope :searchpassa a devolver uma linha por terapeuta: a filtragem atual vira subconsulta comDISTINCT ON (memberships.user_id)ordenada porvalid_until DESC, id DESC, e o escopo externo (where(id: ...)) é ordenado porvalid_until DESC, id DESC. A relação externa continua sendo umaActiveRecord::Relationcomum, entãopage/per/total_countdo Kaminari seguem corretos — etotal_countpassa a contar terapeutas, não filiações.active_currentnão muda de código: comosearchagora ordena por vigência,.firstdevolve a filiação de vigência mais distante em vez da criada por último.
Correção de dado (console de produção, uma vez)
Percorre as filiações auto_issued sem card_picture e anexa o blob da foto do perfil. Anexar reusa o blob — nenhum arquivo é enviado de novo. Filiação cujo usuário não tem foto no perfil é apenas listada.
Como verificar
Testes (make run.test path=<arquivo>):
test/models/membership_test.rbautomatic_approve!anexa a foto do perfil na carteira.searchdevolve uma única filiação para o usuário com vigente + renovação aprovada, e é a devalid_untilmais distante.searchcontinua devolvendo um resultado por terapeuta distinto quando dois usuários diferentes batem no termo.active_currentdevolve a renovação, não a filiação vigente, quando as duas estão ativas.
test/controllers/api/v2/therapist_controller_test.rb- busca por nome e por
register_numberdevolveitems.length == 1epagination.total_count == 1após a renovação auto-aprovada. GET /api/v2/therapist/card/:tokendevolve a vigência da renovação.
- busca por nome e por
Correção de dado: rodar a contagem antes e depois — Membership.where(card_status: :auto_issued).where.missing(:card_picture_attachment).count deve cair para o número de filiações cujo usuário não tem foto no perfil.
Ponta a ponta: buscar a terapeuta renovada no site do CITRG deve trazer um único resultado, e a carteira aberta a partir dele deve mostrar a foto e a vigência 2026–2027.
Documentação
- Atualizar R-004 e R-009:
automatic_approve!anexa a foto do perfil na carteira, igual amanual_approve!. - Criar regra nova em
.project/docs/rules/membership/— a busca pública devolve um resultado por terapeuta, com a vigência mais distante — e registrá-la em.project/docs/RULES.md. - Criar learning em
.project/docs/learnings/sobre o caminho automático que nasceu sem o efeito colateral do caminho manual.