contract.typ 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. // ==========================================
  2. // PROFILE: ДОГОВОР
  3. // ==========================================
  4. #import "../../domain/document.typ": document-profile, fail
  5. #import "../../domain/attachments.typ": attachment-set
  6. #import "../../domain/parties.typ": validate-parties, signer
  7. #import "../../shared/numbering.typ": attachment_numbering
  8. #import "../foundation.typ": apply-foundation
  9. #import "../components.typ": appendix-heading, multi-party-signing, requisites-table
  10. #let contract-section(
  11. id,
  12. title,
  13. body,
  14. number: auto,
  15. level: 1,
  16. ) = {
  17. if type(id) != str or id.trim() == "" {
  18. fail("contract-section.id", "нужна непустая строка")
  19. }
  20. if type(title) not in (str, content) {
  21. fail("contract-section.title", "ожидались str или content")
  22. }
  23. if type(body) not in (content, function) {
  24. fail("contract-section.body", "ожидались content или function")
  25. }
  26. if type(level) != int or level < 1 {
  27. fail("contract-section.level", "ожидалось положительное целое число")
  28. }
  29. (
  30. kind: "contract-section",
  31. id: id,
  32. title: title,
  33. body: body,
  34. number: number,
  35. level: level,
  36. )
  37. }
  38. #let validate-sections(sections) = {
  39. if type(sections) != array {
  40. fail("contract.sections", "ожидался array")
  41. }
  42. let seen = ()
  43. for section in sections {
  44. if type(section) != dictionary or section.at("kind", default: none) != "contract-section" {
  45. fail("contract.sections", "каждый элемент должен быть contract-section")
  46. }
  47. if section.id in seen { fail("contract.sections", "повторяющийся id " + repr(section.id)) }
  48. seen.push(section.id)
  49. }
  50. sections
  51. }
  52. #let normalize-contract(metadata) = {
  53. if type(metadata.attachments) == array {
  54. metadata.insert("attachments", attachment-set(items: metadata.attachments))
  55. }
  56. metadata
  57. }
  58. #let validate-contract(metadata) = {
  59. if type(metadata.title) not in (str, content) {
  60. fail("contract.title", "ожидались str или content")
  61. }
  62. if metadata.number == none {
  63. fail("contract.number", "номер договора обязателен")
  64. }
  65. let _ = validate-parties(metadata.parties, minimum: 2)
  66. let _ = validate-sections(metadata.sections)
  67. if type(metadata.attachments) != dictionary or metadata.attachments.at("kind", default: none) != "attachment-set" {
  68. fail("contract.attachments", "ожидался attachment-set")
  69. }
  70. metadata
  71. }
  72. #let contract-preamble(data) = {
  73. if data.preamble == auto {
  74. [Стороны, указанные в разделе «Реквизиты и подписи сторон», заключили настоящий договор о нижеследующем.]
  75. } else {
  76. data.preamble
  77. }
  78. }
  79. #let render-contract-sections(sections) = {
  80. for (index, section) in sections.enumerate() {
  81. let number = if section.number == auto { index + 1 } else { section.number }
  82. heading(level: section.level, numbering: none)[#number. #upper(section.title)]
  83. if type(section.body) == function { (section.body)() } else { section.body }
  84. }
  85. }
  86. #let signing-parties(parties, include-media: true) = {
  87. parties.map(item => {
  88. if item.representative == none or include-media {
  89. item
  90. } else {
  91. let person = item.representative
  92. let clean-person = signer(
  93. person.name,
  94. person.title,
  95. basis: person.basis,
  96. signature: none,
  97. stamp: none,
  98. )
  99. let clean-item = item
  100. clean-item.insert("representative", clean-person)
  101. clean-item
  102. }
  103. })
  104. }
  105. #let render-contract-attachments(attachments) = {
  106. for (index, item) in attachments.items.enumerate() {
  107. pagebreak()
  108. let source-number = if item.number == auto { index + attachments.start } else { item.number }
  109. let display-number = attachment_numbering(attachments.numbering, source-number)
  110. appendix-heading(item, display-number)
  111. v(0.5cm)
  112. if type(item.body) == function { (item.body)() } else { item.body }
  113. }
  114. }
  115. #let render-contract(body, ctx) = {
  116. let data = ctx.metadata
  117. let include-media = ctx.options.mode == "final"
  118. let media-policy = if include-media { ctx.options.at("media-policy") } else { "reserve-space" }
  119. let signing = signing-parties(data.parties, include-media: include-media)
  120. let composed = [
  121. #set heading(numbering: "1.1")
  122. #show heading: it => {
  123. set text(font: "Times New Roman", size: 11pt, weight: "bold", hyphenate: false)
  124. set par(first-line-indent: 0pt, justify: false)
  125. v(1em, weak: true)
  126. block(sticky: true, width: 100%)[#it]
  127. v(0.5em)
  128. }
  129. #align(center)[
  130. #text(weight: "bold", size: 14pt)[#upper(data.title)] \
  131. № #data.number
  132. ]
  133. #v(0.7cm)
  134. #grid(
  135. columns: (1fr, 1fr),
  136. [#data.place],
  137. align(right)[#data.date],
  138. )
  139. #v(0.8cm)
  140. #contract-preamble(data)
  141. #v(0.6cm)
  142. #if data.sections.len() > 0 { render-contract-sections(data.sections) }
  143. #body
  144. #pagebreak(weak: true)
  145. #heading(numbering: none)[РЕКВИЗИТЫ И ПОДПИСИ СТОРОН]
  146. #requisites-table(data.parties)
  147. #v(1cm)
  148. #multi-party-signing(signing, media_policy: media-policy)
  149. #if data.attachments.items.len() > 0 {
  150. render-contract-attachments(data.attachments)
  151. }
  152. ]
  153. apply-foundation(
  154. composed,
  155. ctx: ctx,
  156. tokens: (
  157. text-font: "Times New Roman",
  158. text-size: 11pt,
  159. line-height: 1.15em,
  160. page-margin: (left: 2.5cm, right: 2cm, top: 2cm, bottom: 2cm),
  161. ),
  162. )
  163. }
  164. #let contract-profile(
  165. number: none,
  166. date: none,
  167. place: "Екатеринбург",
  168. title: "Договор",
  169. parties: (),
  170. preamble: auto,
  171. sections: (),
  172. attachments: attachment-set(),
  173. ) = {
  174. document-profile(
  175. "contract",
  176. render-contract,
  177. metadata: (
  178. number: number,
  179. date: date,
  180. place: place,
  181. title: title,
  182. parties: parties,
  183. preamble: preamble,
  184. sections: sections,
  185. attachments: attachments,
  186. bibliographies: (),
  187. ),
  188. normalize: normalize-contract,
  189. validate: validate-contract,
  190. )
  191. }