import 'package:equatable/equatable.dart'; import 'package:flux/core/utils/extensions.dart'; /// Enum per il tipo di ticket enum TicketType { repair('repair', 'Riparazione'), softwareSetup('software_setup', 'Setup software'), dataTransfer('data_transfer', 'Trasferimento dati'), operationTicket('operation_ticket', 'Ticket di operazione'), other('other', 'Altro'); final String value; final String displayValue; const TicketType(this.value, this.displayValue); static TicketType fromString(String val) { return TicketType.values.firstWhere( (e) => e.value == val, orElse: () => TicketType.other, ); } } /// Enum per lo stato del ticket enum TicketStatus { open('open', 'Aperto'), inProgress('in_progress', 'In corso'), waitingForParts('waiting_for_parts', 'In attesa di ricambi'), ready('ready', 'Pronto'), closed('closed', 'Chiuso'), waitingForShipping('waiting_for_shipping', 'In attesa di spedire'), waitingForReturn('waiting_for_return', 'In attesa di ritorno'); final String value; final String displayValue; const TicketStatus(this.value, this.displayValue); static TicketStatus? fromString(String? val) { if (val == null) return null; return TicketStatus.values.firstWhere( (e) => e.value == val, orElse: () => TicketStatus.open, ); } } /// Enum per il risultato del ticket (OK / KO) enum TicketResult { success('success', 'Risolto (OK)'), failure('failure', 'Non Risolto (KO)'); final String value; final String displayValue; const TicketResult(this.value, this.displayValue); static TicketResult? fromString(String? val) { if (val == null) return null; return TicketResult.values.firstWhere( (e) => e.value == val, orElse: () => TicketResult.success, ); } } /// Enum per il tipo di garanzia enum WarrantyType { manufacturerWarranty('manufacturer_warranty', 'Garanzia produttore'), providerWarranty('provider_warranty', 'Garanzia gestore'), internalWarranty('internal_warranty', 'Garanzia interna'), noWarranty('no_warranty', 'Fuori garanzia'); final String value; final String displayValue; const WarrantyType(this.value, this.displayValue); static WarrantyType? fromString(String? val) { return WarrantyType.values.firstWhere( (e) => e.value == val, orElse: () => WarrantyType.noWarranty, ); } } class TicketModel extends Equatable { final String? id; // Null se non ancora salvato final DateTime? createdAt; final String companyId; final String? storeId; final String? customerId; final String? targetModelId; final String? targetSn; final String? sourceModelId; final String? sourceSn; final double customerPrice; final double internalCost; final DateTime? closedAt; final DateTime? returnedAt; final String request; final String? staffId; final WarrantyType? warrantyType; final String? publicNotes; final String? internalNotes; final int? referenceNumber; final String? alternativePhoneNumber; final bool hasCourtesyDevice; final TicketType ticketType; final TicketStatus? status; final DateTime? estimatedDeliveryAt; final TicketResult? result; final String? resolutionNotes; final String? legacyId; final String? customerName; final String? targetModelName; final String? sourceModelName; final String? staffName; final String? includedAccessories; const TicketModel({ this.id, this.createdAt, required this.companyId, this.storeId, this.customerId, this.targetModelId, this.targetSn, this.sourceModelId, this.sourceSn, this.customerPrice = 0.0, this.internalCost = 0.0, this.closedAt, this.returnedAt, this.request = '', this.staffId, this.warrantyType, this.publicNotes, this.internalNotes, this.referenceNumber, this.alternativePhoneNumber, this.hasCourtesyDevice = false, required this.ticketType, this.status, this.estimatedDeliveryAt, this.result, this.resolutionNotes, this.legacyId, this.customerName, this.targetModelName, this.sourceModelName, this.staffName, this.includedAccessories, }); /// Factory per creare un ticket vuoto (utile per i form di creazione) factory TicketModel.empty({required String companyId, String? storeId}) { return TicketModel( companyId: companyId, storeId: storeId, ticketType: TicketType.repair, // Valore di default status: TicketStatus.open, customerPrice: 0.0, internalCost: 0.0, hasCourtesyDevice: false, request: '', ); } TicketModel copyWith({ String? id, DateTime? createdAt, String? companyId, String? storeId, String? customerId, String? targetModelId, String? targetSn, String? sourceModelId, String? sourceSn, double? customerPrice, double? internalCost, DateTime? closedAt, DateTime? returnedAt, String? request, String? staffId, WarrantyType? warrantyType, String? publicNotes, String? internalNotes, int? referenceNumber, String? alternativePhoneNumber, bool? hasCourtesyDevice, TicketType? ticketType, TicketStatus? status, DateTime? estimatedDeliveryAt, TicketResult? result, String? resolutionNotes, String? legacyId, String? customerName, String? targetModelName, String? sourceModelName, String? staffName, String? includedAccessories, }) { return TicketModel( id: id ?? this.id, createdAt: createdAt ?? this.createdAt, companyId: companyId ?? this.companyId, storeId: storeId ?? this.storeId, customerId: customerId ?? this.customerId, targetModelId: targetModelId ?? this.targetModelId, targetSn: targetSn ?? this.targetSn, sourceModelId: sourceModelId ?? this.sourceModelId, sourceSn: sourceSn ?? this.sourceSn, customerPrice: customerPrice ?? this.customerPrice, internalCost: internalCost ?? this.internalCost, closedAt: closedAt ?? this.closedAt, returnedAt: returnedAt ?? this.returnedAt, request: request ?? this.request, staffId: staffId ?? this.staffId, warrantyType: warrantyType ?? this.warrantyType, publicNotes: publicNotes ?? this.publicNotes, internalNotes: internalNotes ?? this.internalNotes, referenceNumber: referenceNumber ?? this.referenceNumber, alternativePhoneNumber: alternativePhoneNumber ?? this.alternativePhoneNumber, hasCourtesyDevice: hasCourtesyDevice ?? this.hasCourtesyDevice, ticketType: ticketType ?? this.ticketType, status: status ?? this.status, estimatedDeliveryAt: estimatedDeliveryAt ?? this.estimatedDeliveryAt, result: result ?? this.result, resolutionNotes: resolutionNotes ?? this.resolutionNotes, legacyId: legacyId ?? this.legacyId, customerName: customerName ?? this.customerName, targetModelName: targetModelName ?? this.targetModelName, sourceModelName: sourceModelName ?? this.sourceModelName, staffName: staffName ?? this.staffName, includedAccessories: includedAccessories ?? this.includedAccessories, ); } /// Deserializzazione da Supabase factory TicketModel.fromMap(Map map) { return TicketModel( id: map['id'] as String, createdAt: map['created_at'] != null ? DateTime.parse(map['created_at']).toLocal() : null, companyId: map['company_id'] as String, storeId: map['store_id'] as String?, customerId: map['customer_id'] as String?, targetModelId: map['target_model_id'] as String?, targetSn: map['target_sn'] as String?, sourceModelId: map['source_model_id'] as String?, sourceSn: map['source_sn'] as String?, // Fix per i field numerici di Postgres che potrebbero arrivare come int o double customerPrice: (map['customer_price'] as num?)?.toDouble() ?? 0.0, internalCost: (map['internal_cost'] as num?)?.toDouble() ?? 0.0, closedAt: map['closed_at'] != null ? DateTime.parse(map['closed_at']).toLocal() : null, returnedAt: map['returned_at'] != null ? DateTime.parse(map['returned_at']).toLocal() : null, request: map['request'] as String? ?? '', staffId: map['staff_id'] as String?, warrantyType: WarrantyType.fromString(map['warranty_type'] as String?), publicNotes: map['public_notes'] as String?, internalNotes: map['internal_notes'] as String?, referenceNumber: map['reference_number'] as int?, alternativePhoneNumber: map['alternative_phone_number'] as String?, hasCourtesyDevice: map['has_courtesy_device'] as bool? ?? false, ticketType: TicketType.fromString(map['ticket_type'] as String), status: TicketStatus.fromString(map['status'] as String?), estimatedDeliveryAt: map['estimated_delivery_at'] != null ? DateTime.parse(map['estimated_delivery_at']).toLocal() : null, result: TicketResult.fromString(map['result'] as String?), resolutionNotes: map['resolution_notes'] as String?, legacyId: map['legacy_id'] as String?, customerName: (map['customer']?['name'] as String?).myFormat(), targetModelName: (map['target_model']?['name_with_brand'] as String?) ?.myFormat(), sourceModelName: (map['source_model']?['name_with_brand'] as String?) ?.myFormat(), staffName: (map['staff']?['name'] as String?).myFormat(), includedAccessories: map['included_accessories'] as String?, ); } /// Serializzazione per Supabase Map toMap() { return { if (id != null) 'id': id, 'company_id': companyId, 'store_id': storeId, 'customer_id': customerId, 'target_model_id': targetModelId, 'target_sn': targetSn, 'source_model_id': sourceModelId, 'source_sn': sourceSn, 'customer_price': customerPrice, 'internal_cost': internalCost, if (closedAt != null) 'closed_at': closedAt!.toUtc().toIso8601String(), if (returnedAt != null) 'returned_at': returnedAt!.toUtc().toIso8601String(), 'request': request, 'staff_id': staffId, 'warranty_type': warrantyType, 'public_notes': publicNotes, 'internal_notes': internalNotes, 'alternative_phone_number': alternativePhoneNumber, 'has_courtesy_device': hasCourtesyDevice, 'ticket_type': ticketType.value, if (status != null) 'status': status!.value, if (estimatedDeliveryAt != null) 'estimated_delivery_at': estimatedDeliveryAt!.toUtc().toIso8601String(), if (result != null) 'result': result!.value, 'resolution_notes': resolutionNotes, 'legacy_id': legacyId, 'included_accessories': includedAccessories, }; } @override List get props => [ id, createdAt, companyId, storeId, customerId, targetModelId, targetSn, sourceModelId, sourceSn, customerPrice, internalCost, closedAt, returnedAt, request, staffId, warrantyType, publicNotes, internalNotes, referenceNumber, alternativePhoneNumber, hasCourtesyDevice, ticketType, status, estimatedDeliveryAt, result, resolutionNotes, legacyId, includedAccessories, customerName, targetModelName, sourceModelName, staffName, ]; }