Add CBankverbindungen entity with multilingual support, tooltips, and layout definitions

This commit is contained in:
2026-01-23 15:51:42 +01:00
parent 185524e432
commit ac58b51452
14 changed files with 533 additions and 49 deletions

View File

@@ -0,0 +1,99 @@
<?php
namespace Espo\Custom\Classes\FormulaFunctions\IbanGroup;
use Espo\Core\Formula\Functions\BaseFunction;
use Espo\Core\Formula\ArgumentList;
/**
* IBAN-Validierung mit Modulo-97-Algorithmus (ISO 13616)
* Mathematische Prüfung ohne externe Datenbank
*/
class ValidateType extends BaseFunction
{
public function process(ArgumentList $args)
{
if (count($args) < 1) {
return false;
}
$iban = $this->evaluate($args[0]);
if (!$iban || !is_string($iban)) {
return false;
}
// Leerzeichen entfernen und in Großbuchstaben umwandeln
$iban = strtoupper(str_replace(' ', '', trim($iban)));
// Basis-Format prüfen: Mindestens 15, maximal 34 Zeichen
$length = strlen($iban);
if ($length < 15 || $length > 34) {
return false;
}
// Format prüfen: 2 Buchstaben (Ländercode) + 2 Ziffern (Prüfziffer) + Rest alphanumerisch
if (!preg_match('/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/', $iban)) {
return false;
}
// Ländercode extrahieren und Länge prüfen (optionale erweiterte Prüfung)
$countryCode = substr($iban, 0, 2);
$expectedLength = $this->getIbanLength($countryCode);
if ($expectedLength && $length !== $expectedLength) {
return false;
}
// Modulo-97-Algorithmus (ISO 13616)
// 1. IBAN umstellen: ersten 4 Zeichen ans Ende
$rearranged = substr($iban, 4) . substr($iban, 0, 4);
// 2. Buchstaben in Zahlen umwandeln (A=10, B=11, ..., Z=35)
$numericString = '';
for ($i = 0; $i < strlen($rearranged); $i++) {
$char = $rearranged[$i];
if (ctype_alpha($char)) {
// A=65 -> 10, B=66 -> 11, ..., Z=90 -> 35
$numericString .= (string)(ord($char) - 55);
} else {
$numericString .= $char;
}
}
// 3. Modulo 97 berechnen (schrittweise wegen großer Zahlen)
$remainder = 0;
for ($i = 0; $i < strlen($numericString); $i++) {
$remainder = ($remainder * 10 + (int)$numericString[$i]) % 97;
}
// 4. Rest muss genau 1 sein für gültige IBAN
return $remainder === 1;
}
/**
* Gibt die erwartete Länge einer IBAN für einen Ländercode zurück
* Quelle: ISO 13616 / SWIFT IBAN Registry
*/
private function getIbanLength(string $countryCode): ?int
{
$lengths = [
'AD' => 24, 'AE' => 23, 'AL' => 28, 'AT' => 20, 'AZ' => 28,
'BA' => 20, 'BE' => 16, 'BG' => 22, 'BH' => 22, 'BR' => 29,
'BY' => 28, 'CH' => 21, 'CR' => 22, 'CY' => 28, 'CZ' => 24,
'DE' => 22, 'DK' => 18, 'DO' => 28, 'EE' => 20, 'EG' => 29,
'ES' => 24, 'FI' => 18, 'FO' => 18, 'FR' => 27, 'GB' => 22,
'GE' => 22, 'GI' => 23, 'GL' => 18, 'GR' => 27, 'GT' => 28,
'HR' => 21, 'HU' => 28, 'IE' => 22, 'IL' => 23, 'IS' => 26,
'IT' => 27, 'JO' => 30, 'KW' => 30, 'KZ' => 20, 'LB' => 28,
'LC' => 32, 'LI' => 21, 'LT' => 20, 'LU' => 20, 'LV' => 21,
'MC' => 27, 'MD' => 24, 'ME' => 22, 'MK' => 19, 'MR' => 27,
'MT' => 31, 'MU' => 30, 'NL' => 18, 'NO' => 15, 'PK' => 24,
'PL' => 28, 'PS' => 29, 'PT' => 25, 'QA' => 29, 'RO' => 24,
'RS' => 22, 'SA' => 24, 'SE' => 24, 'SI' => 19, 'SK' => 24,
'SM' => 27, 'TN' => 24, 'TR' => 26, 'UA' => 29, 'VA' => 22,
'VG' => 24, 'XK' => 20,
];
return $lengths[$countryCode] ?? null;
}
}

View File

@@ -1,5 +1,52 @@
{
"labels": {
"Create CBankverbindungen": "Bankverbindung erstellen"
"Create CBankverbindungen": "Bankverbindung erstellen",
"CBankverbindungen": "Bankverbindungen"
},
"fields": {
"name": "Name",
"iban": "IBAN",
"bic": "BIC/SWIFT",
"kontoinhaber": "Kontoinhaber",
"bankname": "Bankname",
"istAktiv": "Ist aktiv",
"istStandard": "Standard-Konto",
"advowareKontoId": "Advoware Konto-ID",
"advowareLastSync": "Advoware letzte Synchronisation",
"syncStatus": "Sync-Status",
"beteiligte": "Beteiligter",
"createdAt": "Erstellt am",
"modifiedAt": "Geändert am",
"createdBy": "Erstellt von",
"modifiedBy": "Geändert von",
"assignedUser": "Zugewiesen an",
"teams": "Teams"
},
"links": {
"beteiligte": "Beteiligter",
"createdBy": "Erstellt von",
"modifiedBy": "Geändert von",
"assignedUser": "Zugewiesen an",
"teams": "Teams"
},
"tooltips": {
"name": "Bezeichnung der Bankverbindung (z.B. 'Hauptkonto', 'Geschäftskonto', 'Mietkautionskonto')",
"iban": "Internationale Bankkontonummer im Format DE89370400440532013000. Nur Großbuchstaben und Ziffern, 2-stelliger Ländercode gefolgt von Prüfziffer und Kontonummer (max. 34 Zeichen)",
"bic": "Bank Identifier Code (SWIFT-Code) im Format COBADEFFXXX. 8 oder 11 Zeichen: 4 Zeichen Bankcode, 2 Zeichen Ländercode, 2 Zeichen Ortscode, optional 3 Zeichen Filialcode",
"kontoinhaber": "Vollständiger Name des Kontoinhabers, wie auf dem Bankkonto hinterlegt",
"bankname": "Name der kontoführenden Bank (z.B. 'Sparkasse Köln Bonn', 'Deutsche Bank')",
"istAktiv": "Deaktivierte Bankverbindungen werden nicht mehr für neue Transaktionen verwendet, bleiben aber im System erhalten",
"istStandard": "Das Standard-Konto wird automatisch vorgeschlagen, wenn für diesen Beteiligten eine Zahlung erfasst wird. Nur ein Konto kann als Standard markiert sein",
"beteiligte": "Der Beteiligte, dem diese Bankverbindung zugeordnet ist",
"advowareKontoId": "Eindeutige Konto-ID aus Advoware für die Synchronisation. Wird automatisch bei der Synchronisation gesetzt",
"advowareLastSync": "Zeitpunkt der letzten erfolgreichen Synchronisation mit Advoware. Wird automatisch aktualisiert",
"syncStatus": "Status der Advoware-Synchronisation: 'Synchronisiert' = Daten sind aktuell, 'Abweichungen' = Daten weichen ab, 'Fehlgeschlagen' = Sync-Fehler aufgetreten"
},
"options": {
"syncStatus": {
"clean": "Synchronisiert",
"unclean": "Abweichungen",
"failed": "Fehlgeschlagen"
}
}
}

View File

@@ -13,6 +13,7 @@
"calls1": "Anrufe",
"contactsBeteiligte": "Freigegebene Nutzer",
"dokumentesBeteiligte": "Dokumente",
"bankverbindungens": "Bankverbindungen",
"betnr": "Advoware Identifikator",
"advowareLastSync": "Advoware letzte Synchronisation",
"syncStatus": "Sync-Status"
@@ -28,7 +29,8 @@
"adressens": "Adressen",
"calls1": "Anrufe",
"contactsBeteiligte": "Freigegebene Nutzer",
"dokumentesBeteiligte": "Dokumente"
"dokumentesBeteiligte": "Dokumente",
"bankverbindungens": "Bankverbindungen"
},
"labels": {
"Create CBeteiligte": "Beteiligte erstellen"

View File

@@ -1,9 +1,41 @@
{
"fields": {
},
"links": {
},
"labels": {
"Create CBankverbindungen": "Create Bankverbindung"
}
}
{
"fields": {
"name": "Name",
"iban": "IBAN",
"bic": "BIC/SWIFT",
"kontoinhaber": "Account Holder",
"bankname": "Bank Name",
"istAktiv": "Is Active",
"istStandard": "Default Account",
"advowareKontoId": "Advoware Account ID",
"advowareLastSync": "Advoware Last Sync",
"syncStatus": "Sync Status",
"beteiligte": "Participant"
},
"links": {
"beteiligte": "Participant"
},
"labels": {
"Create CBankverbindungen": "Create Bank Account"
},
"tooltips": {
"name": "Name or description of the bank account (e.g. 'Main Account', 'Business Account', 'Deposit Account')",
"iban": "International Bank Account Number in format DE89370400440532013000. Only uppercase letters and digits, 2-letter country code followed by check digit and account number (max. 34 characters)",
"bic": "Bank Identifier Code (SWIFT code) in format COBADEFFXXX. 8 or 11 characters: 4 characters bank code, 2 characters country code, 2 characters location code, optionally 3 characters branch code",
"kontoinhaber": "Full name of the account holder as registered with the bank",
"bankname": "Name of the bank (e.g. 'Sparkasse Cologne Bonn', 'Deutsche Bank')",
"istAktiv": "Inactive bank accounts are no longer used for new transactions but remain in the system",
"istStandard": "The default account is automatically suggested when a payment is recorded for this participant. Only one account can be marked as default",
"beteiligte": "The participant to whom this bank account belongs",
"advowareKontoId": "Unique account ID from Advoware for synchronization. Automatically set during synchronization",
"advowareLastSync": "Time of last successful synchronization with Advoware. Automatically updated",
"syncStatus": "Advoware synchronization status: 'Synchronized' = data is current, 'Discrepancies' = data differs, 'Failed' = sync error occurred"
},
"options": {
"syncStatus": {
"clean": "Synchronized",
"unclean": "Discrepancies",
"failed": "Failed"
}
}
}

View File

@@ -0,0 +1,40 @@
[
{
"label": "Übersicht",
"rows": [
[
{"name": "name"},
{"name": "beteiligte"}
],
[
{"name": "istAktiv"},
{"name": "istStandard"}
]
]
},
{
"label": "Bankdaten",
"rows": [
[
{"name": "iban"},
{"name": "bic"}
],
[
{"name": "kontoinhaber"},
{"name": "bankname"}
]
]
},
{
"label": "Advoware Sync",
"tabBreak": true,
"tabLabel": "Erweitert",
"rows": [
[
{"name": "advowareKontoId"},
{"name": "syncStatus"},
{"name": "advowareLastSync"}
]
]
}
]

View File

@@ -0,0 +1,32 @@
[
{
"rows": [
[
{
"name": "name"
},
{
"name": "istAktiv"
}
],
[
{
"name": "iban"
},
{
"name": "bic"
}
],
[
{
"name": "bankname"
},
{
"name": "istStandard"
}
]
],
"style": "default",
"label": ""
}
]

View File

@@ -0,0 +1,31 @@
[
{
"name": "name",
"link": true,
"width": 20
},
{
"name": "beteiligte",
"width": 20
},
{
"name": "iban",
"width": 20
},
{
"name": "bankname",
"width": 15
},
{
"name": "istAktiv",
"width": 10
},
{
"name": "istStandard",
"width": 10
},
{
"name": "syncStatus",
"width": 10
}
]

View File

@@ -0,0 +1,5 @@
[
{"name": "name"},
{"name": "iban"},
{"name": "istAktiv"}
]

View File

@@ -0,0 +1,12 @@
{
"functionList": [
"__APPEND__",
{
"name": "iban\\validate",
"insertText": "iban\\validate(IBAN)"
}
],
"functionClassNameMap": {
"iban\\validate": "Espo\\Custom\\Classes\\FormulaFunctions\\IbanGroup\\ValidateType"
}
}

View File

@@ -3,10 +3,89 @@
"name": {
"type": "varchar",
"required": true,
"pattern": "$noBadCharacters"
"pattern": "$noBadCharacters",
"tooltip": true
},
"description": {
"type": "text"
"iban": {
"type": "varchar",
"required": false,
"maxLength": 42,
"pattern": "/^[A-Z]{2}[0-9]{2}[\\s]?[A-Z0-9]{4}[\\s]?[A-Z0-9]{4}[\\s]?[A-Z0-9]{4}[\\s]?[A-Z0-9]{4}[\\s]?[A-Z0-9]{0,4}[\\s]?[A-Z0-9]{0,2}$/i",
"tooltip": true,
"isCustom": true,
"copyToClipboard": true,
"audited": true,
"options": []
},
"bic": {
"type": "varchar",
"required": false,
"maxLength": 11,
"pattern": "/^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$/i",
"tooltip": true,
"isCustom": true
},
"kontoinhaber": {
"type": "varchar",
"required": false,
"maxLength": 255,
"tooltip": true,
"isCustom": true
},
"bankname": {
"type": "varchar",
"required": false,
"maxLength": 255,
"tooltip": true,
"isCustom": true
},
"istAktiv": {
"type": "bool",
"default": true,
"tooltip": true,
"isCustom": true
},
"istStandard": {
"type": "bool",
"default": false,
"tooltip": true,
"isCustom": true
},
"advowareKontoId": {
"type": "int",
"required": false,
"tooltip": true,
"isCustom": true
},
"advowareLastSync": {
"type": "datetime",
"required": false,
"readOnly": true,
"tooltip": true,
"isCustom": true
},
"syncStatus": {
"type": "enum",
"required": false,
"options": [
"clean",
"unclean",
"failed"
],
"style": {
"clean": "success",
"unclean": "warning",
"failed": "danger"
},
"default": "clean",
"tooltip": true,
"isCustom": true
},
"beteiligte": {
"type": "link",
"required": false,
"tooltip": true,
"isCustom": true
},
"createdAt": {
"type": "datetime",
@@ -37,6 +116,12 @@
}
},
"links": {
"beteiligte": {
"type": "belongsTo",
"entity": "CBeteiligte",
"foreign": "bankverbindungens",
"isCustom": true
},
"createdBy": {
"type": "belongsTo",
"entity": "User"
@@ -60,6 +145,9 @@
"orderBy": "createdAt",
"order": "desc"
},
"formula": {
"beforeSaveApiScript": "// IBAN-Validierung mit Modulo-97-Algorithmus\nif (!string\\isEmpty(iban)) {\n // Leerzeichen entfernen für Validierung\n $ibanClean = string\\replace(iban, ' ', '');\n \n // Mathematische IBAN-Prüfung\n if (!iban\\validate($ibanClean)) {\n recordService\\throwBadRequest('Ungültige IBAN: Die Prüfziffer ist mathematisch falsch. Bitte überprüfen Sie die eingegebene IBAN.');\n }\n}"
},
"indexes": {
"name": {
"columns": [

View File

@@ -152,6 +152,9 @@
],
"tooltip": true,
"isCustom": true
},
"bankverbindungens": {
"type": "linkMultiple"
}
},
"links": {
@@ -258,6 +261,13 @@
"entity": "CDokumente",
"audited": false,
"isCustom": true
},
"bankverbindungens": {
"type": "hasMany",
"foreign": "beteiligte",
"entity": "CBankverbindungen",
"audited": false,
"isCustom": true
}
},
"collection": {