{
  "id": "migration-10-course-id-em-affiliations",
  "title": "Migration 10 — course_id em affiliations",
  "description": "Adiciona o curso obrigatório ao vínculo de discente sem criar ciclo de FK.",
  "type": "migration-reference",
  "status": "implemented",
  "visibility": "public",
  "tags": [
    "sge/migrations",
    "sge/autorizacao",
    "sge/cursos"
  ],
  "related": [
    "migration-09-courses",
    "enum-affiliationtype",
    "migration-04-affiliations"
  ],
  "sourceRefs": [
    "https://github.com/sge-suite/sge/blob/master/database/migrations/2026_09_22_163558_add_course_id_to_affiliations_table.php",
    "https://github.com/sge-suite/sge/blob/master/app/Models/Affiliation.php",
    "https://github.com/sge-suite/sge/blob/master/app/Concerns/AffiliationValidationRules.php",
    "https://github.com/sge-suite/sge/blob/master/database/factories/AffiliationFactory.php",
    "https://github.com/sge-suite/sge/blob/master/tests/Feature/CourseTest.php"
  ],
  "authors": [],
  "updated": null,
  "diagram": null,
  "body": "> [!success] Estado\n> Migration, validação no Model, factory, relações e testes PostgreSQL implementados após [`courses`](doc:migration-09-courses).\n\n## Contrato\n\n`course_id` é `bigint` nullable, FK para `courses.id` com `ON DELETE RESTRICT`. `AffiliationValidationRules` exige o campo para `AffiliationType::Student` e o proíbe para os demais tipos. O curso escolhido deve pertencer ao mesmo campus do vínculo e estar ativo na atribuição ou reativação. Uma desativação posterior do curso não apaga o vínculo histórico nem bloqueia alterações que não mudem sua associação. Cada curso adicional da mesma conta exige outro vínculo de discente. A Migration 04 não contém essa FK porque `courses` precisa ser criada antes; não há `CHECK` de domínio nem índice secundário explícito nesta etapa.\n\n## Checklist\n\n- [x] Adicionar coluna `course_id` e adiar índice secundário para análise das consultas reais.\n- [x] Restringir a exclusão física do curso referenciado.\n- [x] Aplicar obrigatoriedade condicional e coerência de campus no Model, sem `CHECK` de domínio.\n- [x] Atualizar Model `Affiliation` e relação com `Course`.\n- [x] Atualizar factory com vínculo discente por curso.\n- [x] Testar discente sem curso, servidor com curso indevido e dois cursos para a mesma pessoa.\n- [x] Testar escopo: curso e vínculo devem pertencer ao mesmo campus.\n- [x] Testar migrate/rollback na ordem completa.\n\n## Dependências\n\n- [AffiliationType](doc:enum-affiliationtype)\n- [affiliations](doc:migration-04-affiliations)\n- [courses](doc:migration-09-courses)",
  "sections": [
    {
      "id": "contrato",
      "level": 2,
      "title": "Contrato",
      "text": "`course_id` é `bigint` nullable, FK para `courses.id` com `ON DELETE RESTRICT`. `AffiliationValidationRules` exige o campo para `AffiliationType::Student` e o proíbe para os demais tipos. O curso escolhido deve pertencer ao mesmo campus do vínculo e estar ativo na atribuição ou reativação. Uma desativação posterior do curso não apaga o vínculo histórico nem bloqueia alterações que não mudem sua associação. Cada curso adicional da mesma conta exige outro vínculo de discente. A Migration 04 não contém essa FK porque `courses` precisa ser criada antes; não há `CHECK` de domínio nem índice secundário explícito nesta etapa.",
      "line": 4
    },
    {
      "id": "checklist",
      "level": 2,
      "title": "Checklist",
      "text": "- [x] Adicionar coluna `course_id` e adiar índice secundário para análise das consultas reais. - [x] Restringir a exclusão física do curso referenciado. - [x] Aplicar obrigatoriedade condicional e coerência de campus no Model, sem `CHECK` de domínio. - [x] Atualizar Model `Affiliation` e relação com `Course`. - [x] Atualizar factory com vínculo discente por curso. - [x] Testar discente sem curso, servidor com curso indevido e dois cursos para a mesma pessoa. - [x] Testar escopo: curso e vínculo devem pertencer ao mesmo campus. - [x] Testar migrate/rollback na ordem completa.",
      "line": 8
    },
    {
      "id": "dependencias",
      "level": 2,
      "title": "Dependências",
      "text": "- [AffiliationType](doc:enum-affiliationtype) - [affiliations](doc:migration-04-affiliations) - [courses](doc:migration-09-courses)",
      "line": 19
    }
  ],
  "sourcePath": "content/migration-10-course-id-em-affiliations.md",
  "visuals": [],
  "apiVersion": 1
}
