{
  "id": "migration-01-addresses",
  "title": "Migration 01 — addresses",
  "description": "Base backend de endereços próprios de cada cadastro e cópia histórica na própria tabela.",
  "type": "migration-reference",
  "status": "implemented",
  "visibility": "public",
  "tags": [
    "sge/migrations",
    "sge/banco-de-dados",
    "sge/endereco"
  ],
  "related": [
    "migration-01a-cities",
    "actions",
    "concerns",
    "testes-existentes",
    "dominio-e-modelo-de-dados",
    "migration-02-user-personal-data",
    "migration-03-campuses",
    "migration-12-granting-parties",
    "migration-15-internships"
  ],
  "sourceRefs": [
    "https://github.com/sge-suite/sge/blob/master/database/migrations/2026_09_16_153327_create_addresses_table.php",
    "https://github.com/sge-suite/sge/blob/master/app/Models/Address.php",
    "https://github.com/sge-suite/sge/blob/master/app/Concerns/AddressValidationRules.php",
    "https://github.com/sge-suite/sge/blob/master/app/Actions/CopyAddress.php",
    "https://github.com/sge-suite/sge/blob/master/tests/Feature/AddressesTest.php"
  ],
  "authors": [],
  "updated": null,
  "diagram": null,
  "body": "> [!success] Estado\n> Base backend implementada: migration, model, factories, relacionamentos, validação dos campos obrigatórios, Activity Log e cópia histórica. Consulta e validação de CEP, telas e integrações com os cadastros e a formalização continuam pendentes.\n\n## Contrato implementado\n\n| Coluna | Tipo PostgreSQL | Nulo | Chaves/índices | Regra |\n| --- | --- | --- | --- | --- |\n| `id` | `bigint` | não | `PK` | Identificador interno. |\n| `city_id` | `bigint` | não | `FK`, índice | Referencia `cities.id`; exclusão `RESTRICT`. A UF é obtida da cidade. |\n| `street` | `varchar(255)` | não | — | Rua ou logradouro obrigatório. |\n| `number` | `varchar(255)` | não | — | Número textual obrigatório; aceita `123 A`, `0` e `s/n`. |\n| `neighborhood` | `varchar(255)` | não | — | Bairro obrigatório. |\n| `zip_code` | `varchar(255)` | sim | — | Valor opcional; sem validação de formato ou limpeza de dígitos nesta etapa. |\n| `created_at` | `timestamp(0)` | sim | — | Timestamp nativo do Laravel; preenchido pelo Eloquent no timezone institucional. |\n| `updated_at` | `timestamp(0)` | sim | — | Timestamp nativo do Laravel; atualizado pelo Eloquent. |\n\nA migration usa `$table->timestamps()`, com a nulabilidade padrão do Laravel. O Eloquent converte esses campos automaticamente em datas, sem casts explícitos no model. Inserções diretas que não passem pelo Eloquent podem deixar os timestamps nulos. O `down()` remove a tabela.\n\nA cidade é obrigatória e selecionada no [catálogo local](doc:migration-01a-cities), criado antes desta tabela. Não há `complement`, UF duplicada, tabela de snapshots, `copied_from_address_id` ou campos de autoria. A tabela não usa `SoftDeletes` nem Scout/Searchable.\n\n## Propriedade dos endereços\n\n`addresses` centraliza as colunas de endereço, mas suas linhas não são compartilhadas. Cada linha pode pertencer a no máximo um registro proprietário, seja em `user_personal_data`, `campuses`, `granting_parties` ou nos futuros endereços históricos de `internships`. Isso vale também para dois registros da mesma tabela: conteúdo idêntico recebe IDs diferentes. Um novo proprietário recebe uma nova linha, nunca o `address_id` já vinculado a outro registro. A cópia histórica cria outra linha na mesma tabela.\n\nO schema atual contém FKs nos cadastros proprietários, mas ainda não impõe essa exclusividade entre todas as tabelas. Os futuros fluxos de integração devem respeitar a regra ao criar e trocar endereços; uma garantia no banco exige desenho próprio antes de ser declarada implementada.\n\nNa gestão de campi, a edição atualiza a mesma linha de endereço e preserva seu ID. Antes da mudança, o controller verifica referências em campi, dados pessoais, concedentes e estágios; se encontrar outro proprietário ou uso histórico, rejeita a operação. A verificação é de aplicação, pois o schema não impõe exclusividade entre as FKs.\n\n## Model, validação e auditoria\n\n`Address` declara os campos cadastrais com `#[Fillable]`, usa `HasFactory` e possui casts de `city_id` para inteiro e dos campos textuais para string. `Address::city()` é `BelongsTo`; `City::addresses()` é `HasMany`. As relações com modelos ainda inexistentes não foram adicionadas.\n\n`AddressValidationRules` centraliza as regras aplicadas no evento `saving` do model: cidade existente, logradouro, número e bairro obrigatórios, todos com os limites do schema. O número é textual, sem restringir os valores ao formato numérico. A rota de campus valida o endereço aninhado em um Form Request e cria ou atualiza `Address` via Eloquent. Ainda não há endpoint genérico para gerenciar endereços.\n\nO CEP omitido, nulo ou vazio é persistido como `null`. Valores informados não passam por validação de CEP, normalização por helper ou remoção de máscara; `varchar(255)` preserva o valor sem preenchimento de espaços. A validação de oito dígitos e o tratamento de máscaras serão definidos em uma etapa futura.\n\nO Activity Log registra os campos cadastrais, somente quando há mudanças, sem registros vazios. A autoria vem do próprio mecanismo de auditoria. `AddressFactory` usa `CityFactory` para criar fixtures independentes de rede e da carga completa do catálogo.\n\n## Cópia histórica implementada\n\n`CopyAddress::handle(Address $address): Address` exige uma origem persistida, relê a linha dentro de uma transação com bloqueio compartilhado e cria outra linha em `addresses`. A cópia recebe novo ID e timestamps, não altera a origem e ignora mudanças ainda não salvas no objeto recebido. Cópias sucessivas são independentes.\n\nUma origem não persistida gera `InvalidArgumentException`; se a linha já foi removida, a releitura gera `ModelNotFoundException`. A criação da cópia também passa pelas regras do model e pelo Activity Log.\n\nO fluxo de campus impede editar seu endereço quando uma referência de outro proprietário ou estágio aponta para a mesma linha. Não há bloqueio genérico de edição/exclusão de endereços históricos para os demais cadastros; essa proteção será implementada junto às futuras FKs e operações de formalização. A cópia existe, mas os outros fluxos ainda precisam integrar seu uso.\n\nNa formalização futura, `internships.workplace_address_id` deverá apontar para uma cópia do endereço da concedente nesta mesma tabela. `internships.student_address_id` poderá apontar para a cópia do endereço do discente quando necessário. Essas linhas deverão ser preservadas sem alterações retroativas; a mudança do cadastro atual poderá criar outra linha e trocar sua FK.\n\n## Consulta de CEP futura\n\nA consulta à BrasilAPI está fora da base atual. Quando implementada, sua resposta servirá apenas como sugestão de preenchimento e não impedirá a edição manual em caso de CEP geral, ausência ou indisponibilidade.\n\nA cidade deverá continuar sendo resolvida exclusivamente no catálogo local: primeiro pelo código IBGE quando disponível; caso contrário, pelo nome normalizado e pela UF. Ausência ou ambiguidade exigirá seleção manual. A integração não poderá criar cidades a partir da resposta externa.\n\n## Testes\n\n`tests/Feature/AddressesTest.php` cobre tipos e limites PostgreSQL, nulabilidade, índices, FK `RESTRICT`, migrate e rollback, factories, casts, relacionamentos, campos obrigatórios, número textual, CEP opcional sem validação de formato, cópia histórica e Activity Log. `tests/Feature/CampusManagementTest.php` verifica edição no mesmo endereço, bloqueio quando ele também estiver ligado a outro cadastro ou estágio e rollback dos registros e atividades. Como `user_personal_data` referencia `addresses`, o teste de rollback reverte em conjunto as migrations aplicadas a partir de `addresses`, testa a remoção da tabela e reaplica o conjunto. Os testes usam `Http::fake()` e `preventStrayRequests()` para impedir acesso à rede.\n\n```bash\n./vendor/bin/sail artisan test --compact tests/Feature/AddressesTest.php tests/Feature/CitiesTest.php tests/Unit/DatabaseDriverGuardTest.php\n./vendor/bin/sail exec laravel.test vendor/bin/pint --dirty --format agent\n```\n\nOs testes cobrem o schema PostgreSQL, endereços, cidades e a guarda global de migrations; o mapa atual está em [Testes existentes](doc:testes-existentes).\n\n## Checklist\n\n- [x] Definir cidade por FK para catálogo IBGE e CEP opcional.\n- [x] Confirmar campos obrigatórios e usar o limite padrão de 255 caracteres para os campos textuais.\n- [x] Criar migration reversível `create_addresses_table` com índice em `city_id` e FK `RESTRICT`.\n- [x] Criar `Address`, `AddressFactory`, `CityFactory` e relacionamentos existentes.\n- [x] Validar cidade, logradouro, bairro e número textual, incluindo `s/n`.\n- [x] Usar timestamps nativos, sem casts explícitos de data.\n- [x] Registrar alterações cadastrais no Activity Log, sem campos de autoria.\n- [x] Implementar e testar a cópia histórica na própria tabela.\n- [x] Testar schema, migrate, rollback e persistência em PostgreSQL.\n- [x] Atualizar [Domínio e modelo de dados](doc:dominio-e-modelo-de-dados).\n- [ ] Definir validação e normalização de CEP no futuro fluxo de cadastro.\n- [ ] Implementar a consulta opcional de CEP e resolução da cidade no catálogo local.\n- [ ] Criar telas e endpoints de cadastro nas etapas correspondentes.\n- [ ] Integrar cópias e impedir alteração/exclusão de linhas históricas ao implementar a formalização.\n\n## Próximas dependências\n\n- [user_personal_data](doc:migration-02-user-personal-data)\n- [campuses](doc:migration-03-campuses)\n- [granting_parties](doc:migration-12-granting-parties)\n- [internships](doc:migration-15-internships)",
  "sections": [
    {
      "id": "contrato-implementado",
      "level": 2,
      "title": "Contrato implementado",
      "text": "| Coluna | Tipo PostgreSQL | Nulo | Chaves/índices | Regra | | --- | --- | --- | --- | --- | | `id` | `bigint` | não | `PK` | Identificador interno. | | `city_id` | `bigint` | não | `FK`, índice | Referencia `cities.id`; exclusão `RESTRICT`. A UF é obtida da cidade. | | `street` | `varchar(255)` | não | — | Rua ou logradouro obrigatório. | | `number` | `varchar(255)` | não | — | Número textual obrigatório; aceita `123 A`, `0` e `s/n`. | | `neighborhood` | `varchar(255)` | não | — | Bairro obrigatório. | | `zip_code` | `varchar(255)` | sim | — | Valor opcional; sem validação de formato ou limpeza de dígitos nesta etapa. | | `created_at` | `timestamp(0)` | sim | — | Timestamp nativo do Laravel; preenchido pelo Eloquent no timezone institucional. | | `updated_at` | `timestamp(0)` | sim | — | Timestamp nativo do Laravel; atualizado pelo Eloquent. |  A migration usa `$table->timestamps()`, com a nulabilidade padrão do Laravel. O Eloquent converte esses campos automaticamente em datas, sem casts explícitos no model. Inserções diretas que não passem pelo Eloquent podem deixar os timestamps nulos. O `down()` remove a tabela.  A cidade é obrigatória e selecionada no [catálogo local](doc:migration-01a-cities), criado antes desta tabela. Não há `complement`, UF duplicada, tabela de snapshots, `copied_from_address_id` ou campos de autoria. A tabela não usa `SoftDeletes` nem Scout/Searchable.",
      "line": 4
    },
    {
      "id": "propriedade-dos-enderecos",
      "level": 2,
      "title": "Propriedade dos endereços",
      "text": "`addresses` centraliza as colunas de endereço, mas suas linhas não são compartilhadas. Cada linha pode pertencer a no máximo um registro proprietário, seja em `user_personal_data`, `campuses`, `granting_parties` ou nos futuros endereços históricos de `internships`. Isso vale também para dois registros da mesma tabela: conteúdo idêntico recebe IDs diferentes. Um novo proprietário recebe uma nova linha, nunca o `address_id` já vinculado a outro registro. A cópia histórica cria outra linha na mesma tabela.  O schema atual contém FKs nos cadastros proprietários, mas ainda não impõe essa exclusividade entre todas as tabelas. Os futuros fluxos de integração devem respeitar a regra ao criar e trocar endereços; uma garantia no banco exige desenho próprio antes de ser declarada implementada.  Na gestão de campi, a edição atualiza a mesma linha de endereço e preserva seu ID. Antes da mudança, o controller verifica referências em campi, dados pessoais, concedentes e estágios; se encontrar outro proprietário ou uso histórico, rejeita a operação. A verificação é de aplicação, pois o schema não impõe exclusividade entre as FKs.",
      "line": 21
    },
    {
      "id": "model-validacao-e-auditoria",
      "level": 2,
      "title": "Model, validação e auditoria",
      "text": "`Address` declara os campos cadastrais com `#[Fillable]`, usa `HasFactory` e possui casts de `city_id` para inteiro e dos campos textuais para string. `Address::city()` é `BelongsTo`; `City::addresses()` é `HasMany`. As relações com modelos ainda inexistentes não foram adicionadas.  `AddressValidationRules` centraliza as regras aplicadas no evento `saving` do model: cidade existente, logradouro, número e bairro obrigatórios, todos com os limites do schema. O número é textual, sem restringir os valores ao formato numérico. A rota de campus valida o endereço aninhado em um Form Request e cria ou atualiza `Address` via Eloquent. Ainda não há endpoint genérico para gerenciar endereços.  O CEP omitido, nulo ou vazio é persistido como `null`. Valores informados não passam por validação de CEP, normalização por helper ou remoção de máscara; `varchar(255)` preserva o valor sem preenchimento de espaços. A validação de oito dígitos e o tratamento de máscaras serão definidos em uma etapa futura.  O Activity Log registra os campos cadastrais, somente quando há mudanças, sem registros vazios. A autoria vem do próprio mecanismo de auditoria. `AddressFactory` usa `CityFactory` para criar fixtures independentes de rede e da carga completa do catálogo.",
      "line": 29
    },
    {
      "id": "copia-historica-implementada",
      "level": 2,
      "title": "Cópia histórica implementada",
      "text": "`CopyAddress::handle(Address $address): Address` exige uma origem persistida, relê a linha dentro de uma transação com bloqueio compartilhado e cria outra linha em `addresses`. A cópia recebe novo ID e timestamps, não altera a origem e ignora mudanças ainda não salvas no objeto recebido. Cópias sucessivas são independentes.  Uma origem não persistida gera `InvalidArgumentException`; se a linha já foi removida, a releitura gera `ModelNotFoundException`. A criação da cópia também passa pelas regras do model e pelo Activity Log.  O fluxo de campus impede editar seu endereço quando uma referência de outro proprietário ou estágio aponta para a mesma linha. Não há bloqueio genérico de edição/exclusão de endereços históricos para os demais cadastros; essa proteção será implementada junto às futuras FKs e operações de formalização. A cópia existe, mas os outros fluxos ainda precisam integrar seu uso.  Na formalização futura, `internships.workplace_address_id` deverá apontar para uma cópia do endereço da concedente nesta mesma tabela. `internships.student_address_id` poderá apontar para a cópia do endereço do discente quando necessário. Essas linhas deverão ser preservadas sem alterações retroativas; a mudança do cadastro atual poderá criar outra linha e trocar sua FK.",
      "line": 39
    },
    {
      "id": "consulta-de-cep-futura",
      "level": 2,
      "title": "Consulta de CEP futura",
      "text": "A consulta à BrasilAPI está fora da base atual. Quando implementada, sua resposta servirá apenas como sugestão de preenchimento e não impedirá a edição manual em caso de CEP geral, ausência ou indisponibilidade.  A cidade deverá continuar sendo resolvida exclusivamente no catálogo local: primeiro pelo código IBGE quando disponível; caso contrário, pelo nome normalizado e pela UF. Ausência ou ambiguidade exigirá seleção manual. A integração não poderá criar cidades a partir da resposta externa.",
      "line": 49
    },
    {
      "id": "testes",
      "level": 2,
      "title": "Testes",
      "text": "`tests/Feature/AddressesTest.php` cobre tipos e limites PostgreSQL, nulabilidade, índices, FK `RESTRICT`, migrate e rollback, factories, casts, relacionamentos, campos obrigatórios, número textual, CEP opcional sem validação de formato, cópia histórica e Activity Log. `tests/Feature/CampusManagementTest.php` verifica edição no mesmo endereço, bloqueio quando ele também estiver ligado a outro cadastro ou estágio e rollback dos registros e atividades. Como `user_personal_data` referencia `addresses`, o teste de rollback reverte em conjunto as migrations aplicadas a partir de `addresses`, testa a remoção da tabela e reaplica o conjunto. Os testes usam `Http::fake()` e `preventStrayRequests()` para impedir acesso à rede.   Os testes cobrem o schema PostgreSQL, endereços, cidades e a guarda global de migrations; o mapa atual está em [Testes existentes](doc:testes-existentes).",
      "line": 55
    },
    {
      "id": "checklist",
      "level": 2,
      "title": "Checklist",
      "text": "- [x] Definir cidade por FK para catálogo IBGE e CEP opcional. - [x] Confirmar campos obrigatórios e usar o limite padrão de 255 caracteres para os campos textuais. - [x] Criar migration reversível `create_addresses_table` com índice em `city_id` e FK `RESTRICT`. - [x] Criar `Address`, `AddressFactory`, `CityFactory` e relacionamentos existentes. - [x] Validar cidade, logradouro, bairro e número textual, incluindo `s/n`. - [x] Usar timestamps nativos, sem casts explícitos de data. - [x] Registrar alterações cadastrais no Activity Log, sem campos de autoria. - [x] Implementar e testar a cópia histórica na própria tabela. - [x] Testar schema, migrate, rollback e persistência em PostgreSQL. - [x] Atualizar [Domínio e modelo de dados](doc:dominio-e-modelo-de-dados). - [ ] Definir validação e normalização de CEP no futuro fluxo de cadastro. - [ ] Implementar a consulta opcional de CEP e resolução da cidade no catálogo local. - [ ] Criar telas e endpoints de cadastro nas etapas correspondentes. - [ ] Integrar cópias e impedir alteração/exclusão de linhas históricas ao implementar a formalização.",
      "line": 66
    },
    {
      "id": "proximas-dependencias",
      "level": 2,
      "title": "Próximas dependências",
      "text": "- [user_personal_data](doc:migration-02-user-personal-data) - [campuses](doc:migration-03-campuses) - [granting_parties](doc:migration-12-granting-parties) - [internships](doc:migration-15-internships)",
      "line": 83
    }
  ],
  "sourcePath": "content/migration-01-addresses.md",
  "visuals": [],
  "apiVersion": 1
}
