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/staff/models/staff_member_model.dart'; import 'package:flux/features/tickets/models/ticket_model.dart'; import 'package:flux/features/tickets/data/ticket_repository.dart'; import 'package:flux/features/tracking/data/tracking_repository.dart'; import 'package:flux/features/tracking/models/tracking_model.dart'; import 'package:get_it/get_it.dart'; import 'ticket_form_state.dart'; class TicketFormCubit extends Cubit { final TicketRepository _repository = GetIt.I.get(); final SessionCubit _sessionCubit = GetIt.I.get(); // Costruttore: prepariamo subito il ticket base con i dati di chi lo crea TicketFormCubit({StaffMemberModel? createdBy, TicketModel? existingTicket}) : super( TicketFormState( // Se c'è un ticket esistente usa quello, ALTRIMENTI ne crea uno vuoto // e ci stampa subito il nome del creatore! ticket: existingTicket ?? TicketModel.empty().copyWith( createdById: createdBy?.id, createdByName: createdBy?.name, ), ), ); /// 1. INIZIALIZZAZIONE Future initForm({String? id, TicketModel? existingTicket}) async { if (existingTicket != null) { // SCENARIO 1: Abbiamo il ticket intero passato via record emit( state.copyWith(ticket: existingTicket, status: TicketFormStatus.ready), ); } else if (id != null) { // SCENARIO 2: QR CODE o Web Refresh! (Hai solo l'ID) emit(state.copyWith(status: TicketFormStatus.loading)); try { // Boom! Lo scarica dal database in tempo reale final fetchedTicket = await _repository.getTicketById(id); emit( state.copyWith(ticket: fetchedTicket, status: TicketFormStatus.ready), ); } catch (e) { emit( state.copyWith( status: TicketFormStatus.failure, errorMessage: 'Ticket non trovato', ), ); } } else { // SCENARIO 3: Nuovo Ticket final currentStore = _sessionCubit.state.currentStore; final companyId = _sessionCubit.state.company?.id ?? ''; // IL TRUCCO È QUI: Usiamo `state.ticket` invece di `TicketModel.empty()`. // `state.ticket` HA GIÀ i dati di 'createdBy' settati nel costruttore! final newTicket = state.ticket.copyWith( companyId: companyId, storeId: currentStore?.id, ticketStatus: TicketStatus.open, // <-- O il tuo status di default ticketType: TicketType.repair, ); emit(state.copyWith(ticket: newTicket, status: TicketFormStatus.ready)); } } /// 2. AGGIORNAMENTO CLIENTE (Usato dal nostro SharedCustomerSection!) void updateCustomer(CustomerModel customer) { emit( state.copyWith( ticket: state.ticket.copyWith( customerId: customer.id, customer: customer, alternativePhoneNumber: state.ticket.alternativePhoneNumber ?? customer.phoneNumber, ), ), ); } /// 3. AGGIORNAMENTO MODELLO (Usato dal nostro SharedModelSection!) void updateTargetModel({required String modelId, required String modelName}) { emit( state.copyWith( ticket: state.ticket.copyWith( targetModelId: modelId, targetModelName: modelName, ), ), ); } void updateSourceModel({required String modelId, required String modelName}) { emit( state.copyWith( ticket: state.ticket.copyWith( sourceModelId: modelId, sourceModelName: modelName, ), ), ); } void updateCreator({required String staffId, required String staffName}) { emit( state.copyWith( ticket: state.ticket.copyWith( createdById: staffId, createdByName: staffName, ), ), ); } /// 4. AGGIORNAMENTO GENERICO DEI CAMPI void updateFields({ TicketType? ticketType, TicketStatus? status, String? request, String? targetSn, String? targetPassword, String? sourceSn, String? sourcePassword, String? alternativePhoneNumber, bool? hasCourtesyDevice, String? includedAccessories, String? publicNotes, String? internalNotes, double? customerPrice, double? internalCost, String? assignedToId, String? assignedToName, WarrantyType? warrantyType, }) { emit( state.copyWith( ticket: state.ticket.copyWith( ticketType: ticketType ?? state.ticket.ticketType, ticketStatus: status ?? state.ticket.ticketStatus, request: request ?? state.ticket.request, targetSn: targetSn ?? state.ticket.targetSn, targetPassword: targetPassword ?? state.ticket.targetPassword, sourcePassword: sourcePassword ?? state.ticket.sourcePassword, sourceSn: sourceSn ?? state.ticket.sourceSn, alternativePhoneNumber: alternativePhoneNumber ?? state.ticket.alternativePhoneNumber, hasCourtesyDevice: hasCourtesyDevice ?? state.ticket.hasCourtesyDevice, includedAccessories: includedAccessories ?? state.ticket.includedAccessories, publicNotes: publicNotes ?? state.ticket.publicNotes, internalNotes: internalNotes ?? state.ticket.internalNotes, customerPrice: customerPrice ?? state.ticket.customerPrice, internalCost: internalCost ?? state.ticket.internalCost, assignedToId: assignedToId ?? state.ticket.assignedToId, assignedToName: assignedToName ?? state.ticket.assignedToName, warrantyType: warrantyType ?? state.ticket.warrantyType, ), ), ); } /// 5. SALVATAGGIO Future saveTicket() async { emit(state.copyWith(status: TicketFormStatus.saving)); try { final ticketToSave = state.ticket; // Validazione base if (ticketToSave.customerId == null || ticketToSave.customerId!.isEmpty) { throw Exception("Seleziona un cliente prima di salvare."); } TicketModel? savedTicket; if (ticketToSave.id == null) { savedTicket = await _repository.insertTicket(ticketToSave); } else { savedTicket = await _repository.updateTicket(ticketToSave); } emit( state.copyWith( status: TicketFormStatus.success, ticket: ticketToSave.copyWith( id: savedTicket.id, referenceId: savedTicket.referenceId, ), ), ); } catch (e) { emit( state.copyWith( status: TicketFormStatus.failure, errorMessage: e.toString(), ), ); } } /// 5.1 SALVATAGGIO SILENZIOSO (Per generare il QR Code al volo) Future saveTicketDraft() async { // Non mettiamo lo stato 'saving' per non far sfarfallare tutta la UI, // usiamo un caricamento invisibile. try { final ticketToSave = state.ticket; if (ticketToSave.customerId == null || ticketToSave.customerId!.isEmpty) { throw Exception("Seleziona un cliente prima di poter usare il QR."); } final savedTicket = await _repository.insertTicket(ticketToSave); // Aggiorniamo silenziosamente lo stato con il ticket che ora ha un ID! emit(state.copyWith(ticket: savedTicket, status: TicketFormStatus.ready)); return savedTicket.id; } catch (e) { emit( state.copyWith( status: TicketFormStatus.failure, errorMessage: e.toString(), ), ); return null; } } Future takeInCharge({ required String staffId, required String staffName, }) async { final currentTicket = state.ticket; // Sicurezza: non possiamo prendere in carico un ticket fantasma if (currentTicket.id == null || currentTicket.id!.isEmpty) return; // 1. Prepariamo il ticket aggiornato final updatedTicket = currentTicket.copyWith( ticketStatus: TicketStatus .inProgress, // Assumendo che tu abbia un enum per gli stati assignedToId: staffId, assignedToName: staffName, ); try { // 2. Aggiorniamo il ticket sul Database (usa il tuo metodo esistente del repo) await _repository.updateTicket(updatedTicket); // 3. Spara il log automatico nella Timeline! await GetIt.I.get().logQuickEvent( companyId: currentTicket.companyId, message: "Ticket preso in carico. Inizio lavorazione.", type: TrackingType.statusChange, parentId: currentTicket.id!, parentType: TrackingParentType.ticket, staffId: staffId, // Lo mettiamo pubblico (isInternal: false) così il cliente a casa vede che // il suo dispositivo è ufficialmente sotto i ferri! isInternal: false, ); // 4. Aggiorniamo lo stato locale del Cubit per far scattare la UI emit(state.copyWith(ticket: updatedTicket)); } catch (e) { // Gestisci eventuali errori (es. mostrando una snackbar) emit( state.copyWith( status: TicketFormStatus.failure, errorMessage: 'Errore durante la presa in carico: $e', ), ); } } Future pauseTicket({ required TicketStatus newStatus, required String notes, }) async { final currentTicket = state.ticket; if (currentTicket.id == null || currentTicket.id!.isEmpty) return; // 1. Usiamo lo stato esatto scelto dal tecnico final updatedTicket = currentTicket.copyWith(ticketStatus: newStatus); try { await _repository.updateTicket(updatedTicket); // 2. Componiamo un bel messaggio per la Timeline // Es: "Sospeso per: waitingForQuote. Note: Cliente in ferie" String timelineMessage = "Lavorazione sospesa (${newStatus.name})."; if (notes.isNotEmpty) { timelineMessage += " Note: $notes"; } await GetIt.I.get().logQuickEvent( companyId: currentTicket.companyId, message: timelineMessage, type: TrackingType.statusChange, parentId: currentTicket.id!, parentType: TrackingParentType.ticket, staffId: currentTicket.assignedToId ?? '', isInternal: false, ); emit(state.copyWith(ticket: updatedTicket)); } catch (e) { emit( state.copyWith( status: TicketFormStatus.failure, errorMessage: 'Errore durante la sospensione: $e', ), ); } } Future completeTicket({ required TicketResult result, required double finalPrice, }) async { final currentTicket = state.ticket; if (currentTicket.id == null || currentTicket.id!.isEmpty) return; // 1. Aggiorniamo il ticket con il nuovo status, l'esito e il prezzo! final updatedTicket = currentTicket.copyWith( ticketStatus: TicketStatus.ready, ticketResult: result, customerPrice: finalPrice, ); try { await _repository.updateTicket(updatedTicket); // 2. Timeline personalizzata in base all'esito final esitoTesto = result == TicketResult.success ? "Riparato con successo" : "Non riparabile/Preventivo rifiutato"; await GetIt.I.get().logQuickEvent( companyId: currentTicket.companyId, message: "Lavorazione completata. Esito: $esitoTesto. Il dispositivo è pronto per il ritiro.", type: TrackingType.statusChange, parentId: currentTicket.id!, parentType: TrackingParentType.ticket, staffId: currentTicket.assignedToId ?? '', isInternal: false, ); emit(state.copyWith(ticket: updatedTicket)); } catch (e) { emit( state.copyWith( status: TicketFormStatus.failure, errorMessage: 'Errore durante la chiusura: $e', ), ); } } }