# Feriados Municipais — Arquitetura

## 1. Visão geral das camadas

Este é um dos módulos mais diretos da plataforma: um model, um controller de
CRUD, um Form Request, um repository e uma view de listagem no portal.

| Camada | Classe / arquivo | Papel |
|---|---|---|
| Rota (portal) | `v6/routes/web.php:205` | `GET /feriados-municipais` |
| Controller (portal) | `v6/app/Http/Controllers/Portal/FeriadosMunicipaisController.php` | Filtra o ano corrente e agrupa por mês |
| Rota (CMS) | `v6/routes/basepro.php:335-343` | `Route::resource` + `destroyAll` |
| Controller (CMS) | `v6/app/Http/Controllers/Prefecture/FeriadosMunicipaisController.php` | CRUD completo + exclusão em lote |
| Validação | `v6/app/Http/Requests/FeriadosMunicipaisRequest.php` | Regras de `icon`, `description`, `date` |
| Repositório | `v6/app/Repositories/Eloquent/Prefecture/FeriadosMunicipais/FeriadosMunicipaisRepository.php` | Normaliza a data e monta o payload |
| Contrato | `v6/app/Repositories/Contracts/IFeriadosMunicipaisRepository.php` | Interface **vazia** (herança do `Repository`) |
| Base | `v6/app/Repositories/Repository.php` | `upInsert`, `destroy`, `destroyAll`, `filter_request` |
| Model | `v6/app/Http/Model/Prefecture/FeriadosMunicipais.php` | Eloquent de `feriados_municipais` |
| Controller (DataTable) | `v6/app/Http/Controllers/Prefecture/DatatableController.php:399-403` | `get_feriados` |
| View (portal) | `v6/resources/views/portal/layouts/modelo1/feriados-municipais/index.blade.php` | Página pública (6 temas) |
| Componente (portal) | `v6/resources/views/portal/components/feriados-municipais/card-accordion.blade.php` | Acordeão Bootstrap 4 |
| Componente (portal) | `v6/resources/views/portal/components/feriados-municipais/accordion-item.blade.php` | Acordeão Bootstrap 5 |
| View (CMS) | `v6/resources/views/prefecture/contents/feriados-municipais/{index,create,form}.blade.php` | Listagem e formulário |
| JS (CMS) | `v6/public/assets/js/custom/contents/feriados-municipais/datatable.js` | Colunas do DataTable |
| CSS (portal) | `v6/public/layouts/modelo1/css/feriados-municipais.css` | Estilo do selo do dia (temas 1 a 5) |
| SQL | `v6/database/migrations/2023_02_02_094344_create_feriados_municipais_table.php` | Cria a tabela |
| SQL | `v6/database/migrations/2023_05_29_144236_create_view_lista_de_feriados_municipais.php` | Cria a view |

## 2. Fluxo do portal

```mermaid
flowchart TD
    A[GET /feriados-municipais] --> B[Portal\FeriadosMunicipaisController@index]
    B --> C[FeriadosMunicipais::whereYear date = ano corrente]
    C --> D[orderBy date ASC]
    D --> E[groupBy mês format m]
    E --> F[view portal.layouts.{layout}.feriados-municipais.index]
    F --> G[@include card-accordion ou accordion-item]
    G --> H[um cartão por mês com ícones e descrições]
```

Código real (`v6/app/Http/Controllers/Portal/FeriadosMunicipaisController.php`):

```php
public function index()
{
    $feriados = FeriadosMunicipais::whereYear('date', date('Y'))
    ->orderBy('date', 'ASC')
    ->get()->groupBy(function($val) {
        return Carbon::parse($val->date)->format('m');
    });

    return view('portal.layouts.'.$this->getTemplateActived().'.feriados-municipais.index', compact('feriados'));
}
```

Observações de arquitetura:

- `get()` seguido de `groupBy()` em PHP: **todos** os feriados do ano são
  carregados em memória (sem paginação). Para um calendário municipal, o volume
  é pequeno (dezenas de registros), mas a consulta não tem limite.
- `Carbon::parse($val->date)->format('m')` produz chaves `"01"`..`"12"`.
- O agrupamento é feito **em PHP**, não em SQL.

## 3. Fluxo do CMS — criação e edição

```mermaid
flowchart TD
    A[POST /cms/feriados-municipais] --> B[FeriadosMunicipaisRequest]
    B --> C{validou?}
    C -- não --> D[redirect back com erros]
    C -- sim --> E[FeriadosMunicipaisController@store]
    E --> F[FeriadosMunicipaisRepository@upInsert]
    F --> G[Repository::upInsert]
    G --> H[DB::transaction -> structureUpInsert]
    H --> I[normalize date dd/mm/aaaa -> Y-m-d]
    I --> J[filter_request mantem apenas os campos enviados]
    J --> K[updateOrCreate id = null]
    K --> L[flash de sucesso + redirect index]
```

Para edição, `update()` chama o **mesmo** `upInsert($request, $id)`, que termina
em `updateOrCreate(['id' => $id], $data)`.

### 3.1 `structureUpInsert`

`v6/app/Repositories/Eloquent/Prefecture/FeriadosMunicipais/FeriadosMunicipaisRepository.php`:

```php
protected function structureUpInsert($request): array
{
    $request['date'] = date('Y-m-d', strtotime(str_replace('/', '-', $request['date'])));
    $fields = array_keys($request);

    if (!in_array('status', $fields)) {
        array_push($fields, 'status');
        $request['status'] = null;
    }

    return $this->filter_request($request, $fields);
}
```

- A data chega como `dd/mm/aaaa` (máscara `br-data` do formulário) e é
  convertida para `Y-m-d`.
- O campo `status` é adicionado mesmo **não existindo** na tabela — ele é
  descartado depois, porque não está no `$fillable` do model.
- `filter_request` (`v6/app/Http/Traits/AppTrait.php:523`) devolve **todos** os
  campos enviados na request (incluindo `_token` e `_method`), convertendo
  valores "falsy" para `null`.

### 3.2 `Repository::upInsert`

`v6/app/Repositories/Repository.php`:

```php
public function upInsert($request, int $id = null)
{
    try {
        $data = DB::transaction(function () use ($request, $id) {
            return $this->structureUpInsert($this->dataTrait($request));
        });
        return $this->getModel()->updateOrCreate(['id' => $id], $data);
    } catch (\Throwable $th) {
        dd($th);
    }
    return false;
}
```

Pontos relevantes:

- A `DB::transaction` envolve apenas a **normalização** (que não toca no banco)
  — o `updateOrCreate` fica **fora** da transação.
- O `catch` chama `dd($th)`: em produção, exibe a exceção completa (stack trace)
  na resposta HTTP e aborta a execução.
- `updateOrCreate(['id' => $id], $data)` com `$id = null` faz `INSERT`; com
  `$id` preenchido faz `UPDATE` — o mesmo método serve para os dois casos.

## 4. Fluxo do CMS — exclusão

### 4.1 Individual

```mermaid
flowchart TD
    A[DELETE /cms/feriados-municipais/{id}] --> B[Controller@destroy]
    B --> C[FeriadosMunicipais::destroy id]
    C --> D{deleted?}
    D -- sim --> E[JSON 200 success]
    D -- não --> F[JSON 500 erro]
```

O botão de lixeira é gerado por `createBTN` em
`v6/public/assets/js/custom/utilities/table.js:227`, com o atributo
`target="feriados-municipais/{id}"`. O handler `addDelete()` (mesmo arquivo)
faz `myFetch(url, 'DELETE')` e recarrega o DataTable.

### 4.2 Em lote

```mermaid
flowchart TD
    A[POST /cms/feriados-municipais/destroyAll] --> B[Controller@destroyAll]
    B --> C[$request->toDelete]
    C --> D[FeriadosMunicipaisRepository@destroyAll]
    D --> E[Repository::destroyAll -> whereIn id -> delete]
    E --> F[retorna JsonResponse]
    F --> G{JsonResponse é truthy?}
    G -- sempre sim --> H[JSON 200 success]
```

O JS `v6/public/assets/js/custom/utilities/deleteAll.js` serializa o array como
`toDelete[]=1&toDelete[]=2`; o controller lê `$request->toDelete`.

## 5. Classes envolvidas — papel detalhado

| Classe | Caminho | Papel |
|---|---|---|
| `App\Http\Controllers\Portal\FeriadosMunicipaisController` | `v6/app/Http/Controllers/Portal/FeriadosMunicipaisController.php` | Página pública; filtro por ano; agrupamento por mês |
| `App\Http\Controllers\Prefecture\FeriadosMunicipaisController` | `v6/app/Http/Controllers/Prefecture/FeriadosMunicipaisController.php` | CRUD no CMS; `menuClicks`; `check.route.permission` |
| `App\Http\Requests\FeriadosMunicipaisRequest` | `v6/app/Http/Requests/FeriadosMunicipaisRequest.php` | Validação do formulário |
| `App\Repositories\Eloquent\Prefecture\FeriadosMunicipais\FeriadosMunicipaisRepository` | `v6/app/Repositories/Eloquent/Prefecture/FeriadosMunicipais/FeriadosMunicipaisRepository.php` | Normaliza a data e monta o payload |
| `App\Repositories\Contracts\IFeriadosMunicipaisRepository` | `v6/app/Repositories/Contracts/IFeriadosMunicipaisRepository.php` | Interface **vazia** — herdada de `App\Repositories\Repository` |
| `App\Repositories\Repository` | `v6/app/Repositories/Repository.php` | Base abstrata: `upInsert`, `destroy`, `destroyAll`, `destroyWithRelation` |
| `App\Http\Model\Prefecture\FeriadosMunicipais` | `v6/app/Http/Model/Prefecture/FeriadosMunicipais.php` | Model com `SoftDeletes`, `$table = 'feriados_municipais'` |
| `App\Http\Controllers\Prefecture\DatatableController` | `v6/app/Http/Controllers/Prefecture/DatatableController.php` | `get_feriados` (linha 399) e `struct_views` (linha 504) |
| `App\Prefecture\MenuOrder` | `v6/app/Http/Model/Prefecture/MenuOrder.php` | Contador `clicks` do menu |
| `App\Prefecture\Configuration` | `v6/app/Http/Model/Prefecture/Configuration.php` | Chave `layout` (tema do portal) |

## 6. Middleware

| Middleware | Onde | Efeito |
|---|---|---|
| `check.route.permission` | construtor de `v6/app/Http/Controllers/Prefecture/FeriadosMunicipaisController.php:24` | Exige login Sentinel e permissão textual |
| `check.site.manutention` | grupo de `v6/routes/web.php:7` | Bloqueia o portal inteiro em manutenção |
| `csrf` | grupo `/cms` (`before => csrf`) | Token nos formulários do CMS |
| (nenhum) | `GET /feriados-municipais` | Página pública |

O `CheckRoutePermission` (`v6/app/Http/Middleware/CheckRoutePermission.php`)
contém uma regra **específica deste módulo**:

```php
if (str_contains($access, 'painel.feriados-municipais')) $access = str_replace('painel.feriados-municipais', 'feriados-municipais', $access);
```

Isso converte `painel.feriados-municipais.index` em `feriados-municipais.index`,
de modo que a permissão cadastrada é `feriados-municipais.*` (e não
`painel.feriados-municipais.*`).

## 7. Dependências externas (composer)

| Pacote | Uso no módulo |
|---|---|
| `laravel/framework` | Eloquent, Blade, Form Request, `Response::json` |
| `nesbot/carbon` | `Carbon::parse($val->date)->format('m')` no agrupamento |
| `yajra/laravel-datatables-oracle` | DataTable do CMS |
| `cartalyst/sentinel` | Autenticação e permissões |
| `datatables.net` (CDN) | Front da listagem no CMS |
| `bootstrap` (4 e 5) | Acordeões do portal |

## 8. Dependências de terceiros / acoplamentos

- **Não há** repository de outro módulo, nem `Content`, nem `Archive`.
- Depende de `modulos` (id 51) para exibir o item de menu
  (`v6/app/Providers/MenuServiceProvider.php:233`).
- Depende de `menu_orders` para o contador `clicks` (via `menuClicks`).
- Depende de `resources` (id 70, `modulo_id = 51`) para as permissões.
- Depende do `layout` em `configurations` para escolher o tema do portal.
- **Nenhum outro módulo depende deste** — é um módulo folha.
