Skip to main content

Plano — US 198113 | Inativação completa no job de users

Data: 2026-07-09
Workitem: 198113
US: zzapp | Ajuste na rotina de desativação de usuário
Repo: coezzion-service-jobs
Decisões: context/grill-decisions.md
AS IS: context/as-is-analysis.md
Pesquisa stack: context/dapper-connection-research.md
Glossário: context/CONTEXT.md


1. Resumo

O job LogoutOldAppVersionUsers inativa usuários antigos de app via DeleteUserAsync, que hoje só faz:

UPDATE "Users"
SET "DateDeleted" = ..., "Status" = 2
WHERE "Id" = @userId;

Falta (US):

  1. Soft-delete em {schema}."Sellers" (DateDeleted)
  2. DELETE em public."UserOrganization" para o user

Escopo: apenas o jobs (DeleteUserAsync + caller). Admin/store e limpeza legada ficam de fora.


2. Causa raiz (AS IS)

TabelaEsperado (inativação completa)AS IS no job
public."Users"DateDeleted + Status=Inactive
{schema}."Sellers"DateDeleted
public."UserOrganization"remoção dos vínculos

Caller: LogoutUsersCommandHandler → único uso de DeleteUserAsync.
Schema do job: "arezzo" (argument Hangfire).
Banco: CoreDB único para Users, UserOrganization e Sellers (schemas distintos).


3. Decisões de design

#Escolha
EscopoSó jobs DeleteUserAsync
Schema Sellersrequest.Schema do job
StackDapper + IDbConnectionFactory Write/CoreDb
AtomicidadeMulti-statement SQL + BeginTransaction
EFNão
LegadoNão limpar já-inativados
IdempotênciaWHERE DateDeleted IS NULL em Users/Sellers
User sem Seller0 rows = sucesso
Logout SQSManter após delete ok
TestesChecklist SQL manual em staging

4. Mudanças no backend (coezzion-service-jobs)

4.1 IUserRepository

Arquivo: src/Jobs.Domain/Interfaces/Data/Repositories/IUserRepository.cs

// Antes:
Task DeleteUserAsync(int userId);

// Depois:
Task DeleteUserAsync(int userId, string schema);

4.2 UserRepository.DeleteUserAsync

Arquivo: src/Jobs.Infrastructure/Data/Repositories/UserRepository.cs

  1. Validar schema com ^[a-zA-Z_][a-zA-Z0-9_]*$ (lançar se inválido — antes de abrir tx).
  2. Connection: GetConnectionAsync(EConnectionType.Write, EDatabaseType.CoreDb).
  3. Open + BeginTransaction.
  4. Um ExecuteAsync com:
UPDATE public."Users"
SET "DateDeleted" = NOW() AT TIME ZONE 'America/Sao_Paulo',
"Status" = 2
WHERE "Id" = @userId
AND "DateDeleted" IS NULL;

UPDATE {schema}."Sellers"
SET "DateDeleted" = NOW() AT TIME ZONE 'America/Sao_Paulo'
WHERE "UserId" = @userId
AND "DateDeleted" IS NULL;

DELETE FROM public."UserOrganization"
WHERE "UsersId" = @userId;
  1. Commit / em falha Rollback e rethrow.
  2. Não setar Status em Sellers (coluna inexistente no modelo).
  3. Manter timezone BR igual ao AS IS.

Notas de implementação:

  • Handler usa Parallel.ForEachAsync (DOP 3): cada chamada abre connection/tx própria.
  • Coluna de vínculo: "UsersId" (não UserId) em UserOrganization.
  • Qualificar public."Users" explicitamente (connection CoreDb sem SearchPath de tenant).

4.3 LogoutUsersCommandHandler

Arquivo: src/Jobs.API/Application/Messages/Commands/LogoutUsersCommands/LogoutUsersCommandHandler.cs

// Antes:
await userRepository.DeleteUserAsync(user.UserId);

// Depois:
await userRepository.DeleteUserAsync(user.UserId, request.Schema);

Manter: critério canDeleteUser, log CSV, LogoutUserEvent após delete ok, tratamento de exception por user.

4.4 Não alterar

  • LogoutDeletedUserJob (já limpa token de users inativos)
  • Admin DeleteUser / UpdateStatusUser / UpdateUser
  • Store UpdateSellersByUser (referência de regra, não chamada HTTP)
  • Job de backfill legado

5. Arquivos críticos

ArquivoAção
Jobs.Domain/.../IUserRepository.csAssinatura + schema
Jobs.Infrastructure/.../UserRepository.csSQL atômico Dapper + tx + validação schema
Jobs.API/.../LogoutUsersCommandHandler.csPassar request.Schema

Reuse: IDbConnectionFactory (Coezzion.Common), padrão multi-schema SQL já usado em reads do jobs.


6. Verification (checklist manual / staging)

Pré-condição

User de teste com:

  • public."Users" ativo (Status=1, DateDeleted null)
  • ≥1 arezzo."Sellers" com DateDeleted null
  • ≥1 public."UserOrganization"

Pós-execução (DeleteUserAsync / job)

SELECT "Id", "Status", "DateDeleted"
FROM public."Users" WHERE "Id" = @id;
-- expect: Status = 2, DateDeleted IS NOT NULL

SELECT "Id", "UserId", "StoreId", "DateDeleted"
FROM arezzo."Sellers" WHERE "UserId" = @id;
-- expect: todas com DateDeleted IS NOT NULL

SELECT COUNT(*) FROM public."UserOrganization" WHERE "UsersId" = @id;
-- expect: 0

Cenários extras

CasoExpectativa
User sem SellerUsers + UO ok; Sellers 0 rows
Reexecuçãosem erro; UO = 0
Schema inválidoexception antes/sem commit parcial
Logout SQSevento ainda enviado quando delete ok

7. Residual / follow-ups (fora desta US)

  1. Admin DeleteUser não seta Status nem limpa Sellers/UO
  2. Users já inativos com Sellers/UO órfãos (sem backfill)
  3. Projeto de testes no jobs (hoje inexistente)

8. Ordem de implementação sugerida

  1. Alterar interface IUserRepository
  2. Implementar DeleteUserAsync com validação + tx + SQL batch
  3. Atualizar LogoutUsersCommandHandler
  4. Validar checklist SQL em staging
  5. Deploy jobs