++ gruppi di servizio: sorvegliante con utenza limitata, pacchetto di territori, assegnazione ai membri

Il gruppo ha un sorvegliante (proclamatore con utenza: email e password
temporanea, senza conferma dell'email) e dei membri; l'amministratore
mette in blocco dei territori nel pacchetto del gruppo. Il pacchetto non
e' un'assegnazione e non tocca le statistiche; quando il sorvegliante da'
un territorio a un membro nasce un'assegnazione normale con
gruppo_servizio_id. Al rientro il territorio torna in reparto. Sciogliere
il gruppo libera pacchetto e membri e disattiva l'utenza.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Francesco Picone
2026-10-19 17:51:37 +02:00
parent 5d0ae06eca
commit 96084499f5
41 changed files with 2298 additions and 224 deletions

View File

@@ -7,6 +7,7 @@ use App\Jobs\ImportTerritoryPdfFolder;
use App\Models\AnnoTeocratico;
use App\Models\Assegnazione;
use App\Models\Campagna;
use App\Models\GruppoServizio;
use App\Models\Proclamatore;
use App\Models\Setting;
use App\Models\Territorio;
@@ -469,6 +470,7 @@ class XmlExchange extends Component
try {
DB::transaction(function () use ($xml, $actorId, $attore, $attoreEraAmministratore, &$stats) {
Assegnazione::query()->delete();
GruppoServizio::query()->delete();
Campagna::query()->delete();
Territorio::query()->withTrashed()->forceDelete();
Proclamatore::query()->withTrashed()->forceDelete();
@@ -520,6 +522,7 @@ class XmlExchange extends Component
// accounts in the file are (re)created. $userMap remaps the
// created_by/returned_by FKs on the assignments below.
$userMap = [];
$utentiDaCollegare = [];
foreach (($xml->users->user ?? []) as $userNode) {
$email = trim((string) ($userNode->email ?? ''));
$legacyId = (string) ($userNode['legacy_id'] ?? '');
@@ -589,6 +592,13 @@ class XmlExchange extends Component
DB::table('users')->where('id', $user->id)->update($meta);
// Utenza di un sorvegliante: si collega al proclamatore dopo che
// i proclamatori sono stati importati (vedi i gruppi di servizio).
$utentiDaCollegare[$user->id] = [
'proclamatore' => (string) ($userNode->legacy_proclamatore_id ?? ''),
'disattivato_at' => $this->normalizeDateTimeForDb((string) ($userNode->disattivato_at ?? '')),
];
$stats['utenti_importati']++;
if ($legacyId !== '') {
$userMap[$legacyId] = $user->id;
@@ -624,6 +634,7 @@ class XmlExchange extends Component
}
$proclamatoreMap = [];
$gruppiDeiProclamatori = [];
foreach (($xml->proclamatori->proclamatore ?? []) as $proclamatoreNode) {
$p = Proclamatore::create([
'nome' => (string) $proclamatoreNode->nome,
@@ -634,14 +645,42 @@ class XmlExchange extends Component
]);
$this->applyRowMeta('proclamatori', $p->id, $proclamatoreNode, true);
$stats['proclamatori_importati']++;
$gruppiDeiProclamatori[$p->id] = (string) ($proclamatoreNode->legacy_gruppo_id ?? '');
$legacyId = (string) ($proclamatoreNode['legacy_id'] ?? '');
if ($legacyId !== '') {
$proclamatoreMap[$legacyId] = $p->id;
}
}
$annoMap = [];
foreach (($xml->anni_teocratici->anno ?? []) as $annoNode) {
// Gruppi di servizio, dopo i proclamatori: il sorvegliante è uno di
// loro. Poi i collegamenti che li aspettavano (membri, utenze).
$gruppoMap = [];
foreach (($xml->gruppi_servizio->gruppo ?? []) as $gruppoNode) {
$legacySorvegliante = (string) ($gruppoNode->legacy_sorvegliante_id ?? '');
$g = GruppoServizio::create([
'nome' => (string) $gruppoNode->nome,
'sorvegliante_id' => $proclamatoreMap[$legacySorvegliante] ?? null,
'sciolto_at' => $this->normalizeDateTimeForDb((string) ($gruppoNode->sciolto_at ?? '')),
]);
$this->applyRowMeta('gruppi_servizio', $g->id, $gruppoNode);
$legacyId = (string) ($gruppoNode['legacy_id'] ?? '');
if ($legacyId !== '') {
$gruppoMap[$legacyId] = $g->id;
}
}
foreach ($gruppiDeiProclamatori as $idProclamatore => $legacyGruppo) {
if (isset($gruppoMap[$legacyGruppo])) {
DB::table('proclamatori')->where('id', $idProclamatore)->update(['gruppo_servizio_id' => $gruppoMap[$legacyGruppo]]);
}
}
foreach ($utentiDaCollegare as $idUtente => $collegamento) {
DB::table('users')->where('id', $idUtente)->update([
'proclamatore_id' => $proclamatoreMap[$collegamento['proclamatore']] ?? null,
'disattivato_at' => $collegamento['disattivato_at'],
]);
}
$annoMap = []; foreach (($xml->anni_teocratici->anno ?? []) as $annoNode) {
$label = (string) $annoNode->label;
$anno = AnnoTeocratico::create([
'label' => $label,
@@ -706,6 +745,7 @@ class XmlExchange extends Component
'note' => (string) ($territorioNode->note ?? ''),
'attivo' => ((int) ($territorioNode->attivo ?? 1)) === 1,
'prioritario' => ((int) ($territorioNode->prioritario ?? 0)) === 1,
'gruppo_servizio_id' => $gruppoMap[(string) ($territorioNode->legacy_gruppo_id ?? '')] ?? null,
'pdf_path' => $pdfPath !== '' ? $pdfPath : null,
'thumbnail_path' => $thumbnailPath !== '' ? $thumbnailPath : null,
]);
@@ -768,6 +808,7 @@ class XmlExchange extends Component
'campaign_id' => $legacyCampagna !== '' && isset($campagnaMap[$legacyCampagna]) ? $campagnaMap[$legacyCampagna] : null,
'link_sent' => ((int) ($assegnazioneNode->link_sent ?? 0)) === 1,
'link_da_inviare' => ((int) ($assegnazioneNode->link_da_inviare ?? 0)) === 1,
'gruppo_servizio_id' => $gruppoMap[(string) ($assegnazioneNode->legacy_gruppo_id ?? '')] ?? null,
'link_canale' => in_array((string) ($assegnazioneNode->link_canale ?? ''), array_keys(Assegnazione::CANALI_LINK), true) ? (string) $assegnazioneNode->link_canale : null,
'pdf_access_code' => $pdfAccessCode !== '' ? $pdfAccessCode : null,
'note' => (string) ($assegnazioneNode->note ?? ''),
@@ -824,16 +865,20 @@ class XmlExchange extends Component
// remember_token no: all'import si azzera comunque, e nel file
// sarebbe solo una credenziale in più per chi lo tiene.
'roles' => $u->roles->pluck('name')->all(),
// Sorvegliante di gruppo: il proclamatore collegato e se l'utenza è disattivata.
'proclamatore_id' => $u->proclamatore_id ? (string) $u->proclamatore_id : '',
'disattivato_at' => optional($u->disattivato_at)->toIso8601String() ?? '',
'created_at' => optional($u->created_at)->toIso8601String() ?? '',
'updated_at' => optional($u->updated_at)->toIso8601String() ?? '',
])->all(),
'zones' => $this->righeEsportate(Zona::query()->orderBy('id'), ['id', 'nome', 'attivo', 'created_at', 'updated_at']),
'tipologie' => $this->righeEsportate(Tipologia::query()->orderBy('id'), ['id', 'nome', 'attivo', 'created_at', 'updated_at']),
'proclamatori' => $this->righeEsportate(Proclamatore::query()->withTrashed()->orderBy('id'), ['id', 'nome', 'cognome', 'attivo', 'deleted_at', 'created_at', 'updated_at']),
'territori' => $this->righeEsportate(Territorio::query()->withTrashed()->orderBy('id'), ['id', 'numero', 'zona_id', 'tipologia_id', 'confini', 'note', 'attivo', 'prioritario', 'pdf_path', 'thumbnail_path', 'deleted_at', 'created_at', 'updated_at']),
'proclamatori' => $this->righeEsportate(Proclamatore::query()->withTrashed()->orderBy('id'), ['id', 'nome', 'cognome', 'attivo', 'gruppo_servizio_id', 'deleted_at', 'created_at', 'updated_at']),
'gruppi_servizio' => $this->righeEsportate(GruppoServizio::query()->orderBy('id'), ['id', 'nome', 'sorvegliante_id', 'sciolto_at', 'created_at', 'updated_at']),
'territori' => $this->righeEsportate(Territorio::query()->withTrashed()->orderBy('id'), ['id', 'numero', 'zona_id', 'tipologia_id', 'confini', 'note', 'attivo', 'prioritario', 'gruppo_servizio_id', 'pdf_path', 'thumbnail_path', 'deleted_at', 'created_at', 'updated_at']),
'anni_teocratici' => $this->righeEsportate(AnnoTeocratico::query()->orderBy('id'), ['id', 'label', 'start_date', 'end_date', 'created_at', 'updated_at']),
'campagne' => $this->righeEsportate(Campagna::query()->orderBy('id'), ['id', 'descrizione', 'start_date', 'end_date', 'created_at', 'updated_at']),
'assegnazioni' => $this->righeEsportate(Assegnazione::query()->orderBy('id'), ['id', 'territorio_id', 'proclamatore_id', 'anno_teocratico_id', 'campaign_id', 'assigned_at', 'returned_at', 'counted_in_campaign', 'note', 'created_by', 'returned_by', 'link_sent', 'link_da_inviare', 'link_canale', 'pdf_access_code', 'created_at', 'updated_at']),
'assegnazioni' => $this->righeEsportate(Assegnazione::query()->orderBy('id'), ['id', 'territorio_id', 'proclamatore_id', 'anno_teocratico_id', 'campaign_id', 'assigned_at', 'returned_at', 'counted_in_campaign', 'note', 'created_by', 'returned_by', 'link_sent', 'link_da_inviare', 'link_canale', 'gruppo_servizio_id', 'pdf_access_code', 'created_at', 'updated_at']),
];
}
@@ -1232,6 +1277,8 @@ class XmlExchange extends Component
$this->addXmlText($node, 'email', (string) ($user['email'] ?? ''));
$this->addXmlText($node, 'password', (string) ($user['password'] ?? ''));
$node->addChild('email_verified_at', (string) ($user['email_verified_at'] ?? ''));
$node->addChild('legacy_proclamatore_id', (string) ($user['proclamatore_id'] ?? ''));
$node->addChild('disattivato_at', (string) ($user['disattivato_at'] ?? ''));
$node->addChild('created_at', (string) ($user['created_at'] ?? ''));
$node->addChild('updated_at', (string) ($user['updated_at'] ?? ''));
$rolesNode = $node->addChild('roles');
@@ -1265,6 +1312,7 @@ class XmlExchange extends Component
$this->addXmlText($node, 'nome', (string) ($proclamatore['nome'] ?? ''));
$this->addXmlText($node, 'cognome', (string) ($proclamatore['cognome'] ?? ''));
$node->addChild('attivo', (string) ((int) ($proclamatore['attivo'] ?? 1)));
$node->addChild('legacy_gruppo_id', (string) ($proclamatore['gruppo_servizio_id'] ?? ''));
$node->addChild('deleted_at', (string) ($proclamatore['deleted_at'] ?? ''));
$this->addTimestampNodes($node, $proclamatore);
}
@@ -1280,6 +1328,7 @@ class XmlExchange extends Component
$this->addXmlText($node, 'note', (string) ($territorio['note'] ?? ''));
$node->addChild('attivo', (string) ((int) ($territorio['attivo'] ?? 1)));
$node->addChild('prioritario', (string) ((int) ($territorio['prioritario'] ?? 0)));
$node->addChild('legacy_gruppo_id', (string) ($territorio['gruppo_servizio_id'] ?? ''));
$this->addXmlText($node, 'pdf_path', (string) ($territorio['pdf_path'] ?? ''));
$this->addXmlText($node, 'thumbnail_path', (string) ($territorio['thumbnail_path'] ?? ''));
$node->addChild('deleted_at', (string) ($territorio['deleted_at'] ?? ''));
@@ -1296,6 +1345,17 @@ class XmlExchange extends Component
$this->addTimestampNodes($node, $anno);
}
// Gruppi di servizio: il sorvegliante è un proclamatore (legacy id).
$gruppiNode = $xml->addChild('gruppi_servizio');
foreach (($dataset['gruppi_servizio'] ?? []) as $gruppo) {
$node = $gruppiNode->addChild('gruppo');
$this->addLegacyIdAttribute($node, $gruppo);
$this->addXmlText($node, 'nome', (string) ($gruppo['nome'] ?? ''));
$node->addChild('legacy_sorvegliante_id', (string) ($gruppo['sorvegliante_id'] ?? ''));
$node->addChild('sciolto_at', (string) ($gruppo['sciolto_at'] ?? ''));
$this->addTimestampNodes($node, $gruppo);
}
$campagneNode = $xml->addChild('campagne');
foreach ($dataset['campagne'] as $campagna) {
$node = $campagneNode->addChild('campagna');
@@ -1321,6 +1381,7 @@ class XmlExchange extends Component
$node->addChild('counted_in_campaign', (string) ((int) ($assegnazione['counted_in_campaign'] ?? 0)));
$node->addChild('link_sent', (string) ((int) ($assegnazione['link_sent'] ?? 0)));
$node->addChild('link_da_inviare', (string) ((int) ($assegnazione['link_da_inviare'] ?? 0)));
$node->addChild('legacy_gruppo_id', (string) ($assegnazione['gruppo_servizio_id'] ?? ''));
$this->addXmlText($node, 'link_canale', (string) ($assegnazione['link_canale'] ?? ''));
$this->addXmlText($node, 'pdf_access_code', (string) ($assegnazione['pdf_access_code'] ?? ''));
$this->addXmlText($node, 'note', (string) ($assegnazione['note'] ?? ''));