Initial commit
This commit is contained in:
commit
41d230d524
163 files changed
+11500
No files matched your search
@@ -0,0 +1,203 @@
|
||||
// ==========================================
|
||||
// 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,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// ==========================================
|
||||
// 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,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Публичный namespace profiles.
|
||||
#import "report.typ": report-profile
|
||||
#import "letter.typ": letter-profile
|
||||
#import "commercial-offer.typ": commercial-offer-profile, commercial-terms
|
||||
#import "contract.typ": contract-profile, contract-section
|
||||
|
||||
#let report = report-profile
|
||||
#let letter = letter-profile
|
||||
#let commercial_offer = commercial-offer-profile
|
||||
#let contract = contract-profile
|
||||
#let contract_section = contract-section
|
||||
#let commercial_terms = commercial-terms
|
||||
@@ -0,0 +1,147 @@
|
||||
// ==========================================
|
||||
// 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 normalize-letter(metadata) = {
|
||||
if type(metadata.attachments) == array {
|
||||
metadata.insert("attachments", attachment-set(items: metadata.attachments))
|
||||
}
|
||||
metadata
|
||||
}
|
||||
|
||||
#let validate-letter(metadata) = {
|
||||
if metadata.recipient == none or type(metadata.recipient) != dictionary or metadata.recipient.at("kind", default: none) != "recipient" {
|
||||
fail("letter.recipient", "ожидался recipient")
|
||||
}
|
||||
if metadata.title != none and type(metadata.title) not in (str, content) {
|
||||
fail("letter.title", "ожидались none, str или content")
|
||||
}
|
||||
if metadata.signer != none and (type(metadata.signer) != dictionary or metadata.signer.at("kind", default: none) != "signer") {
|
||||
fail("letter.signer", "ожидались none или signer")
|
||||
}
|
||||
if type(metadata.attachments) != dictionary or metadata.attachments.at("kind", default: none) != "attachment-set" {
|
||||
fail("letter.attachments", "ожидался attachment-set")
|
||||
}
|
||||
metadata
|
||||
}
|
||||
|
||||
#let effective-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 render-letter-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-letter(body, ctx) = {
|
||||
let company = ctx.company
|
||||
let data = ctx.metadata
|
||||
let include-media = ctx.options.mode == "final"
|
||||
let person = effective-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-letter-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.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-letter-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 letter-profile(
|
||||
recipient: none,
|
||||
date: none,
|
||||
reference: none,
|
||||
title: "Деловое письмо",
|
||||
signer: none,
|
||||
note: none,
|
||||
show_stamp: true,
|
||||
attachments: attachment-set(),
|
||||
render_attachments: false,
|
||||
) = {
|
||||
document-profile(
|
||||
"letter",
|
||||
render-letter,
|
||||
metadata: (
|
||||
recipient: recipient,
|
||||
date: date,
|
||||
reference: reference,
|
||||
title: title,
|
||||
signer: signer,
|
||||
note: note,
|
||||
show-stamp: show_stamp,
|
||||
parties: (),
|
||||
attachments: attachments,
|
||||
bibliographies: (),
|
||||
render-attachments: render_attachments,
|
||||
),
|
||||
normalize: normalize-letter,
|
||||
validate: validate-letter,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
// ==========================================
|
||||
// PROFILE: НАУЧНО-ТЕХНИЧЕСКИЙ ОТЧЁТ
|
||||
// ==========================================
|
||||
#import "../../domain/document.typ": document-profile, fail
|
||||
#import "../../domain/attachments.typ": attachment-set
|
||||
#import "../../report.typ": report
|
||||
#import "../../appendices.typ": make_appendices
|
||||
#import "../references.typ": render-bibliographies
|
||||
|
||||
#let placeholder-path(kind) = {
|
||||
if kind == "signature" {
|
||||
path("../../assets/placeholders/signature.svg")
|
||||
} else if kind == "stamp" {
|
||||
path("../../assets/placeholders/stamp.svg")
|
||||
} else {
|
||||
path("../../assets/placeholders/logo.svg")
|
||||
}
|
||||
}
|
||||
|
||||
#let report-company-summary(company) = {
|
||||
let summary = company.contacts.at("summary", default: none)
|
||||
if summary != none {
|
||||
summary
|
||||
} else {
|
||||
let lines = (company.legal.name,)
|
||||
let ids = ()
|
||||
for key in ("inn", "kpp", "ogrn", "bin", "kbe") {
|
||||
let value = company.legal.at(key, default: none)
|
||||
if value != none { ids.push(upper(key) + " " + value) }
|
||||
}
|
||||
if ids.len() > 0 { lines.push(ids.join(" ")) }
|
||||
for key in ("address", "phone", "email", "website") {
|
||||
let value = company.contacts.at(key, default: none)
|
||||
if value != none { lines.push(value) }
|
||||
}
|
||||
lines.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
#let resolve-signing-resource(resource, kind, ctx) = {
|
||||
if ctx.options.mode != "final" {
|
||||
none
|
||||
} else if resource != none {
|
||||
resource
|
||||
} else if ctx.options.at("media-policy") == "placeholder" {
|
||||
placeholder-path(kind)
|
||||
} else {
|
||||
none
|
||||
}
|
||||
}
|
||||
|
||||
#let render-report-appendices(sources, numbering, start) = {
|
||||
if sources.len() > 0 {
|
||||
make_appendices(
|
||||
[
|
||||
#for source in sources {
|
||||
include(source)
|
||||
}
|
||||
],
|
||||
numbering: numbering,
|
||||
start: start,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#let normalize-report(metadata) = metadata
|
||||
|
||||
#let validate-report(metadata) = {
|
||||
if type(metadata.title) != str or metadata.title.trim() == "" {
|
||||
fail("report.title", "название отчёта обязательно")
|
||||
}
|
||||
if type(metadata.year) != int {
|
||||
fail("report.year", "ожидалось целое число")
|
||||
}
|
||||
if type(metadata.executors) != array {
|
||||
fail("report.executors", "ожидался array")
|
||||
}
|
||||
for key in ("show-title-page", "show-executors", "show-outline") {
|
||||
if type(metadata.at(key)) != bool {
|
||||
fail("report." + key, "ожидался bool")
|
||||
}
|
||||
}
|
||||
if type(metadata.appendices) != array {
|
||||
fail("report.appendices", "ожидался array путей к файлам")
|
||||
}
|
||||
let seen = ()
|
||||
for source in metadata.appendices {
|
||||
if type(source) != path {
|
||||
fail("report.appendices", "каждый элемент должен быть path(...)")
|
||||
}
|
||||
if source in seen {
|
||||
fail("report.appendices", "файл указан повторно: " + repr(source))
|
||||
}
|
||||
seen.push(source)
|
||||
}
|
||||
if metadata.at("appendix-numbering") not in ("cyrillic", "arabic", "none") {
|
||||
fail("report.appendix-numbering", "допустимы cyrillic, arabic или none")
|
||||
}
|
||||
if type(metadata.at("appendix-start")) != int or metadata.at("appendix-start") < 1 {
|
||||
fail("report.appendix-start", "нужно положительное целое число")
|
||||
}
|
||||
metadata
|
||||
}
|
||||
|
||||
#let render-report(body, ctx) = {
|
||||
let company = ctx.company
|
||||
let data = ctx.metadata
|
||||
let mode = if ctx.options.mode == "final" { "default" } else { "draft" }
|
||||
let watermark = if ctx.options.mode == "draft" and ctx.options.watermark == none {
|
||||
"DRAFT"
|
||||
} else {
|
||||
ctx.options.watermark
|
||||
}
|
||||
let signature = resolve-signing-resource(company.resources.signature, "signature", ctx)
|
||||
let stamp = resolve-signing-resource(company.resources.stamp, "stamp", ctx)
|
||||
let logo = if company.resources.logo != none {
|
||||
company.resources.logo
|
||||
} else if ctx.options.at("media-policy") == "placeholder" {
|
||||
placeholder-path("logo")
|
||||
} else {
|
||||
none
|
||||
}
|
||||
let composed = [
|
||||
#body
|
||||
#if ctx.bibliographies.len() > 0 {
|
||||
pagebreak()
|
||||
render-bibliographies(ctx.bibliographies)
|
||||
}
|
||||
#if data.appendices.len() > 0 {
|
||||
render-report-appendices(
|
||||
data.appendices,
|
||||
data.at("appendix-numbering"),
|
||||
data.at("appendix-start"),
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
report(
|
||||
composed,
|
||||
company_info: report-company-summary(company),
|
||||
udk: data.udk,
|
||||
director_company: company.director.title + " " + company.legal.name,
|
||||
director_name: company.director.name,
|
||||
director_date: data.at("director-date"),
|
||||
sign_image: signature,
|
||||
stamp_image: stamp,
|
||||
logo_image: logo,
|
||||
is_research: data.at("is-research"),
|
||||
is_intermediate: data.at("is-intermediate"),
|
||||
stage_num: data.at("stage-number"),
|
||||
vol_num: data.at("volume-number"),
|
||||
title: data.title,
|
||||
theme: data.theme,
|
||||
contract_num: data.at("contract-number"),
|
||||
contract_date: data.at("contract-date"),
|
||||
city: data.city,
|
||||
year: data.year,
|
||||
executors: data.executors,
|
||||
figure-before: data.at("figure-before"),
|
||||
figure-after: data.at("figure-after"),
|
||||
table-before: data.at("table-before"),
|
||||
table-after: data.at("table-after"),
|
||||
figure-caption-before: data.at("figure-caption-before"),
|
||||
figure-caption-after: data.at("figure-caption-after"),
|
||||
table-caption-before: data.at("table-caption-before"),
|
||||
table-caption-after: data.at("table-caption-after"),
|
||||
show_title_page: data.at("show-title-page"),
|
||||
show_executors: data.at("show-executors"),
|
||||
show_outline: data.at("show-outline"),
|
||||
report_mode: mode,
|
||||
watermark: watermark,
|
||||
)
|
||||
}
|
||||
|
||||
#let report-profile(
|
||||
title: "Название работы",
|
||||
theme: none,
|
||||
udk: none,
|
||||
director_date: none,
|
||||
is_research: false,
|
||||
is_intermediate: false,
|
||||
stage_number: none,
|
||||
volume_number: none,
|
||||
contract_number: none,
|
||||
contract_date: none,
|
||||
city: "Екатеринбург",
|
||||
year: datetime.today().year(),
|
||||
executors: (),
|
||||
appendices: (),
|
||||
appendix_numbering: "cyrillic",
|
||||
appendix_start: 1,
|
||||
bibliographies: (),
|
||||
show_title_page: true,
|
||||
show_executors: true,
|
||||
show_outline: true,
|
||||
figure_before: 0.75em,
|
||||
figure_after: 0.75em,
|
||||
table_before: 0.75em,
|
||||
table_after: 0.75em,
|
||||
figure_caption_before: 0pt,
|
||||
figure_caption_after: 0pt,
|
||||
table_caption_before: 0pt,
|
||||
table_caption_after: 0pt,
|
||||
) = {
|
||||
document-profile(
|
||||
"report",
|
||||
render-report,
|
||||
metadata: (
|
||||
title: title,
|
||||
theme: theme,
|
||||
udk: udk,
|
||||
director-date: director_date,
|
||||
is-research: is_research,
|
||||
is-intermediate: is_intermediate,
|
||||
stage-number: stage_number,
|
||||
volume-number: volume_number,
|
||||
contract-number: contract_number,
|
||||
contract-date: contract_date,
|
||||
city: city,
|
||||
year: year,
|
||||
executors: executors,
|
||||
parties: (),
|
||||
// Общая attachment-модель остаётся для писем и договоров. Отчётные
|
||||
// приложения являются самодостаточными Typst-файлами.
|
||||
attachments: attachment-set(),
|
||||
appendices: appendices,
|
||||
appendix-numbering: appendix_numbering,
|
||||
appendix-start: appendix_start,
|
||||
bibliographies: bibliographies,
|
||||
show-title-page: show_title_page,
|
||||
show-executors: show_executors,
|
||||
show-outline: show_outline,
|
||||
figure-before: figure_before,
|
||||
figure-after: figure_after,
|
||||
table-before: table_before,
|
||||
table-after: table_after,
|
||||
figure-caption-before: figure_caption_before,
|
||||
figure-caption-after: figure_caption_after,
|
||||
table-caption-before: table_caption_before,
|
||||
table-caption-after: table_caption_after,
|
||||
),
|
||||
normalize: normalize-report,
|
||||
validate: validate-report,
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user