677 lines
24 KiB
JavaScript
677 lines
24 KiB
JavaScript
(function (global) {
|
||
"use strict";
|
||
|
||
const CREATE_NEW = "__create_new__";
|
||
const ACCEPT_EXT = [".xml", ".pdf", ".xlsx", ".xls"];
|
||
|
||
let parseResult = null;
|
||
let components = [];
|
||
let selectedFile = null;
|
||
|
||
const WARN_LABELS = {
|
||
omd_from_tdn: "ВРХ через TDN — не lab-grade",
|
||
omd_missing: "нет ВРХ — подставим дефолт",
|
||
dm_missing: "нет СВ — chemistry on hold",
|
||
};
|
||
|
||
function $(id) {
|
||
return document.getElementById(id);
|
||
}
|
||
|
||
function showEl(el) {
|
||
if (!el) return;
|
||
el.hidden = false;
|
||
el.classList.remove("lab-agrostar-reveal");
|
||
requestAnimationFrame(() => el.classList.add("lab-agrostar-reveal"));
|
||
}
|
||
|
||
function hideEl(el) {
|
||
if (!el) return;
|
||
el.hidden = true;
|
||
el.classList.remove("lab-agrostar-reveal");
|
||
}
|
||
|
||
function escapeHtml(s) {
|
||
return String(s ?? "")
|
||
.replace(/&/g, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """);
|
||
}
|
||
|
||
function importKind() {
|
||
return parseResult?.kind || "lab_samples";
|
||
}
|
||
|
||
function isLabSamples() {
|
||
return importKind() === "lab_samples";
|
||
}
|
||
|
||
async function api(path, opts) {
|
||
const resp = await fetch(path, opts);
|
||
const data = await resp.json().catch(() => ({}));
|
||
if (!resp.ok || data.error) {
|
||
throw new Error(data.message || `Наука, чёрт возьми — ошибка ${resp.status}`);
|
||
}
|
||
return data;
|
||
}
|
||
|
||
async function loadComponents(force) {
|
||
if (components.length && !force) return components;
|
||
const rows = await api("/api/components?limit=2000");
|
||
components = Array.isArray(rows) ? rows : [];
|
||
return components;
|
||
}
|
||
|
||
function getSampleByNo(sampleNo) {
|
||
return (parseResult?.samples || []).find((s) => s.sampleNo === sampleNo);
|
||
}
|
||
|
||
function buildComponentOptions(matchSource, selectedId) {
|
||
const suggested = matchSource?.suggestedComponents || [];
|
||
const seen = new Set();
|
||
const opts = [
|
||
'<option value="">— какой реагент? —</option>',
|
||
`<option value="${CREATE_NEW}">+ добавить новый реагент</option>`,
|
||
];
|
||
|
||
for (const m of suggested) {
|
||
if (!m.componentId || seen.has(m.componentId)) continue;
|
||
seen.add(m.componentId);
|
||
const sel = m.componentId === selectedId ? " selected" : "";
|
||
opts.push(
|
||
`<option value="${escapeHtml(m.componentId)}"${sel}>${escapeHtml(m.name)} (${Math.round(m.score * 100)}%)</option>`,
|
||
);
|
||
}
|
||
for (const c of components) {
|
||
if (!c.id || seen.has(c.id)) continue;
|
||
const sel = c.id === selectedId ? " selected" : "";
|
||
opts.push(`<option value="${escapeHtml(c.id)}"${sel}>${escapeHtml(c.name)}</option>`);
|
||
}
|
||
return opts.join("");
|
||
}
|
||
|
||
function selectComponentForSample(sampleNo, componentId) {
|
||
const sel = document.querySelector(
|
||
`.lab-agrostar-component[data-sample-no="${CSS.escape(sampleNo)}"]`,
|
||
);
|
||
const sample = getSampleByNo(sampleNo);
|
||
if (!sel || !sample) return;
|
||
sel.innerHTML = buildComponentOptions(sample, componentId);
|
||
sel.value = componentId;
|
||
}
|
||
|
||
function formatUnsupportedNotice(storage) {
|
||
if (!storage?.unsupportedCount) return "";
|
||
return storage.unsupportedMessage || `${storage.unsupportedCount} показат. AgroStar без полей в WESP`;
|
||
}
|
||
|
||
function notifyCatalogChanged() {
|
||
try {
|
||
global.dispatchEvent(new CustomEvent("wesp:components-catalog-changed"));
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
}
|
||
|
||
async function createComponentFromSample(sampleNo, sel) {
|
||
const sample = getSampleByNo(sampleNo);
|
||
if (!sample || !sel) return;
|
||
|
||
const preview = sample.preview || {};
|
||
const name = (preview.suggestedName || sample.label || "").trim();
|
||
const type = preview.suggestedType || "Сочные корма";
|
||
if (!name) {
|
||
sel.value = "";
|
||
setResult("Нет названия для нового реагента", true);
|
||
return;
|
||
}
|
||
|
||
sel.disabled = true;
|
||
setResult(`Создаём «${name}»…`);
|
||
try {
|
||
const data = await api("/api/components", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({
|
||
name,
|
||
type,
|
||
dry_matter: sample.dryMatterPct || 0,
|
||
nutrients: sample.nutrients || {},
|
||
}),
|
||
});
|
||
await loadComponents(true);
|
||
selectComponentForSample(sampleNo, data.id);
|
||
const storage = preview.storage || {};
|
||
const skipNote = formatUnsupportedNotice(storage);
|
||
setResult(
|
||
`Реагент «${name}» создан — СВ и ${storage.nutrientCount || Object.keys(sample.nutrients || {}).length} показат.` +
|
||
(skipNote ? ` ${skipNote}` : ""),
|
||
false,
|
||
);
|
||
notifyCatalogChanged();
|
||
} catch (err) {
|
||
sel.value = "";
|
||
setResult(err.message || "Не удалось создать реагент", true);
|
||
} finally {
|
||
sel.disabled = false;
|
||
}
|
||
}
|
||
|
||
function onComponentSelectChange(e) {
|
||
const sel = e.target;
|
||
if (!sel.classList?.contains("lab-agrostar-component")) return;
|
||
if (sel.value !== CREATE_NEW) return;
|
||
const sampleNo = sel.getAttribute("data-sample-no") || "";
|
||
createComponentFromSample(sampleNo, sel);
|
||
}
|
||
|
||
function prime(reason) {
|
||
global.WespLabHeisenbergPrime?.error?.(reason, "agrostar");
|
||
}
|
||
|
||
function setStatus(text, isError) {
|
||
const el = $("labAgrostarStatus");
|
||
if (!el) return;
|
||
el.textContent = text || "";
|
||
el.className = "small mt-2 mb-0" + (isError ? " lab-agrostar-result--err" : " text-muted");
|
||
if (isError && text) prime(text);
|
||
}
|
||
|
||
function setResult(text, isError) {
|
||
const el = $("labAgrostarResult");
|
||
if (!el) return;
|
||
el.textContent = text || "";
|
||
el.className = "small mt-2 " + (isError ? "lab-agrostar-result--err" : "lab-agrostar-result--ok");
|
||
if (isError && text) prime(text);
|
||
}
|
||
|
||
function formatWarnings(warnings) {
|
||
if (!warnings?.length) return "—";
|
||
return warnings
|
||
.map((w) => {
|
||
const key = String(w).split("=")[0];
|
||
const label = WARN_LABELS[key] || w;
|
||
return `<span class="lab-agrostar-warn">${escapeHtml(label)}</span>`;
|
||
})
|
||
.join("");
|
||
}
|
||
|
||
function getSelectedComponentLabel(sampleNo) {
|
||
const sel = document.querySelector(`.lab-agrostar-component[data-sample-no="${CSS.escape(sampleNo)}"]`);
|
||
if (!sel?.value || sel.value === CREATE_NEW) return null;
|
||
const opt = sel.options[sel.selectedIndex];
|
||
return opt ? opt.text.replace(/\s*\(\d+%\)\s*$/, "").trim() : null;
|
||
}
|
||
|
||
function renderMetaLine(extra) {
|
||
const meta = $("labAgrostarMeta");
|
||
if (!meta || !parseResult) return;
|
||
showEl(meta);
|
||
const label = parseResult.sourceLabel || parseResult.labName || "Импорт";
|
||
meta.innerHTML =
|
||
`<strong>${escapeHtml(label)}</strong>${extra || ""}` +
|
||
(parseResult.warnings?.length ? ` · ${escapeHtml(parseResult.warnings.join("; "))}` : "");
|
||
}
|
||
|
||
function setActionButtons() {
|
||
const previewBtn = $("labAgrostarPreview");
|
||
const applyBtn = $("labAgrostarApply");
|
||
const clearBtn = $("labAgrostarClear");
|
||
if (previewBtn) previewBtn.disabled = false;
|
||
if (clearBtn) clearBtn.disabled = false;
|
||
if (applyBtn) {
|
||
applyBtn.disabled = !isLabSamples();
|
||
applyBtn.title = isLabSamples() ? "" : "Запись в каталог только для лабораторных проб AgroStar";
|
||
}
|
||
}
|
||
|
||
function renderLabSamplesTable() {
|
||
const wrap = $("labAgrostarTableWrap");
|
||
const body = $("labAgrostarTableBody");
|
||
const head = $("labImportTableHead");
|
||
const title = $("labImportTableTitle");
|
||
if (!wrap || !body || !parseResult) return;
|
||
|
||
const samples = parseResult.samples || [];
|
||
if (!samples.length) {
|
||
hideEl(wrap);
|
||
return;
|
||
}
|
||
|
||
if (title) title.textContent = "Кому в каталог";
|
||
if (head) {
|
||
head.innerHTML = `<tr>
|
||
<th>Образец</th><th>СВ, %</th><th>Реагент</th><th>Косяки</th>
|
||
</tr>`;
|
||
}
|
||
|
||
renderMetaLine(` · образцов: ${samples.length}`);
|
||
|
||
body.innerHTML = samples
|
||
.map((sample) => {
|
||
const sid = escapeHtml(sample.sampleNo);
|
||
const defaultId = sample.suggestedComponents?.[0]?.componentId || "";
|
||
return `<tr data-sample-no="${sid}">
|
||
<td>
|
||
<div class="fw-semibold">${escapeHtml(sample.label || sample.desc1 || sample.sampleNo)}</div>
|
||
<div class="text-muted small">${sid} · ${escapeHtml(sample.datePrinted || "")}</div>
|
||
</td>
|
||
<td>${sample.dryMatterPct != null ? escapeHtml(sample.dryMatterPct) : "—"}</td>
|
||
<td>
|
||
<select class="form-select form-select-sm lab-agrostar-component" data-sample-no="${sid}">
|
||
${buildComponentOptions(sample, defaultId)}
|
||
</select>
|
||
</td>
|
||
<td>${formatWarnings(sample.warnings)}</td>
|
||
</tr>`;
|
||
})
|
||
.join("");
|
||
|
||
showEl(wrap);
|
||
setActionButtons();
|
||
}
|
||
|
||
function renderRationCompositionTable() {
|
||
const wrap = $("labAgrostarTableWrap");
|
||
const body = $("labAgrostarTableBody");
|
||
const head = $("labImportTableHead");
|
||
const title = $("labImportTableTitle");
|
||
const lines = parseResult?.feedLines || [];
|
||
if (!wrap || !body || !lines.length) return;
|
||
|
||
if (title) title.textContent = "Состав рациона";
|
||
if (head) {
|
||
head.innerHTML = `<tr>
|
||
<th>Корм</th><th>кг/сут</th><th>руб.</th><th>Реагент WESP</th>
|
||
</tr>`;
|
||
}
|
||
|
||
const meta = parseResult.meta || {};
|
||
const extra = [
|
||
meta.group ? escapeHtml(meta.group) : "",
|
||
meta.rationDate ? escapeHtml(meta.rationDate) : "",
|
||
meta.totalCostRub ? `Σ ${escapeHtml(meta.totalCostRub)} руб.` : "",
|
||
]
|
||
.filter(Boolean)
|
||
.join(" · ");
|
||
renderMetaLine(` · ${lines.length} корм(ов)${extra ? " · " + extra : ""}`);
|
||
|
||
body.innerHTML = lines
|
||
.map((line, idx) => {
|
||
const defaultId = line.suggestedComponents?.[0]?.componentId || "";
|
||
return `<tr>
|
||
<td>${escapeHtml(line.feedName)}</td>
|
||
<td>${escapeHtml(line.dailyKg)}</td>
|
||
<td>${escapeHtml(line.costRub)}</td>
|
||
<td>
|
||
<select class="form-select form-select-sm lab-agrostar-component lab-agrostar-component--readonly" data-feed-idx="${idx}" disabled>
|
||
${buildComponentOptions(line, defaultId)}
|
||
</select>
|
||
</td>
|
||
</tr>`;
|
||
})
|
||
.join("");
|
||
|
||
showEl(wrap);
|
||
setActionButtons();
|
||
}
|
||
|
||
function renderRationIndicatorsTable() {
|
||
const wrap = $("labAgrostarTableWrap");
|
||
const body = $("labAgrostarTableBody");
|
||
const head = $("labImportTableHead");
|
||
const title = $("labImportTableTitle");
|
||
const rows = parseResult?.indicators || [];
|
||
if (!wrap || !body || !rows.length) return;
|
||
|
||
if (title) title.textContent = "Показатели рациона";
|
||
if (head) {
|
||
head.innerHTML = `<tr>
|
||
<th>Показатель</th><th>Норма</th><th>Текущий</th><th>Δ</th>
|
||
</tr>`;
|
||
}
|
||
|
||
const meta = parseResult.meta || {};
|
||
renderMetaLine(
|
||
` · ${rows.length} показат.${meta.group ? " · " + escapeHtml(meta.group) : ""}${meta.calcDate ? " · " + escapeHtml(meta.calcDate) : ""}`,
|
||
);
|
||
|
||
body.innerHTML = rows
|
||
.map((row) => {
|
||
const delta =
|
||
row.norm != null && row.current != null ? (row.current - row.norm).toFixed(2) : "—";
|
||
return `<tr>
|
||
<td>${escapeHtml(row.name)}</td>
|
||
<td>${row.norm != null ? escapeHtml(row.norm) : "—"}</td>
|
||
<td>${row.current != null ? escapeHtml(row.current) : "—"}</td>
|
||
<td>${escapeHtml(delta)}</td>
|
||
</tr>`;
|
||
})
|
||
.join("");
|
||
|
||
showEl(wrap);
|
||
setActionButtons();
|
||
}
|
||
|
||
function renderAfterParse() {
|
||
hideSniffPreview();
|
||
hideEl($("labAgrostarTableWrap"));
|
||
const kind = importKind();
|
||
if (kind === "lab_samples") renderLabSamplesTable();
|
||
else if (kind === "ration_composition") renderRationCompositionTable();
|
||
else if (kind === "ration_indicators") renderRationIndicatorsTable();
|
||
}
|
||
|
||
function renderSniffPreview() {
|
||
const panel = $("labAgrostarSniff");
|
||
const body = $("labAgrostarSniffBody");
|
||
if (!panel || !body || !parseResult) {
|
||
setResult("Нечего нюхать — сначала кинь файл", true);
|
||
return;
|
||
}
|
||
|
||
const kind = importKind();
|
||
if (kind === "ration_composition") {
|
||
renderSniffRationComposition(panel, body);
|
||
return;
|
||
}
|
||
if (kind === "ration_indicators") {
|
||
renderSniffRationIndicators(panel, body);
|
||
return;
|
||
}
|
||
renderSniffLabSamples(panel, body);
|
||
}
|
||
|
||
function renderSniffRationComposition(panel, body) {
|
||
const lines = parseResult.feedLines || [];
|
||
const meta = parseResult.meta || {};
|
||
const metaRows = Object.entries(meta)
|
||
.map(([k, v]) => `<tr><th>${escapeHtml(k)}</th><td>${escapeHtml(v)}</td></tr>`)
|
||
.join("");
|
||
body.innerHTML = `<article class="lab-agrostar-sniff-card">
|
||
<header class="lab-agrostar-sniff-card__head">
|
||
<h3 class="h6 mb-0">ПЛИНОР — состав рациона</h3>
|
||
<span class="badge text-bg-secondary">${lines.length} корм(ов)</span>
|
||
</header>
|
||
<p class="small text-muted">Превью · запись рациона в WESP — отдельным шагом</p>
|
||
<table class="table table-sm lab-agrostar-sniff-nutrients mb-2">
|
||
<thead><tr><th>Корм</th><th class="text-end">кг</th><th class="text-end">руб.</th></tr></thead>
|
||
<tbody>${lines
|
||
.map(
|
||
(l) =>
|
||
`<tr><td>${escapeHtml(l.feedName)}</td><td class="text-end">${escapeHtml(l.dailyKg)}</td><td class="text-end">${escapeHtml(l.costRub)}</td></tr>`,
|
||
)
|
||
.join("")}</tbody>
|
||
</table>
|
||
${metaRows ? `<table class="table table-sm lab-agrostar-sniff-meta mb-0"><tbody>${metaRows}</tbody></table>` : ""}
|
||
</article>`;
|
||
showEl(panel);
|
||
setResult(`Понюхали · ${parseResult.sourceLabel || "ПЛИНОР"} · ${lines.length} строк`, false);
|
||
}
|
||
|
||
function renderSniffRationIndicators(panel, body) {
|
||
const rows = parseResult.indicators || [];
|
||
body.innerHTML = `<article class="lab-agrostar-sniff-card">
|
||
<header class="lab-agrostar-sniff-card__head">
|
||
<h3 class="h6 mb-0">ПЛИНОР — зоопоказатели</h3>
|
||
<span class="badge text-bg-secondary">${rows.length} показат.</span>
|
||
</header>
|
||
<table class="table table-sm lab-agrostar-sniff-nutrients mb-0">
|
||
<thead><tr><th>Показатель</th><th class="text-end">Норма</th><th class="text-end">Текущий</th></tr></thead>
|
||
<tbody>${rows
|
||
.map(
|
||
(r) =>
|
||
`<tr><td>${escapeHtml(r.name)}</td><td class="text-end">${escapeHtml(r.norm)}</td><td class="text-end fw-semibold">${escapeHtml(r.current)}</td></tr>`,
|
||
)
|
||
.join("")}</tbody>
|
||
</table>
|
||
</article>`;
|
||
showEl(panel);
|
||
setResult(`Понюхали · ${parseResult.sourceLabel || "ПЛИНОР"} · ${rows.length} показат.`, false);
|
||
}
|
||
|
||
function renderSniffLabSamples(panel, body) {
|
||
if (!parseResult?.samples?.length) {
|
||
setResult("Нечего нюхать — нет проб", true);
|
||
return;
|
||
}
|
||
|
||
const labName = parseResult.labName || parseResult.sourceLabel || "AgroStar";
|
||
const cards = parseResult.samples.map((sample) => {
|
||
const preview = sample.preview;
|
||
if (!preview) return "";
|
||
|
||
const selectedName = getSelectedComponentLabel(sample.sampleNo);
|
||
const targetLine = selectedName
|
||
? `<p class="lab-agrostar-sniff__target mb-2"><span class="text-muted">Куда запишем:</span> <strong>${escapeHtml(selectedName)}</strong></p>`
|
||
: `<p class="lab-agrostar-sniff__target lab-agrostar-sniff__target--warn mb-2">Реагент не выбран — «+ добавить новый реагент»</p>`;
|
||
|
||
const metaRows = (preview.meta || [])
|
||
.map((row) => `<tr><th scope="row">${escapeHtml(row.label)}</th><td>${escapeHtml(row.value)}</td></tr>`)
|
||
.join("");
|
||
const nutrientRows = (preview.willWrite || [])
|
||
.map((row) => {
|
||
const sourceCell =
|
||
row.sourceValue != null
|
||
? `<td class="text-end">${escapeHtml(row.sourceValue)} <span class="text-muted small">${escapeHtml(row.sourceUnit || "")}</span></td>`
|
||
: `<td class="text-end text-muted">—</td>`;
|
||
return `<tr><td>${escapeHtml(row.label)}</td>${sourceCell}<td class="text-end fw-semibold">${escapeHtml(row.value)}</td><td class="text-muted small">${escapeHtml(row.unit)}</td></tr>`;
|
||
})
|
||
.join("");
|
||
const notes = (preview.notes || []).map((note) => `<li>${escapeHtml(note)}</li>`).join("");
|
||
const notesBlock = notes
|
||
? `<div class="lab-agrostar-sniff__notes mt-3"><div class="small text-muted mb-1">Замечания</div><ul class="small mb-0">${notes}</ul></div>`
|
||
: "";
|
||
const storage = preview.storage || {};
|
||
const skipNote = formatUnsupportedNotice(storage);
|
||
const storageBlock = skipNote
|
||
? `<p class="small lab-agrostar-sniff__skip mt-3 mb-0">${escapeHtml(skipNote)}</p>`
|
||
: `<p class="small text-muted mt-3 mb-0">В каталог: СВ + ${storage.nutrientCount || 0} показат.</p>`;
|
||
|
||
return `<article class="lab-agrostar-sniff-card">
|
||
<header class="lab-agrostar-sniff-card__head">
|
||
<h3 class="h6 mb-0">${escapeHtml(preview.title || sample.label)}</h3>
|
||
<span class="badge text-bg-secondary">${escapeHtml(preview.feedTypeRu || "—")}</span>
|
||
</header>
|
||
${targetLine}
|
||
<div class="row g-3">
|
||
<div class="col-md-6"><div class="small text-muted mb-1">Из файла</div>
|
||
<table class="table table-sm lab-agrostar-sniff-meta mb-0"><tbody>${metaRows}</tbody></table>
|
||
</div>
|
||
<div class="col-md-6"><div class="small text-muted mb-1">WESP · ${preview.recognizedCount || 0} показат. · % из документа → г/кг СВ (×10)</div>
|
||
<table class="table table-sm lab-agrostar-sniff-nutrients mb-0">
|
||
<thead><tr><th>Показатель</th><th class="text-end">Документ</th><th class="text-end">WESP</th><th></th></tr></thead>
|
||
<tbody>${nutrientRows}</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
${notesBlock}${storageBlock}
|
||
</article>`;
|
||
});
|
||
|
||
body.innerHTML = cards.join("");
|
||
showEl(panel);
|
||
setResult(`Понюхали · ${labName} · ${parseResult.samples.length} образц(ов)`, false);
|
||
}
|
||
|
||
function hideSniffPreview() {
|
||
const panel = $("labAgrostarSniff");
|
||
const body = $("labAgrostarSniffBody");
|
||
hideEl(panel);
|
||
if (body) body.innerHTML = "";
|
||
}
|
||
|
||
function collectAssignments() {
|
||
if (!parseResult?.samples?.length) return [];
|
||
const byNo = new Map(parseResult.samples.map((s) => [s.sampleNo, s]));
|
||
const assignments = [];
|
||
document.querySelectorAll(".lab-agrostar-component[data-sample-no]").forEach((sel) => {
|
||
const sampleNo = sel.getAttribute("data-sample-no") || "";
|
||
const sample = byNo.get(sampleNo);
|
||
if (!sample) return;
|
||
const componentId = sel.value && sel.value !== CREATE_NEW ? sel.value : null;
|
||
assignments.push({
|
||
sampleNo,
|
||
componentId,
|
||
dryMatterPct: sample.dryMatterPct,
|
||
nutrients: sample.nutrients || {},
|
||
warnings: sample.warnings || [],
|
||
});
|
||
});
|
||
return assignments;
|
||
}
|
||
|
||
function fileAllowed(name) {
|
||
const lower = (name || "").toLowerCase();
|
||
return ACCEPT_EXT.some((ext) => lower.endsWith(ext));
|
||
}
|
||
|
||
async function uploadFile(file) {
|
||
if (!file) return;
|
||
if (!fileAllowed(file.name)) {
|
||
setStatus("Формат не тот — xml, pdf или xlsx", true);
|
||
return;
|
||
}
|
||
selectedFile = file;
|
||
const fileNameEl = $("labAgrostarFileName");
|
||
if (fileNameEl) {
|
||
showEl(fileNameEl);
|
||
fileNameEl.textContent = file.name;
|
||
}
|
||
setStatus("Разбираем поставку…");
|
||
setResult("");
|
||
hideSniffPreview();
|
||
|
||
const fd = new FormData();
|
||
fd.append("file", file);
|
||
try {
|
||
parseResult = await api("/api/lab/import/parse", { method: "POST", body: fd });
|
||
await loadComponents();
|
||
renderAfterParse();
|
||
const kind = importKind();
|
||
if (kind === "lab_samples") {
|
||
setStatus(`На столе: ${parseResult.sampleCount || 0} образц(ов) · ${parseResult.sourceLabel || ""}`);
|
||
} else if (kind === "ration_composition") {
|
||
setStatus(`Рацион: ${parseResult.feedCount || 0} корм(ов) · ${parseResult.sourceLabel || ""}`);
|
||
} else {
|
||
setStatus(`Показатели: ${parseResult.indicatorCount || 0} · ${parseResult.sourceLabel || ""}`);
|
||
}
|
||
} catch (err) {
|
||
parseResult = null;
|
||
hideEl($("labAgrostarTableWrap"));
|
||
hideEl($("labAgrostarMeta"));
|
||
setStatus(err.message || "Файл не разобрался", true);
|
||
}
|
||
}
|
||
|
||
async function runApply() {
|
||
if (!isLabSamples()) {
|
||
setResult("Запись в каталог — только для лабораторных проб AgroStar", true);
|
||
return;
|
||
}
|
||
const assignments = collectAssignments();
|
||
if (!assignments.length) {
|
||
setResult("Нечего готовить — сначала кинь файл", true);
|
||
return;
|
||
}
|
||
const missing = assignments.filter((a) => !a.componentId);
|
||
if (missing.length) {
|
||
setResult(`Укажи реагент для ${missing.length} образц(ов) — Jesse бы не забыл`, true);
|
||
return;
|
||
}
|
||
setResult("Пишем в каталог…");
|
||
try {
|
||
const data = await api("/api/lab/import/agrostar-xml/apply", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ assignments, dryRun: false }),
|
||
});
|
||
const lines = (data.results || []).map(
|
||
(r) => `${r.sampleNo}: ${r.skipped ? "мимо" : r.componentName || "—"} (${r.nutrientCount} показат.)`,
|
||
);
|
||
setResult(
|
||
`Запомнили · в деле ${data.applied}, мимо ${data.skipped}` +
|
||
(lines.length ? "\n" + lines.join("\n") : ""),
|
||
false,
|
||
);
|
||
notifyCatalogChanged();
|
||
} catch (err) {
|
||
setResult(err.message || "Наука не сработала — импорт сгорел", true);
|
||
}
|
||
}
|
||
|
||
function clearAll() {
|
||
parseResult = null;
|
||
selectedFile = null;
|
||
const input = $("labAgrostarFile");
|
||
if (input) input.value = "";
|
||
const fileNameEl = $("labAgrostarFileName");
|
||
if (fileNameEl) {
|
||
hideEl(fileNameEl);
|
||
fileNameEl.textContent = "";
|
||
}
|
||
hideEl($("labAgrostarTableWrap"));
|
||
hideEl($("labAgrostarMeta"));
|
||
hideSniffPreview();
|
||
$("labAgrostarPreview").disabled = true;
|
||
$("labAgrostarApply").disabled = true;
|
||
$("labAgrostarClear").disabled = true;
|
||
setStatus("");
|
||
setResult("");
|
||
}
|
||
|
||
function bindDropZone() {
|
||
const drop = $("labAgrostarDrop");
|
||
const input = $("labAgrostarFile");
|
||
if (!drop || !input) return;
|
||
|
||
drop.addEventListener("click", () => input.click());
|
||
drop.addEventListener("keydown", (e) => {
|
||
if (e.key === "Enter" || e.key === " ") {
|
||
e.preventDefault();
|
||
input.click();
|
||
}
|
||
});
|
||
input.addEventListener("change", () => {
|
||
const file = input.files?.[0];
|
||
if (file) uploadFile(file);
|
||
});
|
||
|
||
["dragenter", "dragover"].forEach((ev) => {
|
||
drop.addEventListener(ev, (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
drop.classList.add("lab-agrostar-drop--active");
|
||
});
|
||
});
|
||
["dragleave", "drop"].forEach((ev) => {
|
||
drop.addEventListener(ev, (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
drop.classList.remove("lab-agrostar-drop--active");
|
||
});
|
||
});
|
||
drop.addEventListener("drop", (e) => {
|
||
const file = e.dataTransfer?.files?.[0];
|
||
if (file) uploadFile(file);
|
||
});
|
||
}
|
||
|
||
function init() {
|
||
if (!$("labAgrostarDrop")) return;
|
||
bindDropZone();
|
||
$("labAgrostarTableWrap")?.addEventListener("change", onComponentSelectChange);
|
||
$("labAgrostarPreview")?.addEventListener("click", renderSniffPreview);
|
||
$("labAgrostarApply")?.addEventListener("click", runApply);
|
||
$("labAgrostarClear")?.addEventListener("click", clearAll);
|
||
loadComponents().catch(() => {});
|
||
}
|
||
|
||
if (document.readyState === "loading") {
|
||
document.addEventListener("DOMContentLoaded", init);
|
||
} else {
|
||
init();
|
||
}
|
||
})(window);
|