diff --git a/README.md b/README.md index 513f572..a3de85a 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,87 @@ # harbour-henflow -Porting **nativo Sailfish OS** dell'app Android *Henflow* (`it.carlobaratto.henflow`, -Gestione ordini uova, galline e consegne). UI in QML/Silica, logica e dati in C++, -pubblicazione prevista sullo **store Jolla Harbour**. +Porting **nativo Sailfish OS** dell'app Android *Henflow* (`it.carlobaratto.henflow`). +UI in QML/Silica, calcolo e dati locali, pubblicazione prevista sullo +**store Jolla Harbour**. -Il porting **non** usa il runtime Flutter community (`flutter-sailfishos`): quello -resta una via OpenRepos, non Harbour-safe, e non supporta i plugin nativi Android -(AdMob/Firebase/Billing). Dati locali sul telefono: nessun account, nessun cloud. +Il porting **non** usa il runtime Flutter community (`flutter-sailfishos`): +quello resta una via OpenRepos, non Harbour-safe, e non supporta i plugin nativi +Android (AdMob/File picker/Share). Nessun account, nessun cloud, come l'originale. + +## Sorgenti di riferimento + +- Flutter (originale): `workspace/henflow-flutter` — export da Nextcloud, repo Gitea `HenFlow`. +- Screenshot dello store usati come riferimento UI. ## Stato -Guscio completo e pronto alla prima build (finestra, pagina principale, cover, -impostazioni con lingua, icone, catalogo traduzioni, spec RPM). Le schermate sono -segnaposto: vanno mappate 1:1 dai sorgenti Flutter. +| parte | stato | +|---|---| +| Packaging (spec RPM, desktop SailJail, icone, i18n) | pronto | +| Guscio app (finestra, cover, Settings con lingua) | pronto | +| Motore di calcolo (`qml/js/henflow.js`) | portato e verificato con node | +| Schermate QML | da implementare | ## Struttura ``` -harbour-henflow.pro # QT += quick qml, INSTALLS icone -rpm/harbour-henflow.spec # lrelease + desktop cercati con find (layout sorgente variabile) +harbour-henflow.pro # QT += quick qml, INSTALLS icone +rpm/harbour-henflow.spec # lrelease + desktop cercati con find (layout sorgente variabile) rpm/harbour-henflow.desktop # profilo [X-Sailjail] esplicito (dati persistenti) -src/main.cpp # SailfishApp, context property appSettings, lingua -src/settings.h/.cpp # QSettings su path esplicito AppConfigLocation + sync() -qml/harbour-henflow.qml # ApplicationWindow: initialPage + cover -qml/pages/MainPage.qml # segnaposto con i moduli previsti -qml/cover/CoverPage.qml -icons/make_icons.py # icone 86/108/128/172 (PIL) -translations/*.ts # stringhe sorgente INGLESI + catalogo italiano +src/main.cpp # SailfishApp, context property appSettings, lingua +src/settings.h/.cpp # QSettings su path esplicito AppConfigLocation + sync() +qml/harbour-henflow.qml # ApplicationWindow: initialPage + cover +qml/pages/MainPage.qml # indice dei moduli (segnaposto) +qml/js/henflow.js # motore di calcolo (ES5, testabile con node) +tests/engine.test.js # 30 controlli: node tests/engine.test.js +icons/make_icons.py # icone 86/108/128/172 (PIL) +translations/*.ts # stringhe sorgente INGLESI + catalogo italiano ``` +## Motore di calcolo (portato fedelmente da `lib/main.dart`) + +- **Produzione stimata** per razza = galline in deposizione x uova/gallina/giorno + (catalogo: tutte 1.0). Una gallina conta da **6 mesi** in su (`_ageInMonths`). +- **Magazzino**: all'apertura, i giorni passati dall'ultimo aggiornamento + aggiungono la produzione giornaliera alle scorte di ogni razza attiva. +- **Coda FIFO**: ordini in ordine di creazione; ognuno consuma le scorte virtuali + della sua razza, chi le trova consegna oggi, gli altri aspettano + `ceil(uova_mancanti / produzione)` giorni. Gli ordini gia' consegnati non + consumano scorte. +- **Stima per un nuovo ordine**: `ceil((pendenti + richieste - scorte) / produzione)`. + +Le date sono sempre normalizzate a `YYYY-MM-DD` e le funzioni non mutano l'input. + +## Modello dati (fedele a `StorageService`, SharedPreferences + JSON) + +| chiave | contenuto | +|---|---| +| `breed_settings_v2` | `[{breedId, hens:[{id, name, birthDate, isLaying}]}]` | +| `reservations_v2` | `[{id, customerName, breedId, eggsRequested, createdAt, deliveryDate, isDelivered}]` | +| `customers_v1` | `[{id, firstName, lastName, phoneNumber}]` | +| `warehouse_stock_v1` | `{breedId: uova}` | +| `last_production_update_v1` | data ISO dell'ultimo accrual | + +Nel porting: un unico file JSON in `~/.config/harbour/henflow/` (nessuna +dipendenza extra, nessun driver SQL da verificare sul device) con la stessa +struttura, cosi' il backup/ripristino resta compatibile con l'app Android. + +## Schermate mappate (da sorgenti + screenshot) + +| schermata Flutter | contenuto | porting | +|---|---|---| +| `OnboardingPage` | 4 passi introduttivi | da fare | +| `ModeSelectionPage` | modalita' allevatore / cliente | da decidere | +| `HomePage` | tab *Orders list* / *Calendar* (+ magazzino), card produzione stimata, card ordini con stato e azioni (condividi/modifica/elimina), FAB *New order* | da fare | +| `SettingsPage` | tab *Available breeds* (catalogo con foto e spunta) / *My coop* (galline registrate, in deposizione, produzione) + backup/ripristino JSON + lingua | da fare | +| `BreedDetailPage` | galline della razza, eta' in mesi, stato deposizione | da fare | +| `CustomersPage` + dialoghi | anagrafica clienti, nuovo ordine | da fare | +| `AboutPage` | info app | da fare | +| `CoopIcon` (CustomPainter) | icona pollaio disegnata a mano | da ridisegnare (Canvas QML o PNG) | + +Differenze da tenere presenti: **niente annunci** (AdMob non esiste su Sailfish), +**share/export** via file JSON in Download invece di `share_plus`/`file_picker`. + ## Build e deploy Sul PC con l'SDK Sailfish (qui non c'e' sfdk): @@ -45,32 +98,10 @@ Tarball per lo store (con `--prefix`, esclude .git e i file non tracciati): git archive --format=tar.gz --prefix=harbour-henflow-0.1.0/ -o harbour-henflow-0.1.0.tar.gz HEAD ``` -## Modello dati proposto (da confermare sui sorgenti Flutter) +## Aperti -Un file JSON in `AppConfigLocation` (`~/.config/harbour/henflow/henflow.json`) -e' sufficiente: nessuna dipendenza extra e nessun driver SQL da verificare sul -device. SQLite resta l'alternativa se servono query piu' ricche. - -| entita' | campi | -|---|---| -| `hens` | id, nome, razza, data_nascita, uova_settimana (stima), attiva, note | -| `egg_log` | data, uova_raccolte (+ eventuale id gallina) | -| `customers` | id, nome, telefono, quantita_abituale, giorno_consegna, note | -| `orders` | id, id_cliente, quantita, data_ordine, data_consegna, stato, note | - -Derivati: **uova disponibili oggi** = produzione stimata/registrata meno gli ordini -con data di consegna odierna; **data consegna automatica** = primo giorno di -consegna del cliente successivo all'ordine. - -## Prossimi passi - -1. Copiare i sorgenti Flutter sul server (vedi sotto). -2. Mappare schermate e campi reali (non la descrizione dello store). -3. Implementare storage + schermate, verifica su device (192.168.2.15). -4. Asset store (icona, cover 1080x540), descrizioni EN, upload Harbour. - -```sh -# dal PC con i sorgenti Flutter (LAN o VPN) -rsync -av --exclude build --exclude .dart_tool --exclude .git \ - ~/percorso/henflow/ kaneda@192.168.3.21:/home/kaneda/workspace/henflow-flutter/ -``` +1. **Asset**: nel materiale di Nextcloud mancano `assets/breeds/*.jpg` (7 foto) + e `pubspec.yaml`. +2. **Repo Gitea `HenFlow`**: cancellata (era un upload parziale); per ricrearla + serve un permesso che il token attuale non ha (`write:user`). +3. **Ambito**: modalita' cliente e 4 lingue (EN/IT/FR/DE) da confermare. diff --git a/harbour-henflow.pro b/harbour-henflow.pro index 3f94eb2..2664c31 100644 --- a/harbour-henflow.pro +++ b/harbour-henflow.pro @@ -19,6 +19,7 @@ OTHER_FILES += \ qml/harbour-henflow.qml \ qml/pages/*.qml \ qml/cover/*.qml \ + qml/js/henflow.js \ rpm/harbour-henflow.spec \ rpm/harbour-henflow.desktop \ translations/harbour-henflow.ts \ diff --git a/qml/js/henflow.js b/qml/js/henflow.js new file mode 100644 index 0000000..907bc38 --- /dev/null +++ b/qml/js/henflow.js @@ -0,0 +1,255 @@ +// Henflow — motore di calcolo. +// Porting fedele della logica di lib/main.dart dell'app Flutter: +// produzione stimata per razza, magazzino uova (accrual giornaliero), +// coda FIFO delle prenotazioni e data di consegna calcolata. +// +// ES5 PURO: il motore JS di QML su Qt 5.6 non supporta let/const, arrow +// function, template literal, class, destructuring. Funzioni TOP-LEVEL +// (un `var HF = (function(){...})()` non sarebbe visibile da QML). +// Banchi di prova: tests/engine.test.js (node). + +// ---------------------------------------------------------------- date + +function pad2(n) { + return (n < 10 ? "0" : "") + n; +} + +// "2026-04-27T10:11:12.000" -> Date a mezzanotte locale (Qt 5.6 non e' +// affidabile sul parsing ISO di Date.parse: si estrae a mano). +function parseIso(s) { + if (!s) return null; + var m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(s)); + if (!m) return null; + return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3])); +} + +function isoDay(d) { + return d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate()); +} + +function todayMidnight() { + var n = new Date(); + return new Date(n.getFullYear(), n.getMonth(), n.getDate()); +} + +function addDays(d, n) { + return new Date(d.getFullYear(), d.getMonth(), d.getDate() + n); +} + +// Giorni interi tra due date (ignora l'ora). +function dayDiff(from, to) { + var a = new Date(from.getFullYear(), from.getMonth(), from.getDate()); + var b = new Date(to.getFullYear(), to.getMonth(), to.getDate()); + return Math.round((b.getTime() - a.getTime()) / 86400000); +} + +// ------------------------------------------------- razze e produzione + +function catalogEntry(catalog, breedId) { + for (var i = 0; i < catalog.length; i++) { + if (catalog[i].id === breedId) return catalog[i]; + } + return null; +} + +function eggsPerHenPerDay(catalog, breedId) { + var c = catalogEntry(catalog, breedId); + return c ? c.eggsPerHenPerDay : 0; +} + +function activeHens(breed) { + var n = 0; + for (var i = 0; i < breed.hens.length; i++) { + if (breed.hens[i].isLaying) n++; + } + return n; +} + +// Produzione giornaliera di una razza = galline in deposizione x uova/gallina. +function breedDailyProduction(breedSettings, catalog, breedId) { + for (var i = 0; i < breedSettings.length; i++) { + if (breedSettings[i].breedId === breedId) { + return activeHens(breedSettings[i]) * eggsPerHenPerDay(catalog, breedId); + } + } + return 0; +} + +function totalDailyProduction(breedSettings, catalog) { + var total = 0; + for (var i = 0; i < breedSettings.length; i++) { + total += activeHens(breedSettings[i]) * eggsPerHenPerDay(catalog, breedSettings[i].breedId); + } + return total; +} + +// Dettaglio per la card "produzione stimata": una voce per razza attiva. +function productionDetail(breedSettings, catalog) { + var out = []; + for (var i = 0; i < breedSettings.length; i++) { + var b = breedSettings[i]; + var hens = activeHens(b); + if (hens > 0) { + out.push({ + breedId: b.breedId, + hens: hens, + daily: hens * eggsPerHenPerDay(catalog, b.breedId) + }); + } + } + return out; +} + +// Eta' in mesi compiuti (stessa formula di _ageInMonths in main.dart). +function ageInMonths(birthDate, today) { + var months = (today.getFullYear() - birthDate.getFullYear()) * 12 + + (today.getMonth() - birthDate.getMonth()); + if (today.getDate() < birthDate.getDate()) months--; + return months; +} + +// Una gallina contribuisce alla produzione da 6 mesi in su. +function isProductive(birthDate, today) { + return ageInMonths(birthDate, today) >= 6; +} + +// ----------------------------------------------------------- magazzino + +// Accrual all'apertura: ogni giorno passato dall'ultimo aggiornamento +// aggiunge la produzione giornaliera delle razze con galline in deposizione. +function accrueStock(stock, breedSettings, catalog, daysPassed) { + var out = {}, k; + for (k in stock) { + if (stock.hasOwnProperty(k)) out[k] = stock[k]; + } + if (daysPassed <= 0) return out; + + for (var i = 0; i < breedSettings.length; i++) { + var b = breedSettings[i]; + if (activeHens(b) > 0) { + var prod = breedDailyProduction(breedSettings, catalog, b.breedId); + out[b.breedId] = (out[b.breedId] || 0) + prod * daysPassed; + } + } + return out; +} + +// --------------------------------------------------------- prenotazioni + +function cloneReservation(r) { + return { + id: r.id, + customerName: r.customerName, + breedId: r.breedId, + eggsRequested: r.eggsRequested, + createdAt: r.createdAt, + deliveryDate: r.deliveryDate, + isDelivered: r.isDelivered + }; +} + +function pendingEggs(reservations, breedId) { + var sum = 0; + for (var i = 0; i < reservations.length; i++) { + var r = reservations[i]; + if (r.breedId === breedId && !r.isDelivered) sum += r.eggsRequested; + } + return sum; +} + +// Stima per una NUOVA prenotazione (_calculateDeliveryDate in main.dart): +// uova pendenti della razza + richieste - scorte, divise per la produzione. +function estimateDeliveryDate(reservations, stock, breedSettings, catalog, + breedId, eggsRequested, today) { + var production = breedDailyProduction(breedSettings, catalog, breedId); + var available = stock[breedId] || 0; + var needed = pendingEggs(reservations, breedId) + eggsRequested - available; + if (needed <= 0) return isoDay(today); + if (production <= 0) return isoDay(today); + return isoDay(addDays(today, Math.ceil(needed / production))); +} + +// Ricalcolo della coda (_refreshDates in main.dart): ordine FIFO per data di +// creazione, ogni ordine consuma le scorte virtuali della sua razza; chi trova +// uova sufficienti consegna oggi, gli altri aspettano i giorni necessari. +// Le prenotazioni consegnate non consumano scorte e tengono la loro data. +function refreshDeliveryDates(reservations, stock, breedSettings, catalog, today) { + var list = reservations.slice(); + list.sort(function (a, b) { + return parseIso(a.createdAt).getTime() - parseIso(b.createdAt).getTime(); + }); + + var virtual = {}, k; + for (k in stock) { + if (stock.hasOwnProperty(k)) virtual[k] = stock[k]; + } + + var out = []; + for (var i = 0; i < list.length; i++) { + var r = list[i]; + if (r.isDelivered) { + // Consegnata: nessun consumo di scorte, data invariata (giorno). + var done = cloneReservation(r); + done.deliveryDate = isoDay(parseIso(r.deliveryDate)); + out.push(done); + continue; + } + + var production = breedDailyProduction(breedSettings, catalog, r.breedId); + virtual[r.breedId] = (virtual[r.breedId] || 0) - r.eggsRequested; + + if (virtual[r.breedId] >= 0) { + var ok = cloneReservation(r); + ok.deliveryDate = isoDay(today); + out.push(ok); + } else if (production <= 0) { + // Razza senza galline in deposizione: data non toccata. + var stuck = cloneReservation(r); + stuck.deliveryDate = isoDay(parseIso(r.deliveryDate)); + out.push(stuck); + } else { + var missing = -virtual[r.breedId]; + var late = cloneReservation(r); + late.deliveryDate = isoDay(addDays(today, Math.ceil(missing / production))); + out.push(late); + } + } + + out.sort(function (a, b) { + return parseIso(a.deliveryDate).getTime() - parseIso(b.deliveryDate).getTime(); + }); + return out; +} + +// Stato mostrato nella card: giorni mancanti alla consegna. +function statusKey(days) { + if (days < 0) return "late"; + if (days === 0) return "today"; + if (days === 1) return "tomorrow"; + return "in_days"; +} + +// Guard per i test node: in QML `module` non esiste, le funzioni top-level +// sono gia' visibili al file che importa questo modulo. +if (typeof module !== "undefined" && module.exports) { + module.exports = { + parseIso: parseIso, + isoDay: isoDay, + todayMidnight: todayMidnight, + addDays: addDays, + dayDiff: dayDiff, + catalogEntry: catalogEntry, + eggsPerHenPerDay: eggsPerHenPerDay, + activeHens: activeHens, + breedDailyProduction: breedDailyProduction, + totalDailyProduction: totalDailyProduction, + productionDetail: productionDetail, + ageInMonths: ageInMonths, + isProductive: isProductive, + accrueStock: accrueStock, + pendingEggs: pendingEggs, + estimateDeliveryDate: estimateDeliveryDate, + refreshDeliveryDates: refreshDeliveryDates, + statusKey: statusKey + }; +} diff --git a/qml/pages/MainPage.qml b/qml/pages/MainPage.qml index c0bcd73..bc5a035 100644 --- a/qml/pages/MainPage.qml +++ b/qml/pages/MainPage.qml @@ -15,15 +15,18 @@ Page { model: ListModel { id: sections - // Moduli previsti dall'app Android, da mappare sulle schermate - // Flutter quando i sorgenti arrivano sul server. + // Struttura reale dell'app Flutter (lib/main.dart): la Home ha le tab + // "Orders list" e "Calendar", la schermata Configuration le tab + // "Available breeds" e "My coop". Qui resta l'indice dei moduli + // finche' le schermate non sono implementate. Component.onCompleted: { - append({ "name": qsTr("Hens"), "note": qsTr("name, breed, hatch date, estimated production") }) - append({ "name": qsTr("Available eggs"), "note": qsTr("eggs ready today") }) - append({ "name": qsTr("Orders"), "note": qsTr("customer, quantity, automatic delivery date") }) - append({ "name": qsTr("Calendar"), "note": qsTr("orders and pickups") }) - append({ "name": qsTr("Customers"), "note": qsTr("regular buyers") }) - append({ "name": qsTr("Dashboard"), "note": qsTr("summary of the day") }) + append({ "name": qsTr("Orders list"), "note": qsTr("daily production, reservations, delivery status") }) + append({ "name": qsTr("Calendar"), "note": qsTr("undelivered reservations by day") }) + append({ "name": qsTr("Warehouse"), "note": qsTr("eggs in stock per breed") }) + append({ "name": qsTr("Customers"), "note": qsTr("first name, last name, phone") }) + append({ "name": qsTr("Available breeds"), "note": qsTr("catalog with photos, enable the ones you keep") }) + append({ "name": qsTr("My coop"), "note": qsTr("registered hens, laying, estimated production") }) + append({ "name": qsTr("Backup"), "note": qsTr("export and import the database as JSON") }) } } diff --git a/tests/engine.test.js b/tests/engine.test.js new file mode 100644 index 0000000..dca9e37 --- /dev/null +++ b/tests/engine.test.js @@ -0,0 +1,120 @@ +// Banchi di prova del motore Henflow (node, fuori dal pacchetto RPM). +// node tests/engine.test.js +// I casi replicano il comportamento dell'app Flutter (lib/main.dart) e i dati +// visibili nello screenshot dello store: Marans 2 galline, Araucana 2, +// Leghorn 3 (di cui 2 in deposizione) -> 6.0 uova/giorno. + +var HF = require("../qml/js/henflow.js"); + +var failures = 0; +function check(name, got, expected) { + var ok = JSON.stringify(got) === JSON.stringify(expected); + if (!ok) failures++; + console.log((ok ? "OK " : "FAIL ") + name + " got=" + JSON.stringify(got) + + (ok ? "" : " expected=" + JSON.stringify(expected))); +} + +// catalogo (dal breed_catalog.dart: tutte le razze 1.0 uovo/gallina/giorno) +var CATALOG = [ + { id: "marans", eggsPerHenPerDay: 1.0 }, + { id: "araucana", eggsPerHenPerDay: 1.0 }, + { id: "olive", eggsPerHenPerDay: 1.0 }, + { id: "livorno", eggsPerHenPerDay: 1.0 } +]; + +function hen(id, laying) { + return { id: id, name: id, birthDate: "2025-01-01T00:00:00.000", isLaying: laying }; +} + +var BREEDS = [ + { breedId: "marans", hens: [hen("m1", true), hen("m2", true)] }, + { breedId: "araucana", hens: [hen("a1", true), hen("a2", true)] }, + { breedId: "livorno", hens: [hen("l1", true), hen("l2", true), hen("l3", false)] } +]; + +var TODAY = new Date(2026, 3, 27); // 27 aprile 2026, come nello screenshot + +// --- produzione --------------------------------------------------------- +check("produzione marans (2 galline)", HF.breedDailyProduction(BREEDS, CATALOG, "marans"), 2.0); +check("produzione livorno (2 su 3 in deposizione)", HF.breedDailyProduction(BREEDS, CATALOG, "livorno"), 2.0); +check("produzione totale = 6.0 uova/giorno", HF.totalDailyProduction(BREEDS, CATALOG), 6.0); +check("razza non configurata -> 0", HF.breedDailyProduction(BREEDS, CATALOG, "olive"), 0); +check("dettaglio razze attive", HF.productionDetail(BREEDS, CATALOG).map(function (e) { return e.daily; }), [2, 2, 2]); + +// --- eta' e produttivita' (soglia 6 mesi) -------------------------------- +check("eta' 6 mesi compiuti", HF.ageInMonths(new Date(2025, 9, 27), TODAY), 6); +check("un giorno prima dei 6 mesi -> 5", HF.ageInMonths(new Date(2025, 9, 28), TODAY), 5); +check("6 mesi -> produttiva", HF.isProductive(new Date(2025, 9, 27), TODAY), true); +check("5 mesi -> non ancora produttiva", HF.isProductive(new Date(2025, 10, 28), TODAY), false); + +// --- magazzino: accrual all'apertura ------------------------------------- +check("3 giorni di produzione", HF.accrueStock({}, BREEDS, CATALOG, 3), + { marans: 6, araucana: 6, livorno: 6 }); +check("scorte esistenti incrementate", HF.accrueStock({ marans: 2 }, BREEDS, CATALOG, 1), + { marans: 4, araucana: 2, livorno: 2 }); +check("nessun giorno passato -> invariate", HF.accrueStock({ marans: 1 }, BREEDS, CATALOG, 0), + { marans: 1 }); + +// --- data di consegna per una nuova prenotazione ------------------------- +check("scorte sufficienti -> oggi", + HF.estimateDeliveryDate([], { araucana: 3 }, BREEDS, CATALOG, "araucana", 2, TODAY), "2026-04-27"); +check("2 uova senza scorte, 2/giorno -> domani", + HF.estimateDeliveryDate([], {}, BREEDS, CATALOG, "araucana", 2, TODAY), "2026-04-28"); +check("5 uova senza scorte, 2/giorno -> 3 giorni (ceil)", + HF.estimateDeliveryDate([], {}, BREEDS, CATALOG, "araucana", 5, TODAY), "2026-04-30"); +check("ordini pendenti contano nelle scorte richieste", + HF.estimateDeliveryDate([res("p", "araucana", 4)], {}, BREEDS, CATALOG, "araucana", 2, TODAY), + "2026-04-30"); +check("razza senza galline in deposizione -> oggi (produzione 0)", + HF.estimateDeliveryDate([], {}, BREEDS, CATALOG, "olive", 3, TODAY), "2026-04-27"); + +// --- coda FIFO ------------------------------------------------------------ +function res(id, breedId, eggs, createdDay, delivered) { + return { + id: id, + customerName: "Cli", + breedId: breedId, + eggsRequested: eggs, + createdAt: "2026-04-2" + createdDay + "T09:00:00.000", + deliveryDate: "2026-04-20T00:00:00.000", + isDelivered: !!delivered + }; +} + +var queue = HF.refreshDeliveryDates( + [res("b", "araucana", 2, 5), res("a", "araucana", 1, 1)], + { araucana: 0 }, BREEDS, CATALOG, TODAY); +// ordine FIFO: "a" (creato prima) consuma la prima produzione +check("FIFO: ordine piu' vecchio prima", [queue[0].id, queue[1].id], ["a", "b"]); +check("FIFO: date calcolate in ordine di arrivo", + [queue[0].deliveryDate, queue[1].deliveryDate], ["2026-04-28", "2026-04-29"]); +check("ordine evaso subito con scorte", + HF.refreshDeliveryDates([res("c", "araucana", 2, 1)], { araucana: 2 }, BREEDS, CATALOG, TODAY)[0].deliveryDate, + "2026-04-27"); +check("ordine consegnato: data invariata e nessun consumo di scorte", + HF.refreshDeliveryDates([res("d", "araucana", 99, 1, true), res("e", "araucana", 1, 2)], + { araucana: 1 }, BREEDS, CATALOG, TODAY)[0].deliveryDate, + "2026-04-20"); +check("razza a produzione 0: data non toccata", + HF.refreshDeliveryDates([res("f", "olive", 3, 1)], {}, BREEDS, CATALOG, TODAY)[0].deliveryDate, + "2026-04-20"); +check("la coda modificata non altera l'input (immutabilita')", + (function () { + var input = [res("g", "araucana", 1, 1)]; + HF.refreshDeliveryDates(input, {}, BREEDS, CATALOG, TODAY); + return input[0].deliveryDate; + })(), "2026-04-20T00:00:00.000"); + +// --- stato della card ----------------------------------------------------- +check("stato: scaduto", HF.statusKey(-1), "late"); +check("stato: oggi", HF.statusKey(0), "today"); +check("stato: domani", HF.statusKey(1), "tomorrow"); +check("stato: fra N giorni", HF.statusKey(4), "in_days"); + +// --- date ----------------------------------------------------------------- +check("dayDiff ignora l'ora", HF.dayDiff(new Date(2026, 3, 27, 23, 59), new Date(2026, 3, 28, 0, 1)), 1); +check("parseIso -> solo il giorno", HF.isoDay(HF.parseIso("2026-04-28T09:30:00.000")), "2026-04-28"); +check("isoDay con padding", HF.isoDay(new Date(2026, 0, 5)), "2026-01-05"); + +console.log(failures === 0 ? "\ntutti i controlli superati" : "\n" + failures + " controlli falliti"); +process.exit(failures === 0 ? 0 : 1); diff --git a/translations/harbour-henflow.ts b/translations/harbour-henflow.ts index 8c38603..7b6d9c4 100644 --- a/translations/harbour-henflow.ts +++ b/translations/harbour-henflow.ts @@ -19,27 +19,11 @@ - Hens + Orders list - name, breed, hatch date, estimated production - - - - Available eggs - - - - eggs ready today - - - - Orders - - - - customer, quantity, automatic delivery date + daily production, reservations, delivery status @@ -47,7 +31,15 @@ - orders and pickups + undelivered reservations by day + + + + Warehouse + + + + eggs in stock per breed @@ -55,15 +47,31 @@ - regular buyers + first name, last name, phone - Dashboard + Available breeds - summary of the day + catalog with photos, enable the ones you keep + + + + My coop + + + + registered hens, laying, estimated production + + + + Backup + + + + export and import the database as JSON diff --git a/translations/harbour-henflow_it.ts b/translations/harbour-henflow_it.ts index a120e09..59ce513 100644 --- a/translations/harbour-henflow_it.ts +++ b/translations/harbour-henflow_it.ts @@ -19,52 +19,60 @@ Henflow - Hens - Galline + Orders list + Elenco ordini - name, breed, hatch date, estimated production - nome, razza, data di nascita, produzione stimata - - - Available eggs - Uova disponibili - - - eggs ready today - uova pronte oggi - - - Orders - Ordini - - - customer, quantity, automatic delivery date - cliente, quantità, data di consegna automatica + daily production, reservations, delivery status + produzione giornaliera, prenotazioni, stato consegne Calendar Calendario - orders and pickups - ordini e ritiri + undelivered reservations by day + prenotazioni da consegnare per giorno + + + Warehouse + Magazzino + + + eggs in stock per breed + uova in scorta per razza Customers Clienti - regular buyers - acquirenti abituali + first name, last name, phone + nome, cognome, telefono - Dashboard - Dashboard + Available breeds + Razze disponibili - summary of the day - riepilogo della giornata + catalog with photos, enable the ones you keep + catalogo con foto, attiva quelle che allevi + + + My coop + Il mio pollaio + + + registered hens, laying, estimated production + galline registrate, in deposizione, produzione stimata + + + Backup + Backup + + + export and import the database as JSON + esporta e importa il database in JSON Native port in progress