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

412 lines
13 KiB
Dart
Raw Normal View History

2026-05-08 12:28:14 +02:00
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_cubit.dart';
2026-05-12 11:14:48 +02:00
import 'package:flux/features/customers/models/customer_model.dart';
2026-06-02 13:12:21 +02:00
import 'package:flux/features/master_data/providers/models/provider_model.dart';
import 'package:flux/features/master_data/providers/models/provider_model_extensions.dart';
import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
2026-05-08 12:28:14 +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:uuid/uuid.dart';
part 'operation_form_state.dart';
class OperationFormCubit extends Cubit<OperationFormState> {
final OperationsRepository _repository = GetIt.I<OperationsRepository>();
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
final Uuid _uuid = const Uuid();
OperationFormCubit({
StaffMemberModel? createdBy,
OperationModel? existingOperation,
}) : super(
OperationFormState(
operation:
existingOperation ??
OperationModel.empty().copyWith(
staffId: createdBy?.id,
staffDisplayName: createdBy?.name,
),
),
);
2026-05-08 12:28:14 +02:00
Future<void> initForm({
OperationModel? existingOperation,
String? operationId,
}) async {
emit(state.copyWith(status: OperationFormStatus.loading));
try {
if (existingOperation != null) {
emit(
state.copyWith(
operation: existingOperation,
status: OperationFormStatus.ready,
),
);
} else if (operationId != null) {
emit(state.copyWith(status: OperationFormStatus.loading));
try {
final operation = await _repository.fetchOperationById(operationId);
emit(
state.copyWith(
operation: operation,
status: OperationFormStatus.ready,
),
);
} on Exception catch (e) {
emit(
state.copyWith(
status: OperationFormStatus.failure,
errorMessage: e.toString(),
),
);
}
2026-05-08 12:28:14 +02:00
} else {
// NUOVA PRATICA: Creiamo un nuovo Batch UUID
final currentStore = _sessionCubit.state.currentStore;
final companyId = _sessionCubit.state.company?.id ?? '';
final newOperation = state.operation.copyWith(
companyId: companyId,
storeId: currentStore?.id,
status: OperationStatus.success,
reference: '',
batchUuid: _uuid.v4(),
);
2026-05-08 12:28:14 +02:00
emit(
state.copyWith(
operation: newOperation,
2026-05-08 12:28:14 +02:00
status: OperationFormStatus.ready,
),
);
}
} catch (e) {
emit(
state.copyWith(
status: OperationFormStatus.failure,
errorMessage: "Errore inizializzazione form: $e",
),
);
}
}
// --- LOGICA BATCH ---
void _prepareNextOperationInBatch() {
final current = state.operation;
emit(
state.copyWith(
2026-06-03 12:08:59 +02:00
status: OperationFormStatus.ready,
2026-05-08 12:28:14 +02:00
operation: OperationModel(
companyId: current.companyId,
storeId: current.storeId,
storeDisplayName: current.storeDisplayName,
2026-06-03 12:08:59 +02:00
// 🥷 REINSERIAMO LO STAFF (Il "colpevole" era qui)
staffId: current.staffId,
staffDisplayName: current.staffDisplayName,
batchUuid: current.batchUuid,
customerId: current.customerId,
2026-05-12 11:14:48 +02:00
customer: current.customer,
2026-05-20 11:03:33 +02:00
reference: current.reference,
2026-05-08 12:28:14 +02:00
status: OperationStatus.draft,
createdAt: DateTime.now(),
2026-06-03 12:08:59 +02:00
// Mantieni isBusiness se vuoi che rimanga coerente col cliente
isBusiness: current.isBusiness,
2026-05-08 12:28:14 +02:00
),
),
);
}
// --- SALVATAGGIO ---
Future<void> saveOperation({
required OperationStatus targetStatus,
required bool keepAdding,
}) async {
emit(
state.copyWith(status: OperationFormStatus.saving, errorMessage: null),
);
try {
2026-05-20 11:03:33 +02:00
OperationModel operationToSave = state.operation.copyWith(
status: targetStatus,
);
if (operationToSave.reference.isEmpty) {
if (operationToSave.customer != null &&
operationToSave.customer!.phoneNumber.isNotEmpty) {
operationToSave = operationToSave.copyWith(
reference: '${operationToSave.customer?.phoneNumber} - auto',
);
} else {
operationToSave = operationToSave.copyWith(
reference: 'Nessun riferimento',
);
}
}
2026-05-08 12:28:14 +02:00
final savedOperation = await _repository.saveFullOperation(
operation: operationToSave,
);
if (keepAdding) {
// Salviamo nella "memoria" del batch le pratiche create finora
final updatedBatchList = List<OperationModel>.from(
state.savedBatchOperations,
)..add(savedOperation);
emit(
state.copyWith(
status: OperationFormStatus.successAndAddAnother,
savedBatchOperations: updatedBatchList,
),
);
// Pulisce i campi per la prossima operazione
_prepareNextOperationInBatch();
} else {
emit(
state.copyWith(
status: OperationFormStatus.success,
operation: savedOperation, // Aggiorniamo con l'ID restituito dal DB
),
);
}
} catch (e) {
emit(
state.copyWith(
status: OperationFormStatus.failure,
errorMessage: e.toString(),
),
);
}
}
Future<String?> saveOperationDraft() async {
try {
final operationToSave = state.operation;
if (operationToSave.customerId == null ||
operationToSave.customerId!.isEmpty) {
throw Exception('Seleziona un cliente prima di poter usare il QR');
}
final savedOperation = await _repository.saveFullOperation(
operation: operationToSave,
);
emit(
state.copyWith(
operation: savedOperation,
status: OperationFormStatus.ready,
),
);
return savedOperation.id;
} catch (e) {
emit(
state.copyWith(
status: OperationFormStatus.failure,
errorMessage: e.toString(),
),
);
return null;
}
}
// --- GESTIONE DEI CAMPI IN TEMPO REALE ---
void updateFields({
String? reference,
String? note,
String? type,
String? providerId,
String? providerDisplayName,
2026-06-03 12:08:59 +02:00
String? subType,
2026-05-08 12:28:14 +02:00
String? description,
DateTime? expirationDate,
int? quantity,
String? modelId,
String? modelDisplayName,
String? staffId,
String? staffDisplayName,
OperationStatus? status,
2026-05-19 11:54:59 +02:00
bool? isBusiness,
2026-05-08 12:28:14 +02:00
bool clearProvider = false,
bool clearType = false,
2026-06-03 12:08:59 +02:00
bool clearSubType = false,
2026-05-08 12:28:14 +02:00
bool clearDescription = false,
bool clearExpiration = false,
bool clearQuantity = false,
bool clearModel = false,
}) {
final current = state.operation;
int? newQuantity;
if (clearQuantity) newQuantity = 1;
if (quantity != null && quantity <= 0) newQuantity = 0;
if (quantity != null && quantity > 0) newQuantity = quantity;
final updated = current.copyWith(
reference: reference ?? current.reference,
note: note ?? current.note,
providerId: clearProvider ? null : (providerId ?? current.providerId),
providerDisplayName: clearProvider
? null
: (providerDisplayName ?? current.providerDisplayName),
quantity: newQuantity ?? current.quantity,
type: clearType ? null : (type ?? current.type),
description: clearDescription
? null
: (description ?? current.description),
2026-06-03 12:08:59 +02:00
subType: clearSubType ? null : (subType ?? current.subType),
2026-05-08 12:28:14 +02:00
expirationDate: clearExpiration
? null
: (expirationDate ?? current.expirationDate),
modelId: clearModel ? null : (modelId ?? current.modelId),
modelDisplayName: clearModel
? null
: (modelDisplayName ?? current.modelDisplayName),
staffId: staffId ?? current.staffId,
staffDisplayName: staffDisplayName ?? current.staffDisplayName,
status: status ?? current.status,
2026-05-19 11:54:59 +02:00
isBusiness: isBusiness ?? current.isBusiness,
2026-05-08 12:28:14 +02:00
);
emit(state.copyWith(operation: updated));
}
void updateCustomer(CustomerModel customer) {
final bool isBusiness = customer.isBusiness;
final updatedOperation = state.operation.copyWith(
customer: customer,
customerId: customer.id,
isBusiness: isBusiness,
);
emit(state.copyWith(operation: updatedOperation));
}
2026-05-08 12:28:14 +02:00
// --- UTILS ---
2026-06-02 13:12:21 +02:00
void updateOperationType(
String newType, {
required List<ProviderModel> allProviders,
String? defaultProviderId,
}) {
// 1. Aggiorniamo il tipo nel modello in canna
// (Presumo tu abbia un metodo copyWith o simile)
2026-06-03 12:08:59 +02:00
final updatedOp = state.operation.copyWith(type: newType, subType: '');
2026-06-02 13:12:21 +02:00
// 2. Prepariamoci ad auto-selezionare il provider
String? newProviderId = updatedOp.providerId;
String? newProviderName = updatedOp.providerDisplayName;
// 3. LA LOGICA DI DEFAULT
if (defaultProviderId != null) {
// Troviamo il provider di default nella lista
final defaultProvider = allProviders
.where((p) => p.id == defaultProviderId)
.firstOrNull;
if (defaultProvider != null) {
// Usiamo l'extension appena creata!
if (defaultProvider.supportsOperation(newType)) {
newProviderId = defaultProvider.id;
newProviderName = defaultProvider.name;
} else {
// Se cambi tipo (es. da Mobile a Luce) e il default non lo supporta, sbianchiamo
newProviderId = null;
newProviderName = null;
}
}
}
// Emettiamo il nuovo stato
emit(
state.copyWith(
operation: updatedOp.copyWith(
providerId: newProviderId,
providerDisplayName: newProviderName,
),
),
);
}
void setTypeWithSmartDefaults({
required String newType,
required List<ProviderModel> allProviders,
String? defaultProviderId,
}) {
final currentOp = state.operation;
// -----------------------------------------
// 1. SMART DATES: Calcolo Scadenze Default
// -----------------------------------------
2026-05-08 12:28:14 +02:00
DateTime? defaultDate;
final now = DateTime.now();
2026-06-02 13:12:21 +02:00
if (newType == 'Energy') {
2026-05-08 12:28:14 +02:00
defaultDate = DateTime(now.year, now.month + 24, now.day);
}
2026-06-02 13:12:21 +02:00
if (newType == 'Fin') {
2026-05-08 12:28:14 +02:00
defaultDate = DateTime(now.year, now.month + 30, now.day);
}
2026-06-02 13:12:21 +02:00
if (newType == 'Entertainment') {
2026-05-08 12:28:14 +02:00
defaultDate = DateTime(now.year, now.month + 12, now.day);
}
2026-06-02 13:12:21 +02:00
// -----------------------------------------
// 2. SMART PROVIDER: Filtro e Auto-Selezione
// -----------------------------------------
String? newProviderId = currentOp.providerId;
String? newProviderName = currentOp.providerDisplayName;
// A) Il provider attuale è ancora compatibile col nuovo tipo scelto?
if (newProviderId != null && newProviderId.isNotEmpty) {
final currentProvider = allProviders
.where((p) => p.id == newProviderId)
.firstOrNull;
if (currentProvider == null ||
!currentProvider.supportsOperation(newType)) {
// Non è più compatibile (es. da TIM fisso passo a Energy). Lo sbianchiamo!
newProviderId = null;
newProviderName = null;
}
}
// B) Se non c'è un provider selezionato, proviamo ad auto-inserire quello di default del negozio
if ((newProviderId == null || newProviderId.isEmpty) &&
defaultProviderId != null) {
final defaultProvider = allProviders
.where((p) => p.id == defaultProviderId)
.firstOrNull;
// Controlliamo che il default del negozio supporti questa specifica operazione
if (defaultProvider != null &&
defaultProvider.supportsOperation(newType)) {
newProviderId = defaultProvider.id;
newProviderName = defaultProvider.name;
}
}
// -----------------------------------------
// 3. EMISSIONE DELLO STATO PULITO
// -----------------------------------------
emit(
state.copyWith(
operation: currentOp.copyWith(
type: newType,
2026-06-03 12:08:59 +02:00
subType:
2026-06-02 13:12:21 +02:00
'', // Resettiamo il sottotipo per evitare incongruenze (es. passo da Luce a DAZN)
expirationDate:
defaultDate, // Impostiamo la scadenza di default se calcolata
providerId: newProviderId,
providerDisplayName: newProviderName,
modelId: null,
modelDisplayName: null,
),
),
2026-05-08 12:28:14 +02:00
);
}
}