commit 5cdc847a2b76ea20f27679b0f9100e02c856ec10
parent b3baf8d3c38b972b108eb9d8a6a43cedf990e3e8
Author: Christian Grothoff <christian@grothoff.org>
Date: Mon, 10 Nov 2025 23:05:51 +0100
add Typst template for VQF_902_9
Diffstat:
1 file changed, 203 insertions(+), 0 deletions(-)
diff --git a/contrib/typst/VQF_902_9.typ b/contrib/typst/VQF_902_9.typ
@@ -0,0 +1,202 @@
+// VQF 902.9 Declaration of identity of the beneficial owner (A)
+// Pass JSON data as content dictionary
+
+#let form(data) = {
+ set page(
+ paper: "a4",
+ margin: (left: 2cm, right: 2cm, top: 2cm, bottom: 2.5cm),
+ footer: context [
+ #grid(
+ columns: (1fr, 1fr),
+ align: (left, right),
+ text(size: 8pt)[
+ VQF doc. Nr. 902.9#linebreak()
+ Version of 1 December 2015
+ ],
+ text(size: 8pt)[
+ Page #here().page() of #counter(page).final().first()
+ ]
+ )
+ ]
+ )
+
+ set text(font: "Liberation Sans", size: 10pt)
+ set par(justify: false, leading: 0.65em)
+
+ // Helper function to get value or empty string
+ let get(key, default: "") = {
+ data.at(key, default: default)
+ }
+
+ // Helper function for checkbox
+ let checkbox(checked) = {
+ box(
+ width: 3mm,
+ height: 3mm,
+ stroke: 0.5pt + black,
+ inset: 0.3mm,
+ if checked == true or checked == "true" {
+ place(center + horizon, text(size: 8pt, sym.checkmark))
+ }
+ )
+ }
+
+ // Header
+ align(center, text(size: 11pt, weight: "bold")[CONFIDENTIAL])
+
+ v(0.5em)
+
+ grid(
+ columns: (50%, 50%),
+ gutter: 1em,
+ image("vss_vqf_verein.png", width: 80%),
+ align(right)[
+ #table(
+ columns: (1fr, 1fr),
+ stroke: 0.5pt + black,
+ inset: 5pt,
+ align: (left, left),
+ [VQF member no.], [AMLA File No.],
+ [#get("VQF_MEMBER_NUMBER")], [#get("FILE_NUMBER")]
+ )
+ ]
+ )
+
+ v(1em)
+
+ align(left, text(size: 14pt, weight: "bold")[Declaration of identity of the beneficial owner (A)])
+
+ v(-1em)
+ line(length:100%)
+
+ v(1em)
+
+ // Section 1: Contracting Partner
+ text(size: 11pt, weight: "bold")[Contracting partner:]
+
+ v(0.5em)
+
+ table(
+ columns: (1fr),
+ stroke: 0.5pt + black,
+ inset: 5pt,
+ [#get("IDENTITY_CONTRACTING_PARTNER")]
+ )
+
+ v(1em)
+
+ text()[The contracting partner hereby declares that the person(s) listed below is/are the beneficial owner(s) of the assets involved in the business relationship. If the contracting partner is also the sole beneficial owner of the assets, the contracting partner's detail must be set out below]
+
+ v(1em)
+
+ // Section 2: Beneficial Owners
+ let owners = get("IDENTITY_LIST", default: ())
+ let has_owners = type(owners) == array and owners.len() > 0
+
+ if has_owners {
+ for owner in owners {
+ let get_owner(key) = {
+ owner.at(key, default: "")
+ }
+
+ block(breakable: false)[
+ #v(0.5em)
+ #table(
+ columns: (35%, 65%),
+ stroke: 0.5pt + black,
+ inset: 5pt,
+ [Fullname:], [#get_owner("FULL_NAME")],
+ [Date of birth:], [#get_owner("DATE_OF_BIRTH")],
+ [Nationality:], [#get_owner("NATIONALITY")],
+ [Actual address of domicile:], [#get_owner("DOMICILE_ADDRESS")]
+ )
+ #v(0.5em)
+ ]
+ }
+ } else {
+ block(breakable: false)[
+ #v(0.5em)
+ #table(
+ columns: (35%, 65%),
+ stroke: 0.5pt + black,
+ inset: 5pt,
+ [Surname(s):], [],
+ [First name(s):], [],
+ [Date(s) of birth:], [],
+ [Nationality:], [],
+ [Actual address of domicile:], []
+ )
+ #v(0.5em)
+ ]
+ }
+
+ v(1em)
+
+ text()[The contracting partner hereby undertakes to inform automatically of any changes to the information contained herein.]
+
+ v(1.5em)
+
+ // Signature Section
+ let submitted_by = get("SUBMITTED_BY")
+
+ if submitted_by == "CUSTOMER" {
+ table(
+ columns: (40%, 10%, 50%),
+ stroke: 0.5pt + black,
+ inset: 5pt,
+ [Date:],
+ [],
+ [Signature(s):],
+ [#get("SIGN_DATE")],
+ [],
+ [#get("SIGNATURE")]
+ )
+
+ v(1em)
+
+ text(size: 9pt, style: "italic")[
+ It is a criminal offence to deliberately provide false information on this form (article 251 of the Swiss Criminal Code, document forgery)
+ ]
+ } else if submitted_by == "AML_OFFICER" {
+ text(weight: "bold")[Signed declaration by the customer]
+
+ v(0.5em)
+
+ text(size: 9pt)[The attachment contains the customer's signature on the beneficial owner declaration.]
+
+ v(0.5em)
+
+ table(
+ columns: (1fr),
+ stroke: 0.5pt + black,
+ inset: 5pt,
+ [Signed Document:],
+ [#if (get("ATTACHMENT_SIGNED_DOCUMENT") != ""){
+ [Document attached]
+ } else {
+ [No document]
+ }
+ ]
+ )
+ } else {
+ text(weight: "bold")[Invalid submitter (#submitted_by)]
+ }
+}
+
+// Example usage:
+#form((
+ "VQF_MEMBER_NUMBER": "12345",
+ "FILE_NUMBER": "42",
+ "IDENTITY_CONTRACTING_PARTNER": "Example Company AG\nBahnhofstrasse 1\n8001 Zurich\nSwitzerland",
+ "IDENTITY_LIST": (
+ (
+ "FULL_NAME": "John Doe",
+ "DATE_OF_BIRTH": "01.01.1980",
+ "NATIONALITY": "CH",
+ "DOMICILE_ADDRESS": "Teststrasse 123\n8001 Zurich"
+ ),
+ ),
+ "SUBMITTED_BY": "CUSTOMER",
+ "SIGNATURE": "John Doe",
+ "SIGN_DATE": "10.11.2025",
+))
+\ No newline at end of file