Revision 3277b6bd
Von Moritz Bunkus vor mehr als 11 Jahren hinzugefügt
locale/de/all | ||
---|---|---|
'All changes in that file have been reverted.' => 'Alle Änderungen in dieser Datei wurden rückgängig gemacht.',
|
||
'All database upgrades have been applied.' => 'Alle Datenbankupdates wurden eingespielt.',
|
||
'All general ledger entries' => 'Alle Hauptbucheinträge',
|
||
'All groups' => 'Alle Gruppen',
|
||
'All of the exports you have selected were already closed.' => 'Alle von Ihnen ausgewählten Exporte sind bereits abgeschlossen.',
|
||
'All reports' => 'Alle Berichte (Kontenübersicht, Summen- u. Saldenliste, GuV, BWA, Bilanz, Projektbuchungen)',
|
||
'All the selected exports have already been closed, or all of their items have already been executed.' => 'Alle ausgewählten Exporte sind als abgeschlossen markiert, oder für alle Einträge wurden bereits Zahlungen verbucht.',
|
||
... | ... | |
'Cleared Balance' => 'abgeschlossen',
|
||
'Clearing Tax Received (No 71)' => 'Verrechnung des Erstattungsbetrages erwünscht (Zeile 71)',
|
||
'Click on login name to edit!' => 'Zum Bearbeiten den Benutzernamen anklicken!',
|
||
'Client #1' => 'Mandant #1',
|
||
'Client Configuration' => 'Mandantenkonfiguration',
|
||
'Client Configuration saved!' => 'Mandantenkonfiguration gespeichert!',
|
||
'Client name' => 'Mandantenname',
|
||
'Close' => 'Übernehmen',
|
||
'Close Books up to' => 'Die Bücher abschließen bis zum',
|
||
'Close Flash' => 'Schließen',
|
||
... | ... | |
'Comment' => 'Kommentar',
|
||
'Company' => 'Firma',
|
||
'Company Name' => 'Firmenname',
|
||
'Company name' => 'Firmenname',
|
||
'Compare to' => 'Gegenüberstellen zu',
|
||
'Configuration' => 'Einstellungen',
|
||
'Configuration of individual TODO items' => 'Konfiguration für die einzelnen Aufgabenlistenpunkte',
|
||
... | ... | |
'Create new' => 'Neu erfassen',
|
||
'Create new background job' => 'Neuen Hintergrund-Job anlegen',
|
||
'Create new business' => 'Kunden-/Lieferantentyp erfassen',
|
||
'Create new client #1' => 'Neuen Mandanten #1 anlegen',
|
||
'Create new department' => 'Neue Abteilung erfassen',
|
||
'Create new payment term' => 'Neue Zahlungsbedingung anlegen',
|
||
'Create tables' => 'Tabellen anlegen',
|
||
... | ... | |
'Database User missing!' => 'Datenbankbenutzer fehlt!',
|
||
'Database backups and restorations are disabled in the configuration.' => 'Datenbanksicherungen und -wiederherstellungen sind in der Konfiguration deaktiviert.',
|
||
'Database name' => 'Datenbankname',
|
||
'Database settings' => 'Datenbankeinstellungen',
|
||
'Database template' => 'Datenbankvorlage',
|
||
'Database update error:' => 'Fehler beim Datenbankupgrade:',
|
||
'Dataset' => 'Datenbank',
|
||
... | ... | |
'Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.' => 'Fehler in Position #1: Sie müssen einer Position entweder gar keinen Lagerausgang oder die vollständige im Lieferschein vermerkte Menge von #2 #3 zuweisen.',
|
||
'Error in row #1: The quantity you entered is bigger than the stocked quantity.' => 'Fehler in Zeile #1: Die angegebene Menge ist größer als die vorhandene Menge.',
|
||
'Error message from the database driver:' => 'Fehlermeldung des Datenbanktreibers:',
|
||
'Error message from the database: #1' => 'Fehlermeldung der Datenbank: #1',
|
||
'Error when saving: #1' => 'Fehler beim Speichern: #1',
|
||
'Error!' => 'Fehler!',
|
||
'Error: Buchungsgruppe missing or invalid' => 'Fehler: Buchungsgruppe fehlt oder ungültig',
|
||
... | ... | |
'General Ledger Transaction' => 'Dialogbuchung',
|
||
'General ledger and cash' => 'Finanzbuchhaltung und Zahlungsverkehr',
|
||
'General ledger corrections' => 'Korrekturen im Hauptbuch',
|
||
'General settings' => 'Allgemeine Einstellungen',
|
||
'Generic Tax Report' => 'USTVA Bericht',
|
||
'Git revision: #1, #2 #3' => 'Git-Revision: #1, #2 #3',
|
||
'Given Name' => 'Vorname',
|
||
... | ... | |
'Group missing!' => 'Warengruppe fehlt!',
|
||
'Group saved!' => 'Warengruppe gespeichert!',
|
||
'Groups' => 'Warengruppen',
|
||
'Groups that are valid for this client for access rights' => 'Gruppen, die für diesen Mandanten gültig sind',
|
||
'Groups valid for this client' => 'Für Mandanten gültige Gruppen',
|
||
'HTML' => 'HTML',
|
||
'HTML Templates' => 'HTML-Vorlagen',
|
||
'Hardcopy' => 'Seite drucken',
|
||
... | ... | |
'International' => 'Ausland',
|
||
'Internet' => 'Internet',
|
||
'Introduction of Buchungsgruppen' => 'Einführung von Buchungsgruppen',
|
||
'Introduction of clients' => 'Einführung von Mandanten',
|
||
'Introduction of units' => 'Einführung von Einheiten',
|
||
'Inv. Duedate' => 'Rg. Fälligkeit',
|
||
'Invalid' => 'Ungültig',
|
||
... | ... | |
'Invoices, Credit Notes & AR Transactions' => 'Rechnungen, Gutschriften & Debitorenbuchungen',
|
||
'Is Searchable' => 'Durchsuchbar',
|
||
'Is this a summary account to record' => 'Sammelkonto für',
|
||
'It can be changed later but must be unique within the installation.' => 'Er ist nachträglich änderbar, muss aber im System eindeutig sein.',
|
||
'It is not allowed that a summary account occurs in a drop-down menu!' => 'Ein Sammelkonto darf nicht in Aufklappmenüs aufgenommen werden!',
|
||
'It is possible that even after such a correction there is something wrong with this transaction (e.g. taxes that don\'t match the selected taxkey). Therefore you should re-run the general ledger analysis.' => 'Auch nach einer Korrektur kann es mit dieser Buchung noch weitere Probleme geben (z.B. nicht zum Steuerschlüssel passende Steuern), weshalb ein erneutes Ausführen der Hauptbuchanalyse empfohlen wird.',
|
||
'It is possible to do this automatically for some Buchungsgruppen, but not for all.' => 'Es ist möglich, dies für einige, aber nicht für alle Buchungsgruppen automatisch zu erledigen.',
|
||
... | ... | |
'New Templates' => 'Erzeuge Vorlagen, Name',
|
||
'New assembly' => 'Neues Erzeugnis',
|
||
'New bank account' => 'Neues Bankkonto',
|
||
'New client #1: The database configuration fields "host", "port", "name" and "user" must not be empty.' => 'Neuer Mandant #1: Die Datenbankkonfigurationsfelder "Host", "Port" und "Name" dürfen nicht leer sein.',
|
||
'New client #1: The name must be unique and not empty.' => 'Neuer Mandant #1: Der Name darf nicht leer und muss eindeutig sein.',
|
||
'New contact' => 'Neue Ansprechperson',
|
||
'New customer' => 'Neuer Kunde',
|
||
'New filter for tax accounts' => 'Neue Filter für Steuerkonten',
|
||
... | ... | |
'One or more Perl modules missing' => 'Ein oder mehr Perl-Module fehlen',
|
||
'Only Warnings and Errors' => 'Nur Warnungen und Fehler',
|
||
'Only due follow-ups' => 'Nur fällige Wiedervorlagen',
|
||
'Only groups that have been configured for the client the user logs in to will be considered.' => 'Allerdings werden nur diejenigen Gruppen herangezogen, die für den Mandanten konfiguriert sind.',
|
||
'Only shown in item mode' => 'werden nur im Artikelmodus angezeigt',
|
||
'Oops. No valid action found to dispatch. Please report this case to the kivitendo team.' => 'Ups. Es wurde keine gültige Funktion zum Aufrufen gefunden. Bitte berichten Sie diesen Fall den kivitendo-Entwicklern.',
|
||
'Open' => 'Offen',
|
||
... | ... | |
'Please choose for which categories the taxes should be displayed (otherwise remove the ticks):' => 'Bitte wählen Sie für welche Kontoart die Steuer angezeigt werden soll (ansonsten einfach die Häkchen entfernen)',
|
||
'Please contact your administrator or a service provider.' => 'Bitte kontaktieren Sie Ihren Administrator oder einen Dienstleister.',
|
||
'Please contact your administrator.' => 'Bitte wenden Sie sich an Ihren Administrator.',
|
||
'Please correct the settings and try again or deactivate that client.' => 'Bitte korrigieren Sie die Einstellungen und versuchen Sie es erneut, oder deaktivieren Sie diesen Mandanten.',
|
||
'Please define a taxkey for the following taxes and run the update again:' => 'Bitte definieren Sie einen Steuerschlüssel für die folgenden Steuern und starten Sie dann das Update erneut:',
|
||
'Please enter a profile name.' => 'Bitte geben Sie einen Profilnamen an.',
|
||
'Please enter the currency you are working with.' => 'Bitte geben Sie die Währung an, mit der Sie arbeiten.',
|
||
... | ... | |
'Please select the database you want to backup' => 'Bitte wählen Sie die zu sichernde Datenbank gefunden',
|
||
'Please select the destination bank account for the collections:' => 'Bitte wählen Sie das Bankkonto als Ziel für die Einzüge aus:',
|
||
'Please select the source bank account for the transfers:' => 'Bitte wählen Sie das Bankkonto als Quelle für die Überweisungen aus:',
|
||
'Please select which client configurations you want to create.' => 'Bitte wählen Sie aus, welche Mandanten mit welchen Einstellungen angelegt werden sollen.',
|
||
'Please seletct the dataset you want to delete:' => 'Bitte wählen Sie die zu löschende Datenbank aus:',
|
||
'Please set another taxnumber for the following taxes and run the update again:' => 'Bitte wählen Sie ein anderes Steuerautomatik-Konto für die folgenden Steuern aus uns starten Sie dann das Update erneut.',
|
||
'Please specify a description for the warehouse designated for these goods.' => 'Bitte geben Sie den Namen des Ziellagers für die übernommenen Daten ein.',
|
||
... | ... | |
'Print dunnings' => 'Mahnungen drucken',
|
||
'Print list' => 'Liste ausdrucken',
|
||
'Print options' => 'Druckoptionen',
|
||
'Print templates' => 'Druckvorlagen',
|
||
'Printer' => 'Drucker',
|
||
'Printer Command' => 'Druckbefehl',
|
||
'Printer Command missing!' => 'Druckbefehl fehlt',
|
||
... | ... | |
'Text, text field and number variables: The default value will be used as-is.' => 'Textzeilen, Textfelder und Zahlenvariablen: Der Standardwert wird so wie er ist übernommen.',
|
||
'That export does not exist.' => 'Dieser Export existiert nicht.',
|
||
'That is why kivitendo could not find a default currency.' => 'Daher konnte kivitendo keine Standardwährung finden.',
|
||
'The \'name\' is the field shown to the user during login.' => 'Der \'Name\' ist derjenige, der dem Benutzer beim Login angezeigt wird.',
|
||
'The \'tag\' field must only consist of alphanumeric characters or the carachters - _ ( )' => 'Das Feld \'tag\' darf nur aus alphanumerischen Zeichen und den Zeichen - _ ( ) bestehen.',
|
||
'The AP transaction #1 has been deleted.' => 'Die Kreditorenbuchung #1 wurde gelöscht.',
|
||
'The AR transaction #1 has been deleted.' => 'Die Debitorenbuchung #1 wurde gelöscht.',
|
||
... | ... | |
'The LDAP server "#1:#2" is unreachable. Please check config/kivitendo.conf.' => 'Der LDAP-Server "#1:#2" ist nicht erreichbar. Bitte überprüfen Sie die Angaben in config/kivitendo.conf.',
|
||
'The SEPA export has been created.' => 'Der SEPA-Export wurde erstellt',
|
||
'The SEPA strings have been saved.' => 'Die bei SEPA-Überweisungen verwendeten Begriffe wurden gespeichert.',
|
||
'The access rights a user has within a client instance is still governed by his group membership.' => 'Welche Zugriffsrechte ein Benutzer innerhalb eines Mandanten hat, wird weiterhin über Gruppenmitgliedschaften geregelt.',
|
||
'The access rights have been saved.' => 'Die Zugriffsrechte wurden gespeichert.',
|
||
'The account 3804 already exists, the update will be skipped.' => 'Das Konto 3804 existiert schon, das Update wird übersprungen.',
|
||
'The account 3804 will not be added automatically.' => 'Das Konto 3804 wird nicht automatisch hinzugefügt.',
|
||
... | ... | |
'The columns "Dunning Duedate", "Total Fees" and "Interest" show data for the previous dunning created for this invoice.' => 'Die Spalten "Zahlbar bis", "Kumulierte Gebühren" und "Zinsen" zeigen Daten der letzten für diese Rechnung erzeugten Mahnung.',
|
||
'The connection to the LDAP server cannot be encrypted (SSL/TLS startup failure). Please check config/kivitendo.conf.' => 'Die Verbindung zum LDAP-Server kann nicht verschlüsselt werden (Fehler bei SSL/TLS-Initialisierung). Bitte überprüfen Sie die Angaben in config/kivitendo.conf.',
|
||
'The connection to the authentication database failed:' => 'Die Verbindung zur Authentifizierungsdatenbank schlug fehl:',
|
||
'The connection to the configured client database "#1" on host "#2:#3" failed.' => 'Die Verbindung zur konfigurierten Datenbank "#1" auf Host "#2:#3" schlug fehl.',
|
||
'The connection to the database could not be established.' => 'Die Verbindung zur Datenbank konnte nicht hergestellt werden.',
|
||
'The connection to the template database failed:' => 'Die Verbindung zur Vorlagendatenbank schlug fehl:',
|
||
'The connection was established successfully.' => 'Die Verbindung zur Datenbank wurde erfolgreich hergestellt.',
|
||
... | ... | |
'The following Datasets need to be updated' => 'Folgende Datenbanken müssen aktualisiert werden',
|
||
'The following currencies have been used, but they are not defined:' => 'Die folgenden Währungen wurden benutzt, sind aber nicht ordnungsgemäß in der Datenbank eingetragen:',
|
||
'The following drafts have been saved and can be loaded.' => 'Die folgenden Entwürfe wurden gespeichert und können geladen werden.',
|
||
'The following list has been generated automatically from existing users collapsing users with identical settings into a single entry.' => 'Die folgende Liste wurde automatisch aus den im System vorhandenen Benutzern zusammengestellt, wobei identische Einstellungen zu einem Eintrag zusammengefasst wurden.',
|
||
'The following old files whose settings have to be merged manually into the new configuration file "config/kivitendo.conf" still exist:' => 'Es existieren noch die folgenden alten Dateien, deren Einstellungen manuell in die neue Konfiguratsdatei "config/kivitendo.conf" migriert werden müssen:',
|
||
'The following transaction contains wrong taxes:' => 'Die folgende Buchung enthält falsche Steuern:',
|
||
'The following transaction contains wrong taxkeys:' => 'Die folgende Buchung enthält falsche Steuerschlüssel:',
|
||
... | ... | |
'The unit in row %d has been deleted in the meantime.' => 'Die Einheit in Zeile %d ist in der Zwischentzeit gelöscht worden.',
|
||
'The unit in row %d has been used in the meantime and cannot be changed anymore.' => 'Die Einheit in Zeile %d wurde in der Zwischenzeit benutzt und kann nicht mehr geändert werden.',
|
||
'The units have been saved.' => 'Die Einheiten wurden gespeichert.',
|
||
'The user can chose which client to connect to during login.' => 'Bei der Anmeldung kann der Benutzer auswählen, welchen Mandanten er benutzen möchte.',
|
||
'The user is a member in the following group(s):' => 'Der Benutzer ist Mitglied in den folgenden Gruppen:',
|
||
'The variable name must only consist of letters, numbers and underscores. It must begin with a letter. Example: send_christmas_present' => 'Der Variablenname darf nur aus Zeichen (keine Umlaute), Ziffern und Unterstrichen bestehen. Er muss mit einem Buchstaben beginnen. Beispiel: weihnachtsgruss_verschicken',
|
||
'The warehouse could not be deleted because it has already been used.' => 'Das Lager konnte nicht gelöscht werden, da es bereits in Benutzung war.',
|
||
... | ... | |
'There is nothing to do in this step.' => 'In diesem Schritt gibt es nichts mehr zu tun.',
|
||
'There was an error executing the background job.' => 'Bei der Ausführung des Hintergrund-Jobs trat ein Fehler auf.',
|
||
'There was an error parsing the csv file: #1 in line #2.' => 'Es gab einen Fehler beim Parsen der CSV Datei: "#1" in der Zeile "#2"',
|
||
'Therefore several settings that had to be made for each user in the past have been consolidated into the client configuration.' => 'Dazu wurden gewisse Einstellungen, die vorher bei jedem Benutzer vorgenommen werden mussten, in die Konfiguration eines Mandanten verschoben.',
|
||
'Therefore the definition of "kg" with the base unit "g" and a factor of 1000 is valid while defining "g" with a base unit of "kg" and a factor of "0.001" is not.' => 'So ist die Definition von "kg" mit der Basiseinheit "g" und dem Faktor 1000 zulässig, die Definition von "g" mit der Basiseinheit "kg" und dem Faktor "0,001" hingegen nicht.',
|
||
'Therefore there\'s no need to create the same article more than once if it is sold or bought in/from another tax zone.' => 'Deswegen muss man den gleichen Artikel nicht mehr mehrmals anlegen, wenn er in verschiedenen Steuerzonen gehandelt werden soll.',
|
||
'These units can be based on other units so that kivitendo can convert prices when the user switches from one unit to another.' => 'Einheiten können auf anderen Einheiten basieren, sodass kivitendo Preise automatisch umrechnen kann, wenn die Benutzer zwischen solchen Einheiten umschalten.',
|
||
... | ... | |
'Updated' => 'Erneuert am',
|
||
'Updating existing entry in database' => 'Existierenden Eintrag in Datenbank aktualisieren',
|
||
'Updating prices of existing entry in database' => 'Preis des Eintrags in der Datenbank wird aktualisiert',
|
||
'Updating the client fields in the database "#1" on host "#2:#3" failed.' => 'Die Aktualisierung der Mandantenfelder in der Datenbank "#1" auf Host "#2:#3" schlug fehl.',
|
||
'Uploaded on #1, size #2 kB' => 'Am #1 hochgeladen, Größe #2 kB',
|
||
'Use As New' => 'Als neu verwenden',
|
||
'Use Templates' => 'Benutze Vorlagen',
|
||
... | ... | |
'User' => 'Benutzer',
|
||
'User Config' => 'Einstellungen',
|
||
'User Login' => 'Als Benutzer anmelden',
|
||
'User access' => 'Benutzerzugriff',
|
||
'User data migration' => 'Benutzerdatenmigration',
|
||
'User deleted!' => 'Benutzer gelöscht!',
|
||
'User login' => 'Benutzeranmeldung',
|
||
'User name' => 'Benutzername',
|
||
'User saved!' => 'Benutzer gespeichert!',
|
||
'Username' => 'Benutzername',
|
||
'Users in this group' => 'BenutzerInnen in dieser Gruppe',
|
||
'Users with access' => 'Benutzer mit Zugriff',
|
||
'Users with access to this client' => 'Benutzer mit Zugriff auf diesen Mandanten',
|
||
'Ust-IDNr' => 'USt-IdNr.',
|
||
'VAT ID' => 'UStdID-Nr',
|
||
'Valid' => 'Gültig',
|
||
'Valid from' => 'Gültig ab',
|
||
'Valid until' => 'gültig bis',
|
||
... | ... | |
'You have to enter a company name in your user preferences (see the "Program" menu, "Preferences").' => 'Sie müssen einen Firmennamen in Ihren Einstellungen angeben (siehe Menü "Programm", "Einstellungen").',
|
||
'You have to enter the SEPA creditor ID in your user preferences (see the "Program" menu, "Preferences").' => 'Sie müssen die SEPA-Kreditoren-Identifikation in Ihren Einstellungen angeben (siehe Menü "Programm", "Einstellungen").',
|
||
'You have to fill in at least an account number, the bank code, the IBAN and the BIC.' => 'Sie müssen zumindest die Kontonummer, die Bankleitzahl, die IBAN und den BIC angeben.',
|
||
'You have to grant users access to one or more clients.' => 'Benutzern muss dann Zugriff auf einzelne Mandanten gewährt werden.',
|
||
'You have to specify a department.' => 'Sie müssen eine Abteilung wählen.',
|
||
'You have to specify an execution date for each antry.' => 'Sie müssen für jeden zu buchenden Eintrag ein Ausführungsdatum angeben.',
|
||
'You must chose a user.' => 'Sie müssen einen Benutzer auswählen.',
|
||
... | ... | |
'kivitendo Homepage' => 'Infos zu kivitendo',
|
||
'kivitendo administration' => 'kivitendo Administration',
|
||
'kivitendo can fix these problems automatically.' => 'kivitendo kann solche Probleme automatisch beheben.',
|
||
'kivitendo has been extended to handle multiple clients within a single installation.' => 'kivitendo wurde um Mandantenfähigkeit erweitert.',
|
||
'kivitendo has been switched to group-based access restrictions.' => 'kivitendo wurde auf eine gruppenbasierte Benutzerzugriffsverwaltung umgestellt.',
|
||
'kivitendo has found one or more problems in the general ledger.' => 'kivitendo hat ein oder mehrere Probleme im Hauptbuch gefunden.',
|
||
'kivitendo is about to update the database [ #1 ].' => 'kivitendo wird gleich die Datenbank [ #1 ] aktualisieren.',
|
||
'kivitendo is now able to manage warehouses instead of just tracking the amount of goods in your system.' => 'kivitendo enthält jetzt auch echte Lagerverwaultung anstatt reiner Mengenzählung.',
|
sql/Pg-upgrade2-auth/clients.pl | ||
---|---|---|
# @tag: clients
|
||
# @description: Einführung von Mandaten
|
||
# @depends: release_3_0_0
|
||
# @ignore: 0
|
||
package SL::DBUpgrade2::clients;
|
||
|
||
use strict;
|
||
use utf8;
|
||
|
||
use parent qw(SL::DBUpgrade2::Base);
|
||
|
||
use List::MoreUtils qw(any all);
|
||
use List::Util qw(first);
|
||
|
||
use SL::DBConnect;
|
||
use SL::DBUtils;
|
||
use SL::Template;
|
||
use SL::Helper::Flash;
|
||
|
||
use Rose::Object::MakeMethods::Generic (
|
||
scalar => [ qw(clients) ],
|
||
'scalar --get_set_init' => [ qw(users groups templates auth_db_settings data_dbhs) ],
|
||
);
|
||
|
||
sub init_users {
|
||
my ($self) = @_;
|
||
my @users = selectall_hashref_query($::form, $self->dbh, qq|SELECT * FROM auth."user" ORDER BY lower(login)|);
|
||
|
||
foreach my $user (@users) {
|
||
my @attributes = selectall_hashref_query($::form, $self->dbh, <<SQL, $user->{id});
|
||
SELECT cfg_key, cfg_value
|
||
FROM auth.user_config
|
||
WHERE user_id = ?
|
||
SQL
|
||
|
||
$user->{ $_->{cfg_key} } = $_->{cfg_value} for @attributes;
|
||
}
|
||
|
||
return \@users;
|
||
}
|
||
|
||
sub init_groups {
|
||
my ($self) = @_;
|
||
return [ selectall_hashref_query($::form, $self->dbh, qq|SELECT * FROM auth."group" ORDER BY lower(name)|) ];
|
||
}
|
||
|
||
sub init_templates {
|
||
my %templates = SL::Template->available_templates;
|
||
return $templates{print_templates};
|
||
}
|
||
|
||
sub init_auth_db_settings {
|
||
my $cfg = $::lx_office_conf{'authentication/database'};
|
||
return {
|
||
dbhost => $cfg->{host} || 'localhost',
|
||
dbport => $cfg->{port} || 5432,
|
||
dbname => $cfg->{name},
|
||
};
|
||
}
|
||
|
||
sub init_data_dbhs {
|
||
return [];
|
||
}
|
||
|
||
sub _clear_field {
|
||
my ($text) = @_;
|
||
|
||
$text ||= '';
|
||
$text =~ s/^\s+|\s+$//g;
|
||
|
||
return $text;
|
||
}
|
||
|
||
sub _group_into_clients {
|
||
my ($self) = @_;
|
||
|
||
my @match_fields = qw(dbhost dbport dbname);
|
||
my @copy_fields = (@match_fields, qw(address company co_ustid dbuser dbpasswd duns sepa_creditor_id taxnumber templates));
|
||
my @clients;
|
||
|
||
# Group users into clients. Users which have identical database
|
||
# settings (host, port and name) will be grouped. The other fields
|
||
# like tax number etc. are taken from the first user and only filled
|
||
# from user users if they're still unset.
|
||
foreach my $user (@{ $self->users }) {
|
||
$user->{$_} = _clear_field($user->{$_}) for @copy_fields;
|
||
|
||
my $existing_client = first { my $client = $_; all { ($user->{$_} || '') eq ($client->{$_} || '') } @match_fields } @clients;
|
||
|
||
if ($existing_client) {
|
||
push @{ $existing_client->{users} }, $user->{id};
|
||
$existing_client->{$_} ||= $user->{$_} for @copy_fields;
|
||
next;
|
||
}
|
||
|
||
push @clients, {
|
||
map({ $_ => $user->{$_} } @copy_fields),
|
||
name => $::locale->text('Client #1', scalar(@clients) + 1),
|
||
users => [ $user->{id} ],
|
||
groups => [ map { $_->{id} } @{ $self->groups } ],
|
||
enabled => 1,
|
||
};
|
||
}
|
||
|
||
# Ignore users (and therefore clients) for which no database
|
||
# configuration has been given.
|
||
@clients = grep { my $client = $_; any { $client->{$_} } @match_fields } @clients;
|
||
|
||
# If there's only one client set that one as default.
|
||
$clients[0]->{is_default} = 1 if scalar(@clients) == 1;
|
||
|
||
# Set a couple of defaults for database fields.
|
||
my $num = 0;
|
||
foreach my $client (@clients) {
|
||
$num += 1;
|
||
$client->{dbhost} ||= 'localhost';
|
||
$client->{dbport} ||= 5432;
|
||
$client->{templates} =~ s:templates/::;
|
||
}
|
||
|
||
$self->clients(\@clients);
|
||
}
|
||
|
||
sub _analyze {
|
||
my ($self, %params) = @_;
|
||
|
||
$self->_group_into_clients;
|
||
|
||
return $self->_do_convert if !@{ $self->clients };
|
||
|
||
print $::form->parse_html_template('dbupgrade/auth/clients', { SELF => $self });
|
||
|
||
return 2;
|
||
}
|
||
|
||
sub _verify_clients {
|
||
my ($self) = @_;
|
||
|
||
my (%names, @errors);
|
||
|
||
my $num = 0;
|
||
foreach my $client (@{ $self->clients }) {
|
||
$num += 1;
|
||
|
||
next if !$client->{enabled};
|
||
|
||
$client->{$_} = _clear_field($client->{$_}) for qw(address co_ustid company dbhost dbname dbpasswd dbport dbuser duns sepa_creditor_id taxnumber templates);
|
||
|
||
if (!$client->{name} || $names{ $client->{name} }) {
|
||
push @errors, $::locale->text('New client #1: The name must be unique and not empty.', $num);
|
||
}
|
||
|
||
$names{ $client->{name} } = 1;
|
||
|
||
if (any { !$client->{$_} } qw(dbhost dbport dbname dbuser)) {
|
||
push @errors, $::locale->text('New client #1: The database configuration fields "host", "port", "name" and "user" must not be empty.', $num);
|
||
}
|
||
}
|
||
|
||
return @errors;
|
||
}
|
||
|
||
sub _alter_auth_database_structure {
|
||
my ($self) = @_;
|
||
|
||
my @queries = (
|
||
qq|CREATE TABLE auth.clients (
|
||
id SERIAL PRIMARY KEY,
|
||
name TEXT NOT NULL UNIQUE,
|
||
dbhost TEXT NOT NULL,
|
||
dbport INTEGER NOT NULL DEFAULT 5432,
|
||
dbname TEXT NOT NULL,
|
||
dbuser TEXT NOT NULL,
|
||
dbpasswd TEXT NOT NULL,
|
||
is_default BOOLEAN NOT NULL DEFAULT FALSE,
|
||
|
||
UNIQUE (dbhost, dbport, dbname)
|
||
)|,
|
||
qq|CREATE TABLE auth.clients_users (
|
||
client_id INTEGER NOT NULL REFERENCES auth.clients (id),
|
||
user_id INTEGER NOT NULL REFERENCES auth."user" (id),
|
||
|
||
PRIMARY KEY (client_id, user_id)
|
||
)|,
|
||
qq|CREATE TABLE auth.clients_groups (
|
||
client_id INTEGER NOT NULL REFERENCES auth.clients (id),
|
||
group_id INTEGER NOT NULL REFERENCES auth."group" (id),
|
||
|
||
PRIMARY KEY (client_id, group_id)
|
||
)|,
|
||
);
|
||
|
||
$self->db_query($_, may_fail => 0) for @queries;
|
||
}
|
||
|
||
sub _alter_data_database_structure {
|
||
my ($self, $dbh) = @_;
|
||
|
||
my @queries = (
|
||
qq|ALTER TABLE defaults ADD COLUMN company TEXT|,
|
||
qq|ALTER TABLE defaults ADD COLUMN address TEXT|,
|
||
qq|ALTER TABLE defaults ADD COLUMN taxnumber TEXT|,
|
||
qq|ALTER TABLE defaults ADD COLUMN co_ustid TEXT|,
|
||
qq|ALTER TABLE defaults ADD COLUMN duns TEXT|,
|
||
qq|ALTER TABLE defaults ADD COLUMN sepa_creditor_id TEXT|,
|
||
qq|ALTER TABLE defaults ADD COLUMN templates TEXT|,
|
||
qq|INSERT INTO schema_info (tag, login) VALUES ('clients', 'admin')|,
|
||
);
|
||
|
||
foreach my $query (@queries) {
|
||
$dbh->do($query) || die $self->db_errstr($dbh);
|
||
}
|
||
}
|
||
|
||
sub _create_clients_in_auth_database {
|
||
my ($self) = @_;
|
||
|
||
my @client_columns = qw(name dbhost dbport dbname dbuser dbpasswd is_default);
|
||
my $q_client = qq|INSERT INTO auth.clients (| . join(', ', @client_columns) . qq|) VALUES (| . join(', ', ('?') x @client_columns) . qq|) RETURNING id|;
|
||
my $sth_client = $self->dbh->prepare($q_client) || die $self->db_errstr;
|
||
|
||
my $q_client_user = qq|INSERT INTO auth.clients_users (client_id, user_id) VALUES (?, ?)|;
|
||
my $sth_client_user = $self->dbh->prepare($q_client_user) || die $self->db_errstr;
|
||
|
||
my $q_client_group = qq|INSERT INTO auth.clients_groups (client_id, group_id) VALUES (?, ?)|;
|
||
my $sth_client_group = $self->dbh->prepare($q_client_group) || die $self->db_errstr;
|
||
|
||
foreach my $client (@{ $self->clients }) {
|
||
next unless $client->{enabled};
|
||
|
||
$client->{is_default} = $client->{is_default} ? 1 : 0;
|
||
|
||
$sth_client->execute(@{ $client }{ @client_columns }) || die;
|
||
my $client_id = $sth_client->fetch->[0];
|
||
|
||
$sth_client_user ->execute($client_id, $_) || die for @{ $client->{users} || [] };
|
||
$sth_client_group->execute($client_id, $_) || die for @{ $client->{groups} || [] };
|
||
}
|
||
|
||
$sth_client ->finish;
|
||
$sth_client_user ->finish;
|
||
$sth_client_group->finish;
|
||
}
|
||
|
||
sub _clean_auth_database {
|
||
my ($self) = @_;
|
||
|
||
my @keys_to_delete = qw(acs address admin anfragen angebote bestellungen businessnumber charset companies company co_ustid currency dbconnect dbdriver dbhost dbname dboptions dbpasswd dbport dbuser duns
|
||
einkaufsrechnungen in_numberformat lieferantenbestellungen login pdonumber printer rechnungen role sdonumber sepa_creditor_id sid steuernummer taxnumber templates);
|
||
|
||
$self->dbh->do(qq|DELETE FROM auth.user_config WHERE cfg_key IN (| . join(', ', ('?') x @keys_to_delete) . qq|)|, undef, @keys_to_delete)
|
||
|| die $self->db_errstr;
|
||
}
|
||
|
||
sub _copy_fields_to_data_database {
|
||
my ($self, $client) = @_;
|
||
|
||
my $dbh = SL::DBConnect->connect('dbi:Pg:dbname=' . $client->{dbname} . ';host=' . $client->{dbhost} . ';port=' . $client->{dbport},
|
||
$client->{dbuser}, $client->{dbpasswd},
|
||
SL::DBConnect->get_options(AutoCommit => 0));
|
||
if (!$dbh) {
|
||
die join("\n",
|
||
$::locale->text('The connection to the configured client database "#1" on host "#2:#3" failed.', $client->{dbname}, $client->{dbhost}, $client->{dbport}),
|
||
$::locale->text('Please correct the settings and try again or deactivate that client.'),
|
||
$::locale->text('Error message from the database: #1', $self->db_errstr('DBI')));
|
||
}
|
||
|
||
my ($has_been_applied) = $dbh->selectrow_array(qq|SELECT tag FROM schema_info WHERE tag = 'clients'|);
|
||
|
||
if (!$has_been_applied) {
|
||
$self->_alter_data_database_structure($dbh);
|
||
}
|
||
|
||
my @columns = qw(company address taxnumber co_ustid duns sepa_creditor_id templates);
|
||
my $query = join ', ', map { "$_ = ?" } @columns;
|
||
my @values = @{ $client }{ @columns };
|
||
|
||
if (!$dbh->do(qq|UPDATE defaults SET $query|, undef, @values)) {
|
||
die join("\n",
|
||
$::locale->text('Updating the client fields in the database "#1" on host "#2:#3" failed.', $client->{dbname}, $client->{dbhost}, $client->{dbport}),
|
||
$::locale->text('Please correct the settings and try again or deactivate that client.'),
|
||
$::locale->text('Error message from the database: #1', $self->db_errstr('DBI')));
|
||
}
|
||
|
||
$self->data_dbhs([ @{ $self->data_dbhs }, $dbh ]);
|
||
}
|
||
|
||
sub _commit_data_database_changes {
|
||
my ($self) = @_;
|
||
|
||
foreach my $dbh (@{ $self->data_dbhs }) {
|
||
$dbh->commit;
|
||
$dbh->disconnect;
|
||
}
|
||
}
|
||
|
||
sub _do_convert {
|
||
my ($self) = @_;
|
||
|
||
# Skip clients that are not enabled. Clean fields.
|
||
my $num = 0;
|
||
foreach my $client (@{ $self->clients }) {
|
||
$num += 1;
|
||
|
||
next if !$client->{enabled};
|
||
|
||
$client->{$_} = _clear_field($client->{$_}) for qw(dbhost dbport dbname dbuser dbpasswd address company co_ustid dbuser dbpasswd duns sepa_creditor_id taxnumber templates);
|
||
$client->{templates} = 'templates/' . $client->{templates};
|
||
}
|
||
|
||
$self->_copy_fields_to_data_database($_) for grep { $_->{enabled} } @{ $self->clients };
|
||
|
||
$self->_alter_auth_database_structure;
|
||
$self->_create_clients_in_auth_database;
|
||
$self->_clean_auth_database;
|
||
|
||
$self->_commit_data_database_changes;
|
||
|
||
return 1;
|
||
}
|
||
|
||
sub run {
|
||
my ($self) = @_;
|
||
|
||
return $self->_analyze if !$::form->{clients} || !@{ $::form->{clients} };
|
||
|
||
$self->clients($::form->{clients});
|
||
|
||
my @errors = $self->_verify_clients;
|
||
|
||
return $self->_do_convert if !@errors;
|
||
|
||
flash('error', @errors);
|
||
|
||
print $::form->parse_html_template('dbupgrade/auth/clients', { SELF => $self });
|
||
|
||
return 1;
|
||
}
|
||
|
||
1;
|
templates/webpages/dbupgrade/auth/clients.html | ||
---|---|---|
[%- USE LxERP -%][%- USE L -%]
|
||
|
||
[%- INCLUDE 'common/flash.html' %]
|
||
|
||
[% L.javascript_tag('jquery.selectboxes', 'jquery.multiselect2side') %]
|
||
|
||
<h1>[%- LxERP.t8("Introduction of clients") %]</h1>
|
||
|
||
<p>
|
||
[% LxERP.t8("kivitendo has been extended to handle multiple clients within a single installation.") %]
|
||
[% LxERP.t8("Therefore several settings that had to be made for each user in the past have been consolidated into the client configuration.") %]
|
||
[% LxERP.t8("You have to grant users access to one or more clients.") %]
|
||
[% LxERP.t8("The user can chose which client to connect to during login.") %]
|
||
</p>
|
||
|
||
<p>
|
||
[% LxERP.t8("The access rights a user has within a client instance is still governed by his group membership.") %]
|
||
[% LxERP.t8("Only groups that have been configured for the client the user logs in to will be considered.") %]
|
||
</p>
|
||
|
||
<p>
|
||
[% LxERP.t8("The following list has been generated automatically from existing users collapsing users with identical settings into a single entry.") %]
|
||
[% LxERP.t8("Please select which client configurations you want to create.") %]
|
||
[% LxERP.t8("The 'name' is the field shown to the user during login.") %]
|
||
[% LxERP.t8("It can be changed later but must be unique within the installation.") %]
|
||
</p>
|
||
|
||
<form method="post" action="admin.pl">
|
||
[%- FOREACH client = SELF.clients %]
|
||
[%- L.hidden_tag("clients[+].dummy", 1) %]
|
||
|
||
<h2>[%- L.checkbox_tag("clients[].enabled", label=LxERP.t8("Create new client #1", loop.count), checked=client.enabled) %]</h2>
|
||
|
||
<table>
|
||
<tr>
|
||
<th colspan="6">[%- LxERP.t8("General settings") %]</th>
|
||
</tr>
|
||
|
||
<tr>
|
||
<td align="right" valign="top">[%- LxERP.t8("Client name") %]:</td>
|
||
<td valign="top">[%- L.input_tag("clients[].name", client.name) %]</td>
|
||
|
||
<td align="right" valign="top">[%- LxERP.t8("Company name") %]:</td>
|
||
<td valign="top">[%- L.input_tag("clients[].company", client.company) %]</td>
|
||
|
||
<td align="right" valign="top">[%- LxERP.t8("Address") %]:</td>
|
||
<td valign="top">[%- L.textarea_tag("clients[].address", client.address, rows=4, cols=40) %]</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<td align="right">[%- LxERP.t8("Tax number") %]:</td>
|
||
<td>[%- L.input_tag("clients[].taxnumber", client.taxnumber) %]</td>
|
||
|
||
<td align="right">[%- LxERP.t8("VAT ID") %]:</td>
|
||
<td>[%- L.input_tag("clients[].co_ustid", client.co_ustid) %]</td>
|
||
|
||
<td align="right">[%- LxERP.t8("DUNS-Nr") %]:</td>
|
||
<td>[%- L.input_tag("clients[].duns", client.duns) %]</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<td align="right">[%- LxERP.t8("SEPA creditor ID") %]:</td>
|
||
<td colspan="5">[%- L.input_tag("clients[].sepa_creditor_id", client.sepa_creditor_id) %]</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<td align="right">[%- LxERP.t8("Print templates") %]:</td>
|
||
<td colspan="5">[%- L.select_tag("clients[].templates", SELF.templates, default=client.templates) %]</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<th colspan="6">[%- LxERP.t8("User access") %]</th>
|
||
</tr>
|
||
|
||
<tr>
|
||
<td valign="top">[%- LxERP.t8("Users with access to this client") %]:</td>
|
||
|
||
<td valign="top" colspan="6" class="clearfix">
|
||
[% L.select_tag('clients[].users[]', SELF.users, id='users_multi_' _ loop.count, value_key='id', title_key='login', default=client.users, multiple=1) %]
|
||
</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<td valign="top">[%- LxERP.t8("Groups that are valid for this client for access rights") %]:</td>
|
||
|
||
<td valign="top" colspan="6" class="clearfix">
|
||
[% L.select_tag('clients[].groups[]', SELF.groups, id='groups_multi_' _ loop.count, value_key='id', title_key='name', default=client.groups, multiple=1) %]
|
||
</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<th colspan="6">[%- LxERP.t8("Database settings") %]</th>
|
||
</tr>
|
||
|
||
<tr>
|
||
<td align="right">[%- LxERP.t8("Database Host") %]:</td>
|
||
<td>[%- L.input_tag("clients[].dbhost", client.dbhost) %]</td>
|
||
|
||
<td align="right">[%- LxERP.t8("Port") %]:</td>
|
||
<td>[%- L.input_tag("clients[].dbport", (client.dbport || 5432)) %]</td>
|
||
|
||
<td align="right">[%- LxERP.t8("Database name") %]:</td>
|
||
<td>[%- L.input_tag("clients[].dbname", client.dbname) %]</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<td align="right">[%- LxERP.t8("User") %]:</td>
|
||
<td>[%- L.input_tag("clients[].dbuser", client.dbuser) %]</td>
|
||
|
||
<td align="right">[%- LxERP.t8("Password") %]:</td>
|
||
<td>[%- L.input_tag("clients[].dbpasswd", client.dbpasswd) %]</td>
|
||
</tr>
|
||
|
||
</table>
|
||
|
||
[% L.multiselect2side('users_multi_' _ loop.count, labelsx => LxERP.t8('All users'), labeldx => LxERP.t8('Users with access')) %]
|
||
[% L.multiselect2side('groups_multi_' _ loop.count, labelsx => LxERP.t8('All groups'), labeldx => LxERP.t8('Groups valid for this client')) %]
|
||
[%- END %]
|
||
|
||
<p>
|
||
[%- L.hidden_tag('action', 'list_users') %]
|
||
[% L.submit_tag('dummy', LxERP.t8('Continue')) %]
|
||
</p>
|
||
</form>
|
Auch abrufbar als: Unified diff
Datenbankupgradescript für Mandanten