@@ -1,14 +1,13 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
import 'package:flux/core/utils/extensions.dart';
|
||||
import 'package:flux/features/attachments/models/attachment_model.dart';
|
||||
import 'package:flux/features/operations/data/operations_repository.dart';
|
||||
import 'package:flux/features/operations/models/operation_file_model.dart';
|
||||
import 'package:flux/features/operations/models/operation_model.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
part 'operation_files_events.dart';
|
||||
part 'operation_files_state.dart';
|
||||
@@ -29,9 +28,10 @@ class OperationFilesBloc
|
||||
on<LoadOperationFilesEvent>(_onLoadOperationFiles);
|
||||
on<AddOperationFilesEvent>(_onAddOperationFiles);
|
||||
on<UploadOperationFilesEvent>(_onUploadOperationFiles);
|
||||
on<UploadMultipleOperationFilesEvent>(_onUploadMultipleOperationFiles);
|
||||
on<DeleteOperationFilesEvent>(_onDeleteOperationFiles);
|
||||
on<ToggleOperationFileSelectionEvent>(_onToggleOperationFileSelection);
|
||||
on<LinkFilesToCustomerEvent>(_onLinkFilesToCustomer);
|
||||
|
||||
// Se il BLoC nasce con un ID, accendiamo subito lo stream!
|
||||
if (operationId != null) {
|
||||
add(LoadOperationFilesEvent(operationId: operationId));
|
||||
@@ -41,18 +41,53 @@ class OperationFilesBloc
|
||||
FutureOr<void> _onOperationsaved(
|
||||
OperationsavedEvent event,
|
||||
Emitter<OperationFilesState> emit,
|
||||
) {
|
||||
// 1. Aggiorniamo l'ID nello stato
|
||||
// 2. PIALLIAMO i file locali: ormai sono partiti per Supabase!
|
||||
// Così la UI si pulisce all'istante e aspetta quelli remoti.
|
||||
) async {
|
||||
// 1. Aggiorniamo l'ID e mettiamo in loading
|
||||
emit(
|
||||
state.copyWith(
|
||||
operationId: event.operationId,
|
||||
localFiles: [], // <-- LA MAGIA ANTI-DUPLICATI
|
||||
status: OperationFilesStatus.uploading,
|
||||
),
|
||||
);
|
||||
|
||||
// Lanciamo il caricamento
|
||||
// 2. RECUPERO E UPLOAD DEI FILE "PARCHEGGIATI" (Pratica Nuova)
|
||||
if (state.localFiles.isNotEmpty) {
|
||||
try {
|
||||
final List<Future<void>> uploadTasks = [];
|
||||
|
||||
for (var file in state.localFiles) {
|
||||
// Ricreiamo il PlatformFile dal nostro AttachmentModel
|
||||
// così il repository lo accetta senza fare storie!
|
||||
final fakePlatformFile = PlatformFile(
|
||||
name: '${file.name}.${file.extension}',
|
||||
size: file.fileSize,
|
||||
bytes: file.localBytes,
|
||||
);
|
||||
|
||||
uploadTasks.add(
|
||||
_repository.uploadAndRegisterOperationFile(
|
||||
operationId: event.operationId, // L'ID APPENA NATO!
|
||||
pickedFile: fakePlatformFile,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Lanciamo tutti gli upload in parallelo
|
||||
await Future.wait(uploadTasks);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: OperationFilesStatus.failure,
|
||||
error: "Errore upload post-salvataggio: $e",
|
||||
),
|
||||
);
|
||||
return; // Ci fermiamo qui se esplode qualcosa
|
||||
}
|
||||
}
|
||||
|
||||
// 3. FINE DEI GIOCHI! Svuotiamo i locali, passiamo a success e accendiamo lo Stream
|
||||
emit(state.copyWith(localFiles: [], status: OperationFilesStatus.success));
|
||||
|
||||
add(LoadOperationFilesEvent(operationId: event.operationId));
|
||||
}
|
||||
|
||||
@@ -60,17 +95,14 @@ class OperationFilesBloc
|
||||
LoadOperationFilesEvent event,
|
||||
Emitter<OperationFilesState> emit,
|
||||
) async {
|
||||
// Usiamo l'ID dell'evento, e se non c'è usiamo quello dello stato
|
||||
final currentId = event.operationId ?? state.operationId;
|
||||
|
||||
if (currentId != null) {
|
||||
emit(state.copyWith(status: OperationFilesStatus.loading));
|
||||
|
||||
await emit.forEach(
|
||||
_repository.getOperationFilesStream(
|
||||
currentId,
|
||||
), // <-- Usiamo l'ID corretto!
|
||||
onData: (data) => state.copyWith(
|
||||
_repository.getOperationFilesStream(currentId),
|
||||
onData: (List<AttachmentModel> data) => state.copyWith(
|
||||
status: OperationFilesStatus.success,
|
||||
remoteFiles: data,
|
||||
),
|
||||
@@ -87,13 +119,15 @@ class OperationFilesBloc
|
||||
Emitter<OperationFilesState> emit,
|
||||
) async {
|
||||
final currentId = state.operationId;
|
||||
// BIVIO 1: PRATICA NUOVA (Nessun ID)
|
||||
|
||||
// BIVIO 1: PRATICA NUOVA (Nessun ID - salvataggio locale)
|
||||
if (currentId == null) {
|
||||
// Mettiamo i file nel "parcheggio" locale dello State
|
||||
final companyId = GetIt.I.get<SessionCubit>().state.company!.id!;
|
||||
final newLocalFiles = event.files.map((file) {
|
||||
return OperationFileModel(
|
||||
return AttachmentModel(
|
||||
id: null,
|
||||
operationId: operationId ?? '',
|
||||
companyId: companyId,
|
||||
operationId: '', // Sarà riempito al salvataggio
|
||||
name: file.name.fileNameWithoutExtension(),
|
||||
extension: file.name.fileExtension(),
|
||||
storagePath: '',
|
||||
@@ -101,29 +135,29 @@ class OperationFilesBloc
|
||||
localBytes: file.bytes,
|
||||
);
|
||||
}).toList();
|
||||
final List<OperationFileModel> updatedLocalFiles = [
|
||||
...state.localFiles,
|
||||
...newLocalFiles,
|
||||
];
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
localFiles: updatedLocalFiles,
|
||||
localFiles: [...state.localFiles, ...newLocalFiles],
|
||||
status: OperationFilesStatus.success,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// BIVIO 2: PRATICA ESISTENTE (Abbiamo l'ID)
|
||||
// BIVIO 2: PRATICA ESISTENTE (Abbiamo l'ID - Upload immediato)
|
||||
emit(state.copyWith(status: OperationFilesStatus.uploading));
|
||||
try {
|
||||
// Logica identica a quella che abbiamo fatto per i clienti
|
||||
final List<Future<void>> uploadTasks = [];
|
||||
for (var file in event.files) {
|
||||
await _repository.uploadAndRegisterOperationFile(
|
||||
operationId: operationId!,
|
||||
pickedFile: file,
|
||||
uploadTasks.add(
|
||||
_repository.uploadAndRegisterOperationFile(
|
||||
operationId: currentId,
|
||||
pickedFile: file,
|
||||
),
|
||||
);
|
||||
}
|
||||
await Future.wait(uploadTasks);
|
||||
emit(state.copyWith(status: OperationFilesStatus.success));
|
||||
} catch (e) {
|
||||
emit(
|
||||
@@ -139,21 +173,55 @@ class OperationFilesBloc
|
||||
UploadOperationFilesEvent event,
|
||||
Emitter<OperationFilesState> emit,
|
||||
) async {
|
||||
if (event.pickedFiles == null && event.photos == null) return;
|
||||
if (event.pickedFiles!.isEmpty && event.photos!.isEmpty) return;
|
||||
if ((event.pickedFiles == null || event.pickedFiles!.isEmpty) &&
|
||||
(event.photos == null || event.photos!.isEmpty)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.operationId == null) return;
|
||||
|
||||
// BIVIO 2: PRATICA ESISTENTE (Abbiamo l'ID
|
||||
emit(state.copyWith(status: OperationFilesStatus.uploading));
|
||||
try {
|
||||
// Logica identica a quella che abbiamo fatto per i clienti
|
||||
if (event.pickedFiles != null && event.pickedFiles!.isNotEmpty) {
|
||||
final List<Future<void>> uploadTasks = [];
|
||||
|
||||
// 1. Gestione Documenti normali (PlatformFile)
|
||||
if (event.pickedFiles != null) {
|
||||
for (var file in event.pickedFiles!) {
|
||||
await _repository.uploadAndRegisterOperationFile(
|
||||
operationId: state.operationId!,
|
||||
pickedFile: file,
|
||||
uploadTasks.add(
|
||||
_repository.uploadAndRegisterOperationFile(
|
||||
operationId: state.operationId!,
|
||||
pickedFile: file,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Gestione Foto Fotocamera (XFile)
|
||||
if (event.photos != null) {
|
||||
for (var photo in event.photos!) {
|
||||
// Leggiamo i byte asincronamente
|
||||
final bytes = await photo.readAsBytes();
|
||||
final fileSize = await photo.length();
|
||||
|
||||
// Lo travestiamo da PlatformFile per passarlo al Repository!
|
||||
final fakePlatformFile = PlatformFile(
|
||||
name: photo.name,
|
||||
size: fileSize,
|
||||
bytes: bytes,
|
||||
path: photo.path,
|
||||
);
|
||||
|
||||
uploadTasks.add(
|
||||
_repository.uploadAndRegisterOperationFile(
|
||||
operationId: state.operationId!,
|
||||
pickedFile: fakePlatformFile,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Esecuzione parallela di tutti i documenti e foto
|
||||
await Future.wait(uploadTasks);
|
||||
emit(state.copyWith(status: OperationFilesStatus.success));
|
||||
} catch (e) {
|
||||
emit(
|
||||
@@ -165,48 +233,6 @@ class OperationFilesBloc
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onUploadMultipleOperationFiles(
|
||||
UploadMultipleOperationFilesEvent event,
|
||||
Emitter<OperationFilesState> emit,
|
||||
) async {
|
||||
if (event.files.isEmpty) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: OperationFilesStatus.failure,
|
||||
error: "Nessun file selezionato",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
emit(state.copyWith(status: OperationFilesStatus.uploading, error: null));
|
||||
try {
|
||||
// 2. Creiamo una lista di "Promesse" (Futures) per il repository
|
||||
final List<Future<void>> uploadTasks = [];
|
||||
for (var file in event.files) {
|
||||
// Aggiungiamo il task alla lista, ma NON usiamo await qui dentro!
|
||||
uploadTasks.add(
|
||||
_repository.uploadAndRegisterOperationFile(
|
||||
operationId: state.operationId!,
|
||||
pickedFile: file,
|
||||
),
|
||||
);
|
||||
}
|
||||
// 3. ESECUZIONE PARALLELA!
|
||||
// Aspettiamo che tutti i file siano caricati contemporaneamente.
|
||||
await Future.wait(uploadTasks);
|
||||
// 4. GRAN FINALE: Tutto caricato, emettiamo il success!
|
||||
emit(state.copyWith(status: OperationFilesStatus.success));
|
||||
} catch (e) {
|
||||
// Se anche un solo file fallisce, catturiamo l'errore
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: OperationFilesStatus.failure,
|
||||
error: "Errore durante l'upload multiplo: $e",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onDeleteOperationFiles(
|
||||
DeleteOperationFilesEvent event,
|
||||
Emitter<OperationFilesState> emit,
|
||||
@@ -231,7 +257,7 @@ class OperationFilesBloc
|
||||
ToggleOperationFileSelectionEvent event,
|
||||
Emitter<OperationFilesState> emit,
|
||||
) {
|
||||
List<OperationFileModel> selectedFiles = List.from(state.selectedFiles);
|
||||
final selectedFiles = List<AttachmentModel>.from(state.selectedFiles);
|
||||
if (selectedFiles.contains(event.file)) {
|
||||
selectedFiles.remove(event.file);
|
||||
} else {
|
||||
@@ -239,4 +265,62 @@ class OperationFilesBloc
|
||||
}
|
||||
emit(state.copyWith(selectedFiles: selectedFiles));
|
||||
}
|
||||
|
||||
FutureOr<void> _onLinkFilesToCustomer(
|
||||
LinkFilesToCustomerEvent event,
|
||||
Emitter<OperationFilesState> emit,
|
||||
) async {
|
||||
if (state.selectedFiles.isEmpty) return;
|
||||
|
||||
// BIVIO 1: PRATICA NUOVA (Modalità Locale)
|
||||
if (state.operationId == null) {
|
||||
// Mappiamo i file locali: se sono tra quelli selezionati, iniettiamo il customerId
|
||||
final updatedLocalFiles = state.localFiles.map((file) {
|
||||
if (state.selectedFiles.contains(file)) {
|
||||
return file.copyWith(customerId: event.customerId);
|
||||
}
|
||||
return file;
|
||||
}).toList();
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
localFiles: updatedLocalFiles,
|
||||
selectedFiles: [], // Svuotiamo la selezione dopo averli associati
|
||||
status: OperationFilesStatus.success, // o un toast di feedback
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// BIVIO 2: PRATICA ESISTENTE (Modalità Remota su DB)
|
||||
emit(state.copyWith(status: OperationFilesStatus.loading));
|
||||
try {
|
||||
final List<Future<void>> linkTasks = [];
|
||||
|
||||
for (var file in state.selectedFiles) {
|
||||
linkTasks.add(
|
||||
_repository.copyFileToCustomer(
|
||||
file: file,
|
||||
customerId: event.customerId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await Future.wait(linkTasks);
|
||||
|
||||
// Svuotiamo la selezione.
|
||||
// NON serve aggiornare la lista a mano, perché il DB si aggiorna
|
||||
// e lo Stream di Supabase spingerà automaticamente in UI i file aggiornati!
|
||||
emit(
|
||||
state.copyWith(status: OperationFilesStatus.success, selectedFiles: []),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: OperationFilesStatus.failure,
|
||||
error: "Errore associazione: $e",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class OperationsavedEvent extends OperationFilesEvent {
|
||||
|
||||
class LoadOperationFilesEvent extends OperationFilesEvent {
|
||||
final String? operationId;
|
||||
final OperationModel? operation;
|
||||
final AttachmentModel? operation;
|
||||
const LoadOperationFilesEvent({this.operationId, this.operation});
|
||||
|
||||
@override
|
||||
@@ -34,23 +34,25 @@ class AddOperationFilesEvent extends OperationFilesEvent {
|
||||
|
||||
class UploadOperationFilesEvent extends OperationFilesEvent {
|
||||
final List<PlatformFile>? pickedFiles;
|
||||
final List<File>? photos;
|
||||
final List<XFile>? photos;
|
||||
const UploadOperationFilesEvent({this.pickedFiles, this.photos});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [pickedFiles, photos];
|
||||
}
|
||||
|
||||
class UploadMultipleOperationFilesEvent extends OperationFilesEvent {
|
||||
final List<PlatformFile> files;
|
||||
const UploadMultipleOperationFilesEvent(this.files);
|
||||
class LinkFilesToCustomerEvent extends OperationFilesEvent {
|
||||
final String customerId;
|
||||
|
||||
const LinkFilesToCustomerEvent({required this.customerId});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [files];
|
||||
List<Object?> get props => [customerId];
|
||||
}
|
||||
|
||||
class DeleteOperationFilesEvent extends OperationFilesEvent {}
|
||||
|
||||
class ToggleOperationFileSelectionEvent extends OperationFilesEvent {
|
||||
final OperationFileModel file;
|
||||
final AttachmentModel file;
|
||||
const ToggleOperationFileSelectionEvent(this.file);
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@ class OperationFilesState extends Equatable {
|
||||
final String? operationId;
|
||||
final OperationFilesStatus status;
|
||||
final String? error;
|
||||
final List<OperationFileModel> localFiles;
|
||||
final List<OperationFileModel> remoteFiles;
|
||||
final List<AttachmentModel> localFiles;
|
||||
final List<AttachmentModel> remoteFiles;
|
||||
|
||||
final List<OperationFileModel> selectedFiles;
|
||||
final List<AttachmentModel> selectedFiles;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
@@ -30,15 +30,15 @@ class OperationFilesState extends Equatable {
|
||||
selectedFiles,
|
||||
];
|
||||
|
||||
List<OperationFileModel> get allFiles => [...remoteFiles, ...localFiles];
|
||||
List<AttachmentModel> get allFiles => [...remoteFiles, ...localFiles];
|
||||
|
||||
OperationFilesState copyWith({
|
||||
String? operationId,
|
||||
OperationFilesStatus? status,
|
||||
String? error,
|
||||
List<OperationFileModel>? localFiles,
|
||||
List<OperationFileModel>? remoteFiles,
|
||||
List<OperationFileModel>? selectedFiles,
|
||||
List<AttachmentModel>? localFiles,
|
||||
List<AttachmentModel>? remoteFiles,
|
||||
List<AttachmentModel>? selectedFiles,
|
||||
}) {
|
||||
return OperationFilesState(
|
||||
operationId: operationId ?? this.operationId,
|
||||
|
||||
@@ -4,19 +4,18 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
import 'package:flux/core/utils/extensions.dart';
|
||||
import 'package:flux/features/attachments/models/attachment_model.dart';
|
||||
import 'package:flux/features/operations/data/operations_repository.dart';
|
||||
import 'package:flux/features/operations/models/energy_operation_model.dart';
|
||||
import 'package:flux/features/operations/models/entertainment_operation_model.dart';
|
||||
import 'package:flux/features/operations/models/fin_operation_model.dart';
|
||||
import 'package:flux/features/operations/models/operation_file_model.dart';
|
||||
import 'package:flux/features/operations/models/operation_model.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
part 'operations_state.dart';
|
||||
|
||||
class OperationsCubit extends Cubit<OperationsState> {
|
||||
final OperationsRepository _repository = GetIt.I<OperationsRepository>();
|
||||
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
||||
final Uuid _uuid = const Uuid(); // Generatore di UUID per il batch
|
||||
|
||||
OperationsCubit()
|
||||
: super(const OperationsState(status: OperationsStatus.initial));
|
||||
@@ -24,17 +23,13 @@ class OperationsCubit extends Cubit<OperationsState> {
|
||||
// --- CARICAMENTO E PAGINAZIONE ---
|
||||
|
||||
Future<void> loadOperations({bool refresh = false}) async {
|
||||
// Se stiamo già caricando, evitiamo chiamate doppie
|
||||
if (state.status == OperationsStatus.loading) return;
|
||||
|
||||
// Se non è un refresh e abbiamo già raggiunto la fine dei dati, ci fermiamo
|
||||
if (!refresh && state.hasReachedMax) return;
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: OperationsStatus.loading,
|
||||
errorMessage: null,
|
||||
// Se è un refresh, svuotiamo la lista attuale per mostrare lo shimmer/loading
|
||||
allOperations: refresh ? [] : state.allOperations,
|
||||
hasReachedMax: refresh ? false : state.hasReachedMax,
|
||||
),
|
||||
@@ -56,7 +51,6 @@ class OperationsCubit extends Cubit<OperationsState> {
|
||||
dateRange: state.dateRange,
|
||||
);
|
||||
|
||||
// Se ricevi meno record del limite, significa che non ce ne sono altri sul DB
|
||||
final bool reachedMax = newOperations.length < 50;
|
||||
|
||||
emit(
|
||||
@@ -72,7 +66,7 @@ class OperationsCubit extends Cubit<OperationsState> {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: OperationsStatus.failure,
|
||||
errorMessage: "Errore nel caricamento servizi: $e",
|
||||
errorMessage: "Errore nel caricamento operazioni: $e",
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -80,7 +74,6 @@ class OperationsCubit extends Cubit<OperationsState> {
|
||||
|
||||
// --- GESTIONE FILTRI ---
|
||||
|
||||
/// Aggiorna i parametri di ricerca e ricarica da zero
|
||||
void updateFilters({String? query, DateTimeRange? range}) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
@@ -91,15 +84,11 @@ class OperationsCubit extends Cubit<OperationsState> {
|
||||
loadOperations(refresh: true);
|
||||
}
|
||||
|
||||
/// Pulisce tutti i filtri
|
||||
void clearFilters() {
|
||||
emit(state.copyWith(query: '', dateRange: null));
|
||||
loadOperations(refresh: true);
|
||||
}
|
||||
|
||||
// --- GESTIONE BOZZA (DRAFT) ---
|
||||
|
||||
/// Inizializza un nuovo servizio o ne carica uno esistente per la modifica
|
||||
void initOperationForm({
|
||||
OperationModel? existingOperation,
|
||||
String? operationId,
|
||||
@@ -123,14 +112,16 @@ class OperationsCubit extends Cubit<OperationsState> {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// Crea un template vuoto con lo store di default (se disponibile)
|
||||
// NUOVA PRATICA: Creiamo un nuovo Batch UUID
|
||||
emit(
|
||||
state.copyWith(
|
||||
currentOperation: OperationModel(
|
||||
storeId: _sessionCubit.state.currentStore?.id ?? '',
|
||||
number: '', // Sarà compilato dall'utente
|
||||
reference: '',
|
||||
createdAt: DateTime.now(),
|
||||
companyId: _sessionCubit.state.company!.id!,
|
||||
status: OperationStatus.draft,
|
||||
batchUuid: _uuid.v4(), // <-- GENERIAMO IL BATCH UNIVOCO
|
||||
),
|
||||
status: OperationsStatus.ready,
|
||||
),
|
||||
@@ -138,68 +129,25 @@ class OperationsCubit extends Cubit<OperationsState> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Metodo generico per aggiornare i campi base (AL, MNP, Note, ecc.)
|
||||
void updateField({
|
||||
int? al,
|
||||
int? mnp,
|
||||
int? nip,
|
||||
int? unica,
|
||||
int? telepass,
|
||||
String? note,
|
||||
String? number,
|
||||
bool? isBozza,
|
||||
bool? resultOk,
|
||||
String? customerId,
|
||||
String? customerDisplayName,
|
||||
}) {
|
||||
/// MAGIA PURA: Prepara il form per inserire un altro servizio nella stessa pratica.
|
||||
/// Mantiene il Cliente, il Batch e lo Store, ma svuota il resto.
|
||||
void prepareNextOperationInBatch() {
|
||||
if (state.currentOperation == null) return;
|
||||
|
||||
final updated = state.currentOperation!.copyWith(
|
||||
al: al,
|
||||
mnp: mnp,
|
||||
nip: nip,
|
||||
unica: unica,
|
||||
telepass: telepass,
|
||||
note: note,
|
||||
number: number,
|
||||
isBozza: isBozza,
|
||||
resultOk: resultOk,
|
||||
customerId: customerId,
|
||||
customerDisplayName: customerDisplayName,
|
||||
);
|
||||
final current = state.currentOperation!;
|
||||
|
||||
emit(state.copyWith(currentOperation: updated));
|
||||
}
|
||||
|
||||
// --- GESTIONE MODULI COMPLESSI ---
|
||||
|
||||
void updateEnergyOperations(List<EnergyOperationModel> energyList) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
currentOperation: state.currentOperation?.copyWith(
|
||||
energyOperations: energyList,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void updateFinOperations(List<FinOperationModel> finList) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
currentOperation: state.currentOperation?.copyWith(
|
||||
finOperations: finList,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void updateEntertainmentOperations(
|
||||
List<EntertainmentOperationModel> entList,
|
||||
) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
currentOperation: state.currentOperation?.copyWith(
|
||||
entertainmentOperations: entList,
|
||||
status: OperationsStatus.ready,
|
||||
currentOperation: OperationModel(
|
||||
companyId: current.companyId,
|
||||
storeId: current.storeId,
|
||||
storeDisplayName: current.storeDisplayName,
|
||||
batchUuid: current.batchUuid, // <-- MANTIENE IL COLLEGAMENTO
|
||||
customerId: current.customerId, // <-- MANTIENE IL CLIENTE
|
||||
customerDisplayName: current.customerDisplayName,
|
||||
status: OperationStatus.draft,
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -208,35 +156,33 @@ class OperationsCubit extends Cubit<OperationsState> {
|
||||
// --- PERSISTENZA ---
|
||||
|
||||
Future<void> saveCurrentOperation({
|
||||
required bool isBozza,
|
||||
required OperationStatus targetStatus,
|
||||
bool shouldPop = true,
|
||||
List<OperationFileModel>? files,
|
||||
}) async {
|
||||
if (state.currentOperation == null) return;
|
||||
|
||||
emit(state.copyWith(status: OperationsStatus.saving, errorMessage: null));
|
||||
try {
|
||||
// 1. Aggiorniamo il flag bozza in base a quale pulsante ha premuto l'utente
|
||||
final operationToSave = state.currentOperation!.copyWith(
|
||||
isBozza: isBozza,
|
||||
files: files,
|
||||
status: targetStatus,
|
||||
);
|
||||
|
||||
// 2. Salvataggio corazzato
|
||||
final updatedOperation = await _repository.saveFullOperation(
|
||||
operationToSave,
|
||||
);
|
||||
|
||||
// 3. Reset e ricaricamento
|
||||
emit(
|
||||
state.copyWith(
|
||||
// Se non facciamo Pop (es. l'utente vuole aggiungere un altro servizio), non killiamo l'operazione corrente
|
||||
status: shouldPop
|
||||
? OperationsStatus.saved
|
||||
: OperationsStatus.savedNoPop,
|
||||
currentOperation: shouldPop ? null : updatedOperation,
|
||||
),
|
||||
);
|
||||
await loadOperations(refresh: true);
|
||||
|
||||
// Ricarica in background per la dashboard
|
||||
loadOperations(refresh: true);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
@@ -247,115 +193,29 @@ class OperationsCubit extends Cubit<OperationsState> {
|
||||
}
|
||||
}
|
||||
|
||||
// --- GESTIONE ALLEGATI LOCALI ---
|
||||
// --- RECUPERO OPERAZIONI DELLO STESSO BATCH (Per UI di riepilogo) ---
|
||||
|
||||
void addAttachments(List<PlatformFile> files) {
|
||||
final newAttachments = files.map((file) {
|
||||
return OperationFileModel(
|
||||
id: null, // Meglio null se non è su DB
|
||||
operationId: state.currentOperation?.id ?? '',
|
||||
name: file.name.fileNameWithoutExtension(),
|
||||
extension: file.name.fileExtension(),
|
||||
storagePath: '',
|
||||
fileSize: file.size,
|
||||
localBytes: file.bytes,
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
}).toList();
|
||||
/// Puoi usare questa funzione se nella UI vuoi mostrare "Hai inserito 3 servizi in questa pratica"
|
||||
List<OperationModel> getOperationsInCurrentBatch() {
|
||||
if (state.currentOperation == null) return [];
|
||||
final currentBatch = state.currentOperation!.batchUuid;
|
||||
|
||||
// Creiamo una nuova lista pulita
|
||||
final List<OperationFileModel> updatedList = [
|
||||
...(state.currentOperation?.files ?? []),
|
||||
...newAttachments,
|
||||
];
|
||||
|
||||
// Emettiamo lo stato assicurandoci che il OperationModel venga clonato
|
||||
if (state.currentOperation != null) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
currentOperation: state.currentOperation!.copyWith(
|
||||
files: updatedList,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
// Filtriamo dalla lista caricata (o potresti fare una query diretta a Supabase se preferisci)
|
||||
return state.allOperations
|
||||
.where(
|
||||
(op) =>
|
||||
op.batchUuid == currentBatch &&
|
||||
op.id != state.currentOperation!.id,
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
void removeAttachment(int index) {
|
||||
void updateField({String? customerId, String? customerDisplayName}) {
|
||||
if (state.currentOperation == null) return;
|
||||
|
||||
final updatedList = List<OperationFileModel>.from(
|
||||
state.currentOperation!.files,
|
||||
final updated = state.currentOperation!.copyWith(
|
||||
customerId: customerId,
|
||||
customerDisplayName: customerDisplayName,
|
||||
);
|
||||
updatedList.removeAt(index);
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
currentOperation: state.currentOperation?.copyWith(files: updatedList),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void saveAndCopyFileToCustomer(List<OperationFileModel> selectedFiles) async {
|
||||
final currentOperation = state.currentOperation;
|
||||
|
||||
// 1. Check di sicurezza: se non c'è il cliente, non sappiamo dove copiare
|
||||
if (currentOperation == null || currentOperation.customerId == null) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: OperationsStatus.failure,
|
||||
errorMessage:
|
||||
"Impossibile copiare: nessun cliente associato alla pratica.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
emit(state.copyWith(status: OperationsStatus.loading));
|
||||
|
||||
try {
|
||||
// 2. SALVATAGGIO CORAZZATO
|
||||
// Chiamiamo il repo e otteniamo la pratica con TUTTI i file ora dotati di ID e storagePath
|
||||
final updatedOperation = await _repository.saveFullOperation(
|
||||
currentOperation,
|
||||
);
|
||||
|
||||
// 3. COPIA RELAZIONALE
|
||||
// Per ogni file che l'utente ha selezionato nella UI, cerchiamo la sua versione
|
||||
// "ufficiale" (quella con lo storagePath) nel modello appena tornato dal DB.
|
||||
for (var selectedFile in selectedFiles) {
|
||||
// Cerchiamo il match nel modello aggiornato
|
||||
final persistedFile = updatedOperation.files.firstWhere(
|
||||
(f) =>
|
||||
f.name == selectedFile.name &&
|
||||
f.extension == selectedFile.extension,
|
||||
orElse: () => throw Exception(
|
||||
"File ${selectedFile.name} non trovato dopo il salvataggio.",
|
||||
),
|
||||
);
|
||||
|
||||
// Creiamo il link nel database del cliente
|
||||
await _repository.copyFileToCustomer(
|
||||
file: persistedFile,
|
||||
customerId: currentOperation.customerId!,
|
||||
);
|
||||
}
|
||||
|
||||
// 4. AGGIORNAMENTO STATO
|
||||
// Aggiorniamo il Cubit con il servizio salvato così la UI mostra i file come "Remoti"
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: OperationsStatus.success,
|
||||
currentOperation: updatedOperation,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: OperationsStatus.failure,
|
||||
errorMessage: "Errore durante il salvataggio e copia: $e",
|
||||
),
|
||||
);
|
||||
}
|
||||
emit(state.copyWith(currentOperation: updated));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user