Bankkonten auf Controller umgestellt
authorG. Richardson <information@kivitendo-premium.de>
Sun, 12 Apr 2015 19:55:45 +0000 (21:55 +0200)
committerG. Richardson <information@kivitendo-premium.de>
Tue, 5 May 2015 07:45:02 +0000 (09:45 +0200)
Außerdem wurde neue Datenbankspalten eingeführt: obsolete, sortkey

In Vorbereitung auf den Kontenabgleich wurden zwei neue Spalten
eingeführt:
reconciliation_starting_balance
reconciliation_starting_date

Damit kann man einstellen, ab welchem Datum der Kontenabgleich für das
jeweilige Konto beginnen soll, und, für den Abgleich des Gesamtsaldos,
welchen Saldo das Konto zu dem Zeitpunkt hatte. Dies ist nützlich, wenn
man mit dem Kontenabgleich im laufenden Betrieb anfangen möchte, aber
nicht alle Buchungen der Vergangenheit nachträglich abgleichen möchte.

15 files changed:
SL/BankAccount.pm [deleted file]
SL/Controller/BankAccount.pm [new file with mode: 0644]
SL/DB/BankAccount.pm
SL/DB/MetaSetup/BankAccount.pm
bin/mozilla/bankaccounts.pl [deleted file]
js/locale/de.js
locale/de/all
menus/erp.ini
sql/Pg-upgrade2/bank_accounts_unique_chart_constraint.sql [new file with mode: 0644]
sql/Pg-upgrade2/bankaccounts_reconciliation.sql [new file with mode: 0644]
sql/Pg-upgrade2/bankaccounts_sortkey_and_obsolete.sql [new file with mode: 0644]
templates/webpages/bankaccounts/bank_account_display_form.html [deleted file]
templates/webpages/bankaccounts/bank_account_list_bottom.html [deleted file]
templates/webpages/bankaccounts/form.html [new file with mode: 0644]
templates/webpages/bankaccounts/list.html [new file with mode: 0644]

diff --git a/SL/BankAccount.pm b/SL/BankAccount.pm
deleted file mode 100644 (file)
index 3178b2a..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-package SL::BankAccount;
-
-use strict;
-
-use SL::DBUtils;
-
-sub save {
-  $main::lxdebug->enter_sub();
-
-  my $self     = shift;
-  my %params   = @_;
-
-  my $myconfig = \%main::myconfig;
-  my $form     = $main::form;
-
-  my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
-
-  if (!$params{id}) {
-    ($params{id}) = selectfirst_array_query($form, $dbh, qq|SELECT nextval('id')|);
-    do_query($form, $dbh, qq|INSERT INTO bank_accounts (id, chart_id)
-                             VALUES (?, (SELECT id FROM chart LIMIT 1))|, conv_i($params{id}));
-  }
-
-  my $query =
-    qq|UPDATE bank_accounts
-       SET name= ?, account_number = ?, bank_code = ?, bank = ?, iban = ?, bic = ?, chart_id = ?
-       WHERE id = ?|;
-  my @values = (@params{qw(name account_number bank_code bank iban bic)}, conv_i($params{chart_id}), conv_i($params{id}));
-
-  do_query($form, $dbh, $query, @values);
-
-  $dbh->commit() unless ($params{dbh});
-
-  $main::lxdebug->leave_sub();
-
-  return $params{id};
-}
-
-sub retrieve {
-  $main::lxdebug->enter_sub();
-
-  my $self     = shift;
-  my %params   = @_;
-
-  Common::check_params(\%params, qw(id));
-
-  my $myconfig = \%main::myconfig;
-  my $form     = $main::form;
-
-  my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
-
-  my $query    = qq|SELECT * FROM bank_accounts WHERE id = ?|;
-  my $account  = selectfirst_hashref_query($form, $dbh, $query, conv_i($params{id}));
-
-  $main::lxdebug->leave_sub();
-
-  return $account;
-}
-
-sub delete {
-  $::lxdebug->enter_sub();
-
-  my $self     = shift;
-  my %params   = @_;
-
-  Common::check_params(\%params, qw(id));
-
-  my $dbh = $params{dbh} || $::form->get_standard_dbh(%::myconfig);
-
-  my $query = '
-    DELETE
-    FROM bank_accounts
-    WHERE id = ?';
-
-  do_query($::form, $dbh, $query, conv_i($params{id}));
-
-  $dbh->commit();
-
-  $::lxdebug->leave_sub();
-}
-
-sub list {
-  $main::lxdebug->enter_sub();
-
-  my $self     = shift;
-  my %params   = @_;
-
-  my $myconfig = \%main::myconfig;
-  my $form     = $main::form;
-
-  my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
-
-  my %sort_columns = (
-    'name'              => [ 'ba.name', ],
-    'account_number'    => [ 'ba.account_number', ],
-    'bank_code'         => [ 'ba.bank_code', 'ba.account_number', ],
-    'bank'              => [ 'ba.bank',      'ba.account_number', ],
-    'iban'              => [ 'ba.iban',      'ba.account_number', ],
-    'bic'               => [ 'ba.bic',       'ba.account_number', ],
-    'chart_accno'       => [ 'c.accno', ],
-    'chart_description' => [ 'c.description', ],
-    );
-
-  my %sort_spec = create_sort_spec('defs' => \%sort_columns, 'default' => 'bank', 'column' => $params{sortorder}, 'dir' => $params{sortdir});
-
-  my $query =
-    qq|SELECT ba.id, ba.name, ba.account_number, ba.bank_code, ba.bank, ba.iban, ba.bic, ba.chart_id,
-         c.accno AS chart_accno, c.description AS chart_description
-       FROM bank_accounts ba
-       LEFT JOIN chart c ON (ba.chart_id = c.id)
-       ORDER BY $sort_spec{sql}|;
-
-  my $results = selectall_hashref_query($form, $dbh, $query);
-
-  $main::lxdebug->leave_sub();
-
-  return $results;
-}
-
-
-1;
diff --git a/SL/Controller/BankAccount.pm b/SL/Controller/BankAccount.pm
new file mode 100644 (file)
index 0000000..15d5db3
--- /dev/null
@@ -0,0 +1,122 @@
+ SL::Controller::BankAccount;
+
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use SL::Helper::Flash;
+use SL::Locale::String;
+use SL::DB::Default;
+use SL::DB::Manager::BankAccount;
+use SL::DB::Manager::BankTransaction;
+
+use Rose::Object::MakeMethods::Generic (
+  scalar                  => [ qw(bank_account) ],
+);
+
+__PACKAGE__->run_before('check_auth');
+__PACKAGE__->run_before('load_bank_account', only => [ qw(edit update delete) ]);
+
+#
+# actions
+#
+
+sub action_list {
+  my ($self) = @_;
+
+  $self->render('bankaccounts/list',
+                title           => t8('Bank accounts'),
+                BANKACCOUNTS    => SL::DB::Manager::BankAccount->get_all_sorted,
+               );
+}
+
+sub action_new {
+  my ($self) = @_;
+
+  $self->{bank_account} = SL::DB::BankAccount->new;
+  $self->render('bankaccounts/form',
+                 title => t8('Add bank account'));
+}
+
+sub action_edit {
+  my ($self) = @_;
+
+  $self->render('bankaccounts/form', title => t8('Edit bank account'));
+}
+
+sub action_create {
+  my ($self) = @_;
+
+  $self->{bank_account} = SL::DB::BankAccount->new;
+  $self->create_or_update;
+}
+
+sub action_update {
+  my ($self) = @_;
+  $self->create_or_update;
+}
+
+sub action_delete {
+  my ($self) = @_;
+
+  if ( $self->{bank_account}->{number_of_bank_transactions} > 0 ) {
+    flash_later('error', $::locale->text('The bank account has been used and cannot be deleted.'));
+  } elsif ( eval { $self->{bank_account}->delete; 1; } ) {
+    flash_later('info',  $::locale->text('The bank account has been deleted.'));
+  } else {
+    flash_later('error', $::locale->text('The bank account has been used and cannot be deleted.'));
+  };
+  $self->redirect_to(action => 'list');
+
+}
+
+sub action_reorder {
+  my ($self) = @_;
+
+  SL::DB::BankAccount->reorder_list(@{ $::form->{account_id} || [] });
+  $self->render(\'', { type => 'json' });
+}
+
+#
+# filters
+#
+
+sub check_auth {
+  $::auth->assert('config');
+}
+
+sub load_bank_account {
+  my ($self) = @_;
+
+  $self->{bank_account} = SL::DB::BankAccount->new(id => $::form->{id})->load;
+  $self->{bank_account}->{number_of_bank_transactions} = SL::DB::Manager::BankTransaction->get_all_count( query => [ local_bank_account_id => $self->{bank_account}->{id} ] );
+}
+
+#
+# helpers
+#
+
+sub create_or_update {
+  my ($self) = @_;
+  my $is_new = !$self->{bank_account}->id;
+
+  my $params = delete($::form->{bank_account}) || { };
+
+  $self->{bank_account}->assign_attributes(%{ $params });
+
+  my @errors = $self->{bank_account}->validate;
+
+  if (@errors) {
+    flash('error', @errors);
+    $self->render('bankaccounts/form',
+                   title => $is_new ? t8('Add bank account') : t8('Edit bank account'));
+    return;
+  }
+
+  $self->{bank_account}->save;
+
+  flash_later('info', $is_new ? t8('The bank account has been created.') : t8('The bank account has been saved.'));
+  $self->redirect_to(action => 'list');
+}
+
+1;
index 4ef201b..9ac755b 100644 (file)
@@ -6,9 +6,40 @@ package SL::DB::BankAccount;
 use strict;
 
 use SL::DB::MetaSetup::BankAccount;
+use SL::DB::Manager::BankAccount;
+use SL::DB::Helper::ActsAsList;
 
 __PACKAGE__->meta->initialize;
-# Creates get_all, get_all_count, get_all_iterator, delete_all and update_all.
-__PACKAGE__->meta->make_manager_class;
+
+sub validate {
+  my ($self) = @_;
+
+  my @errors;
+
+  if ( not $self->{chart_id} ) {
+    push @errors, $::locale->text('There is no connected chart.');
+  } else {
+    # check whether the assigned chart is valid or is already being used by
+    # another bank account (there is also a UNIQUE database constraint on
+    # chart_id)
+
+    my $chart_id = $self->chart_id;
+    my $chart = SL::DB::Chart->new( id => $chart_id );
+    if ( $chart->load(speculative => 1) ) {
+      my $linked_bank = SL::DB::Manager::BankAccount->find_by( chart_id => $chart_id );
+      if ( $linked_bank ) {
+        if ( not $self->{id} or ( $self->{id} && $linked_bank->id != $self->{id} )) {
+          push @errors, $::locale->text('The account #1 is already being used by bank account #2.', $chart->displayable_name, $linked_bank->{name});
+        };
+      };
+    } else {
+      push @errors, $::locale->text('The chart is not valid.');
+    };
+  };
+
+  push @errors, $::locale->text('The IBAN is missing.') unless $self->{iban};
+
+  return @errors;
+}
 
 1;
index 8057f44..d778138 100644 (file)
@@ -9,22 +9,29 @@ use base qw(SL::DB::Object);
 __PACKAGE__->meta->table('bank_accounts');
 
 __PACKAGE__->meta->columns(
-  account_number => { type => 'varchar', length => 100 },
-  bank           => { type => 'text' },
-  bank_code      => { type => 'varchar', length => 100 },
-  bic            => { type => 'varchar', length => 100 },
-  chart_id       => { type => 'integer', not_null => 1 },
-  iban           => { type => 'varchar', length => 100 },
-  id             => { type => 'integer', not_null => 1, sequence => 'id' },
-  name           => { type => 'text' },
+  account_number                  => { type => 'varchar', length => 100 },
+  bank                            => { type => 'text' },
+  bank_code                       => { type => 'varchar', length => 100 },
+  bic                             => { type => 'varchar', length => 100 },
+  chart_id                        => { type => 'integer', not_null => 1 },
+  iban                            => { type => 'varchar', length => 100 },
+  id                              => { type => 'integer', not_null => 1, sequence => 'id' },
+  name                            => { type => 'text' },
+  obsolete                        => { type => 'boolean' },
+  reconciliation_starting_balance => { type => 'numeric', precision => 15, scale => 5 },
+  reconciliation_starting_date    => { type => 'date' },
+  sortkey                         => { type => 'integer', not_null => 1 },
 );
 
 __PACKAGE__->meta->primary_key_columns([ 'id' ]);
 
+__PACKAGE__->meta->unique_keys([ 'chart_id' ]);
+
 __PACKAGE__->meta->foreign_keys(
   chart => {
     class       => 'SL::DB::Chart',
     key_columns => { chart_id => 'id' },
+    rel_type    => 'one to one',
   },
 );
 
diff --git a/bin/mozilla/bankaccounts.pl b/bin/mozilla/bankaccounts.pl
deleted file mode 100644 (file)
index 941e407..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-use strict;
-
-use List::MoreUtils qw(any);
-use POSIX qw(strftime);
-
-use SL::BankAccount;
-use SL::Chart;
-use SL::Form;
-use SL::ReportGenerator;
-
-require "bin/mozilla/common.pl";
-require "bin/mozilla/reportgenerator.pl";
-
-sub bank_account_add {
-  $main::lxdebug->enter_sub();
-
-  bank_account_display_form('account' => {});
-
-  $main::lxdebug->leave_sub();
-}
-
-sub bank_account_edit {
-  $main::lxdebug->enter_sub();
-
-  my %params  = @_;
-  my $form    = $main::form;
-
-  my $account = SL::BankAccount->retrieve('id' => $params{id} || $form->{id});
-
-  bank_account_display_form('account' => $account);
-
-  $main::lxdebug->leave_sub();
-}
-
-sub bank_account_delete {
-  $::lxdebug->enter_sub();
-
-  SL::BankAccount->delete(id => $::form->{account}{id});
-
-  print $::form->redirect_header('bankaccounts.pl?action=bank_account_list');
-
-  $::lxdebug->leave_sub();
-}
-
-sub bank_account_display_form {
-  $main::lxdebug->enter_sub();
-
-  my %params     = @_;
-  my $account    = $params{account} || {};
-  my $form       = $main::form;
-  my $locale     = $main::locale;
-
-  my $charts     = SL::Chart->list('link' => 'AP_paid');
-  my $label_sub  = sub { join '--', map { $_[0]->{$_} } qw(accno description) };
-
-  $form->{title} = $account->{id} ? $locale->text('Edit bank account') : $locale->text('Add bank account');
-
-  $form->header();
-  print $form->parse_html_template('bankaccounts/bank_account_display_form',
-                                   { 'CHARTS'      => $charts,
-                                     'account'     => $account,
-                                     'chart_label' => $label_sub,
-                                     'params'      => \%params });
-
-  $main::lxdebug->leave_sub();
-}
-
-sub bank_account_save {
-  $main::lxdebug->enter_sub();
-
-  my $form    = $main::form;
-  my $locale  = $main::locale;
-
-  my $account = $form->{account} && (ref $form->{account} eq 'HASH') ? $form->{account} : { };
-
-  if (any { !$account->{$_} } qw(name account_number bank_code iban bic)) {
-    bank_account_display_form('account' => $account,
-                              'error'   => $locale->text('You have to fill in at least a name, an account number, the bank code, the IBAN and the BIC.'));
-
-    $main::lxdebug->leave_sub();
-    return;
-  }
-
-  my $id = SL::BankAccount->save(%{ $account });
-
-  if ($form->{callback}) {
-    $form->redirect();
-
-  } else {
-    bank_account_edit('id' => $id);
-  }
-
-  $main::lxdebug->leave_sub();
-}
-
-
-sub bank_account_list {
-  $main::lxdebug->enter_sub();
-
-  my $form   = $main::form;
-  my $locale = $main::locale;
-
-  $form->{title}     = $locale->text('List of bank accounts');
-
-  $form->{sort}    ||= 'account_number';
-  $form->{sortdir}   = '1' if (!defined $form->{sortdir});
-
-  $form->{callback}  = build_std_url('action=bank_account_list', 'sort', 'sortdir');
-
-  my $accounts       = SL::BankAccount->list('sortorder' => $form->{sort},
-                                             'sortdir'   => $form->{sortdir});
-
-  my $report         = SL::ReportGenerator->new(\%main::myconfig, $form);
-
-  my $href           = build_std_url('action=bank_account_list');
-
-  my %column_defs = (
-    'name'           => { 'text' => $locale->text('Name'), },
-    'account_number' => { 'text' => $locale->text('Account number'), },
-    'bank_code'      => { 'text' => $locale->text('Bank code'), },
-    'bank'           => { 'text' => $locale->text('Bank'), },
-    'bic'            => { 'text' => $locale->text('BIC'), },
-    'iban'           => { 'text' => $locale->text('IBAN'), },
-  );
-
-  my @columns = qw(name account_number bank bank_code bic iban);
-
-  foreach my $name (@columns) {
-    my $sortdir                 = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
-    $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
-  }
-
-  $report->set_options('raw_bottom_info_text'  => $form->parse_html_template('bankaccounts/bank_account_list_bottom'),
-                       'std_column_visibility' => 1,
-                       'output_format'         => 'HTML',
-                       'title'                 => $form->{title},
-                       'attachment_basename'   => $locale->text('bankaccounts') . strftime('_%Y%m%d', localtime time),
-    );
-  $report->set_options_from_form();
-  $locale->set_numberformat_wo_thousands_separator(\%::myconfig) if lc($report->{options}->{output_format}) eq 'csv';
-
-  $report->set_columns(%column_defs);
-  $report->set_column_order(@columns);
-  $report->set_export_options('bank_account_list');
-  $report->set_sort_indicator($form->{sort}, $form->{sortdir});
-
-  my $edit_url = build_std_url('action=bank_account_edit', 'callback');
-
-  foreach my $account (@{ $accounts }) {
-    my $row = { map { $_ => { 'data' => $account->{$_} } } keys %{ $account } };
-
-    $row->{account_number}->{link} = $edit_url . '&id=' . E($account->{id});
-
-    $report->add_data($row);
-  }
-
-  $report->generate_with_headers();
-
-  $main::lxdebug->leave_sub();
-}
-
-sub dispatcher {
-  my $form = $main::form;
-
-  foreach my $action (qw(bank_account_save bank_account_delete)) {
-    if ($form->{"action_${action}"}) {
-      call_sub($action);
-      return;
-    }
-  }
-
-  $form->error($main::locale->text('No action defined.'));
-}
-
-1;
index 874ccc9..95b1357 100644 (file)
@@ -69,6 +69,7 @@ namespace("kivi").setupLocale({
 "The recipient, subject or body is missing.":"Der Empfäger, der Betreff oder der Text ist leer.",
 "The selected database is still configured for client \"#1\". If you delete the database that client will stop working until you re-configure it. Do you still want to delete the database?":"Die auswählte Datenbank ist noch für Mandant \"#1\" konfiguriert. Wenn Sie die Datenbank löschen, wird der Mandanten nicht mehr funktionieren, bis er anders konfiguriert wurde. Wollen Sie die Datenbank trotzdem löschen?",
 "There are still transfers not matching the qty of the delivery order. Stock operations can not be changed later. Do you really want to proceed?":"Einige der Lagerbewegungen sind nicht vollständig und Lagerbewegungen können nachträglich nicht mehr verändert werden. Wollen Sie wirklich fortfahren?",
+"There is no connected chart.":"Es fehlt ein verknüpftes Buchungskonto.",
 "There is one or more sections for which no part has been assigned yet; therefore creating the new record is not possible yet.":"Es gibt einen oder mehrere Abschnitte ohne Artikelzuweisung; daher kann der neue Beleg noch nicht erstellt werden.",
 "This sales order has an active configuration for periodic invoices. If you save then all subsequently created invoices will contain those changes as well, but not those that have already been created. Do you want to continue?":"Dieser Auftrag besitzt eine aktive Konfiguration für wiederkehrende Rechnungen. Wenn Sie jetzt speichern, so werden alle zukünftig hieraus erzeugten Rechnungen die Änderungen enthalten, nicht aber die bereits erzeugten Rechnungen. Wollen Sie speichern?",
 "Time/cost estimate actions":"Aktionen für Kosten-/Zeitabschätzung",
index ef144cb..620fe6c 100755 (executable)
@@ -1598,7 +1598,6 @@ $self->{texts} = {
   'New Purchase Price Rule'     => 'Neue Einkaufspreisregel',
   'New Sales Price Rule'        => 'Neue Verkaufspreisregel',
   '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',
@@ -2369,6 +2368,8 @@ $self->{texts} = {
   'Start the correction assistant' => 'Korrekturassistenten starten',
   'Startdate_coa'               => 'Gültig ab',
   'Starting Balance'            => 'Eröffnungsbilanzwerte',
+  'Starting balance'            => 'Anfangssaldo',
+  'Starting date'               => 'Anfangsdatum',
   'Starting the task server failed.' => 'Das Starten des Task-Servers schlug fehl.',
   'Starting with version 2.6.3 the configuration files in "config" have been consolidated.' => 'Ab Version 2.6.3 wurden die Konfiguration vereinfacht und es gibt nur noch eine Konfigurationsdatei im Verzeichnis config',
   'Statement'                   => 'Sammelrechnung',
@@ -2488,6 +2489,7 @@ $self->{texts} = {
   'The Buchungsgruppe has been created.' => 'Die Buchungsgruppe wurde erstellt.',
   'The Buchungsgruppe has been saved.' => 'Die Buchungsgruppe wurde gespeichert.',
   'The GL transaction #1 has been deleted.' => 'Die Dialogbuchung #1 wurde gelöscht.',
+  'The IBAN is missing.'        => 'Die IBAN fehlt.',
   'The LDAP server "#1:#2" is unreachable. Please check config/kivitendo.conf.' => 'Der LDAP-Server "#1:#2" ist nicht erreichbar. Bitte &uuml;berpr&uuml;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.',
@@ -2498,6 +2500,7 @@ $self->{texts} = {
   'The acceptance status is in use and cannot be deleted.' => 'Der Abnahmestatus wird verwendet und kann nicht gelöscht werden.',
   '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 #1 is already being used by bank account #2.' => 'Das Konto #1 wird schon von Bankkonto #2 benutzt.',
   '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 action is missing or invalid.' => 'Die action fehlt, oder sie ist ungültig.',
@@ -2513,6 +2516,10 @@ $self->{texts} = {
   'The background job has been deleted.' => 'Der Hintergrund-Job wurde gelöscht.',
   'The background job has been saved.' => 'Der Hintergrund-Job wurde gespeichert.',
   'The background job was executed successfully.' => 'Der Hintergrund-Job wurde erfolgreich ausgeführt.',
+  'The bank account has been created.' => 'Das Bankkonto wurde erstellt.',
+  'The bank account has been deleted.' => 'Das Bankkonto wurde gelöscht.',
+  'The bank account has been saved.' => 'Das Bankkonto wurde gespeichert',
+  'The bank account has been used and cannot be deleted.' => 'Das Bankkonto wurde benutzt und kann nicht gelöscht werden.',
   'The bank information must not be empty.' => 'Die Bankinformationen müssen vollständig ausgefüllt werden.',
   'The base file name without a path or an extension to be used for printing for this type of requirement spec.' => 'Der Basisdateiname ohne Pfadanteil oder Erweiterung, der bei Drucken dieses Pflichtenhefttyps verwendet wird.',
   'The base unit does not exist or it is about to be deleted in row %d.' => 'Die Basiseinheit in Zeile %d existiert nicht oder soll gel&ouml;scht werden.',
@@ -2527,6 +2534,7 @@ $self->{texts} = {
   'The business has been saved.' => 'Der Kunden-/Lieferantentyp wurde gespeichert.',
   'The business is in use and cannot be deleted.' => 'Der Kunden-/Lieferantentyp wird benutzt und kann nicht gelöscht werden.',
   'The changing of tax-o-matic account is NOT recommended, but if you do so please also (re)configure buchungsgruppen and reconfigure ALL charts which point to this tax-o-matic account. ' => 'Es wird nicht empfohlen Steuerkonten (Umsatzsteuer oder Vorsteuer) "umzuhängen", aber falls es gemacht wird, bitte auch entsprechend konsequent die Buchungsgruppen und die Konten die mit dieser Steuer verknüpft sind umkonfigurieren.',
+  'The chart is not valid.'     => 'Das Konto ist nicht gültig.',
   'The client could not be deleted.' => 'Der Mandant konnte nicht gelöscht werden.',
   'The client has been created.' => 'Der Mandant wurde angelegt.',
   'The client has been deleted.' => 'Der Mandant wurde gelöscht.',
@@ -2562,7 +2570,6 @@ $self->{texts} = {
   'The database user is missing.' => 'Der Datenbankbenutzer fehlt.',
   'The dataset #1 has been created.' => 'Die Datenbank #1 wurde angelegt.',
   'The dataset #1 has been deleted.' => 'Die Datenbank #1 wurde gelöscht.',
-  'The date is missing.'        => 'Das Datum fehlt',
   'The deductible amount'       => 'Der abziehbare Skontobetrag',
   'The default delivery plan only checks if all delivery orders have been created not if the goods are transferred. This feature will check if all the goods are transferred. Caveat: Only the state of the delivery orders are checked not partial transferred delivery orders (in technical terms: the table inventory is not checked' => 'Standardmässig wird beim Lieferplan überprüft, ob es eine vollständige Liefermenge über alle Lieferscheine gibt. Dies ist dann die Statusbedingung für geliefert oder nicht geliefert. Mit dieser Erweiterung wird geprüft ob die Lieferbelege auch wirklich ausgelagert sind oder nicht. Teilausgelagerte Lieferscheine werden allerdings nicht berücksichtigt (Technischer Hintergrund: Keine Überprüfung der Lagertabelle inventory).',
   'The default value depends on the variable type:' => 'Die Bedeutung des Standardwertes h&auml;ngt vom Variablentypen ab:',
@@ -2771,6 +2778,7 @@ $self->{texts} = {
   'There is an inconsistancy in your database.' => 'In Ihrer Datenbank sind Unstimmigkeiten vorhanden.',
   'There is at least one sales or purchase invoice for which kivitendo recorded an inventory transaction with taxkeys even though no tax was recorded.' => 'Es gibt mindestens eine Verkaufs- oder Einkaufsrechnung, für die kivitendo eine Warenbestandsbuchung ohne dazugehörige Steuerbuchung durchgeführt hat.',
   'There is at least one transaction for which the user has chosen a logically wrong taxkey.' => 'Es gibt mindestens eine Buchung, bei der ein logisch nicht passender Steuerschlüssel ausgewählt wurde.',
+  'There is no connected chart.' => 'Es fehlt ein verknüpftes Buchungskonto.',
   'There is not enough available of \'#1\' at warehouse \'#2\', bin \'#3\', #4, #5, for the transfer of #6.' => 'Von \'#1\' ist in Lager \'#2\', Lagerplatz \'#3\', #4, #5, nicht gen&uuml;gend eingelagert, um insgesamt #6 auszulagern.',
   'There is not enough available of \'#1\' at warehouse \'#2\', bin \'#3\', #4, for the transfer of #5.' => 'Von \'#1\' ist in Lager \'#2\', Lagerplatz \'#3\', #4 nicht gen&uuml;gend eingelagert, um insgesamt #5 auszulagern.',
   'There is not enough left of \'#1\' in bin \'#2\' for the removal of #3.' => 'In Lagerplatz \'#2\' ist nicht genug von \'#1\' vorhanden, um #3 zu entnehmen.',
index c968475..e337809 100644 (file)
@@ -619,8 +619,8 @@ module=am.pl
 action=list_tax
 
 [System--Bank accounts]
-module=bankaccounts.pl
-action=bank_account_list
+module=controller.pl
+action=BankAccount/list
 
 [System--Groups]
 module=pe.pl
diff --git a/sql/Pg-upgrade2/bank_accounts_unique_chart_constraint.sql b/sql/Pg-upgrade2/bank_accounts_unique_chart_constraint.sql
new file mode 100644 (file)
index 0000000..59e0b0a
--- /dev/null
@@ -0,0 +1,6 @@
+-- @tag: bank_accounts_unique_chart_constraint
+-- @description: Bankkonto - Constraint für eindeutiges Konto
+-- @depends: release_3_2_0
+-- @encoding: utf-8
+
+ALTER TABLE bank_accounts ADD CONSTRAINT chart_id_unique UNIQUE (chart_id);
diff --git a/sql/Pg-upgrade2/bankaccounts_reconciliation.sql b/sql/Pg-upgrade2/bankaccounts_reconciliation.sql
new file mode 100644 (file)
index 0000000..cb9e53b
--- /dev/null
@@ -0,0 +1,7 @@
+-- @tag: bankaccounts_reconciliation
+-- @description: Kontenabgleichsststartdatum und -saldo
+-- @depends: release_3_2_0
+-- @encoding: utf-8
+
+ALTER TABLE bank_accounts ADD COLUMN reconciliation_starting_date DATE;
+ALTER TABLE bank_accounts ADD COLUMN reconciliation_starting_balance numeric(15,5);
diff --git a/sql/Pg-upgrade2/bankaccounts_sortkey_and_obsolete.sql b/sql/Pg-upgrade2/bankaccounts_sortkey_and_obsolete.sql
new file mode 100644 (file)
index 0000000..49baff9
--- /dev/null
@@ -0,0 +1,12 @@
+-- @tag: bankaccounts_sortkey_and_obsolete
+-- @description: Bankkonto - Sortierreihenfolge und Ungültig
+-- @depends: release_3_2_0
+-- @encoding: utf-8
+
+ALTER TABLE bank_accounts ADD COLUMN obsolete BOOLEAN;
+
+ALTER TABLE bank_accounts ADD COLUMN sortkey INTEGER;
+CREATE SEQUENCE tmp_counter;
+UPDATE bank_accounts SET sortkey = nextval('tmp_counter');
+DROP SEQUENCE tmp_counter;
+ALTER TABLE bank_accounts ALTER COLUMN sortkey SET NOT NULL;
diff --git a/templates/webpages/bankaccounts/bank_account_display_form.html b/templates/webpages/bankaccounts/bank_account_display_form.html
deleted file mode 100644 (file)
index dbc4596..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-[%- USE T8 %]
-[% USE HTML %]
-<h1>[% title %]</h1>
-
-[%- IF params.error %]
- <p><div class="message_error">[% params.error %]</div></p>
-[%- END %]
-
- <form method="post" action="bankaccounts.pl">
-
-  <p>
-   <table>
-    <tr>
-     <td align="right">[% 'Name' | $T8 %]</td>
-     <td><input name="account.name" size="20" maxlength="100" value="[% HTML.escape(account.name) %]"></td>
-    </tr>
-
-    <tr>
-     <td align="right">[% 'Account number' | $T8 %]</td>
-     <td><input name="account.account_number" size="20" maxlength="100" value="[% HTML.escape(account.account_number) %]"></td>
-    </tr>
-
-    <tr>
-     <td align="right">[% 'Bank code' | $T8 %]</td>
-     <td><input name="account.bank_code" size="20" maxlength="100" value="[% HTML.escape(account.bank_code) %]"></td>
-    </tr>
-
-    <tr>
-     <td align="right">[% 'Bank' | $T8 %]</td>
-     <td><input name="account.bank" size="30" value="[% HTML.escape(account.bank) %]"></td>
-    </tr>
-
-    <tr>
-     <td align="right">[% 'IBAN' | $T8 %]</td>
-     <td><input name="account.iban" size="30" maxlength="100" value="[% HTML.escape(account.iban) %]"></td>
-    </tr>
-
-    <tr>
-     <td align="right">[% 'BIC' | $T8 %]</td>
-     <td><input name="account.bic" size="30" maxlength="100" value="[% HTML.escape(account.bic) %]"></td>
-    </tr>
-
-    <tr>
-     <td align="right">[% 'Chart' | $T8 %]</td>
-     <td>
-      [%- INCLUDE generic/multibox.html
-            name      = 'account.chart_id',
-            DATA      = CHARTS,
-            id_key    = 'id',
-            label_sub = 'chart_label',
-            style     = 'width: 300px',
-      -%]
-     </td>
-    </tr>
-
-   </table>
-  </p>
-
-  <p>
-   <input type="hidden" name="action" value="dispatcher">
-   <input type="hidden" name="account.id" value="[% HTML.escape(account.id) %]">
-   <input type="hidden" name="callback" value="[% HTML.escape(callback) %]">
-
-[%- IF account.id %]
-   <input type="submit" name="action_bank_account_save" value="[% 'Save' | $T8 %]">
-   <input type="submit" name="action_bank_account_delete" value="[% 'Delete' | $T8 %]">
-[%- ELSE %]
-   <input type="submit" name="action_bank_account_save" value="[% 'Add' | $T8 %]">
-[%- END %]
-  </p>
- </form>
-
diff --git a/templates/webpages/bankaccounts/bank_account_list_bottom.html b/templates/webpages/bankaccounts/bank_account_list_bottom.html
deleted file mode 100644 (file)
index 202dd2d..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-[% USE T8 %][% USE HTML %]
- <p>
-  <a href="dispatcher.pl?M=bankaccounts&A=bank_account_add&callback=[% HTML.url(callback) %]">[%- 'New bank account' | $T8 %]</a>
- </p>
diff --git a/templates/webpages/bankaccounts/form.html b/templates/webpages/bankaccounts/form.html
new file mode 100644 (file)
index 0000000..e1c323c
--- /dev/null
@@ -0,0 +1,92 @@
+[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%]
+
+[% SET style="width: 400px" %]
+[% SET size=34 %]
+
+<h1>[% HTML.escape(title) %]</h1>
+
+<form action="controller.pl" method="post">
+
+[%- INCLUDE 'common/flash.html' %]
+
+[%- L.hidden_tag("id", SELF.bank_account.id) %]
+
+<table>
+  <tr>
+    <th align="right">[% 'Description' | $T8 %]</th>
+    <td>[%- L.input_tag("bank_account.name", SELF.bank_account.name, size=size) %]</td>
+  </tr>
+  <tr>
+    <th align="right">[% 'IBAN' | $T8 %]</th>
+    <td>[%- L.input_tag("bank_account.iban", SELF.bank_account.iban, size=size) %]</td>
+  </tr>
+  <tr>
+    <th align="right">[% 'Bank' | $T8 %]</th>
+    <td>[%- L.input_tag("bank_account.bank", SELF.bank_account.bank, size=size) %]</td>
+  </tr>
+  <tr>
+    <th align="right">[% 'Account number' | $T8 %]</th>
+    <td>[%- L.input_tag("bank_account.account_number", SELF.bank_account.account_number, size=size) %]</td>
+  </tr>
+  <tr>
+    <th align="right">[% 'BIC' | $T8 %]</th>
+    <td>[%- L.input_tag("bank_account.bic", SELF.bank_account.bic, size=size) %]</td>
+  </tr>
+  <tr>
+    <th align="right">[% 'Bank code' | $T8 %]</th>
+    <td>[%- L.input_tag("bank_account.bank_code", SELF.bank_account.bank_code, size=size) %]</td>
+  </tr>
+  <tr>
+    <th align="right">[% 'Chart' | $T8 %]</th>
+    <td>[% L.chart_picker('bank_account.chart_id', SELF.bank_account.chart_id, type='AR_paid,AP_paid', category='A,L,Q', choose=1, style=style) %]</td>
+  </tr>
+  <tr>
+    <th align="right">[% 'Obsolete' | $T8 %]</th>
+    <td>[% L.checkbox_tag('bank_account.obsolete', checked = SELF.bank_account.obsolete, for_submit=1) %]</td>
+  </tr>
+  <tr>
+    <td align="left">[% 'Reconciliation' | $T8 %]:</td>
+    <td></td>
+  </tr>
+  <tr>
+    <th align="right">[% 'Starting date' | $T8 %]</th>
+    <td>[% L.date_tag('bank_account.reconciliation_starting_date', SELF.bank_account.reconciliation_starting_date) %]</td>
+  </tr>
+  <tr>
+    <th align="right">[% 'Starting balance' | $T8 %]</th>
+    <td>[%- L.input_tag('bank_account.reconciliation_starting_balance_as_number', SELF.bank_account.reconciliation_starting_balance_as_number) %]</td>
+  </tr>
+</table>
+
+ <p>
+  [% L.hidden_tag("action", "BankAccount/dispatch") %]
+  [% L.submit_tag("action_" _  (SELF.bank_account.id ? "update" : "create"), LxERP.t8('Save'), onclick="return check_prerequisites();") %]
+  [%- IF SELF.bank_account.id AND SELF.bank_account.number_of_bank_transactions == 0 -%]
+    [% L.submit_tag("action_delete", LxERP.t8('Delete')) %]
+  [%- END %]
+  <a href="[% SELF.url_for(action='list') %]">[%- LxERP.t8("Cancel") %]</a>
+ </p>
+
+ <hr>
+
+<script type="text/javascript">
+<!--
+function check_prerequisites() {
+  if ($('#bank_account_name').val() === "") {
+    alert(kivi.t8('The name is missing.'));
+    return false;
+  }
+  if ($('#bank_account_iban').val() === "") {
+    alert(kivi.t8('The IBAN is missing.'));
+    return false;
+  }
+  if ($('#bank_account_chart_id').val() === "") {
+    alert(kivi.t8('There is no connected chart.'));
+    return false;
+  }
+
+  return true;
+}
+-->
+</script>
+</form>
diff --git a/templates/webpages/bankaccounts/list.html b/templates/webpages/bankaccounts/list.html
new file mode 100644 (file)
index 0000000..c1a9ff0
--- /dev/null
@@ -0,0 +1,43 @@
+[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%][%- INCLUDE 'common/flash.html' %]
+
+<h1>[% title %]</h1>
+
+<p>
+ <table width="100%" id="bankaccount_list">
+  <thead>
+   <tr class="listheading">
+    <th align="center" width="1%"><img src="image/updown.png" alt="[ LxERP.t8('reorder item') %]"></th>
+    <th>[% 'Name' | $T8 %]</th>
+    <th>[% 'IBAN' | $T8 %]</th>
+    <th>[% 'Bank' | $T8 %]</th>
+    <th>[% 'Bank code' | $T8 %]</th>
+    <th>[% 'BIC' | $T8 %]</th>
+    <th>[% 'Date' | $T8 %]</th>
+    <th>[% 'Balance' | $T8 %]</th>
+   </tr>
+  </thead>
+
+  <tbody>
+   [%- FOREACH account = BANKACCOUNTS %]
+    <tr class="listrow" id="account_id_[% account.id %]">
+     <td align="center" class="dragdrop"><img src="image/updown.png" alt="[ LxERP.t8('reorder item') %]"></td>
+     <td><a href="[% SELF.url_for(action='edit', id=account.id) %]">[% HTML.escape(account.name) %]</a></td>
+     <td>[% HTML.escape(account.iban) %]</a></td>
+     <td>[% HTML.escape(account.bank) %]</a></td>
+     <td>[% HTML.escape(account.bank_code) %]</a></td>
+     <td>[% HTML.escape(account.bic) %]</a></td>
+     <td>[% HTML.escape(account.reconciliation_starting_date.to_kivitendo) %]</a></td>
+     <td align="right">[% HTML.escape(account.reconciliation_starting_balance_as_number) %]</a></td>
+    </tr>
+   [%- END %]
+  </tbody>
+ </table>
+</p>
+
+<hr height="3">
+
+[% L.sortable_element('#bankaccount_list tbody', url=SELF.url_for(action='reorder'), with='account_id') %]
+
+<p>
+ <a href="[% SELF.url_for(action='new') %]">[%- 'Add' | $T8 %]</a>
+</p>