(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, """); } 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 = [ '', ``, ]; 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( ``, ); } for (const c of components) { if (!c.id || seen.has(c.id)) continue; const sel = c.id === selectedId ? " selected" : ""; opts.push(``); } 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 `${escapeHtml(label)}`; }) .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 = `${escapeHtml(label)}${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 = ` ОбразецСВ, %РеагентКосяки `; } renderMetaLine(` · образцов: ${samples.length}`); body.innerHTML = samples .map((sample) => { const sid = escapeHtml(sample.sampleNo); const defaultId = sample.suggestedComponents?.[0]?.componentId || ""; return `
${escapeHtml(sample.label || sample.desc1 || sample.sampleNo)}
${sid} · ${escapeHtml(sample.datePrinted || "")}
${sample.dryMatterPct != null ? escapeHtml(sample.dryMatterPct) : "—"} ${formatWarnings(sample.warnings)} `; }) .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 = ` Кормкг/сутруб.Реагент WESP `; } 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 ` ${escapeHtml(line.feedName)} ${escapeHtml(line.dailyKg)} ${escapeHtml(line.costRub)} `; }) .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 = ` ПоказательНормаТекущийΔ `; } 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 ` ${escapeHtml(row.name)} ${row.norm != null ? escapeHtml(row.norm) : "—"} ${row.current != null ? escapeHtml(row.current) : "—"} ${escapeHtml(delta)} `; }) .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]) => `${escapeHtml(k)}${escapeHtml(v)}`) .join(""); body.innerHTML = `

ПЛИНОР — состав рациона

${lines.length} корм(ов)

Превью · запись рациона в WESP — отдельным шагом

${lines .map( (l) => ``, ) .join("")}
Кормкгруб.
${escapeHtml(l.feedName)}${escapeHtml(l.dailyKg)}${escapeHtml(l.costRub)}
${metaRows ? `${metaRows}
` : ""}
`; showEl(panel); setResult(`Понюхали · ${parseResult.sourceLabel || "ПЛИНОР"} · ${lines.length} строк`, false); } function renderSniffRationIndicators(panel, body) { const rows = parseResult.indicators || []; body.innerHTML = `

ПЛИНОР — зоопоказатели

${rows.length} показат.
${rows .map( (r) => ``, ) .join("")}
ПоказательНормаТекущий
${escapeHtml(r.name)}${escapeHtml(r.norm)}${escapeHtml(r.current)}
`; 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 ? `

Куда запишем: ${escapeHtml(selectedName)}

` : `

Реагент не выбран — «+ добавить новый реагент»

`; const metaRows = (preview.meta || []) .map((row) => `${escapeHtml(row.label)}${escapeHtml(row.value)}`) .join(""); const nutrientRows = (preview.willWrite || []) .map((row) => { const sourceCell = row.sourceValue != null ? `${escapeHtml(row.sourceValue)} ${escapeHtml(row.sourceUnit || "")}` : `—`; return `${escapeHtml(row.label)}${sourceCell}${escapeHtml(row.value)}${escapeHtml(row.unit)}`; }) .join(""); const notes = (preview.notes || []).map((note) => `
  • ${escapeHtml(note)}
  • `).join(""); const notesBlock = notes ? `
    Замечания
    ` : ""; const storage = preview.storage || {}; const skipNote = formatUnsupportedNotice(storage); const storageBlock = skipNote ? `

    ${escapeHtml(skipNote)}

    ` : `

    В каталог: СВ + ${storage.nutrientCount || 0} показат.

    `; return `

    ${escapeHtml(preview.title || sample.label)}

    ${escapeHtml(preview.feedTypeRu || "—")}
    ${targetLine}
    Из файла
    ${metaRows}
    WESP · ${preview.recognizedCount || 0} показат. · % из документа → г/кг СВ (×10)
    ${nutrientRows}
    ПоказательДокументWESP
    ${notesBlock}${storageBlock}
    `; }); 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);