406 lines
12 KiB
Dart
406 lines
12 KiB
Dart
import 'package:equatable/equatable.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:flux/core/blocs/session/session_cubit.dart';
|
|
import 'package:flux/features/customers/models/customer_model.dart';
|
|
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';
|
|
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,
|
|
),
|
|
),
|
|
);
|
|
|
|
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(),
|
|
),
|
|
);
|
|
}
|
|
} 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(),
|
|
);
|
|
emit(
|
|
state.copyWith(
|
|
operation: newOperation,
|
|
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(
|
|
status: OperationFormStatus.ready, // Torna ready per il nuovo form
|
|
operation: OperationModel(
|
|
companyId: current.companyId,
|
|
storeId: current.storeId,
|
|
storeDisplayName: current.storeDisplayName,
|
|
batchUuid: current.batchUuid, // MANTIENE IL COLLEGAMENTO
|
|
customerId: current.customerId, // MANTIENE IL CLIENTE
|
|
customer: current.customer,
|
|
reference: current.reference,
|
|
status: OperationStatus.draft,
|
|
createdAt: DateTime.now(),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// --- SALVATAGGIO ---
|
|
|
|
Future<void> saveOperation({
|
|
required OperationStatus targetStatus,
|
|
required bool keepAdding,
|
|
}) async {
|
|
emit(
|
|
state.copyWith(status: OperationFormStatus.saving, errorMessage: null),
|
|
);
|
|
|
|
try {
|
|
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',
|
|
);
|
|
}
|
|
}
|
|
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,
|
|
String? subtype,
|
|
String? description,
|
|
DateTime? expirationDate,
|
|
int? quantity,
|
|
String? modelId,
|
|
String? modelDisplayName,
|
|
String? staffId,
|
|
String? staffDisplayName,
|
|
OperationStatus? status,
|
|
bool? isBusiness,
|
|
|
|
bool clearProvider = false,
|
|
bool clearType = false,
|
|
bool clearSubtype = false,
|
|
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),
|
|
subtype: clearSubtype ? null : (subtype ?? current.subtype),
|
|
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,
|
|
isBusiness: isBusiness ?? current.isBusiness,
|
|
);
|
|
|
|
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));
|
|
}
|
|
|
|
// --- UTILS ---
|
|
|
|
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)
|
|
final updatedOp = state.operation.copyWith(type: newType, subtype: '');
|
|
|
|
// 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
|
|
// -----------------------------------------
|
|
DateTime? defaultDate;
|
|
final now = DateTime.now();
|
|
|
|
if (newType == 'Energy') {
|
|
defaultDate = DateTime(now.year, now.month + 24, now.day);
|
|
}
|
|
if (newType == 'Fin') {
|
|
defaultDate = DateTime(now.year, now.month + 30, now.day);
|
|
}
|
|
if (newType == 'Entertainment') {
|
|
defaultDate = DateTime(now.year, now.month + 12, now.day);
|
|
}
|
|
|
|
// -----------------------------------------
|
|
// 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,
|
|
subtype:
|
|
'', // 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,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|