Files
site/apps/web/public/wesp/js/pages/lab-components-nutrients.js
T

331 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function (global) {
"use strict";
/** Показатели для зоотех-расчёта рациона (weighted average). */
const NUTRIENT_ROWS = [
{
label: "Осн. корм",
unit: "г/кг СВ",
keys: ["Осн.Корм"],
hint: "СВ этого сырья, учитываемое как объёмный (основной) корм в норме dm_main. 0 — концентрат/добавка.",
},
{ label: "Сыр. протеин", unit: "г/кг", keys: ["Сыр. Протеин"] },
{ label: "уСП", unit: "г/кг", keys: ["уСП"] },
{ label: "RNB", unit: "г/кг", keys: ["RNB", "БРА", " БРА "] },
{ label: "ЧЭЛ — КРС", unit: "МДж/кг", keys: ["ЧЭЛ- КРС", " ЧЭЛ- КРС"] },
{ label: "ОЭ — КРС", unit: "МДж/кг", keys: ["ОЭ-КРС", " ОЭ-КРС"] },
{ label: "Сырая клетчатка", unit: "г/кг", keys: ["Сырая клетч", "Сырая клетчатка"] },
{ label: "Структур. клетч.", unit: "г/кг", keys: ["Структур клетч", "Структур. клетч", "Структур. клетчатка"] },
{ label: "Сырой жир", unit: "г/кг", keys: ["Сырой жир"] },
];
/** Лаб. ввод для native derive (см. LAB_FORMULAS.md). СВ — только component.dry_matter (%). */
const LAB_INPUT_ROWS = [
{ label: "Сырая зола", unit: "г/кг", keys: ["Сырая зола"] },
{
label: "ВРХ орг. вещ.",
unit: "%",
keys: ["ВРХ Орг Вещ"],
hint: "Обязательно для грубых и сочных кормов. Без значения подставится дефолт WESP (65% / 72%).",
},
{
label: "уСП в ОР",
unit: "г/кг",
keys: ["уСП в ОР", "уСП в ОВ"],
hint: "Лабораторное уСП: при значении > 1 заменяет расчёт по формуле Lebzien.",
},
{ label: "КРС протеин", unit: "%", keys: ["КРС Протеин"] },
{ label: "КРС сырой жир", unit: "%", keys: ["КРС Сырой жир"] },
{ label: "КРС сырая клетч.", unit: "%", keys: ["КРС Сырая клетч"] },
{ label: "КРС БЭВ", unit: "%", keys: ["КРС БЭВ"] },
{ label: "НДК", unit: "г/кг", keys: ["НДК", "НДК Общ"] },
{ label: "NFC", unit: "г/кг", keys: ["NFC", "БЕР"] },
];
/** Минералы, аминокислоты и прочий лаб. ввод из AgroStar / импорта. */
const EXTRA_LAB_ROWS = [
{ label: "КДК (ADF)", unit: "г/кг", keys: ["КДК", "КДК общ"] },
{ label: "Ca", unit: "г/кг", keys: ["Ca"] },
{ label: "P", unit: "г/кг", keys: ["P"] },
{ label: "Mg", unit: "г/кг", keys: ["Mg"] },
{ label: "K", unit: "г/кг", keys: ["K"] },
{ label: "S", unit: "г/кг", keys: ["S"] },
{ label: "Cl", unit: "г/кг", keys: ["CL", "Cl"] },
{ label: "Сахар", unit: "г/кг", keys: ["Сахар"] },
{ label: "Крахмал", unit: "г/кг", keys: ["Крахмал"] },
{ label: "Нераств. протеин", unit: "%", keys: ["% нераствор протеин"] },
{ label: "Лизин", unit: "г/кг", keys: ["Лизин"] },
{ label: "Метионин", unit: "г/кг", keys: ["Метионин"] },
{ label: "Лейцин", unit: "г/кг", keys: ["Лейцин"] },
{ label: "Изолейцин", unit: "г/кг", keys: ["Изолейцин"] },
{ label: "Валин", unit: "г/кг", keys: ["Валин"] },
];
const ALL_FORM_ROWS = [...NUTRIENT_ROWS, ...LAB_INPUT_ROWS, ...EXTRA_LAB_ROWS];
const CARD_INFO_COLUMNS = [
{
label: "Сухое вещество",
value(component) {
const dm = component.dryMatter ?? component.dry_matter;
return dm == null || dm === "" ? "—" : `${dm}%`;
},
},
{
label: "№ справочника",
value(component) {
const n = component.externalNo ?? component.external_no;
return n == null || n === "" ? "—" : String(n);
},
},
{
label: "Цена",
value(component) {
const price = component.price;
return price == null || price === "" ? "—" : `${price} руб/кг`;
},
},
{
label: "Осн. корм",
value(_component, nutrients) {
const val = readNutrientValue(nutrients, ["Осн.Корм"]);
return val == null ? "—" : `${val} г/кг`;
},
},
{
label: "Сыр. протеин",
value(_component, nutrients) {
const val = readNutrientValue(nutrients, ["Сыр. Протеин"]);
return val == null ? "—" : `${val} г/кг`;
},
},
{
label: "ОЭ — КРС",
value(_component, nutrients) {
const val = readNutrientValue(nutrients, ["ОЭ-КРС", " ОЭ-КРС"]);
return val == null ? "—" : `${val} МДж/кг`;
},
},
{
label: "СВ (расч.)",
value(component) {
const dm = component.dryMatter ?? component.dry_matter;
if (dm == null || dm === "") return "—";
const n = Number(dm);
if (Number.isNaN(n)) return "—";
return `${(n * 10).toFixed(1)} г/кг`;
},
},
{
label: "Показателей",
value(_component, nutrients) {
return String(Object.keys(nutrients || {}).length);
},
},
];
function normalizeKey(s) {
return String(s || "")
.replace(/\s+/g, " ")
.trim()
.toLowerCase();
}
function parseNutrients(raw) {
if (!raw) return {};
if (typeof raw === "object") return { ...raw };
try {
return JSON.parse(raw) || {};
} catch (_) {
return {};
}
}
function readNutrientValue(nutrients, keys) {
const map = nutrients || {};
for (const search of keys) {
const target = normalizeKey(search);
for (const [k, v] of Object.entries(map)) {
if (normalizeKey(k) !== target) continue;
const n = Number(v);
if (!Number.isNaN(n)) return n;
}
}
return null;
}
function canonicalKey(keys) {
return keys[0];
}
function buildNutrientsPayload(root) {
const payload = {};
if (!root) return payload;
root.querySelectorAll("[data-nutrient-key]").forEach((input) => {
const key = input.getAttribute("data-nutrient-key");
const val = input.value.trim();
if (key && val !== "") payload[key] = Number(val);
});
return payload;
}
function rowFieldsHtml(rows, nutrients) {
return rows
.map((row) => {
const key = canonicalKey(row.keys);
const val = readNutrientValue(nutrients, row.keys);
const hint = row.hint ? ` title="${row.hint}"` : "";
return (
`<div class="lab-nutrient-field"${hint}>` +
`<label class="form-label small mb-0 lab-nutrient-field__label">${row.label}` +
`<span class="text-muted"> (${row.unit})</span></label>` +
`<input type="number" step="0.01" class="form-control form-control-sm" ` +
`data-nutrient-key="${key}" value="${val == null ? "" : val}" placeholder="—">` +
`</div>`
);
})
.join("");
}
const _OMD_FEED_TYPES = new Set(["грубые корма", "сочные корма", "объемные корма", "объёмные корма"]);
function feedGroupNeedsOmd(component) {
if (!component || !component.type) return false;
return _OMD_FEED_TYPES.has(normalizeKey(component.type));
}
function omdWarningHtml(component, nutrients) {
if (!feedGroupNeedsOmd(component)) return "";
if (readNutrientValue(nutrients, ["ВРХ Орг Вещ", "КРС Орг Вещ"]) != null) return "";
return (
`<div class="alert alert-warning py-2 px-3 small mb-2" role="status">` +
`Для грубых и сочных кормов укажите <strong>ВРХ орг. вещ.</strong> — ` +
`без анализа расчёт энергии и уСП будет ненадёжным.` +
`</div>`
);
}
function nutrientsFormHtml(nutrients, component) {
const calcRows = rowFieldsHtml(NUTRIENT_ROWS, nutrients);
const labRows = rowFieldsHtml(LAB_INPUT_ROWS, nutrients);
const extraRows = rowFieldsHtml(EXTRA_LAB_ROWS, nutrients);
const warn = omdWarningHtml(component, nutrients);
return (
warn +
`<div class="lab-nutrients-block" data-lab-nutrients-block>` +
`<div class="lab-nutrients-block__head">` +
`<i class="fas fa-flask text-primary me-1"></i>` +
`<span class="lab-nutrients-block__title">Показатели для расчёта рациона</span>` +
`</div>` +
`<div class="lab-nutrients-row lab-nutrients-row--calc">${calcRows}</div>` +
`</div>` +
`<div class="lab-nutrients-block lab-nutrients-block--lab-input mt-3" data-lab-nutrients-lab>` +
`<div class="lab-nutrients-block__head">` +
`<i class="fas fa-vial text-secondary me-1"></i>` +
`<span class="lab-nutrients-block__title">Лабораторный ввод (derive)</span>` +
`</div>` +
`<div class="lab-nutrients-row lab-nutrients-row--lab">${labRows}</div>` +
`</div>` +
`<div class="lab-nutrients-block lab-nutrients-block--extra mt-3" data-lab-nutrients-extra>` +
`<div class="lab-nutrients-block__head">` +
`<i class="fas fa-atom text-secondary me-1"></i>` +
`<span class="lab-nutrients-block__title">Минералы и аминокислоты</span>` +
`</div>` +
`<div class="lab-nutrients-row lab-nutrients-row--extra">${extraRows}</div>` +
`</div>`
);
}
function mountCreateForm() {
const mount = document.getElementById("labNutrientsCreateMount");
if (!mount) return;
mount.innerHTML = nutrientsFormHtml({});
mount.dataset.mounted = "1";
}
function mountEditForm() {
const mount = document.getElementById("labNutrientsEditMount");
if (!mount) return;
mount.innerHTML = nutrientsFormHtml({});
mount.dataset.mounted = "1";
}
function resetCreateForm() {
const mount = document.getElementById("labNutrientsCreateMount");
if (!mount) return;
mount.innerHTML = nutrientsFormHtml({});
mount.dataset.mounted = "1";
}
function fillCreateFromComponent(component) {
const mount = document.getElementById("labNutrientsCreateMount");
if (!mount || !component) return;
const nutrients = parseNutrients(component.nutrients);
mount.innerHTML = nutrientsFormHtml(nutrients, component);
mount.dataset.mounted = "1";
}
function fillEditNutrients(component) {
const mount = document.getElementById("labNutrientsEditMount");
if (!mount) return;
mount.innerHTML = nutrientsFormHtml(parseNutrients(component.nutrients), component);
mount.dataset.mounted = "1";
}
function formatCardItems(component) {
const nutrients = parseNutrients(component.nutrients);
return CARD_INFO_COLUMNS.map((col) => {
const value = col.value(component, nutrients);
return {
label: col.label,
value,
empty: value === "—",
};
});
}
function renderCardInfoHtml(component) {
if (!global.WESP_CAN_LAB) return "";
return formatCardItems(component)
.map(
(item) =>
`<div class="info-item${item.empty ? " info-item--empty" : ""}">` +
`<span class="info-label">${item.label}</span>` +
`<span class="info-value">${item.value}</span>` +
`</div>`
)
.join("");
}
function collectFromCreateForm() {
return buildNutrientsPayload(document.getElementById("labNutrientsCreateMount"));
}
function collectFromEditForm() {
return buildNutrientsPayload(document.getElementById("labNutrientsEditMount"));
}
global.WespLabComponentsNutrients = {
NUTRIENT_ROWS,
LAB_INPUT_ROWS,
EXTRA_LAB_ROWS,
normalizeKey,
readNutrientValue,
parseNutrients,
nutrientsFormHtml,
mountCreateForm,
mountEditForm,
resetCreateForm,
fillCreateFromComponent,
fillEditNutrients,
renderCardInfoHtml,
collectFromCreateForm,
collectFromEditForm,
init() {
if (!global.WESP_CAN_LAB) return;
mountCreateForm();
mountEditForm();
},
};
})(window);