@page "/ispezione" @using Microsoft.Extensions.Logging @using SteUp.Shared.Components.Layout @using SteUp.Shared.Components.Layout.Overlay @using SteUp.Shared.Components.SingleElements @using SteUp.Shared.Components.SingleElements.Card @using SteUp.Shared.Components.SingleElements.MessageBox @using SteUp.Shared.Core.Dto @using SteUp.Shared.Core.Entities @using SteUp.Shared.Core.Enum @using SteUp.Shared.Core.Helpers @using SteUp.Shared.Core.Interface.IntegryApi @using SteUp.Shared.Core.Interface.LocalDb @using SteUp.Shared.Core.Interface.System @using SteUp.Shared.Core.Messages.Ispezione @using SteUp.Shared.Core.Messages.Scheda @inject NewSchedaService NewScheda @inject CompleteInspectionService CompleteInspection @inject IIspezioniService IspezioniService @inject IDialogService Dialog @inject IIntegrySteupService IntegrySteupService @inject IFileManager FileManager @inject ILogger Logger @implements IDisposable
@* §6.6 Pannello di riepilogo: primo elemento della pagina, superficie tenue, con lo stato del documento e i conteggi sempre a vista. *@

@Titolo

@Ispezione.Data.ToString("d MMMM yyyy")

@SchedeList.Count
@(SchedeList.Count == 1 ? "scheda" : "schede")
@DaInviare
da inviare
@Bozze
da completare
@if (SchedeList.Count > 0) {
@Inviate di @SchedeList.Count inviate @PercentualeInviate%
}
@if (Bozze > 0) { @* Feedback inline: resta a video finche' il problema esiste, a differenza del toast che sparisce. *@ } @if (SchedeGrouped.Count == 0) {

Nessuna scheda

@if (!IsCompleted) {

Tocca + nella barra in basso per creare la prima scheda di questa ispezione.

}
} else { @foreach (var group in SchedeGrouped) { var daInviare = group.Value.CountDaInviare(); var daCompletare = group.Value.CountBozze();

@group.Key.Descrizione

@(group.Value.Count == 1 ? "1 scheda" : $"{group.Value.Count} schede")

@if (!IsCompleted && NetworkService.IsNetworkAvailable() && daInviare > 0) {
Invia @daInviare
}
@if (daCompletare > 0) {
}
@foreach (var scheda in group.Value) { }
} }
@code { private List SchedeList { get; set; } = []; private Dictionary> SchedeGrouped { get; set; } = []; private bool VisibleOverlay { get; set; } private bool _isBusy; private bool _success; private string? _progressMessage; private ConfirmMessageBox _exportRepartoBox = null!; private Ispezione Ispezione => SteupDataService.InspectionPageState.Ispezione; private bool IsCompleted => Ispezione.Stato == StatusEnum.Completata; private int DaInviare => SchedeList.CountDaInviare(); private int Bozze => SchedeList.CountBozze(); private int Inviate => SchedeList.Count(s => s.GetSyncState() == SyncState.Inviato); private int PercentualeInviate => SchedeList.Count == 0 ? 0 : (int)Math.Round(Inviate * 100.0 / SchedeList.Count); private string Progresso => (PercentualeInviate / 100.0).ToString("0.###", System.Globalization.CultureInfo.InvariantCulture); private string Titolo => SteupDataService.PuntiVenditaList .Find(x => x.CodMdep != null && x.CodMdep.EqualsIgnoreCase(Ispezione.CodMdep))? .Descrizione is { Length: > 0 } descrizione ? descrizione : Ispezione.CodMdep; private string Sottotitolo => Ispezione.CodMdep; protected override void OnInitialized() { NewScheda.OnNewScheda += LoadSchede; CompleteInspection.OnComplete += HandleCompleteInspection; LoadSchede(); } private void LoadSchede() { var ispezione = Ispezione; InvokeAsync(async () => { SchedeList = await IspezioniService.GetAllSchedeOfIspezioneAsync( ispezione.CodMdep, ispezione.Data, ispezione.Rilevatore ); GroupSchede(); StateHasChanged(); }); } private async void HandleCompleteInspection() { // Guardia anti doppio-invio: il completamento è irreversibile. if (_isBusy) return; _isBusy = true; try { await InvokeAsync(() => { VisibleOverlay = true; StateHasChanged(); }); var ispezione = Ispezione; // Le bozze non sono inviabili: l'ispezione non si chiude finché ci sono. var bozze = SchedeList.CountBozze(); if (bozze > 0) { await InvokeAsync(() => { VisibleOverlay = false; StateHasChanged(); }); await Dialog.ShowWarning( bozze == 1 ? "C'è 1 scheda da completare. Completala o eliminala prima di concludere l'ispezione." : $"Ci sono {bozze} schede da completare. Completale o eliminale prima di concludere l'ispezione." ); return; } var daInviare = ispezione.Schede.Where(x => x.ActivityId == null && !x.Bozza).ToList(); var totale = daInviare.Count; var indice = 0; var fallite = 0; foreach (var scheda in daInviare) { indice++; await SetProgress($"Invio scheda {indice} di {totale}…"); var apiResponse = await IntegrySteupService.SaveScheda( new SaveRequestDto { LocalIdScheda = scheda.Id, ActivityTypeId = scheda.ActivityTypeId, CodJfas = scheda.CodJfas, CodMdep = ispezione.CodMdep, DataCreazione = ispezione.Data, Note = scheda.Note, PersonaRif = scheda.Responsabile, Barcodes = scheda.Articoli.ConvertAll(x => x.Barcode), Scandeza = (ScadenzaEnum)scheda.Scadenza, // Aggancia all'ispezione esistente: dopo la prima scheda l'id e' noto, // così le successive non creano nuove ispezioni. ParentActivityId = ispezione.ActivityId } ); // Fallita: resta "Da inviare". Non completeremo l'ispezione finché tutte non sono arrivate. if (apiResponse == null) { fallite++; continue; } scheda.ActivityId = apiResponse.ActivityIdScheda; await IspezioniService.UpdateSchedaAsync(scheda); await IspezioniService.UpdateActivityIdIspezioneAsync( ispezione.CodMdep, ispezione.Data, ispezione.Rilevatore, apiResponse.ActivityIdIspezione ); // Aggiorna l'id in memoria per l'iterazione successiva. ispezione.ActivityId = apiResponse.ActivityIdIspezione; if (scheda.ImageNames == null) continue; var fileList = (await FileManager.GetInspectionFiles(ispezione, scheda.ImageNames))? .Where(x => x.ToUpload).ToList(); if (fileList == null) continue; var totFoto = fileList.Count; var indiceFoto = 0; foreach (var file in fileList) { indiceFoto++; await SetProgress($"Scheda {indice}/{totale} · foto {indiceFoto}/{totFoto}…"); await IntegrySteupService.UploadFile(scheda.ActivityId!, file.FileBytes!, file.Name!); } } // Integrità dati: se anche una sola scheda non è arrivata al server, NON completare. // L'ispezione resta editabile e le schede fallite restano "Da inviare". if (fallite > 0) { await InvokeAsync(() => { _progressMessage = null; StateHasChanged(); }); LoadSchede(); // rinfresca i badge: riuscite → "Inviato", fallite → restano "Da inviare" await Dialog.ShowWarning( fallite == 1 ? "1 scheda non è stata inviata e resta salvata sul dispositivo. Controlla la connessione e riprova prima di concludere l'ispezione." : $"{fallite} schede non sono state inviate e restano salvate sul dispositivo. Controlla la connessione e riprova prima di concludere l'ispezione." ); return; } await SetProgress("Completamento ispezione…"); await IntegrySteupService.CompleteInspection(ispezione.ActivityId!); await IspezioniService.UpdateStatoIspezioneAsync( ispezione.CodMdep, ispezione.Data, ispezione.Rilevatore, StatusEnum.Completata ); ispezione.Stato = StatusEnum.Completata; // Riscontro al momento più importante: la spunta di successo prima di chiudere l'overlay. await InvokeAsync(() => { _progressMessage = "Ispezione inviata"; _success = true; StateHasChanged(); }); await Task.Delay(1800); } catch (Exception e) { OnError(e, e.Message); } finally { _isBusy = false; _success = false; _progressMessage = null; await InvokeAsync(() => { VisibleOverlay = false; StateHasChanged(); }); } } private Task SetProgress(string message) => InvokeAsync(() => { _progressMessage = message; StateHasChanged(); }); private void GroupSchede() { SchedeGrouped = SchedeList .Where(s => s.Reparto != null) .GroupBy(s => s.CodJfas) .ToDictionary( g => g.First().Reparto!, g => g.ToList() ); } private void OnSchedaModified(Scheda obj) { var index = SchedeList.FindIndex(x => x.Id.Equals(obj.Id)); if (index >= 0) SchedeList[index] = obj; GroupSchede(); StateHasChanged(); } private void OnSchedaDeleted(Scheda obj) { SchedeList.Remove(obj); GroupSchede(); StateHasChanged(); } private async Task ExportReparto(JtbFasiDto jtbFasi) { // Guardia anti doppio-tap: un secondo tocco durante l'invio non ri-esporta il batch. if (_isBusy) return; var confirmed = await _exportRepartoBox.ShowAsync(); if (confirmed is not true) return; _isBusy = true; VisibleOverlay = true; StateHasChanged(); try { var ispezione = Ispezione; // Le bozze restano sul dispositivo: si esporta solo ciò che è completo. var daEsportare = SchedeGrouped[jtbFasi].FindAll(x => !x.Bozza); if (daEsportare.Count == 0) { Snackbar.Add("Nessuna scheda completa da inviare in questo reparto", Severity.Info); return; } var saveRequest = daEsportare.ConvertAll(x => { return new SaveRequestDto { LocalIdScheda = x.Id, ActivityTypeId = x.ActivityTypeId, CodJfas = x.CodJfas, CodMdep = ispezione.CodMdep, DataCreazione = ispezione.Data, Note = x.Note, PersonaRif = x.Responsabile, Barcodes = x.Articoli.ConvertAll(y => y.Barcode), Scandeza = (ScadenzaEnum)x.Scadenza, // Aggancia all'ispezione esistente sul server (evita di crearne una nuova a ogni export). ParentActivityId = ispezione.ActivityId }; }); var apiResponse = await IntegrySteupService.SaveMultipleSchede(saveRequest); if (apiResponse == null) { Snackbar.Add("Invio non riuscito. Le schede restano sul dispositivo, riprova.", Severity.Error); return; } ispezione.ActivityId = apiResponse.ActivityIdIspezione; await IspezioniService.UpdateActivityIdIspezioneAsync(ispezione.CodMdep, ispezione.Data, UserSession.User.Username, apiResponse.ActivityIdIspezione ); if (!apiResponse.ActivityIdSchedaList.IsNullOrEmpty()) { foreach (var scheda in daEsportare) { var activityId = apiResponse.ActivityIdSchedaList!.Find(x => x.LocalId != null && x.LocalId == scheda.Id )?.ActivityId; if (activityId == null) continue; scheda.ActivityId = activityId; await IspezioniService.UpdateActivityIdSchedaAsync(scheda.Id, scheda.ActivityId); } } Snackbar.Add(daEsportare.Count == 1 ? "Scheda inviata" : $"{daEsportare.Count} schede inviate", Severity.Success); } catch (Exception e) { OnError(e, e.Message); } finally { _isBusy = false; VisibleOverlay = false; StateHasChanged(); } } private void OnError(Exception? e, string? technicalMessage) { // Il dettaglio tecnico va nei log, non davanti al rilevatore in campo. if (e != null) Logger.LogError(e, technicalMessage); _ = Dialog.ShowError( "Invio non riuscito. Il lavoro resta salvato sul dispositivo: controlla la connessione e riprova." ); } void IDisposable.Dispose() { NewScheda.OnNewScheda -= LoadSchede; CompleteInspection.OnComplete -= HandleCompleteInspection; } }