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? sourceSn, String? alternativePhoneNumber, bool? hasCourtesyDevice, String? includedAccessories, String? publicNotes, String? internalNotes, double? customerPrice, double? internalCost, String? assignedToId, String? assignedToName, }) { 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, 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, ), ), ); } /// 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', ), ); } } }