| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- // ==========================================
- // 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,
- )
- }
|