(function (global) { "use strict"; function escapeHtml(s) { return String(s ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function createLabMasterPanel() { let root = null; let recipeId = null; let state = null; let components = []; let profiles = []; async function api(path, opts) { const resp = await fetch(path, opts); const data = await resp.json().catch(() => ({})); if (!resp.ok) throw new Error(data.message || "Ошибка API"); return data; } async function ensureCatalogs(rationType) { const [compResp, profData] = await Promise.all([ components.length ? Promise.resolve(components) : fetch("/api/components").then((r) => r.json()), api(`/api/lab/animal-profiles${rationType ? `?ration_type=${encodeURIComponent(rationType)}` : ""}`), ]); if (Array.isArray(compResp)) components = compResp; profiles = profData.profiles || []; } async function loadRecipes() { const data = await api("/api/lab/recipes"); return data.recipes || []; } async function loadRation(id) { return api(`/api/lab/rations/${encodeURIComponent(id)}`); } function componentOptionsHtml(selectedId) { const opts = ['']; components.forEach((c) => { const sel = String(c.id) === String(selectedId) ? " selected" : ""; opts.push(``); }); return opts.join(""); } function profileOptionsHtml(selectedId) { const opts = ['']; profiles.forEach((p) => { const sel = String(p.id) === String(selectedId) ? " selected" : ""; opts.push( `` ); }); return opts.join(""); } function render() { if (!root) return; const indicators = (state?.rationResults?.indicators || []).slice(0, 8); const lines = state?.lines || []; root.innerHTML = `
` + `
` + `` + `` + `` + `` + `` + `` + `` + `` + `
` + `
Зоотехнический мастер · ${escapeHtml(state?.recipeName || "")}
` + `` + `` + (lines.length ? lines .map( (l, i) => `` + `` + `` + `` + `` + `` + `` ) .join("") : ``) + `
Компонент WESPкг/деньВ рац.Комб.
Нет строк — добавьте или создайте из рецепта
` + `
` + indicators .map( (ind) => `${escapeHtml(ind.label)}: ${escapeHtml(ind.content)} ${escapeHtml(ind.unit)}` ) .join("") + `
`; const sel = root.querySelector("[data-lab-recipe-select]"); if (sel && sel.options.length === 0) { loadRecipes().then((recipes) => { sel.innerHTML = '' + recipes .map( (r) => `` ) .join(""); }); } } async function reload() { if (!recipeId) return; try { state = await loadRation(recipeId); await ensureCatalogs(state.rationType); if (!state.exists) { const ok = await global.WespDialog?.confirm?.( "Создать мастер из текущего рецепта?", { title: "Мастер рациона" } ); if (ok) { await api(`/api/lab/rations/${encodeURIComponent(recipeId)}/seed-from-execution`, { method: "POST", }); state = await loadRation(recipeId); await ensureCatalogs(state.rationType); } else { await api(`/api/lab/rations/${encodeURIComponent(recipeId)}/ensure-empty`, { method: "POST", }); state = await loadRation(recipeId); await ensureCatalogs(state.rationType); } } render(); } catch (e) { global.WespZootechNotify?.createAdapter?.()?.error?.(e.message, { fallback: "Не удалось выполнить операцию" }); } } function collectLines() { if (!root || !state) return []; return (state.lines || []).map((line, i) => { const row = root.querySelector(`tr[data-line-idx="${i}"]`); if (!row) return line; const componentId = row.querySelector('[data-field="componentId"]')?.value || null; const comp = components.find((c) => String(c.id) === String(componentId)); return { ...line, componentId, ingredientName: comp?.name || line.ingredientName, dailyKg: parseFloat(row.querySelector('[data-field="dailyKg"]')?.value) || null, inRation: row.querySelector('[data-field="inRation"]')?.checked, inCompound: row.querySelector('[data-field="inCompound"]')?.checked, }; }); } function collectPayload() { const rationType = root.querySelector("[data-lab-ration-type]")?.value || state?.rationType; return { lines: collectLines(), rationType, animalProfileId: root.querySelector("[data-lab-profile-select]")?.value || null, }; } return { mount(el) { root = el; root.addEventListener("click", async (ev) => { const btn = ev.target.closest("[data-action]"); if (!btn || !recipeId) return; const action = btn.getAttribute("data-action"); try { if (action === "lab-add-line") { state.lines = [...(state?.lines || []), { componentId: "", ingredientName: "", dailyKg: 0, inRation: true, inCompound: false, }]; render(); return; } if (action === "lab-remove-line") { const idx = parseInt(btn.getAttribute("data-line-idx"), 10); if (!Number.isNaN(idx)) { state.lines = (state.lines || []).filter((_, i) => i !== idx); render(); } return; } if (action === "lab-recalculate") { await api(`/api/lab/rations/${encodeURIComponent(recipeId)}/recalculate`, { method: "POST", }); await reload(); } if (action === "lab-save") { await api(`/api/lab/rations/${encodeURIComponent(recipeId)}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(collectPayload()), }); await reload(); global.WespZootechNotify?.createAdapter?.()?.success?.("Мастер сохранён"); } if (action === "lab-print-ration" || action === "lab-print-compound") { const mode = action === "lab-print-compound" ? "compound" : "ration"; global.open?.( `/api/lab/rations/${encodeURIComponent(recipeId)}/print?mode=${mode}`, "_blank", "noopener" ); } } catch (e) { global.WespZootechNotify?.createAdapter?.()?.error?.(e.message, { fallback: "Не удалось выполнить операцию" }); } }); root.addEventListener("change", async (ev) => { if (ev.target.matches("[data-lab-recipe-select]")) { recipeId = ev.target.value; reload(); return; } if (ev.target.matches("[data-lab-ration-type]")) { const rt = ev.target.value; state = { ...state, rationType: rt }; profiles = []; await ensureCatalogs(rt); render(); return; } if (ev.target.matches("[data-lab-profile-select]")) { const profile = profiles.find((p) => String(p.id) === String(ev.target.value)); if (profile?.rationType) { state = { ...state, rationType: profile.rationType, animalProfileId: profile.id }; const typeSel = root.querySelector("[data-lab-ration-type]"); if (typeSel) typeSel.value = profile.rationType; } } }); }, destroy() { root = null; state = null; }, reload, setRecipeId(id) { recipeId = id; reload(); }, }; } global.WespLabMasterPanel = { createLabMasterPanel }; })(window);