refactor providers e basi per spedizioni

This commit is contained in:
2026-05-15 10:12:05 +02:00
parent ad35f641b3
commit f19f19a279
21 changed files with 1542 additions and 830 deletions

View File

@@ -1,176 +0,0 @@
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/master_data/providers/data/provider_repository.dart';
import 'package:flux/features/master_data/store/models/store_model.dart';
import 'package:get_it/get_it.dart';
import '../models/provider_model.dart';
class ProvidersState extends Equatable {
final List<ProviderModel> allProviders;
final List<String> associatedIds;
// NUOVO CAMPO: Lista dei provider pronti per essere usati nel form pratiche
final List<ProviderModel> activeProviders;
final bool isLoading;
final String? errorMessage;
const ProvidersState({
this.allProviders = const [],
this.associatedIds = const [],
this.activeProviders = const [], // Inizializza
this.isLoading = false,
this.errorMessage,
});
ProvidersState copyWith({
List<ProviderModel>? allProviders,
List<String>? associatedIds,
List<ProviderModel>? activeProviders, // Aggiungi qui
bool? isLoading,
String? errorMessage,
}) {
return ProvidersState(
allProviders: allProviders ?? this.allProviders,
associatedIds: associatedIds ?? this.associatedIds,
activeProviders: activeProviders ?? this.activeProviders, // Aggiungi qui
isLoading: isLoading ?? this.isLoading,
errorMessage:
errorMessage ??
this.errorMessage, // Correzione bug: mancava "?? this.errorMessage" nel tuo originale
);
}
@override
List<Object?> get props => [
allProviders,
associatedIds,
activeProviders, // Aggiungi qui
isLoading,
errorMessage,
];
}
class ProvidersCubit extends Cubit<ProvidersState> {
final ProviderRepository _repository = GetIt.I<ProviderRepository>();
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
ProvidersCubit() : super(const ProvidersState());
// Carica i provider della company e quelli associati a uno store specifico
Future<void> loadProviders({StoreModel? store}) async {
emit(state.copyWith(isLoading: true));
try {
final all = await _repository.fetchAllCompanyProviders(
_sessionCubit.state.company!.id!,
);
List<String> associated = [];
if (store != null) {
associated = await _repository.fetchAssociatedProviderIds(store.id!);
}
emit(
state.copyWith(
allProviders: all,
associatedIds: associated,
isLoading: false,
),
);
} catch (e) {
emit(state.copyWith(isLoading: false, errorMessage: e.toString()));
}
}
Future<void> loadActiveProvidersForStore(String storeId) async {
emit(state.copyWith(isLoading: true));
try {
final activeList = await _repository.fetchActiveProvidersForStore(
storeId,
);
emit(state.copyWith(activeProviders: activeList, isLoading: false));
} catch (e) {
emit(
state.copyWith(
isLoading: false,
errorMessage: "Errore caricamento gestori: $e",
),
);
}
}
// Aggiunge o rimuove l'associazione con lo store
Future<void> toggleProviderAssociation({
required String providerId,
required String storeId,
required bool isCurrentlyAssociated,
}) async {
try {
if (isCurrentlyAssociated) {
await _repository.disassociateProviderFromStore(
providerId: providerId,
storeId: storeId,
);
// Aggiorniamo lo stato locale rimuovendo l'ID
final newIds = List<String>.from(state.associatedIds)
..remove(providerId);
emit(state.copyWith(associatedIds: newIds));
} else {
await _repository.associateProviderToStore(
providerId: providerId,
storeId: storeId,
);
// Aggiorniamo lo stato locale aggiungendo l'ID
final newIds = List<String>.from(state.associatedIds)..add(providerId);
emit(state.copyWith(associatedIds: newIds));
}
} catch (e) {
emit(state.copyWith(errorMessage: "Errore durante l'aggiornamento: $e"));
}
}
// Salvataggio/Update anagrafica (nuovo o modifica)
Future<void> saveProvider(
ProviderModel provider,
List<String> selectedStoreIds,
) async {
emit(state.copyWith(isLoading: true));
// Assicuriamoci di settare la companyId prima di salvare
provider = provider.copyWith(companyId: _sessionCubit.state.company!.id);
try {
// 1. Salviamo l'anagrafica (upsert)
// Se è un nuovo provider, l'ID potrebbe essere generato qui dal DB
// Quindi carichiamo il risultato del salvataggio per avere l'ID
final response = await _repository.saveProvider(provider);
// Assumiamo che il saveProvider restituisca l'oggetto salvato con l'ID
final pId = provider.id ?? response.id;
// 2. Sincronizziamo i negozi
await _repository.syncProviderStores(pId!, selectedStoreIds);
// 3. Ricarichiamo tutto
await loadProviders();
} catch (e) {
emit(state.copyWith(isLoading: false, errorMessage: e.toString()));
}
}
Future<void> saveProviderWithStores(
ProviderModel provider,
List<String> storeIds,
) async {
emit(state.copyWith(isLoading: true));
try {
// 1. Salva l'anagrafica provider
await _repository.saveProvider(provider);
// 2. Sincronizza i negozi (la via più semplice è cancellare e reinserire
// o fare un confronto tra i presenti e i nuovi)
await _repository.syncProviderStores(provider.id!, storeIds);
await loadProviders();
} catch (e) {
emit(state.copyWith(isLoading: false, errorMessage: e.toString()));
}
}
}

View File

@@ -0,0 +1,182 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:supabase_flutter/supabase_flutter.dart'; // Per estrarre gli store
import '../models/provider_model.dart';
import '../models/provider_role.dart';
import '../models/provider_location_model.dart';
import '../data/provider_repository.dart';
part 'provider_form_state.dart';
class ProviderFormCubit extends Cubit<ProviderFormState> {
final ProviderRepository _repository = GetIt.I.get<ProviderRepository>();
final _client = Supabase.instance.client; // Lo usiamo al volo per gli store
ProviderFormCubit()
: super(ProviderFormState(provider: ProviderModel.empty(companyId: '')));
// --- INIZIALIZZAZIONE ---
Future<void> initForm({
required String companyId,
ProviderModel? existingProvider,
}) async {
emit(state.copyWith(status: ProviderFormStatus.loading));
try {
// 1. Scarichiamo tutti i negozi dell'azienda
final storesResponse = await _client
.from('store')
.select('id, name')
.eq('company_id', companyId);
// 2. Se stiamo modificando, carichiamo gli store collegati
List<String> linkedStoreIds = [];
if (existingProvider != null && existingProvider.id != null) {
// ... (Vecchio codice di recupero)
final links = await _client
.from('providers_in_stores')
.select('store_id')
.eq('provider_id', existingProvider.id!);
linkedStoreIds = (links as List)
.map((l) => l['store_id'] as String)
.toList();
} else {
// --- IL TOCCO NINJA: AUTO-SELEZIONE ---
// Se stiamo creando un nuovo fornitore e c'è 1 solo negozio in tutto il DB, accendilo!
if ((storesResponse as List).length == 1) {
linkedStoreIds.add(storesResponse.first['id'] as String);
}
}
emit(
state.copyWith(
status: ProviderFormStatus.initial,
provider: existingProvider ?? ProviderModel.empty(companyId: ''),
availableStores: storesResponse as List<dynamic>,
selectedStoreIds: linkedStoreIds,
localLocations: existingProvider?.locations ?? [],
),
);
} catch (e) {
emit(
state.copyWith(
status: ProviderFormStatus.failure,
errorMessage: 'Errore durante l\'inizializzazione: $e',
),
);
}
}
// --- AGGIORNAMENTO CAMPI ---
void updateFields({
String? name,
String? businessName,
String? vatNumber,
String? fiscalCode,
String? sdiCode,
String? emailPec,
}) {
emit(
state.copyWith(
provider: state.provider.copyWith(
name: name,
businessName: businessName,
vatNumber: vatNumber,
fiscalCode: fiscalCode,
sdiCode: sdiCode,
emailPec: emailPec,
),
),
);
}
// --- GESTIONE RUOLI (CHIPS) ---
void toggleRole(ProviderRole role) {
final currentRoles = List<ProviderRole>.from(state.provider.roles);
if (currentRoles.contains(role)) {
currentRoles.remove(role);
} else {
currentRoles.add(role);
}
emit(
state.copyWith(provider: state.provider.copyWith(roles: currentRoles)),
);
}
// --- GESTIONE NEGOZI ABILITATI (CHECKBOX) ---
void toggleStore(String storeId) {
final currentStoreIds = List<String>.from(state.selectedStoreIds);
if (currentStoreIds.contains(storeId)) {
currentStoreIds.remove(storeId);
} else {
currentStoreIds.add(storeId);
}
emit(state.copyWith(selectedStoreIds: currentStoreIds));
}
Future<void> addLocationLocal(ProviderLocationModel location) async {
final currentLocations = List<ProviderLocationModel>.from(
state.localLocations,
);
currentLocations.add(location);
emit(state.copyWith(localLocations: currentLocations));
}
void removeLocationLocal(int index) {
final currentLocations = List<ProviderLocationModel>.from(
state.localLocations,
);
if (index >= 0 && index < currentLocations.length) {
currentLocations.removeAt(index);
emit(state.copyWith(localLocations: currentLocations));
}
}
// --- SALVATAGGIO FINALE ---
Future<void> save() async {
// Sicurezza di base
if (state.provider.name.trim().isEmpty) {
emit(
state.copyWith(
status: ProviderFormStatus.failure,
errorMessage: 'Il nome è obbligatorio',
),
);
return;
}
emit(state.copyWith(status: ProviderFormStatus.loading));
try {
// Passiamo provider e storeId al repository che farà la magia
final savedProvider = await _repository.saveProvider(
state.provider,
state.selectedStoreIds,
);
if (state.localLocations.isNotEmpty) {
for (var loc in state.localLocations) {
final locToSave = loc.copyWith(
providerId: savedProvider.id!,
companyId: savedProvider.companyId,
);
await _repository.saveLocation(locToSave);
}
}
emit(
state.copyWith(
status: ProviderFormStatus.success,
provider: savedProvider,
),
);
} catch (e) {
emit(
state.copyWith(
status: ProviderFormStatus.failure,
errorMessage: 'Errore di salvataggio: $e',
),
);
}
}
}

View File

@@ -0,0 +1,55 @@
part of 'provider_form_cubit.dart';
// Importa il tuo StoreModel se lo hai
enum ProviderFormStatus { initial, loading, success, failure }
class ProviderFormState extends Equatable {
final ProviderFormStatus status;
final ProviderModel provider;
// Dati di supporto per l'interfaccia
final List<dynamic>
availableStores; // Metti List<StoreModel> se hai il modello
final List<String> selectedStoreIds; // IDs dei negozi in cui è attivo
final List<ProviderLocationModel>
localLocations; // Sedi aggiunte prima del salvataggio
final String? errorMessage;
const ProviderFormState({
this.status = ProviderFormStatus.initial,
required this.provider,
this.availableStores = const [],
this.selectedStoreIds = const [],
this.localLocations = const [],
this.errorMessage,
});
ProviderFormState copyWith({
ProviderFormStatus? status,
ProviderModel? provider,
List<dynamic>? availableStores,
List<String>? selectedStoreIds,
List<ProviderLocationModel>? localLocations,
String? errorMessage,
}) {
return ProviderFormState(
status: status ?? this.status,
provider: provider ?? this.provider,
availableStores: availableStores ?? this.availableStores,
selectedStoreIds: selectedStoreIds ?? this.selectedStoreIds,
localLocations: localLocations ?? this.localLocations,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@override
List<Object?> get props => [
status,
provider,
availableStores,
selectedStoreIds,
localLocations,
errorMessage,
];
}

View File

@@ -0,0 +1,53 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:get_it/get_it.dart';
import '../models/provider_model.dart';
import '../data/provider_repository.dart';
part 'provider_list_state.dart';
class ProviderListCubit extends Cubit<ProviderListState> {
final ProviderRepository _repository = GetIt.I.get<ProviderRepository>();
ProviderListCubit() : super(const ProviderListState());
Future<void> loadProviders(String storeId) async {
emit(state.copyWith(status: ProviderListStatus.loading));
try {
final providers = await _repository.getProvidersByStore(storeId);
emit(
state.copyWith(
status: ProviderListStatus.success,
providers: providers,
),
);
} catch (e) {
emit(
state.copyWith(
status: ProviderListStatus.failure,
errorMessage: e.toString(),
),
);
}
}
Future<void> loadAllProviders() async {
emit(state.copyWith(status: ProviderListStatus.loading));
try {
final allProviders = await _repository.getAllCompanyProviders();
emit(
state.copyWith(
status: ProviderListStatus.success,
allProviders: allProviders,
),
);
} catch (e) {
emit(
state.copyWith(
status: ProviderListStatus.failure,
errorMessage: e.toString(),
),
);
}
}
}

View File

@@ -0,0 +1,34 @@
part of 'provider_list_cubit.dart';
enum ProviderListStatus { initial, loading, success, failure }
class ProviderListState extends Equatable {
final ProviderListStatus status;
final List<ProviderModel> providers;
final List<ProviderModel> allProviders;
final String? errorMessage;
const ProviderListState({
this.status = ProviderListStatus.initial,
this.providers = const [],
this.allProviders = const [],
this.errorMessage,
});
ProviderListState copyWith({
ProviderListStatus? status,
List<ProviderModel>? providers,
List<ProviderModel>? allProviders,
String? errorMessage,
}) {
return ProviderListState(
status: status ?? this.status,
providers: providers ?? this.providers,
allProviders: allProviders ?? this.allProviders,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@override
List<Object?> get props => [status, providers, allProviders, errorMessage];
}