Files
flux/lib/temp/migration_tools.dart

337 lines
12 KiB
Dart
Raw Normal View History

2026-05-06 01:18:14 +02:00
import 'dart:convert';
2026-05-04 19:32:14 +02:00
import 'package:cloud_firestore/cloud_firestore.dart';
2026-05-06 01:18:14 +02:00
import 'package:flutter/material.dart';
import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:get_it/get_it.dart';
2026-05-04 19:32:14 +02:00
import 'package:supabase_flutter/supabase_flutter.dart';
Future<void> migrateCustomersToSupabase() async {
// 1. IL TUO COMPANY ID REALE SU SUPABASE
// Vai nel database Supabase, copia l'UUID della tua azienda e incollalo qui
final String myRealCompanyId = '6c4b2323-2a60-4d33-bf21-c5a8eb6b4a5b';
try {
print("Inizio download modello da Firebase...");
// 2. Scarichiamo TUTTI i clienti da Firebase
final snapshot = await FirebaseFirestore.instance.collection('marca').get();
if (snapshot.docs.isEmpty) {
print("Nessun marca trovato su Firebase!");
return;
}
// Questa lista conterrà i dati formattati pronti per Supabase
List<Map<String, dynamic>> supabaseBrands = [];
// 3. Cicliamo i documenti di Firebase e li trasformiamo
for (var doc in snapshot.docs) {
final data = doc.data();
// Creiamo la riga per Supabase
supabaseBrands.add({
'legacy_id': doc.id, // L'ID vecchio di Firebase
//'company_id': myRealCompanyId, // ECCO IL TUO COMPANY ID!
// Mappa i campi (attento a far combaciare i nomi esatti delle colonne Supabase!)
'name': (data['nome'] as String).trim().toLowerCase(),
'company_id': myRealCompanyId,
// Se avevi una data di creazione su Firebase, convertila, altrimenti ignorala
// e Supabase userà il suo 'default now()'
// 'created_at': (data['createdAt'] as Timestamp?)?.toDate().toIso8601String(),
});
}
print("Sto per inviare ${supabaseBrands.length} brand a Supabase...");
// 4. Invio a Supabase con UPSERT
await Supabase.instance.client
.from('brand')
.upsert(
supabaseBrands,
onConflict:
'legacy_id', // Se il legacy_id c'è già, aggiorna invece di duplicare
);
print("BOOM! Migrazione brand completata con successo! 🚀");
} catch (e) {
print("Porca miseria, errore durante la migrazione: $e");
}
}
Future<void> migrateModelsToSupabase() async {
final String myRealCompanyId = '6c4b2323-2a60-4d33-bf21-c5a8eb6b4a5b';
try {
print("Inizio migrazione Modelli...");
// ==========================================================
// FASE 1: CREAZIONE DEL DIZIONARIO DI TRADUZIONE (LA MAGIA)
// ==========================================================
print("Scarico i Brand da Supabase per tradurre gli ID...");
// Chiediamo a Supabase solo 2 colonne: il nuovo UUID e il vecchio ID di Firebase
final List<dynamic> brandResponse = await Supabase.instance.client
.from('brand')
.select('id, legacy_id');
// Creiamo la mappa: la chiave è il vecchio ID, il valore è il nuovo UUID
Map<String, String> brandTranslationMap = {};
for (var b in brandResponse) {
2026-05-05 09:30:03 +02:00
if (b['legacy_id'] != null) {
2026-05-04 19:32:14 +02:00
brandTranslationMap[b['legacy_id']] = b['id'];
}
}
print("Dizionario pronto! Trovati ${brandTranslationMap.length} Brand.");
// ==========================================================
// FASE 2: SCARICAMENTO E TRADUZIONE DEI MODELLI
// ==========================================================
final snapshot = await FirebaseFirestore.instance
.collection('modello')
.get(); // Controlla il nome esatto della collection!
if (snapshot.docs.isEmpty) {
print("Nessun modello trovato su Firebase!");
return;
}
List<Map<String, dynamic>> supabaseModels = [];
for (var doc in snapshot.docs) {
final data = doc.data();
// 1. Prendiamo il vecchio ID del brand salvato su Firebase
String? oldFirebaseBrandId = data['idMarca'];
// 2. TRADUZIONE ISTANTANEA! Cerchiamo il nuovo UUID nel nostro dizionario
String? newSupabaseBrandUuid;
if (oldFirebaseBrandId != null) {
newSupabaseBrandUuid = brandTranslationMap[oldFirebaseBrandId];
}
2026-05-05 09:30:03 +02:00
// 3. Controllo di sicurezza: se il brand non esiste su Supabase, saltiamo il record o mettiamo null?
2026-05-04 19:32:14 +02:00
// Se nella tua tabella 'model' il 'brand_id' NON PUÒ essere null, devi per forza avere un match!
if (newSupabaseBrandUuid == null && oldFirebaseBrandId != null) {
print(
"ATTENZIONE: Il modello ${data['nome']} ha un brand_id ($oldFirebaseBrandId) che non esiste su Supabase. Salto o metto null.",
);
continue; // Decommenta questo se vuoi saltare i modelli orfani
}
// Creiamo la riga per Supabase
supabaseModels.add({
'legacy_id': doc.id,
// ECCO LA CHIAVE ESTERNA TRADOTTA!
'brand_id': newSupabaseBrandUuid,
// Mappa gli altri campi
2026-05-05 09:30:03 +02:00
'name': (data['nome'] as String).trim().toLowerCase(),
2026-05-04 19:32:14 +02:00
'name_with_brand': (data['nomeConMarca'] as String)
.toLowerCase()
.trim(),
});
}
// ==========================================================
// FASE 3: INVIO A SUPABASE
// ==========================================================
print("Sto per inviare ${supabaseModels.length} modelli a Supabase...");
await Supabase.instance.client
.from('model')
.upsert(supabaseModels, onConflict: 'legacy_id');
print("BOOM! Migrazione modelli completata con successo! 🚀");
} catch (e) {
print("Errore durante la migrazione dei modelli: $e");
}
}
2026-05-05 09:30:03 +02:00
2026-05-06 01:18:14 +02:00
class TicketMigrationScript {
final SupabaseClient supabase = Supabase.instance.client;
final String companyId = GetIt.I.get<SessionCubit>().state.company!.id!;
final String storeId = GetIt.I.get<SessionCubit>().state.currentStore!.id!;
/// Esegui questa funzione passandole la stringa JSON grezza (es. copiata da un file)
/// e l'ID della tua Company su Supabase (visto che Firebase non lo aveva).
Future<void> runMigration(String jsonString) async {
debugPrint('🚀 INIZIO MIGRAZIONE TICKET...');
try {
// 1. Parsing del JSON
final Map<String, dynamic> decoded = jsonDecode(jsonString);
// Scendiamo al piano di sotto, direttamente nella "pancia" dei dati!
final Map<String, dynamic> rawData = decoded['data'];
debugPrint('Trovati ${rawData.length} elementi alla radice.');
if (rawData.isNotEmpty) {
debugPrint(
'Il primo elemento contiene: ${rawData.entries.first.value}',
);
2026-05-05 09:30:03 +02:00
}
2026-05-06 01:18:14 +02:00
// 2. CREAZIONE DELLA CACHE (IL TRUCCO PER NON IMPAZZIRE CON LE JOIN)
debugPrint('📥 Scarico le mappe dei legacy_id da Supabase...');
final customersRes = await supabase
.from('customer')
.select('id, legacy_id')
.not('legacy_id', 'is', null);
final modelsRes = await supabase
.from('model')
.select('id, legacy_id')
.not('legacy_id', 'is', null);
// Creiamo i dizionari: chiave = legacy_id (Firebase), valore = uuid (Supabase)
final Map<String, String> customerMap = {
for (var row in customersRes)
if (row['legacy_id'] != null)
row['legacy_id'].toString(): row['id'].toString(),
};
final Map<String, String> modelMap = {
for (var row in modelsRes)
if (row['legacy_id'] != null)
row['legacy_id'].toString(): row['id'].toString(),
};
debugPrint(
'✅ Mappe pronte: ${customerMap.length} clienti, ${modelMap.length} modelli.',
);
// 3. MAPPATURA DEI DATI
List<Map<String, dynamic>> ticketsToInsert = [];
for (var entry in rawData.entries) {
final data = entry.value as Map<String, dynamic>;
// Recuperiamo le relazioni usando i nostri dizionari
final String? customerId = customerMap[data['idCliente']];
final String? modelId = modelMap[data['idModello']];
// Se non troviamo il cliente o il modello, magari loggiamo e saltiamo (o mettiamo null)
// Per ora li mettiamo null, ma almeno non spacca il DB
// Risoluzione Date
DateTime? createdAt = _parseFirebaseDate(data['dataAperturaScheda']);
//DateTime? closedAt = _parseFirebaseDate(data['dataChiusuraScheda']);
//DateTime? returnedAt = _parseFirebaseDate(data['dataRiconsegnaCliente']);
// Costruzione del Ticket
ticketsToInsert.add({
'legacy_id': data['fsId'], // Il vecchio ID del doc Firebase
'company_id': companyId,
'store_id': storeId,
'customer_id': customerId,
'target_model_id': modelId,
'target_sn': data['seriale'] ?? '',
'customer_price': data['costoTotaleCliente'] ?? 0.0,
'internal_cost': data['costoTotaleNostro'] ?? 0.0,
'created_at':
createdAt?.toUtc().toIso8601String() ??
DateTime.now().toUtc().toIso8601String(),
//'closed_at': closedAt?.toUtc().toIso8601String(),
//'returned_at': returnedAt?.toUtc().toIso8601String(),
'request': (data['guasto']?.toString() ?? ''),
'included_accessories': data['accessoriConsegnati'],
'public_notes': data['note'],
'internal_notes': data['noteInterne'],
'resolution_notes':
data['operazioneEffettuata'], // Il nuovo campo di cui parlavamo!
'alternative_phone_number': data['recapitoCliente'],
'has_courtesy_device': data['prestatoMuletto'] ?? false,
// Mappatura Enums
'ticket_type': _mapTicketType(data),
'ticket_status': _mapTicketStatus(data),
'ticket_result': _mapTicketResult(data['risultato']),
// 'warranty_type': _mapWarranty(data['nomeTipoGaranzia']), // De-commenta se hai la logica pronta
});
2026-05-05 09:30:03 +02:00
}
2026-05-06 01:18:14 +02:00
// 4. INSERIMENTO BATCH (A botte di 100 per non far arrabbiare Postgres)
debugPrint(
'🚀 Inizio inserimento di ${ticketsToInsert.length} ticket su Supabase...',
);
2026-05-05 09:30:03 +02:00
2026-05-06 01:18:14 +02:00
const int batchSize = 100;
for (int i = 0; i < ticketsToInsert.length; i += batchSize) {
final end = (i + batchSize < ticketsToInsert.length)
? i + batchSize
: ticketsToInsert.length;
final batch = ticketsToInsert.sublist(i, end);
2026-05-05 09:30:03 +02:00
2026-05-06 01:18:14 +02:00
await supabase.from('ticket').insert(batch);
debugPrint('✅ Inseriti ticket da $i a $end');
2026-05-05 09:30:03 +02:00
}
2026-05-06 01:18:14 +02:00
debugPrint('🎉 MIGRAZIONE COMPLETATA CON SUCCESSO!');
} catch (e, stacktrace) {
debugPrint('❌ ERRORE DURANTE LA MIGRAZIONE: $e');
debugPrint(stacktrace.toString());
}
}
2026-05-05 09:30:03 +02:00
2026-05-06 01:18:14 +02:00
// --- FUNZIONI DI AIUTO (PARSER E MAPPER) ---
2026-05-05 09:30:03 +02:00
2026-05-06 01:18:14 +02:00
/// Estrae la data dalla fastidiosa struttura {"__time__": "..."} di Firestore export
DateTime? _parseFirebaseDate(dynamic dateData) {
if (dateData == null) return null;
if (dateData is Map && dateData.containsKey('__time__')) {
return DateTime.tryParse(dateData['__time__'].toString());
}
if (dateData is String) {
return DateTime.tryParse(dateData);
}
return null;
}
2026-05-05 09:30:03 +02:00
2026-05-06 01:18:14 +02:00
/// Converte i boolean di Firebase nel tuo Enum TicketType
String _mapTicketType(Map<String, dynamic> data) {
if (data['tipoLavorazionePassaggioDati'] == true) return 'data_transfer';
if (data['tipoLavorazioneRiparazione'] == true) return 'repair';
if (data['tipoLavorazioneConfigurazione'] == true) return 'software_setup';
return 'other'; // Include tipoLavorazioneAltro o fallback
}
2026-05-05 09:30:03 +02:00
2026-05-06 01:18:14 +02:00
/// Converte la logica di stato di Firebase nel tuo Enum TicketStatus
String _mapTicketStatus(Map<String, dynamic> data) {
// Se è stato riconsegnato al cliente o ritirato, è chiuso/consegnato
if (data['riconsegnato'] == true ||
data['nomeStatoScheda'] == 'Ritirato da cliente') {
return 'closed'; // o 'closed', in base alla tua logica
2026-05-05 09:30:03 +02:00
}
2026-05-06 01:18:14 +02:00
// Altrimenti valutiamo le stringhe
final String statoFirebase =
data['nomeStatoScheda']?.toString().toLowerCase() ?? '';
2026-05-05 09:30:03 +02:00
2026-05-06 01:18:14 +02:00
if (statoFirebase.contains('accettazione')) return 'open';
if (statoFirebase.contains('da inviare centro esterno'))
return 'waiting_for_shipping';
if (statoFirebase.contains('attesa ricambi')) return 'waiting_for_parts';
if (statoFirebase.contains('pronto')) return 'ready';
if (data['daLavorare'] == true) return 'in_progress';
2026-05-05 09:30:03 +02:00
2026-05-06 01:18:14 +02:00
return 'closed'; // Fallback
}
String? _mapTicketResult(dynamic risultato) {
if (risultato == null || risultato.toString().isEmpty) return null;
final r = risultato.toString().toUpperCase();
if (r == 'OK') return 'success';
if (r == 'KO' || r == 'NON RIPARATO') return 'failure';
return null;
2026-05-05 09:30:03 +02:00
}
}