Reforma dos lotes de inscrição: lotes gerais seguidos pelas categorias, overrides por categoria, períodos de datas, limites por lote e histórico do lote por participante. Abaixo: o que já existe no código, os riscos mapeados e as perguntas que destravam o modelo.
Hoje o lote é um acréscimo (% ou fixo) sobre o preço base, com modo exclusivo (General ou PerCategory), limite e data final — sem data de início. Não há "geral seguido por categoria" nem override.
Semântica de preço, auto-avanço na virada, precedência geral × categoria, pendente segurando vaga, fuso horário e o que o site mostra. As respostas definem as tabelas e a lógica de resolução.
Fuso (UTC vs Brasília) na virada · pendente segura vaga do lote · 1 lote por participante (multi-categoria) · sem GET de lotes (e preço base no site diverge do cobrado) · edits do admin sem guardrails.
Resumo do estado real do código (não da intenção):
| Peça | Como está |
|---|---|
PriceBatch | Acréscimo (% ou fixo) sobre o preço base da categoria — "lote nunca dá desconto". Campos: nome, %/fixo, ParticipantLimit, EndDate, DisplayOrder, IsActive. Sem data de início. |
| Modos | BatchPriceMode: None / General / PerCategory — exclusivos: ou lote geral, ou por categoria; nunca convivendo. |
| Resolução | Primeiro lote ativo e não esgotado por DisplayOrder; sem vigente → 409 Event.BatchSoldOut; sem lotes configurados → preço base. |
| Snapshot | Participante guarda BatchId + BatchName — um só por participante ("primeiro lote com ajuste" entre as categorias). |
| Deleção | Bloqueia por vendas (pendente conta; cancelado some da contagem). Sem regra de "só futuro". |
| API admin | PUT /batches/settings, POST, PUT, DELETE — não existe GET/listagem (nem usados/restantes/vigente). |
| Público | ShowBatchesPublicly é flag morta (nunca lida); GET /events/{id} mostra o preço base — diverge do cobrado no lote. |
| Cancelamento | O registro de cancelado não guarda o lote. |
| Concorrência | Compra pega advisory lock do evento; edição/deleção de lote não pega lock nenhum. |
Onde o requisito já tem base e onde o modelo atual não suporta:
| Decisão da reunião | Hoje | Situação |
|---|---|---|
| Lote geral é seguido pelas categorias | Modo é XOR — geral e categoria nunca coexistem | Não suporta |
| Categoria pode ter lote próprio que sobrepõe o geral | Não existe cadeia de precedência | Não suporta |
| Lotes com período (datas) + limite próprio | Tem EndDate + limite; sem StartDate; sem validação de sobreposição | Parcial |
| Último lote sem data/limite rígido | "Sem dados" funciona, mas não há noção de "último" nem corte explícito | Parcial |
| Salvar nome e tipo do lote no participante | Salva só nome; tipo não existe; 1 lote por participante (errado p/ multi-categoria) | Parcial |
| Registrar o lote no cancelamento | Registro de cancelado não tem campos de lote | Não existe |
| Excluir apenas lotes futuros | Regra atual é "sem vendas"; sem conceito de futuro sem StartDate | Não existe |
| Desativar lote "sem utilidade" | IsActive existe e interfere na resolução | Existe a mais |
| Endpoints para resgatar dados | Sem GET de lotes; sem usados/restantes/status | Não existe |
| Interface agrupada na edição do evento | Backend sem listagem para a tela montar | Bloqueado |
Ordenados por severidade — cada um ancorado no código atual:
Hoje o lote é acréscimo sobre a base; a reunião fala em "preço de cada categoria por lote". Se for valor final, muda modelo, UI, voucher, relatórios e contrato do front. Decidir antes de codar.
Comparações usam DateTime.UtcNow. Se o front/planilha mandar "23:59" sem offset, o lote vira 3h mais cedo (21h BRT). Precisa regra explícita de fuso na API e na UI.
SetBatch grava o primeiro lote resolvido entre as categorias. Com lote específico por categoria, o dado de auditoria/relatório fica errado por design. O correto é guardar por categoria do participante.
Carrinho não pago conta no limite; a expiração roda a cada 10 min (60 min em prod) → lote esgota "sem dinheiro". Quando expira/cancela, a vaga volta e o lote mais barato pode reabrir para quem chegar depois (preço regride).
Re-precifica pelo preço base e não limpa BatchName → cobra/credita errado e o Excel mostra lote antigo com preço novo. Limitação já conhecida e documentada.
UpdateEventCommand passa hasPaidInscriptions: false e dribla o guard do domínio; como o lote é % sobre a base, isso muda o preço das próximas vendas de um lote vigente silenciosamente.
Usuário vê "Lote 1 R$100", paga R$120 ou recebe 409 genérico. Com virada por data/limite isso será frequente — precisa UX e erro amigáveis ("lote esgotou, preço atualizado para X").
A tela administrativa prometida não tem de onde ler; e GET /events/{id} devolve o preço base enquanto a cobrança usa o lote. ShowBatchesPublicly nunca é lida.
A compra tranca o evento (advisory lock); o admin não. Editar/criar/deletar em paralelo com vendas = lost update/estado inconsistente.
Não há definição de "futuro" hoje. E como cancelado some da contagem, um lote pode voltar a ser deletável depois que todos cancelarem.
O behavior de transação só dá rollback em exceção — validações novas de lote não podem mutar estado antes de retornar erro (ex.: contadores/trackers).
A cotação bloqueia (blocked), o preview de voucher cai para o preço base silenciosamente e a criação devolve 409. Padronizar.
RequiresBatch conta inativos como "configurados"; não existe erro NoBatch distinto de sold-out.
batch_id sem FK + cascade de categoriaDeletar categoria cascateia lotes e pode deixar ids órfãos no participante.
Sem testes para lote no add participants, troca de modo, visibilidade pública e regra "só futuros"; vários testes atuais vão quebrar por design.
Só 2 linhas no wiki sobre lotes; zero no book/README. A regra do repo exige docs na mesma entrega.
As destrava o modelo são as que precisam de resposta antes de qualquer código — elas definem tabelas e a lógica de virada.
Dica: dá pra responder direto neste artefato no canal com o Jonathan — as marcadas com destrava o modelo são as únicas bloqueantes para começar o desenho técnico.