2 # @description: Einführung von Mandanten
 
   3 # @depends: release_3_0_0
 
   5 package SL::DBUpgrade2::Auth::clients;
 
  10 use parent qw(SL::DBUpgrade2::Base);
 
  12 use List::MoreUtils qw(any all);
 
  13 use List::Util qw(first);
 
  18 use SL::Helper::Flash;
 
  20 use Rose::Object::MakeMethods::Generic (
 
  21   scalar                  => [ qw(clients) ],
 
  22   'scalar --get_set_init' => [ qw(users groups templates auth_db_settings data_dbhs) ],
 
  27   my @users  = selectall_hashref_query($::form, $self->dbh, qq|SELECT * FROM auth."user" ORDER BY lower(login)|);
 
  29   foreach my $user (@users) {
 
  30     my @attributes = selectall_hashref_query($::form, $self->dbh, <<SQL, $user->{id});
 
  31      SELECT cfg_key, cfg_value
 
  36     $user->{ $_->{cfg_key} } = $_->{cfg_value} for @attributes;
 
  44   return [ selectall_hashref_query($::form, $self->dbh, qq|SELECT * FROM auth."group" ORDER BY lower(name)|) ];
 
  48   my %templates = SL::Template->available_templates;
 
  49   return $templates{print_templates};
 
  52 sub init_auth_db_settings {
 
  53   my $cfg = $::lx_office_conf{'authentication/database'};
 
  55     dbhost => $cfg->{host} || 'localhost',
 
  56     dbport => $cfg->{port} || 5432,
 
  57     dbname => $cfg->{name},
 
  69   $text   =~ s/^\s+|\s+$//g;
 
  74 sub _group_into_clients {
 
  77   my @match_fields = qw(dbhost dbport dbname);
 
  78   my @copy_fields  = (@match_fields, qw(address company co_ustid dbuser dbpasswd duns sepa_creditor_id taxnumber templates));
 
  81   # Group users into clients. Users which have identical database
 
  82   # settings (host, port and name) will be grouped. The other fields
 
  83   # like tax number etc. are taken from the first user and only filled
 
  84   # from user users if they're still unset.
 
  85   foreach my $user (@{ $self->users }) {
 
  86     $user->{$_} = _clear_field($user->{$_}) for @copy_fields;
 
  88     my $existing_client = first { my $client = $_; all { ($user->{$_} || '') eq ($client->{$_} || '') } @match_fields } @clients;
 
  90     if ($existing_client) {
 
  91       push @{ $existing_client->{users} }, $user->{id};
 
  92       $existing_client->{$_} ||= $user->{$_} for @copy_fields;
 
  97       map({ $_ => $user->{$_} } @copy_fields),
 
  98       users   => [ $user->{id} ],
 
  99       groups  => [ map { $_->{id} } @{ $self->groups } ],
 
 104   # Ignore users (and therefore clients) for which no database
 
 105   # configuration has been given.
 
 106   @clients = grep { my $client = $_; any { $client->{$_} } @match_fields } @clients;
 
 108   # If there's only one client set that one as default.
 
 109   $clients[0]->{is_default} = 1 if scalar(@clients) == 1;
 
 111   # Set a couple of defaults for database fields.
 
 113   foreach my $client (@clients) {
 
 115     $client->{name}    ||= $::locale->text('Client #1', $num);
 
 116     $client->{dbhost}  ||= 'localhost';
 
 117     $client->{dbport}  ||= 5432;
 
 118     $client->{templates} =~ s:templates/::;
 
 121   $self->clients(\@clients);
 
 125   my ($self, %params) = @_;
 
 127   $self->_group_into_clients;
 
 129   return $self->_do_convert if !@{ $self->clients };
 
 131   print $::form->parse_html_template('dbupgrade/auth/clients', { SELF => $self });
 
 136 sub _verify_clients {
 
 139   my (%names, @errors);
 
 142   foreach my $client (@{ $self->clients }) {
 
 145     next if !$client->{enabled};
 
 147     $client->{$_} = _clear_field($client->{$_}) for qw(address co_ustid company dbhost dbname dbpasswd dbport dbuser duns sepa_creditor_id taxnumber templates);
 
 149     if (!$client->{name} || $names{ $client->{name} }) {
 
 150       push @errors, $::locale->text('New client #1: The name must be unique and not empty.', $num);
 
 153     $names{ $client->{name} } = 1;
 
 155     if (any { !$client->{$_} } qw(dbhost dbport dbname dbuser)) {
 
 156       push @errors, $::locale->text('New client #1: The database configuration fields "host", "port", "name" and "user" must not be empty.', $num);
 
 163 sub _alter_auth_database_structure {
 
 167     qq|CREATE TABLE auth.clients (
 
 168          id         SERIAL  PRIMARY KEY,
 
 169          name       TEXT    NOT NULL UNIQUE,
 
 170          dbhost     TEXT    NOT NULL,
 
 171          dbport     INTEGER NOT NULL DEFAULT 5432,
 
 172          dbname     TEXT    NOT NULL,
 
 173          dbuser     TEXT    NOT NULL,
 
 174          dbpasswd   TEXT    NOT NULL,
 
 175          is_default BOOLEAN NOT NULL DEFAULT FALSE,
 
 177          UNIQUE (dbhost, dbport, dbname)
 
 179     qq|CREATE TABLE auth.clients_users (
 
 180          client_id INTEGER NOT NULL REFERENCES auth.clients (id),
 
 181          user_id   INTEGER NOT NULL REFERENCES auth."user"  (id),
 
 183          PRIMARY KEY (client_id, user_id)
 
 185     qq|CREATE TABLE auth.clients_groups (
 
 186          client_id INTEGER NOT NULL REFERENCES auth.clients (id),
 
 187          group_id  INTEGER NOT NULL REFERENCES auth."group" (id),
 
 189          PRIMARY KEY (client_id, group_id)
 
 193   $self->db_query($_, may_fail => 0) for @queries;
 
 196 sub _alter_data_database_structure {
 
 197   my ($self, $dbh) = @_;
 
 200     qq|ALTER TABLE defaults ADD COLUMN company          TEXT|,
 
 201     qq|ALTER TABLE defaults ADD COLUMN address          TEXT|,
 
 202     qq|ALTER TABLE defaults ADD COLUMN taxnumber        TEXT|,
 
 203     qq|ALTER TABLE defaults ADD COLUMN co_ustid         TEXT|,
 
 204     qq|ALTER TABLE defaults ADD COLUMN duns             TEXT|,
 
 205     qq|ALTER TABLE defaults ADD COLUMN sepa_creditor_id TEXT|,
 
 206     qq|ALTER TABLE defaults ADD COLUMN templates        TEXT|,
 
 207     qq|INSERT INTO schema_info (tag, login) VALUES ('clients', 'admin')|,
 
 210   foreach my $query (@queries) {
 
 211     $dbh->do($query) || die $self->db_errstr($dbh);
 
 215 sub _create_clients_in_auth_database {
 
 218   my @client_columns   = qw(name dbhost dbport dbname dbuser dbpasswd is_default);
 
 219   my $q_client         = qq|INSERT INTO auth.clients (| . join(', ', @client_columns) . qq|) VALUES (| . join(', ', ('?') x @client_columns) . qq|) RETURNING id|;
 
 220   my $sth_client       = $self->dbh->prepare($q_client) || die $self->db_errstr;
 
 222   my $q_client_user    = qq|INSERT INTO auth.clients_users (client_id, user_id) VALUES (?, ?)|;
 
 223   my $sth_client_user  = $self->dbh->prepare($q_client_user) || die $self->db_errstr;
 
 225   my $q_client_group   = qq|INSERT INTO auth.clients_groups (client_id, group_id) VALUES (?, ?)|;
 
 226   my $sth_client_group = $self->dbh->prepare($q_client_group) || die $self->db_errstr;
 
 228   foreach my $client (@{ $self->clients }) {
 
 229     next unless $client->{enabled};
 
 231     $client->{is_default} = $client->{is_default} ? 1 : 0;
 
 233     $sth_client->execute(@{ $client }{ @client_columns }) || die;
 
 234     my $client_id = $sth_client->fetch->[0];
 
 236     $sth_client_user ->execute($client_id, $_) || die for @{ $client->{users}  || [] };
 
 237     $sth_client_group->execute($client_id, $_) || die for @{ $client->{groups} || [] };
 
 240   $sth_client      ->finish;
 
 241   $sth_client_user ->finish;
 
 242   $sth_client_group->finish;
 
 245 sub _clean_auth_database {
 
 248   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
 
 249                           einkaufsrechnungen in_numberformat lieferantenbestellungen login pdonumber printer rechnungen role sdonumber sepa_creditor_id sid steuernummer taxnumber templates);
 
 251   $self->dbh->do(qq|DELETE FROM auth.user_config WHERE cfg_key IN (| . join(', ', ('?') x @keys_to_delete) . qq|)|, undef, @keys_to_delete)
 
 252     || die $self->db_errstr;
 
 255 sub _copy_fields_to_data_database {
 
 256   my ($self, $client) = @_;
 
 258   my $dbh = SL::DBConnect->connect('dbi:Pg:dbname=' . $client->{dbname} . ';host=' . $client->{dbhost} . ';port=' . $client->{dbport},
 
 259                                    $client->{dbuser}, $client->{dbpasswd},
 
 260                                    SL::DBConnect->get_options(AutoCommit => 0));
 
 263              $::locale->text('The connection to the configured client database "#1" on host "#2:#3" failed.', $client->{dbname}, $client->{dbhost}, $client->{dbport}),
 
 264              $::locale->text('Please correct the settings and try again or deactivate that client.'),
 
 265              $::locale->text('Error message from the database: #1', $self->db_errstr('DBI')));
 
 268   my ($has_been_applied) = $dbh->selectrow_array(qq|SELECT tag FROM schema_info WHERE tag = 'clients'|);
 
 270   if (!$has_been_applied) {
 
 271     $self->_alter_data_database_structure($dbh);
 
 274   my @columns = qw(company address taxnumber co_ustid duns sepa_creditor_id templates);
 
 275   my $query   = join ', ', map { "$_ = ?" } @columns;
 
 276   my @values  = @{ $client }{ @columns };
 
 278   if (!$dbh->do(qq|UPDATE defaults SET $query|, undef, @values)) {
 
 280              $::locale->text('Updating the client fields in the database "#1" on host "#2:#3" failed.', $client->{dbname}, $client->{dbhost}, $client->{dbport}),
 
 281              $::locale->text('Please correct the settings and try again or deactivate that client.'),
 
 282              $::locale->text('Error message from the database: #1', $self->db_errstr('DBI')));
 
 285   $self->data_dbhs([ @{ $self->data_dbhs }, $dbh ]);
 
 288 sub _commit_data_database_changes {
 
 291   foreach my $dbh (@{ $self->data_dbhs }) {
 
 300   # Skip clients that are not enabled. Clean fields.
 
 302   foreach my $client (@{ $self->clients }) {
 
 305     next if !$client->{enabled};
 
 307     $client->{$_}        = _clear_field($client->{$_}) for qw(dbhost dbport dbname dbuser dbpasswd address company co_ustid dbuser dbpasswd duns sepa_creditor_id taxnumber templates);
 
 308     $client->{templates} = 'templates/' . $client->{templates};
 
 311   $self->_copy_fields_to_data_database($_) for grep { $_->{enabled} } @{ $self->clients };
 
 313   $self->_alter_auth_database_structure;
 
 314   $self->_create_clients_in_auth_database;
 
 315   $self->_clean_auth_database;
 
 317   $self->_commit_data_database_changes;
 
 325   return $self->_analyze if !$::form->{clients} || !@{ $::form->{clients} };
 
 327   $self->clients($::form->{clients});
 
 329   my @errors = $self->_verify_clients;
 
 331   return $self->_do_convert if !@errors;
 
 333   flash('error', @errors);
 
 335   print $::form->parse_html_template('dbupgrade/auth/clients', { SELF => $self });