Repository navigation
feat(services): desativar e reativar pessoa atendida (#157, #158, backend) - #173
Conversation
…kend)
Expõe POST /api/v1/clients/{id}/deactivate e /reactivate, que mudam a
situação da pessoa sem tocar nos dados nem nos contatos e devolvem o
mesmo ClientResponse de POST, PUT e GET.
- Client.Inactivate() e Client.Reactivate() recusam a transição que já
estão (Client.AlreadyInactive, Client.AlreadyActive). O handler
responde 200 com o estado atual quando a ação é repetida, sem consultar
nem gravar nada.
- Desativar consulta IAppointmentRepository.ExistsNotCancelledStartingAfter
com o instante da operação: início estritamente futuro e não cancelado
bloqueia com 409 Client.HasUpcomingAppointments. Enquanto o módulo de
agendamentos (#153) não existe, PendingAppointmentRepository responde
"nenhum" e deve ser trocado no DI por quem criar o primeiro agendamento.
- Reativar verifica o e-mail contra outra pessoa ativa do tenant: 409
Client.DuplicateEmail, com meta clientId e clientName. Pessoa inativa
com o mesmo e-mail não bloqueia. A corrida é decidida pelo índice
único parcial e responde Client.SaveFailed (ADR 0048).
- IClientRepository.UpdateStatusAsync grava só a coluna Status, sem
recriar os contatos como o UpdateAsync faz.
- Tipos do frontend regenerados (services-api.d.ts).
- ADR 0062 e linha de transição de estado no §10 do ARCHITECTURE.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
📒 Files selected for processing (59)
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
⛔ Files ignored due to path filters (1)
📒 Files selected for processing (24)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThis change adds client deactivation and reactivation commands and API endpoints. Domain methods guard status transitions. The application checks appointments before deactivation and email conflicts before reactivation. Both flows persist status changes without updating other client fields. ChangesClient status lifecycle
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Feature Sequence Diagram(s)sequenceDiagram
participant Client
participant ClientsController
participant DeactivateClientCommandHandler
participant IAppointmentRepository
participant IClientRepository
participant IUnitOfWork
Client->>ClientsController: POST deactivate
ClientsController->>DeactivateClientCommandHandler: Dispatch command
DeactivateClientCommandHandler->>IClientRepository: Look up client
DeactivateClientCommandHandler->>IAppointmentRepository: Check upcoming appointments
DeactivateClientCommandHandler->>IClientRepository: Update status
DeactivateClientCommandHandler->>IUnitOfWork: Save changes
IUnitOfWork-->>ClientsController: Save result
ClientsController-->>Client: Return response
sequenceDiagram
participant Client
participant ClientsController
participant ReactivateClientCommandHandler
participant IClientRepository
participant IUnitOfWork
Client->>ClientsController: POST reactivate
ClientsController->>ReactivateClientCommandHandler: Dispatch command
ReactivateClientCommandHandler->>IClientRepository: Look up client
ReactivateClientCommandHandler->>IClientRepository: Check active email match
ReactivateClientCommandHandler->>IClientRepository: Update status
ReactivateClientCommandHandler->>IUnitOfWork: Save changes
IUnitOfWork-->>ClientsController: Save result
ClientsController-->>Client: Return response
Merge Risk: ⚪ Minimal · up to The change is mergeable on the supplied evidence. Appointment-based blocking remains deferred until the appointments module is integrated. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 3.03% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 66 functions across 21 files. (3 skipped: 3 unsupported.)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
… backend) IAppointmentRepository.ExistsNotCancelledStartingAfterAsync(clientId, instant) vira HasUpcomingAppointmentsAsync(clientId): a porta deixa de carregar um DateTimeOffset e o adaptador decide o que é "futuro" com o TimeProvider em UTC. O handler de desativar deixa de injetar o TimeProvider. A regra não muda: bloqueia o agendamento não cancelado cujo início do atendimento é estritamente depois do instante da consulta. O ADR 0062 registra o novo desenho e a versão com parâmetro como rejeitada. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ativação (#157, #158, backend) Atende a revisão do PR. - UnitOfWork passa a logar, uma vez, todo save rejeitado pelo banco (Warning, tipo e constraint). Criar, editar, desativar e reativar cliente perdem o ILogger e só devolvem Client.SaveFailed. - IClientRepository.UpdateAsync compara os contatos por id e grava só a diferença. Antes lançava exceção para um cliente com contatos que não mudaram, então desativar e reativar usam o mesmo UpdateAsync do PUT e UpdateStatusAsync deixa de existir. O PUT continua substituindo tudo, porque cria contatos com ids novos. - Reativar também checa o CPF contra outra pessoa (409 Client.DuplicateCpf, antes do e-mail), como criar e editar. O índice já impede o conflito, então é uma checagem defensiva. - ADR 0062 reescrito nessas partes, ADR 0048 e ARCHITECTURE §4 e as referências da skill de slice acompanham. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Updated client repository update logic to synchronize guardians and reference contacts instead of manually tracking stale entries, keeping client contact records consistent during edits. This also removes redundant usings and stale documentation imports across backend services and fixes a few compile-time cleanups in the identity and shared kernel projects.
Resumo
Parte backend de #157 e #158:
POST /api/v1/clients/{id}/deactivateePOST /api/v1/clients/{id}/reactivate. Os dois mudam só a situação da pessoa, preservam id, dados e contatos, e devolvem o mesmoClientResponsedePOST,PUTeGET.Os botões Desativar e Reativar, a confirmação e as mensagens são do frontend e não estão aqui. Por isso a descrição usa
Refs, nãoCloses.Decisões (ADR 0062)
200com o estado atual e não grava nada, nemUpdatedAt. O domínio continua recusando a transição repetida (Client.AlreadyInactive,Client.AlreadyActive); o handler só não chega a ela.IAppointmentRepository.HasUpcomingAppointmentsAsync(clientId)tem um adaptador provisório,PendingAppointmentRepository, que responde "nenhum". Hoje isso é verdade. Quem criar o primeiro agendamento em Agendar um atendimento #153 precisa trocar o registro emInfrastructure/DependencyInjection.cse apagar o adaptador.TimeProviderem UTC. Bloqueia se o início do atendimento é depois desse instante. Um atendimento que começa exatamente no instante, um já em andamento e um cancelado não bloqueiam. Bloqueio responde409 Client.HasUpcomingAppointmentse a pessoa segue ativa.409 Client.DuplicateEmail(campoemail,meta.clientIdemeta.clientName, o contrato do ADR 0044) com mensagem própria mandando corrigir o e-mail. Pessoa inativa com o mesmo e-mail não bloqueia, e quem não tem e-mail reativa. A correção é oPUTque já existe. A checagem de CPF (409 Client.DuplicateCpf, igual a criar e editar) vem antes e é defensiva: o índice de CPF cobre ativas e inativas, então o fluxo normal nunca a dispara.IX_Clients_TenantId_Email ... WHERE Status = 'Active'já impede duas ativas com o e-mail. A gravação perdedora responde409 Client.SaveFailed(ADR 0048) e a nova tentativa recebe a resposta específica.UpdateAsynccompleto. Ele lançava exceção para um cliente cujos contatos não mudaram (reinseria os contatos carregados sob ids marcados para remoção; provado com uma sonda). Agora compara por id e grava só a diferença: oPUTsegue substituindo tudo, porque cria contatos com ids novos, e uma mudança de status não toca em nenhuma linha de contato. Não há método de repositório só para status.UnitOfWork. UmWarningcom tipo e constraint por save rejeitado, uma vez para todos os handlers. Criar, editar, desativar e reativar cliente perdem oILogger; ARCHITECTURE §4, ADR 0048 e a skill de slice acompanham. Tags, Categories e Services seguem com seus mappers, então numa constraint não reconhecida o mesmo save aparece duas vezes no log até serem convertidos.Também entram:
services-api.d.tsregenerado (só adições), a linha de transição de estado no §10 do ARCHITECTURE, e os dois helpers de teste que deixavam um cliente inativo por reflexão agora chamamInactivate().Verificação
dotnet build backend/AdminBackend.slnx -c Release: 0 avisos, 0 erros.dotnet test: verde, com o gate de cobertura (ServicesService.Tests427,ServicesService.PersistenceTests61,Admin.SharedKernel.Tests228,Admin.Logging.Tests36,IdentityService.Tests19).Received,DidNotReceive): sucesso, contatos mantidos, uma única consulta de agendamentos, bloqueio, conflito de CPF e de e-mail commeta, não encontrado, repetição, falha de gravação.UnitOfWork(violação única devolve a falha e loga um aviso, sucesso não loga, outra falha propaga sem log).dotnet ef migrations has-pending-model-changes: sem mudanças. Não há migração.postgres:18inicializado porinfra/postgres/init, com identity e services em portas privadas (5180/5181/5433), sem tocar na stack Aspire nem no volumeagenza-postgres-data. 55 verificações com token real (PKCE) passaram:200nas duas ações, mesmo id, status novo, contatos com os mesmos ids e nenhuma linha de contato tocada; repetição sem gravar.404 Client.NotFoundcom o mesmo corpo para id desconhecido, de outro tenant (a linha existe na tabela, segue intacta) e excluído;400 Client.IdRequiredpara id vazio;401sem token.metaapontando a pessoa ativa; edição corrige o e-mail (oPUTainda substitui os contatos) e a reativação passa; inativa contra inativa não bloqueia e a segunda reativação é bloqueada pela primeira.200e um conflito (Client.SaveFailedou, se a corrida não foi perdida,Client.DuplicateEmail), nunca duas ativas; o log do serviço trouxe um aviso doUnitOfWorkpor corrida perdida e nenhuma mensagem de handler. 6 desativações paralelas: todas200.200,400,404e409nas duas rotas.Limites conhecidos
Fora deste PR
admin-frontend, com confirmação e as mensagens de bloqueio.Refs #157
Refs #158
🤖 Generated with Claude Code