motore di calcolo portato in JS ES5 con 30 test node; mappatura schermate dal codice Flutter e dagli screenshot
This commit is contained in:
@@ -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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user