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