// ========================================== // PROFILE: ТЕХНИКО-КОММЕРЧЕСКОЕ ПРЕДЛОЖЕНИЕ // ========================================== #import "../../domain/document.typ": document-profile, fail #import "../../domain/attachments.typ": attachment-set #import "../../domain/parties.typ": signer #import "../foundation.typ": apply-foundation #import "../components.typ": attachment-list, appendix-heading, company-footer, letter-header, signature-block #let commercial-terms( amount: none, currency: "RUB", tax_note: none, delivery_term: none, validity: none, payment_terms: none, ) = { ( amount: amount, currency: currency, tax-note: tax_note, delivery-term: delivery_term, validity: validity, payment-terms: payment_terms, ) } #let normalize-offer(metadata) = { if type(metadata.attachments) == array { metadata.insert("attachments", attachment-set(items: metadata.attachments)) } metadata } #let validate-offer(metadata) = { if metadata.recipient == none or type(metadata.recipient) != dictionary or metadata.recipient.at("kind", default: none) != "recipient" { fail("commercial-offer.recipient", "ожидался recipient") } if type(metadata.subject) != str or metadata.subject.trim() == "" { fail("commercial-offer.subject", "предмет предложения обязателен") } if type(metadata.terms) != dictionary { fail("commercial-offer.terms", "ожидался commercial-terms") } if metadata.terms.amount == none { fail("commercial-offer.terms.amount", "стоимость обязательна") } if type(metadata.attachments) != dictionary or metadata.attachments.at("kind", default: none) != "attachment-set" { fail("commercial-offer.attachments", "ожидался attachment-set") } metadata } #let effective-offer-signer(data, company, include-media: true) = { let source = if data.signer == none { signer( company.director.name, company.director.title, signature: company.resources.signature, stamp: company.resources.stamp, ) } else { data.signer } signer( source.name, source.title, basis: source.basis, signature: if include-media { source.signature } else { none }, stamp: if include-media { source.stamp } else { none }, ) } #let offer-summary(data) = { let terms = data.terms set par(first-line-indent: 0pt, justify: false, leading: 0.7em) table( columns: (4cm, 1fr), stroke: 0.5pt + luma(170), inset: 6pt, fill: (x, _) => if x == 0 { luma(245) } else { none }, [*Предмет*], [#data.subject], [*Стоимость*], [#terms.amount #terms.currency], ..if terms.at("tax-note") != none { ([*Налоги*], [#terms.at("tax-note")]) } else { () }, ..if terms.at("delivery-term") != none { ([*Срок выполнения*], [#terms.at("delivery-term")]) } else { () }, ..if terms.validity != none { ([*Срок действия*], [#terms.validity]) } else { () }, ..if terms.at("payment-terms") != none { ([*Порядок оплаты*], [#terms.at("payment-terms")]) } else { () }, ) } #let render-offer-attachments(attachments, appendix-state) = { for (index, item) in attachments.items.enumerate() { pagebreak() appendix-state.update(true) let number = if item.number == auto { index + attachments.start } else { item.number } appendix-heading(item, str(number)) v(0.5cm) if type(item.body) == function { (item.body)() } else { item.body } } } #let render-commercial-offer(body, ctx) = { let company = ctx.company let data = ctx.metadata let include-media = ctx.options.mode == "final" let person = effective-offer-signer(data, company, include-media: include-media) let media-policy = if include-media { ctx.options.at("media-policy") } else { "reserve-space" } let appendix-state = state("scientia-offer-in-appendix", false) let footer = context { if not appendix-state.get() { company-footer(company) } } let composed = [ #letter-header( company, data.recipient, date: data.date, reference: data.reference, title: data.title, media_policy: ctx.options.at("media-policy"), ) #v(0.6cm) #offer-summary(data) #v(0.7cm) #body #if data.attachments.items.len() > 0 { v(0.8cm) attachment-list(data.attachments) } #signature-block( person, company: company, media_policy: media-policy, show_stamp: data.at("show-stamp"), note: data.note, ) #if data.at("render-attachments") and data.attachments.items.len() > 0 { render-offer-attachments(data.attachments, appendix-state) } ] apply-foundation( composed, ctx: ctx, tokens: ( text-font: "Arial", text-size: 10pt, line-height: 0.65em, page-margin: (top: 1.5cm, bottom: 4cm, left: 1.5cm, right: 1.5cm), ), page-options: ( footer: footer, footer-descent: 20%, ), ) } #let commercial-offer-profile( recipient: none, subject: "", amount: none, currency: "RUB", tax_note: none, delivery_term: none, validity: none, payment_terms: none, date: none, reference: none, title: "Технико-коммерческое предложение", signer: none, note: none, show_stamp: true, attachments: attachment-set(), render_attachments: true, ) = { document-profile( "commercial-offer", render-commercial-offer, metadata: ( recipient: recipient, subject: subject, terms: commercial-terms( amount: amount, currency: currency, tax_note: tax_note, delivery_term: delivery_term, validity: validity, payment_terms: payment_terms, ), date: date, reference: reference, title: title, signer: signer, note: note, show-stamp: show_stamp, parties: (), attachments: attachments, bibliographies: (), render-attachments: render_attachments, ), normalize: normalize-offer, validate: validate-offer, ) }