Initial commit

This commit is contained in:
tkachenko committed 2026-10-09 01:45:17 +00:00
commit 41d230d524
163 files changed
+11500

No files matched your search

@@ -0,0 +1,205 @@
// ==========================================
// PROFILE: ДОГОВОР
// ==========================================
#import "../../domain/document.typ": document-profile, fail
#import "../../domain/attachments.typ": attachment-set
#import "../../domain/parties.typ": validate-parties, signer
#import "../../shared/numbering.typ": attachment_numbering
#import "../foundation.typ": apply-foundation
#import "../components.typ": appendix-heading, multi-party-signing, requisites-table
#let contract-section(
id,
title,
body,
number: auto,
level: 1,
) = {
if type(id) != str or id.trim() == "" {
fail("contract-section.id", "нужна непустая строка")
}
if type(title) not in (str, content) {
fail("contract-section.title", "ожидались str или content")
}
if type(body) not in (content, function) {
fail("contract-section.body", "ожидались content или function")
}
if type(level) != int or level < 1 {
fail("contract-section.level", "ожидалось положительное целое число")
}
(
kind: "contract-section",
id: id,
title: title,
body: body,
number: number,
level: level,
)
}
#let validate-sections(sections) = {
if type(sections) != array {
fail("contract.sections", "ожидался array")
}
let seen = ()
for section in sections {
if type(section) != dictionary or section.at("kind", default: none) != "contract-section" {
fail("contract.sections", "каждый элемент должен быть contract-section")
}
if section.id in seen { fail("contract.sections", "повторяющийся id " + repr(section.id)) }
seen.push(section.id)
}
sections
}
#let normalize-contract(metadata) = {
if type(metadata.attachments) == array {
metadata.insert("attachments", attachment-set(items: metadata.attachments))
}
metadata
}
#let validate-contract(metadata) = {
if type(metadata.title) not in (str, content) {
fail("contract.title", "ожидались str или content")
}
if metadata.number == none {
fail("contract.number", "номер договора обязателен")
}
let _ = validate-parties(metadata.parties, minimum: 2)
let _ = validate-sections(metadata.sections)
if type(metadata.attachments) != dictionary or metadata.attachments.at("kind", default: none) != "attachment-set" {
fail("contract.attachments", "ожидался attachment-set")
}
metadata
}
#let contract-preamble(data) = {
if data.preamble == auto {
[Стороны, указанные в разделе «Реквизиты и подписи сторон», заключили настоящий договор о нижеследующем.]
} else {
data.preamble
}
}
#let render-contract-sections(sections) = {
for (index, section) in sections.enumerate() {
let number = if section.number == auto { index + 1 } else { section.number }
heading(level: section.level, numbering: none)[#number. #upper(section.title)]
if type(section.body) == function { (section.body)() } else { section.body }
}
}
#let signing-parties(parties, include-media: true) = {
parties.map(item => {
if item.representative == none or include-media {
item
} else {
let person = item.representative
let clean-person = signer(
person.name,
person.title,
basis: person.basis,
signature: none,
stamp: none,
)
let clean-item = item
clean-item.insert("representative", clean-person)
clean-item
}
})
}
#let render-contract-attachments(attachments) = {
for (index, item) in attachments.items.enumerate() {
pagebreak()
let source-number = if item.number == auto { index + attachments.start } else { item.number }
let display-number = attachment_numbering(attachments.numbering, source-number)
appendix-heading(item, display-number)
v(0.5cm)
if type(item.body) == function { (item.body)() } else { item.body }
}
}
#let render-contract(body, ctx) = {
let data = ctx.metadata
let include-media = ctx.options.mode == "final"
let media-policy = if include-media { ctx.options.at("media-policy") } else { "reserve-space" }
let signing = signing-parties(data.parties, include-media: include-media)
let composed = [
#set heading(numbering: "1.1")
#show heading: it => {
set text(font: "Times New Roman", size: 11pt, weight: "bold", hyphenate: false)
set par(first-line-indent: 0pt, justify: false)
v(1em, weak: true)
block(sticky: true, width: 100%)[#it]
v(0.5em)
}
#align(center)[
#text(weight: "bold", size: 14pt)[#upper(data.title)] \
№ #data.number
]
#v(0.7cm)
#grid(
columns: (1fr, 1fr),
[#data.place],
align(right)[#data.date],
)
#v(0.8cm)
#contract-preamble(data)
#v(0.6cm)
#if data.sections.len() > 0 { render-contract-sections(data.sections) }
#body
#pagebreak(weak: true)
#heading(numbering: none)[РЕКВИЗИТЫ И ПОДПИСИ СТОРОН]
#requisites-table(data.parties)
#v(1cm)
#multi-party-signing(signing, media_policy: media-policy)
#if data.attachments.items.len() > 0 {
render-contract-attachments(data.attachments)
}
]
apply-foundation(
composed,
ctx: ctx,
tokens: (
text-font: "Times New Roman",
text-size: 11pt,
line-height: 1.15em,
page-margin: (left: 2.5cm, right: 2cm, top: 2cm, bottom: 2cm),
),
)
}
#let contract-profile(
number: none,
date: none,
place: "Екатеринбург",
title: "Договор",
parties: (),
preamble: auto,
sections: (),
attachments: attachment-set(),
) = {
document-profile(
"contract",
render-contract,
metadata: (
number: number,
date: date,
place: place,
title: title,
parties: parties,
preamble: preamble,
sections: sections,
attachments: attachments,
bibliographies: (),
),
normalize: normalize-contract,
validate: validate-contract,
)
}