@@ -0,0 +1,330 @@
|
||||
(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);
|
||||
Reference in New Issue
Block a user