{
  "id": "migration-23-internship-work-schedules",
  "title": "Migration 23 — internship_work_schedules",
  "description": "Vigências da jornada criadas por aditivos após a jornada inicial do estágio.",
  "type": "migration-reference",
  "status": "implemented",
  "visibility": "public",
  "tags": [
    "sge/migrations",
    "sge/jornada",
    "sge/calculos"
  ],
  "related": [
    "migration-15-internships",
    "migration-16-generated-documents",
    "migration-17-internship-pauses",
    "migration-22-holidays",
    "migration-22-internship-calendar-overrides",
    "service-internshipenddatecalculator"
  ],
  "sourceRefs": [],
  "authors": [],
  "updated": null,
  "diagram": null,
  "body": "> [!success] Estado\n> Migration, Model, factory e relações implementados. A jornada inicial permanece em `internships.weekly_hours`; a aplicação transacional do aditivo e o recálculo ainda pertencem ao fluxo funcional.\n\n## Contrato da tabela\n\n| Coluna | Tipo SQL | Nulável | Índice/constraint | Regra |\n| --- | --- | --- | --- | --- |\n| `id` | `bigint` | não | `PK` | Identificador da vigência da jornada. |\n| `internship_id` | `bigint` | não | `FK`, índice | Referencia `internships.id`; exclusão `RESTRICT`. |\n| `starts_on` | `date` | não | índice composto | Primeiro dia inclusivo desta distribuição de horas. |\n| `ends_on` | `date` | sim | índice composto | Último dia inclusivo; nulo apenas na vigência atual. |\n| `weekly_hours` | `jsonb` | não | — | Objeto com as sete chaves de dia da semana e inteiros não negativos em horas. |\n| `generated_document_id` | `bigint` | não | `FK`, índice | Referencia o aditivo formalizado em `generated_documents.id`. |\n\n| `created_at` | `timestamp(0)` | não | — | Instante de criação. |\n| `updated_at` | `timestamp(0)` | não | — | Instante da última alteração técnica permitida. |\n\n`internships.weekly_hours` vale desde `planned_start_date` até o dia anterior ao primeiro aditivo efetivado. As linhas desta tabela representam apenas vigências posteriores; a aplicação impede sobreposição e lacunas entre elas. Cada nova vigência começa no dia seguinte ao encerramento da anterior.\n\nO Model valida que `ends_on` seja nulo ou maior/igual a `starts_on`, que `weekly_hours` contenha exatamente os dias esperados e que cada valor seja inteiro não negativo dentro dos limites do snapshot. Também verifica a referência a um aditivo assinado do mesmo estágio, sobreposição e continuidade com a vigência anterior. A Action ainda deverá serializar essas mudanças com lock no estágio; não há SQL bruto nem `CHECK` adicional na migration.\n\n## Regras de negócio\n\nAo aceitar a solicitação, o SGE grava a jornada inicial em `internships.weekly_hours`, com início em `planned_start_date`. Durante a execução não existe edição ordinária nem mudança temporária de carga horária: pausas apenas suspendem o cômputo. Se for necessário alterar a distribuição de horas, o Setor gera um aditivo e só aplica a nova jornada depois de conferir as assinaturas. A primeira linha delimita implicitamente a jornada inicial até o dia anterior; as linhas seguintes encerram a vigência anterior no dia precedente. Nunca se recalculam dias passados usando a distribuição nova.\n\nO documento principal usa `internships.weekly_hours`. O aditivo referencia a vigência que ele formaliza. Toda alteração efetivada por aditivo recalcula `internships.projected_end_date` usando a jornada correta para cada data, o calendário de feriados nacional/estadual/municipal persistido aplicável ao endereço do local de trabalho, as pausas e a margem definida no snapshot do tipo de estágio.\n\n`weekly_hours` não registra horário de entrada e saída. Tanto no estágio quanto nesta tabela, ela informa somente quantas horas são creditadas em cada dia da semana. A quantidade exigida e os limites ordinários ou excepcionais continuam no snapshot de `internship_type_snapshot`.\n\n## Integridade histórica\n\nA jornada inicial e uma vigência posterior que já participaram de cálculo, documento ou auditoria não podem ser apagadas nem ter suas horas reescritas. Correções contratuais criam uma nova vigência e preservam a anterior. Alterações em `updated_at` não devem mudar a distribuição histórica; na prática, o Model deve tratar a linha como imutável depois de utilizada.\n\nUma pausa não é uma jornada e não altera `weekly_hours`. Uma exceção de feriado também não é uma jornada: fica em [`internship_calendar_overrides`](doc:migration-22-internship-calendar-overrides). A lista de dias trabalhados é derivada no momento da consulta a partir das vigências, pausas, feriados e exceções; não há tabela materializada de dias individuais.\n\n## Checklist\n\n- [x] Criar migration `create_internship_work_schedules_table` com tipos SQL, FKs e índices.\n- [x] Criar Model com cast de `weekly_hours` e relações para estágio e documento; autoria no Activity Log.\n- [x] Validar exatamente as sete chaves de `weekly_hours` e os limites do snapshot do tipo no Model.\n- [x] Rejeitar sobreposição e lacuna com a vigência anterior no Model.\n- [ ] Garantir serialização concorrente das alterações na Action transacional.\n- [ ] Exigir aditivo e assinaturas conferidas para cada vigência posterior.\n- [x] Impedir reescrita da jornada e exclusão física pelo Model; permitir apenas encerramento único da vigência.\n- [ ] Recalcular a previsão somente após a nova vigência ser efetivada.\n- [ ] Testar jornada inicial em `internships`, aditivo, vigência encerrada, sobreposição, lacuna, pausa e reprocessamento idempotente.\n- [x] Testar migrate/rollback na ordem completa.\n\n## Dependências\n\n- [internships](doc:migration-15-internships)\n- [generated_documents](doc:migration-16-generated-documents)\n- [internship_pauses](doc:migration-17-internship-pauses)\n- [holidays](doc:migration-22-holidays)\n- [internship_calendar_overrides](doc:migration-22-internship-calendar-overrides)\n- [Service — InternshipEndDateCalculator](doc:service-internshipenddatecalculator)",
  "sections": [
    {
      "id": "contrato-da-tabela",
      "level": 2,
      "title": "Contrato da tabela",
      "text": "| Coluna | Tipo SQL | Nulável | Índice/constraint | Regra | | --- | --- | --- | --- | --- | | `id` | `bigint` | não | `PK` | Identificador da vigência da jornada. | | `internship_id` | `bigint` | não | `FK`, índice | Referencia `internships.id`; exclusão `RESTRICT`. | | `starts_on` | `date` | não | índice composto | Primeiro dia inclusivo desta distribuição de horas. | | `ends_on` | `date` | sim | índice composto | Último dia inclusivo; nulo apenas na vigência atual. | | `weekly_hours` | `jsonb` | não | — | Objeto com as sete chaves de dia da semana e inteiros não negativos em horas. | | `generated_document_id` | `bigint` | não | `FK`, índice | Referencia o aditivo formalizado em `generated_documents.id`. |  | `created_at` | `timestamp(0)` | não | — | Instante de criação. | | `updated_at` | `timestamp(0)` | não | — | Instante da última alteração técnica permitida. |  `internships.weekly_hours` vale desde `planned_start_date` até o dia anterior ao primeiro aditivo efetivado. As linhas desta tabela representam apenas vigências posteriores; a aplicação impede sobreposição e lacunas entre elas. Cada nova vigência começa no dia seguinte ao encerramento da anterior.  O Model valida que `ends_on` seja nulo ou maior/igual a `starts_on`, que `weekly_hours` contenha exatamente os dias esperados e que cada valor seja inteiro não negativo dentro dos limites do snapshot. Também verifica a referência a um aditivo assinado do mesmo estágio, sobreposição e continuidade com a vigência anterior. A Action ainda deverá serializar essas mudanças com lock no estágio; não há SQL bruto nem `CHECK` adicional na migration.",
      "line": 4
    },
    {
      "id": "regras-de-negocio",
      "level": 2,
      "title": "Regras de negócio",
      "text": "Ao aceitar a solicitação, o SGE grava a jornada inicial em `internships.weekly_hours`, com início em `planned_start_date`. Durante a execução não existe edição ordinária nem mudança temporária de carga horária: pausas apenas suspendem o cômputo. Se for necessário alterar a distribuição de horas, o Setor gera um aditivo e só aplica a nova jornada depois de conferir as assinaturas. A primeira linha delimita implicitamente a jornada inicial até o dia anterior; as linhas seguintes encerram a vigência anterior no dia precedente. Nunca se recalculam dias passados usando a distribuição nova.  O documento principal usa `internships.weekly_hours`. O aditivo referencia a vigência que ele formaliza. Toda alteração efetivada por aditivo recalcula `internships.projected_end_date` usando a jornada correta para cada data, o calendário de feriados nacional/estadual/municipal persistido aplicável ao endereço do local de trabalho, as pausas e a margem definida no snapshot do tipo de estágio.  `weekly_hours` não registra horário de entrada e saída. Tanto no estágio quanto nesta tabela, ela informa somente quantas horas são creditadas em cada dia da semana. A quantidade exigida e os limites ordinários ou excepcionais continuam no snapshot de `internship_type_snapshot`.",
      "line": 22
    },
    {
      "id": "integridade-historica",
      "level": 2,
      "title": "Integridade histórica",
      "text": "A jornada inicial e uma vigência posterior que já participaram de cálculo, documento ou auditoria não podem ser apagadas nem ter suas horas reescritas. Correções contratuais criam uma nova vigência e preservam a anterior. Alterações em `updated_at` não devem mudar a distribuição histórica; na prática, o Model deve tratar a linha como imutável depois de utilizada.  Uma pausa não é uma jornada e não altera `weekly_hours`. Uma exceção de feriado também não é uma jornada: fica em [`internship_calendar_overrides`](doc:migration-22-internship-calendar-overrides). A lista de dias trabalhados é derivada no momento da consulta a partir das vigências, pausas, feriados e exceções; não há tabela materializada de dias individuais.",
      "line": 30
    },
    {
      "id": "checklist",
      "level": 2,
      "title": "Checklist",
      "text": "- [x] Criar migration `create_internship_work_schedules_table` com tipos SQL, FKs e índices. - [x] Criar Model com cast de `weekly_hours` e relações para estágio e documento; autoria no Activity Log. - [x] Validar exatamente as sete chaves de `weekly_hours` e os limites do snapshot do tipo no Model. - [x] Rejeitar sobreposição e lacuna com a vigência anterior no Model. - [ ] Garantir serialização concorrente das alterações na Action transacional. - [ ] Exigir aditivo e assinaturas conferidas para cada vigência posterior. - [x] Impedir reescrita da jornada e exclusão física pelo Model; permitir apenas encerramento único da vigência. - [ ] Recalcular a previsão somente após a nova vigência ser efetivada. - [ ] Testar jornada inicial em `internships`, aditivo, vigência encerrada, sobreposição, lacuna, pausa e reprocessamento idempotente. - [x] Testar migrate/rollback na ordem completa.",
      "line": 36
    },
    {
      "id": "dependencias",
      "level": 2,
      "title": "Dependências",
      "text": "- [internships](doc:migration-15-internships) - [generated_documents](doc:migration-16-generated-documents) - [internship_pauses](doc:migration-17-internship-pauses) - [holidays](doc:migration-22-holidays) - [internship_calendar_overrides](doc:migration-22-internship-calendar-overrides) - [Service — InternshipEndDateCalculator](doc:service-internshipenddatecalculator)",
      "line": 49
    }
  ],
  "sourcePath": "content/migration-23-internship-work-schedules.md",
  "visuals": [],
  "apiVersion": 1
}
