/* ============================================================
Акт №4 — приёмка товара (экспедитор / сборщик)
#/assembly/:id/act4
============================================================ */
const Act4Screen = (function () {
"use strict";
function escHtml(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """);
}
function el(html) {
const t = document.createElement("template");
t.innerHTML = html.trim();
return t.content.firstChild;
}
async function _api(path, body = {}) {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 15000);
try {
const res = await fetch(`${BACKEND_URL}/api/${path}`, {
method: "POST", signal: ctrl.signal,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
initData: typeof Platform !== "undefined" ? Platform.initData : (window.tg?.initData || ""),
initDataUnsafe: typeof Platform !== "undefined" ? Platform.initDataUnsafe : null,
...body,
}),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (e) {
if (e.name === "AbortError") throw new Error("Сервер не отвечает");
throw e;
} finally { clearTimeout(t); }
}
// Состояние акта
let _state = {
act_num: "", act_date: "", supplier: "", notes: "",
items: [], // [{id, name, qty, condition, note}]
signed_by_name: "", signed_by_phone: "", signed_via: "",
};
let _data = {}; // данные с сервера
let _container = null;
let _assemblyId = "";
function _itemId() {
return "i" + Math.random().toString(36).slice(2, 8);
}
/* ── Главный mount ──────────────────────────────────────────── */
async function mount(container, assemblyId) {
_container = container;
_assemblyId = assemblyId;
container.innerHTML = "";
document.body.classList.remove("has-bottom-nav");
document.getElementById("bottom-nav")?.remove();
const h = el(`
`);
h.querySelector(".podbor-back").addEventListener("click", () => { haptic && haptic("impact"); history.back(); });
container.appendChild(h);
const screen = el(`
`);
screen.innerHTML = ``;
container.appendChild(screen);
try {
const d = await _api("act4_preview", { assembly_id: assemblyId });
if (d.error) { screen.innerHTML = `${escHtml(d.error)}
`; return; }
_data = d;
_state = {
act_num: d.act_num || `${assemblyId}-4`,
act_date: d.act_date || new Date().toISOString().slice(0, 10),
supplier: d.supplier || "",
notes: d.notes || "",
items: (d.items || []).map(it => ({ ...it, id: it.id || _itemId() })),
signed_by_name: d.signed_by_name || "",
signed_by_phone: d.signed_by_phone || "",
signed_via: d.signed_via || "",
};
_render(screen, d.is_signed);
} catch (e) {
screen.innerHTML = `Ошибка: ${escHtml(e.message)}
`;
}
}
/* ── Рендер ─────────────────────────────────────────────────── */
function _render(screen, isSigned) {
screen.innerHTML = "";
// Баннер если подписан
if (isSigned) {
screen.appendChild(el(`
✅ Акт подписан
${escHtml(_state.signed_by_name)}
${_state.signed_at ? " · " + escHtml(new Date(_state.signed_at).toLocaleDateString("ru-RU")) : ""}
`));
}
// Данные клиента
screen.appendChild(el(`
Клиент
${escHtml(_data.client_name || "—")}
${_data.address ? `
${escHtml(_data.address)}
` : ""}
`));
// Реквизиты акта
const reqs = el(`
`);
screen.appendChild(reqs);
if (!isSigned) {
reqs.querySelector("#a4-num").addEventListener("input", e => { _state.act_num = e.target.value; });
reqs.querySelector("#a4-date").addEventListener("change", e => { _state.act_date = e.target.value; });
reqs.querySelector("#a4-supplier").addEventListener("input", e => { _state.supplier = e.target.value; });
}
// === Список позиций ===
const itemsHead = el(`
Позиции
${!isSigned ? `
` : ""}
`);
screen.appendChild(itemsHead);
const itemsList = el(``);
screen.appendChild(itemsList);
_renderItemsList(itemsList, isSigned);
if (!isSigned) {
itemsHead.querySelector("#a4-add-item")?.addEventListener("click", () => {
haptic && haptic("impact");
_state.items.push({ id: _itemId(), name: "", qty: 1, condition: "ok", note: "" });
_renderItemsList(itemsList, false);
});
}
// Итог
const totalEl = el(``);
screen.appendChild(totalEl);
_renderTotal(totalEl);
// Примечание
const noteWrap = el(`
`);
screen.appendChild(noteWrap);
if (!isSigned) {
noteWrap.querySelector("#a4-notes").addEventListener("input", e => { _state.notes = e.target.value; });
}
// Блок подписи
if (!isSigned) {
const signWrap = el(`
`);
screen.appendChild(signWrap);
signWrap.querySelector("#a4-sign-name").addEventListener("input", e => { _state.signed_by_name = e.target.value; });
signWrap.querySelector("#a4-sign-phone").addEventListener("input", e => { _state.signed_by_phone = e.target.value; });
// Кнопки
const btns = el(`
`);
screen.appendChild(btns);
const statusEl = el(``);
screen.appendChild(statusEl);
btns.querySelector("#a4-save-btn").addEventListener("click", () => _doSave(false, statusEl));
btns.querySelector("#a4-sign-btn").addEventListener("click", () => _doSave(true, statusEl));
}
}
/* ── Список позиций ─────────────────────────────────────────── */
function _renderItemsList(container, isSigned) {
container.innerHTML = "";
if (!_state.items.length) {
container.appendChild(el(`
${isSigned ? "Позиции не добавлены" : "Нажмите «+ Добавить» чтобы внести позиции"}
`));
return;
}
_state.items.forEach((item, idx) => {
const row = el(`
${!isSigned ? `` : ""}
Состояние
${item.condition === "damaged" && !isSigned ? `
` : (item.note && isSigned ? `
${escHtml(item.note)}
` : "")}
`);
if (!isSigned) {
row.querySelector(".it-name").addEventListener("input", e => {
_state.items[idx].name = e.target.value;
});
row.querySelector(".it-qty").addEventListener("input", e => {
_state.items[idx].qty = parseInt(e.target.value) || 1;
_renderTotal(document.getElementById("a4-total"));
});
row.querySelector(".it-del").addEventListener("click", () => {
haptic && haptic("impact");
_state.items.splice(idx, 1);
_renderItemsList(container, false);
_renderTotal(document.getElementById("a4-total"));
});
row.querySelectorAll(".cond-btn").forEach(btn => {
btn.addEventListener("click", () => {
haptic && haptic("selection");
_state.items[idx].condition = btn.dataset.cond;
_renderItemsList(container, false);
_renderTotal(document.getElementById("a4-total"));
});
});
row.querySelector(".it-note")?.addEventListener("input", e => {
_state.items[idx].note = e.target.value;
});
}
container.appendChild(row);
});
}
function _renderTotal(container) {
if (!container) return;
const total = _state.items.reduce((s, it) => s + (parseInt(it.qty) || 1), 0);
const damaged = _state.items.filter(it => it.condition === "damaged")
.reduce((s, it) => s + (parseInt(it.qty) || 1), 0);
container.innerHTML = damaged > 0
? `
Итого: ${total} позиций · ⚠️ Повреждений: ${damaged}
`
: `
Итого: ${total} позиций · ✅ Без повреждений
`;
}
/* ── Сохранение / подпись ───────────────────────────────────── */
async function _doSave(withSign, statusEl) {
haptic && haptic("impact");
if (withSign && !_state.signed_by_name.trim()) {
if (statusEl) { statusEl.style.color = "#E74C3C"; statusEl.textContent = "Укажите ФИО принявшего"; }
return;
}
if (statusEl) { statusEl.style.color = "var(--muted)"; statusEl.textContent = "Сохраняем…"; }
const payload = {
assembly_id: _assemblyId,
act_num: _state.act_num,
act_date: _state.act_date,
supplier: _state.supplier,
items: _state.items,
notes: _state.notes,
};
if (withSign) {
payload.signed_by_name = _state.signed_by_name;
payload.signed_by_phone = _state.signed_by_phone;
payload.signed_via = "manual";
}
try {
const res = await _api("act4_save", payload);
if (res.error) {
if (statusEl) { statusEl.style.color = "#E74C3C"; statusEl.textContent = "Ошибка: " + res.error; }
return;
}
if (withSign) {
// Перезагружаем экран — покажет баннер «Подписан»
mount(_container, _assemblyId);
} else {
if (statusEl) { statusEl.style.color = "#27AE60"; statusEl.textContent = "✅ Сохранено"; }
setTimeout(() => { if (statusEl) statusEl.textContent = ""; }, 3000);
}
} catch (e) {
if (statusEl) { statusEl.style.color = "#E74C3C"; statusEl.textContent = "Ошибка: " + e.message; }
}
}
return { mount };
})();