// data.jsx — реалістичні дані для ERP «Укрбудпроєкт» const TEAM = [ { id: "ok", initials: "ОК", name: "Олена Кравченко", role: "ГІП (адмін)", load: 95, phone: "+380 67 123 45 67", email: "o.kravchenko@ukrbudproiekt.ua", employment: "staff", hireDate: "2018-04-02", birthDate: "1985-09-14", salaryModel: "fixed_bonus", baseSalary: 65000, bonusRate: 0.04, cashSalary: 0, education: "КНУБА, ПЦБ, маг. 2008", certs: ["Сертифікат відповідального виконавця №АА-1247", "ГІП-сертифікат, 2020"], docs: ["passport", "id_card", "tax_id"], vacationBalance: 18, sickBalance: 0, address: "Київ, вул. Лук'янівська 21, кв. 14", iban: "UA293052990000026003123456789", // — кадрові та податкові дані (для довідок про доходи / ДПС / банк) positionOfficial: "Директор, головний інженер проєкту", taxId: "2891405672", idDoc: { kind: "ID-картка", number: "001 245 789", issuedBy: "1234 (ДМС у Шевченківському р-ні м. Києва)", issuedDate: "2019-06-10" }, hireOrder: "Наказ №1-К від 02.04.2018", contractType: "Безстроковий трудовий договір", // — КЕП (кваліфікований електронний підпис) kep: { type: "Печатка ТОВ + КЕП ФОП", provider: "Дія.Підпис", serial: "5E 9A 1C 04", issuedDate: "2025-03-12", validUntil: "2027-03-12" }, qualCerts: [ { kind: "Головний інженер проєкту (ГІП)", number: "ГІП-0571", class: "СС3", scope: "Об'єкти зі значними наслідками (СС3)", issuedDate: "2008-11-20", lastTraining: "2023-04-15", lastEdessbReport: "2025-06-10", scan: { name: "Сертифікат ГІП-0571.pdf", slot: "scan-cert-ok", uploaded: "2023-04-20" } }, ], // — страхування професійної відповідальності (поновлюється щороку) insurance: { policyNumber: "ARX-2025-44871", insurer: "СК «ARX»", sumInsured: 2000000, premium: 14000, startDate: "2025-09-01", endDate: "2026-09-01", scan: { name: "Поліс ARX-2025-44871.pdf", slot: "scan-ins-ok" } }, // — членство у профспілці / СРО (внесок щороку) membership: { org: "Гільдія інженерів-проєктувальників України", memberSince: "2012", feeAmount: 4800, lastPaid: "2026-01-15", paidUntil: "2027-01-15", scan: { name: "Квитанція про сплату 2026.pdf", slot: "scan-mem-ok" } }, }, { id: "ap", initials: "АП", name: "Андрій Петренко", role: "Конструктор", load: 80, phone: "+380 67 234 56 78", email: "a.petrenko@ukrbudproiekt.ua", employment: "staff", hireDate: "2020-07-15", birthDate: "1988-03-22", salaryModel: "fixed_bonus", baseSalary: 45000, bonusRate: 0.025, cashSalary: 20000, education: "КНУБА, ПЦБ, маг. 2011", certs: ["Сертифікат відповідального виконавця №КП-3382"], docs: ["passport", "id_card", "tax_id"], vacationBalance: 12, sickBalance: 0, address: "Київ, пр. Перемоги 75, кв. 88", iban: "UA293052990000026003234567890", positionOfficial: "Інженер-конструктор I категорії", taxId: "3012284561", idDoc: { kind: "ID-картка", number: "002 318 640", issuedBy: "8021 (ДМС у Святошинському р-ні м. Києва)", issuedDate: "2018-11-03" }, hireOrder: "Наказ №18-К від 15.07.2020", contractType: "Безстроковий трудовий договір", kep: { type: "КЕП фахівця (підпис як автор КЖ)", provider: "АЦСК «Україна»", serial: "7B 22 D5 81", issuedDate: "2025-08-15", validUntil: "2026-08-15" }, qualCerts: [ { kind: "Відповідальний виконавець окремих видів робіт", number: "КП-3382", class: "СС1", scope: "Конструктивні рішення", issuedDate: "2016-05-12", lastTraining: "2021-03-20", lastEdessbReport: "2025-09-01", scan: { name: "Сертифікат КП-3382.pdf", slot: "scan-cert-ap", uploaded: "2021-03-25" } }, ], insurance: { policyNumber: "ВУСО-7720-118", insurer: "СК «ВУСО»", sumInsured: 1000000, premium: 7200, startDate: "2025-06-11", endDate: "2026-06-11", scan: { name: "Поліс ВУСО-7720-118.pdf", slot: "scan-ins-ap" } }, membership: { org: "Спілка інженерів-будівельників України", memberSince: "2017", feeAmount: 3600, lastPaid: "2025-04-20", paidUntil: "2026-04-20", scan: { name: "Квитанція про сплату 2025.pdf", slot: "scan-mem-ap" } }, }, { id: "tm", initials: "ТМ", name: "Тарас Мельник", role: "Архітектор", load: 70, phone: "+380 67 345 67 89", email: "t.melnyk@ukrbudproiekt.ua", employment: "staff", hireDate: "2021-02-10", birthDate: "1990-11-08", salaryModel: "fixed_bonus", baseSalary: 42000, bonusRate: 0.02, cashSalary: 15000, edessbRegistrar: { since: "2023-02-01", scope: "Розділи АР · подача та реєстрація об'єктів у ЄДЕССБ", contractType: "Внутрішнє суміщення (наказ №18)", rate: 450, hours: 22 }, education: "КНУБА, Архітектура, маг. 2013", certs: ["Сертифікат архітектора №АР-2891"], docs: ["passport", "id_card", "tax_id"], vacationBalance: 24, sickBalance: 2, address: "Київ, вул. Антоновича 102", iban: "UA293052990000026003345678901", positionOfficial: "Архітектор", taxId: "3105187423", idDoc: { kind: "ID-картка", number: "003 901 226", issuedBy: "8043 (ДМС у Печерському р-ні м. Києва)", issuedDate: "2020-02-18" }, hireOrder: "Наказ №27-К від 10.02.2021", contractType: "Безстроковий трудовий договір", kep: { type: "КЕП фахівця (підпис як автор АР)", provider: "АЦСК «Україна»", serial: "3C 90 4F 17", issuedDate: "2025-02-08", validUntil: "2027-02-08" }, qualCerts: [ { kind: "Архітектор (відповідальний виконавець)", number: "АР-2891", class: "СС2", scope: "Архітектурно-будівельні рішення", issuedDate: "2014-07-01", lastTraining: "2022-09-10", lastEdessbReport: "2024-12-15", scan: { name: "Сертифікат АР-2891.pdf", slot: "scan-cert-tm", uploaded: "2022-09-15" } }, ], insurance: { policyNumber: "УНІКА-AR-3391", insurer: "СК «УНІКА»", sumInsured: 1500000, premium: 9600, startDate: "2025-05-18", endDate: "2026-05-18", scan: { name: "Поліс УНІКА-AR-3391.pdf", slot: "scan-ins-tm" } }, membership: { org: "Національна спілка архітекторів України (НСАУ)", memberSince: "2015", feeAmount: 2400, lastPaid: "2026-02-14", paidUntil: "2027-02-14", scan: { name: "Квитанція про сплату 2026.pdf", slot: "scan-mem-tm" } }, }, { id: "vs", initials: "ВС", name: "Вікторія Савчук", role: "Інженер ОВ-ВК", load: 50, phone: "+380 67 456 78 90", email: "v.savchuk@ukrbudproiekt.ua", employment: "part", hireDate: "2022-09-01", birthDate: "1992-06-17", salaryModel: "fixed", baseSalary: 22000, partRate: 0.5, cashSalary: 7000, edessbRegistrar: { since: "2024-03-15", scope: "Розділи ОВ-ВК · подача та реєстрація об'єктів у ЄДЕССБ", contractType: "Внутрішнє суміщення (наказ №31)", rate: 400, hours: 14 }, education: "КНУБА, Теплогазопостачання, спец. 2015", certs: ["Сертифікат інженера ОВ-ВК"], docs: ["passport", "id_card", "tax_id"], vacationBalance: 12, sickBalance: 0, address: "Київ, вул. Сім'ї Сосніних 9", iban: "UA293052990000026003456789012", positionOfficial: "Інженер з опалення, вентиляції та водопостачання (0,5 ставки)", taxId: "3217064895", idDoc: { kind: "ID-картка", number: "004 552 130", issuedBy: "8002 (ДМС у Голосіївському р-ні м. Києва)", issuedDate: "2017-07-22" }, hireOrder: "Наказ №34-К від 01.09.2022", contractType: "Трудовий договір, 0,5 ставки", kep: { type: "КЕП фахівця", provider: "АЦСК «Україна»", serial: "9F 18 6B 2A", issuedDate: "2025-11-10", validUntil: "2026-11-10" }, }, { id: "dl", initials: "ДЛ", name: "Дмитро Левченко", role: "Електрик", load: 40, phone: "+380 67 567 89 01", email: "d.levchenko@ukrbudproiekt.ua", employment: "contractor", hireDate: "2024-03-01", birthDate: "1987-12-04", salaryModel: "task_rate", taskRate: 800, // ₴/год за задачу education: "НТУУ КПІ, Електропостачання, маг. 2010", certs: ["Сертифікат інженера-електрика"], docs: ["contract_cph"], vacationBalance: 0, sickBalance: 0, address: "Київ, вул. Богатирська 6", iban: "UA293052990000026003567890123", positionOfficial: "Інженер-електрик (за договором ЦПХ)", taxId: "2845128370", idDoc: { kind: "Паспорт громадянина України", number: "СН 482190", issuedBy: "Деснянський РУ ГУ МВС у м. Києві", issuedDate: "2006-04-19" }, hireOrder: "Договір ЦПХ №7-ЦПХ від 01.03.2024", contractType: "Договір цивільно-правового характеру (ЦПХ)", kep: { type: "КЕП ФОП (для ЦПХ)", provider: "Дія.Підпис", serial: "1D 70 A3 55", issuedDate: "2024-12-31", validUntil: "2025-12-31" }, }, ]; // Реквізити компанії — використовуються у довідках про доходи та інших офіційних документах const COMPANY = { shortName: "ТОВ «УКРБУДПРОЄКТ»", fullName: "Товариство з обмеженою відповідальністю «УКРБУДПРОЄКТ»", fullNameUpper: "ТОВАРИСТВО З ОБМЕЖЕНОЮ ВІДПОВІДАЛЬНІСТЮ «УКРБУДПРОЄКТ»", edrpou: "44531964", vatId: "445319615512", vatPayer: true, address: "65062, м. Одеса, Приморський район, вул. Аркадійське плато, буд. 5/2, прим. 13", city: "Одеса", iban: "UA293220010000026004700004299", bank: "АТ «Універсал Банк»", mfo: "322001", director: "Підкапка М.І.", directorFull: "Підкапка Марія Іванівна", directorGen: "Підкапки Марії Іванівни", directorSign: "Марія ПІДКАПКА", directorRole: "Директорка", directorBasis: "Статуту", accountant: "Підкапка М.І.", phone: "+380 (48) 700 16 69", email: "info@ukrbudproiekt.ua", web: "ukrbudproiekt.ua", tmText: "УКРБУДПРОЄКТ® — зареєстрована торговельна марка", tmCert: "Свідоцтво № 37394 від 24.12.2025 р.", }; window.COMPANY = COMPANY; // ФОП Підкапка М.І. — друга юрособа (неплатник ПДВ), для договорів менших об'єктів const COMPANY_FOP = { shortName: "ФОП Підкапка М.І.", fullName: "Фізична особа — підприємець Підкапка Марія Іванівна", fullNameUpper: "ФІЗИЧНА ОСОБА — ПІДПРИЄМЕЦЬ ПІДКАПКА МАРІЯ ІВАНІВНА", edrpou: "3405603368", edrpouLabel: "ІПН (РНОКПП)", vatId: null, vatPayer: false, taxNote: "платник єдиного податку, не є платником ПДВ", address: "65123, Одеська обл., м. Одеса, вул. Заболотного Академіка, буд. 57/1, кв. 55", city: "Одеса", iban: "UA493220010000026005350073107", bank: "АТ «Універсал Банк»", mfo: "322001", director: "Підкапка М.І.", directorFull: "Підкапка Марія Іванівна", directorGen: "Підкапки Марії Іванівни", directorSign: "Марія ПІДКАПКА", directorRole: "Фізична особа — підприємець", directorBasis: "запису в ЄДР", edrRecord: "2010350010001763879", edrDate: "2025-07-07", phone: "+380 63 051 99 16, +380 50 224 84 49", email: "info@ukrbudproiekt.ua", web: "ukrbudproiekt.ua", tmText: "УКРБУДПРОЄКТ® — зареєстрована торговельна марка", tmCert: "Свідоцтво № 37394 від 24.12.2025 р.", }; window.COMPANY_FOP = COMPANY_FOP; // Повертає реквізити Виконавця за обраною юрособою window.execEntity = (kind) => (kind === "fop" ? COMPANY_FOP : COMPANY); // Поточна дата системи як ISO-рядок (окремо від window.TODAY — той об'єкт для UI) const SYS_DATE = "2026-05-29"; window.SYS_DATE = SYS_DATE; // Статус КЕП за датою завершення дії: active / expiring (<= 60 днів) / expired window.kepStatus = function (validUntil, today) { const t = new Date((today || SYS_DATE) + "T00:00:00"); const v = new Date(validUntil + "T00:00:00"); const days = Math.round((v - t) / 86400000); if (days < 0) return { state: "expired", days, label: "Прострочено", tone: "late" }; if (days <= 60) return { state: "expiring", days, label: "Завершується", tone: "warn" }; return { state: "active", days, label: "Дійсний", tone: "live" }; }; // Помісячний дохід за останні 6 місяців — для довідки про доходи / банку / ДПС. // Детермінований (без random), повертає брутто, ПДФО (18%), військовий збір (5%), «на руки». window.incomeRows = function (person, months) { const MONTHS_UA = ["січень","лютий","березень","квітень","травень","червень","липень","серпень","вересень","жовтень","листопад","грудень"]; const n = months || 6; // базовий місячний нарахунок let base; if (person.salaryModel === "task_rate") base = (person.taskRate || 0) * 80; // ~80 год/міс за задачами else base = (person.baseSalary || 0) * (person.partRate || 1); // невеликі детерміновані коливання (бонуси/години) за номером місяця const wobble = [1.0, 1.0, 1.06, 1.0, 1.12, 1.0, 1.0, 1.04, 1.0, 1.08, 1.0, 1.15]; // 6 останніх повних місяців до травня 2026 → лист.2025 … квіт.2026 const seq = [ { y: 2025, m: 10 }, { y: 2025, m: 11 }, { y: 2026, m: 0 }, { y: 2026, m: 1 }, { y: 2026, m: 2 }, { y: 2026, m: 3 }, ].slice(-n); return seq.map(({ y, m }) => { const gross = Math.round(base * (person.salaryModel === "fixed_bonus" ? wobble[m] : 1) / 10) * 10; const pdfo = Math.round(gross * 0.18); const vz = Math.round(gross * 0.05); return { label: `${MONTHS_UA[m]} ${y}`, gross, pdfo, vz, net: gross - pdfo - vz }; }); }; // — Кваліфікаційні сертифікати: підвищення кваліфікації (раз на 5 р.) + безперервність стажу в ЄДЕССБ (раз на рік) function addYearsISO(iso, n) { const [y, m, d] = iso.split("-").map(Number); return `${y + n}-${String(m).padStart(2, "0")}-${String(d).padStart(2, "0")}`; } function daysUntil(iso, today) { const t = new Date((today || window.SYS_DATE) + "T00:00:00"); const v = new Date(iso + "T00:00:00"); return Math.round((v - t) / 86400000); } window.addYearsISO = addYearsISO; // Статус підвищення кваліфікації: дедлайн = остання атестація (або видача) + 5 років window.qualTrainingStatus = function (qc, today) { const base = qc.lastTraining || qc.issuedDate; const dueDate = addYearsISO(base, 5); const days = daysUntil(dueDate, today); let state = "active", label = "За графіком", tone = "live"; if (days < 0) { state = "expired"; label = "Прострочено"; tone = "late"; } else if (days <= 90) { state = "expiring"; label = "Скоро дедлайн"; tone = "warn"; } return { dueDate, days, state, label, tone }; }; // Статус подання відомостей про безперервність стажу в ЄДЕССБ: дедлайн = останнє подання + 1 рік window.edessbContinuityStatus = function (qc, today) { const base = qc.lastEdessbReport || qc.issuedDate; const dueDate = addYearsISO(base, 1); const days = daysUntil(dueDate, today); let state = "active", label = "Подано", tone = "live"; if (days < 0) { state = "expired"; label = "Час подавати"; tone = "late"; } else if (days <= 30) { state = "expiring"; label = "Скоро подавати"; tone = "warn"; } return { dueDate, days, state, label, tone }; }; // Статус договору страхування професійної відповідальності: дедлайн = дата завершення поліса (щороку) window.insuranceStatus = function (ins, today) { if (!ins) return null; const dueDate = ins.endDate; const days = daysUntil(dueDate, today); let state = "active", label = "Чинний", tone = "live"; if (days < 0) { state = "expired"; label = "Прострочено"; tone = "late"; } else if (days <= 45) { state = "expiring"; label = "Спливає"; tone = "warn"; } return { dueDate, days, state, label, tone }; }; // Статус сплати членських внесків: дедлайн = дата, до якої сплачено (щороку) window.membershipStatus = function (m, today) { if (!m) return null; const dueDate = m.paidUntil; const days = daysUntil(dueDate, today); let state = "active", label = "Сплачено", tone = "live"; if (days < 0) { state = "expired"; label = "Час сплатити"; tone = "late"; } else if (days <= 45) { state = "expiring"; label = "Скоро внесок"; tone = "warn"; } return { dueDate, days, state, label, tone }; }; // Наступний клас наслідків для підвищення (СС1 → СС2 → СС3) window.qualUpgrade = function (qc) { const order = ["СС1", "СС2", "СС3"]; const i = order.indexOf((qc.class || "").trim()); if (i < 0 || i >= order.length - 1) return null; return { current: order[i], next: order[i + 1] }; }; // Класи наслідків та обсяг експертизи: // СС1 (прості умови) — expertiseScope: "estimate" (тільки експертиза кошторису) // СС1 (складні техногенні умови, напр. сейсмічність) — expertiseScope: "strength" (МОС — міцність, остійність, // стійкість — РАЗОМ із кошторисом; для бюджету кошторис завжди) // СС1 (складні) — expertiseScope: "full" (комплексна) // СС2 — expertiseScope: "full" // СС3 — expertiseScope: "full"+ // // Кожен етап має масив задач і прогноз скільки задач буде взагалі (`plan`) // — це дозволяє показувати «зроблено 3 з 8» навіть до повного декомпозу. const STAGES = [ { id: "project", name: "Проєктні роботи", short: "Проєкт" }, { id: "estimate", name: "Кошторис та аналіз цін", short: "Кошторис" }, { id: "expertise", name: "Експертиза", short: "Експерт." }, { id: "edessb", name: "Реєстрація в ЄДЕССБ", short: "ЄДЕССБ" }, { id: "supervision", name: "Авторський нагляд", short: "Авт. нагл." }, ]; // Коригування — паралельний цикл з тих самих 4 етапів, показуємо тільки якщо є задачі/план const CORRECTION_STAGES = [ { id: "c-project", name: "Коригування: проєкт", short: "Проєкт" }, { id: "c-estimate", name: "Коригування: кошторис", short: "Кошторис" }, { id: "c-expertise", name: "Коригування: експертиза", short: "Експерт." }, { id: "c-edessb", name: "Коригування: ЄДЕССБ", short: "ЄДЕССБ" }, ]; const OBJECTS = [ { id: "ukp-47", code: "УКП-2025-47", name: "ЖК «Подільський»", client: "ТОВ «Подільський Девелопмент»", address: "Київ, Подільський р-н", workType: "new", grade: "СС2", expertiseScope: "full", gradeKind: "calculated", gradeHistory: [ { value: "СС2", kind: "preliminary", date: "2025-03-12", by: "ok", basis: "Завдання на проєктування, погоджене із замовником" }, { value: "СС2", kind: "calculated", date: "2025-04-20", by: "ap", basis: "Розрахунок класу наслідків за ДБН В.1.2-14: підтверджено СС2 (>300 осіб)" }, ], activeStage: "project", revision: "Ред. 2", deadline: "12.06", contract: "8.4 млн ₴", team: ["ok","ap","tm","vs"], plan: { project: 18, estimate: 5, expertise: 9, edessb: 3, supervision: 0, "c-project": 0, "c-estimate": 0, "c-expertise": 0, "c-edessb": 0 }, }, { id: "ukp-32", code: "УКП-2025-32", name: "Школа №14", client: "Управління освіти м. Києва", address: "Святошин", grade: "СС2", expertiseScope: "full", gradeKind: "preliminary", workType: "new", gradeHistory: [ { value: "СС2", kind: "preliminary", date: "2025-09-20", by: "ok", basis: "Завдання на проєктування (бюджетний тендер). Розрахунок класу ще не виконано." }, ], activeStage: "project", revision: "Ред. 1", deadline: "28.06", contract: "3.1 млн ₴", team: ["ok","tm","vs","dl"], plan: { project: 22, estimate: 6, expertise: 11, edessb: 3, supervision: 0, "c-project": 0, "c-estimate": 0, "c-expertise": 0, "c-edessb": 0 }, }, { id: "ukp-51", code: "УКП-2025-51", name: "«Соснові гаї»", client: "ПП «Лісове»", address: "с. Лісники", grade: "СС1", expertiseScope: "estimate", gradeKind: "preliminary", workType: "new", gradeHistory: [ { value: "СС1", kind: "preliminary", date: "2025-12-18", by: "ok", basis: "Завдання на проєктування. Попередньо СС1 — малоповерхова житлова забудова." }, ], activeStage: "project", revision: "Ред. 1", deadline: "15.07", contract: "1.7 млн ₴", team: ["ok","ap","tm"], plan: { project: 14, estimate: 4, expertise: 2, edessb: 2, supervision: 0, "c-project": 0, "c-estimate": 0, "c-expertise": 0, "c-edessb": 0 }, }, { id: "ukp-89", code: "УКП-2024-89", name: "Реконструкція адмінбудівлі", client: "Міністерство юстиції", address: "вул. Грушевського 8", grade: "СС2", expertiseScope: "full", gradeKind: "calculated", workType: "reconstruction", gradeHistory: [ { value: "СС1", kind: "preliminary", date: "2024-06-01", by: "ok", basis: "Завдання на проєктування — попередньо СС1." }, { value: "СС2", kind: "calculated", date: "2024-07-10", by: "ap", basis: "За результатами ТО: пам'ятка-складова + понад 300 відвідувачів — підвищено до СС2." }, ], activeStage: "supervision", revision: "—", deadline: "31.12", contract: "0.8 млн ₴", team: ["ok","ap"], plan: { project: 16, estimate: 5, expertise: 8, edessb: 3, supervision: 12, "c-project": 6, "c-estimate": 2, "c-expertise": 4, "c-edessb": 2 }, completedStages: ["project","estimate","expertise","edessb","c-project","c-estimate","c-expertise","c-edessb"], }, ]; // Сьогодні = вівторок, 26 травня 2026 // Кожна задача має stage (етап) або null для адміністративних const TASKS = [ // === ЖК Подільський (УКП-47), проєктні роботи === { id: "t1", title: "Розрахунок плит перекриття 4-9 поверхів", obj: "ukp-47", stage: "project", assignee: "ap", section: "КЖ", day: "26", time: "10:30 — 13:00", est: "2.5 год", status: "live", note: "Перерахувати з урахуванням нової специфікації арматури А500С.", subtasks: [ { id: "st-t1-a", title: "Узгодити специфікацію арматури з постачальником", assignee: "ap", status: "done", due: "28 трав" }, { id: "st-t1-b", title: "Передати розрахункову модель на перевірку ГІПу", assignee: "ok", status: "live", due: "30 трав" }, ], subs: [ { t: "Збір навантажень", done: true, h: "0.5 год" }, { t: "Розрахункова модель ЛІРА", done: true, h: "1.0 год" }, { t: "Армування основних балок", done: false, h: "0.5 год" }, { t: "Перевірка прогинів", done: false, h: "0.5 год" }, ] }, { id: "t2", title: "Узгодження фасадних рішень з замовником", obj: "ukp-47", stage: "project", assignee: "tm", section: "АР", day: "26", time: "14:00 — 15:30", est: "1.5 год", status: "live", subs: [ { t: "Підготувати 3 варіанти", done: true, h: "" }, { t: "Zoom-зустріч з ТОВ", done: false, h: "1.0 год" }, { t: "Зафіксувати в протоколі", done: false, h: "0.5 год" }, ] }, { id: "t4", title: "Перевірити креслення розділу ОВ", obj: "ukp-47", stage: "project", assignee: "ok", section: "ОВ", day: "26", time: "09:00 — 10:00", est: "1 год", status: "todo", subs: [] }, { id: "t8", title: "Зустріч з командою — планерка", obj: "ukp-47", stage: "project", assignee: "ok", section: "ГІП", day: "26", time: "08:00 — 08:30", est: "0.5 год", status: "done", subs: [] }, { id: "t9", title: "Видача КЖ Розділу 4 (каркас)", obj: "ukp-47", stage: "project", assignee: "ap", section: "КЖ", day: "27", est: "8 год", status: "todo", subs: [] }, { id: "t13", title: "Узгодження з ДСНС", obj: "ukp-47", stage: "project", assignee: "ok", section: "ГІП", day: "29", est: "2 год", status: "todo", subs: [] }, // === Школа №14 (УКП-32), проєктні роботи === { id: "t3", title: "Видача КЖ Розділу 3 (фундаменти)", obj: "ukp-32", stage: "project", assignee: "ap", section: "КЖ", day: "25", est: "—", status: "late", note: "Прострочено на 1 день. Завершити сьогодні до 18:00.", subs: [ { t: "Перевірка експертом", done: true, h: "" }, { t: "Підпис ГІП", done: false, h: "" }, { t: "Відправка замовнику", done: false, h: "" }, ] }, { id: "t5", title: "Розрахунок вентиляції харчоблоку", obj: "ukp-32", stage: "project", assignee: "vs", section: "ОВ", day: "26", time: "11:00 — 13:00", est: "2 год", status: "todo", subs: [] }, { id: "t10", title: "Креслення АР спортзалу", obj: "ukp-32", stage: "project", assignee: "tm", section: "АР", day: "27", est: "6 год", status: "todo", subs: [] }, { id: "t11", title: "Розрахунок ВК (господарські стоки)", obj: "ukp-32", stage: "project", assignee: "vs", section: "ВК", day: "28", est: "3 год", status: "todo", subs: [] }, { id: "t16", title: "Перевірка кошторису на матеріали", obj: "ukp-32", stage: "estimate", assignee: "ok", section: "ГІП", day: "28", est: "1.5 год", status: "todo", subs: [] }, // === Соснові гаї (УКП-51), проєктні роботи === { id: "t7", title: "Підготувати ТЗ електропостачання", obj: "ukp-51", stage: "project", assignee: "dl", section: "ЕО", day: "26", est: "1.5 год", status: "todo", subs: [] }, { id: "t12", title: "Підготовка тендерного пакету «Соснові гаї»", obj: "ukp-51", stage: "estimate", assignee: "ok", section: "ТД", day: "28", est: "4 год", status: "todo", subs: [] }, { id: "t14", title: "Розділ ЕО — основні споживачі", obj: "ukp-51", stage: "project", assignee: "dl", section: "ЕО", day: "29", est: "5 год", status: "todo", subs: [] }, // === Адмінбудівля (УКП-89), авторський нагляд === { id: "t6", title: "Виїзд на майданчик — авт. нагляд", obj: "ukp-89", stage: "supervision", assignee: "ok", section: "АН", day: "26", time: "15:00 — 17:30", est: "2.5 год", status: "todo", note: "Контрольна перевірка вузлів примикання покрівлі.", subs: [ { t: "Перевірка вузлів покрівлі", done: false, h: "" }, { t: "Журнал авт. нагляду", done: false, h: "" }, { t: "Фотофіксація", done: false, h: "" }, ] }, { id: "t15", title: "Передача архіву по об'єкту УКП-2024-89", obj: "ukp-89", stage: "supervision", assignee: "ap", section: "АР", day: "29", est: "2 год", status: "todo", subs: [] }, // === ПОЗА ПРОЄКТАМИ — адміністративні === { id: "a1", title: "Оплата оренди офісу за травень", obj: null, stage: "admin", assignee: "ok", section: "АДМ", day: "28", est: "0.5 год", status: "todo", subs: [] }, { id: "a2", title: "Фотосесія команди для сайту", obj: null, stage: "admin", assignee: "ok", section: "АДМ", day: "29", time: "16:00 — 18:00", est: "2 год", status: "todo", note: "Підтвердити локацію та фотографа.", subs: [ { t: "Підтвердити фотографа", done: true, h: "" }, { t: "Узгодити дрес-код", done: false, h: "" }, { t: "Бронювати студію", done: false, h: "" }, ] }, { id: "a3", title: "Купити канцелярію та картриджі", obj: null, stage: "admin", assignee: "ok", section: "АДМ", day: "27", est: "0.5 год", status: "todo", subs: [] }, ]; // Нормалізація задачі: гарантуємо поля subtasks (повноцінні міні-задачі) // та checklists (групи галочок). Старий формат `subs` → перша чекліст-група. function normalizeTask(t) { const out = { ...t }; if (!Array.isArray(out.subtasks)) out.subtasks = []; out.subtasks = out.subtasks.map((s, i) => ({ id: s.id || `st-${out.id}-${i}`, title: s.title || s.t || "", assignee: s.assignee || null, status: s.status || (s.done ? "done" : "todo"), due: s.due || null, })); if (!Array.isArray(out.checklists)) { if (Array.isArray(out.subs) && out.subs.length) { out.checklists = [{ id: `cl-${out.id}`, title: "Чек-ліст", items: out.subs.map((s, i) => ({ id: `ci-${out.id}-${i}`, text: s.t || s.text || "", done: !!s.done, hint: s.h || "" })), }]; } else { out.checklists = []; } } return out; } window.normalizeTask = normalizeTask; window.DATA = { TEAM, OBJECTS, TASKS: TASKS.map(normalizeTask), STAGES, CORRECTION_STAGES }; window.getTeam = (id) => TEAM.find(t => t.id === id); window.getObject = (id) => OBJECTS.find(o => o.id === id); window.getStage = (id) => STAGES.find(s => s.id === id) || CORRECTION_STAGES.find(s => s.id === id); // ============ HR / SALARY ============ // Тип зайнятості const EMPLOYMENT_LABELS = { staff: { label: "Штат", short: "Шт", color: "blue" }, part: { label: "Часткова", short: "Ч", color: "green" }, contractor: { label: "Субпідряд", short: "СП", color: "neutral" }, }; window.EMPLOYMENT_LABELS = EMPLOYMENT_LABELS; // Модель ЗП const SALARY_MODEL_LABELS = { fixed: "Оклад", fixed_bonus: "Оклад + бонус", task_rate: "Гонорар за задачу", }; window.SALARY_MODEL_LABELS = SALARY_MODEL_LABELS; // ВІДПУСТКИ та ЛІКАРНЯНІ — заявки const VACATIONS = [ { id: "v1", who: "ap", type: "vacation", from: "2026-06-15", to: "2026-06-26", days: 10, status: "approved", note: "Літня відпустка" }, { id: "v2", who: "vs", type: "vacation", from: "2026-07-06", to: "2026-07-17", days: 10, status: "pending", note: "Очікує підтвердження" }, { id: "v3", who: "tm", type: "sick", from: "2026-05-12", to: "2026-05-14", days: 3, status: "approved", note: "Лікарняний (відкритий)" }, { id: "v4", who: "ap", type: "vacation", from: "2026-12-23", to: "2026-12-31", days: 6, status: "pending", note: "Новорічні свята" }, { id: "v5", who: "ok", type: "vacation", from: "2026-08-10", to: "2026-08-23", days: 12, status: "approved", note: "Серпнева відпустка" }, ]; // ДОКУМЕНТИ — кадрові накази, договори const DOCUMENTS = [ { id: "d1", title: "Наказ №47-К про преміювання за травень", type: "order", date: "2026-05-31", status: "draft", who: ["ok","ap","tm","vs"], note: "У підготовці" }, { id: "d2", title: "Договір №ЦПХ-15 з Левченко Д.", type: "contract", date: "2024-03-01", status: "signed", who: ["dl"], note: "Діючий" }, { id: "d3", title: "Заява на відпустку (Петренко)", type: "request", date: "2026-05-18", status: "approved", who: ["ap"], note: "Підписано" }, { id: "d4", title: "Заява на відпустку (Савчук)", type: "request", date: "2026-05-24", status: "pending", who: ["vs"], note: "Очікує підпису ГІП" }, { id: "d5", title: "Трудовий договір (Мельник)", type: "contract", date: "2021-02-10", status: "signed", who: ["tm"], note: "Діючий" }, { id: "d6", title: "Наказ №46-К про прийняття", type: "order", date: "2024-03-01", status: "signed", who: ["dl"], note: "Архів" }, ]; const DOC_TYPE_LABELS = { order: "Наказ", contract: "Договір", request: "Заява", }; const DOC_STATUS_LABELS = { draft: { label: "Чернетка", tone: "neutral" }, pending: { label: "На підписанні", tone: "warn" }, signed: { label: "Підписано", tone: "live" }, approved: { label: "Затверджено", tone: "live" }, }; window.DOC_TYPE_LABELS = DOC_TYPE_LABELS; window.DOC_STATUS_LABELS = DOC_STATUS_LABELS; // TIMESHEET — спрощено: відпрацьовані години на тиждень // Тижні останніх 4 + поточний, по людях const TIMESHEET = { // тиждень: { who: години_по_днях [пн, вт, ср, чт, пт] } "2026-W22": { ok: { hours: [8, 7.5, 0, 0, 0], planned: 40, note: "Сьогодні вівторок, день у процесі" }, ap: { hours: [8, 8, 0, 0, 0], planned: 40 }, tm: { hours: [0, 8, 0, 0, 0], planned: 40, note: "Понеділок — лікарняний" }, vs: { hours: [4, 4, 0, 0, 0], planned: 20 }, // пів-ставки dl: { hours: [0, 2, 0, 0, 0], planned: 0, note: "По задачам — без планового часу" }, }, "2026-W21": { ok: { hours: [9, 8, 8.5, 8, 7], planned: 40 }, ap: { hours: [8, 8, 8, 8, 8], planned: 40 }, tm: { hours: [8, 8, 0, 0, 0], planned: 40, note: "Лікарняний з середи" }, vs: { hours: [4, 4, 4, 4, 4], planned: 20 }, dl: { hours: [0, 0, 6, 0, 4], planned: 0 }, }, "2026-W20": { ok: { hours: [8, 8, 9, 8, 8], planned: 40 }, ap: { hours: [8, 9, 8, 8, 7], planned: 40 }, tm: { hours: [8, 8, 8, 8, 8], planned: 40 }, vs: { hours: [4, 4, 4, 4, 4], planned: 20 }, dl: { hours: [4, 0, 4, 0, 0], planned: 0 }, }, }; const CURRENT_WEEK = "2026-W22"; window.CURRENT_WEEK = CURRENT_WEEK; window.TIMESHEET = TIMESHEET; // ЗАРПЛАТА — за квітень (попередній закритий період) // Кожна позиція збирається з: оклад × коефіцієнт + бонуси − утримання function computePayroll(personId, periodHours, periodPlan) { const p = TEAM.find(t => t.id === personId); if (!p) return null; let base = 0, bonus = 0, notes = []; if (p.salaryModel === "fixed" || p.salaryModel === "fixed_bonus") { const part = p.partRate || 1; base = p.baseSalary * part; if (periodPlan && periodHours && periodPlan > 0 && periodHours < periodPlan) { // якщо відпрацював менше плану — пропорційно const ratio = periodHours / periodPlan; base = base * ratio; notes.push(`Відпрацьовано ${periodHours}/${periodPlan} год — оклад × ${ratio.toFixed(2)}`); } } if (p.salaryModel === "fixed_bonus") { // бонус: % від обсягу зданих за період договорів // тут спрощено — фіксована сума для прикладу bonus = Math.round(p.baseSalary * (p.bonusRate || 0)); } if (p.salaryModel === "task_rate") { base = Math.round((periodHours || 0) * p.taskRate); notes.push(`${periodHours || 0} год × ${p.taskRate} ₴`); } // Окрема погодинна посада: реєстратор ЄДЕССБ (внутрішнє суміщення) let edessb = 0; const er = p.edessbRegistrar; if (er && er.rate && er.hours) { edessb = Math.round(er.hours * er.rate); notes.push(`ЄДЕССБ: ${er.hours} год × ${er.rate} ₴ = ${edessb} ₴`); } // утримання (для штатних) let pdfo = 0, vz = 0; const gross = base + bonus + edessb; if (p.employment !== "contractor") { // Ставки — єдине джерело істини: window.TAX_RATES (profit-report.jsx) const R = window.TAX_RATES || { pdfo: 0.18, vz: 0.05 }; pdfo = Math.round(gross * R.pdfo); // ПДФО 18% vz = Math.round(gross * R.vz); // військовий збір 5% } const net = gross - pdfo - vz; return { person: p, base, bonus, edessb, gross, pdfo, vz, net, notes }; } window.computePayroll = computePayroll; // ============ ТАБЕЛЬ ОБЛІКУ РОБОЧОГО ЧАСУ (форма П-5) ============ // Коди обліку використання робочого часу. «Р» зберігається як число годин, // решта — буквені позначення особливих днів/неявок (за типовою формою П-5). const TABEL_CODES = { Р: { label: "Відпрацьовано", full: "Робочі години", tone: "work", paid: true, work: true }, ВД: { label: "Вихідний", full: "Вихідний / святковий день", tone: "off", paid: false, work: false }, В: { label: "Відпустка", full: "Щорічна основна відпустка", tone: "blue", paid: true, work: false }, ТН: { label: "Лікарняний", full: "Тимчасова непрацездатність", tone: "amber", paid: true, work: false }, ВП: { label: "Відрядження", full: "Службове відрядження", tone: "live", paid: true, work: true }, НА: { label: "За свій рахунок", full: "Відпустка без збереження ЗП", tone: "neutral", paid: false, work: false }, ІН: { label: "Інші неявки", full: "Інші невідпрацьовані причини",tone: "late", paid: false, work: false }, }; window.TABEL_CODES = TABEL_CODES; // Святкові/неробочі дні (виробничий календар) — для розрахунку норми годин const TABEL_HOLIDAYS = { "2026-05": [1, 8], // 1 травня (День праці), 8 травня (День памʼяті та перемоги) "2026-04": [], }; // Метадані місяця: масив днів з ознаками вихідний/свято/робочий function monthMeta(monthId) { const [y, m] = monthId.split("-").map(Number); const total = new Date(y, m, 0).getDate(); const hol = TABEL_HOLIDAYS[monthId] || []; const DOW = ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"]; const out = []; for (let d = 1; d <= total; d++) { const dow = new Date(y, m - 1, d).getDay(); const weekend = dow === 0 || dow === 6; const holiday = hol.includes(d); out.push({ day: d, dow, dowName: DOW[dow], weekend, holiday, working: !weekend && !holiday }); } return out; } window.monthMeta = monthMeta; // Норма годин на робочий день для людини (часткова ставка → пропорційно) function personDayHours(person) { return 8 * (person && person.partRate ? person.partRate : 1); } // Генератор сидів табеля: заповнює робочі дні базою з урахуванням // правок (overrides — буквений код) та погодинних значень (byDay). function seedTabel(monthId, rules) { const meta = monthMeta(monthId); const entries = {}; for (const pid in rules) { const r = rules[pid]; const e = {}; meta.forEach(dm => { if (!dm.working) return; if (r.overrides && r.overrides[dm.day] != null) { e[dm.day] = r.overrides[dm.day]; return; } const v = (r.byDay && r.byDay[dm.day] != null) ? r.byDay[dm.day] : (r.base != null ? r.base : 0); if (v !== 0) e[dm.day] = v; }); entries[pid] = e; } return entries; } // Сидові дані. Травень — поточний (на затвердженні/до закриття); // квітень — закритий, його підсумки збігаються з виплаченою ЗП. const TABEL = { "2026-05": { status: "submitted", closedAt: null, entries: seedTabel("2026-05", { ok: { base: 8, byDay: { 18: 9, 26: 9 } }, // 154 год (2 дні понаднормово) ap: { base: 8 }, // 152 год tm: { base: 8, overrides: { 12: "ТН", 13: "ТН", 14: "ТН" } }, // 128 год + 3 дні лікарняного vs: { base: 4 }, // 76 год (0,5 ставки) dl: { base: 0, byDay: { 5: 6, 6: 4, 12: 6, 13: 4, 19: 6, 20: 4, 27: 8 } }, // 38 год за задачами }), }, "2026-04": { status: "closed", closedAt: "2026-05-04", entries: seedTabel("2026-04", { ok: { base: 8, byDay: { 6: 9, 20: 9 } }, // 178 год ap: { base: 8 }, // 176 год tm: { base: 8, overrides: { 15: "ТН" } }, // 168 год vs: { base: 4 }, // 88 год dl: { base: 0, byDay: { 7: 6, 8: 4, 14: 6, 15: 4, 21: 6, 22: 4, 28: 8, 29: 4 } }, // 42 год }), }, }; window.TABEL = TABEL; window.TABEL_MONTHS = [ { id: "2026-05", label: "Травень 2026" }, { id: "2026-04", label: "Квітень 2026" }, ]; // Робочий стан табеля (з правками користувача), зберігається у localStorage const TABEL_STORE_KEY = "ukrbud_tabel_v1"; function freshTabelState() { const s = { __v: 1 }; for (const mid in TABEL) s[mid] = { status: TABEL[mid].status, closedAt: TABEL[mid].closedAt, entries: JSON.parse(JSON.stringify(TABEL[mid].entries)), }; return s; } window.__tabelState = (function () { try { const raw = localStorage.getItem(TABEL_STORE_KEY); if (raw) { const p = JSON.parse(raw); if (p && p.__v === 1) return p; } } catch (e) {} return freshTabelState(); })(); window.saveTabelState = function () { try { localStorage.setItem(TABEL_STORE_KEY, JSON.stringify(window.__tabelState)); } catch (e) {} window.dispatchEvent(new Event("ukrbud-tabel")); }; window.resetTabelState = function () { window.__tabelState = freshTabelState(); window.saveTabelState(); }; // Хук перерендеру при зміні табеля window.useTabel = function () { const [, force] = React.useState(0); React.useEffect(() => { const h = () => force(x => x + 1); window.addEventListener("ukrbud-tabel", h); return () => window.removeEventListener("ukrbud-tabel", h); }, []); return window.__tabelState; }; // Підсумок по людині за місяць: відпрацьовані год/дні, норма, коди неявок window.tabelSummary = function (monthId, personId) { const month = window.__tabelState[monthId]; if (!month) return null; const e = month.entries[personId] || {}; const meta = monthMeta(monthId); const person = window.getTeam(personId); const dayHours = personDayHours(person); let workedHours = 0, workedDays = 0, normHours = 0, normDays = 0; const codeDays = {}; meta.forEach(dm => { if (dm.working) { normDays++; normHours += dayHours; } const v = e[dm.day]; if (typeof v === "number") { workedHours += v; workedDays++; } else if (typeof v === "string" && TABEL_CODES[v] && TABEL_CODES[v].work) { workedHours += dayHours; workedDays++; } else if (typeof v === "string") { codeDays[v] = (codeDays[v] || 0) + 1; } }); return { workedHours, workedDays, normHours, normDays, dayHours, codeDays }; }; // Години для нарахування ЗП (по всіх людях за місяць) window.tabelHours = function (monthId) { const out = {}; const m = window.__tabelState[monthId]; if (!m) return out; for (const pid in m.entries) out[pid] = window.tabelSummary(monthId, pid).workedHours; return out; }; window.tabelNorm = function (monthId, personId) { const s = window.tabelSummary(monthId, personId); return s ? s.normHours : 0; }; window.tabelStatus = function (monthId) { const m = window.__tabelState[monthId]; return m ? m.status : null; }; window.TABEL_STATUS_LABELS = { draft: { label: "Чернетка", tone: "neutral" }, submitted: { label: "На затвердженні", tone: "amber" }, closed: { label: "Закрито", tone: "live" }, }; // Розрахунковий період ЗП — травень 2026; години беруться з табеля const PAYROLL_PERIOD = { id: "2026-05", label: "Травень 2026", workdays: 19, workhours: 152, }; Object.defineProperty(PAYROLL_PERIOD, "hoursByPerson", { enumerable: true, get() { return window.tabelHours("2026-05"); }, }); Object.defineProperty(PAYROLL_PERIOD, "status", { enumerable: true, get() { return window.tabelStatus("2026-05") === "closed" ? "closed" : "open"; }, }); Object.defineProperty(PAYROLL_PERIOD, "closedAt", { enumerable: true, get() { const m = window.__tabelState["2026-05"]; return m ? m.closedAt : null; }, }); window.PAYROLL_PERIOD = PAYROLL_PERIOD; window.DATA.VACATIONS = VACATIONS; window.DATA.DOCUMENTS = DOCUMENTS; // ============ ФІНАНСИ З ЗАМОВНИКАМИ ============ // КЛІЄНТИ (юр. та фіз. особи) const CLIENTS = [ { id: "c-pod", name: "ТОВ «Подільський Девелопмент»", short: "Подільський Девелопмент", type: "legal", edrpou: "41835672", address: "м. Київ, вул. Хорива 12, оф. 305", contact: { name: "Олександр Гриценко", role: "Директор з розвитку", phone: "+380 67 412 38 90", email: "o.grytsenko@podildev.ua" }, rating: 5, since: "2023-01-15", note: "Перевірений замовник, стабільні оплати, працюємо з 2023." }, { id: "c-osv", name: "Управління освіти м. Києва", short: "Управління освіти", type: "state", edrpou: "02147853", address: "м. Київ, вул. Богдана Хмельницького 32", contact: { name: "Тетяна Сергіївна Заварзіна", role: "Заст. начальника", phone: "+380 44 234 56 78", email: "uo@kyivcity.gov.ua" }, rating: 3, since: "2024-09-01", note: "Бюджетний замовник. Оплати з затримками 30-90 днів — особливість тендерів." }, { id: "c-lis", name: "ПП «Лісове»", short: "Лісове", type: "legal", edrpou: "39184562", address: "Київська обл., с. Лісники, вул. Соснова 1", contact: { name: "Ігор Петрук", role: "Власник", phone: "+380 50 887 23 45", email: "lisove.pp@gmail.com" }, rating: 4, since: "2024-12-10", note: "Котеджне містечко, перший спільний проєкт." }, { id: "c-mju", name: "Міністерство юстиції України", short: "Мін'юст", type: "state", edrpou: "00015622", address: "м. Київ, вул. Архітектора Городецького 13", contact: { name: "Наталія Корчинська", role: "Гол. спец. відділу буд.", phone: "+380 44 271 17 17", email: "natalia.k@minjust.gov.ua" }, rating: 4, since: "2022-05-20", note: "Реконструкція. Працюємо третій рік." }, ]; window.DATA.CLIENTS = CLIENTS; window.getClient = (id) => CLIENTS.find(c => c.id === id); // ДОГОВОРИ (прив'язані до об'єктів) // Графік платежів: по етапах + аванс const CONTRACTS = [ { id: "k-47", number: "47/2025", date: "2025-03-12", client: "c-pod", obj: "ukp-47", total: 8400000, status: "active", schedule: [ { stage: "Аванс при підписанні", pct: 20, amount: 1680000, dueDate: "2025-03-15", paidDate: "2025-03-14", invoice: "i-47-01" }, { stage: "Здача ескізного проєкту", pct: 15, amount: 1260000, dueDate: "2025-06-30", paidDate: "2025-07-08", invoice: "i-47-02" }, { stage: "Проміжна виплата — травень", pct: 15, amount: 1260000, dueDate: "2026-05-20", invoice: "i-47-03" }, // прострочено на 6 днів { stage: "Здача робочого проєкту", pct: 35, amount: 2940000, dueDate: "2026-06-12", invoice: "i-47-04" }, // скоро { stage: "Реєстрація в ЄДЕССБ", pct: 10, amount: 840000, dueDate: "2026-08-30" }, { stage: "Остаточний розрахунок", pct: 5, amount: 420000, dueDate: "2026-12-31" }, ] }, { id: "k-32", number: "32/2025", date: "2025-09-20", client: "c-osv", obj: "ukp-32", total: 3100000, status: "active", schedule: [ { stage: "Аванс після тендеру", pct: 30, amount: 930000, dueDate: "2025-10-15", paidDate: "2025-11-18", invoice: "i-32-01", lateDays: 34 }, { stage: "Експертиза кошторису", pct: 15, amount: 465000, dueDate: "2026-04-30", invoice: "i-32-02" }, // ДУЖЕ прострочено { stage: "Здача робочого проєкту", pct: 40, amount: 1240000, dueDate: "2026-06-28", invoice: "i-32-03" }, // скоро { stage: "Експертиза", pct: 10, amount: 310000, dueDate: "2026-09-30" }, { stage: "Остаточний", pct: 5, amount: 155000, dueDate: "2026-12-15" }, ] }, { id: "k-51", number: "51/2025", date: "2025-12-18", client: "c-lis", obj: "ukp-51", total: 1700000, status: "active", schedule: [ { stage: "Аванс", pct: 25, amount: 425000, dueDate: "2025-12-25", paidDate: "2025-12-23", invoice: "i-51-01" }, { stage: "Ескізний проєкт", pct: 25, amount: 425000, dueDate: "2026-04-15", paidDate: "2026-04-18", invoice: "i-51-02" }, { stage: "Робочий проєкт", pct: 35, amount: 595000, dueDate: "2026-07-15", invoice: "i-51-03" }, { stage: "Завершальний", pct: 15, amount: 255000, dueDate: "2026-10-01" }, ] }, { id: "k-89", number: "89/2024", date: "2024-06-01", client: "c-mju", obj: "ukp-89", total: 800000, status: "supervision", schedule: [ { stage: "Аванс", pct: 30, amount: 240000, dueDate: "2024-06-10", paidDate: "2024-06-08", invoice: "i-89-01" }, { stage: "Робочий проєкт", pct: 40, amount: 320000, dueDate: "2024-12-30", paidDate: "2025-01-22", invoice: "i-89-02", lateDays: 23 }, { stage: "Експертиза + ЄДЕССБ", pct: 20, amount: 160000, dueDate: "2025-08-15", paidDate: "2025-08-30", invoice: "i-89-03", lateDays: 15 }, { stage: "Авт. нагляд — травень", pct: 5, amount: 40000, dueDate: "2026-06-05", invoice: "i-89-04" }, // скоро { stage: "Авт. нагляд — фінал", pct: 5, amount: 40000, dueDate: "2026-12-31" }, ] }, ]; window.DATA.CONTRACTS = CONTRACTS; window.getContract = (id) => CONTRACTS.find(c => c.id === id); const CONTRACT_STATUS = { draft: { label: "Чернетка", tone: "neutral" }, active: { label: "Активний", tone: "live" }, supervision: { label: "Авт. нагляд", tone: "neutral" }, completed: { label: "Виконано", tone: "live" }, cancelled: { label: "Розірвано", tone: "warn" }, }; window.CONTRACT_STATUS = CONTRACT_STATUS; // РАХУНКИ (виставлені інвойси) function buildInvoices() { const today = "2026-05-26"; const result = []; CONTRACTS.forEach(k => { k.schedule.forEach((s, idx) => { if (!s.invoice) return; // Виставлення: 14 днів до сплати (для сплачених) або 30 днів до терміну (для невиставлених) let issued; if (s.paidDate) { issued = offsetDate(s.paidDate, -14); } else { // виставляємо рахунок за 30 днів до dueDate, якщо це у межах сьогодні+45 днів const planned = offsetDate(s.dueDate, -30); if (planned > today) return; // ще не настав час виставляти issued = planned; } let status, lateDays = 0; if (s.paidDate) { status = "paid"; } else if (s.dueDate < today) { status = "overdue"; lateDays = daysBetween(s.dueDate, today); } else { status = "issued"; } result.push({ id: s.invoice, number: `${k.number.split('/')[0]}/${String(idx+1).padStart(2,'0')}`, contract: k.id, client: k.client, obj: k.obj, title: s.stage, amount: s.amount, issueDate: issued, dueDate: s.dueDate, paidDate: s.paidDate || null, status, lateDays }); }); }); return result.sort((a,b) => b.issueDate.localeCompare(a.issueDate)); } function offsetDate(iso, days) { const d = new Date(iso); d.setDate(d.getDate() + days); return d.toISOString().slice(0, 10); } function daysBetween(iso1, iso2) { return Math.round((new Date(iso2) - new Date(iso1)) / 86400000); } const INVOICES = buildInvoices(); window.DATA.INVOICES = INVOICES; window.daysBetween = daysBetween; // ============ ВИТРАТИ КОМПАНІЇ ============ // Категорії витрат з типом (АДМ vs ЗВВ) за замовчуванням const EXPENSE_CATEGORIES = [ // АДМ — адміністративні (загальні витрати утримання офісу/команди) { id: "rent", label: "Оренда офісу", type: "adm", icon: "🏢" }, { id: "utilities", label: "Комунальні послуги", type: "adm", icon: "💡" }, { id: "software", label: "ПЗ (підписки)", type: "adm", icon: "💻" }, { id: "supplies", label: "Споживчі матеріали", type: "adm", icon: "📎" }, { id: "equipment", label: "Обладнання", type: "adm", icon: "🖥" }, { id: "comms", label: "Зв'язок, інтернет", type: "adm", icon: "📡" }, { id: "marketing", label: "Реклама / маркетинг",type: "adm", icon: "📢" }, { id: "bank", label: "Банківські комісії", type: "adm", icon: "🏦" }, // ЗВВ — загальновиробничі (прямі виробничі витрати) { id: "subcontract", label: "Субпідряд", type: "zvv", icon: "🤝" }, { id: "expertise", label: "Експертиза", type: "zvv", icon: "📋" }, { id: "delivery", label: "Виїзди на об'єкт", type: "zvv", icon: "🚗" }, { id: "fuel", label: "Пальне / авто", type: "zvv", icon: "⛽" }, { id: "training", label: "Навчання", type: "zvv", icon: "🎓" }, ]; window.EXPENSE_CATEGORIES = EXPENSE_CATEGORIES; window.getCategory = (id) => EXPENSE_CATEGORIES.find(c => c.id === id); const EXPENSE_TYPE_LABELS = { adm: { label: "Адміністративні", short: "АДМ" }, zvv: { label: "Загальновиробничі", short: "ЗВВ" }, }; window.EXPENSE_TYPE_LABELS = EXPENSE_TYPE_LABELS; const PERIODICITY_LABELS = { monthly: "щомісячно", quarterly: "щокварталу", yearly: "щорічно", once: "разово", }; window.PERIODICITY_LABELS = PERIODICITY_LABELS; // ВИТРАТИ — реальні і планові // monthlyAmount — у скільки місячно ця витрата виливається (для річних розраховано) // vatRate — ставка вхідного ПДВ (20 / 7 / 0). 0 → постачальник не платник ПДВ або послуга звільнена. // taxInvoice — чи зареєстрована податкова накладна (без неї вхідний ПДВ не йде в кредит). const EXPENSES = [ // АДМ — постійні { id: "e1", cat: "rent", title: "Оренда офісу, Хорива 12", supplier: "ТОВ «Хорива Дев»", amount: 28000, monthlyAmount: 28000, periodicity: "monthly", vatRate: 20, taxInvoice: true, nextDate: "2026-06-01", note: "Договір №О-15/2024, до 31.12.2026" }, { id: "e2", cat: "utilities", title: "Комунальні (опалення, ел.енергія)", supplier: "Київенерго · Київгаз", amount: 4200, monthlyAmount: 4200, periodicity: "monthly", vatRate: 20, taxInvoice: true, nextDate: "2026-06-10" }, { id: "e3", cat: "comms", title: "Інтернет + хостинг", supplier: "Воля Бізнес + Hetzner", amount: 2800, monthlyAmount: 2800, periodicity: "monthly", vatRate: 20, taxInvoice: true, nextDate: "2026-06-05" }, { id: "e4", cat: "software", title: "Autodesk AutoCAD + Revit (5 ліц.)", supplier: "Autodesk", amount: 145000, monthlyAmount: 12083, periodicity: "yearly", vatRate: 0, taxInvoice: false, nextDate: "2026-08-15", note: "Нерезидент · послуги ІТ, вхідного ПДВ немає. 5 ліцензій × $580/міс" }, { id: "e5", cat: "software", title: "ЛІРА-САПР (3 ліцензії)", supplier: "ЛІРА", amount: 36000, monthlyAmount: 3000, periodicity: "yearly", vatRate: 20, taxInvoice: true, nextDate: "2026-09-01" }, { id: "e6", cat: "software", title: "АВК-5 (кошторисна, 2 ліц.)", supplier: "АВК-Прайм", amount: 24000, monthlyAmount: 2000, periodicity: "yearly", vatRate: 20, taxInvoice: true, nextDate: "2026-10-10" }, { id: "e7", cat: "software", title: "Google Workspace Business (5)", supplier: "Google", amount: 750, monthlyAmount: 750, periodicity: "monthly", vatRate: 0, taxInvoice: false, nextDate: "2026-06-02", note: "Нерезидент (Google Ireland) — вхідного ПДВ немає" }, { id: "e8", cat: "supplies", title: "Канцелярія, картриджі", supplier: "Епіцентр", amount: 1800, monthlyAmount: 1800, periodicity: "monthly", vatRate: 20, taxInvoice: true, nextDate: "2026-06-20" }, { id: "e9", cat: "bank", title: "Банківські комісії (monobank)", supplier: "monobank", amount: 1200, monthlyAmount: 1200, periodicity: "monthly", vatRate: 0, taxInvoice: false, nextDate: "2026-05-31", note: "Банківські послуги звільнені від ПДВ" }, { id: "e10", cat: "marketing", title: "Реклама в Google Ads + сайт", supplier: "Google + UAhost", amount: 5000, monthlyAmount: 5000, periodicity: "monthly", vatRate: 20, taxInvoice: false, nextDate: "2026-06-15", note: "Накладна від UAhost ще не зареєстрована" }, // АДМ — разові { id: "e11", cat: "equipment", title: "Принтер HP Color LaserJet", supplier: "Rozetka", amount: 32000, monthlyAmount: 0, periodicity: "once", vatRate: 20, taxInvoice: true, nextDate: "2026-04-12", paid: true, paidDate: "2026-04-12", note: "Списано на квітень" }, // ЗВВ — субпідряд (прив'язано до об'єктів) { id: "e12", cat: "subcontract", title: "Геологія по УКП-2025-47", supplier: "ТОВ «Інженерна геологія»", obj: "ukp-47", amount: 48000, monthlyAmount: 0, periodicity: "once", vatRate: 20, taxInvoice: true, nextDate: "2026-04-20", paid: true, paidDate: "2026-04-25" }, { id: "e13", cat: "expertise", title: "Експертиза УКП-2024-89", supplier: "Київекспертиза", obj: "ukp-89", amount: 35000, monthlyAmount: 0, periodicity: "once", vatRate: 20, taxInvoice: true, nextDate: "2025-08-10", paid: true, paidDate: "2025-08-15" }, { id: "e14", cat: "subcontract", title: "Геодезичні роботи по УКП-2025-32", supplier: "ФОП Іваненко О.", obj: "ukp-32", amount: 18000, monthlyAmount: 0, periodicity: "once", vatRate: 0, taxInvoice: false, nextDate: "2026-06-10", note: "ФОП на єдиному податку — без ПДВ" }, // ЗВВ — поточні { id: "e15", cat: "fuel", title: "Пальне для виїздів", supplier: "WOG", amount: 6000, monthlyAmount: 6000, periodicity: "monthly", vatRate: 20, taxInvoice: true, nextDate: "2026-06-01" }, { id: "e16", cat: "delivery", title: "Виїзди на об'єкти (відрядження)", supplier: "—", amount: 3500, monthlyAmount: 3500, periodicity: "monthly", vatRate: 0, taxInvoice: false, nextDate: "2026-06-01", note: "Добові та компенсації — поза об'єктом оподаткування ПДВ" }, { id: "e17", cat: "training", title: "Курс «Реновація 2026»", supplier: "АСЛО", amount: 8500, monthlyAmount: 0, periodicity: "once", vatRate: 0, taxInvoice: false, nextDate: "2026-06-25", note: "Освітні послуги звільнені від ПДВ" }, ]; window.DATA.EXPENSES = EXPENSES; // Розклад витрати на нетто / ПДВ. amount вважаємо сумою З ПДВ (брутто). window.expenseVat = function (e) { const rate = e.vatRate || 0; const gross = e.amount || 0; const vatable = rate > 0; const net = vatable ? Math.round(gross / (1 + rate / 100)) : gross; const vat = gross - net; return { rate, vatable, hasInvoice: !!e.taxInvoice, gross, net, vat, // вхідний ПДВ іде в кредит лише за наявності зареєстрованої податкової накладної creditable: vatable && !!e.taxInvoice ? vat : 0 }; }; // Місячна сума вхідного ПДВ (run-rate; разові не входять) — для оглядів і P&L window.expenseVatMonthly = function (e) { const rate = e.vatRate || 0; if (!rate || !(e.monthlyAmount > 0)) return 0; const net = Math.round(e.monthlyAmount / (1 + rate / 100)); return { vat: e.monthlyAmount - net, creditable: e.taxInvoice ? e.monthlyAmount - net : 0 }; }; // Річний вхідний ПДВ-кредит із фактичних витрат (а не оцінка база/6): // регулярні → monthlyAmount×12, разові цього року → повна сума. Лише з податковою накладною. window.inputVatAnnual = function () { return (window.DATA.EXPENSES || []).reduce((s, e) => { const v = window.expenseVat(e); if (!v.creditable) return s; if (e.monthlyAmount > 0) return s + Math.round(v.creditable / e.amount * e.monthlyAmount) * 12; return s + v.creditable; // разові }, 0); }; // ============ РОЗПОДІЛ ВИТРАТ НА ОБ'ЄКТИ ============ // Налаштування: коефіцієнти АДМ і ЗВВ як % надбавка до прямих витрат const OVERHEAD_RATES = { admPct: 22, // 22% — типово для проєктних організацій згідно настанови zvvPct: 15, // 15% — додатково на ЗВВ profitPct: 18, // нормативний прибуток allocationMethod: "contract_volume", // contract_volume | hours | equal }; window.OVERHEAD_RATES = OVERHEAD_RATES; // Загальна сума АДМ-витрат за місяць (для розподілу) window.totalMonthlyByType = (type) => { return EXPENSES .filter(e => window.getCategory(e.cat).type === type) .reduce((sum, e) => sum + (e.monthlyAmount || 0), 0); }; // ============ РАХУНКИ І КАСА ============ const CASH_ACCOUNTS = [ { id: "main", label: "Основний рахунок", bank: "monobank (бізнес)", iban: "UA29 3220 0100 0026 0031 2345 6789", balance: 1240000, currency: "UAH", primary: true }, { id: "usd", label: "Валютний рахунок", bank: "monobank", iban: "UA29 3220 0100 0026 0099 1234 5678", balance: 8400, currency: "USD" }, { id: "card", label: "Корпоративна картка",bank: "monobank", iban: "UA73 3220 0100 0000 0026 0099 5512", balance: 38000, currency: "UAH" }, { id: "cash", label: "Готівка (каса)", bank: "—", iban: "", balance: 12500, currency: "UAH" }, ]; window.DATA.CASH_ACCOUNTS = CASH_ACCOUNTS; // Останні операції (з рахунку — приклади) const CASH_OPS = [ { id: "o1", date: "2026-05-26", type: "out", category: "salary", amount: -162524, account: "main", note: "ЗП за квітень · Кравченко О." }, { id: "o2", date: "2026-05-25", type: "out", category: "salary", amount: -135000, account: "main", note: "ЗП за квітень · Петренко А." }, { id: "o3", date: "2026-05-24", type: "out", category: "salary", amount: -126000, account: "main", note: "ЗП за квітень · Мельник Т." }, { id: "o4", date: "2026-05-22", type: "in", category: "client_pay", amount: 425000, account: "main", note: "Аванс ескізний · ПП Лісове · УКП-2025-51" }, { id: "o5", date: "2026-05-20", type: "out", category: "rent", amount: -28000, account: "main", note: "Оренда офісу · Хорива 12" }, { id: "o6", date: "2026-05-15", type: "out", category: "software", amount: -750, account: "card", note: "Google Workspace" }, { id: "o7", date: "2026-05-12", type: "in", category: "client_pay", amount: 1680000, account: "main", note: "Аванс · ТОВ Подільський Девелопмент · УКП-2025-47" }, { id: "o8", date: "2026-05-10", type: "out", category: "utilities", amount: -4200, account: "main", note: "Комунальні послуги" }, { id: "o9", date: "2026-05-08", type: "out", category: "expertise", amount: -35000, account: "main", note: "Київекспертиза · УКП-2024-89" }, { id: "o10", date: "2026-05-05", type: "out", category: "fuel", amount: -6000, account: "card", note: "Пальне для виїздів" }, ]; window.DATA.CASH_OPS = CASH_OPS; // ============ ГОТІВКОВА КАСА (неофіційний управлінський контур) ============ // ⚠ Поза офіційним обліком. Частина підрядників/замовників розраховується готівкою; // з цих коштів виплачується готівкова частина ЗП («у конверті») та дрібні витрати. // Доступ — лише director / accountant + перемикач видимості готівки. Фіксуємо тут, // щоб гроші не «губилися» і було видно реальний залишок. // dir: "in" (надходження) | "out" (видаток) // kind: client | contractor | other (in) · salary | supplier | cph | other (out) const CASH_LEDGER = { opening: 35000, // залишок готівки на 01.04.2026 openingDate: "2026-04-01", ops: [ { id: "c1", date: "2026-04-03", dir: "in", kind: "client", amount: 60000, counterparty: "Замовник котеджу · Лісники", note: "Готівка за робочий проєкт (приватний)" }, { id: "c2", date: "2026-04-10", dir: "out", kind: "salary", amount: 42000, counterparty: "ЗП «у конверті» · березень", note: "Петренко 20 000 · Мельник 15 000 · Савчук 7 000" }, { id: "c3", date: "2026-04-18", dir: "in", kind: "contractor", amount: 25000, counterparty: "Бригада оздоблення", note: "Розрахунок за координацію" }, { id: "c4", date: "2026-04-22", dir: "out", kind: "supplier", amount: 8000, counterparty: "Матеріали (ринок)", note: "Без документів" }, { id: "c5", date: "2026-04-29", dir: "in", kind: "client", amount: 40000, counterparty: "Приватний замовник · аванс", note: "Готівковий аванс" }, { id: "c6", date: "2026-05-05", dir: "out", kind: "salary", amount: 42000, counterparty: "ЗП «у конверті» · квітень", note: "Петренко 20 000 · Мельник 15 000 · Савчук 7 000" }, { id: "c7", date: "2026-05-12", dir: "in", kind: "client", amount: 55000, counterparty: "Замовник реконструкції", note: "Готівка за етап" }, { id: "c8", date: "2026-05-15", dir: "out", kind: "cph", amount: 12000, counterparty: "Електрик (ЦПХ) · готівкою", note: "Поза офіційним договором ЦПХ" }, { id: "c9", date: "2026-05-20", dir: "in", kind: "contractor", amount: 30000, counterparty: "Субпідрядник · повернення", note: "Залишок невикористаного авансу" }, { id: "c10", date: "2026-05-26", dir: "out", kind: "supplier", amount: 6000, counterparty: "Дрібні витрати готівкою", note: "Канцелярія, дрібний інструмент" }, ], }; window.DATA.CASH_LEDGER = CASH_LEDGER; // Довідники готівкової каси window.CASH_LEDGER_KINDS = { client: { label: "Готівка від замовника", dir: "in", icon: "💵" }, contractor: { label: "Від субпідрядника", dir: "in", icon: "🤝" }, salary: { label: "ЗП «у конверті»", dir: "out", icon: "✉️" }, supplier: { label: "Постачальнику готівкою", dir: "out", icon: "📦" }, cph: { label: "Субпідряднику (ЦПХ)", dir: "out", icon: "🔧" }, other: { label: "Інше", dir: "both", icon: "•" }, }; // Поточний залишок готівки (opening + усі операції) window.cashLedgerBalance = function () { const L = window.DATA.CASH_LEDGER; return L.ops.reduce((s, o) => s + (o.dir === "in" ? o.amount : -o.amount), L.opening); }; // ============ ЛІДИ (потенційні замовники) ============ // Дві колії: direct (воронка продажів, Варіант Б) і tender (дзеркало Prozorro/zakupivli.pro). // Поле stage — ключ зі LEAD_STAGES; для тендерів додаються externalId/platform/sync/ourBid. const LEADS = [ // ——— ПРЯМІ ЛІДИ · Звернення → Прорахунок/КП → Перемовини → Договір ——— { id: "L1", track: "direct", stage: "negotiation", companyId: "ubp", name: "Реконструкція фасаду ЖК «Подільський»", client: "ТОВ «Подільський Девелопмент»", contact: "Михайло Тарасенко · +380 67 555 12 34 · m.tarasenko@podilsky.ua", type: "private", grade: "СС2", estimatedValue: 340000, probability: 75, source: "Повторний замовник", nextAction: "Узгодити правки до договору, підписати до 12 червня", expectedClose: "2026-06-15", addedDate: "2026-04-22", owner: "ok", notes: "Повторний замовник, бюджет підтверджено. Лишилось узгодити аванс." }, { id: "L2", track: "direct", stage: "estimate", companyId: "ubp", name: "Котедж «Сосновий», 2 будинки", client: "Іван Левчук (фіз. особа)", contact: "+380 50 123 99 88 · ivan.levchuk@gmail.com", type: "individual", grade: "СС1", estimatedValue: 180000, probability: 55, source: "Рекомендація", nextAction: "Надіслати КП за результатом виїзду на ділянку", expectedClose: "2026-07-01", addedDate: "2026-05-15", owner: "tm", notes: "Прийшов за порадою попереднього замовника. Чекає прорахунок." }, { id: "L3", track: "direct", stage: "inquiry", companyId: "ubp", name: "Адмінбудівля, 2-й корпус", client: "Міністерство юстиції України", contact: "Наталія Корчинська · natalia.k@minjust.gov.ua", type: "state", grade: "СС2", estimatedValue: 260000, probability: 45, source: "Повторний замовник", nextAction: "Зустріч 11 червня — уточнити обсяг розділів", expectedClose: "2026-07-10", addedDate: "2026-05-28", owner: "ok", notes: "Розширення співпраці. Замовник перевірений." }, { id: "L4", track: "direct", stage: "estimate", companyId: "ubp", name: "Розділ КМ для виробничого корпусу", client: "БК «Альтіс-Буд» (субпідряд)", contact: "Сергій Іваненко · +380 63 240 77 10", type: "private", grade: "СС2", estimatedValue: 210000, probability: 60, source: "Партнер (будівельна компанія)", nextAction: "Узгодити обсяг розділу й строки", expectedClose: "2026-06-25", addedDate: "2026-05-20", owner: "tm", notes: "Партнерська будкомпанія віддає суміжний розділ КМ на субпідряд." }, { id: "L5", track: "direct", stage: "contract", companyId: "ubp", name: "Складський комплекс, стадія П", client: "ТОВ «Логістик Плюс»", contact: "Олег Гнатюк · +380 67 110 22 33", type: "private", grade: "СС2", estimatedValue: 300000, probability: 95, source: "Партнер (будівельна компанія)", nextAction: "Підписання договору призначено на 9 червня", expectedClose: "2026-06-09", addedDate: "2026-04-30", owner: "ok", notes: "Договір узгоджено, чекаємо підпис і аванс." }, { id: "L6", track: "direct", stage: "declined", companyId: "ubp", name: "Інтер'єр салону краси", client: "ФОП Кравчук О.", contact: "+380 50 909 11 22", type: "individual", grade: "СС1", estimatedValue: 90000, probability: 0, source: "Рекомендація", nextAction: "—", expectedClose: "2026-05-20", addedDate: "2026-04-18", owner: "tm", closedDate: "2026-05-22", closeReason: "Обрали дешевшого виконавця (фрилансер). Бюджет не зійшовся.", notes: "" }, // ——— ТЕНДЕРИ · дзеркало статусу Prozorro через zakupivli.pro (синхронізується) ——— { id: "T1", track: "tender", stage: "auction", companyId: "ubp", name: "Реконструкція ДНЗ №47 (ПКД)", client: "Управління освіти м. Київ", contact: "Кабінет zakupivli.pro · UA-2026-05-21-007842", type: "state_tender", grade: "СС2", estimatedValue: 480000, probability: 40, source: "Prozorro", platform: "zakupivli.pro", externalId: "UA-2026-05-21-007842", synced: "2026-06-07 09:12", ourBid: 455000, competitorsCount: 5, priceRank: 2, awaitingReview: true, nextAction: "Аукціон завершено. Дешевший учасник на розгляді — чекаємо рішення замовника", expectedClose: "2026-06-18", addedDate: "2026-05-21", owner: "ok", notes: "Ми другі за ціною. Якщо першого відхилять за невідповідністю — піднімаємось на кваліфікацію." }, { id: "T2", track: "tender", stage: "monitoring", companyId: "ubp", name: "Капремонт покрівлі ЗОШ №112", client: "Управління освіти м. Київ", contact: "Кабінет zakupivli.pro · UA-2026-06-02-004417", type: "state_tender", grade: "СС1", estimatedValue: 160000, probability: 20, source: "Prozorro", platform: "zakupivli.pro", externalId: "UA-2026-06-02-004417", synced: "2026-06-07 09:12", nextAction: "Оцінити доцільність участі до 10 червня", expectedClose: "2026-06-28", addedDate: "2026-06-02", owner: "ok", notes: "З'явилось у підписці майданчика. Вирішуємо, чи беремось." }, { id: "T3", track: "tender", stage: "lost", companyId: "ubp", name: "Капремонт спортзалу школи №87", client: "Управління освіти м. Київ", contact: "Кабінет zakupivli.pro · UA-2026-04-15-000234", type: "state_tender", grade: "СС1", estimatedValue: 140000, probability: 0, source: "Prozorro", platform: "zakupivli.pro", externalId: "UA-2026-04-15-000234", synced: "2026-05-02 14:30", ourBid: 140000, competitorsCount: 6, priceRank: 3, nextAction: "—", expectedClose: "2026-05-01", addedDate: "2026-04-15", owner: "ok", closedDate: "2026-05-02", closeReason: "Переможець визначений — конкурент на 18% дешевший пройшов кваліфікацію.", notes: "" }, ]; window.DATA.LEADS = LEADS; // Колії воронки const LEAD_TRACKS = { direct: { label: "Прямі ліди", sub: "Воронка продажів", icon: "handshake" }, tender: { label: "Тендери", sub: "Prozorro · zakupivli.pro", icon: "target" }, }; window.LEAD_TRACKS = LEAD_TRACKS; // Стадії. won — цільова (договір); terminal+lost — закрито без угоди. const LEAD_STAGES = { // прямі (Варіант Б) inquiry: { track: "direct", order: 1, label: "Звернення", tone: "neutral" }, estimate: { track: "direct", order: 2, label: "Прорахунок / КП", tone: "blue" }, negotiation: { track: "direct", order: 3, label: "Перемовини", tone: "amber" }, contract: { track: "direct", order: 4, label: "Договір", tone: "live", won: true }, declined: { track: "direct", order: 5, label: "Відмова", tone: "warn", terminal: true, lost: true }, // тендери (дзеркало Prozorro) monitoring: { track: "tender", order: 1, label: "Моніторинг", tone: "neutral" }, preparing: { track: "tender", order: 2, label: "Підготовка пропозиції", tone: "blue" }, auction: { track: "tender", order: 3, label: "Участь в аукціоні", tone: "amber" }, qualification: { track: "tender", order: 4, label: "Кваліфікація", tone: "blue" }, awarded: { track: "tender", order: 5, label: "Договір", tone: "live", won: true }, lost: { track: "tender", order: 6, label: "Програли", tone: "warn", terminal: true, lost: true }, rejected: { track: "tender", order: 7, label: "Відхилили", tone: "warn", terminal: true, lost: true }, }; window.LEAD_STAGES = LEAD_STAGES; window.leadStage = (l) => { const s = LEAD_STAGES[l.stage] || { label: l.stage, track: l.track, tone: "neutral", order: 99 }; return { key: l.stage, ...s }; }; window.leadStagesFor = (track) => Object.keys(LEAD_STAGES).filter(k => LEAD_STAGES[k].track === track) .map(k => ({ key: k, ...LEAD_STAGES[k] })).sort((a, b) => a.order - b.order); // Джерела під реальність компанії (сарафан/повторні/партнери; Prozorro рідко) const LEAD_SOURCES = ["Рекомендація", "Повторний замовник", "Партнер (будівельна компанія)", "Prozorro", "Інше"]; window.LEAD_SOURCES = LEAD_SOURCES; const LEAD_TYPE = { private: "Приватний замовник", state: "Державний", state_tender: "Тендер", individual: "Фіз. особа", }; window.LEAD_TYPE = LEAD_TYPE; // ============ КОМПАНІЇ (шов під майбутній другий напрям) ============ // Поки компанія одна. companyId проставляється фінансовим/об'єктним записам, щоб // згодом (будівельний напрям з партнером) розділити контури без переробки структури. const COMPANIES = [ { id: "ubp", name: "ТОВ «УКРБУДПРОЄКТ»", short: "Проєктування", kind: "design", taxSystem: "Загальна · платник ПДВ", active: true }, { id: "bud", name: "Будівельний напрям", short: "Будівництво", kind: "construction", taxSystem: "—", active: false, planned: true, note: "Спільний проєкт із партнером. Переважно готівка. Активуємо після оформлення юрособи.", // Орієнтовний штат напряму (50+ людей) — ролі на майбутнє, поки не реалізуємо futureRoles: [ "Головний інженер", "Менеджер із закупівель (будівельні матеріали)", "Виконавець робіт (прораб)", "Інженер виробничо-технічного відділу (ВТВ)", "Інженер з охорони праці (+ служба ОП)", "Відділ кадрів (імовірно)", ] }, ]; window.DATA.COMPANIES = COMPANIES; window.DEFAULT_COMPANY = "ubp"; window.getCompany = (id) => COMPANIES.find(c => c.id === id) || COMPANIES[0]; // Проставляємо companyId за замовчуванням усім записам, де його ще немає (шов). ["OBJECTS", "LEADS", "CONTRACTS", "INVOICES", "EXPENSES", "CASH_ACCOUNTS", "CASH_OPS"].forEach(k => { (window.DATA[k] || []).forEach(r => { if (!r.companyId) r.companyId = window.DEFAULT_COMPANY; }); }); // ============ РОЛІ ТА ПРАВА ============ const ROLES = [ { id: "director", label: "Директор", icon: "👔", color: "var(--accent)", description: "Повний доступ до всіх модулів і даних", members: ["ok"], permissions: { read: "*", write: "*", delete: "*", export: "*" }, }, { id: "accountant", label: "Бухгалтер", icon: "🧮", color: "var(--c-blue)", description: "Фінанси, зарплата, документообіг", members: [], permissions: { read: ["finance", "salary", "hr_documents", "reports"], write: ["finance", "salary", "hr_documents"], delete: ["finance"], export: ["finance", "salary", "reports"], }, }, { id: "gip", label: "Головний інженер проєкту", icon: "👷", color: "var(--c-green-deep)", description: "Управління командою, об'єктами, задачами; ЗП тільки своя", members: [], permissions: { read: ["work", "objects", "hr_team", "own_salary"], write: ["work", "objects", "hr_team"], delete: ["work_tasks"], export: ["work", "objects"], }, }, { id: "deputy", label: "Заступник директора", icon: "🎖", color: "var(--c-green-deep)", description: "Повноваження як у ГІП: команда, об'єкти, задачі; ЗП тільки своя", members: [], permissions: { read: ["work", "objects", "hr_team", "own_salary"], write: ["work", "objects", "hr_team"], delete: ["work_tasks"], export: ["work", "objects"], }, }, { id: "engineer", label: "Інженер / Архітектор", icon: "📐", color: "var(--c-mint)", description: "Свої задачі і об'єкти, обмежений доступ", members: ["ap", "tm", "vs"], permissions: { read: ["own_tasks", "own_objects", "own_salary"], write: ["own_tasks"], delete: [], export: [], }, }, { id: "contractor", label: "Субпідрядник", icon: "🤝", color: "var(--ink-3)", description: "Тільки свої задачі і виплати", members: ["dl"], permissions: { read: ["own_tasks"], write: ["own_tasks"], delete: [], export: [], }, }, ]; window.DATA.ROLES = ROLES; // Каталог дозволів для відображення const PERMISSIONS_CATALOG = [ { group: "Робота", id: "work", items: [ { id: "today", label: "Сьогодні / Тиждень" }, { id: "objects", label: "Об'єкти" }, { id: "admin", label: "Адміністрація" }, ]}, { group: "Продажі і клієнти", id: "sales", items: [ { id: "leads", label: "Ліди і тендери" }, { id: "clients", label: "Клієнти" }, { id: "contracts", label: "Договори" }, ]}, { group: "Фінанси", id: "finance", items: [ { id: "receivables", label: "Рахунки і дебіторка" }, { id: "expenses", label: "Витрати" }, { id: "budgets", label: "Бюджети об'єктів" }, { id: "cashflow", label: "Cash flow" }, { id: "estimates", label: "Кошториси" }, ]}, { group: "Персонал", id: "hr", items: [ { id: "profiles", label: "Профілі команди" }, { id: "time", label: "Табель" }, { id: "vacation", label: "Відпустки" }, { id: "documents", label: "Документи" }, { id: "orders", label: "Накази" }, ]}, { group: "Зарплата", id: "salary", items: [ { id: "payroll", label: "Розрахунок" }, { id: "registry", label: "Реєстр виплат" }, { id: "all_salary", label: "Зарплати всіх" }, { id: "own_salary", label: "Тільки своя ЗП" }, ]}, { group: "Звіти", id: "reports", items: [ { id: "internal_reports", label: "Внутрішні" }, ]}, { group: "Адмін", id: "admin", items: [ { id: "roles", label: "Ролі і права" }, { id: "company", label: "Налаштування компанії" }, { id: "audit", label: "Журнал аудиту" }, ]}, ]; window.PERMISSIONS_CATALOG = PERMISSIONS_CATALOG; // ============ ПІДРЯДНИКИ ============ const SUPPLIERS = [ { id: "s1", name: "ТОВ «Інженерна геологія»", category: "geology", contact: "Сергій Бондаренко · +380 67 555 23 45 · s.bondarenko@geology.ua", address: "Київ, вул. Кіото 15", edrpou: "32147589", rating: 5, since: "2023-04-12", completedProjects: 8, activeProjects: 1, totalPaid: 384000, avgCost: 48000, specialties: ["Інженерно-геологічні вишукування", "Звіти ІГВ", "Гідрогеологія"], note: "Перевірений підрядник, працюємо з 2023. Завжди в дедлайні." }, { id: "s2", name: "Київекспертиза", category: "expertise", contact: "Тетяна Швед · +380 44 222 11 33 · contact@kyivexp.com.ua", address: "Київ, бул. Шевченка 50", edrpou: "00012543", rating: 4, since: "2022-01-15", completedProjects: 12, activeProjects: 2, totalPaid: 540000, avgCost: 45000, specialties: ["Комплексна експертиза", "Експертиза кошторисної частини", "СС2 СС3"], note: "Бюрократія, але якість висока. Затримки на тиждень-два." }, { id: "s3", name: "ФОП Іваненко О.І.", category: "geodesy", contact: "+380 50 887 11 22 · ivanenko.geo@gmail.com", address: "Київська обл., с. Чубинське", edrpou: "2987654321", rating: 5, since: "2024-02-20", completedProjects: 6, activeProjects: 1, totalPaid: 108000, avgCost: 18000, specialties: ["Геодезичні роботи", "Топозйомка", "Виставлення осей"], note: "Швидкий, точний, без проблем з документацією." }, { id: "s4", name: "ТОВ «Архітектурні рішення»", category: "subcontractor", contact: "Михайло Климчук · +380 67 333 44 55 · arh.solutions@gmail.com", address: "Київ, вул. Драгомирова 22", edrpou: "41789632", rating: 4, since: "2023-09-01", completedProjects: 4, activeProjects: 1, totalPaid: 280000, avgCost: 70000, specialties: ["Архітектурні рішення", "АР для тендерів", "Допомога з 3D"], note: "Допомагають коли наші архітектори перевантажені." }, { id: "s5", name: "ТОВ «ЕнергоПроект»", category: "subcontractor", contact: "Андрій Левчук · +380 67 666 77 88 · contact@energo-project.ua", address: "Київ, пр. Перемоги 95", edrpou: "39875421", rating: 3, since: "2024-08-10", completedProjects: 2, activeProjects: 0, totalPaid: 95000, avgCost: 47500, specialties: ["Розділ ЕО", "Зовнішнє електропостачання", "Системи безпеки"], note: "Якість середня, ціна нижче ринку. Беремо коли термінових витрат немає." }, { id: "s6", name: "ФОП Левченко Д.І.", category: "individual", contact: "+380 67 567 89 01 · d.levchenko@ukrbudproiekt.ua", address: "Київ, вул. Богатирська 6", edrpou: "3456789012", rating: 5, since: "2024-03-01", completedProjects: 5, activeProjects: 1, totalPaid: 168000, avgCost: 33600, specialties: ["Електрика", "Розділ ЕО", "Слабкострумові"], note: "Наш постійний субпідрядник. Реєстрований у системі як ЦПХ." }, ]; window.DATA.SUPPLIERS = SUPPLIERS; const SUPPLIER_CATEGORIES = { geology: { label: "Геологія", icon: "🪨", color: "#8b6f47" }, geodesy: { label: "Геодезія", icon: "📐", color: "#3c6287" }, expertise: { label: "Експертиза", icon: "📋", color: "#a87038" }, subcontractor: { label: "Субпідряд", icon: "🤝", color: "#068b49" }, individual: { label: "ФОП-фахівець", icon: "👤", color: "#6b7c93" }, edessb: { label: "ЄДЕССБ-реєстр.", icon: "🏛", color: "#5a8aa8" }, }; window.SUPPLIER_CATEGORIES = SUPPLIER_CATEGORIES; // ============ KPI ТА 1:1 ============ // Кожен співробітник має KPI цілі на квартал const KPI_GOALS = { ok: [ { id: "k1", title: "Виторг компанії за Q2", target: 12000000, current: 5440000, unit: "₴", trend: "on_track" }, { id: "k2", title: "Маржа об'єктів", target: 50, current: 59, unit: "%", trend: "exceeding" }, { id: "k3", title: "Закрито лідів", target: 8, current: 3, unit: "", trend: "behind" }, { id: "k4", title: "Команда без перевантажень", target: 90, current: 85, unit: "%", trend: "at_risk" }, ], ap: [ { id: "k1", title: "Видано розділів КЖ", target: 6, current: 5, unit: "", trend: "on_track" }, { id: "k2", title: "Своєчасність здачі", target: 95, current: 87, unit: "%", trend: "at_risk" }, { id: "k3", title: "Внутрішні зауваження", target: 5, current: 8, unit: " на проєкт", trend: "behind", inverse: true }, ], tm: [ { id: "k1", title: "Архітектурних рішень", target: 8, current: 6, unit: "", trend: "on_track" }, { id: "k2", title: "Узгоджень із замовником", target: 100, current: 100, unit: "%", trend: "exceeding" }, ], vs: [ { id: "k1", title: "Розділів ОВ/ВК", target: 4, current: 3, unit: "", trend: "on_track" }, { id: "k2", title: "Своєчасність", target: 90, current: 92, unit: "%", trend: "exceeding" }, ], dl: [ { id: "k1", title: "Розділів ЕО", target: 3, current: 2, unit: "", trend: "on_track" }, ], }; window.KPI_GOALS = KPI_GOALS; const KPI_TREND_LABELS = { exceeding: { label: "Перевиконує", tone: "live" }, on_track: { label: "У графіку", tone: "live" }, at_risk: { label: "Ризик", tone: "warn" }, behind: { label: "Відстає", tone: "warn" }, }; window.KPI_TREND_LABELS = KPI_TREND_LABELS; // 1:1 РОЗМОВИ const ONE_ON_ONES = [ { id: "o1", who: "ap", date: "2026-05-20", duration: 45, status: "done", topics: ["Q2 KPIs review", "Проблема зі своєчасністю", "Запит на навчання"], notes: "Андрій просив додатковий курс ЛІРА. План — записати на липень. Своєчасність обговорили — дамо +20% буфер у плануванні.", nextSteps: ["Записати на курс ЛІРА (до 15.06)", "Обговорити з командою бажане навантаження"] }, { id: "o2", who: "tm", date: "2026-05-15", duration: 30, status: "done", topics: ["Завантаження", "Кар'єрні цілі"], notes: "Тарас задоволений, хоче більше зацікавлених архітектурних проєктів. Розглянемо передати йому ЖК «Подільський» фасадну частину повністю.", nextSteps: ["Передати фасадну частину УКП-47 повністю"] }, { id: "o3", who: "vs", date: "2026-06-02", duration: 30, status: "scheduled", topics: ["Перехід на повну ставку?", "Q2 results"], notes: "", nextSteps: [] }, { id: "o4", who: "ap", date: "2026-06-15", duration: 30, status: "scheduled", topics: ["Прогрес з KPI", "Курс ЛІРА — статус"], notes: "", nextSteps: [] }, ]; window.ONE_ON_ONES = ONE_ON_ONES; // ============ ШАБЛОНИ ЗАДАЧ ============ const TASK_TEMPLATES = [ { id: "tt1", name: "Школа (новобудова, СС2)", icon: "🏫", category: "object_type", description: "Стандартний набір задач на проєктування школи з нуля", objectGrade: "СС2", expertiseScope: "full", estimatedDays: 90, stages: [ { stage: "project", tasks: [ { title: "Геодезичні вишукування", est: "5 дн", section: "ВД" }, { title: "Геологічні вишукування", est: "10 дн", section: "ВД" }, { title: "Архітектурне рішення (АР)", est: "20 дн", section: "АР" }, { title: "Конструктивне рішення (КЖ)", est: "25 дн", section: "КЖ" }, { title: "Розділ ОВ", est: "10 дн", section: "ОВ" }, { title: "Розділ ВК", est: "8 дн", section: "ВК" }, { title: "Розділ ЕО", est: "10 дн", section: "ЕО" }, { title: "Слабкострумові", est: "5 дн", section: "СС" }, { title: "Розрахунок вентиляції харчоблоку", est: "3 дн", section: "ОВ" }, { title: "Пожежна безпека", est: "5 дн", section: "ПБ" }, ]}, { stage: "estimate", tasks: [ { title: "Збір цін на матеріали", est: "5 дн", section: "ТД" }, { title: "Кошторисна частина", est: "10 дн", section: "ТД" }, { title: "Зведений кошторис", est: "3 дн", section: "ТД" }, ]}, { stage: "expertise", tasks: [ { title: "Підготовка пакету до експертизи", est: "5 дн", section: "ГІП" }, { title: "Перша подача експертизи", est: "0 дн", section: "ГІП" }, { title: "Виправлення зауважень", est: "10 дн", section: "ГІП" }, { title: "Отримання експертного висновку", est: "0 дн", section: "ГІП" }, ]}, { stage: "edessb", tasks: [ { title: "Підготовка пакету до ЄДЕССБ", est: "2 дн", section: "ГІП" }, { title: "Подача в ЄДЕССБ", est: "0 дн", section: "ГІП" }, { title: "Отримання реєстраційної квитанції", est: "0 дн", section: "ГІП" }, ]}, ] }, { id: "tt2", name: "Житловий комплекс (5-9 поверхів, СС2)", icon: "🏢", category: "object_type", description: "ЖК середньої поверховості", objectGrade: "СС2", expertiseScope: "full", estimatedDays: 120, stages: [ { stage: "project", tasks: [ { title: "Геологічні вишукування", est: "12 дн", section: "ВД" }, { title: "Концепція ЖК", est: "10 дн", section: "АР" }, { title: "АР всіх блоків", est: "30 дн", section: "АР" }, { title: "КЖ всіх блоків", est: "35 дн", section: "КЖ" }, { title: "Інженерні мережі", est: "20 дн", section: "ОВ/ВК/ЕО" }, { title: "Благоустрій території", est: "10 дн", section: "АР" }, { title: "Дитячі майданчики", est: "5 дн", section: "АР" }, { title: "Паркінг", est: "10 дн", section: "АР" }, ]}, ] }, { id: "tt3", name: "Котедж (СС1, прості умови)", icon: "🏡", category: "object_type", description: "Приватний будинок, спрощена процедура (тільки експертиза кошторису)", objectGrade: "СС1", expertiseScope: "estimate", estimatedDays: 45, stages: [ { stage: "project", tasks: [ { title: "Архітектурне рішення", est: "8 дн", section: "АР" }, { title: "Конструктивні рішення", est: "10 дн", section: "КЖ" }, { title: "Інженерні мережі (ОВ/ВК)", est: "5 дн", section: "ОВ/ВК" }, { title: "Електрика і слаботструмові", est: "5 дн", section: "ЕО" }, ]}, ] }, { id: "tt4", name: "Реконструкція адмінбудівлі", icon: "🏛", category: "object_type", description: "Реконструкція з посиленням, без зміни функції", objectGrade: "СС2", expertiseScope: "full", estimatedDays: 75, stages: [ { stage: "project", tasks: [ { title: "Технічне обстеження конструкцій", est: "15 дн", section: "ТО" }, { title: "Архітектурно-планувальне рішення", est: "12 дн", section: "АР" }, { title: "Конструктивне підсилення", est: "20 дн", section: "КЖ" }, { title: "Заміна інженерних мереж", est: "15 дн", section: "ОВ/ВК/ЕО" }, ]}, ] }, { id: "tt5", name: "Авторський нагляд (щомісячний)", icon: "👷", category: "process", description: "Регулярний цикл задач АН для активного будівництва", estimatedDays: 30, stages: [ { stage: "supervision", tasks: [ { title: "Виїзд на майданчик", est: "0.5 дн", section: "АН" }, { title: "Перевірка вузлів виконання", est: "0.5 дн", section: "АН" }, { title: "Журнал АН", est: "0.3 дн", section: "АН" }, { title: "Фотофіксація", est: "0.2 дн", section: "АН" }, { title: "Звіт замовнику", est: "0.5 дн", section: "АН" }, ]}, ] }, { id: "tt6", name: "Подача в ЄДЕССБ", icon: "🏛", category: "process", description: "Підготовка і подача документації", estimatedDays: 7, stages: [ { stage: "edessb", tasks: [ { title: "Перевірка комплектності пакету", est: "1 дн", section: "ГІП" }, { title: "Підпис КЕП всіх авторів", est: "1 дн", section: "ГІП" }, { title: "Подача через кабінет ЄДЕССБ", est: "0.3 дн", section: "ГІП" }, { title: "Сплата збору", est: "0.2 дн", section: "ГІП" }, { title: "Отримання реєстраційного номера", est: "5 дн", section: "ГІП" }, ]}, ] }, ]; window.DATA.TASK_TEMPLATES = TASK_TEMPLATES; const TEMPLATE_CATEGORIES = { object_type: "Тип об'єкта", process: "Процес", }; window.TEMPLATE_CATEGORIES = TEMPLATE_CATEGORIES; // ============ ВИПЛАТИ ПІДРЯДНИКАМ ============ const SUB_PAYMENTS = [ { id: "sp1", supplier: "s1", obj: "ukp-47", amount: 48000, invoiceNumber: "ІГ-2026-104", invoiceDate: "2026-04-18", dueDate: "2026-05-02", paidDate: "2026-04-25", status: "paid", description: "Інженерно-геологічні вишукування · ЖК «Подільський»", contractType: "act_pdv", pdvIncluded: 8000, }, { id: "sp2", supplier: "s2", obj: "ukp-89", amount: 35000, invoiceNumber: "КЕ-2025-2241", invoiceDate: "2025-08-05", dueDate: "2025-08-19", paidDate: "2025-08-30", status: "paid", lateDays: 11, description: "Експертний висновок · УКП-2024-89 · комплексна", contractType: "act_pdv", pdvIncluded: 5833, }, { id: "sp3", supplier: "s6", obj: "ukp-51", amount: 33600, invoiceNumber: "ФОП-2026-12", invoiceDate: "2026-05-15", dueDate: "2026-05-29", status: "issued", description: "Розділ ЕО · УКП-2025-51 · «Соснові гаї»", contractType: "cph", pdvIncluded: 0, }, { id: "sp4", supplier: "s3", obj: "ukp-32", amount: 18000, invoiceNumber: "ГД-2026-67", invoiceDate: "2026-05-20", dueDate: "2026-06-03", status: "issued", description: "Геодезичні роботи · Школа №14", contractType: "act_no_pdv", pdvIncluded: 0, }, { id: "sp5", supplier: "s4", obj: "ukp-47", amount: 70000, invoiceNumber: "АР-2026-08", invoiceDate: "2026-05-08", dueDate: "2026-05-22", status: "overdue", lateDays: 4, description: "АР додаткові варіанти фасадів · УКП-2025-47", contractType: "act_pdv", pdvIncluded: 11667, }, { id: "sp6", supplier: "s1", obj: "ukp-32", amount: 52000, invoiceNumber: "ІГ-2025-098", invoiceDate: "2025-09-10", dueDate: "2025-09-24", paidDate: "2025-09-20", status: "paid", description: "Інженерно-геологічні вишукування · Школа №14", contractType: "act_pdv", pdvIncluded: 8667, }, ]; window.DATA.SUB_PAYMENTS = SUB_PAYMENTS; const CONTRACT_TYPE_LABELS = { act_pdv: { label: "Акт + ПДВ", short: "АКТ+ПДВ" }, act_no_pdv: { label: "Акт", short: "АКТ" }, cph: { label: "ЦПХ", short: "ЦПХ" }, }; window.CONTRACT_TYPE_LABELS = CONTRACT_TYPE_LABELS; // ============ АВАНСОВІ ЗВІТИ ============ const ADVANCE_REPORTS = [ { id: "a1", who: "ok", date: "2026-05-15", amount: 4200, status: "approved", approvedDate: "2026-05-16", title: "Відрядження в Одесу · виїзд на майданчик", items: [ { what: "Квитки потяг Київ-Одеса-Київ", amount: 1800 }, { what: "Готель «Чорне море» (2 ночі)", amount: 2000 }, { what: "Таксі від вокзалу", amount: 400 }, ] }, { id: "a2", who: "ap", date: "2026-05-22", amount: 1450, status: "pending", title: "Канцелярія + картриджі для офісу", items: [ { what: "Тонер HP CF410X", amount: 980 }, { what: "Папір А4 (10 пачок)", amount: 470 }, ] }, { id: "a3", who: "tm", date: "2026-05-08", amount: 850, status: "approved", approvedDate: "2026-05-09", title: "Виїзд УКП-2024-89 авт.нагляд", items: [ { what: "Пальне (250 км)", amount: 650 }, { what: "Платна дорога", amount: 200 }, ] }, { id: "a4", who: "vs", date: "2026-05-25", amount: 320, status: "rejected", approvedDate: "2026-05-26", title: "Обід під час зустрічі з замовником", rejectReason: "Не передбачено політикою компанії — потрібно узгодити заздалегідь", items: [ { what: "Ресторан «Веранда»", amount: 320 }, ] }, { id: "a5", who: "dl", date: "2026-04-30", amount: 1200, status: "approved", approvedDate: "2026-05-02", title: "Інструменти для електромонтажних розрахунків", items: [ { what: "Мультиметр Fluke", amount: 1200 }, ] }, ]; window.DATA.ADVANCE_REPORTS = ADVANCE_REPORTS; const ADVANCE_STATUS = { pending: { label: "На розгляді", tone: "neutral" }, approved: { label: "Затверджено", tone: "live" }, rejected: { label: "Відхилено", tone: "warn" }, }; window.ADVANCE_STATUS = ADVANCE_STATUS; // ============ ВАЛЮТНІ КУРСИ ============ const FX_RATES = { USD: 41.50, EUR: 44.80, asOf: "2026-05-26", source: "НБУ", }; window.FX_RATES = FX_RATES; // ============ ОНБОРДИНГ ============ const ONBOARDING_STEPS = [ { id: "ob1", category: "office", label: "Видати ключі від офісу", required: true }, { id: "ob2", category: "office", label: "Виділити робоче місце", required: true }, { id: "ob3", category: "office", label: "Видати ноутбук + монітор", required: true }, { id: "ob4", category: "office", label: "Налаштувати робочу пошту", required: true }, { id: "ob5", category: "office", label: "Видати корпоративний телефон", required: false }, { id: "ob6", category: "docs", label: "Оформити трудовий договір / ЦПХ", required: true }, { id: "ob7", category: "docs", label: "Підписати NDA", required: true }, { id: "ob8", category: "docs", label: "Зібрати копії паспорта + ІПН", required: true }, { id: "ob9", category: "docs", label: "Інструктаж з безпеки праці", required: true }, { id: "ob10", category: "access", label: "Видати доступ до ERP (роль + права)", required: true }, { id: "ob11", category: "access", label: "Підключити до Google Workspace", required: true }, { id: "ob12", category: "access", label: "Додати в Telegram-чат команди", required: true }, { id: "ob13", category: "access", label: "Видати ліцензії: Autodesk / ЛІРА / АВК", required: true }, { id: "ob14", category: "people", label: "Знайомство з командою (вступна зустріч)", required: true }, { id: "ob15", category: "people", label: "Призначити ментора на перший місяць", required: false }, { id: "ob16", category: "people", label: "1:1 з ГІП — обговорити ролі та очікування", required: true }, { id: "ob17", category: "people", label: "Дати огляд активних об'єктів", required: true }, { id: "ob18", category: "review", label: "Зустріч за тиждень — як влаштувалось", required: true }, { id: "ob19", category: "review", label: "Перший 1:1 за місяць — оцінка прогресу", required: true }, { id: "ob20", category: "review", label: "Випробувальний термін — фінальна оцінка (3 міс)", required: true }, ]; window.ONBOARDING_STEPS = ONBOARDING_STEPS; const ONBOARDING_CATEGORIES = { office: { label: "Офіс і обладнання", icon: "🖥" }, docs: { label: "Документи", icon: "📄" }, access: { label: "Доступи і ліцензії", icon: "🔑" }, people: { label: "Команда", icon: "👥" }, review: { label: "Перевірки", icon: "✅" }, }; window.ONBOARDING_CATEGORIES = ONBOARDING_CATEGORIES; // Активні онбординги const ACTIVE_ONBOARDINGS = [ { id: "on1", who: "Сергій Гончаренко", role: "Інженер-конструктор", startDate: "2026-05-19", mentor: "ap", progress: { ob1: true, ob2: true, ob3: true, ob4: true, ob6: true, ob7: true, ob8: true, ob9: false, ob10: true, ob11: true, ob12: true, ob14: true, ob15: true, ob17: false } }, { id: "on2", who: "Марія Іщенко", role: "Архітектор (інтерн)", startDate: "2026-05-26", mentor: "tm", progress: { ob1: true, ob2: true, ob4: true, ob6: true, ob7: true, ob8: true } }, ]; window.ACTIVE_ONBOARDINGS = ACTIVE_ONBOARDINGS; // ============ НАВЧАННЯ ============ const TRAININGS = [ { id: "tr1", title: "ЛІРА-САПР · поглиблений курс", provider: "ЛІРА Education", duration: "40 год / 5 днів", cost: 12000, who: "ap", date: "2026-07-15", status: "scheduled", type: "course", note: "Запит Андрія на 1:1 від 20.05" }, { id: "tr2", title: "ДБН 2024: Зміни і застосування", provider: "АСЛО", duration: "8 год / 1 день", cost: 2500, who: "ok", date: "2026-06-12", status: "scheduled", type: "seminar" }, { id: "tr3", title: "BIM-моделювання Revit", provider: "Autodesk", duration: "60 год / 6 тижнів онлайн", cost: 18000, who: "tm", date: "2026-04-10", status: "in_progress", type: "online", progress: 65 }, { id: "tr4", title: "Сертифікат відповідального виконавця", provider: "АСЛО", duration: "—", cost: 4800, who: "ap", date: "2026-09-01", status: "planned", type: "certification" }, { id: "tr5", title: "Кошторисна справа: АВК-5 для практиків", provider: "АВК-Прайм", duration: "24 год / 3 дні", cost: 6500, who: "ok", date: "2025-11-20", status: "completed", type: "course", completedDate: "2025-11-22", certificateUrl: "drive.google.com/cert" }, { id: "tr6", title: "Прозоро для проєктантів", provider: "Prozorro", duration: "4 год онлайн", cost: 0, who: "ok", date: "2025-09-10", status: "completed", type: "seminar", completedDate: "2025-09-10" }, ]; window.DATA.TRAININGS = TRAININGS; const TRAINING_STATUS = { planned: { label: "Заплановано", tone: "neutral" }, scheduled: { label: "Підтверджено", tone: "neutral" }, in_progress: { label: "Триває", tone: "live" }, completed: { label: "Завершено", tone: "live" }, cancelled: { label: "Скасовано", tone: "warn" }, }; window.TRAINING_STATUS = TRAINING_STATUS; const TRAINING_TYPE_LABELS = { course: { label: "Курс", icon: "📚" }, seminar: { label: "Семінар", icon: "🎤" }, online: { label: "Онлайн", icon: "💻" }, certification: { label: "Сертифікація", icon: "📜" }, }; window.TRAINING_TYPE_LABELS = TRAINING_TYPE_LABELS; // ============ АУДИТ ============ const AUDIT_LOG = [ { id: "au1", date: "2026-05-26T14:32", who: "ok", action: "edit", entity: "task", entityName: "Розрахунок плит перекриття 4-9 поверхів", details: "статус: todo → live" }, { id: "au2", date: "2026-05-26T14:18", who: "ap", action: "create", entity: "task", entityName: "Перевірка прогинів плит", details: "об'єкт: УКП-2025-47, призначено АП" }, { id: "au3", date: "2026-05-26T13:45", who: "ok", action: "approve",entity: "invoice", entityName: "Рахунок №47/03", details: "виставлено замовнику на 1 260 000 ₴" }, { id: "au4", date: "2026-05-26T11:20", who: "tm", action: "edit", entity: "object", entityName: "УКП-2025-32 Школа №14", details: "оновлено опис конструктивної частини" }, { id: "au5", date: "2026-05-26T10:05", who: "ok", action: "login", entity: "system", entityName: "Вхід у систему", details: "IP 178.137.42.18, Chrome 138, Київ" }, { id: "au6", date: "2026-05-25T18:42", who: "ok", action: "create", entity: "payment", entityName: "ЗП за квітень · Петренко А.", details: "135 000 ₴, виплачено через Приват24" }, { id: "au7", date: "2026-05-25T16:30", who: "ok", action: "edit", entity: "lead", entityName: "Реконструкція ДНЗ №47", details: "ймовірність: 30% → 35%" }, { id: "au8", date: "2026-05-25T14:15", who: "ap", action: "complete", entity: "task", entityName: "Видача КЖ Розділу 3", details: "статус: live → late (пропустив дедлайн)" }, { id: "au9", date: "2026-05-25T11:00", who: "ok", action: "delete", entity: "expense", entityName: "Витрата на каву для офісу", details: "сума: 850 ₴, причина: не передбачено політикою" }, { id: "au10", date: "2026-05-24T17:22", who: "vs", action: "create", entity: "vacation_request", entityName: "Заявка на відпустку 6-17 липня", details: "10 днів, очікує затвердження" }, { id: "au11", date: "2026-05-24T15:10", who: "ok", action: "approve", entity: "advance", entityName: "Авансовий звіт Кравченко О. · Одеса", details: "затверджено 4 200 ₴" }, { id: "au12", date: "2026-05-24T09:30", who: "ok", action: "edit", entity: "settings", entityName: "Налаштування ролей", details: "Інженер: додано доступ до Cash flow (read-only)" }, { id: "au13", date: "2026-05-23T16:45", who: "tm", action: "upload", entity: "file", entityName: "Фасади_v3.dwg", details: "завантажено в Drive, папка УКП-47 / 03-Проєкт" }, { id: "au14", date: "2026-05-23T11:18", who: "ok", action: "create", entity: "contract", entityName: "Договір №51/2025", details: "сума: 1 700 000 ₴, ПП «Лісове»" }, { id: "au15", date: "2026-05-22T18:00", who: "system", action: "auto", entity: "backup", entityName: "Автоматичне резервне копіювання", details: "успішно, розмір: 1.2 GB" }, ]; window.AUDIT_LOG = AUDIT_LOG; const AUDIT_ACTIONS = { create: { label: "Створено", color: "var(--c-green-deep)", icon: "+" }, edit: { label: "Редагуван.", color: "var(--c-blue)", icon: "✎" }, delete: { label: "Видалено", color: "var(--late)", icon: "−" }, approve: { label: "Затверд.", color: "var(--c-green-deep)", icon: "✓" }, complete: { label: "Завершено", color: "var(--c-green-deep)", icon: "✓" }, login: { label: "Вхід", color: "var(--ink-3)", icon: "→" }, upload: { label: "Завантаж.", color: "var(--c-blue)", icon: "↑" }, auto: { label: "Авто", color: "var(--ink-3)", icon: "⚙" }, }; window.AUDIT_ACTIONS = AUDIT_ACTIONS; const AUDIT_ENTITY_LABELS = { task: "Задача", object: "Об'єкт", invoice: "Рахунок", payment: "Виплата", lead: "Лід", contract: "Договір", file: "Файл", expense: "Витрата", vacation_request: "Відпустка", advance: "Авансовий звіт", settings: "Налаштування", system: "Система", backup: "Резерв", }; window.AUDIT_ENTITY_LABELS = AUDIT_ENTITY_LABELS; // ============ СПОВІЩЕННЯ ============ const NOTIFICATIONS = [ { id: "n1", time: "2026-05-26T14:32", kind: "late_task", important: true, title: "Прострочена задача", text: "«Видача КЖ Розділу 3 (фундаменти)» — на 1 день після дедлайну", actor: "system", link: "task:t3", read: false }, { id: "n2", time: "2026-05-26T14:00", kind: "overdue_invoice", important: true, title: "Прострочений рахунок", text: "Управління освіти м. Києва — рахунок №32/02 на 465 000 ₴, +26 днів", actor: "system", link: "invoice:i-32-02", read: false }, { id: "n3", time: "2026-05-26T13:18", kind: "vacation_request", important: false, title: "Нова заявка на відпустку", text: "Вікторія Савчук подала заявку 6–17 липня", actor: "vs", link: "vacation:v2", read: false }, { id: "n4", time: "2026-05-26T11:00", kind: "advance_request", important: false, title: "Авансовий звіт на затвердження", text: "Петренко А. — канцелярія + картриджі, 1 450 ₴", actor: "ap", link: "advance:a2", read: true }, { id: "n5", time: "2026-05-26T09:15", kind: "incoming_payment", important: false, title: "Надходження коштів", text: "+425 000 ₴ від ПП «Лісове» · аванс ескізного проєкту", actor: "system", link: "invoice:i-51-02", read: true }, { id: "n6", time: "2026-05-25T17:45", kind: "birthday", important: false, title: "Завтра день народження", text: "Тарас Мельник — 8 листопада (приклад: за тиждень нагадаємо)", actor: "system", link: "profile:tm", read: true }, { id: "n7", time: "2026-05-25T16:30", kind: "lead_followup", important: false, title: "Час повернутися до ліда", text: "ТРЦ «Лук'янівська-Плаза» — 18 днів без контакту", actor: "system", link: "lead:L5", read: true }, { id: "n8", time: "2026-05-25T10:00", kind: "expert_due", important: false, title: "Експертиза наближається", text: "УКП-2025-47 — здача експертизи через 17 днів, статус «у роботі»", actor: "system", link: "object:ukp-47", read: true }, ]; window.NOTIFICATIONS = NOTIFICATIONS; const NOTIF_KINDS = { late_task: { icon: "⚠️", label: "Прострочення" }, overdue_invoice: { icon: "💰", label: "Дебіторка" }, vacation_request: { icon: "🏖", label: "Відпустки" }, advance_request: { icon: "📋", label: "Аванс. звіти" }, incoming_payment: { icon: "💵", label: "Надходження" }, birthday: { icon: "🎂", label: "Дні народження" }, lead_followup: { icon: "🎯", label: "Ліди" }, expert_due: { icon: "📅", label: "Дедлайни" }, }; window.NOTIF_KINDS = NOTIF_KINDS; // Розподіл місячних АДМ/ЗВВ між активними об'єктами пропорційно сумі договорів window.allocateOverheadToObjects = () => { const activeContracts = window.DATA.CONTRACTS.filter(c => c.status === "active" || c.status === "supervision"); const totalContractsSum = activeContracts.reduce((s, c) => s + c.total, 0); const monthlyAdm = window.totalMonthlyByType("adm"); const monthlyZvv = window.totalMonthlyByType("zvv"); return activeContracts.map(c => { const share = c.total / totalContractsSum; return { contract: c, shareRatio: share, monthlyAdm: Math.round(monthlyAdm * share), monthlyZvv: Math.round(monthlyZvv * share), monthlyTotal: Math.round((monthlyAdm + monthlyZvv) * share), }; }); }; // Поточна дата для UI window.TODAY = { day: "26", dayName: "Вівторок", date: "26 травня 2026" }; window.WEEK_DAYS = [ { day: "25", short: "ПН", name: "Понеділок", isPast: true, isToday: false }, { day: "26", short: "ВТ", name: "Вівторок", isPast: false, isToday: true }, { day: "27", short: "СР", name: "Середа", isPast: false, isToday: false }, { day: "28", short: "ЧТ", name: "Четвер", isPast: false, isToday: false }, { day: "29", short: "ПТ", name: "П'ятниця", isPast: false, isToday: false }, ]; // Helper: яка стадія активна для об'єкта (повертає id) window.stagesForObject = (obj) => { // СС1 + спрощені форми експертизи (кошторис / МОС+кошторис) — пропускаємо повний експертний цикл if (obj.expertiseScope === "estimate" || obj.expertiseScope === "strength") { return ["project", "estimate", "expertise", "edessb", "correction", "supervision"]; } return ["project", "estimate", "expertise", "edessb", "correction", "supervision"]; }; // Helper: рахує задачі по етапу для об'єкта window.stageStats = (objId, stageId, tasks) => { const obj = window.getObject(objId); const list = tasks.filter(t => t.obj === objId && t.stage === stageId); const done = list.filter(t => t.status === "done").length; const total = list.length; const planned = obj?.plan?.[stageId] || total; return { done, total, planned }; };