Skip to content

feat(services): atualizar pessoa atendida e contatos vinculados (#156, backend) - #165

Open
evertonschuster wants to merge 2 commits into
mainfrom
feat/156-clients-update-backend
Open

evertonschuster wants to merge 2 commits into
mainfrom
feat/156-clients-update-backend

Conversation

@evertonschuster

@evertonschuster evertonschuster commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

O que muda

Backend de #156 (a tela de edição fica em outro PR, cortado da main depois deste). Não fecha a issue.

PUT /api/v1/clients/{id}: o corpo traz os dados atuais da pessoa e a lista final de contatos.

  • Contatos por id: com id o contato é alterado no lugar; sem id é novo; omitido é removido. Tudo em uma única transação.
  • Menor de idade: a regra do responsável vale sobre a lista final; remover o último responsável de um menor dá 400. Sem nascimento salva sem perguntar.
  • id de contato que não é da pessoa (outra pessoa, outro tenant, inexistente, já removido, lista trocada): 404 Client.ContactNotFound, sem gravar nada e sem revelar existência.
  • CPF e e-mail: regras da criação (ADR 0044) excluindo a própria pessoa. E-mail só é checado se a pessoa está ativa (editar uma pessoa inativa é permitido). CPF vale também para inativas.
  • Tenant e situação do corpo são ignorados; este endpoint não altera situação.
  • ClientResponse igual ao da criação; [RequestSizeLimit] de 64 KB como no POST.
  • Tipos do OpenAPI do frontend regenerados (só adições; generate:api-types:check ok).

Decisões (detalhes na ADR 0053)

  • Client.Update único, em duas fases: valida tudo (limites, menor, ids do próprio cliente, ids repetidos, dados de cada contato) e só então atribui e sincroniza, para uma falha não deixar a pessoa pela metade.
  • Contato removido é exclusão lógica (DeletedAt), como todo registro do serviço; não há política de retenção ainda.
  • Lista null no corpo = sem contatos daquele tipo (igual à criação).
  • Entradas de contato novas (UpdateGuardianInput, UpdateReferenceContactInput) em vez de reaproveitar as da criação com id opcional.

Para o revisor

  1. Divergência com a issue: Cadastrar pessoas atendidas/Clientes #139/Cadastrar pessoa atendida #154/Atualizar pessoa atendida e contatos vinculados #156 dizem que o CPF é único entre ativas, inativas e excluídas, mas o ADR 0044 (decisão de produto antes do merge de Cadastrar pessoa atendida #154) libera o CPF quando a pessoa é excluída, e o índice único e os lookups já funcionam assim. A edição segue o 0044 para criar e editar responderem igual. O texto das issues ficou desatualizado.
  2. Bug do EF corrigido: com a chave já preenchida pela raiz (Guid.CreateVersion7()), um contato novo adicionado a um cliente carregado era rastreado como Modified e a gravação falhava (DbUpdateConcurrencyException). Os ids dos dois contatos agora são ValueGeneratedNever(). O schema não muda (has-pending-model-changes limpo), então não há migração. Há teste de modelo que trava a configuração.
  3. Toquei no código da criação (escopo mínimo): as regras de entrada compartilhadas foram para ClientRuleBuilderExtensions e o mapeamento de contatos para ClientContactMapping, para os dois validators não divergirem; FindByCpfAsync/FindActiveByEmailAsync ganharam excludeClientId. Os testes de criação foram ajustados e seguem verdes.
  4. Sem token de concorrência: edições simultâneas da mesma pessoa são "última gravação vence" nos dados da pessoa; uma requisição só remove os contatos que carregou.
  5. docs/adr/README.md vai conflitar de forma trivial com o PR feat(services): manter os serviços oferecidos (#141, backend) #164 (ele edita as mesmas linhas, ADR 0052).

Verificação

  • dotnet build backend/AdminBackend.slnx -c Release: 0 avisos. dotnet test: 476 (unidade) + 38 (persistência) verdes; cobertura de Domain + Application 94,8%.
  • À mão, em PostgreSQL 18 descartável (porta 5433, sem tocar no volume do AppHost) com serviços reais e login real, 78 verificações: sincronização de contatos por id com as linhas removidas mantidas e carimbadas com DeletedAt; regra de menor; ids alheios (mesmo tenant, outro tenant, inexistente, lista trocada) → 404 sem gravar; pessoa de outro tenant/excluída → 404 e linha intacta; CPF/e-mail por situação; tenantId e status do corpo ignorados; corpo > 64 KB → 413; e 12 rodadas de duas edições disputando o mesmo CPF, cada uma com exatamente um 200 e um 409 cujo perdedor ficou intacto (nome, CPF e contatos). 11 delas foram barradas pelo índice único (Client.SaveFailed).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added the ability to edit client profiles, including personal details, guardians, and reference contacts.
    • Contact lists can be updated by keeping, adding, changing, or removing contacts in a single edit.
  • Bug Fixes
    • Updates now check for duplicate CPF numbers and active email addresses belonging to other clients.
    • Invalid or unavailable contact references are rejected without applying partial changes.
  • Documentation
    • Added guidance describing how client edits synchronize contacts and validate updates.

… backend)

PUT /api/v1/clients/{id}: o corpo traz os dados atuais da pessoa e a lista
final de contatos. Contato com id é alterado no lugar, sem id é novo e
omitido é removido (exclusão lógica), tudo em uma única transação.

- Client.Update valida tudo antes de atribuir (limites, responsável de
  menor sobre a lista final, ids do próprio cliente, ids repetidos, dados de
  cada contato) e depois sincroniza as duas listas; uma falha não deixa a
  pessoa pela metade.
- Id de contato que não é da pessoa (outra pessoa, outro tenant,
  inexistente, removido ou na lista errada): 404 Client.ContactNotFound,
  sem gravar nada.
- CPF e e-mail seguem as regras da criação (ADR 0044) excluindo a própria
  pessoa; e-mail só é checado se a pessoa está ativa. Tenant e situação do
  corpo são ignorados.
- Regras de entrada compartilhadas e mapeamento de contatos movidos para a
  raiz de Clients, para criar e editar não divergirem; os lookups do
  repositório ganharam excludeClientId.
- Ids dos contatos passam a ValueGeneratedNever: com a chave já preenchida
  pela raiz, o EF tratava o contato novo como Modified e a gravação falhava.
  Sem mudança de schema, sem migração.
- Tipos do OpenAPI do frontend regenerados.
- ADR 0053, ARCHITECTURE §5/§10 e skills de slice atualizadas.

Verificado à mão em PostgreSQL descartável (78 verificações, incluindo 12
rodadas de edições concorrentes pelo mesmo CPF sem gravação parcial).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 4 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: eeb10e1d-c239-4cbc-82c0-983c3e1372ec
📥 Commits

Reviewing files that changed from the base of the PR and between 6464aa1 and dc17275.

⛔ Files ignored due to path filters (1)
  • apps/admin-frontend/src/shared/api/generated/services-api.d.ts is excluded by !**/generated/**
📒 Files selected for processing (29)
  • .claude/skills/agenza-backend-slice/references/persistence.md
  • .claude/skills/agenza-backend-slice/references/use-case.md
  • backend/docs/ARCHITECTURE.md
  • backend/services/services-service/ServicesService.Api/Controllers/ClientsController.cs
  • backend/services/services-service/ServicesService.Application/Abstractions/IClientRepository.cs
  • backend/services/services-service/ServicesService.Application/Clients/ClientContactMapping.cs
  • backend/services/services-service/ServicesService.Application/Clients/ClientRuleBuilderExtensions.cs
  • backend/services/services-service/ServicesService.Application/Clients/CreateClient/CreateClientCommandExtensions.cs
  • backend/services/services-service/ServicesService.Application/Clients/CreateClient/CreateClientCommandHandler.cs
  • backend/services/services-service/ServicesService.Application/Clients/CreateClient/CreateClientCommandValidator.cs
  • backend/services/services-service/ServicesService.Application/Clients/UpdateClient/UpdateClientCommand.cs
  • backend/services/services-service/ServicesService.Application/Clients/UpdateClient/UpdateClientCommandExtensions.cs
  • backend/services/services-service/ServicesService.Application/Clients/UpdateClient/UpdateClientCommandHandler.cs
  • backend/services/services-service/ServicesService.Application/Clients/UpdateClient/UpdateClientCommandValidator.cs
  • backend/services/services-service/ServicesService.Domain/Entities/Client.cs
  • backend/services/services-service/ServicesService.Domain/Entities/ClientContact.cs
  • backend/services/services-service/ServicesService.Domain/Entities/ClientGuardian.cs
  • backend/services/services-service/ServicesService.Domain/Entities/ClientReferenceContact.cs
  • backend/services/services-service/ServicesService.Infrastructure/Persistence/Configurations/ClientGuardianConfiguration.cs
  • backend/services/services-service/ServicesService.Infrastructure/Persistence/Configurations/ClientReferenceContactConfiguration.cs
  • backend/services/services-service/ServicesService.Infrastructure/Repositories/ClientRepository.cs
  • backend/services/services-service/ServicesService.PersistenceTests/ClientPersistenceTests.cs
  • backend/services/services-service/ServicesService.Tests/Clients/ClientUpdateTests.cs
  • backend/services/services-service/ServicesService.Tests/Clients/CreateClient/CreateClientCommandHandlerTests.cs
  • backend/services/services-service/ServicesService.Tests/Clients/UpdateClient/UpdateClientCommandBindingTests.cs
  • backend/services/services-service/ServicesService.Tests/Clients/UpdateClient/UpdateClientCommandHandlerTests.cs
  • backend/services/services-service/ServicesService.Tests/Clients/UpdateClient/UpdateClientCommandValidatorTests.cs
  • docs/adr/0053-clients-edit-synchronizes-contacts-by-id.md
  • docs/adr/README.md

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 571b7ef9-fe01-4de8-a6b7-60c9ca3b669b
📥 Commits

Reviewing files that changed from the base of the PR and between 6f26c5a and 6464aa1.

⛔ Files ignored due to path filters (1)
  • apps/admin-frontend/src/shared/api/generated/services-api.d.ts is excluded by !**/generated/**
📒 Files selected for processing (29)
  • .claude/skills/agenza-backend-slice/references/persistence.md
  • .claude/skills/agenza-backend-slice/references/use-case.md
  • backend/docs/ARCHITECTURE.md
  • backend/services/services-service/ServicesService.Api/Controllers/ClientsController.cs
  • backend/services/services-service/ServicesService.Application/Abstractions/IClientRepository.cs
  • backend/services/services-service/ServicesService.Application/Clients/ClientContactMapping.cs
  • backend/services/services-service/ServicesService.Application/Clients/ClientRuleBuilderExtensions.cs
  • backend/services/services-service/ServicesService.Application/Clients/CreateClient/CreateClientCommandExtensions.cs
  • backend/services/services-service/ServicesService.Application/Clients/CreateClient/CreateClientCommandHandler.cs
  • backend/services/services-service/ServicesService.Application/Clients/CreateClient/CreateClientCommandValidator.cs
  • backend/services/services-service/ServicesService.Application/Clients/UpdateClient/UpdateClientCommand.cs
  • backend/services/services-service/ServicesService.Application/Clients/UpdateClient/UpdateClientCommandExtensions.cs
  • backend/services/services-service/ServicesService.Application/Clients/UpdateClient/UpdateClientCommandHandler.cs
  • backend/services/services-service/ServicesService.Application/Clients/UpdateClient/UpdateClientCommandValidator.cs
  • backend/services/services-service/ServicesService.Domain/Entities/Client.cs
  • backend/services/services-service/ServicesService.Domain/Entities/ClientContact.cs
  • backend/services/services-service/ServicesService.Domain/Entities/ClientGuardian.cs
  • backend/services/services-service/ServicesService.Domain/Entities/ClientReferenceContact.cs
  • backend/services/services-service/ServicesService.Infrastructure/Persistence/Configurations/ClientGuardianConfiguration.cs
  • backend/services/services-service/ServicesService.Infrastructure/Persistence/Configurations/ClientReferenceContactConfiguration.cs
  • backend/services/services-service/ServicesService.Infrastructure/Repositories/ClientRepository.cs
  • backend/services/services-service/ServicesService.PersistenceTests/ClientPersistenceTests.cs
  • backend/services/services-service/ServicesService.Tests/Clients/ClientUpdateTests.cs
  • backend/services/services-service/ServicesService.Tests/Clients/CreateClient/CreateClientCommandHandlerTests.cs
  • backend/services/services-service/ServicesService.Tests/Clients/UpdateClient/UpdateClientCommandBindingTests.cs
  • backend/services/services-service/ServicesService.Tests/Clients/UpdateClient/UpdateClientCommandHandlerTests.cs
  • backend/services/services-service/ServicesService.Tests/Clients/UpdateClient/UpdateClientCommandValidatorTests.cs
  • docs/adr/0053-clients-edit-synchronizes-contacts-by-id.md
  • docs/adr/README.md

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change adds a client update endpoint and command flow. The domain validates and synchronizes guardian and reference-contact lists by ID. Repository lookups can exclude the edited client, and persistence configuration and tests cover contact updates.

Changes

Client profile editing

Layer / File(s) Summary
Update request and validation
backend/services/services-service/ServicesService.Application/Clients/UpdateClient/*, backend/services/services-service/ServicesService.Application/Clients/ClientContactMapping.cs, backend/services/services-service/ServicesService.Application/Clients/ClientRuleBuilderExtensions.cs, backend/services/services-service/ServicesService.Application/Clients/CreateClient/*, backend/services/services-service/ServicesService.Tests/Clients/UpdateClient/UpdateClientCommandBindingTests.cs, backend/services/services-service/ServicesService.Tests/Clients/UpdateClient/UpdateClientCommandValidatorTests.cs
The update command carries client fields and optional contact lists. Validators check fields, contact IDs, list limits, and minor guardian requirements. Shared contact mapping also replaces the create flow’s local contact conversions.
Domain contact synchronization
backend/services/services-service/ServicesService.Domain/Entities/Client*.cs, backend/services/services-service/ServicesService.Tests/Clients/ClientUpdateTests.cs
Client.Update validates and synchronizes contacts by ID. It creates entries without IDs, updates matching entries, and removes omitted entries. Tests cover contact changes, validation failures, and minor guardian rules.
Update endpoint and orchestration
backend/services/services-service/ServicesService.Api/Controllers/ClientsController.cs, backend/services/services-service/ServicesService.Application/Abstractions/IClientRepository.cs, backend/services/services-service/ServicesService.Application/Clients/UpdateClient/*, backend/services/services-service/ServicesService.Infrastructure/Repositories/ClientRepository.cs, backend/services/services-service/ServicesService.Application/Clients/CreateClient/CreateClientCommandHandler.cs, backend/services/services-service/ServicesService.Tests/Clients/UpdateClient/UpdateClientCommandHandlerTests.cs, backend/services/services-service/ServicesService.Tests/Clients/CreateClient/CreateClientCommandHandlerTests.cs, .claude/skills/agenza-backend-slice/references/use-case.md
The PUT action dispatches the update command. The handler loads the client, applies the update, checks CPF and active-email uniqueness while excluding that client, and saves. Creation callers pass no excluded client ID.
Persistence configuration and verification
backend/services/services-service/ServicesService.Infrastructure/Persistence/Configurations/Client*Configuration.cs, backend/services/services-service/ServicesService.PersistenceTests/ClientPersistenceTests.cs, backend/docs/ARCHITECTURE.md, .claude/skills/agenza-backend-slice/references/persistence.md, docs/adr/0053-clients-edit-synchronizes-contacts-by-id.md, docs/adr/README.md
Guardian and reference-contact IDs are configured as not database-generated. Persistence tests cover contact loading, synchronization, tenant filtering, and excluded-client lookups. Architecture guidance and ADR 0053 document the update flow.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant ClientsController
  participant UpdateClientCommandHandler
  participant ClientRepository
  participant UpdateClientCommandExtensions
  participant Client
  participant UnitOfWork
  ClientsController->>UpdateClientCommandHandler: Dispatch UpdateClientCommand
  UpdateClientCommandHandler->>ClientRepository: Load client and contacts
  UpdateClientCommandHandler->>UpdateClientCommandExtensions: ApplyTo(command, client, today)
  UpdateClientCommandExtensions->>Client: Update profile and contact collections
  UpdateClientCommandHandler->>ClientRepository: Check CPF and active email, excluding client
  UpdateClientCommandHandler->>UnitOfWork: Save changes
Loading

Merge Risk: ⚪ Minimal · up to 6464a

The client update endpoint follows the existing tenant access policy. No issue requiring resolution before merge was established.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 6464a

Authentication, tenant isolation and contact ownership checks are preserved. However, concurrent edits can defeat the required-guardian rule and persist a minor with no remaining guardian. The exposure is limited to authorized edits within the caller’s tenant.

Retained concerns

  • Medium · reliability · inferred: Concurrent valid edits can persist a minor without a guardian. Starting with guardians G1 and G2, two requests can load the same client and submit final lists retaining only G1 and only G2. Each passes validation and soft-deletes the guardian omitted from its snapshot. Without an aggregate version check or serialization, both deletions can commit. Per-request transactions and uniqueness indexes do not contain this violation of the sensitive-record integrity invariant.
Security review details

Security Blast Radius

  • inferred — The new mutation surface concerns client profiles and owned contacts within the authenticated tenant. Under the documented tenant-wide access model, an authorized tenant user can select clients by route ID. The concurrency concern can affect multiple clients through repeated authorized edits, but the traced path does not expand authority to other tenants.

Security Findings and Attack Paths

  • inferred — A caller already authorized to edit a tenant’s client can submit overlapping valid PUT requests retaining different guardians. Independent stale-snapshot deletions can bypass the final guardian-count requirement without bypassing authentication or contact ownership. The resulting integrity failure is inferred from source and has not been reproduced against PostgreSQL.

Trust Boundaries and Controls

  • observed — The endpoint inherits JWT authentication and fallback authorization. The global tenant filter requires X-Tenant-Id to match the authenticated tenant_id claim. These mechanisms predate the PR and are unchanged. The route overrides bound client identity, EF filters scope clients and contacts, and composite foreign keys preserve tenant/client ownership.

Resilience and Maintainability Implications

  • observed — Validation runs before aggregate mutation, and the handler saves only after domain and uniqueness checks succeed. Database unique violations become a generic conflict response. The persistence test verifies retained identities and logical removal, but uses an in-memory provider; it does not establish concurrent cross-row invariant protection or interrupted-commit behavior.

Hardening Proposals

  • proposed — Protect the complete aggregate transition with serialization before loading, or an aggregate version checked and advanced for every profile or contact change. Reject stale edits or reload and revalidate the effective final guardian set under that protection; a version that changes only for profile fields would not contain contact-only races.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.63% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 158 functions across 24 files. (5 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed O título descreve a edição de pessoas atendidas e dos contatos vinculados, que é a mudança principal do PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.63% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 158 functions across 24 files. (5 skipped: 5 unsupported.)

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…te-backend

# Conflicts:
#	docs/adr/README.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant