SGEProduto · Domínio · Desenvolvimento
Repositório

technical-reference · planned

Schedules

Contrato dos comandos agendados que reconciliam a execução do estágio e vencimentos documentais.

Objetivo e limite

Os schedules tratam somente fatos que passam a valer com o tempo, mesmo sem alguém abrir o sistema. Eles reconciliam o estado persistido com a data institucional, sempre por Actions e regras de transição já usadas pela interface. Não podem fazer update em massa, pular Policies quando houver um ator humano nem inventar um usuário como autor da alteração.

O horário de referência será o APP_TIMEZONE institucional, configurado como America/Sao_Paulo antes da produção. Não haverá fuso por campus nesta primeira versão: todos os campi atendidos estão no mesmo fuso nacional. Cada comando aceita --date=AAAA-MM-DD para testes, reprocessamento controlado e investigação; sem a opção, usa a data corrente nesse fuso.

Todo processamento associado a campus deve ignorar campi desativados na seleção e conferir o estado novamente dentro da transação antes de gravar. Para evitar corrida com a desativação, bloquear primeiro o campus e depois o estágio. Durante o período inativo, schedules não mudam estados nem criam notificações ou e-mails para esses processos. Reativar não dispara um replay; a próxima execução regular segue os critérios temporais normais, sem reenviar lembretes cujo prazo já passou.

Comandos iniciais

ComandoHorárioSeleção e efeitoNão faz
internships:sync-execution-statusdiariamente, 00:05Move Released para InProgress quando a data planejada de início chegou; move InProgress para Paused se houver pausa ativa; move Paused para InProgress quando não houver mais pausa ativa.Não conclui, cancela, recalcula a previsão nem altera estágios finais.
internships:notify-projected-enddiariamente, 08:00Para estágio InProgress com projected_end_date = data + 7 dias, avisa o discente e monta o resumo interno do Setor de Estágio.Não conclui o estágio, não altera a previsão nem envia e-mail ao Setor.

Os horários são deliberadamente separados: o aviso de término roda depois da sincronização de início, pausa e retomada. Em produção, os dois comandos serão registrados em routes/console.php com dailyAt, onOneServer() e withoutOverlapping(). O servidor executará php artisan schedule:run a cada minuto; schedule:work é apenas a alternativa de desenvolvimento local.

Regra de sincronização de execução

A Action SynchronizeInternshipExecutionStatus recebe um estágio bloqueado e uma data de referência. Ela consulta a pausa cujo intervalo inclusivo contém a data (starts_at <= data <= ends_at) e aplica somente uma das regras abaixo, nesta ordem de elegibilidade.

Estado atualCondiçãoEstado resultante
Releasedplanned_start_date <= dataInProgress
InProgresshá uma pausa ativa na dataPaused
Pausednão há pausa ativa na dataInProgress
PendingFormalization, AwaitingSignatures, PendingCorrection, Completed ou Cancelledqualquer condiçãosem alteração

Uma pausa pertence à execução: só pode ser cadastrada para estágio já InProgress, tem starts_at, ends_at e reason obrigatórios e não pode começar antes de planned_start_date. Assim, não existe transição direta de Released para Paused nem uma pausa prévia que oculte a entrada em andamento.

Completed continua uma decisão de negócio, não uma consequência do calendário. Mesmo que a previsão de término tenha chegado, o estágio só é concluído quando a carga horária integral é confirmada; avaliações e notas permanecem em trilha acadêmica separada. O cálculo da previsão também não é agendado: ele é refeito na Action que muda data de início, jornada, calendário ou pausa e persiste sua base reproduzível.

Auditoria, concorrência e idempotência

Cada transição de status feita por schedule é processada individualmente, em transação curta, com lockForUpdate() no estágio. A consulta externa usa chunkById; o estado e as condições sempre são lidos novamente dentro do lock. Nessas transições, as propriedades do activity_log registram ao menos:

  • source: scheduled, nome do comando e data de referência;
  • status anterior e resultante, ou motivo de não haver alteração;
  • internship_pause_id, quando aplicável; e
  • identificador técnico da execução para correlação operacional.

O causer é nulo ou representa explicitamente o sistema, nunca o Setor de Estágio ou outro usuário sem ação humana. O lembrete de término previsto não cria Activity Log: a notificação deduplicada, os snapshots de e-mail e a telemetria do command são sua trilha operacional. Cancelamento, correção e alteração de estado continuam exclusivamente manuais e registrados no Activity Log. Falhas de uma unidade não interrompem as demais; elas são registradas e tornam o comando inelegível como sucesso para o monitoramento.

O lock distribuído de onOneServer() depende do cache compartilhado já previsto. withoutOverlapping() deve ter TTL acima da duração observada do comando, com alerta se o lock ficar retido. A operação de produção monitora falha do scheduler, comandos atrasados, duração anormal e quantidade de estágios processados; não deve corrigir registros diretamente no banco.

Notificações dos eventos temporais

A própria Action cria a notificação interna na mesma transação da transição. Para o discente, o Job de e-mail é despachado após o commit; a falha desse Job nunca remove o aviso dentro do sistema. O Setor de Estágio não recebe um e-mail nem uma notificação por estágio: às 08:00 recebe uma única notificação interna por campus e destinatário, com contagens e links filtrados. A fila trata tentativas, backoff e falhas conforme E-mails, notificações e entregas.

EventoDiscenteSetor de Estágio
entrada automática em andamentonotificação interna e e-mailentra no resumo diário.
início ou término de pausanotificação interna e e-mailentra no resumo diário.
previsão de término a sete diasnotificação interna e e-mail, uma única vezentra no resumo diário.

O lembrete de término usa exclusivamente a projected_end_date vigente, porque ela já incorpora jornada, calendário, exceções e pausas. A chave estável do aviso inclui estágio, data projetada vigente, limiar de sete dias e destinatário. Assim, a rotina diária só envia uma vez para a mesma previsão; se um evento autorizado alterar a data, a mudança relevante da previsão é comunicada pelo próprio fluxo de alteração, não por uma sequência de lembretes.

O resumo do Setor agrega, no mínimo, estágios iniciados, pausados ou retomados no dia, estágios com término previsto em sete dias e estágios cuja previsão já passou sem conclusão. Ele é somente interno e não expõe dados sensíveis na listagem resumida.

Cada destinatário recebe no máximo uma notificação por fato temporal. A Action usa uma chave estável que combina evento, estágio, data efetiva e destinatário. Quando houver e-mail, essa chave participa de email_messages.idempotency_key. Para o resumo somente interno, a implementação deve definir antes um registro operacional durável de despacho; a tabela nativa notifications não deve receber colunas próprias para essa finalidade.

Testes obrigatórios

  • congelar a data institucional e testar início, pausa ativa, término de pausa e todos os estados que devem permanecer imutáveis;
  • executar cada comando duas vezes para provar idempotência de status, Activity Log, correção, notificação e e-mail;
  • testar concorrência entre alteração manual de pausa e schedule, incluindo releitura sob lock;
  • garantir que Completed nunca seja produzido pelo command e que a previsão só seja recalculada pelos gatilhos de domínio;
  • testar lembrete exatamente a sete dias, nova previsão após mudança autorizada e ausência de aviso fora do limiar;
  • testar resumo único interno por campus/destinatário, sem email_message para o Setor;
  • testar despacho de e-mail do discente somente após commit, destinatário sem canal de e-mail e a idempotência da fonte de despacho;
  • testar que um campus desativado é ignorado antes da gravação e revalidado dentro do lock, sem status, notificação ou e-mail; e
  • executar em ambiente com cache compartilhado e validar onOneServer, withoutOverlapping e os alertas operacionais.

Relações