Files
flux/lib/features/operations/blocs/operations_cubit.dart

250 lines
7.8 KiB
Dart
Raw Normal View History

import 'package:equatable/equatable.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_cubit.dart';
2026-04-29 19:25:48 +02:00
import 'package:flux/core/utils/extensions.dart';
import 'package:flux/features/attachments/models/attachment_model.dart';
2026-05-01 10:11:44 +02:00
import 'package:flux/features/operations/data/operations_repository.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';
2026-05-01 10:11:44 +02:00
part 'operations_state.dart';
2026-05-01 10:11:44 +02:00
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
2026-05-01 10:11:44 +02:00
OperationsCubit()
: super(const OperationsState(status: OperationsStatus.initial));
// --- CARICAMENTO E PAGINAZIONE ---
2026-05-01 10:11:44 +02:00
Future<void> loadOperations({bool refresh = false}) async {
if (state.status == OperationsStatus.loading) return;
if (!refresh && state.hasReachedMax) return;
emit(
state.copyWith(
2026-05-01 10:11:44 +02:00
status: OperationsStatus.loading,
errorMessage: null,
2026-05-01 10:11:44 +02:00
allOperations: refresh ? [] : state.allOperations,
hasReachedMax: refresh ? false : state.hasReachedMax,
),
);
try {
2026-05-01 10:11:44 +02:00
final currentOffset = refresh ? 0 : state.allOperations.length;
final companyId = _sessionCubit.state.company?.id;
if (companyId == null) {
throw Exception("Company ID non trovato nella sessione");
}
2026-05-01 10:11:44 +02:00
final newOperations = await _repository.fetchOperations(
companyId: companyId,
offset: currentOffset,
limit: 50,
searchTerm: state.query,
dateRange: state.dateRange,
);
2026-05-01 10:11:44 +02:00
final bool reachedMax = newOperations.length < 50;
emit(
state.copyWith(
2026-05-01 10:11:44 +02:00
status: OperationsStatus.ready,
allOperations: refresh
? newOperations
: [...state.allOperations, ...newOperations],
hasReachedMax: reachedMax,
),
);
} catch (e) {
emit(
state.copyWith(
2026-05-01 10:11:44 +02:00
status: OperationsStatus.failure,
errorMessage: "Errore nel caricamento operazioni: $e",
),
);
}
}
// --- GESTIONE FILTRI ---
void updateFilters({String? query, DateTimeRange? range}) {
emit(
state.copyWith(
query: query ?? state.query,
dateRange: range ?? state.dateRange,
),
);
2026-05-01 10:11:44 +02:00
loadOperations(refresh: true);
}
void clearFilters() {
emit(state.copyWith(query: '', dateRange: null));
2026-05-01 10:11:44 +02:00
loadOperations(refresh: true);
}
2026-05-01 10:11:44 +02:00
void initOperationForm({
OperationModel? existingOperation,
String? operationId,
}) async {
2026-05-01 10:11:44 +02:00
if (existingOperation != null) {
emit(
state.copyWith(
2026-05-01 10:11:44 +02:00
currentOperation: existingOperation,
status: OperationsStatus.ready,
),
);
2026-05-01 10:11:44 +02:00
} else if (operationId != null) {
OperationModel? operationModel = state.allOperations.firstWhereOrNull(
(s) => s.id == operationId,
);
2026-05-01 10:11:44 +02:00
operationModel ??= await _repository.fetchOperationById(operationId);
emit(
state.copyWith(
2026-05-01 10:11:44 +02:00
currentOperation: operationModel,
status: OperationsStatus.ready,
),
);
} else {
// NUOVA PRATICA: Creiamo un nuovo Batch UUID
emit(
state.copyWith(
2026-05-01 10:11:44 +02:00
currentOperation: OperationModel(
storeId: _sessionCubit.state.currentStore?.id ?? '',
reference: '',
createdAt: DateTime.now(),
companyId: _sessionCubit.state.company!.id!,
status: OperationStatus.draft,
batchUuid: _uuid.v4(), // <-- GENERIAMO IL BATCH UNIVOCO
),
2026-05-01 10:11:44 +02:00
status: OperationsStatus.ready,
),
);
}
}
/// 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() {
2026-05-01 10:11:44 +02:00
if (state.currentOperation == null) return;
final current = state.currentOperation!;
emit(
state.copyWith(
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(),
),
),
);
}
// --- PERSISTENZA ---
2026-05-01 10:11:44 +02:00
Future<void> saveCurrentOperation({
required OperationStatus targetStatus,
bool shouldPop = true,
}) async {
2026-05-01 10:11:44 +02:00
if (state.currentOperation == null) return;
2026-05-01 10:11:44 +02:00
emit(state.copyWith(status: OperationsStatus.saving, errorMessage: null));
try {
2026-05-01 10:11:44 +02:00
final operationToSave = state.currentOperation!.copyWith(
status: targetStatus,
);
2026-05-01 10:11:44 +02:00
final updatedOperation = await _repository.saveFullOperation(
operationToSave,
);
emit(
state.copyWith(
// Se non facciamo Pop (es. l'utente vuole aggiungere un altro servizio), non killiamo l'operazione corrente
2026-05-01 10:11:44 +02:00
status: shouldPop
? OperationsStatus.saved
: OperationsStatus.savedNoPop,
currentOperation: shouldPop ? null : updatedOperation,
),
);
// Ricarica in background per la dashboard
loadOperations(refresh: true);
} catch (e) {
emit(
state.copyWith(
2026-05-01 10:11:44 +02:00
status: OperationsStatus.failure,
errorMessage: e.toString(),
),
);
}
}
// --- RECUPERO OPERAZIONI DELLO STESSO BATCH (Per UI di riepilogo) ---
/// 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;
// 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();
}
// --- GESTIONE DELLO STATO DEL FORM IN TEMPO REALE ---
void updateOperationFields({
String? customerId,
String? customerDisplayName,
String? type,
String? providerId,
String? subtype,
DateTime? expirationDate,
int? quantity,
// Aggiungiamo questi flag per forzare la pulizia dei campi quando cambi tipo
bool clearProvider = false,
bool clearType = false,
bool clearSubtype = false,
bool clearExpiration = false,
}) {
2026-05-01 10:11:44 +02:00
if (state.currentOperation == null) return;
final current = state.currentOperation!;
// Creiamo il modello aggiornato
// ATTENZIONE: adatta questa logica in base a come è scritto il tuo copyWith!
final updated = current.copyWith(
customerId: customerId,
customerDisplayName: customerDisplayName,
type: clearType ? null : type,
subtype: clearSubtype ? null : subtype,
expirationDate: clearExpiration ? null : expirationDate,
// Se clearProvider è true, forziamo una stringa vuota (o null se il tuo modello lo supporta)
providerId: clearProvider ? null : (providerId ?? current.providerId),
// Idem per subtype e date.
// Se expirationDate è nullabile nel copyWith, dovresti poterlo gestire
quantity: quantity ?? current.quantity,
);
emit(state.copyWith(currentOperation: updated));
}
}