125 lines
3.6 KiB
Typst
125 lines
3.6 KiB
Typst
// ==========================================
|
|
// DOMAIN: СТОРОНЫ, АДРЕСАТЫ И ПОДПИСАНТЫ
|
|
// ==========================================
|
|
#import "document.typ": fail, ensure-dictionary
|
|
|
|
#let normalize-text(value, scope, required: false) = {
|
|
if value == none {
|
|
if required { fail(scope, "поле обязательно") }
|
|
none
|
|
} else if type(value) != str {
|
|
fail(scope, "ожидалась строка")
|
|
} else if value.trim() == "" {
|
|
if required { fail(scope, "поле не может быть пустым") }
|
|
none
|
|
} else {
|
|
value
|
|
}
|
|
}
|
|
|
|
#let signer(
|
|
name,
|
|
title,
|
|
basis: none,
|
|
signature: none,
|
|
stamp: none,
|
|
) = {
|
|
for (key, resource) in (("signature", signature), ("stamp", stamp)) {
|
|
if resource != none and type(resource) not in (path, content) {
|
|
fail("signer." + key, "ожидались none, path или content")
|
|
}
|
|
}
|
|
(
|
|
kind: "signer",
|
|
name: normalize-text(name, "signer.name", required: true),
|
|
title: normalize-text(title, "signer.title", required: true),
|
|
basis: normalize-text(basis, "signer.basis"),
|
|
signature: signature,
|
|
stamp: stamp,
|
|
)
|
|
}
|
|
|
|
#let recipient(
|
|
company: none,
|
|
title: none,
|
|
name: none,
|
|
address: none,
|
|
) = {
|
|
let resolved = (
|
|
kind: "recipient",
|
|
company: normalize-text(company, "recipient.company"),
|
|
title: normalize-text(title, "recipient.title"),
|
|
name: normalize-text(name, "recipient.name"),
|
|
address: normalize-text(address, "recipient.address"),
|
|
)
|
|
if resolved.company == none and resolved.name == none {
|
|
fail("recipient", "нужно указать организацию или имя адресата")
|
|
}
|
|
resolved
|
|
}
|
|
|
|
#let party(
|
|
id,
|
|
role,
|
|
name,
|
|
legal: (:),
|
|
contacts: (:),
|
|
banking: (:),
|
|
representative: none,
|
|
) = {
|
|
if representative != none and (
|
|
type(representative) != dictionary
|
|
or representative.at("kind", default: none) != "signer"
|
|
) {
|
|
fail("party.representative", "ожидались none или signer")
|
|
}
|
|
(
|
|
kind: "party",
|
|
id: normalize-text(id, "party.id", required: true),
|
|
role: normalize-text(role, "party.role", required: true),
|
|
name: normalize-text(name, "party.name", required: true),
|
|
legal: ensure-dictionary(legal, "party.legal"),
|
|
contacts: ensure-dictionary(contacts, "party.contacts"),
|
|
banking: ensure-dictionary(banking, "party.banking"),
|
|
representative: representative,
|
|
)
|
|
}
|
|
|
|
#let approval(signer, date: none, label: "УТВЕРЖДАЮ") = {
|
|
if type(signer) != dictionary or signer.at("kind", default: none) != "signer" {
|
|
fail("approval.signer", "ожидался signer")
|
|
}
|
|
(
|
|
kind: "approval",
|
|
signer: signer,
|
|
date: normalize-text(date, "approval.date"),
|
|
label: normalize-text(label, "approval.label", required: true),
|
|
)
|
|
}
|
|
|
|
#let validate-parties(parties, minimum: 0) = {
|
|
if type(parties) != array {
|
|
fail("parties", "ожидался array")
|
|
}
|
|
if parties.len() < minimum {
|
|
fail("parties", "нужно не менее " + str(minimum) + " сторон")
|
|
}
|
|
let seen = ()
|
|
for item in parties {
|
|
if type(item) != dictionary or item.at("kind", default: none) != "party" {
|
|
fail("parties", "каждый элемент должен быть party")
|
|
}
|
|
if item.id in seen {
|
|
fail("parties", "повторяющийся id " + repr(item.id))
|
|
}
|
|
if item.representative != none and (
|
|
type(item.representative) != dictionary
|
|
or item.representative.at("kind", default: none) != "signer"
|
|
) {
|
|
fail("parties", "representative должен быть signer")
|
|
}
|
|
seen.push(item.id)
|
|
}
|
|
parties
|
|
}
|