@@ -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