// act-template.jsx — друковані «Акт приймання-передачі наданих послуг» + «Накладна // приймання-передавання технічної документації» (2× A4). Виконавець = COMPANY (ТОВ, // платник ПДВ); дані тягнуться з акта/договору/об'єкта. ПДВ 20% нараховується ЗВЕРХУ. // Потребує window.moneyInWords (invoice-template.jsx) — завантажувати ПІСЛЯ нього. function actFmt(n) { return Number(n).toLocaleString("uk-UA", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } const ACT_MONTHS = ["січня", "лютого", "березня", "квітня", "травня", "червня", "липня", "серпня", "вересня", "жовтня", "листопада", "грудня"]; function actDateUA(iso) { if (!iso) return null; const d = new Date(iso); if (isNaN(d)) return null; return `«${String(d.getDate()).padStart(2, "0")}» ${ACT_MONTHS[d.getMonth()]} ${d.getFullYear()} р.`; } function ActPh({ children }) { return {children}; } // Нижній блок: реквізити + підписи сторін // rc — конфіг ролей (Наш бік / контрагент) function ActFooter({ co, client, rc }) { const cust = client && client.contact; const ourLabel = rc ? rc.our.label : "ВИКОНАВЕЦЬ"; const counterLabel = rc ? rc.counter.label : "ЗАМОВНИК"; return (
Реквізити та підписи сторін
{ourLabel}
{co.fullName || co.shortName}
Адреса: {co.address}
Код ЄДРПОУ {co.edrpou} · ІПН {co.vatId || уточнити}
р/р {co.iban}
{co.bank}
E-mail: {co.email || "office@ukrbudproiekt.ua"}
Директор {co.shortName}
{co.director || П.І.Б.}
М.П.
{counterLabel}
{client ? client.name : Назва замовника}
Адреса: {client?.address || адреса}
Код ЄДРПОУ {client?.edrpou || ________}
р/р UA__ ____ ____ ____
банк, МФО
{cust ? cust.role : посада}
{cust ? cust.name : П.І.Б.}
М.П.
); } function ActPrintDoc({ act, onClose }) { React.useEffect(() => { const onKey = (e) => e.key === "Escape" && onClose(); window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [onClose]); const isFop = !!(act && act.execEntity === "fop"); const co = isFop ? (window.COMPANY_FOP || window.COMPANY || {}) : (window.COMPANY || {}); const obj = act ? window.getObject(act.obj) : null; const contract = obj ? (window.DATA.CONTRACTS || []).find(c => c.obj === obj.id) : null; let client = contract ? window.getClient(contract.client) : null; // Проєкт ГО: контрагент — партнер-ГО if (!client && act && act.partnerId && window.getNgoPartner) { const ng = window.getNgoPartner(act.partnerId); if (ng) client = { name: ng.name, edrpou: ng.edrpou, address: ng.city ? `м. ${ng.city}` : null, contact: ng.contact }; } const noVat = !!(act && act.noVat); // Ролі сторін (Виконавець / Підрядник) — з договору const rc = (window.actRoleConfig && contract) ? window.actRoleConfig(contract) : { our: { labelGen: "Виконавця", short: "Виконавець", label: "ВИКОНАВЕЦЬ" }, counter: { labelGen: "Замовника", short: "Замовник", label: "ЗАМОВНИК" }, subj: window.ACT_SUBJECTS ? window.ACT_SUBJECTS.services : { titleSuffix: "наданих послуг", verb: "надав", verbPl: "надано", noun: "послуги", genPl: "послуг", unit: "послуга", noteDone: "Послуги надані в повному обсязі" } }; // ПДВ 20% ЗВЕРХУ: сума акта трактується як база (без ПДВ) const base = act && typeof act.amount === "number" ? act.amount : 0; const vat = noVat ? 0 : Math.round(base * 0.20 * 100) / 100; const total = noVat ? base : Math.round((base + vat) * 100) / 100; const hasAmount = base > 0; const num = act ? act.num : null; const dateUA = actDateUA(act && act.date); const contractNo = contract ? contract.number : ((act && act.contractNumber) || null); const contractDate = contract ? (actDateUA(contract.date) || contract.date) : ((act && act.contractDate) ? (actDateUA(act.contractDate) || act.contractDate) : null); const custRep = client && client.contact ? `${client.contact.role} ${client.contact.name}` : null; const venue = obj ? `по об'єкту: «${obj.name}»${obj.code ? ` (${obj.code})` : ""}` : (act && act.projectName ? `за проєктом: «${act.projectName}»${act.projectCode ? ` (${act.projectCode})` : ""}` : ""); const serviceLine = act ? `${rc.subj.noun === "роботи" ? "Роботи" : "Послуги"} з розроблення проєктно-кошторисної документації${act.stage ? ` (${act.stage})` : ""} ${venue}` : null; // Перелік томів технічної документації (типовий комплект; редагується) const docRows = [ { code: "ПЗ", name: "Том 1. Пояснювальна записка", qty: "4 прим." }, { code: "АР", name: "Том 2. Архітектурні рішення", qty: "4 прим." }, { code: "КР", name: "Том 3. Конструктивні рішення", qty: "4 прим." }, { code: "ІМ", name: "Том 4. Інженерні мережі (ОВ, ВК, ЕО)", qty: "4 прим." }, { code: "К", name: "Том 5. Кошторисна документація", qty: "4 прим." }, { code: "ВД", name: "Том 6. Вихідні дані", qty: "4 прим." }, { code: null, name: "Електронний носій (PDF + DWG)", qty: "1 прим.", note: "CD/USB" }, ]; const desig = (c) => c ? `${obj ? obj.code + "." : ""}${c}` : "—"; const doPrint = () => window.print(); return ( <>
Акт + накладна{num ? ` · ${num}` : ""}
{/* ===================== СТОРІНКА 1: АКТ ===================== */}
{co.shortName}

АКТ № {num || __}
приймання-передачі {rc.subj.titleSuffix}

м. {co.city || "Київ"} {dateUA || «__» ________ 2026 року}

Ми, що нижче підписалися, представник {rc.our.labelGen} — {co.fullName || co.shortName} в особі директора {co.director || П.І.Б.}, що діє на підставі Статуту, і представник {" "}{rc.counter.labelGen} — {custRep || посада, П.І.Б.}, уклали цей акт про те, що {rc.our.short}{" "} у повному обсязі {rc.subj.verb} {rc.subj.noun} відповідно до Договору № {contractNo || ____} {" "}від {contractDate || «__» ________ 20__ р.}:

Найменування Од. виміру К-сть Ціна за од., грн без ПДВ Сума, грн без ПДВ
1 {serviceLine || Найменування послуг} {rc.subj.unit} 1 {hasAmount ? actFmt(base) : 0,00} {hasAmount ? actFmt(base) : 0,00}
Всього без ПДВ:{hasAmount ? actFmt(base) : 0,00} грн
ПДВ:{noVat ? "без ПДВ" : {hasAmount ? actFmt(vat) : 0,00} грн}
Всього{noVat ? "" : " з ПДВ"}:{hasAmount ? actFmt(total) : 0,00} грн

Всього {rc.subj.verbPl} {rc.subj.genPl} на суму (прописом): {hasAmount ? actFmt(total) : "0,00"} грн {" — "} {hasAmount ? `${window.moneyInWords(total)}${noVat ? " (без ПДВ)." : `, у тому числі ПДВ 20% — ${actFmt(vat)} грн.`}` : сума прописом}

{rc.subj.noteDone}, сторони претензій одна до одної не мають.

Акт складено у 2-х екземплярах, по одному для кожної із сторін.

{/* ===================== СТОРІНКА 2: НАКЛАДНА ===================== */}
{co.shortName}

НАКЛАДНА № {num || __}
приймання-передавання технічної документації
по Договору № {contractNo || ____} від {contractDate || «__» ________ 20__ р.}

м. {co.city || "Київ"} {dateUA || «__» ________ 2026 року}

{co.fullName || co.shortName} передало, а {client ? client.name : Замовник} в особі {" "}{custRep || посада, П.І.Б.} прийняло технічну документацію по Договору № {contractNo || ____} від {contractDate || «__» ________ 20__ р.}:

{docRows.map((r, i) => ( ))}
Позначення Найменування документів К-сть Примітка
{i + 1} {desig(r.code)} {r.name} {r.qty} {r.note || ""}

Документацію передано в повному обсязі. Сторони претензій одна до одної не мають.

); } function ActStyles() { return ( ); } window.ActPrintDoc = ActPrintDoc; // Синтез чернетки акта з договору (наступна несплачена/поточна віха графіку). window.draftActForContract = function (contract, stageIdx) { if (!contract) return null; const today = window.TODAY_ISO || "2026-05-26"; const sched = contract.schedule || []; let idx = (typeof stageIdx === "number" && stageIdx >= 0) ? stageIdx : sched.findIndex(s => !s.paidDate); if (idx < 0) idx = sched.length ? sched.length - 1 : -1; const m = idx >= 0 ? sched[idx] : {}; const obj = window.getObject(contract.obj); const codeNum = obj && obj.code ? (obj.code.match(/(\d+)\s*$/) || [])[1] : null; const base = codeNum || (contract.number || "").split("/")[0] || ""; const proj = contract.__project || null; const execEntity = proj ? proj.execEntity : (contract.execEntity || null); return { num: base ? `${base}-АКТ-${String(idx >= 0 ? idx + 1 : 1).padStart(2, "0")}` : null, date: today, obj: contract.obj, stage: m.stage || "", amount: m.amount || 0, status: "draft", execEntity, noVat: execEntity === "fop", projectName: proj ? proj.name : (contract.projectName || null), projectCode: proj ? proj.code : (contract.projectCode || null), partnerId: proj ? proj.partner : (contract.partnerId || null), contractNumber: contract.number || null, contractDate: contract.date || null, }; }; // ───────────────────────────────────────────────────────────── // Пікер «Сформувати акт»: обрати договір (або проєкт ГО) і віху, // далі відкриває друковану форму акта. Аналог InvoiceContractPicker. // ───────────────────────────────────────────────────────────── function ActContractPicker({ onClose, onPick }) { const { useState, useEffect } = React; const today = window.TODAY_ISO || "2026-05-26"; const designList = (window.DATA.CONTRACTS || []).filter(c => c.status !== "draft"); const projList = (window.DATA.PROJECTS || []).map(p => ({ ...p.contract, kind: "project", __project: p })); const list = [...designList, ...projList]; const dispCode = (c) => c.__project ? c.__project.code : (window.getObject(c.obj)?.code || "—"); const dispName = (c) => c.__project ? c.__project.name : (window.getObject(c.obj)?.name || "Без об'єкта"); const dispClient = (c) => c.__project ? (window.getNgoPartner(c.__project.partner)?.short || "ГО") : (window.getClient(c.client)?.short || "—"); const dispNum = (c) => c.number; const firstActive = list.find(c => c.status === "active") || list[0]; const keyOf = (c) => c.__project ? "prj-" + c.__project.id : c.id; const [selKey, setSelKey] = useState(firstActive ? keyOf(firstActive) : null); const nextUnpaidIdx = (c) => { const i = (c.schedule || []).findIndex(s => !s.paidDate); return i < 0 ? Math.max(0, (c.schedule || []).length - 1) : i; }; const [milestoneIdx, setMilestoneIdx] = useState(firstActive ? nextUnpaidIdx(firstActive) : 0); useEffect(() => { const onKey = (e) => e.key === "Escape" && onClose(); window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [onClose]); const pick = (c) => { setSelKey(keyOf(c)); setMilestoneIdx(nextUnpaidIdx(c)); }; const selected = list.find(c => keyOf(c) === selKey) || null; const sched = selected ? (selected.schedule || []) : []; const fmt = (n) => (window.formatMoney ? window.formatMoney(n) : n); const submit = () => { if (selected) onPick(window.draftActForContract(selected, milestoneIdx)); }; return (
Сформувати акт
Оберіть договір / проєкт і етап графіку
Договір / проєкт
{list.map(c => { const st = window.CONTRACT_STATUS[c.status] || window.PROJECT_STATUS?.[c.__project?.status] || {}; const ni = nextUnpaidIdx(c); const next = c.schedule ? c.schedule[ni] : null; const allPaid = (c.schedule || []).every(s => s.paidDate); const k = keyOf(c); return ( ); })}
{selected ? (
Етап для акта
{sched.map((s, i) => { const disabled = !!s.paidDate; return ( ); })}
Сума акта {fmt(sched[milestoneIdx]?.amount || 0)} ₴
Сформується чернетка акта {selected.__project ? "наданих послуг (проєкт ГО)" : "наданих послуг / виконаних робіт"} за обраний етап. Друкована форма відкриється у наступному кроці.
) : (
Немає договорів чи проєктів для акта.
)}
); } window.ActContractPicker = ActContractPicker;