585 lines
22 KiB
PHP
Executable File
585 lines
22 KiB
PHP
Executable File
<?php
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\GestioneContabile;
|
|
use App\Models\Stabile;
|
|
use Illuminate\Console\Command;
|
|
use Symfony\Component\Process\Process;
|
|
|
|
class ImportGestioniFromLegacy extends Command
|
|
{
|
|
protected $signature = 'gescon:import-gestioni \
|
|
{--stabile= : Codice stabile a 4 cifre (es. 0019)} \
|
|
{--root=/mnt/gescon-archives/gescon : Root archivi legacy} \
|
|
{--stabili-mdb= : Path al master MDB (Stabili/cartellestabili)} \
|
|
{--tenant=default : Tenant ID da usare} \
|
|
{--force-update-months : Aggiorna i mesi anche se la gestione esiste} \
|
|
{--dry-run : Non scrivere, solo report}';
|
|
|
|
protected $description = 'Crea le gestioni contabili (per anno) leggendo MDB legacy: mesi da Stabili (ORD_RATA_/RIS_RATA_), anni da generale_stabile.mdb';
|
|
|
|
public function handle(): int
|
|
{
|
|
$stabileCode = (string) $this->option('stabile');
|
|
if (! $stabileCode) {
|
|
$this->error('Specificare --stabile=0000');
|
|
return 1;
|
|
}
|
|
$stabileCode = str_pad($stabileCode, 4, '0', STR_PAD_LEFT);
|
|
$root = rtrim((string) $this->option('root') ?: '/mnt/gescon-archives/gescon', '/');
|
|
$tenant = (string) $this->option('tenant') ?: 'default';
|
|
$dry = (bool) $this->option('dry-run');
|
|
|
|
// Heuristics per master MDB (Stabili)
|
|
$stabiliCandidates = [];
|
|
if ($this->option('stabili-mdb')) {
|
|
$stabiliCandidates[] = (string) $this->option('stabili-mdb');
|
|
}
|
|
foreach (['Stabili.mdb', 'stabili.mdb', 'cartellestabili_e_anni.mdb'] as $n) {
|
|
$stabiliCandidates[] = $root . '/' . $n;
|
|
$stabiliCandidates[] = $root . '/dbc/' . $n;
|
|
}
|
|
$stabiliMdb = null;
|
|
foreach ($stabiliCandidates as $cand) {
|
|
if (is_file($cand)) {
|
|
$stabiliMdb = $cand;
|
|
break;
|
|
}
|
|
}
|
|
if (! $stabiliMdb) {
|
|
$this->error('Master MDB Stabili non trovato: senza Stabili.mdb non leggo ORD_RATA_/RIS_RATA_ e non applico fallback.');
|
|
return 1;
|
|
}
|
|
|
|
// 1) Leggi mesi da Stabili (ORD_RATA_*, RIS_RATA_*) e risolvi directory
|
|
$ordMesi = [];
|
|
$risMesi = [];
|
|
$stableDir = $root . '/' . $stabileCode;
|
|
if ($stabiliMdb) {
|
|
[$row, $headers] = $this->findRowInMdb($stabiliMdb, ['cod', 'codice', 'cod_stabile', 'stabile', 'nome_directory', 'codice_directory', 'internet_cod_stab'], $stabileCode);
|
|
if ($row) {
|
|
$hmap = array_change_key_case($headers, CASE_LOWER);
|
|
// Estrai mesi
|
|
foreach ($hmap as $col => $idx) {
|
|
if (str_starts_with($col, 'ord_rata_')) {
|
|
$num = (int) preg_replace('/[^0-9]/', '', substr($col, strlen('ord_rata_')));
|
|
if ($num >= 1 && $num <= 12) {
|
|
if (self::truthy($row[$idx] ?? null)) {
|
|
$ordMesi[] = $num;
|
|
}
|
|
|
|
}
|
|
} elseif (str_starts_with($col, 'ris_rata_')) {
|
|
$num = (int) preg_replace('/[^0-9]/', '', substr($col, strlen('ris_rata_')));
|
|
if ($num >= 1 && $num <= 12) {
|
|
if (self::truthy($row[$idx] ?? null)) {
|
|
$risMesi[] = $num;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
// Directory se presente (campi possibili)
|
|
foreach (['directory', 'dir', 'cartella', 'path', 'folder', 'dir_stabile', 'nome_directory', 'codice_directory', 'internet_cod_stab'] as $k) {
|
|
if (isset($hmap[$k])) {
|
|
$val = trim((string) ($row[$hmap[$k]] ?? ''));
|
|
if ($val) {
|
|
$stableDir = $root . '/' . trim($val, '/');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! is_dir($stableDir)) {
|
|
$this->error("Directory stabile non trovata: {$stableDir}");
|
|
return 1;
|
|
}
|
|
|
|
// 2) Determina anni gestione da generale_stabile.mdb; se assenti prova i singolo_anno.mdb.
|
|
$generale = $stableDir . '/generale_stabile.mdb';
|
|
$anni = [];
|
|
$gestioniAnnuali = [];
|
|
if (is_file($generale)) {
|
|
$gestioniAnnuali = $this->extractGestioniAnnualiFromGenerale($generale);
|
|
$anni = $this->extractAnniFromGenerale($generale);
|
|
}
|
|
if (! $anni) {
|
|
foreach (glob($stableDir . '/*/singolo_anno.mdb') as $mdb) {
|
|
$anni = array_values(array_unique(array_merge($anni, $this->extractAnniFromSingoloAnno($mdb))));
|
|
}
|
|
}
|
|
sort($anni);
|
|
if (! $anni) {
|
|
$this->error('Nessun anno trovato negli archivi legacy dello stabile; import gestioni interrotto.');
|
|
return 1;
|
|
}
|
|
|
|
$this->info("Stabile {$stabileCode} → dir: {$stableDir}");
|
|
$this->line('Mesi Ordinaria (master Stabili): ' . json_encode($ordMesi) . ' | Mesi Riscaldamento: ' . json_encode($risMesi));
|
|
if ($ordMesi === [] && $risMesi === []) {
|
|
$this->warn('In Stabili.mdb non risultano mesi ORD_RATA_/RIS_RATA_ attivi per questo stabile.');
|
|
}
|
|
$this->line('Anni trovati: ' . implode(', ', $anni));
|
|
|
|
if ($dry) {
|
|
$this->warn('Dry-run attivo: non scrivo su database.');
|
|
return 0;
|
|
}
|
|
|
|
// 3) Crea/aggiorna gestioni per ciascun anno
|
|
$created = 0;
|
|
$skipped = 0;
|
|
$updated = 0;
|
|
// Recupera Stabile record
|
|
$stabileModel = Stabile::where('codice_stabile', $stabileCode)->first();
|
|
$stabileId = $stabileModel?->id;
|
|
if (($tenant === '' || $tenant === 'default') && $stabileId) {
|
|
$tenant = (string) (GestioneContabile::query()
|
|
->where('stabile_id', $stabileId)
|
|
->whereNotNull('tenant_id')
|
|
->where('tenant_id', '!=', '')
|
|
->orderByDesc('updated_at')
|
|
->value('tenant_id') ?: $tenant);
|
|
}
|
|
$forceUpdate = (bool) $this->option('force-update-months');
|
|
foreach ($anni as $anno) {
|
|
foreach (['ordinaria' => $ordMesi, 'riscaldamento' => $risMesi] as $tipo => $mesi) {
|
|
$exists = GestioneContabile::where('tenant_id', $tenant)
|
|
->when($stabileId, fn($q) => $q->where('stabile_id', $stabileId))
|
|
->where('anno_gestione', $anno)
|
|
->where('tipo_gestione', $tipo)
|
|
->first();
|
|
$meta = $gestioniAnnuali[$anno] ?? [];
|
|
[$denominazione, $dataInizio, $dataFine] = $this->buildGestioneLegacyPayload($tipo, $anno, $meta);
|
|
|
|
if ($exists) {
|
|
// Aggiorna mesi se forzato o vuoti
|
|
$campo = $tipo === 'ordinaria' ? 'mesi_rate_ordinaria' : 'mesi_rate_riscaldamento';
|
|
$valEsist = (array) ($exists->{$campo} ?? []);
|
|
$dirty = false;
|
|
if ($forceUpdate || (empty($valEsist) && ! empty($mesi))) {
|
|
$exists->{$campo} = array_values(array_unique(array_map('intval', $mesi)));
|
|
$dirty = true;
|
|
}
|
|
|
|
if ((string) ($exists->denominazione ?? '') !== $denominazione) {
|
|
$exists->denominazione = $denominazione;
|
|
$dirty = true;
|
|
}
|
|
if ((string) ($exists->data_inizio?->format('Y-m-d') ?? $exists->data_inizio ?? '') !== $dataInizio) {
|
|
$exists->data_inizio = $dataInizio;
|
|
$dirty = true;
|
|
}
|
|
if ((string) ($exists->data_fine?->format('Y-m-d') ?? $exists->data_fine ?? '') !== $dataFine) {
|
|
$exists->data_fine = $dataFine;
|
|
$dirty = true;
|
|
}
|
|
|
|
if ($dirty) {
|
|
if (! $dry) {
|
|
$exists->save();
|
|
}
|
|
$updated++;
|
|
} else {
|
|
$skipped++;
|
|
}
|
|
continue;
|
|
}
|
|
$gestione = new GestioneContabile();
|
|
$gestione->tenant_id = $tenant;
|
|
$gestione->stabile_id = $stabileId;
|
|
$gestione->anno_gestione = $anno;
|
|
$gestione->tipo_gestione = $tipo;
|
|
$gestione->denominazione = $denominazione;
|
|
$gestione->data_inizio = $dataInizio;
|
|
$gestione->data_fine = $dataFine;
|
|
$gestione->stato = 'aperta';
|
|
$gestione->protocollo_prefix = strtoupper(substr($tipo, 0, 1)) . $anno;
|
|
$gestione->ultimo_protocollo = 0;
|
|
if ($tipo === 'ordinaria') {
|
|
$gestione->mesi_rate_ordinaria = array_values(array_unique(array_map('intval', $mesi)));
|
|
}
|
|
if ($tipo === 'riscaldamento') {
|
|
$gestione->mesi_rate_riscaldamento = array_values(array_unique(array_map('intval', $mesi)));
|
|
}
|
|
$gestione->gestione_attiva = true;
|
|
$gestione->codice_archivio_legacy = $stableDir;
|
|
if (! $dry) {
|
|
$gestione->save();
|
|
}
|
|
$created++;
|
|
}
|
|
}
|
|
|
|
$this->info("Gestioni create: {$created}, aggiornate: {$updated}, saltate: {$skipped}");
|
|
return 0;
|
|
}
|
|
|
|
private static function truthy($v): bool
|
|
{
|
|
$s = strtolower(trim((string) $v));
|
|
return $s !== '' && ! in_array($s, ['0', 'no', 'false', 'off', 'n'], true);
|
|
}
|
|
|
|
/**
|
|
* Trova una riga nel MDB master dove un campo (tra keys) corrisponde al valore cercato
|
|
* @return array{0: array|null, 1: array<string,int>} row e mappa header->index
|
|
*/
|
|
private function findRowInMdb(string $mdbPath, array $keys, string $value): array
|
|
{
|
|
$table = $this->resolveTable($mdbPath, ['stabili', 'Stabili', 'STABILI']);
|
|
if (! $table) {
|
|
return [null, []];
|
|
}
|
|
|
|
$csv = $this->mdbExport($mdbPath, $table);
|
|
if ($csv === null) {
|
|
return [null, []];
|
|
}
|
|
|
|
$fh = fopen('php://temp', 'r+');
|
|
fwrite($fh, $csv);
|
|
rewind($fh);
|
|
$header = fgetcsv($fh);
|
|
if (! $header) {
|
|
return [null, []];
|
|
}
|
|
|
|
$hmap = [];
|
|
foreach ($header as $i => $h) {
|
|
$hmap[strtolower(trim((string) $h))] = $i;
|
|
}
|
|
while (($row = fgetcsv($fh)) !== false) {
|
|
foreach ($keys as $k) {
|
|
$i = $hmap[strtolower($k)] ?? null;
|
|
if ($i === null) {
|
|
continue;
|
|
}
|
|
|
|
if (str_pad(trim((string) ($row[$i] ?? '')), 4, '0', STR_PAD_LEFT) === $value) {
|
|
return [$row, $hmap];
|
|
}
|
|
}
|
|
}
|
|
return [null, $hmap];
|
|
}
|
|
|
|
private function extractAnniFromGenerale(string $mdbPath): array
|
|
{
|
|
$years = [];
|
|
$table = $this->resolveTable($mdbPath, ['anni', 'Anni', 'ANNI', 'gestioni', 'gestione', 'esercizi', 'Esercizi']);
|
|
if (! $table) {
|
|
return $years;
|
|
}
|
|
|
|
$csv = $this->mdbExport($mdbPath, $table);
|
|
if ($csv === null) {
|
|
return $years;
|
|
}
|
|
|
|
$fh = fopen('php://temp', 'r+');
|
|
fwrite($fh, $csv);
|
|
rewind($fh);
|
|
$header = fgetcsv($fh);
|
|
if (! $header) {
|
|
return $years;
|
|
}
|
|
|
|
$hmap = [];
|
|
foreach ($header as $i => $h) {
|
|
$hmap[strtolower(trim((string) $h))] = $i;
|
|
}
|
|
while (($row = fgetcsv($fh)) !== false) {
|
|
$y = null;
|
|
foreach (['anno_o', 'anno_r', 'anno', 'esercizio', 'str_anno'] as $k) {
|
|
if (isset($hmap[$k])) {
|
|
$val = trim((string) ($row[$hmap[$k]] ?? ''));
|
|
if ($val !== '' && preg_match('/(19|20)\d{2}/', $val, $m)) {
|
|
$y = (int) $m[0];
|
|
break;
|
|
}
|
|
if ($val !== '' && is_numeric($val)) {
|
|
$y = (int) $val;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ($y === null) {
|
|
foreach (['data_inizio', 'inizio', 'dal'] as $k) {
|
|
if (isset($hmap[$k])) {
|
|
$val = trim((string) ($row[$hmap[$k]] ?? ''));
|
|
if ($val && preg_match('/(19|20)\d{2}/', $val, $m)) {
|
|
$y = (int) $m[0];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($y !== null) {
|
|
$years[] = $y;
|
|
}
|
|
}
|
|
return array_values(array_unique($years));
|
|
}
|
|
|
|
private function extractGestioniAnnualiFromGenerale(string $mdbPath): array
|
|
{
|
|
$rows = [];
|
|
$table = $this->resolveTable($mdbPath, ['anni', 'Anni', 'ANNI', 'gestioni', 'gestione', 'esercizi', 'Esercizi']);
|
|
if (! $table) {
|
|
return $rows;
|
|
}
|
|
|
|
$csv = $this->mdbExport($mdbPath, $table);
|
|
if ($csv === null) {
|
|
return $rows;
|
|
}
|
|
|
|
$fh = fopen('php://temp', 'r+');
|
|
fwrite($fh, $csv);
|
|
rewind($fh);
|
|
$header = fgetcsv($fh);
|
|
if (! $header) {
|
|
return $rows;
|
|
}
|
|
|
|
$hmap = [];
|
|
foreach ($header as $i => $h) {
|
|
$hmap[strtolower(trim((string) $h))] = $i;
|
|
}
|
|
|
|
while (($row = fgetcsv($fh)) !== false) {
|
|
$annoRaw = trim((string) ($row[$hmap['anno_o'] ?? $hmap['anno'] ?? $hmap['anno_gestione'] ?? -1] ?? ''));
|
|
if ($annoRaw === '' || ! preg_match('/(19|20)\d{2}/', $annoRaw, $match)) {
|
|
continue;
|
|
}
|
|
|
|
$anno = (int) $match[0];
|
|
$descrizione = trim((string) ($row[$hmap['anno_o'] ?? -1] ?? $row[$hmap['descrizione'] ?? -1] ?? $annoRaw));
|
|
$rows[$anno] = [
|
|
'label' => $descrizione !== '' ? preg_replace('/\s+/', '', $descrizione) : (string) $anno,
|
|
'ordinaria_dal' => $this->normalizeLegacyDateValue($row[$hmap['ordinarie_dal'] ?? $hmap['data_inizio'] ?? $hmap['dal'] ?? -1] ?? null),
|
|
'ordinaria_al' => $this->normalizeLegacyDateValue($row[$hmap['ordinarie_al'] ?? $hmap['data_fine'] ?? $hmap['al'] ?? -1] ?? null),
|
|
'riscaldamento_dal' => $this->normalizeLegacyDateValue($row[$hmap['riscaldamento_dal'] ?? -1] ?? null),
|
|
'riscaldamento_al' => $this->normalizeLegacyDateValue($row[$hmap['riscaldamento_al'] ?? -1] ?? null),
|
|
];
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
private function buildGestioneLegacyPayload(string $tipo, int $anno, array $meta): array
|
|
{
|
|
$label = trim((string) ($meta['label'] ?? ''));
|
|
$dataInizio = $tipo === 'riscaldamento'
|
|
? ($meta['riscaldamento_dal'] ?? null)
|
|
: ($meta['ordinaria_dal'] ?? null);
|
|
$dataFine = $tipo === 'riscaldamento'
|
|
? ($meta['riscaldamento_al'] ?? null)
|
|
: ($meta['ordinaria_al'] ?? null);
|
|
|
|
return [
|
|
ucfirst($tipo) . ' ' . ($label !== '' ? $label : (string) $anno),
|
|
$dataInizio ?: sprintf('%04d-01-01', $anno),
|
|
$dataFine ?: sprintf('%04d-12-31', $anno),
|
|
];
|
|
}
|
|
|
|
private function normalizeLegacyDateValue($value): ?string
|
|
{
|
|
$value = trim((string) ($value ?? ''));
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
|
|
foreach (['d/m/Y', 'd-m-Y', 'Y-m-d', 'Y/m/d'] as $format) {
|
|
$dt = \DateTimeImmutable::createFromFormat($format, $value);
|
|
if ($dt instanceof \DateTimeImmutable) {
|
|
return $dt->format('Y-m-d');
|
|
}
|
|
}
|
|
|
|
$ts = strtotime($value);
|
|
return $ts ? date('Y-m-d', $ts) : null;
|
|
}
|
|
|
|
private function extractAnniFromSingoloAnno(string $mdbPath): array
|
|
{
|
|
$table = $this->resolveTable($mdbPath, ['incassi', 'Incassi']);
|
|
if (! $table) {
|
|
return [];
|
|
}
|
|
|
|
$csv = $this->mdbExport($mdbPath, $table);
|
|
if ($csv === null) {
|
|
return [];
|
|
}
|
|
|
|
$fh = fopen('php://temp', 'r+');
|
|
fwrite($fh, $csv);
|
|
rewind($fh);
|
|
$header = fgetcsv($fh);
|
|
if (! $header) {
|
|
return [];
|
|
}
|
|
|
|
$hmap = [];
|
|
foreach ($header as $i => $h) {
|
|
$hmap[strtolower(trim((string) $h))] = $i;
|
|
}
|
|
$years = [];
|
|
while (($row = fgetcsv($fh)) !== false) {
|
|
$ar = $hmap['anno_rif'] ?? null;
|
|
if ($ar !== null) {
|
|
$val = trim((string) ($row[$ar] ?? ''));
|
|
if ($val && is_numeric($val)) {
|
|
$years[] = (int) $val;
|
|
continue;
|
|
}
|
|
}
|
|
$dt = $hmap['dt_empag'] ?? null;
|
|
if ($dt !== null) {
|
|
$val = trim((string) ($row[$dt] ?? ''));
|
|
if ($val && preg_match('/^(\d{4})-/', $val, $m)) {
|
|
$years[] = (int) $m[1];
|
|
}
|
|
}
|
|
}
|
|
return array_values(array_unique($years));
|
|
}
|
|
|
|
private function resolveTable(string $mdbPath, array $candidates): ?string
|
|
{
|
|
$bin = trim((string) @shell_exec('command -v mdb-tables')) ?: '/usr/bin/mdb-tables';
|
|
$list = @shell_exec(escapeshellcmd($bin) . ' -1 ' . escapeshellarg($mdbPath));
|
|
if (! $list) {
|
|
return null;
|
|
}
|
|
|
|
$names = array_filter(array_map('trim', explode("\n", $list)));
|
|
foreach ($candidates as $c) {
|
|
foreach ($names as $n) {
|
|
if (strcasecmp($n, $c) === 0) {
|
|
return $n;
|
|
}
|
|
|
|
}
|
|
}
|
|
// fallback: first contains substring
|
|
foreach ($candidates as $c) {
|
|
foreach ($names as $n) {
|
|
if (stripos($n, $c) !== false) {
|
|
return $n;
|
|
}
|
|
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private function mdbExport(string $mdbPath, string $table): ?string
|
|
{
|
|
$bin = trim((string) @shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
|
|
$p = new Process([$bin, '-D', '%Y-%m-%d', $mdbPath, $table]);
|
|
$p->setTimeout(120);
|
|
$p->run();
|
|
if (! $p->isSuccessful()) {
|
|
return null;
|
|
}
|
|
|
|
return $p->getOutput();
|
|
}
|
|
|
|
/** Estrae mesi per anno dalla tabella rate del singolo_anno.mdb: ritorna [anno=>['O'=>[mesi],'R'=>[mesi]]] */
|
|
private function extractMesiPerAnnoFromSingoloAnno(string $mdbPath): array
|
|
{
|
|
$out = [];
|
|
$table = $this->resolveTable($mdbPath, ['rate', 'Rate']);
|
|
if (! $table) {
|
|
return $out;
|
|
}
|
|
|
|
$csv = $this->mdbExport($mdbPath, $table);
|
|
if ($csv === null) {
|
|
return $out;
|
|
}
|
|
|
|
$fh = fopen('php://temp', 'r+');
|
|
fwrite($fh, $csv);
|
|
rewind($fh);
|
|
$header = fgetcsv($fh);
|
|
if (! $header) {
|
|
return $out;
|
|
}
|
|
|
|
$hmap = [];
|
|
foreach ($header as $i => $h) {
|
|
$hmap[strtolower(trim((string) $h))] = $i;
|
|
}
|
|
while (($row = fgetcsv($fh)) !== false) {
|
|
$tipo = null;
|
|
if (isset($hmap['o_r_s'])) {
|
|
$tipo = strtoupper(trim((string) ($row[$hmap['o_r_s']] ?? '')));
|
|
}
|
|
if (! in_array($tipo, ['O', 'R'], true)) {
|
|
continue;
|
|
}
|
|
|
|
$mese = null;
|
|
if (isset($hmap['n_mese'])) {
|
|
$val = trim((string) ($row[$hmap['n_mese']] ?? ''));
|
|
if ($val !== '' && is_numeric($val)) {
|
|
$mese = max(1, min(12, (int) $val));
|
|
}
|
|
}
|
|
if ($mese === null) {
|
|
continue;
|
|
}
|
|
|
|
$anno = null;
|
|
foreach (['str_anno'] as $k) {
|
|
if (isset($hmap[$k])) {
|
|
$s = trim((string) ($row[$hmap[$k]] ?? ''));
|
|
if ($s && preg_match('/(\d{4})/', $s, $m)) {
|
|
$anno = (int) $m[1];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ($anno === null) {
|
|
foreach (['dt_empag', 'dt1_da', 'data_emissione'] as $k) {
|
|
if (isset($hmap[$k])) {
|
|
$s = trim((string) ($row[$hmap[$k]] ?? ''));
|
|
if ($s && preg_match('/^(\d{4})-/', $s, $m)) {
|
|
$anno = (int) $m[1];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($anno === null) {
|
|
continue;
|
|
}
|
|
|
|
if (! isset($out[$anno])) {
|
|
$out[$anno] = ['O' => [], 'R' => []];
|
|
}
|
|
if ($tipo === 'O') {
|
|
$out[$anno]['O'][] = $mese;
|
|
} else {
|
|
$out[$anno]['R'][] = $mese;
|
|
}
|
|
}
|
|
// Unique/sorted
|
|
foreach ($out as $y => $tipi) {
|
|
$out[$y]['O'] = array_values(array_unique(array_filter($out[$y]['O'], fn($m) => $m >= 1 && $m <= 12)));
|
|
sort($out[$y]['O']);
|
|
$out[$y]['R'] = array_values(array_unique(array_filter($out[$y]['R'], fn($m) => $m >= 1 && $m <= 12)));
|
|
sort($out[$y]['R']);
|
|
}
|
|
return $out;
|
|
}
|
|
}
|