256 lines
8.6 KiB
JavaScript
256 lines
8.6 KiB
JavaScript
// 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
|
|
};
|
|
}
|