schermate di configurazione complete: razze disponibili, mio pollaio, registro galline, clienti, backup/ripristino nel formato dell'app Android, lingua a runtime, introduzione e informazioni

- datastore: export/import JSON con le chiavi camelCase dell'app Android (breedSettings/reservations/customers), scorte e ultimo aggiornamento preservati al ripristino
- settings: flag 'introduzione vista'
- desktop: permesso Documents (dove finisce il backup)
- nuovi controlli: tools/check_qml.py (chiavi di traduzione, simboli JS, pagine, componenti) e qmllint nella suite
- versione 0.2.0
This commit is contained in:
2026-10-09 09:21:47 +02:00
parent a73d94a74e
commit 248e0db899
25 changed files with 2060 additions and 74 deletions
+75 -12
View File
@@ -123,17 +123,74 @@ int main(int argc, char *argv[])
check("chiave last_production_update_v1", root.contains("last_production_update_v1"));
}
// --- backup / ripristino ----------------------------------------------
// --- backup nel formato dell'app Android -------------------------------
QString exported;
{
const QString backup = QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation))
.filePath("backup-test.json");
check("export", store.exportTo(backup));
check("clearAll", store.clearAll());
check("dopo clearAll: vuoto", store.reservations().isEmpty() && store.customers().isEmpty());
check("import", store.importFrom(backup));
check("dopo import: prenotazioni ripristinate", store.reservations().size() == 1);
check("dopo import: clienti ripristinati", store.customers().size() == 1);
QFile::remove(backup);
exported = store.exportBackup();
check("export: percorso restituito", !exported.isEmpty(), exported);
check("export: file scritto in Documenti",
exported.contains(QStringLiteral("/Documents/")), exported);
check("export: file esistente", QFile::exists(exported));
QFile f(exported);
f.open(QIODevice::ReadOnly);
const QJsonObject root = QJsonDocument::fromJson(f.readAll()).object();
f.close();
check("backup: chiave breedSettings (come l'app Android)", root.contains("breedSettings"));
check("backup: chiave reservations", root.contains("reservations"));
check("backup: chiave customers", root.contains("customers"));
check("backup: nessuna chiave interna (breed_settings_v2)",
!root.contains("breed_settings_v2"));
check("backup: esattamente 3 chiavi", root.size() == 3);
}
// backup prodotto dall'app Android (scritto a mano come lo esporta lei):
// deve essere accettato e sostituire i dati.
{
const QString android = QStringLiteral(
"{\n"
" \"breedSettings\": [\n"
" {\"breedId\": \"livorno\", \"hens\": [\n"
" {\"id\": \"h9\", \"name\": \"Gina\", \"birthDate\": \"2024-03-01T00:00:00.000\", \"isLaying\": true}\n"
" ]}\n"
" ],\n"
" \"reservations\": [\n"
" {\"id\": \"r9\", \"customerName\": \"Luca\", \"breedId\": \"livorno\", \"eggsRequested\": 6,\n"
" \"createdAt\": \"2026-10-01T08:00:00.000\", \"deliveryDate\": \"2026-10-03\", \"isDelivered\": false}\n"
" ],\n"
" \"customers\": [\n"
" {\"id\": \"c9\", \"firstName\": \"Luca\", \"lastName\": \"Bianchi\", \"phoneNumber\": \"333\"}\n"
" ]\n"
"}\n");
check("backup Android riconosciuto valido", store.isValidBackup(android));
check("import del backup Android", store.importBackup(android));
check("import: razze sostituite", store.breedSettings().size() == 1
&& store.breedSettings().at(0).toMap().value("breedId").toString() == "livorno");
check("import: gallina annidata letta", store.breedSettings().at(0).toMap()
.value("hens").toList().at(0).toMap().value("name").toString() == "Gina");
check("import: prenotazioni sostituite", store.reservations().size() == 1
&& store.reservations().at(0).toMap().value("eggsRequested").toInt() == 6);
check("import: clienti sostituiti", store.customers().size() == 1
&& store.customers().at(0).toMap().value("lastName").toString() == "Bianchi");
// il backup Android non porta scorte ne' ultima produzione: si tengono
check("import: magazzino preservato", !store.warehouseStock().isEmpty());
check("import: data aggiornamento preservata",
store.lastProductionUpdate() == "2026-10-09");
// rilettura da disco: l'import e' stato salvato
DataStore reopened;
check("import: salvato su disco", reopened.reservations().size() == 1
&& reopened.breedSettings().at(0).toMap().value("breedId").toString() == "livorno");
}
// backup senza 'customers' (le versioni vecchie non lo avevano)
{
const QString old = QStringLiteral("{\"breedSettings\": [], \"reservations\": []}");
check("backup senza clienti: valido", store.isValidBackup(old));
check("backup senza clienti: importato", store.importBackup(old));
check("backup senza clienti: liste vuote", store.breedSettings().isEmpty()
&& store.customers().isEmpty());
}
// --- file corrotto: non deve crashare e non deve perdere i dati ---------
@@ -147,10 +204,16 @@ int main(int argc, char *argv[])
check("file corrotto: non crasha", true);
check("file corrotto: stato vuoto", broken.reservations().isEmpty());
// il backup di un file corrotto viene rifiutato
check("import di un file non valido rifiutato", !broken.importFrom(store.dataFilePath()));
check("import di un JSON non valido rifiutato", !broken.importBackup("{ rotto"));
check("import di un oggetto senza le chiavi richieste rifiutato",
!broken.importBackup("{\"altro\": 1}"));
check("import di un array (non oggetto) rifiutato", !broken.importBackup("[]"));
check("import di testo vuoto rifiutato", !broken.importBackup(" "));
check("isValidBackup su testo vuoto", !broken.isValidBackup(""));
}
QFile::remove(exported);
qInfo().noquote() << (failures == 0 ? "\ntutti i controlli superati"
: QString("\n%1 controlli falliti").arg(failures));
return failures == 0 ? 0 : 1;