// 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 }; }