// ========================================== // PRESENTATION: ПЕРЕИСПОЛЬЗУЕМЫЕ КОМПОНЕНТЫ // ========================================== #import "../components.typ": corp_table, formula #import "foundation.typ": render-media-slot #import "../shared/numbering.typ": attachment_numbering #let letter-accent = rgb("fbb20d") #let footer-icon(name, color) = { box(width: 20pt, height: 20pt, fill: color, radius: 2pt)[ #align(center + horizon)[ #image( path("../assets/icons/" + name + ".png"), width: 15pt, height: 15pt, fit: "contain", ) ] ] } #let company-logo(company, width: 4cm, height: 2.5cm, policy: "placeholder") = { render-media-slot( company.resources.logo, policy: policy, width: width, height: height, label: "ЛОГОТИП", ) } #let signing-media(stamp, signature, policy, show_stamp: true, offset: -1cm) = { let has-missing-media = signature == none or (show_stamp and stamp == none) let stamp-resource = if stamp == none and policy == "placeholder" { path("../assets/placeholders/stamp.svg") } else { stamp } let signature-resource = if signature == none and policy == "placeholder" { path("../assets/placeholders/signature.svg") } else { signature } if policy == "placeholder" and has-missing-media { if show_stamp { place(center, dx: -1.6cm, dy: offset)[ #render-media-slot( stamp-resource, policy: policy, width: 2.8cm, height: 2.8cm, label: "ПЕЧАТЬ", ) ] } place(center, dx: if show_stamp { 1.6cm } else { 0cm }, dy: offset)[ #render-media-slot( signature-resource, policy: policy, width: 2.8cm, height: 1.4cm, label: "ПОДПИСЬ", ) ] } else { if show_stamp { place(center, dx: -0.5cm, dy: offset)[ #render-media-slot( stamp-resource, policy: policy, width: 3.5cm, height: 3.5cm, label: "МЕСТО ПЕЧАТИ", ) ] } place(center, dx: if show_stamp { 1.5cm } else { 0cm }, dy: offset)[ #render-media-slot( signature-resource, policy: policy, width: 3.5cm, height: 2.5cm, label: "МЕСТО ПОДПИСИ", ) ] } } #let signature-block( signer, company: none, media_policy: "placeholder", show_stamp: true, note: none, ) = { let signature = signer.at( "signature", default: if company == none { none } else { company.resources.signature }, ) let stamp = signer.at( "stamp", default: if company == none { none } else { company.resources.stamp }, ) set par(first-line-indent: 0pt, justify: false, leading: 0.75em) let left-content = [ #signer.title #if company != none { [\ #company.legal.name] } ] let right-content = [#signer.name] let media-content = signing-media( stamp, signature, media_policy, show_stamp: show_stamp, ) v(2cm) layout(size => { let left-width = measure(left-content).width let right-width = measure(right-content).width let roomy = left-width + right-width + 5.5cm <= size.width if roomy { grid( columns: (auto, 1fr, auto), align: (left + bottom, center + bottom, right + bottom), left-content, media-content, right-content, ) } else { grid( columns: (1fr, 6.5cm, 1fr), column-gutter: 8pt, align: (left + bottom, center + bottom, right + bottom), left-content, media-content, right-content, ) } }) if note != none { text(font: "Arial", size: 7.5pt, fill: luma(90))[#note] } } #let approval-block(approval, company, media_policy: "placeholder") = { let signer = approval.signer let signature = signer.at("signature", default: company.resources.signature) let signature-resource = if signature == none and media_policy == "placeholder" { path("../assets/placeholders/signature.svg") } else { signature } set par(first-line-indent: 0pt, justify: false, leading: 0.75em) box(width: 8cm)[ #align(right)[ #text(weight: "bold")[#approval.label] \ #signer.title \ #company.legal.name \ #v(0.2cm) #render-media-slot( signature-resource, policy: media_policy, width: 3cm, height: 1.2cm, label: "ПОДПИСЬ", ) \ #signer.name \ #if approval.date != none { approval.date } ] ] } #let company-footer(company) = { let contacts = company.contacts let banking = company.banking let contact-lines = () if contacts.phone != none { contact-lines.push(contacts.phone) } if contacts.email != none { contact-lines.push(contacts.email) } if contacts.website != none { contact-lines.push(contacts.website) } let id-lines = () for (key, label) in ( ("inn", "ИНН"), ("kpp", "КПП"), ("ogrn", "ОГРН"), ("bin", "БИН"), ("kbe", "КБЕ"), ) { let value = company.legal.at(key, default: none) if value != none { id-lines.push([#label #value]) } } let banking-lines = () if banking.bank != none { banking-lines.push([Банк: #banking.bank]) } if banking.at("correspondent-account", default: none) != none { banking-lines.push([Кор. счёт: #banking.at("correspondent-account")]) } if banking.bik != none { banking-lines.push([БИК: #banking.bik]) } if contacts.at("address-en", default: none) != none { banking-lines.push(contacts.at("address-en")) } set text(font: "Arial", size: 7.5pt, fill: luma(85)) set par(first-line-indent: 0pt, justify: false, leading: 0.2em) grid( columns: (1fr, 1.2fr), column-gutter: 15pt, [ #grid( columns: (20pt, 1fr), rows: (auto, auto), column-gutter: 8pt, row-gutter: 8pt, if contact-lines.len() == 0 { none } else { footer-icon("email", company.brand.color) }, contact-lines.join([\ ]), if contacts.address == none { none } else { footer-icon("location", company.brand.color) }, if contacts.address == none { none } else { contacts.address }, ) ], grid( columns: (1fr, 20pt), rows: (auto, auto), column-gutter: 8pt, row-gutter: 8pt, align: (right, center + horizon), align(right, id-lines.join([\ ])), if id-lines.len() == 0 { none } else { footer-icon("card", company.brand.color) }, align(right, banking-lines.join([\ ])), if banking-lines.len() == 0 { none } else { footer-icon("bank", company.brand.color) }, ), ) } #let letter-header( company, recipient, date: none, reference: none, title: none, media_policy: "placeholder", ) = { set par(first-line-indent: 0pt, justify: false, leading: 0.65em) place(top + left, dx: 10cm, dy: -1.5cm)[ #rect( width: 6.5cm, height: 2cm, fill: letter-accent, radius: (bottom: 2pt), ) ] v(30pt) box(width: 4cm, height: 2.5cm)[ #company-logo(company, width: 4cm, height: 2.5cm, policy: media_policy) ] v(-50pt) v(2.5cm) grid( columns: (1.4fr, 1fr), [ #if reference != none { [Исх. № #reference] } #if date != none { [ от #date] } ], [ #if recipient.company != none { [#recipient.company\ ] } #if recipient.title != none { [#recipient.title\ ] } #if recipient.name != none { recipient.name } #if recipient.address != none { [\ #recipient.address] } ], ) v(0.7cm) if title != none { align(center)[#text(weight: "bold")[#upper(title)]] v(0.2cm) } } #let attachment-list(attachments) = { let items = if type(attachments) == dictionary { attachments.items } else { attachments } if items.len() > 0 { set par(first-line-indent: 0pt, justify: false) for (index, item) in items.enumerate() { let number = if item.number == auto { index + 1 } else { item.number } [Приложение #number: #item.title] if index < items.len() - 1 { linebreak() } } } } #let requisites-table(parties) = { let field-labels = ( inn: "ИНН", kpp: "КПП", ogrn: "ОГРН", bin: "БИН", kbe: "КБе", address: "Юридический адрес", legal-address: "Юридический адрес", phone: "Телефон", email: "E-mail", website: "Сайт", bank: "Банк", account: "Расчётный счёт", settlement-account: "Расчётный счёт", correspondent-account: "Корреспондентский счёт", ks: "Корреспондентский счёт", bik: "БИК", iban: "IBAN", ) let hidden-fields = ( "name", "short-name", "jurisdiction", "summary", "address-en", "city", ) let render-fields(fields) = { for (key, value) in fields { if value != none and key not in hidden-fields { let label = field-labels.at(key, default: key) [#label: #value\ ] } } } let columns = range(parties.len()).map(_ => 1fr) table( columns: columns, stroke: 0.5pt, inset: 6pt, ..parties.map(item => [ #set par(first-line-indent: 0pt, justify: false, leading: 0.75em) #text(weight: "bold")[#item.role] \ #item.name \ #render-fields(item.legal) #render-fields(item.contacts) #render-fields(item.banking) ]), ) } #let multi-party-signing(parties, media_policy: "reserve-space") = { let columns = range(parties.len()).map(_ => 1fr) grid( columns: columns, column-gutter: 16pt, ..parties.map(item => { let person = item.representative [ #set par(first-line-indent: 0pt, justify: false, leading: 0.75em) #if person != none { grid( columns: (1fr,), rows: (auto, 0.35cm, 3.5cm, 0.25cm, auto), [#text(weight: "bold")[#item.role] \ #person.title], [], block(width: 100%, height: 3.5cm)[ #signing-media( person.stamp, person.signature, media_policy, show_stamp: true, offset: 0cm, ) ], [], [#person.name], ) } else { text(weight: "bold")[#item.role] } ] }), ) } #let appendix-heading(item, display-number) = { set par(first-line-indent: 0pt, justify: false, leading: 0.75em) align(right)[Приложение #display-number] v(0.2cm) align(center)[ #text(weight: "bold")[#upper(item.title)] #if item.subtitle != none { [\ #text(weight: "bold")[#item.subtitle]] } ] }