AS IS — US 198113 | Inativação de usuário (job)
Data: 2026-07-09
Repo principal: coezzion-service-jobs
US: context/us.md
1. Problema descrito na US
A rotina de inativação hoje:
- grava
DateDeletedemUsers - altera
StatusemUsers
e não:
- grava
DateDeletedemSellers - remove vínculos em
UserOrganization
Consequência: usuário deixa de acessar o sistema, mas continua referenciado como vendedor e em organizações.
2. Rotina no jobs que casa com a US
2.1 DeleteUserAsync
Arquivo: coezzion-service-jobs/src/Jobs.Infrastructure/Data/Repositories/UserRepository.cs
Interface: Jobs.Domain/Interfaces/Data/Repositories/IUserRepository.cs
public async Task DeleteUserAsync(int userId)
{
var sql = """
update "Users"
set "DateDeleted" = NOW() AT TIME ZONE 'America/Sao_Paulo'
, "Status" = 2
where "Id" = @userId;
""";
using var connection =
await _dbConnectionFactory.GetConnectionAsync(EConnectionType.Write, EDatabaseType.CoreDb);
var result = await connection.ExecuteAsync(sql, new { userId });
}
| Aspecto | Valor |
|---|---|
| Stack | Dapper + IDbConnectionFactory |
| DB | Write / CoreDb (PostgresCoreDb → CoreDB) |
| Status | 2 = EntityStatus.Inactive |
| Timezone | America/Sao_Paulo |
| Sellers | não tocado |
| UserOrganization | não tocado |
| Schema param | não recebe (caller tem schema, não passa) |
2.2 Caller único no jobs
Handler: Jobs.API/Application/Messages/Commands/LogoutUsersCommands/LogoutUsersCommandHandler.cs
Job: LogoutOldAppVersionUsersJob → command com argument.Schema
Scheduler: JobScheduler — LogoutOldAppVersionUsersJobArgument("arezzo")
Fluxo:
Hangfire recurrent: LogoutOldAppVersionUsers
→ SchemaNameScopedWrapper = "arezzo"
→ GetOldAppVersionUsersAsync(schema, daysBack=30)
→ filtro: LastLogin <= hoje - 60 dias → canDeleteUser
→ se canDeleteUser: DeleteUserAsync(userId) // só Users
→ sempre: LogoutUserEvent → SQS (desloga sessão)
Constantes do handler:
DaysBack = 30(seleção de candidatos por app version / login)MaxDaysToConsider = 60(critério para deletar vs só logout)MaxDegreeOfParallelism = 3
2.3 Job correlato (não é a rotina da US)
LogoutDeletedUserJob: varre refresh tokens no Redis; se IsUserActive(userId) for false, remove tokens.
Não inativa usuários — apenas reage a users já inativos. Sem mudança obrigatória se Status continuar sendo setado.
3. Modelagem de dados
| Conceito | Tabela | Soft/Hard | Observação |
|---|---|---|---|
| User | public."Users" | Soft (DateDeleted) + Status Active=1 / Inactive=2 | UserModel : BaseEntity |
| UserOrganization | public."UserOrganization" | Hard delete das linhas | Join N:N EF; colunas UsersId, OrganizationsId; sem DateDeleted |
| Seller | {tenant}."Sellers" | Soft (DateDeleted) | SellerModel : BaseEntity; UserId, StoreId, voucher, etc. Sem Status |
BaseEntity.Delete() (coezzion-nuget-common): só seta DateDeleted.
UserModel.SetDateDeleted(): base.Delete() + UserForceLogoutEvent + DateUpdated.
UserModel.UpdateStatus: só muda Status.
Mapeamento UO: Core.DB/Data/Mappings/UsersMapping.cs — .UsingEntity(x => x.ToTable("UserOrganization")).
4. Multi-tenancy e banco
| Connection string (dev) | Database | Uso |
|---|---|---|
PostgresCoreDb | CoreDB | Users, UserOrganization, Dapper delete |
PostgresCoreDbOrg | CoreDB + SearchPath={0} | Sellers/Stores via EF multi-tenant |
Conclusão: public.Users, public.UserOrganization e {schema}.Sellers estão no mesmo Postgres CoreDB. Uma connection Write/CoreDb pode atualizar os três schemas em uma transaction.
Jobs já fazem joins cross-schema em uma connection (ex.: Users + SellersVacations, Sellers + Users).
5. Caminhos paralelos (admin / store) — contraste
| Caminho | Serviço | Users | Sellers | UserOrganization |
|---|---|---|---|---|
DeleteUserAsync | jobs | DateDeleted + Status=2 | ❌ | ❌ |
DELETE api/user → DeleteUserCommand | admin | user.Delete() → só DateDeleted | ❌ | ❌ |
UpdateStatusUserCommand | admin | só UpdateStatus | ❌ (evento vacation se vendedor) | ❌ |
UpdateUserCommand Status=Inactive | admin | SetDateDeleted + Status | ✅ via Store PUT /api/seller/all-by-userId | só se orgs mudarem no payload |
UpdateSellersByUserCommand | store | — | soft-delete Sellers por loja/user | — |
A descrição literal da US (DateDeleted e Status na mesma operação, sem Sellers/UO) casa com o jobs, não com admin Delete.
6. Efeito prático do gap
Após DeleteUserAsync atual:
- Login bloqueado (Status/DateDeleted;
LogoutDeletedUserJoblimpa token). - Linhas em
UserOrganizationpermanecem. - Linhas em
{schema}.SellerscomDateDeleted IS NULLpermanecem → vendedor “fantasma” onde queries filtram só uma das dimensões.
7. Fora de escopo desta US (residual documentado)
- Completar admin
DeleteUser/UpdateStatusUser - Limpeza de users já inativados no passado com Sellers/UO órfãos
- Reativação de usuário
- Projeto de testes automatizados no jobs (não existe na solution atual)