Files
flux/lib/features/tickets/blocs/ticket_form_cubit.dart

322 lines
10 KiB
Dart
Raw Normal View History

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';
2026-05-13 12:41:07 +02:00
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';
2026-05-13 12:41:07 +02:00
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<TicketFormState> {
final TicketRepository _repository = GetIt.I.get<TicketRepository>();
final SessionCubit _sessionCubit = GetIt.I.get<SessionCubit>();
// Costruttore: prepariamo subito il ticket base con i dati di chi lo crea
TicketFormCubit({StaffMemberModel? createdBy, TicketModel? existingTicket})
: super(
2026-05-13 12:41:07 +02:00
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,
),
2026-05-13 12:41:07 +02:00
),
);
/// 1. INIZIALIZZAZIONE
Future<void> 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,
2026-05-12 11:14:48 +02:00
customer: customer,
alternativePhoneNumber:
state.ticket.alternativePhoneNumber ?? customer.phoneNumber,
),
),
);
}
/// 3. AGGIORNAMENTO MODELLO (Usato dal nostro SharedModelSection!)
2026-05-11 11:44:14 +02:00
void updateTargetModel({required String modelId, required String modelName}) {
emit(
state.copyWith(
ticket: state.ticket.copyWith(
targetModelId: modelId,
targetModelName: modelName,
),
),
);
}
2026-05-12 11:14:48 +02:00
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,
2026-05-14 12:07:05 +02:00
String? targetPassword,
2026-05-12 11:14:48 +02:00
String? sourceSn,
2026-05-14 12:07:05 +02:00
String? sourcePassword,
String? alternativePhoneNumber,
bool? hasCourtesyDevice,
String? includedAccessories,
String? publicNotes,
String? internalNotes,
double? customerPrice,
double? internalCost,
String? assignedToId,
String? assignedToName,
2026-05-14 12:07:05 +02:00
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,
2026-05-14 12:07:05 +02:00
targetPassword: targetPassword ?? state.ticket.targetPassword,
sourcePassword: sourcePassword ?? state.ticket.sourcePassword,
2026-05-12 11:14:48 +02:00
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,
2026-05-14 12:07:05 +02:00
warrantyType: warrantyType ?? state.ticket.warrantyType,
),
),
);
}
/// 5. SALVATAGGIO
2026-05-11 11:44:14 +02:00
Future<void> 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.");
}
2026-05-10 14:09:57 +02:00
TicketModel? savedTicket;
if (ticketToSave.id == null) {
savedTicket = await _repository.insertTicket(ticketToSave);
} else {
savedTicket = await _repository.updateTicket(ticketToSave);
}
2026-05-11 11:44:14 +02:00
emit(
state.copyWith(
status: TicketFormStatus.success,
ticket: ticketToSave.copyWith(
id: savedTicket.id,
referenceId: savedTicket.referenceId,
),
2026-05-11 11:44:14 +02:00
),
);
} catch (e) {
emit(
state.copyWith(
status: TicketFormStatus.failure,
errorMessage: e.toString(),
),
);
}
}
/// 5.1 SALVATAGGIO SILENZIOSO (Per generare il QR Code al volo)
Future<String?> 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.");
}
2026-05-10 14:09:57 +02:00
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;
}
}
2026-05-13 12:41:07 +02:00
Future<void> 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<TrackingRepository>().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',
),
);
}
}
2026-05-14 12:07:05 +02:00
Future<void> 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<TrackingRepository>().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',
),
);
}
}
}