// orders-data.jsx — Кадрові накази (по особовому складу). Наскрізна нумерація НК-0N-2026. // 5 типів: прийняття, звільнення, відпустка, преміювання/доплата, переведення/зміна окладу. // Друкований бланк — у стилі ТОВ (логотип + реквізити + підпис директора + М.П. + ознайомлення). // ——— Відмінювання ПІБ (родовий/давальний; для тіла наказу) ——— // Прагматичний рушій: словник відомих імен + правила для прізвищ. Текст наказу // редагований, тож бухгалтер за потреби виправить будь-яку форму вручну. const FIRST_DECL = { "Олена": { gen: "Олени", dat: "Олені", acc: "Олену", gender: "f" }, "Вікторія": { gen: "Вікторії",dat: "Вікторії", acc: "Вікторію",gender: "f" }, "Андрій": { gen: "Андрія", dat: "Андрієві", acc: "Андрія", gender: "m" }, "Тарас": { gen: "Тараса", dat: "Тарасу", acc: "Тараса", gender: "m" }, "Дмитро": { gen: "Дмитра", dat: "Дмитру", acc: "Дмитра", gender: "m" }, }; function declFirst(first, c) { const d = FIRST_DECL[first]; if (d) return { val: d[c] || first, gender: d.gender }; // правило-фолбек const g = /[аяії]$/.test(first) ? "f" : "m"; if (g === "f") { if (/я$/.test(first)) return { val: c === "acc" ? first.slice(0, -1) + "ю" : first.slice(0, -1) + "ї", gender: g }; if (/а$/.test(first)) return { val: c === "acc" ? first.slice(0, -1) + "у" : first.slice(0, -1) + "и", gender: g }; return { val: first, gender: g }; } if (/о$/.test(first)) return { val: first.slice(0, -1) + (c === "dat" ? "у" : "а"), gender: g }; if (/й$/.test(first)) return { val: first.slice(0, -1) + (c === "dat" ? "єві" : "я"), gender: g }; return { val: first + (c === "dat" ? "у" : "а"), gender: g }; } function declLast(last, c, gender) { if (/енко$|ко$/.test(last)) return last; // -енко не відмінюється if (gender === "f") return last; // жін. прізвища на приголосний — незмінні if (/ук$|юк$|чук$/.test(last)) return last + (c === "dat" ? "ові" : "а"); if (/ник$/.test(last)) return last + (c === "dat" ? "у" : "а"); if (/ій$/.test(last)) return last.slice(0, -2) + (c === "dat" ? "ієві" : "ія"); return last + (c === "dat" ? "у" : "а"); } // Повертає «Прізвище Ім'я» у відмінку c: nom | gen | dat | acc window.declineFio = function (fullName, c) { if (!fullName) return ""; const parts = fullName.trim().split(/\s+/); const first = parts[0], last = parts.slice(1).join(" ") || ""; if (c === "nom" || !last) return [last, first].filter(Boolean).join(" "); const f = declFirst(first, c); const l = declLast(last, c, f.gender); return `${l} ${f.val}`.trim(); }; window.fioGender = function (fullName) { const first = (fullName || "").trim().split(/\s+/)[0]; return declFirst(first, "nom").gender; }; // ——— Типи кадрових наказів ——— window.ORDER_TYPES = { hire: { label: "Про прийняття на роботу", title: "про прийняття на роботу", icon: "user", verb: "ПРИЙНЯТИ" }, dismiss: { label: "Про звільнення", title: "про звільнення", icon: "minus", verb: "ЗВІЛЬНИТИ" }, vacation: { label: "Про надання відпустки", title: "про надання щорічної відпустки", icon: "sun", verb: "НАДАТИ" }, bonus: { label: "Про преміювання / доплату", title: "про преміювання", icon: "coins", verb: "ПРЕМІЮВАТИ" }, transfer: { label: "Про переведення / зміну окладу", title: "про переведення та зміну окладу", icon: "swap", verb: "ПЕРЕВЕСТИ" }, }; window.ORDER_TYPE_ORDER = ["hire", "dismiss", "vacation", "bonus", "transfer"]; window.ORDER_STATUS = { draft: { label: "Чернетка", tone: "neutral" }, signed: { label: "Підписано", tone: "live" }, }; // Підстави звільнення (КЗпП) window.DISMISS_GROUNDS = [ { id: "art38", label: "за власним бажанням (ст. 38 КЗпП)" }, { id: "art36p1",label: "за угодою сторін (п.1 ст. 36 КЗпП)" }, { id: "art36p2",label: "із закінченням строку договору (п.2 ст. 36 КЗпП)" }, { id: "art40", label: "з ініціативи роботодавця (ст. 40 КЗпП)" }, ]; // ——— Журнал реєстрації наказів ——— const fmtNo = (n) => `НК-${String(n).padStart(2, "0")}-2026`; window.orderNo = fmtNo; const ORDERS_SEED = [ { id: "ord-1", no: fmtNo(1), date: "2026-01-09", type: "transfer", personId: "tm", status: "signed", basis: "Службова записка ГІП від 30.12.2025 № 14", f: { posOld: "Архітектор", posNew: "Архітектор (з суміщенням реєстратора ЄДЕССБ)", salaryNew: 42000, from: "2026-01-09" }, ackDate: "2026-01-09", }, { id: "ord-2", no: fmtNo(2), date: "2026-03-20", type: "bonus", personId: "ap", status: "signed", basis: "Подання директора від 18.03.2026", f: { amount: 12000, reason: "за дострокове завершення робочої документації по об'єкту «ЖК-Поділ»" }, ackDate: "2026-03-20", }, { id: "ord-3", no: fmtNo(3), date: "2026-05-12", type: "vacation", personId: "ok", status: "signed", basis: "Заява від 05.05.2026, графік відпусток на 2026 р.", f: { kind: "щорічну основну", days: 10, from: "2026-06-15", to: "2026-06-26", period: "за робочий рік 2025–2026" }, ackDate: "2026-05-12", }, { id: "ord-4", no: fmtNo(4), date: "2026-05-26", type: "hire", personId: "dl", status: "draft", basis: "Заява про прийняття на роботу, трудовий договір", f: { position: "Інженер-електрик", dept: "Проєктний відділ", salary: 38000, partRate: 1, from: "2026-06-02", probation: 3, contract: "Безстроковий трудовий договір" }, ackDate: null, }, ]; // Робочий стан (localStorage) const ORDERS_KEY = "ukrbud_orders_v1"; function freshOrders() { return { __v: 1, list: JSON.parse(JSON.stringify(ORDERS_SEED)) }; } window.__ordersState = (function () { try { const r = localStorage.getItem(ORDERS_KEY); if (r) { const p = JSON.parse(r); if (p && p.__v === 1) return p; } } catch (e) {} return freshOrders(); })(); window.saveOrders = function () { try { localStorage.setItem(ORDERS_KEY, JSON.stringify(window.__ordersState)); } catch (e) {} window.dispatchEvent(new Event("ukrbud-orders")); }; window.useOrders = function () { const [, force] = React.useState(0); React.useEffect(() => { const h = () => force(x => x + 1); window.addEventListener("ukrbud-orders", h); return () => window.removeEventListener("ukrbud-orders", h); }, []); return window.__ordersState; }; window.nextOrderNo = function () { const nums = window.__ordersState.list.map(o => { const m = (o.no || "").match(/НК-(\d+)-2026/); return m ? parseInt(m[1], 10) : 0; }); return fmtNo((nums.length ? Math.max(...nums) : 0) + 1); }; window.addOrder = function (o) { window.__ordersState.list.push(o); window.saveOrders(); }; window.updateOrder = function (id, patch) { const o = window.__ordersState.list.find(x => x.id === id); if (o) Object.assign(o, patch); window.saveOrders(); }; // Формат дати для друкованого наказу: ДД.ММ.РРРР window.dateDots = function (iso) { if (!iso) return "__.__.____"; const d = new Date(iso + (iso.length <= 10 ? "T00:00:00" : "")); if (isNaN(d)) return iso; const p = (n) => String(n).padStart(2, "0"); return `${p(d.getDate())}.${p(d.getMonth() + 1)}.${d.getFullYear()}`; }; // ——— Генератор тіла наказу (преамбула + пункти НАКАЗУЮ + підстава) ——— window.buildOrderBody = function (order) { const p = window.getTeam(order.personId) || {}; const T = window.ORDER_TYPES[order.type] || {}; const f = order.f || {}; const name = p.name || f.name || ""; const money = (n) => (window.formatMoney ? window.formatMoney(n) : n); const dUA = (iso) => window.dateDots(iso) + " р."; const acc = window.declineFio(name, "acc"); const dat = window.declineFio(name, "dat"); const out = { preamble: "", points: [], basis: order.basis || "" }; if (order.type === "hire") { out.preamble = "На підставі поданої заяви та укладеного трудового договору"; out.points = [ `ПРИЙНЯТИ ${acc} на посаду «${f.position || "—"}»${f.dept ? ` до структурного підрозділу «${f.dept}»` : ""} з ${dUA(f.from)}.`, `Встановити посадовий оклад у розмірі ${money(f.salary || 0)} грн на місяць${f.partRate && f.partRate < 1 ? ` (${f.partRate} ставки)` : ""} згідно зі штатним розписом.`, f.probation ? `Установити випробувальний строк тривалістю ${f.probation} міс.` : `Прийняти без випробувального строку.`, `Бухгалтерії здійснювати нарахування та виплату заробітної плати у встановлені строки.`, ]; } else if (order.type === "dismiss") { const g = (window.DISMISS_GROUNDS.find(x => x.id === f.ground) || {}).label || "за власним бажанням (ст. 38 КЗпП)"; out.preamble = "На підставі поданої заяви про звільнення"; out.points = [ `ЗВІЛЬНИТИ ${acc}, ${(p.positionOfficial || p.role || "").toLowerCase()}, ${dUA(f.date)} ${g}.`, `Бухгалтерії провести остаточний розрахунок у день звільнення${f.compDays ? ` та виплатити компенсацію за ${f.compDays} к. дн. невикористаної відпустки` : ""}.`, `Відділу кадрів видати трудову книжку (за наявності) та копію цього наказу в день звільнення.`, ]; } else if (order.type === "vacation") { out.preamble = "На підставі заяви та графіка відпусток"; out.points = [ `НАДАТИ ${dat}, ${(p.positionOfficial || p.role || "").toLowerCase()}, ${f.kind || "щорічну основну"} відпустку тривалістю ${f.days || 0} календарних днів з ${dUA(f.from)} по ${dUA(f.to)}${f.period ? ` (${f.period})` : ""}.`, `Бухгалтерії нарахувати та виплатити відпускні не пізніше ніж за 3 дні до початку відпустки.`, ]; } else if (order.type === "bonus") { out.preamble = "На підставі подання та з метою заохочення"; out.points = [ `ПРЕМІЮВАТИ ${acc}, ${(p.positionOfficial || p.role || "").toLowerCase()}, ${f.percent ? `у розмірі ${f.percent}% посадового окладу` : `у розмірі ${money(f.amount || 0)} грн`} ${f.reason || ""}.`.trim() + (/[.»)]$/.test((f.reason || "")) ? "" : "."), `Бухгалтерії включити суму премії до нарахування заробітної плати за поточний місяць.`, ]; } else if (order.type === "transfer") { out.preamble = "На підставі заяви / службової записки"; out.points = [ `ПЕРЕВЕСТИ ${acc} з посади «${f.posOld || p.positionOfficial || ""}» на посаду «${f.posNew || ""}» з ${dUA(f.from)}.`, `Встановити посадовий оклад у розмірі ${money(f.salaryNew || 0)} грн на місяць з ${dUA(f.from)}.`, `Бухгалтерії здійснювати нарахування заробітної плати за новою посадою та окладом.`, ]; } return out; }; // Короткий підсумок для рядка реєстру window.orderSummary = function (order) { const f = order.f || {}; const m = (n) => window.formatMoney ? window.formatMoney(n) : n; switch (order.type) { case "hire": return `${f.position || ""} · оклад ${m(f.salary || 0)} ₴ · з ${window.formatDate(f.from)}`; case "dismiss": return `звільнення ${window.formatDate(f.date)}`; case "vacation": return `${f.days || 0} к. дн. · ${window.formatDate(f.from)} – ${window.formatDate(f.to)}`; case "bonus": return f.percent ? `премія ${f.percent}% окладу` : `премія ${m(f.amount || 0)} ₴`; case "transfer": return `→ ${f.posNew || ""} · оклад ${m(f.salaryNew || 0)} ₴`; default: return ""; } };