From a166992b049d40bb2bd73f70fd084d512950c2de Mon Sep 17 00:00:00 2001 From: Mark M2 Macbook Date: Sat, 16 May 2026 09:04:18 +0200 Subject: [PATCH] refined document sequence management --- .../data/tickets_shipment_repository.dart | 15 ---- .../blocs/document_sequence_cubit.dart | 77 ++++++++++++------- .../data/document_sequence_repository.dart | 61 +++++++++++---- .../models/document_sequence_model.dart | 4 +- .../ui/document_sequence_section.dart | 62 +++++++++++---- .../tickets/blocs/ticket_shipping_cubit.dart | 29 +++++-- 6 files changed, 170 insertions(+), 78 deletions(-) diff --git a/lib/features/documents/data/tickets_shipment_repository.dart b/lib/features/documents/data/tickets_shipment_repository.dart index 3188219..918f1d6 100644 --- a/lib/features/documents/data/tickets_shipment_repository.dart +++ b/lib/features/documents/data/tickets_shipment_repository.dart @@ -49,19 +49,4 @@ class TicketsShipmentRepository { throw ('Errore durante la creazione della spedizione: $e'); } } - - Future getNextAutoDocumentNumber() async { - try { - final response = await _supabase - .from('document_sequences') - .select('*') - .eq('company_id', _companyId) - .eq('document_type', DocumentType.shipment.name) - .single(); - - return DocumentSequence.fromMap(response); - } catch (e) { - throw ('Errore recupero numero documento: $e'); - } - } } diff --git a/lib/features/settings/document_sequence/blocs/document_sequence_cubit.dart b/lib/features/settings/document_sequence/blocs/document_sequence_cubit.dart index 10d2719..c0827c4 100644 --- a/lib/features/settings/document_sequence/blocs/document_sequence_cubit.dart +++ b/lib/features/settings/document_sequence/blocs/document_sequence_cubit.dart @@ -1,68 +1,93 @@ +import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flux/features/settings/document_sequence/data/document_sequence_repository.dart'; import 'package:flux/features/settings/document_sequence/models/document_sequence_model.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:get_it/get_it.dart'; -class DocumentSequenceState { +enum DocumentSequenceStatus { initial, loading, success, failure } + +class DocumentSequenceState extends Equatable { + final DocumentSequenceStatus status; final List sequences; - final bool isLoading; final String? error; - DocumentSequenceState({ + const DocumentSequenceState({ this.sequences = const [], - this.isLoading = false, this.error, + this.status = DocumentSequenceStatus.initial, }); + + DocumentSequenceState copyWith({ + List? sequences, + String? error, + DocumentSequenceStatus? status, + }) { + return DocumentSequenceState( + sequences: sequences ?? this.sequences, + error: error ?? this.error, + status: status ?? this.status, + ); + } + + @override + List get props => [sequences, error, status]; } class DocumentSequenceCubit extends Cubit { final String companyId; - final _supabase = Supabase.instance.client; + final _repository = GetIt.I.get(); DocumentSequenceCubit(this.companyId) : super(DocumentSequenceState()); Future loadSequences() async { - emit(DocumentSequenceState(isLoading: true)); + emit(state.copyWith(status: DocumentSequenceStatus.loading)); try { - final data = await _supabase - .from('document_sequences') - .select() - .eq('company_id', companyId); - - final list = (data as List) - .map((e) => DocumentSequence.fromMap(e)) - .toList(); - emit(DocumentSequenceState(sequences: list)); + final list = await _repository.getDocumentSequences(); + emit( + state.copyWith(sequences: list, status: DocumentSequenceStatus.success), + ); } catch (e) { - emit(DocumentSequenceState(error: e.toString())); + emit( + state.copyWith( + error: e.toString(), + status: DocumentSequenceStatus.failure, + ), + ); } } void updateLocalSequence(String docType, {String? prefix, int? nextValue}) { + bool found = false; final newList = state.sequences.map((s) { if (s.docType == docType) { + found = true; return s.copyWith(prefix: prefix, nextValue: nextValue); } return s; }).toList(); - emit(DocumentSequenceState(sequences: newList)); + if (!found) { + newList.add( + DocumentSequence( + docType: docType, + prefix: prefix ?? '', + nextValue: nextValue ?? 1, + ), + ); + } + emit(state.copyWith(sequences: newList)); } Future saveSequences() async { try { for (var seq in state.sequences) { - await _supabase.from('document_sequences').upsert({ - 'company_id': companyId, - 'doc_type': seq.docType, - 'next_value': seq.nextValue, - 'prefix': seq.prefix, - }); + await _repository.createOrUpdateSequence(sequence: seq); } // Opzionale: mostra un feedback di successo } catch (e) { emit( - DocumentSequenceState( - sequences: state.sequences, + state.copyWith( error: "Errore nel salvataggio", + status: DocumentSequenceStatus.failure, ), ); } diff --git a/lib/features/settings/document_sequence/data/document_sequence_repository.dart b/lib/features/settings/document_sequence/data/document_sequence_repository.dart index 7de38aa..4d8d72c 100644 --- a/lib/features/settings/document_sequence/data/document_sequence_repository.dart +++ b/lib/features/settings/document_sequence/data/document_sequence_repository.dart @@ -1,30 +1,61 @@ +import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/features/settings/document_sequence/models/document_sequence_model.dart'; import 'package:get_it/get_it.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class DocumentSequenceRepository { final _supabase = GetIt.I.get(); + final _companyId = GetIt.I.get().state.company!.id!; - Future> getDocumentSequences(String companyId) async { - final response = await _supabase - .from('document_sequences') - .select() - .eq('company_id', companyId); + Future> getDocumentSequences() async { + try { + final response = await _supabase + .from('document_sequences') + .select() + .eq('company_id', _companyId); - return (response as List).map((e) => DocumentSequence.fromMap(e)).toList(); + return (response as List) + .map((e) => DocumentSequence.fromMap(e)) + .toList(); + } catch (e) { + throw ('Errore durante il caricamento delle sequenze: $e'); + } } - Future updateSequence({ - required String companyId, - required String docType, - required int nextValue, - required String prefix, + Future getNextDocumentNumber(String docType) async { + try { + // Chiamiamo la funzione SQL che abbiamo appena creato! + final response = await _supabase.rpc( + 'get_next_sequence', + params: {'p_doc_type': docType}, + ); + return response as String; + } catch (e) { + throw ('Errore durante la generazione del numero: $e'); + } + } + + Future createOrUpdateSequence({ + required DocumentSequence sequence, }) async { + try { + await _supabase.from('document_sequences').upsert({ + 'company_id': _companyId, + 'doc_type': sequence.docType, + 'next_value': sequence.nextValue, + 'prefix': sequence.prefix, + }); + } on Exception catch (e) { + throw ('Errore durante la creazione/aggiornamento della sequenza: $e'); + } + } + + Future updateSequence({required DocumentSequence sequence}) async { await _supabase.from('document_sequences').upsert({ - 'company_id': companyId, - 'doc_type': docType, - 'next_value': nextValue, - 'prefix': prefix, + 'company_id': _companyId, + 'doc_type': sequence.docType, + 'next_value': sequence.nextValue, + 'prefix': sequence.prefix, }); } } diff --git a/lib/features/settings/document_sequence/models/document_sequence_model.dart b/lib/features/settings/document_sequence/models/document_sequence_model.dart index 2d3f8b9..1372cf2 100644 --- a/lib/features/settings/document_sequence/models/document_sequence_model.dart +++ b/lib/features/settings/document_sequence/models/document_sequence_model.dart @@ -1,4 +1,6 @@ -enum DocumentType { ticket, shipment, invoice } +// Se un domani ci saranno nuovi tipi di documento, basterà aggiungerli qui +// e alla mappa nella DocumentSequenceSection dei documenti supportati +enum DocumentType { ticket, shipment } class DocumentSequence { final String docType; diff --git a/lib/features/settings/document_sequence/ui/document_sequence_section.dart b/lib/features/settings/document_sequence/ui/document_sequence_section.dart index 407979c..b408202 100644 --- a/lib/features/settings/document_sequence/ui/document_sequence_section.dart +++ b/lib/features/settings/document_sequence/ui/document_sequence_section.dart @@ -1,17 +1,34 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/features/settings/document_sequence/blocs/document_sequence_cubit.dart'; +import 'package:flux/features/settings/document_sequence/models/document_sequence_model.dart'; class DocumentSequenceSection extends StatelessWidget { const DocumentSequenceSection({super.key}); + // La nostra lista "Ninja" di tutti i documenti gestiti dall'app + // Se domani aggiungo le fatture, basta aggiungere una riga qui! + // e all'enum DocumentType nel model + static final supportedDocumentTypes = [ + { + 'type': DocumentType.ticket.name, + 'label': 'TICKET', + 'defaultPrefix': 'TCK', + }, + { + 'type': DocumentType.shipment.name, + 'label': 'DDT (Documento di Trasporto)', + 'defaultPrefix': 'DDT', + }, + ]; + @override Widget build(BuildContext context) { final year = DateTime.now().year; return BlocBuilder( builder: (context, state) { - if (state.isLoading) { + if (state.status == DocumentSequenceStatus.loading) { return const Center(child: CircularProgressIndicator()); } @@ -27,10 +44,26 @@ class DocumentSequenceSection extends StatelessWidget { ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), ), ), - ...state.sequences.map((seq) { - // Anteprima dinamica + + // Invece di mappare state.sequences, mappiamo i documenti supportati + ...supportedDocumentTypes.map((docDef) { + final docType = docDef['type']!; + + // Cerchiamo se c'è già una configurazione nello stato per questo documento + final existingList = state.sequences + .where((s) => s.docType == docType) + .toList(); + final existingSeq = existingList.isNotEmpty + ? existingList.first + : null; + + // Se esiste usiamo i suoi valori, altrimenti i default + final prefix = existingSeq?.prefix ?? docDef['defaultPrefix']!; + final nextValue = existingSeq?.nextValue ?? 1; + + // Anteprima dinamica (aggiornata a 4 zeri come nel DB!) final preview = - "${seq.prefix.isNotEmpty ? '${seq.prefix}-' : ''}$year-${seq.nextValue.toString().padLeft(6, '0')}"; + "${prefix.isNotEmpty ? '$prefix-' : ''}$year-${nextValue.toString().padLeft(4, '0')}"; return Card( margin: const EdgeInsets.only(bottom: 12), @@ -40,7 +73,7 @@ class DocumentSequenceSection extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - seq.docType.toUpperCase(), + docDef['label']!, style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.blue, @@ -52,24 +85,21 @@ class DocumentSequenceSection extends StatelessWidget { Expanded( flex: 2, child: TextFormField( - initialValue: seq.prefix, + initialValue: prefix, decoration: const InputDecoration( labelText: 'Prefisso', hintText: 'es. TCK', ), onChanged: (val) => context .read() - .updateLocalSequence( - seq.docType, - prefix: val, - ), + .updateLocalSequence(docType, prefix: val), ), ), const SizedBox(width: 16), Expanded( flex: 3, child: TextFormField( - initialValue: seq.nextValue.toString(), + initialValue: nextValue.toString(), keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Prossimo Numero', @@ -77,7 +107,7 @@ class DocumentSequenceSection extends StatelessWidget { onChanged: (val) => context .read() .updateLocalSequence( - seq.docType, + docType, nextValue: int.tryParse(val) ?? 1, ), ), @@ -88,7 +118,9 @@ class DocumentSequenceSection extends StatelessWidget { Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.grey.shade100, + color: Colors + .grey + .shade100, // Se hai un tema scuro potresti voler usare Theme.of(context).colorScheme.surfaceContainer borderRadius: BorderRadius.circular(8), ), child: Row( @@ -102,7 +134,9 @@ class DocumentSequenceSection extends StatelessWidget { Text( "Anteprima prossimo: ", style: TextStyle( - color: Colors.grey.shade700, + color: Colors + .grey + .shade700, // Idem per la dark mode fontSize: 12, ), ), diff --git a/lib/features/tickets/blocs/ticket_shipping_cubit.dart b/lib/features/tickets/blocs/ticket_shipping_cubit.dart index ce96ae4..1ed61e5 100644 --- a/lib/features/tickets/blocs/ticket_shipping_cubit.dart +++ b/lib/features/tickets/blocs/ticket_shipping_cubit.dart @@ -5,6 +5,7 @@ import 'package:flux/features/documents/data/tickets_shipment_repository.dart'; import 'package:flux/features/documents/models/shipment_document_model.dart'; import 'package:flux/features/master_data/providers/models/provider_location_model.dart'; import 'package:flux/features/master_data/providers/models/provider_model.dart'; +import 'package:flux/features/settings/document_sequence/data/document_sequence_repository.dart'; import 'package:get_it/get_it.dart'; part 'ticket_shipping_state.dart'; @@ -12,6 +13,8 @@ part 'ticket_shipping_state.dart'; class TicketShippingCubit extends Cubit { final TicketsShipmentRepository _repository = GetIt.I(); + final DocumentSequenceRepository _sequenceRepository = + GetIt.I(); TicketShippingCubit({required List ticketIds}) : super( TicketShippingState( @@ -79,16 +82,28 @@ class TicketShippingCubit extends Cubit { ); } - void toggleAutoNumber(bool value) { + Future toggleAutoNumber(bool value) async { + // Aggiorniamo subito l'UI per mostrare che lo switch si è acceso emit(state.copyWith(isAutoNumber: value)); + if (value) { - final nextNumber = "DDT-${DateTime.now().year}-001"; - emit( - state.copyWith( - document: state.document.copyWith(docNumber: nextNumber), - ), - ); + // Se lo switch è acceso, chiediamo il numero al DB + try { + final nextNumber = await _sequenceRepository.getNextDocumentNumber( + 'ddt', + ); + + emit( + state.copyWith( + document: state.document.copyWith(docNumber: nextNumber), + ), + ); + } catch (e) { + // Se qualcosa va storto, spegniamo lo switch e mostriamo l'errore + emit(state.copyWith(isAutoNumber: false, errorMessage: e.toString())); + } } else { + // Se lo spegne, svuotiamo semplicemente il campo emit(state.copyWith(document: state.document.copyWith(docNumber: ''))); } }