providers-in-store #4

Merged
brontomark merged 3 commits from providers-in-store into main 2026-04-17 15:37:14 +02:00
9 changed files with 78 additions and 54 deletions
Showing only changes of commit 22a4f1dac4 - Show all commits

View File

@@ -71,9 +71,7 @@ class SessionBloc extends Bloc<SessionEvent, SessionState> {
); );
return; return;
} }
final availableStores = stores final availableStores = stores.map((s) => StoreModel.fromMap(s)).toList();
.map((s) => StoreModel.fromJson(s))
.toList();
// 3. Tutto ok, gestiamo le SharedPreferences per il negozio // 3. Tutto ok, gestiamo le SharedPreferences per il negozio
final prefs = GetIt.I.get<SharedPreferences>(); final prefs = GetIt.I.get<SharedPreferences>();
@@ -84,7 +82,7 @@ class SessionBloc extends Bloc<SessionEvent, SessionState> {
lastStoreId = stores.first['id']; lastStoreId = stores.first['id'];
await prefs.setString('last_store_id', lastStoreId!); await prefs.setString('last_store_id', lastStoreId!);
} }
final selectedStore = StoreModel.fromJson( final selectedStore = StoreModel.fromMap(
stores.firstWhere((s) => s['id'] == lastStoreId), stores.firstWhere((s) => s['id'] == lastStoreId),
); );
emit( emit(

View File

@@ -40,29 +40,22 @@ class ProviderRepository {
// Recupera tutti i provider di una company (per la lista generale) // Recupera tutti i provider di una company (per la lista generale)
Future<List<ProviderModel>> fetchAllCompanyProviders(String companyId) async { Future<List<ProviderModel>> fetchAllCompanyProviders(String companyId) async {
try { try {
// La magia è qui: selezioniamo tutto e chiediamo il conteggio (count)
// della tabella pivot providers_in_stores
final response = await _supabase final response = await _supabase
.from('provider') .from('provider')
.select(''' .select('''
*, *,
providers_in_stores(count) associated_stores:providers_in_stores (
store (
*
)
)
''') ''')
.eq('company_id', companyId) .eq('company_id', companyId)
.order('nome'); .order('nome');
return (response as List).map((m) { return (response as List).map((m) => ProviderModel.fromMap(m)).toList();
// Estraiamo il conteggio dalla struttura restituita da Supabase
// La risposta per ogni riga sarà tipo: { "id": "...", "providers_in_stores": [{"count": 5}] }
final storesList = m['providers_in_stores'] as List?;
final count = (storesList != null && storesList.isNotEmpty)
? storesList[0]['count'] as int
: 0;
return ProviderModel.fromMap(m).copyWith(storesCount: count);
}).toList();
} catch (e) { } catch (e) {
throw Exception('Errore fetch all providers: $e'); throw 'Errore fetch providers: $e';
} }
} }

View File

@@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flux/features/master_data/store/models/store_model.dart';
class ProviderModel extends Equatable { class ProviderModel extends Equatable {
final String? id; final String? id;
@@ -11,7 +12,7 @@ class ProviderModel extends Equatable {
final bool altro; final bool altro;
final bool isActive; final bool isActive;
final String companyId; final String companyId;
final int storesCount; final List<StoreModel> associatedStores;
const ProviderModel({ const ProviderModel({
this.id, this.id,
@@ -24,10 +25,21 @@ class ProviderModel extends Equatable {
required this.altro, required this.altro,
required this.isActive, required this.isActive,
required this.companyId, required this.companyId,
this.storesCount = 0, // Numero di store associati, default a 0 this.associatedStores = const [],
}); });
factory ProviderModel.fromMap(Map<String, dynamic> map) { factory ProviderModel.fromMap(Map<String, dynamic> map) {
// Estraiamo la lista dalla pivot e poi prendiamo l'oggetto 'store' annidato
final pivotList = map['associated_stores'] as List?;
List<StoreModel> stores = [];
if (pivotList != null) {
stores = pivotList
.where((item) => item['store'] != null) // Sicurezza
.map(
(item) => StoreModel.fromMap(item['store'] as Map<String, dynamic>),
)
.toList();
}
return ProviderModel( return ProviderModel(
id: map['id'], id: map['id'],
nome: map['nome'], nome: map['nome'],
@@ -39,11 +51,7 @@ class ProviderModel extends Equatable {
altro: map['altro'] ?? false, altro: map['altro'] ?? false,
isActive: map['is_active'] ?? true, isActive: map['is_active'] ?? true,
companyId: map['company_id'], companyId: map['company_id'],
storesCount: associatedStores: stores,
map['providers_in_stores'] != null &&
map['providers_in_stores'].isNotEmpty
? map['providers_in_stores'][0]['count'] as int
: 0, // Assumiamo che l'API possa restituire questo campo
); );
} }
@@ -79,7 +87,7 @@ class ProviderModel extends Equatable {
altro, altro,
isActive, isActive,
companyId, companyId,
storesCount, associatedStores,
]; ];
ProviderModel copyWith({ ProviderModel copyWith({
@@ -93,7 +101,7 @@ class ProviderModel extends Equatable {
bool? altro, bool? altro,
bool? isActive, bool? isActive,
String? companyId, String? companyId,
int? storesCount, List<StoreModel>? associatedStores,
}) { }) {
return ProviderModel( return ProviderModel(
id: id ?? this.id, id: id ?? this.id,
@@ -106,7 +114,7 @@ class ProviderModel extends Equatable {
altro: altro ?? this.altro, altro: altro ?? this.altro,
isActive: isActive ?? this.isActive, isActive: isActive ?? this.isActive,
companyId: companyId ?? this.companyId, companyId: companyId ?? this.companyId,
storesCount: storesCount ?? this.storesCount, associatedStores: associatedStores ?? this.associatedStores,
); );
} }
} }

View File

@@ -29,6 +29,9 @@ class _ProviderFormSheetState extends State<ProviderFormSheet> {
void initState() { void initState() {
super.initState(); super.initState();
final p = widget.initialProvider; final p = widget.initialProvider;
for (final store in p?.associatedStores ?? []) {
_tempSelectedStoreIds.add(store.id!);
}
_nameController = TextEditingController(text: p?.nome ?? ''); _nameController = TextEditingController(text: p?.nome ?? '');
_telefoniaFissa = p?.telefoniaFissa ?? false; _telefoniaFissa = p?.telefoniaFissa ?? false;
_telefoniaMobile = p?.telefoniaMobile ?? false; _telefoniaMobile = p?.telefoniaMobile ?? false;

View File

@@ -126,7 +126,7 @@ class _ProvidersMasterDataScreenState extends State<ProvidersMasterDataScreen> {
// Un piccolo testo che indica il numero di store associati // Un piccolo testo che indica il numero di store associati
// Nota: Dovrai assicurarti che il Cubit carichi queste info // Nota: Dovrai assicurarti che il Cubit carichi queste info
return Text( return Text(
"Disponibile in ${provider.storesCount} negozi", "Disponibile in ${provider.associatedStores.length} negozi",
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: Colors.indigo.withValues(alpha: 0.7), color: Colors.indigo.withValues(alpha: 0.7),

View File

@@ -57,7 +57,7 @@ class StaffRepository {
.eq('staff_member_id', staffId); .eq('staff_member_id', staffId);
return (response as List) return (response as List)
.map((item) => StoreModel.fromJson(item['store'])) .map((item) => StoreModel.fromMap(item['store']))
.toList(); .toList();
} }

View File

@@ -31,7 +31,7 @@ class StoreCubit extends Cubit<StoreState> {
Future<void> loadStores() async { Future<void> loadStores() async {
emit(state.copyWith(status: StoreStatus.loading)); emit(state.copyWith(status: StoreStatus.loading));
try { try {
final stores = await _repository.getStoresByCompany( final stores = await _repository.fetchAllCompanyStores(
_sessionBloc.state.company!.id, _sessionBloc.state.company!.id,
); );
final Map<String, List<StaffMemberModel>> staffByStore = {}; final Map<String, List<StaffMemberModel>> staffByStore = {};

View File

@@ -3,12 +3,12 @@ import 'package:supabase_flutter/supabase_flutter.dart';
import '../models/store_model.dart'; import '../models/store_model.dart';
class StoreRepository { class StoreRepository {
final SupabaseClient _client = GetIt.I.get<SupabaseClient>(); final SupabaseClient _supabase = GetIt.I.get<SupabaseClient>();
/// Crea un nuovo negozio associato alla compagnia dell'utente /// Crea un nuovo negozio associato alla compagnia dell'utente
Future<void> createStore(StoreModel store) async { Future<void> createStore(StoreModel store) async {
try { try {
await _client.from('store').insert(store.toJson()); await _supabase.from('store').insert(store.toMap());
} on PostgrestException catch (e) { } on PostgrestException catch (e) {
// Intercettiamo errori specifici del database // Intercettiamo errori specifici del database
throw e.message; throw e.message;
@@ -18,19 +18,21 @@ class StoreRepository {
} }
/// Recupera tutti i negozi di una determinata compagnia /// Recupera tutti i negozi di una determinata compagnia
Future<List<StoreModel>> getStoresByCompany(String companyId) async { Future<List<StoreModel>> fetchAllCompanyStores(String companyId) async {
try { try {
final response = await _client final response = await _supabase
.from('store') .from('store')
.select() .select('''
*,
providers_count:providers_in_stores(count),
staff_members_count:staff_in_stores(count)
''')
.eq('company_id', companyId) .eq('company_id', companyId)
.order('created_at'); .order('nome');
return (response as List) return (response as List).map((m) => StoreModel.fromMap(m)).toList();
.map((json) => StoreModel.fromJson(json))
.toList();
} catch (e) { } catch (e) {
throw 'Errore nel recupero dei negozi'; throw 'Errore nel recupero dei negozi: $e';
} }
} }
} }

View File

@@ -11,6 +11,9 @@ class StoreModel extends Equatable {
final String cap; final String cap;
final String comune; final String comune;
final String provincia; final String provincia;
final int providersCount; // Numero di provider associati, utile per la lista
final int
staffMembersCount; // Numero di membri dello staff associati, utile per la lista
const StoreModel({ const StoreModel({
this.id, this.id,
@@ -23,6 +26,8 @@ class StoreModel extends Equatable {
required this.cap, required this.cap,
required this.comune, required this.comune,
required this.provincia, required this.provincia,
this.providersCount = 0, // Default a 0 se non specificato
this.staffMembersCount = 0, // Default a 0 se non specificato
}); });
// Fondamentale per Equatable: definisce quali proprietà determinano l'uguaglianza // Fondamentale per Equatable: definisce quali proprietà determinano l'uguaglianza
@@ -38,6 +43,8 @@ class StoreModel extends Equatable {
cap, cap,
comune, comune,
provincia, provincia,
providersCount,
staffMembersCount,
]; ];
// Il mitico copyWith per creare nuove istanze modificando solo ciò che serve // Il mitico copyWith per creare nuove istanze modificando solo ciò che serve
@@ -52,6 +59,8 @@ class StoreModel extends Equatable {
String? cap, String? cap,
String? comune, String? comune,
String? provincia, String? provincia,
int? providersCount,
int? staffMembersCount,
}) { }) {
return StoreModel( return StoreModel(
id: id ?? this.id, id: id ?? this.id,
@@ -64,27 +73,38 @@ class StoreModel extends Equatable {
cap: cap ?? this.cap, cap: cap ?? this.cap,
comune: comune ?? this.comune, comune: comune ?? this.comune,
provincia: provincia ?? this.provincia, provincia: provincia ?? this.provincia,
providersCount: providersCount ?? this.providersCount,
staffMembersCount: staffMembersCount ?? this.staffMembersCount,
); );
} }
factory StoreModel.fromJson(Map<String, dynamic> json) { factory StoreModel.fromMap(Map<String, dynamic> map) {
return StoreModel( return StoreModel(
id: json['id'] as String, id: map['id'] as String,
nome: json['nome'], nome: map['nome'],
companyId: json['company_id'] as String, companyId: map['company_id'] as String,
isActive: json['is_active'] ?? true, isActive: map['is_active'] ?? true,
isPaid: json['is_paid'] ?? false, isPaid: map['is_paid'] ?? false,
paymentExpiration: json['payment_expiration'] != null paymentExpiration: map['payment_expiration'] != null
? DateTime.parse(json['payment_expiration']) ? DateTime.parse(map['payment_expiration'])
: null, : null,
indirizzo: json['indirizzo'], indirizzo: map['indirizzo'],
cap: json['cap'], cap: map['cap'],
comune: json['comune'], comune: map['comune'],
provincia: json['provincia'], provincia: map['provincia'],
providersCount:
map['providers_count'] != null && map['providers_count'].isNotEmpty
? map['providers_count'][0]['count'] as int
: 0,
staffMembersCount:
map['staff_members_count'] != null &&
map['staff_members_count'].isNotEmpty
? map['staff_members_count'][0]['count'] as int
: 0,
); );
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toMap() {
return { return {
if (id != null) 'id': id, if (id != null) 'id': id,
'nome': nome, 'nome': nome,