exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit a5583d64ad06e9357840a69d609f7b3d35caf5bb
parent 776a226a5bd3b307f57290ac643286a4e8e4be43
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat,  6 Dec 2025 17:42:18 +0100

prepare to split forms'

Diffstat:
Mcontrib/typst/Makefile.am | 7+++++--
Acontrib/typst/accept-tos.typ | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontrib/typst/vqf_902_1.typ | 456+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcontrib/typst/vqf_902_11.typ | 4++++
Ccontrib/typst/vqf_902_11.typ -> contrib/typst/vqf_902_11_customer.typ | 0
Ccontrib/typst/vqf_902_11.typ -> contrib/typst/vqf_902_11_officer.typ | 0
Acontrib/typst/vqf_902_1_officer.typ | 452+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcontrib/typst/vqf_902_9.typ | 4++++
Ccontrib/typst/vqf_902_9.typ -> contrib/typst/vqf_902_9_customer.typ | 0
Ccontrib/typst/vqf_902_9.typ -> contrib/typst/vqf_902_9_officer.typ | 0
10 files changed, 1008 insertions(+), 2 deletions(-)

diff --git a/contrib/typst/Makefile.am b/contrib/typst/Makefile.am @@ -9,10 +9,13 @@ dist_formdata_DATA = \ taler-logo.svg \ pointing_finger.svg \ vqf_902_1_customer.typ \ + vqf_902_1_officer.typ \ vqf_902_4.typ \ vqf_902_5.typ \ - vqf_902_9.typ \ - vqf_902_11.typ \ + vqf_902_9_customer.typ \ + vqf_902_9_officer.typ \ + vqf_902_11_customer.typ \ + vqf_902_11_officer.typ \ vqf_902_12.typ \ vqf_902_13.typ \ vqf_902_14.typ \ diff --git a/contrib/typst/accept-tos.typ b/contrib/typst/accept-tos.typ @@ -0,0 +1,86 @@ +// Form to render ToS acceptance form. + +#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)[ + ], + 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("taler-logo.svg", width: 80%), + align(right)[ + #table( + columns: (1fr, 1fr), + stroke: 0.5pt + black, + inset: 5pt, + align: (left, left), + [AMLA File No.], + [#get("FILE_NUMBER")] + ) + ] + ) + + v(1em) + + // Section 1: Acceptance data + text(size: 11pt, weight: "bold")[Accepted terms of service:] + + v(0.5em) + + block(breakable: false)[ + #v(0.5em) + #table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Version:], [#get("ACCEPTED_TERMS_OF_SERVICE")], + [Downloaded:], [#checkbox(get("DOWNLOADED_TERMS_OF_SERVICE"))], + ) + #v(0.5em) + ] +} + +// Example usage: +#form(( + "ACCEPTED_TERMS_OF_SERVICE": "v1", + "DOWNLOADED_TERMS_OF_SERVICE": true, +)) +\ No newline at end of file diff --git a/contrib/typst/vqf_902_1.typ b/contrib/typst/vqf_902_1.typ @@ -0,0 +1,456 @@ +// VQF 902.1 Identification Form Template +// Pass JSON data as content dictionary +// NOTE: This is the original form. We don't use +// it as this form was split in customer + officer parts. +// Preserved here in case some auditor insists on us +// combining the two! + +#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.1#linebreak() + Version of 1 September 2021 + ], + 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 to get value or false + let getb(key, default: false) = { + 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")[Identification Form]) + + v(-1em) + line(length:100%) + + grid( + columns: (auto, 1fr), + gutter: 0.5em, + align: (left, left), + image("pointing_finger.svg", height: 2em), + text(size: 9pt)[ + The customer has to be identified on entering into a permanent business relationship or on concluding a cash transaction, which meets the according threshold. + ] + ) + + v(1em) + + text(weight: "bold")[This form was completed by:] + + v(0.3em) + + table( + columns: (1fr, 1fr), + stroke: 0.5pt + black, + inset: 5pt, + align: (left, left), + [Full name], [#get("FILED_BY_NAME")], + [Date], [#get("FILING_DATE")], + ) + + v(1.5em) + + // Section 1: Information on customer + text(size: 11pt, weight: "bold")[1. Information on customer#footnote[The customer is the person with whom the member concludes the contract with regard to the financial service provided (civil law). Does the member act as director of a domiciliary company, this domiciliary company is the customer.]] + + v(0.5em) + + let is_natural = get("CUSTOMER_TYPE") == "NATURAL_PERSON" + let is_legal = get("CUSTOMER_TYPE") == "LEGAL_ENTITY" + + // Section 1a: Natural Person + grid( + columns: (auto, 1fr), + gutter: 0.5em, + checkbox(is_natural), + [a) The customer is a #underline([natural]) person:] + ) + + block(breakable: false)[ + #table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Full name\*], if is_natural { get("FULL_NAME") } else { "" }, + [Residential address\*], if is_natural { get("DOMICILE_ADDRESS") } else { "" }, + [Telephone], if is_natural { get("CONTACT_PHONE") } else { "" }, + [E-mail], if is_natural { get("CONTACT_EMAIL") } else { "" }, + [Date of birth\*], if is_natural { get("DATE_OF_BIRTH") } else { "" }, + [Nationality\*], if is_natural { get("NATIONALITY") } else { "" }, + [Identification document\*], [#checkbox(is_natural) *Copy enclosed in appendix*], + ) + #v(-1em) + #text(size: 8pt)[*\* mandatory*] + ] + v(1em) + + // Sole proprietorship section + text(weight: "bold")[For sole proprietorship (supplement to above):] + + let is_sole = is_natural and (get("CUSTOMER_IS_SOLE_PROPRIETOR") == true or get("CUSTOMER_IS_SOLE_PROPRIETOR") == "true") + + table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Company name], if is_sole { get("COMPANY_NAME") } else { "" }, + [Registered office], if is_sole { get("REGISTERED_OFFICE_ADDRESS") } else { "" }, + [Company identification document], [#checkbox(is_sole) *Copy enclosed in appendix*], + ) + + v(1em) + + // Section 1b: Legal Entity + grid( + columns: (auto, 1fr), + gutter: 0.5em, + checkbox(is_legal), + [*b) The customer is a legal entity:*] + ) + + block(breakable: false)[ + #table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Company name\*], if is_legal { get("COMPANY_NAME") } else { "" }, + [Domicile\*], if is_legal { get("DOMICILE_ADDRESS") } else { "" }, + [Contact person], if is_legal { get("CONTACT_PERSON_NAME") } else { "" }, + [Telephone], if is_legal { get("CONTACT_PHONE") } else { "" }, + [E-mail], if is_legal { get("CONTACT_EMAIL") } else { "" }, + [Identification document\*\ (not older than 12 months)], [#checkbox(is_legal) *Copy enclosed in appendix*], + ) + + #v(-1em) + #text(size: 8pt)[*\* mandatory*] + ] + v(0.5em) + + // Section 2: Natural persons establishing business relationship + text(size: 11pt, weight: "bold")[2. Information on the natural persons who establish the business relationship for legal entities and partnerships] + + v(0.5em) + + grid( + columns: (auto, 1fr), + gutter: 0.5em, + align: (left, left), + image("pointing_finger.svg", height: 2em), + text(size: 9pt)[ + For legal entities and partnerships the identity of the natural persons who establish the business relationship must be verified. + ] + ) + + v(1em) + + let establishers = get("ESTABLISHER_LIST", default: ()) + let has_establishers = is_legal and type(establishers) == array and establishers.len() > 0 + + // Show at least 1 table for establishers to match VQF form + let num_cols = if has_establishers { calc.max(1, establishers.len()) } else { 1 } + + for col_idx in range(num_cols) { + if col_idx > 0 { + h(2em) + } + } + + // Create a table for each establisher + range(num_cols).map(col_idx => { + let establisher = if has_establishers and col_idx < establishers.len() { + establishers.at(col_idx) + } else { + (:) + } + + let get_est(key) = { + if establisher != (:) { + establisher.at(key, default: "") + } else { + "" + } + } + + block(breakable: false)[ + #table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Full name\*], + [#get_est("FULL_NAME")], + [Residential address\*], + [#get_est("DOMICILE_ADDRESS")], + [Date of birth\*], + [#get_est("DATE_OF_BIRTH")], + [Nationality\*], + [#get_est("NATIONALITY")], + [Type of authorisation\ (signatory of representation)\*], + [#get_est("SIGNING_AUTHORITY_TYPE")], + [Identification document\*], + [#checkbox(establisher != (:)) *Copy enclosed in appendix*], + [*Power of attorney arrangements\**], + [#let evidence = get_est("SIGNING_AUTHORITY_EVIDENCE") + #grid( + columns: (auto, 1fr), + gutter: 0.5em, + row-gutter: 0.3em, + checkbox(evidence == "CR"), + [CR extract], + checkbox(evidence == "MANDATE"), + [Mandate], + checkbox(evidence == "OTHER"), + [Other: #get_est("SIGNING_AUTHORITY_EVIDENCE_OTHER")], + ) + ] + ) + #v(-1em) + #text(size: 8pt)[*\* mandatory*] + ] + }).join() + + + + v(2em) + + // Section 3: Acceptance of business relationship + text(size: 11pt, weight: "bold")[3. Acceptance of business relationship] + + v(0.5em) + + let acceptance = get("ACCEPTANCE_METHOD") + let lang = get("CORRESPONDENCE_LANGUAGE") + table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Date (conclusion of contract)], get("ACCEPTANCE_DATE"), + [Accepted by], + [#grid( + columns: (auto, 1fr), + gutter: 0.5em, + checkbox(acceptance == "FACE_TO_FACE"), + [Face-to-face meeting with customer], + checkbox(acceptance == "WAY_OF_CORRESPONDENCE"), + [Way of correspondence: + \ #v(-0.7em) #grid( + columns: (0.2em, auto), + gutter: 0.5em, + row-gutter: 0.3em, + [], + [#checkbox(acceptance == "AUTHENTICATED_COPY") + authenticated copy of identification document obtained], + [], + [#checkbox(acceptance == "RESIDENTIAL_ADDRESS_VALIDATED") + residential address validated] + ) + ] + )], + [Type of correspondence service], + [#grid( + columns: (auto), + gutter: 0.2em, + [#checkbox(true) to the customer], + [#checkbox(false) hold at bank], + [#checkbox(false) to the member], + [#checkbox(false) + to a third party (full name and address):], + )], + [Language], + [#grid( + columns: (auto), + gutter: 0.2em, + [#checkbox(lang == "de") German], + [#checkbox(lang == "en") English], + [#checkbox(lang == "fr") French], + [#checkbox(lang != "fr" and lang != "de" and lang != "en") + Other: #lang], + )], + [Further information], + [#get("ACCEPTANCE_FURTHER_INFO")] + ) + + // Section 4: Beneficial owner + text(size: 11pt, weight: "bold")[4. Information on the beneficial owner of the assets and/or controlling person] + + v(0.5em) + + let customer_type_vqf = get("CUSTOMER_TYPE_VQF") + grid( + columns: (35%,65%), + stroke: 0.5pt + black, + inset: 5pt, + [Establishment of the beneficial owner of the assets and/or controlling person.], + [The customer is: + #grid( + columns: (1.1em, auto), + inset: 5pt, + [#checkbox(customer_type_vqf == "NATURAL_PERSON")], + [a natural person and there are no doubts that this person is the sole beneficial owner of the assets], + [#checkbox(customer_type_vqf == "OPERATIONAL")], + [an operational legal entity or partnership #h(1fr) + \ $=>$ VQF doc. No. 902.11 (K)], + [#checkbox(customer_type_vqf == "FOUNDATION")], + [a foundation (or a similar construct; incl. underlying companies) + \ $=>$ VQF doc. No. 902.12 (S)], + [#checkbox(customer_type_vqf == "TRUST")], + [a trust (incl. underlying companies) + \ $=>$ VQF doc. No. 902.13 (T)], + [#checkbox(customer_type_vqf == "LIFE_INSURANCE")], + [a life insurance policy with separately managed accounts/ securities accounts (so-called insurance wrappers) + \ $=>$ VQF doc. No. 902.15 (I)], + [#checkbox(customer_type_vqf == "OTHER")], + [all other cases + $=>$ VQF doc. No. 902.9 (A)], + )] + ) + + v(2em) + + // Section 5: Embargo/terrorism evaluation + text(size: 11pt, weight: "bold")[5. Evaluation with regard to embargo procedures/terrorism lists on establishing the business relationship] + + v(0.5em) + + table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Verification whether the customer, beneficial owners of the assets, controlling persons, authorised representatives or other involved persons are listed on an embargo-/terrorism list (date of verification/result)#footnote[The evaluation must be made at the beginning of the business relationship and has to be repeated in the case of permanent business relationship every time the according lists are updated.]], + [Date: #get("EMBARGO_TERRORISM_CHECK_DATE") + \ Result: #get("EMBARGO_TERRORISM_CHECK_RESULT")], + ) + + v(2em) + + // Section 6: Cash transactions + text(size: 11pt, weight: "bold")[6. In the case of cash transactions/occasional customers: Information on type and purpose of business relationship] + + v(0.5em) + + grid( + columns: (auto, 1fr), + gutter: 0.5em, + align: (left, left), + image("pointing_finger.svg", height: 2em), + text(size: 9pt)[ + These details are only necessary for occasional customers, i.e. money exchange, money and asset transfer or other cash transactions provided that #underline([no]) customer profile (VQF doc. No. 902.5) is created + ] + ) + + v(1em) + + table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Type of business relationship], + [#grid( + columns: (auto), + gutter: 0.2em, + [#checkbox(false) Money exchange], + [#checkbox(false) Money and asset transfer], + [#checkbox(false) Other cash transaction, specify?], + )], + [Purpose of the business relationship\ (purpose of service requested)], [], + ) + + // Section 7: Enclosures + text(size: 11pt, weight: "bold")[7. Enclosures] + + v(0.5em) + + grid( + columns: (auto, 1fr), + stroke: 0.5pt + black, + inset: 5pt, + checkbox("" != get("CUSTOMER_ID_AMLA_FILE_REFERENCE_NO")), [Customer identification documents (or: reference#footnote[If the identification document is lists kept in another AMLA-File (in the case of Art. 15 para. 3 SRO Regulations) a reference to the according AMLA-File is sufficient.] to AMLA File No.: #underline([#get("CUSTOMER_ID_AMLA_FILE_REFERENCE_NO")]))], + checkbox("" != get("ESTABLISHER_ID_AMLA_FILE_REFERENCE_NO")), [Identification document of persons establishing the business relationship (or: reference to AMLA File No.: #underline([#get("ESTABLISHER_ID_AMLA_FILE_REFERENCE_NO")]))], + checkbox(getb("HAVE_vqf_902_9") or getb("HAVE_vqf_902_11") or getb("HAVE_vqf_902_12") or getb("HAVE_vqf_902_13") or getb("HAVE_vqf_902_15")), [Establishing of the beneficial owner of the assets/controlling person (VQF Doc No. 902.15, 902.9, 902.11, 902.12 or 902.13)], + checkbox(getb("HAVE_vqf_902_5")), [Customer profile (VQF doc. No. 902.5; only in the case of permanent business relationship and regular customers)], + checkbox(getb("HAVE_vqf_902_4")), [Risk profile (VQF doc. No. 902.4)], + ) + + v(1em) + + text(size: 9pt, style: "italic")[⚠ *This form has to be updated when changes occur.*] + +} + +// Example usage: +#form(( + "VQF_MEMBER_NUMBER": "12345", + "FILE_NUMBER": "42", + "FILED_BY_NAME": "Manuela", + "AML_STAFF_NAME": "Manuela", + "CUSTOMER_ID_AMLA_FILE_REFERENCE_NO": "4242", // optional + "ESTABLISHER_ID_AMLA_FILE_REFERENCE_NO": "4243", // optional + "FILING_DATE": "1.4.2000", + "CUSTOMER_TYPE": "NATURAL_PERSON", + "FULL_NAME": "John Doe", + "DOMICILE_ADDRESS": "123 Main St, 8001 Zurich", + "HAVE_vqf_902_9": false, + "HAVE_vqf_902_11": false, + "HAVE_vqf_902_12": false, + "HAVE_vqf_902_13": false, + "HAVE_vqf_902_15": true, + "HAVE_vqf_902_4": true, + "HAVE_vqf_902_5": true, + // ... other fields + )) diff --git a/contrib/typst/vqf_902_11.typ b/contrib/typst/vqf_902_11.typ @@ -1,5 +1,9 @@ // VQF 902.11 Establishing of the controlling person (K) // Pass JSON data as content dictionary +// NOTE: This is the original form. We don't use +// it as this form was split in customer + officer parts. +// Preserved here in case some auditor insists on us +// combining the two! #let form(data) = { set page( diff --git a/contrib/typst/vqf_902_11.typ b/contrib/typst/vqf_902_11_customer.typ diff --git a/contrib/typst/vqf_902_11.typ b/contrib/typst/vqf_902_11_officer.typ diff --git a/contrib/typst/vqf_902_1_officer.typ b/contrib/typst/vqf_902_1_officer.typ @@ -0,0 +1,452 @@ +// VQF 902.1 Identification Form Template +// 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.1#linebreak() + Version of 1 September 2021 + ], + 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 to get value or false + let getb(key, default: false) = { + 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")[Identification Form]) + + v(-1em) + line(length:100%) + + grid( + columns: (auto, 1fr), + gutter: 0.5em, + align: (left, left), + image("pointing_finger.svg", height: 2em), + text(size: 9pt)[ + The customer has to be identified on entering into a permanent business relationship or on concluding a cash transaction, which meets the according threshold. + ] + ) + + v(1em) + + text(weight: "bold")[This form was completed by:] + + v(0.3em) + + table( + columns: (1fr, 1fr), + stroke: 0.5pt + black, + inset: 5pt, + align: (left, left), + [Full name], [#get("FILED_BY_NAME")], + [Date], [#get("FILING_DATE")], + ) + + v(1.5em) + + // Section 1: Information on customer + text(size: 11pt, weight: "bold")[1. Information on customer#footnote[The customer is the person with whom the member concludes the contract with regard to the financial service provided (civil law). Does the member act as director of a domiciliary company, this domiciliary company is the customer.]] + + v(0.5em) + + let is_natural = get("CUSTOMER_TYPE") == "NATURAL_PERSON" + let is_legal = get("CUSTOMER_TYPE") == "LEGAL_ENTITY" + + // Section 1a: Natural Person + grid( + columns: (auto, 1fr), + gutter: 0.5em, + checkbox(is_natural), + [a) The customer is a #underline([natural]) person:] + ) + + block(breakable: false)[ + #table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Full name\*], if is_natural { get("FULL_NAME") } else { "" }, + [Residential address\*], if is_natural { get("DOMICILE_ADDRESS") } else { "" }, + [Telephone], if is_natural { get("CONTACT_PHONE") } else { "" }, + [E-mail], if is_natural { get("CONTACT_EMAIL") } else { "" }, + [Date of birth\*], if is_natural { get("DATE_OF_BIRTH") } else { "" }, + [Nationality\*], if is_natural { get("NATIONALITY") } else { "" }, + [Identification document\*], [#checkbox(is_natural) *Copy enclosed in appendix*], + ) + #v(-1em) + #text(size: 8pt)[*\* mandatory*] + ] + v(1em) + + // Sole proprietorship section + text(weight: "bold")[For sole proprietorship (supplement to above):] + + let is_sole = is_natural and (get("CUSTOMER_IS_SOLE_PROPRIETOR") == true or get("CUSTOMER_IS_SOLE_PROPRIETOR") == "true") + + table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Company name], if is_sole { get("COMPANY_NAME") } else { "" }, + [Registered office], if is_sole { get("REGISTERED_OFFICE_ADDRESS") } else { "" }, + [Company identification document], [#checkbox(is_sole) *Copy enclosed in appendix*], + ) + + v(1em) + + // Section 1b: Legal Entity + grid( + columns: (auto, 1fr), + gutter: 0.5em, + checkbox(is_legal), + [*b) The customer is a legal entity:*] + ) + + block(breakable: false)[ + #table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Company name\*], if is_legal { get("COMPANY_NAME") } else { "" }, + [Domicile\*], if is_legal { get("DOMICILE_ADDRESS") } else { "" }, + [Contact person], if is_legal { get("CONTACT_PERSON_NAME") } else { "" }, + [Telephone], if is_legal { get("CONTACT_PHONE") } else { "" }, + [E-mail], if is_legal { get("CONTACT_EMAIL") } else { "" }, + [Identification document\*\ (not older than 12 months)], [#checkbox(is_legal) *Copy enclosed in appendix*], + ) + + #v(-1em) + #text(size: 8pt)[*\* mandatory*] + ] + v(0.5em) + + // Section 2: Natural persons establishing business relationship + text(size: 11pt, weight: "bold")[2. Information on the natural persons who establish the business relationship for legal entities and partnerships] + + v(0.5em) + + grid( + columns: (auto, 1fr), + gutter: 0.5em, + align: (left, left), + image("pointing_finger.svg", height: 2em), + text(size: 9pt)[ + For legal entities and partnerships the identity of the natural persons who establish the business relationship must be verified. + ] + ) + + v(1em) + + let establishers = get("ESTABLISHER_LIST", default: ()) + let has_establishers = is_legal and type(establishers) == array and establishers.len() > 0 + + // Show at least 1 table for establishers to match VQF form + let num_cols = if has_establishers { calc.max(1, establishers.len()) } else { 1 } + + for col_idx in range(num_cols) { + if col_idx > 0 { + h(2em) + } + } + + // Create a table for each establisher + range(num_cols).map(col_idx => { + let establisher = if has_establishers and col_idx < establishers.len() { + establishers.at(col_idx) + } else { + (:) + } + + let get_est(key) = { + if establisher != (:) { + establisher.at(key, default: "") + } else { + "" + } + } + + block(breakable: false)[ + #table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Full name\*], + [#get_est("FULL_NAME")], + [Residential address\*], + [#get_est("DOMICILE_ADDRESS")], + [Date of birth\*], + [#get_est("DATE_OF_BIRTH")], + [Nationality\*], + [#get_est("NATIONALITY")], + [Type of authorisation\ (signatory of representation)\*], + [#get_est("SIGNING_AUTHORITY_TYPE")], + [Identification document\*], + [#checkbox(establisher != (:)) *Copy enclosed in appendix*], + [*Power of attorney arrangements\**], + [#let evidence = get_est("SIGNING_AUTHORITY_EVIDENCE") + #grid( + columns: (auto, 1fr), + gutter: 0.5em, + row-gutter: 0.3em, + checkbox(evidence == "CR"), + [CR extract], + checkbox(evidence == "MANDATE"), + [Mandate], + checkbox(evidence == "OTHER"), + [Other: #get_est("SIGNING_AUTHORITY_EVIDENCE_OTHER")], + ) + ] + ) + #v(-1em) + #text(size: 8pt)[*\* mandatory*] + ] + }).join() + + + + v(2em) + + // Section 3: Acceptance of business relationship + text(size: 11pt, weight: "bold")[3. Acceptance of business relationship] + + v(0.5em) + + let acceptance = get("ACCEPTANCE_METHOD") + let lang = get("CORRESPONDENCE_LANGUAGE") + table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Date (conclusion of contract)], get("ACCEPTANCE_DATE"), + [Accepted by], + [#grid( + columns: (auto, 1fr), + gutter: 0.5em, + checkbox(acceptance == "FACE_TO_FACE"), + [Face-to-face meeting with customer], + checkbox(acceptance == "WAY_OF_CORRESPONDENCE"), + [Way of correspondence: + \ #v(-0.7em) #grid( + columns: (0.2em, auto), + gutter: 0.5em, + row-gutter: 0.3em, + [], + [#checkbox(acceptance == "AUTHENTICATED_COPY") + authenticated copy of identification document obtained], + [], + [#checkbox(acceptance == "RESIDENTIAL_ADDRESS_VALIDATED") + residential address validated] + ) + ] + )], + [Type of correspondence service], + [#grid( + columns: (auto), + gutter: 0.2em, + [#checkbox(true) to the customer], + [#checkbox(false) hold at bank], + [#checkbox(false) to the member], + [#checkbox(false) + to a third party (full name and address):], + )], + [Language], + [#grid( + columns: (auto), + gutter: 0.2em, + [#checkbox(lang == "de") German], + [#checkbox(lang == "en") English], + [#checkbox(lang == "fr") French], + [#checkbox(lang != "fr" and lang != "de" and lang != "en") + Other: #lang], + )], + [Further information], + [#get("ACCEPTANCE_FURTHER_INFO")] + ) + + // Section 4: Beneficial owner + text(size: 11pt, weight: "bold")[4. Information on the beneficial owner of the assets and/or controlling person] + + v(0.5em) + + let customer_type_vqf = get("CUSTOMER_TYPE_VQF") + grid( + columns: (35%,65%), + stroke: 0.5pt + black, + inset: 5pt, + [Establishment of the beneficial owner of the assets and/or controlling person.], + [The customer is: + #grid( + columns: (1.1em, auto), + inset: 5pt, + [#checkbox(customer_type_vqf == "NATURAL_PERSON")], + [a natural person and there are no doubts that this person is the sole beneficial owner of the assets], + [#checkbox(customer_type_vqf == "OPERATIONAL")], + [an operational legal entity or partnership #h(1fr) + \ $=>$ VQF doc. No. 902.11 (K)], + [#checkbox(customer_type_vqf == "FOUNDATION")], + [a foundation (or a similar construct; incl. underlying companies) + \ $=>$ VQF doc. No. 902.12 (S)], + [#checkbox(customer_type_vqf == "TRUST")], + [a trust (incl. underlying companies) + \ $=>$ VQF doc. No. 902.13 (T)], + [#checkbox(customer_type_vqf == "LIFE_INSURANCE")], + [a life insurance policy with separately managed accounts/ securities accounts (so-called insurance wrappers) + \ $=>$ VQF doc. No. 902.15 (I)], + [#checkbox(customer_type_vqf == "OTHER")], + [all other cases + $=>$ VQF doc. No. 902.9 (A)], + )] + ) + + v(2em) + + // Section 5: Embargo/terrorism evaluation + text(size: 11pt, weight: "bold")[5. Evaluation with regard to embargo procedures/terrorism lists on establishing the business relationship] + + v(0.5em) + + table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Verification whether the customer, beneficial owners of the assets, controlling persons, authorised representatives or other involved persons are listed on an embargo-/terrorism list (date of verification/result)#footnote[The evaluation must be made at the beginning of the business relationship and has to be repeated in the case of permanent business relationship every time the according lists are updated.]], + [Date: #get("EMBARGO_TERRORISM_CHECK_DATE") + \ Result: #get("EMBARGO_TERRORISM_CHECK_RESULT")], + ) + + v(2em) + + // Section 6: Cash transactions + text(size: 11pt, weight: "bold")[6. In the case of cash transactions/occasional customers: Information on type and purpose of business relationship] + + v(0.5em) + + grid( + columns: (auto, 1fr), + gutter: 0.5em, + align: (left, left), + image("pointing_finger.svg", height: 2em), + text(size: 9pt)[ + These details are only necessary for occasional customers, i.e. money exchange, money and asset transfer or other cash transactions provided that #underline([no]) customer profile (VQF doc. No. 902.5) is created + ] + ) + + v(1em) + + table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Type of business relationship], + [#grid( + columns: (auto), + gutter: 0.2em, + [#checkbox(false) Money exchange], + [#checkbox(false) Money and asset transfer], + [#checkbox(false) Other cash transaction, specify?], + )], + [Purpose of the business relationship\ (purpose of service requested)], [], + ) + + // Section 7: Enclosures + text(size: 11pt, weight: "bold")[7. Enclosures] + + v(0.5em) + + grid( + columns: (auto, 1fr), + stroke: 0.5pt + black, + inset: 5pt, + checkbox("" != get("CUSTOMER_ID_AMLA_FILE_REFERENCE_NO")), [Customer identification documents (or: reference#footnote[If the identification document is lists kept in another AMLA-File (in the case of Art. 15 para. 3 SRO Regulations) a reference to the according AMLA-File is sufficient.] to AMLA File No.: #underline([#get("CUSTOMER_ID_AMLA_FILE_REFERENCE_NO")]))], + checkbox("" != get("ESTABLISHER_ID_AMLA_FILE_REFERENCE_NO")), [Identification document of persons establishing the business relationship (or: reference to AMLA File No.: #underline([#get("ESTABLISHER_ID_AMLA_FILE_REFERENCE_NO")]))], + checkbox(getb("HAVE_vqf_902_9") or getb("HAVE_vqf_902_11") or getb("HAVE_vqf_902_12") or getb("HAVE_vqf_902_13") or getb("HAVE_vqf_902_15")), [Establishing of the beneficial owner of the assets/controlling person (VQF Doc No. 902.15, 902.9, 902.11, 902.12 or 902.13)], + checkbox(getb("HAVE_vqf_902_5")), [Customer profile (VQF doc. No. 902.5; only in the case of permanent business relationship and regular customers)], + checkbox(getb("HAVE_vqf_902_4")), [Risk profile (VQF doc. No. 902.4)], + ) + + v(1em) + + text(size: 9pt, style: "italic")[⚠ *This form has to be updated when changes occur.*] + +} + +// Example usage: +#form(( + "VQF_MEMBER_NUMBER": "12345", + "FILE_NUMBER": "42", + "FILED_BY_NAME": "Manuela", + "AML_STAFF_NAME": "Manuela", + "CUSTOMER_ID_AMLA_FILE_REFERENCE_NO": "4242", // optional + "ESTABLISHER_ID_AMLA_FILE_REFERENCE_NO": "4243", // optional + "FILING_DATE": "1.4.2000", + "CUSTOMER_TYPE": "NATURAL_PERSON", + "FULL_NAME": "John Doe", + "DOMICILE_ADDRESS": "123 Main St, 8001 Zurich", + "HAVE_vqf_902_9": false, + "HAVE_vqf_902_11": false, + "HAVE_vqf_902_12": false, + "HAVE_vqf_902_13": false, + "HAVE_vqf_902_15": true, + "HAVE_vqf_902_4": true, + "HAVE_vqf_902_5": true, + // ... other fields + )) diff --git a/contrib/typst/vqf_902_9.typ b/contrib/typst/vqf_902_9.typ @@ -1,5 +1,9 @@ // VQF 902.9 Declaration of identity of the beneficial owner (A) // Pass JSON data as content dictionary +// NOTE: This is the original form. We don't use +// it as this form was split in customer + officer parts. +// Preserved here in case some auditor insists on us +// combining the two! #let form(data) = { set page( diff --git a/contrib/typst/vqf_902_9.typ b/contrib/typst/vqf_902_9_customer.typ diff --git a/contrib/typst/vqf_902_9.typ b/contrib/typst/vqf_902_9_officer.typ