Merge branch 'master' of lx-office.linet-services.de:lx-office-erp
authorMoritz Bunkus <m.bunkus@linet-services.de>
Thu, 12 May 2011 09:27:01 +0000 (11:27 +0200)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Thu, 12 May 2011 09:27:01 +0000 (11:27 +0200)
18 files changed:
SL/AM.pm
SL/Controller/PaymentTerm.pm [new file with mode: 0644]
SL/DB/Helper/ActsAsList.pm [new file with mode: 0644]
SL/DB/Helper/TransNumberGenerator.pm
SL/DB/Helper/TranslatedAttributes.pm [new file with mode: 0644]
SL/DB/Manager/PaymentTerm.pm [new file with mode: 0644]
SL/DB/Object.pm [changed mode: 0644->0755]
SL/DB/Object/Hooks.pm [new file with mode: 0644]
SL/DB/PaymentTerm.pm
SL/Form.pm
SL/X.pm
bin/mozilla/am.pl
locale/de/all
menu.ini
sql/Pg-upgrade2/payment_terms_translation2.sql [new file with mode: 0644]
sql/Pg-upgrade2/schema_normalization_2.sql
templates/webpages/payment_term/form.html [new file with mode: 0644]
templates/webpages/payment_term/list.html [new file with mode: 0644]

index f73456c..4f5b971 100644 (file)
--- a/SL/AM.pm
+++ b/SL/AM.pm
@@ -900,7 +900,7 @@ sub delete_language {
   # connect to database
   my $dbh = $form->dbconnect_noauto($myconfig);
 
-  foreach my $table (qw(translation_payment_terms units_language)) {
+  foreach my $table (qw(generic_translations units_language)) {
     $query = qq|DELETE FROM $table WHERE language_id = ?|;
     do_query($form, $dbh, $query, $form->{"id"});
   }
@@ -1144,160 +1144,6 @@ sub swap_sortkeys {
   $main::lxdebug->leave_sub();
 }
 
-sub payment {
-  $main::lxdebug->enter_sub();
-
-  my ($self, $myconfig, $form) = @_;
-
-  # connect to database
-  my $dbh = $form->dbconnect($myconfig);
-
-  my $query = qq|SELECT * FROM payment_terms ORDER BY sortkey|;
-
-  my $sth = $dbh->prepare($query);
-  $sth->execute || $form->dberror($query);
-
-  $form->{ALL} = [];
-  while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
-    push @{ $form->{ALL} }, $ref;
-  }
-
-  $sth->finish;
-  $dbh->disconnect;
-
-  $main::lxdebug->leave_sub();
-}
-
-sub get_payment {
-  $main::lxdebug->enter_sub();
-
-  my ($self, $myconfig, $form) = @_;
-
-  # connect to database
-  my $dbh = $form->dbconnect($myconfig);
-
-  my $query = qq|SELECT * FROM payment_terms WHERE id = ?|;
-  my $sth = $dbh->prepare($query);
-  $sth->execute($form->{"id"}) || $form->dberror($query . " ($form->{id})");
-
-  my $ref = $sth->fetchrow_hashref("NAME_lc");
-  map { $form->{$_} = $ref->{$_} } keys %$ref;
-  $sth->finish();
-
-  $query =
-    qq|SELECT t.language_id, t.description_long, l.description AS language | .
-    qq|FROM translation_payment_terms t | .
-    qq|LEFT JOIN language l ON t.language_id = l.id | .
-    qq|WHERE t.payment_terms_id = ? | .
-    qq|UNION | .
-    qq|SELECT l.id AS language_id, NULL AS description_long, | .
-    qq|  l.description AS language | .
-    qq|FROM language l|;
-  $sth = $dbh->prepare($query);
-  $sth->execute($form->{"id"}) || $form->dberror($query . " ($form->{id})");
-
-  my %mapping;
-  while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
-    $mapping{ $ref->{"language_id"} } = $ref
-      unless (defined($mapping{ $ref->{"language_id"} }));
-  }
-  $sth->finish;
-
-  $form->{"TRANSLATION"} = [sort({ $a->{"language"} cmp $b->{"language"} }
-                                 values(%mapping))];
-
-  $dbh->disconnect;
-
-  $main::lxdebug->leave_sub();
-}
-
-sub save_payment {
-  $main::lxdebug->enter_sub();
-
-  my ($self, $myconfig, $form) = @_;
-
-  # connect to database
-  my $dbh = $form->dbconnect_noauto($myconfig);
-
-  my $query;
-
-  if (!$form->{id}) {
-    $query = qq|SELECT nextval('id'), COALESCE(MAX(sortkey) + 1, 1) | .
-      qq|FROM payment_terms|;
-    my $sortkey;
-    ($form->{id}, $sortkey) = selectrow_query($form, $dbh, $query);
-
-    $query = qq|INSERT INTO payment_terms (id, sortkey) VALUES (?, ?)|;
-    do_query($form, $dbh, $query, $form->{id}, $sortkey);
-
-  } else {
-    $query =
-      qq|DELETE FROM translation_payment_terms | .
-      qq|WHERE payment_terms_id = ?|;
-    do_query($form, $dbh, $query, $form->{"id"});
-  }
-
-  $query = qq|UPDATE payment_terms SET
-              description = ?, description_long = ?,
-              terms_netto = ?, terms_skonto = ?,
-              percent_skonto = ?
-              WHERE id = ?|;
-  my @values = ($form->{description}, $form->{description_long},
-                $form->{terms_netto} * 1, $form->{terms_skonto} * 1,
-                $form->{percent_skonto} * 1,
-                $form->{id});
-  do_query($form, $dbh, $query, @values);
-
-  $query = qq|SELECT id FROM language|;
-  my @language_ids;
-  my $sth = $dbh->prepare($query);
-  $sth->execute() || $form->dberror($query);
-
-  while (my ($id) = $sth->fetchrow_array()) {
-    push(@language_ids, $id);
-  }
-  $sth->finish();
-
-  $query =
-    qq|INSERT INTO translation_payment_terms | .
-    qq|(language_id, payment_terms_id, description_long) | .
-    qq|VALUES (?, ?, ?)|;
-  $sth = $dbh->prepare($query);
-
-  foreach my $language_id (@language_ids) {
-    do_statement($form, $sth, $query, $language_id, $form->{"id"},
-                 $form->{"description_long_${language_id}"});
-  }
-  $sth->finish();
-
-  $dbh->commit();
-  $dbh->disconnect;
-
-  $main::lxdebug->leave_sub();
-}
-
-sub delete_payment {
-  $main::lxdebug->enter_sub();
-
-  my ($self, $myconfig, $form) = @_;
-
-  # connect to database
-  my $dbh = $form->dbconnect_noauto($myconfig);
-
-  my $query =
-    qq|DELETE FROM translation_payment_terms WHERE payment_terms_id = ?|;
-  do_query($form, $dbh, $query, $form->{"id"});
-
-  $query = qq|DELETE FROM payment_terms WHERE id = ?|;
-  do_query($form, $dbh, $query, $form->{"id"});
-
-  $dbh->commit();
-  $dbh->disconnect;
-
-  $main::lxdebug->leave_sub();
-}
-
-
 sub prepare_template_filename {
   $main::lxdebug->enter_sub();
 
diff --git a/SL/Controller/PaymentTerm.pm b/SL/Controller/PaymentTerm.pm
new file mode 100644 (file)
index 0000000..631428e
--- /dev/null
@@ -0,0 +1,117 @@
+package SL::Controller::PaymentTerm;
+
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use SL::DB::PaymentTerm;
+use SL::DB::Language;
+use SL::Helper::Flash;
+
+use Rose::Object::MakeMethods::Generic
+(
+ scalar => [ qw(payment_term languages) ],
+);
+
+__PACKAGE__->run_before('load_payment_term', only => [ qw(         edit        update destroy move_up move_down) ]);
+__PACKAGE__->run_before('load_languages',    only => [ qw(new list edit create update) ]);
+
+#
+# actions
+#
+
+sub action_list {
+  my ($self) = @_;
+
+  $self->render('payment_term/list',
+                title               => $::locale->text('Payment terms'),
+                PAYMENT_TERMS => SL::DB::Manager::PaymentTerm->get_all_sorted);
+}
+
+sub action_new {
+  my ($self) = @_;
+
+  $self->{payment_term} = SL::DB::PaymentTerm->new;
+  $self->render('payment_term/form', title => $::locale->text('Create a new payment term'));
+}
+
+sub action_edit {
+  my ($self) = @_;
+  $self->render('payment_term/form', title => $::locale->text('Edit payment term'));
+}
+
+sub action_create {
+  my ($self) = @_;
+
+  $self->{payment_term} = SL::DB::PaymentTerm->new;
+  $self->create_or_update;
+}
+
+sub action_update {
+  my ($self) = @_;
+  $self->create_or_update;
+}
+
+sub action_destroy {
+  my ($self) = @_;
+
+  if (eval { $self->{payment_term}->delete; 1; }) {
+    flash_later('info',  $::locale->text('The payment term has been deleted.'));
+  } else {
+    flash_later('error', $::locale->text('The payment term is in use and cannot be deleted.'));
+  }
+
+  $self->redirect_to(action => 'list');
+}
+
+sub action_move_up {
+  my ($self) = @_;
+  $self->{payment_term}->move_position_up;
+  $self->redirect_to(action => 'list');
+}
+
+sub action_move_down {
+  my ($self) = @_;
+  $self->{payment_term}->move_position_down;
+  $self->redirect_to(action => 'list');
+}
+
+#
+# helpers
+#
+
+sub create_or_update {
+  my $self   = shift;
+  my $is_new = !$self->{payment_term}->id;
+  my $params = delete($::form->{payment_term}) || { };
+
+  $self->{payment_term}->assign_attributes(%{ $params });
+
+  my @errors = $self->{payment_term}->validate;
+
+  if (@errors) {
+    flash('error', @errors);
+    $self->render('payment_term/form', title => $is_new ? $::locale->text('Create a new payment term') : $::locale->text('Edit payment term'));
+    return;
+  }
+
+  $self->{payment_term}->save;
+  foreach my $language (@{ $self->{languages} }) {
+    $self->{payment_term}->save_attribute_translation('description_long', $language, $::form->{"translation_" . $language->id});
+  }
+
+  flash_later('info', $is_new ? $::locale->text('The payment term has been created.') : $::locale->text('The payment term has been saved.'));
+  $self->redirect_to(action => 'list');
+}
+
+sub load_payment_term {
+  my ($self) = @_;
+  $self->{payment_term} = SL::DB::PaymentTerm->new(id => $::form->{id})->load;
+}
+
+sub load_languages {
+  my ($self) = @_;
+  $self->{languages} = SL::DB::Manager::Language->get_all_sorted;
+}
+
+1;
diff --git a/SL/DB/Helper/ActsAsList.pm b/SL/DB/Helper/ActsAsList.pm
new file mode 100644 (file)
index 0000000..a645053
--- /dev/null
@@ -0,0 +1,152 @@
+package SL::DB::Helper::ActsAsList;
+
+use strict;
+
+use parent qw(Exporter);
+our @EXPORT = qw(move_position_up move_position_down);
+
+use Carp;
+
+sub import {
+  my ($class, @params)   = @_;
+  my $importing = caller();
+
+  $importing->before_save(  sub { SL::DB::Helper::ActsAsList::set_position(@_)    });
+  $importing->before_delete(sub { SL::DB::Helper::ActsAsList::remove_position(@_) });
+
+  # Use 'goto' so that Exporter knows which module to import into via
+  # 'caller()'.
+  goto &Exporter::import;
+}
+
+#
+# Exported functions
+#
+
+sub move_position_up {
+  my ($self) = @_;
+  do_move($self, 'up');
+}
+
+sub move_position_down {
+  my ($self) = @_;
+  do_move($self, 'down');
+}
+
+#
+# Helper functions
+#
+
+sub set_position {
+  my ($self) = @_;
+  my $column = column_name($self);
+
+  if (!defined $self->$column) {
+    my $max_position = $self->db->dbh->selectrow_arrayref(qq|SELECT COALESCE(max(${column}), 0) FROM | . $self->meta->table)->[0];
+    $self->$column($max_position + 1);
+  }
+
+  return 1;
+}
+
+sub remove_position {
+  my ($self) = @_;
+  my $column = column_name($self);
+
+  $self->load;
+  if (defined $self->$column) {
+    $self->_get_manager_class->update_all(set   => { $column => \"${column} - 1" },
+                                          where => [ $column => { gt => $self->$column } ]);
+  }
+
+  return 1;
+}
+
+sub do_move {
+  my ($self, $direction) = @_;
+  my $column             = column_name($self);
+
+  croak "Object has not been saved yet" unless $self->id;
+  croak "No position set yet"           unless defined $self->$column;
+
+  my ($comp_sql, $comp_rdbo, $min_max, $plus_minus) = $direction eq 'up' ? ('<', 'ge', 'max', '+') : ('>', 'le', 'min', '-');
+
+  my $new_position = $self->db->dbh->selectrow_arrayref(qq|SELECT ${min_max}(${column}) FROM | . $self->meta->table . qq| WHERE ${column} ${comp_sql} | . $self->$column)->[0];
+
+  return undef unless defined $new_position;
+
+  $self->_get_manager_class->update_all(set   => { $column => $self->$column },
+                                        where => [ $column => $new_position ]);
+  $self->update_attributes($column => $new_position);
+}
+
+sub column_name {
+  my ($self) = @_;
+  return $self->can('sortkey') ? 'sortkey' : 'position';
+}
+
+1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::DB::Helper::ActsAsList - Mixin for managing ordered items by a
+column I<position> or I<sortkey>
+
+=head1 SYNOPSIS
+
+  package SL::DB::SomeObject;
+  use SL::DB::Helper::ActsAsList;
+
+  package SL::Controller::SomeController;
+  ...
+  # Assign a position automatically
+  $obj = SL::DB::SomeObject->new(description => 'bla');
+  $obj->save;
+
+  # Move items up and down
+  $obj = SL::DB::SomeOBject->new(id => 1)->load;
+  $obj->move_position_up;
+  $obj->move_position_down;
+
+  # Adjust all remaining positions automatically
+  $obj->delete
+
+This mixin assumes that the mixing package's table contains a column
+called C<position> or C<sortkey> (for legacy tables). This column is
+set automatically upon saving the object if it hasn't been set
+already. If it hasn't then it will be set to the maximum position used
+in the table plus one.
+
+When the object is deleted all positions greater than the object's old
+position are decreased by one.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<move_position_up>
+
+Swaps the object with the object one step above the current one
+regarding their sort order by exchanging their C<position> values.
+
+=item C<move_position_down>
+
+Swaps the object with the object one step below the current one
+regarding their sort order by exchanging their C<position> values.
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
index c060a2f..0505268 100644 (file)
@@ -93,7 +93,7 @@ SL::DB::Helper::TransNumberGenerator - A mixin for creating unique record number
 
 =over 4
 
-=item C<get_mext_trams_number %params>
+=item C<get_next_trans_number %params>
 
 Generates a new unique record number for the mixing class. Each record
 type (invoices, sales quotations, purchase orders etc) has its own
@@ -147,7 +147,7 @@ and return a value. If it fails then it is due to exceptions.
 
 =item C<create_trans_number %params>
 
-Calls and returns L</get_next_trans_number> with the parameters
+Calls and returns </get_next_trans_number> with the parameters
 C<update_defaults = 1> and C<update_record = 1>. C<%params> is passed
 to it as well.
 
diff --git a/SL/DB/Helper/TranslatedAttributes.pm b/SL/DB/Helper/TranslatedAttributes.pm
new file mode 100644 (file)
index 0000000..2c3857c
--- /dev/null
@@ -0,0 +1,131 @@
+package SL::DB::Helper::TranslatedAttributes;
+
+use strict;
+
+use SL::DB::GenericTranslation;
+
+use parent qw(Exporter);
+our @EXPORT = qw(translated_attribute save_attribute_translation);
+
+use Carp;
+
+sub translated_attribute {
+  my ($self, $attribute, $language_id, $verbatim) = @_;
+
+  $language_id        = _check($self, $attribute, $language_id, $verbatim);
+  my $translation_obj = _find_translation($self, $attribute, $language_id, 0);
+  my $translation     = $translation_obj ? $translation_obj->translation : '';
+
+  return $translation if $verbatim || $translation;
+
+  $translation_obj = _find_translation($self, $attribute, undef, 0);
+  $translation     = $translation_obj ? $translation_obj->translation : '';
+
+  return $translation || $self->$attribute;
+}
+
+sub save_attribute_translation {
+  my ($self, $attribute, $language_id, $value) = @_;
+
+  $language_id = _check($self, $attribute, $language_id);
+
+  return _find_translation($self, $attribute, $language_id, 1)->update_attributes(translation => $value);
+}
+
+sub _check {
+  my ($self, $attribute, $language_id, $verbatim) = @_;
+
+  croak "Invalid attribute '${attribute}'" unless $self->can($attribute);
+  croak "Object has not been saved yet"    unless $self->id || $verbatim;
+
+  return (ref($language_id) eq 'SL::DB::Language' ? $language_id->id : $language_id) || undef;
+}
+
+sub _find_translation {
+  my ($self, $attribute, $language_id, $new_if_not_found) = @_;
+
+  my %params = (language_id      => $language_id,
+                translation_type => ref($self). '/' . $attribute,
+                translation_id   => $self->id);
+
+  return SL::DB::Manager::GenericTranslation->find_by(%params) || ($new_if_not_found ? SL::DB::GenericTranslation->new(%params) : undef);
+}
+
+1;
+
+__END__
+
+=encoding utf8
+
+=head1 NAME
+
+SL::DB::Helper::TranslatedAttributes - Mixin for retrieving and saving
+translations for certain model attributes in the table
+I<generic_translations>
+
+=head1 SYNOPSIS
+
+Declaration:
+
+  package SL::DB::SomeObject;
+  use SL::DB::Helper::Translated;
+
+Usage:
+
+  my $object   = SL::DB::SomeObject->new(id => $::form->{id})->load;
+  my $language = SL::DB::Manager::Language->find_by(description => 'Deutsch');
+  print "Untranslated name: " . $object->name . " translated: " . $object->translated_attribute('name', $language) . "\n";
+
+  print "Now saving new value\n";
+  my $save_ok = $object->save_attribute_translation('name', $language, 'Lieferung frei Haus');
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<translated_attribute $attribute, $language_id, $verbatim>
+
+Returns the translation stored for the attribute C<$attribute> and the
+language C<$language_id> (either an ID or an instance of
+L<SL::DB::Language>).
+
+If C<$verbatim> is falsish and either no translation exists for
+C<$language_id> or if C<$language_id> is undefined then the default
+translation is looked up.
+
+If C<$verbatim> is falsish and neither translation exists then the
+value of C<< $self->$attribute >> is returned.
+
+Requires that C<$self> has a primary ID column named C<id> and that
+the object has been saved.
+
+=item C<save_attribute_translation $attribute, $language_id, $value>
+
+Saves the translation C<$value> for the attribute C<$attribute> and
+the language C<$language_id> (either an ID or an instance of
+L<SL::DB::Language>).
+
+If C<$language_id> is undefined then the default translation will be
+saved.
+
+Requires that C<$self> has a primary ID column named C<id> and that
+the object has been saved.
+
+Returns the same value as C<save>.
+
+=back
+
+=head1 EXPORTS
+
+This mixin exports the functions L</translated_attribute> and
+L</save_attribute_translation>.
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
diff --git a/SL/DB/Manager/PaymentTerm.pm b/SL/DB/Manager/PaymentTerm.pm
new file mode 100644 (file)
index 0000000..abe6837
--- /dev/null
@@ -0,0 +1,21 @@
+package SL::DB::Manager::PaymentTerm;
+
+use strict;
+
+use SL::DB::Helper::Manager;
+use base qw(SL::DB::Helper::Manager);
+
+use SL::DB::Helper::Sorted;
+
+sub object_class { 'SL::DB::PaymentTerm' }
+
+__PACKAGE__->make_manager_methods;
+
+sub _sort_spec {
+  return ( default => [ 'sortkey', 1 ],
+           columns => { SIMPLE => 'ALL',
+                        map { ( $_ => "lower(payment_terms.${_})" ) } qw(description description_long),
+                      });
+}
+
+1;
old mode 100644 (file)
new mode 100755 (executable)
index 71e0a39..a80640c
@@ -9,6 +9,7 @@ use SL::DB;
 use SL::DB::Helper::Attr;
 use SL::DB::Helper::Metadata;
 use SL::DB::Helper::Manager;
+use SL::DB::Object::Hooks;
 
 use base qw(Rose::DB::Object);
 
@@ -84,6 +85,57 @@ sub call_sub {
   return $self->$sub(@_);
 }
 
+sub call_sub_if {
+  my $self  = shift;
+  my $sub   = shift;
+  my $check = shift;
+
+  $check    = $check->($self) if ref($check) eq 'CODE';
+
+  return $check ? $self->$sub(@_) : $self;
+}
+
+# These three functions cannot sit in SL::DB::Object::Hooks because
+# mixins don't deal well with super classes (SUPER is the current
+# package's super class, not $self's).
+sub load {
+  my ($self, @args) = @_;
+
+  SL::DB::Object::Hooks::run_hooks($self, 'before_load');
+  my $result = $self->SUPER::load(@args);
+  SL::DB::Object::Hooks::run_hooks($self, 'after_load', $result);
+
+  return $result;
+}
+
+sub save {
+  my ($self, @args) = @_;
+
+  my $result;
+  my $worker = sub {
+    SL::DB::Object::Hooks::run_hooks($self, 'before_save');
+    $result = $self->SUPER::save(@args);
+    SL::DB::Object::Hooks::run_hooks($self, 'after_save', $result);
+  };
+
+  $self->db->in_transaction ? $worker->() : $self->db->do_transaction($worker);
+  return $result;
+}
+
+sub delete {
+  my ($self, @args) = @_;
+
+  my $result;
+  my $worker = sub {
+    SL::DB::Object::Hooks::run_hooks($self, 'before_delete');
+    $result = $self->SUPER::delete(@args);
+    SL::DB::Object::Hooks::run_hooks($self, 'after_delete', $result);
+  };
+
+  $self->db->in_transaction ? $worker->() : $self->db->do_transaction($worker);
+  return $result;
+}
+
 1;
 
 __END__
@@ -145,6 +197,16 @@ name is a composite, e.g.
 
   my $chart_id = $buchungsgruppe->call_sub(($is_sales ? "income" : "expense") . "_accno_id_${taxzone_id}");
 
+=item C<call_sub_if $name, $check, @args>
+
+Calls the sub C<$name> on C<$self> with the arguments C<@args> if
+C<$check> is trueish. If C<$check> is a code reference then it will be
+called with C<$self> as the only argument and its result determines
+whether or not C<$name> is called.
+
+Returns the sub's result if the check is positive and C<$self>
+otherwise.
+
 =back
 
 =head1 AUTHOR
diff --git a/SL/DB/Object/Hooks.pm b/SL/DB/Object/Hooks.pm
new file mode 100644 (file)
index 0000000..e479514
--- /dev/null
@@ -0,0 +1,143 @@
+package SL::DB::Object::Hooks;
+
+use strict;
+
+use SL::X;
+
+use parent qw(Exporter);
+our @EXPORT = qw(before_load   after_load
+                 before_save   after_save
+                 before_delete after_delete);
+
+my %hooks;
+
+# Adding hooks
+
+sub before_save {
+  _add_hook('before_save', @_);
+}
+
+sub after_save {
+  _add_hook('after_save', @_);
+}
+
+sub before_load {
+  _add_hook('before_load', @_);
+}
+
+sub after_load {
+  _add_hook('after_load', @_);
+}
+
+sub before_delete {
+  _add_hook('before_delete', @_);
+}
+
+sub after_delete {
+  _add_hook('after_delete', @_);
+}
+
+# Running hooks
+
+sub run_hooks {
+  my ($object, $when, @args) = @_;
+
+  foreach my $sub (@{ ( $hooks{$when} || { })->{ ref($object) } || [ ] }) {
+    my $result = ref($sub) eq 'CODE' ? $sub->($object, @args) : $object->call_sub($sub, @args);
+    die SL::X::DBHookError->new(
+      hook   => (ref($sub) eq 'CODE' ? '<anonymous sub>' : $sub),
+      when   => $when,
+      object => $object,
+    ) if !$result;
+  }
+}
+
+# Internals
+
+sub _add_hook {
+  my ($when, $class, $sub_name, $code) = @_;
+  $hooks{$when}           ||= { };
+  $hooks{$when}->{$class} ||= [ ];
+  push @{ $hooks{$when}->{$class} }, $sub_name;
+}
+
+1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::DB::Object::Hooks - Hooks that are run before/after a
+load/save/delete
+
+=head1 SYNOPSIS
+
+Hooks are functions that are called before or after an object is
+loaded, saved or deleted. The package defines the hooks, and those
+hooks themselves are run as instance methods.
+
+Hooks are run in the order they're added.
+
+Hooks must return a trueish value in order to continue processing. If
+any hook returns a falsish value then an exception (instance of
+C<SL::X::DBHookError>) is thrown. However, C<SL::DB::Object> usually
+runs the hooks from within a transaction, catches the exception and
+only returns falsish in error cases.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<before_load $sub>
+
+=item C<before_save $sub>
+
+=item C<before_delete $sub>
+
+=item C<after_load $sub>
+
+=item C<after_save $sub>
+
+=item C<after_delete $sub>
+
+Adds a new hook that is called at the appropriate time. C<$sub> can be
+either a name of an existing sub or a code reference. If it is a code
+reference then the then-current C<$self> will be passed as the first
+argument.
+
+C<before> hooks are called without arguments.
+
+C<after> hooks are called with a single argument: the result of the
+C<save> or C<delete> operation.
+
+=item C<run_hooks $object, $when, @args>
+
+Runs all hooks for the object C<$object> that are defined for
+C<$when>. C<$when> is the same as one of the C<before_xyz> or
+C<after_xyz> function names above.
+
+An exception of C<SL::X::DBHookError> is thrown if any of the hooks
+returns a falsish value.
+
+This function is supposed to be called by L<Rose::DB::Object/load>,
+L<Rose::DB::Object/save> or L<Rose::DB::Object/delete>.
+
+=back
+
+=head1 EXPORTS
+
+This mixin exports the functions L</before_load>, L</after_load>,
+L</before_save>, L</after_save>, L</before_delete>, L</after_delete>.
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
index b525910..3630a70 100644 (file)
@@ -1,13 +1,20 @@
-# This file has been auto-generated only because it didn't exist.
-# Feel free to modify it at will; it will not be overwritten automatically.
-
 package SL::DB::PaymentTerm;
 
 use strict;
 
 use SL::DB::MetaSetup::PaymentTerm;
+use SL::DB::Manager::PaymentTerm;
+use SL::DB::Helper::ActsAsList;
+use SL::DB::Helper::TranslatedAttributes;
+
+sub validate {
+  my ($self) = @_;
+
+  my @errors;
+  push @errors, $::locale->text('The description is missing.')      if !$self->description;
+  push @errors, $::locale->text('The long description is missing.') if !$self->description_long;
 
-# Creates get_all, get_all_count, get_all_iterator, delete_all and update_all.
-__PACKAGE__->meta->make_manager_class;
+  return @errors;
+}
 
 1;
index 28e1225..4c90304 100644 (file)
@@ -1904,10 +1904,12 @@ sub set_payment_options {
 
   if ($self->{"language_id"}) {
     $query =
-      qq|SELECT t.description_long, l.output_numberformat, l.output_dateformat, l.output_longdates | .
-      qq|FROM translation_payment_terms t | .
+      qq|SELECT t.translation, l.output_numberformat, l.output_dateformat, l.output_longdates | .
+      qq|FROM generic_translations t | .
       qq|LEFT JOIN language l ON t.language_id = l.id | .
-      qq|WHERE (t.language_id = ?) AND (t.payment_terms_id = ?)|;
+      qq|WHERE (t.language_id = ?)
+           AND (t.translation_id = ?)
+           AND (t.translation_type = 'SL::DB::PaymentTerm/description_long')|;
     my ($description_long, $output_numberformat, $output_dateformat,
       $output_longdates) =
       selectrow_query($self, $dbh, $query,
diff --git a/SL/X.pm b/SL/X.pm
index ce7552c..0f20694 100644 (file)
--- a/SL/X.pm
+++ b/SL/X.pm
@@ -5,5 +5,6 @@ use strict;
 use Exception::Lite qw(declareExceptionClass);
 
 declareExceptionClass('SL::X::FormError');
+declareExceptionClass('SL::X::DBHookError', [ '%s hook \'%s\' failed', qw(when hook object) ]);
 
 1;
index e96c604..5dfb72f 100644 (file)
@@ -2018,369 +2018,6 @@ sub swap_buchungsgruppen {
   $main::lxdebug->leave_sub();
 }
 
-sub add_payment {
-  $main::lxdebug->enter_sub();
-
-  my $form     = $main::form;
-  my %myconfig = %main::myconfig;
-
-  $main::auth->assert('config');
-
-  $form->{title} = "Add";
-
-  $form->{callback} = "am.pl?action=add_payment" unless $form->{callback};
-
-  $form->{terms_netto} = 0;
-  $form->{terms_skonto} = 0;
-  $form->{percent_skonto} = 0;
-  my @languages = AM->language(\%myconfig, $form, 1);
-  map({ $_->{"language"} = $_->{"description"};
-        $_->{"language_id"} = $_->{"id"}; } @languages);
-  $form->{"TRANSLATION"} = \@languages;
-  &payment_header;
-  &form_footer;
-
-  $main::lxdebug->leave_sub();
-}
-
-sub edit_payment {
-  $main::lxdebug->enter_sub();
-
-  my $form     = $main::form;
-  my %myconfig = %main::myconfig;
-
-  $main::auth->assert('config');
-
-  $form->{title} = "Edit";
-
-  AM->get_payment(\%myconfig, $form);
-  $form->{percent_skonto} =
-    $form->format_amount(\%myconfig, $form->{percent_skonto} * 100);
-
-  &payment_header;
-
-  $form->{orphaned} = 1;
-  &form_footer;
-
-  $main::lxdebug->leave_sub();
-}
-
-sub list_payment {
-  $main::lxdebug->enter_sub();
-
-  my $form     = $main::form;
-  my %myconfig = %main::myconfig;
-  my $locale   = $main::locale;
-
-  $main::auth->assert('config');
-
-  AM->payment(\%myconfig, \%$form);
-
-  $form->{callback} = build_std_url("action=list_payment");
-
-  my $callback = $form->escape($form->{callback});
-
-  $form->{title} = $locale->text('Payment Terms');
-
-  my @column_index = qw(up down description description_long terms_netto
-                     terms_skonto percent_skonto);
-  my %column_header;
-  $column_header{up} =
-      qq|<th class="listheading" align="center" valign="center" width="16">|
-    . qq|<img src="image/up.png" alt="| . $locale->text("up") . qq|">|
-    . qq|</th>|;
-  $column_header{down} =
-      qq|<th class="listheading" align="center" valign="center" width="16">|
-    . qq|<img src="image/down.png" alt="| . $locale->text("down") . qq|">|
-    . qq|</th>|;
-  $column_header{description} =
-      qq|<th class=listheading>|
-    . $locale->text('Description')
-    . qq|</th>|;
-  $column_header{description_long} =
-      qq|<th class=listheading>|
-    . $locale->text('Long Description')
-    . qq|</th>|;
-  $column_header{terms_netto} =
-      qq|<th class=listheading>|
-    . $locale->text('Netto Terms')
-    . qq|</th>|;
-  $column_header{terms_skonto} =
-      qq|<th class=listheading>|
-    . $locale->text('Skonto Terms')
-    . qq|</th>|;
-  $column_header{percent_skonto} =
-      qq|<th class=listheading>|
-    . $locale->text('Skonto')
-    . qq| %</th>|;
-
-  $form->header;
-
-  print qq|
-<body>
-
-<table width=100%>
-  <tr>
-    <th class=listtop>$form->{title}</th>
-  </tr>
-  <tr height="5"></tr>
-  <tr>
-    <td>
-      <table width=100%>
-        <tr class=listheading>
-|;
-
-  map { print "$column_header{$_}\n" } @column_index;
-
-  print qq|
-        </tr>
-|;
-
-  my $swap_link = build_std_url("action=swap_payment_terms");
-
-  my $row = 0;
-  my ($i, %column_data);
-  foreach my $ref (@{ $form->{ALL} }) {
-
-    $i++;
-    $i %= 2;
-
-    print qq|
-        <tr valign=top class=listrow$i>
-|;
-
-    if ($row) {
-      my $pref = $form->{ALL}->[$row - 1];
-      $column_data{up} =
-        qq|<td align="center" valign="center" width="16">| .
-        qq|<a href="${swap_link}&id1=$ref->{id}&id2=$pref->{id}">| .
-        qq|<img border="0" src="image/up.png" alt="| . $locale->text("up") . qq|">| .
-        qq|</a></td>|;
-    } else {
-      $column_data{up} = qq|<td width="16">&nbsp;</td>|;
-    }
-
-    if ($row == (scalar(@{ $form->{ALL} }) - 1)) {
-      $column_data{down} = qq|<td width="16">&nbsp;</td>|;
-    } else {
-      my $nref = $form->{ALL}->[$row + 1];
-      $column_data{down} =
-        qq|<td align="center" valign="center" width="16">| .
-        qq|<a href="${swap_link}&id1=$ref->{id}&id2=$nref->{id}">| .
-        qq|<img border="0" src="image/down.png" alt="| . $locale->text("down") . qq|">| .
-        qq|</a></td>|;
-    }
-
-    $column_data{description} =
-      qq|<td><a href="| .
-      build_std_url("action=edit_payment", "id=$ref->{id}", "callback=$callback") .
-      qq|">| . H($ref->{description}) . qq|</a></td>|;
-    $column_data{description_long} =
-      qq|<td>| . H($ref->{description_long}) . qq|</td>|;
-    $column_data{terms_netto} =
-      qq|<td align=right>$ref->{terms_netto}</td>|;
-    $column_data{terms_skonto} =
-      qq|<td align=right>$ref->{terms_skonto}</td>|;
-    $column_data{percent_skonto} =
-      qq|<td align=right>| .
-      $form->format_amount(\%myconfig, $ref->{percent_skonto} * 100) .
-      qq|%</td>|;
-    map { print "$column_data{$_}\n" } @column_index;
-
-    print qq|
-       </tr>
-|;
-    $row++;
-  }
-
-  print qq|
-      </table>
-    </td>
-  </tr>
-  <tr>
-  <td><hr size=3 noshade></td>
-  </tr>
-</table>
-
-<br>
-<form method=post action=am.pl>
-
-<input name=callback type=hidden value="$form->{callback}">
-
-<input type=hidden name=type value=payment>
-
-<input class=submit type=submit name=action value="|
-    . $locale->text('Add') . qq|">
-
-  </form>
-
-  </body>
-  </html>
-|;
-
-  $main::lxdebug->leave_sub();
-}
-
-sub payment_header {
-  $main::lxdebug->enter_sub();
-
-  my $form     = $main::form;
-  my $locale   = $main::locale;
-
-  $main::auth->assert('config');
-
-  $form->{title}    = $locale->text("$form->{title} Payment Terms");
-
-  # $locale->text('Add Payment Terms')
-  # $locale->text('Edit Payment Terms')
-
-  $form->{description} =~ s/\"/&quot;/g;
-
-
-
-  $form->header;
-
-  print qq|
-<body>
-
-<form method=post action=am.pl>
-
-<input type=hidden name=id value=$form->{id}>
-<input type=hidden name=type value=payment>
-
-<table width=100%>
-  <tr>
-    <th class=listtop colspan=2>$form->{title}</th>
-  </tr>
-  <tr height="5"></tr>
-  <tr>
-    <th align=right>| . $locale->text('Description') . qq|</th>
-    <td><input name=description size=30 value="$form->{description}"></td>
-  <tr>
-  <tr>
-    <th align=right>| . $locale->text('Long Description') . qq|</th>
-    <td><input name=description_long size=50 value="$form->{description_long}"></td>
-  </tr>
-|;
-
-  foreach my $language (@{ $form->{"TRANSLATION"} }) {
-    print qq|
-  <tr>
-    <th align="right">| .
-    sprintf($locale->text('Translation (%s)'),
-            $language->{"language"})
-    . qq|</th>
-    <td><input name="description_long_$language->{language_id}" size="50"
-         value="| . Q($language->{"description_long"}) . qq|"></td>
-  </tr>
-|;
-  }
-
-  print qq|
-  <tr>
-    <th align=right>| . $locale->text('Netto Terms') . qq|</th>
-    <td><input name=terms_netto size=10 value="$form->{terms_netto}"></td>
-  </tr>
-  <tr>
-    <th align=right>| . $locale->text('Skonto Terms') . qq|</th>
-    <td><input name=terms_skonto size=10 value="$form->{terms_skonto}"></td>
-  </tr>
-  <tr>
-    <th align=right>| . $locale->text('Skonto') . qq| %</th>
-    <td><input name=percent_skonto size=10 value="$form->{percent_skonto}"></td>
-  </tr>
-  <td colspan=2><hr size=3 noshade></td>
-  </tr>
-</table>
-
-<p>| . $locale->text("You can use the following strings in the long " .
-                     "description and all translations. They will be " .
-                     "replaced by their actual values by Lx-Office " .
-                     "before they're output.")
-. qq|</p>
-
-<ul>
-  <li>| . $locale->text("&lt;%netto_date%&gt; -- Date the payment is due in " .
-                        "full")
-. qq|</li>
-  <li>| . $locale->text("&lt;%skonto_date%&gt; -- Date the payment is due " .
-                        "with discount")
-. qq|</li>
-  <li>| . $locale->text("&lt;%skonto_amount%&gt; -- The deductible amount")
-. qq|</li>
-  <li>| . $locale->text("&lt;%skonto_in_percent%&gt; -- The discount in percent")
-. qq|</li>
-  <li>| . $locale->text("&lt;%total%&gt; -- Amount payable")
-. qq|</li>
-  <li>| . $locale->text("&lt;%total_wo_skonto%&gt; -- Amount payable less discount")
-. qq|</li>
-  <li>| . $locale->text("&lt;%invtotal%&gt; -- Invoice total")
-. qq|</li>
-  <li>| . $locale->text("&lt;%invtotal_wo_skonto%&gt; -- Invoice total less discount")
-. qq|</li>
-  <li>| . $locale->text("&lt;%currency%&gt; -- The selected currency")
-. qq|</li>
-  <li>| . $locale->text("&lt;%terms_netto%&gt; -- The number of days for " .
-                        "full payment")
-. qq|</li>
-  <li>| . $locale->text("&lt;%account_number%&gt; -- Your account number")
-. qq|</li>
-  <li>| . $locale->text("&lt;%bank%&gt; -- Your bank")
-. qq|</li>
-  <li>| . $locale->text("&lt;%bank_code%&gt; -- Your bank code")
-. qq|</li>
-</ul>|;
-
-  $main::lxdebug->leave_sub();
-}
-
-sub save_payment {
-  $main::lxdebug->enter_sub();
-
-  my $form     = $main::form;
-  my %myconfig = %main::myconfig;
-  my $locale   = $main::locale;
-
-  $main::auth->assert('config');
-
-  $form->isblank("description", $locale->text('Description missing!'));
-  $form->{"percent_skonto"} =
-    $form->parse_amount(\%myconfig, $form->{percent_skonto}) / 100;
-  AM->save_payment(\%myconfig, \%$form);
-  $form->redirect($locale->text('Payment Terms saved!'));
-
-  $main::lxdebug->leave_sub();
-}
-
-sub delete_payment {
-  $main::lxdebug->enter_sub();
-
-  my $form     = $main::form;
-  my %myconfig = %main::myconfig;
-  my $locale   = $main::locale;
-
-  $main::auth->assert('config');
-
-  AM->delete_payment(\%myconfig, \%$form);
-  $form->redirect($locale->text('Payment terms deleted!'));
-
-  $main::lxdebug->leave_sub();
-}
-
-sub swap_payment_terms {
-  $main::lxdebug->enter_sub();
-
-  my $form     = $main::form;
-  my %myconfig = %main::myconfig;
-
-  $main::auth->assert('config');
-
-  AM->swap_sortkeys(\%myconfig, $form, "payment_terms");
-  list_payment();
-
-  $main::lxdebug->leave_sub();
-}
-
 sub edit_defaults {
   $main::lxdebug->enter_sub();
 
index ded1a32..3eeba99 100644 (file)
@@ -14,19 +14,6 @@ $self->{texts} = {
   ' Part Number missing!'       => ' Artikelnummer fehlt!',
   ' missing!'                   => ' fehlt!',
   '#1 prices were updated.'     => '#1 Preise wurden aktualisiert.',
-  '&lt;%account_number%&gt; -- Your account number' => '&lt;%account_number%&gt; -- Ihre Kontonummer',
-  '&lt;%bank%&gt; -- Your bank' => '&lt;%bank%&gt; -- Der Name Ihrer Bank',
-  '&lt;%bank_code%&gt; -- Your bank code' => '&lt;%bank_code%&gt; -- Die Bankleitzahl Ihrer Bank',
-  '&lt;%currency%&gt; -- The selected currency' => '&lt;%currency%&gt; -- Die ausgew&auml;hlte W&auml;hrung',
-  '&lt;%invtotal%&gt; -- Invoice total' => '&lt;%invtotal%&gt; -- Die Rechnungssumme',
-  '&lt;%invtotal_wo_skonto%&gt; -- Invoice total less discount' => '&lt;%invtotal_wo_skonto%&gt; -- Rechnungssumme abz&uuml;glich Skonto',
-  '&lt;%netto_date%&gt; -- Date the payment is due in full' => '&lt;%netto_date%&gt; -- Das Datum, bis die Rechnung in voller H&ouml;he bezahlt werden muss',
-  '&lt;%skonto_amount%&gt; -- The deductible amount' => '&lt;%skonto_amount%&gt; -- Der abziehbare Skontobetrag',
-  '&lt;%skonto_date%&gt; -- Date the payment is due with discount' => '&lt;%skonto_date%&gt; -- Das Datum, bis die Rechnung unter Abzug von Skonto bezahlt werden kann',
-  '&lt;%skonto_in_percent%&gt; -- The discount in percent' => '&lt;%skonto_in_percent%&gt; -- Der prozentuale Rabatt',
-  '&lt;%terms_netto%&gt; -- The number of days for full payment' => '&lt;%terms_netto%&gt; -- Die Anzahl Tage, bis die Rechnung in voller H&ouml;he bezahlt werden muss',
-  '&lt;%total%&gt; -- Amount payable' => '&lt;%total%&gt; -- Noch zu bezahlender Betrag',
-  '&lt;%total_wo_skonto%&gt; -- Amount payable less discount' => '&lt;%total_wo_skonto%&gt; -- Noch zu bezahlender Betrag abz&uuml;glich Skonto',
   '*/'                          => '*/',
   '---please select---'         => '---bitte auswählen---',
   '...after loggin in'          => '...nach dem Anmelden',
@@ -64,6 +51,7 @@ $self->{texts} = {
   'AR Transaction (abbreviation)' => 'D',
   'AR Transactions'             => 'Debitorenbuchungen',
   'ASSETS'                      => 'AKTIVA',
+  'Abort'                       => 'Abbrechen',
   'Abrechnungsnummer'           => 'Abrechnungsnummer',
   'Abteilung'                   => 'Abteilung',
   'Account'                     => 'Konto',
@@ -184,6 +172,8 @@ $self->{texts} = {
   'Amount'                      => 'Betrag',
   'Amount Due'                  => 'Betrag fällig',
   'Amount has to be greater then zero! Wrong row number: ' => 'Leere Eingabe oder Werte kleiner, gleich null eingegeben. Fehler in Reihe Nummer: ',
+  'Amount payable'              => 'Noch zu bezahlender Betrag',
+  'Amount payable less discount' => 'Noch zu bezahlender Betrag abzüglich Skonto',
   'An invalid character was used (invalid characters: #1).' => 'Ein ungültiges Zeichen wurde benutzt (ungültige Zeichen: #1).',
   'An invalid character was used (valid characters: #1).' => 'Ein ungültiges Zeichen wurde benutzt (gültige Zeichen: #1).',
   'An upper-case character is required.' => 'Ein Großbuchstabe ist vorgeschrieben.',
@@ -202,6 +192,7 @@ $self->{texts} = {
   'Are you sure you want to delete Order Number' => 'Soll der Auftrag mit folgender Nummer wirklich gelöscht werden:',
   'Are you sure you want to delete Quotation Number' => 'Sind Sie sicher, dass Angebotnummer gelöscht werden soll?',
   'Are you sure you want to delete Transaction' => 'Buchung wirklich löschen?',
+  'Are you sure you want to delete this payment term?' => 'Wollen Sie diese Zahlungsbedingungen wirklich löschen?',
   'Are you sure you want to remove the marked entries from the queue?' => 'Sind Sie sicher, dass die markierten Einträge von der Warteschlange gelöscht werden sollen?',
   'Are you sure you want to update the prices' => 'Sind Sie sicher, dass Sie die Preise aktualisieren wollen?',
   'Article Code'                => 'Artikelkürzel',
@@ -420,6 +411,7 @@ $self->{texts} = {
   'Create Chart of Accounts'    => 'Zu verwendender Kontenplan',
   'Create Dataset'              => 'Neue Datenbank anlegen',
   'Create Date'                 => 'Erstelldatum',
+  'Create a new payment term'   => 'Neue Zahlungsbedingungen anlegen',
   'Create a standard group'     => 'Eine Standard-Benutzergruppe anlegen',
   'Create and edit RFQs'        => 'Lieferantenanfragen erfassen und bearbeiten',
   'Create and edit customers and vendors' => 'Kunden und Lieferanten erfassen und bearbeiten',
@@ -439,6 +431,7 @@ $self->{texts} = {
   'Create bank transfer via SEPA XML' => 'Überweisung via SEPA XML erzeugen',
   'Create invoice?'             => 'Rechnung erstellen?',
   'Create new'                  => 'Neu erfassen',
+  'Create new payment term'     => 'Neue Zahlungsbedingung anlegen',
   'Create tables'               => 'Tabellen anlegen',
   'Created by'                  => 'Erstellt von',
   'Created for'                 => 'Erstellt f&uuml;r',
@@ -509,6 +502,8 @@ $self->{texts} = {
   'Date Paid'                   => 'Zahlungsdatum',
   'Date and timestamp variables: If the default value equals \'NOW\' then the current date/current timestamp will be used. Otherwise the default value is copied as-is.' => 'Datums- und Uhrzeitvariablen: Wenn der Standardwert \'NOW\' ist, so wird das aktuelle Datum/die aktuelle Uhrzeit eingef&uuml;gt. Andernfalls wird der Standardwert so wie er ist benutzt.',
   'Date missing!'               => 'Datum fehlt!',
+  'Date the payment is due in full' => 'Das Datum, bis die Rechnung in voller Höhe bezahlt werden muss',
+  'Date the payment is due with discount' => 'Das Datum, bis die Rechnung unter Abzug von Skonto bezahlt werden kann',
   'Datevautomatik'              => 'Datevexport',
   'Datum von'                   => 'Datum von',
   'Debit'                       => 'Soll',
@@ -652,7 +647,6 @@ $self->{texts} = {
   'Edit Language'               => 'Sprache bearbeiten',
   'Edit Lead'                   => 'Kundenquelle bearbeiten',
   'Edit Part'                   => 'Ware bearbeiten',
-  'Edit Payment Terms'          => 'Zahlungskonditionen bearbeiten',
   'Edit Preferences for #1'     => 'Einstellungen von #1 bearbeiten',
   'Edit Price Factor'           => 'Preisfaktor bearbeiten',
   'Edit Pricegroup'             => 'Preisgruppe bearbeiten',
@@ -683,6 +677,7 @@ $self->{texts} = {
   'Edit groups'                 => 'Gruppen bearbeiten',
   'Edit membership'             => 'Mitgliedschaft bearbeiten',
   'Edit note'                   => 'Notiz bearbeiten',
+  'Edit payment term'           => 'Zahlungsbedingungen bearbeiten',
   'Edit rights'                 => 'Rechte bearbeiten',
   'Edit templates'              => 'Vorlagen bearbeiten',
   'Edit the Delivery Order'     => 'Lieferschein bearbeiten',
@@ -757,6 +752,7 @@ $self->{texts} = {
   'Feb'                         => 'Feb',
   'February'                    => 'Februar',
   'Fee'                         => 'Gebühr',
+  'Field'                       => 'Feld',
   'File'                        => 'Datei',
   'File name'                   => 'Dateiname',
   'Files created by Lx-Office\'s &quot;Backup Dataset&quot; function are such files.' => 'Dateien, die von Lx-Office\' Funktion &quot;Datenbank sichern&quot; erstellt wurden, erf&uuml;llen diese Kriterien.',
@@ -903,6 +899,8 @@ $self->{texts} = {
   'Invoice for fees'            => 'Rechnung über Gebühren',
   'Invoice has already been storno\'d!' => 'Diese Rechnung wurde bereits storniert.',
   'Invoice number'              => 'Rechnungsnummer',
+  'Invoice total'               => 'Die Rechnungssumme',
+  'Invoice total less discount' => 'Rechnungssumme abzüglich Skonto',
   'Invoice with Storno (abbreviation)' => 'R(S)',
   'Invoices'                    => 'Rechnungen',
   'Is Searchable'               => 'Durchsuchbar',
@@ -971,7 +969,7 @@ $self->{texts} = {
   'List Groups'                 => 'Warengruppen anzeigen',
   'List Languages'              => 'Sprachen anzeigen',
   'List Lead'                   => 'Kundenquelle anzeigen',
-  'List Payment Terms'          => 'Zahlungskonditionen anzeigen',
+  'List Payment Terms'          => 'Zahlungsbedingungen anzeigen',
   'List Price'                  => 'Listenpreis',
   'List Price Factors'          => 'Preisfaktoren anzeigen',
   'List Pricegroups'            => 'Preisgruppen anzeigen',
@@ -1102,6 +1100,7 @@ $self->{texts} = {
   'No licenses were found that match the search criteria.' => 'Es wurden keine Lizenzen gefunden, auf die die Suchkriterien zutreffen.',
   'No or an unknown authenticantion module specified in "config/lx_office.conf".' => 'Es wurde kein oder ein unbekanntes Authentifizierungsmodul in "config/lx_office.conf" angegeben.',
   'No part was found matching the search parameters.' => 'Es wurde kein Artikel gefunden, auf den die Suchparameter zutreffen.',
+  'No payment term has been created yet.' => 'Es wurden noch keine Zahlungsbedingungen angelegt.',
   'No prices will be updated because no prices have been entered.' => 'Es werden keine Preise aktualisiert, weil keine gültigen Preisänderungen eingegeben wurden.',
   'No problems were recognized.' => 'Es wurden keine Probleme gefunden.',
   'No transaction selected!'    => 'Keine Transaktion ausgewählt',
@@ -1209,13 +1208,12 @@ $self->{texts} = {
   'Payables'                    => 'Verbindlichkeiten',
   'Payment'                     => 'Zahlungsausgang',
   'Payment Reminder'            => 'Zahlungserinnerung',
-  'Payment Terms'               => 'Zahlungskonditionen',
+  'Payment Terms'               => 'Zahlungsbedingungen',
   'Payment Terms missing in row ' => 'Zahlungsfrist fehlt in Zeile ',
-  'Payment Terms saved!'        => 'Zahlungskonditionen gespeichert!',
   'Payment date missing!'       => 'Tag der Zahlung fehlt!',
   'Payment list as PDF'         => 'Zahlungsliste als PDF',
   'Payment posted!'             => 'Zahlung gebucht!',
-  'Payment terms deleted!'      => 'Zahlungskonditionen gelöscht!',
+  'Payment terms'               => 'Zahlungsbedingungen',
   'Payments'                    => 'Zahlungsausgänge',
   'Per. Inv.'                   => 'Wied. Rech.',
   'Period'                      => 'Zeitraum',
@@ -1650,12 +1648,14 @@ $self->{texts} = {
   'The dataset backup has been sent via email to #1.' => 'Die Datenbanksicherung wurde per Email an #1 verschickt.',
   'The dataset has to exist before a restoration can be started.' => 'Die Datenbank muss vor der Wiederherstellung bereits angelegt worden sein.',
   'The dataset name is missing.' => 'Der Datenbankname fehlt.',
+  'The deductible amount'       => 'Der abziehbare Skontobetrag',
   'The default value depends on the variable type:' => 'Die Bedeutung des Standardwertes h&auml;ngt vom Variablentypen ab:',
   'The delivery order has not been marked as delivered. The warehouse contents have not changed.' => 'Der Lieferschein wurde nicht als geliefert markiert. Der Lagerinhalt wurde nicht verändert.',
   'The description is missing.' => 'Die Beschreibung fehlt.',
   'The description is shown on the form. Chose something short and descriptive.' => 'Die Beschreibung wird in der jeweiligen Maske angezeigt. Sie sollte kurz und pr&auml;gnant sein.',
   'The directory "%s" could not be created:\n%s' => 'Das Verzeichnis "%s" konnte nicht erstellt werden:\n%s',
   'The directory %s does not exist.' => 'Das Verzeichnis %s existiert nicht.',
+  'The discount in percent'     => 'Der prozentuale Rabatt',
   'The dunning process started' => 'Der Mahnprozess ist gestartet.',
   'The dunnings have been printed.' => 'Die Mahnung(en) wurden gedruckt.',
   'The email address is missing.' => 'Die Emailadresse fehlt.',
@@ -1684,10 +1684,12 @@ $self->{texts} = {
   'The licensing module has been deactivated in the configuration.' => 'Das Lizenzverwaltungsmodul wurde in der Konfiguration deaktiviert.',
   'The list has been printed.'  => 'Die Liste wurde ausgedruckt.',
   'The login is missing.'       => 'Das Login fehlt.',
+  'The long description is missing.' => 'Der Langtext fehlt.',
   'The name in row %d has already been used before.' => 'Der Name in Zeile %d wurde vorher bereits benutzt.',
   'The name is missing in row %d.' => 'Der Name fehlt in Zeile %d.',
   'The name is missing.'        => 'Der Name fehlt.',
   'The name must only consist of letters, numbers and underscores and start with a letter.' => 'Der Name darf nur aus Buchstaben (keine Umlaute), Ziffern und Unterstrichen bestehen und muss mit einem Buchstaben beginnen.',
+  'The number of days for full payment' => 'Die Anzahl Tage, bis die Rechnung in voller Höhe bezahlt werden muss',
   'The old file containing the user information is still present (&quot;#1&quot;). Do you want to migrate these users into the database? If not then you will not be able to log in with any of the users present in the old file.' => 'Die alte Datei mit den Benutzerdaten existiert in dieser Installation noch immer (&quot;#1&quot;). Wollen Sie diese Benutzer in die neue Authentifizierungsdatenbank migrieren lassen? Falls nicht, so werden Sie sich nicht mehr mit den Benutzerdaten aus der alten Mitgliedsdatei anmelden können.',
   'The option field is empty.'  => 'Das Optionsfeld ist leer.',
   'The parts for this delivery order have already been transferred in.' => 'Die Artikel dieses Lieferscheins wurden bereits eingelagert.',
@@ -1698,6 +1700,10 @@ $self->{texts} = {
   'The password is too long (maximum length: #1).' => 'Das Passwort ist zu lang (maximale Länge: #1).',
   'The password is too short (minimum length: #1).' => 'Das Password ist zu kurz (minimale Länge: #1).',
   'The password is weak (e.g. it can be found in a dictionary).' => 'Das Passwort ist schwach (z.B. wenn es in einem Wörterbuch steht).',
+  'The payment term has been created.' => 'Die Zahlungsbedingungen wurden angelegt.',
+  'The payment term has been deleted.' => 'Die Zahlungsbedingungen wurden gelöscht.',
+  'The payment term has been saved.' => 'Die Zahlungsbedingungen wurden gespeichert.',
+  'The payment term is in use and cannot be deleted.' => 'Die Zahlungsbedingungen werden bereits benutzt und können nicht gelöscht werden.',
   'The payments have been posted.' => 'Die Zahlungen wurden gebucht.',
   'The pg_dump process could not be started.' => 'Der pg_dump-Prozess konnte nicht gestartet werden.',
   'The pg_restore process could not be started.' => 'Der pg_restore-Prozess konnte nicht gestartet werden.',
@@ -1712,6 +1718,7 @@ $self->{texts} = {
   'The selected  PostgreSQL installation uses UTF-8 as its encoding. Therefore you have to configure Lx-Office to use UTF-8 as well.' => 'Die ausgewählte PostgreSQL-Installation benutzt UTF-8 als Zeichensatz. Deshalb müssen Sie Lx-Office so konfigurieren, dass es ebenfalls UTF-8 als Zeichensatz benutzt.',
   'The selected bank account does not exist anymore.' => 'Das ausgewählte Bankkonto existiert nicht mehr.',
   'The selected bin does not exist.' => 'Der ausgew&auml;hlte Lagerplatz existiert nicht.',
+  'The selected currency'       => 'Die ausgewählte Währung',
   'The selected exports have been closed.' => 'Die ausgewählten Exporte wurden abgeschlossen.',
   'The selected warehouse does not exist.' => 'Das ausgew&auml;hlte Lager existiert nicht.',
   'The selected warehouse is empty, or no stocked items where found that match the filter settings.' => 'Das ausgewählte Lager ist leer, oder in ihm wurden keine zu den Sucheinstellungen passenden eingelagerten Artikel gefunden.',
@@ -1812,7 +1819,7 @@ $self->{texts} = {
   'Transfer in'                 => 'Einlagern',
   'Transfer out'                => 'Auslagern',
   'Transfer qty'                => 'Umlagermenge',
-  'Translation (%s)'            => '&Uuml;bersetzung (%s)',
+  'Translation'                 => 'Übersetzung',
   'Trial Balance'               => 'Summen- und Saldenliste',
   'Trial balance between %s and %s' => 'Summen- und Saldenlisten vom %s bis zum %s',
   'Trying to call a sub without a name' => 'Es wurde versucht, eine Unterfunktion ohne Namen aufzurufen.',
@@ -1972,6 +1979,9 @@ $self->{texts} = {
   'You\'ve already chosen the following limitations:' => 'Sie haben bereits die folgenden Einschr&auml;nkungen vorgenommen:',
   'Your PostgreSQL installationen uses UTF-8 as its encoding. Therefore you have to configure Lx-Office to use UTF-8 as well.' => 'Ihre PostgreSQL-Installation benutzt UTF-8 als Zeichensatz. Sie müssen deshalb Lx-Office so konfigurieren, dass es ebenfalls UTF-8 als Zeichensatz benutzt.',
   'Your TODO list'              => 'Ihre Aufgabenliste',
+  'Your account number'         => 'Ihre Kontonummer',
+  'Your bank'                   => 'Der Name Ihrer Bank',
+  'Your bank code'              => 'Die Bankleitzahl Ihrer Bank',
   'Your browser does not currently support Javascript.' => 'Ihr Browser unterstützt im Moment kein Javascript!',
   'Your download does not exist anymore. Please re-run the DATEV export assistant.' => 'Ihr Download existiert nicht mehr. Bitte starten Sie den DATEV-Exportassistenten erneut.',
   'Zeitpunkt'                   => 'Zeitpunkt',
index 19a5a7d..0dd0a5e 100644 (file)
--- a/menu.ini
+++ b/menu.ini
@@ -698,12 +698,12 @@ target=acc_menu
 submenu=1
 
 [System--Payment Terms--Add Payment Terms]
-module=am.pl
-action=add_payment
+module=controller.pl
+action=PaymentTerm/new
 
 [System--Payment Terms--List Payment Terms]
-module=am.pl
-action=list_payment
+module=controller.pl
+action=PaymentTerm/list
 
 [System--Manage Custom Variables]
 module=menu.pl
diff --git a/sql/Pg-upgrade2/payment_terms_translation2.sql b/sql/Pg-upgrade2/payment_terms_translation2.sql
new file mode 100644 (file)
index 0000000..09b26f0
--- /dev/null
@@ -0,0 +1,9 @@
+-- @tag: payment_terms_translation2
+-- @description: Eingliederung von payment_terms_translation in generic_translations
+-- @depends: release_2_6_1
+-- @charset: utf-8
+INSERT INTO generic_translations (language_id, translation_type, translation_id, translation)
+  SELECT language_id, 'SL::DB::PaymentTerm/description_long', payment_terms_id, description_long
+  FROM translation_payment_terms;
+
+DROP TABLE translation_payment_terms;
index 87ab636..d6b2b3b 100644 (file)
@@ -24,6 +24,5 @@ ALTER TABLE status ADD COLUMN id SERIAL PRIMARY KEY;
 ALTER TABLE tax_zones ADD PRIMARY KEY (id);
 ALTER TABLE todo_user_config ADD COLUMN id SERIAL PRIMARY KEY;
 ALTER TABLE translation ADD COLUMN id SERIAL PRIMARY KEY;
-ALTER TABLE translation_payment_terms ADD COLUMN id SERIAL PRIMARY KEY;
 ALTER TABLE units_language ADD COLUMN id SERIAL PRIMARY KEY;
 ALTER TABLE vendortax ADD COLUMN id SERIAL PRIMARY KEY;
diff --git a/templates/webpages/payment_term/form.html b/templates/webpages/payment_term/form.html
new file mode 100644 (file)
index 0000000..3ea1cdb
--- /dev/null
@@ -0,0 +1,89 @@
+[% USE HTML %][% USE T8 %][% USE L %][% USE LxERP %]
+<body>
+
+ <form method="post" action="controller.pl">
+  <div class="listtop">[% FORM.title %]</div>
+
+[%- INCLUDE 'common/flash.html' %]
+
+  <table>
+   <tr>
+    <td>[%- 'Description' | $T8 %]</td>
+    <td>
+     <input name="payment_term.description" value="[%- HTML.escape(SELF.payment_term.description) %]">
+    </td>
+   </tr>
+
+   <tr>
+    <td>[%- 'Long Description' | $T8 %]</td>
+    <td>
+     <input name="payment_term.description_long" value="[%- HTML.escape(SELF.payment_term.description_long) %]" size="60">
+    </td>
+   </tr>
+
+   [%- FOREACH language = SELF.languages %]
+    <tr>
+     <td>[%- HTML.escape(language.description) %] ([%- LxERP.t8('Translation') %])</td>
+     <td>
+      <input name="translation_[% language.id %]" value="[%- HTML.escape(SELF.payment_term.translated_attribute('description_long', language, 1)) %]" size="60">
+     </td>
+    </tr>
+   [%- END %]
+
+   <tr>
+    <td>[%- 'Netto Terms' | $T8 %]</td>
+    <td>
+     <input name="payment_term.terms_netto_as_number" value="[%- HTML.escape(SELF.payment_term.terms_netto_as_number) %]" size="6">
+    </td>
+   </tr>
+
+   <tr>
+    <td>[%- 'Skonto Terms' | $T8 %]</td>
+    <td>
+     <input name="payment_term.terms_skonto_as_number" value="[%- HTML.escape(SELF.payment_term.terms_skonto_as_number) %]" size="6">
+    </td>
+   </tr>
+
+   <tr>
+    <td>[%- 'Skonto' | $T8 %]</td>
+    <td>
+     <input name="payment_term.percent_skonto_as_number" value="[%- HTML.escape(SELF.payment_term.percent_skonto_as_number) %]" size="6">%
+    </td>
+   </tr>
+  </table>
+
+  <p>
+   <input type="hidden" name="id" value="[% SELF.payment_term.id %]">
+   <input type="hidden" name="action" value="PaymentTerm/dispatch">
+   <input type="submit" class="submit" name="action_[% IF SELF.payment_term.id %]update[% ELSE %]create[% END %]" value="[% 'Save' | $T8 %]">
+   [%- IF SELF.payment_term.id %]
+    <input type="submit" class="submit" name="action_destroy" value="[% 'Delete' | $T8 %]"
+           onclick="if (confirm('[% 'Are you sure you want to delete this payment term?' | $T8 %]')) return true; else return false;">
+   [%- END %]
+   <a href="[% SELF.url_for(action => 'list') %]">[%- 'Abort' | $T8 %]</a>
+  </p>
+
+  <hr size="3" noshade>
+
+  <p>[% LxERP.t8("You can use the following strings in the long description and all translations. They will be replaced by their actual values by Lx-Office before they're output.") %]</p>
+
+  <table>
+   <tr class="listheading"><th>[%- LxERP.t8('Field') %]</th><th>[%- LxERP.t8('Description') %]</th></tr>
+   <tr><td>&lt;%netto_date%&gt;</td><td>[% LxERP.t8("Date the payment is due in full") %]</td></tr>
+   <tr><td>&lt;%skonto_date%&gt;</td><td>[% LxERP.t8("Date the payment is due with discount") %]</td></tr>
+   <tr><td>&lt;%skonto_amount%&gt;</td><td>[% LxERP.t8("The deductible amount") %]</td></tr>
+   <tr><td>&lt;%skonto_in_percent%&gt;</td><td>[% LxERP.t8("The discount in percent") %]</td></tr>
+   <tr><td>&lt;%total%&gt;</td><td>[% LxERP.t8("Amount payable") %]</td></tr>
+   <tr><td>&lt;%total_wo_skonto%&gt;</td><td>[% LxERP.t8("Amount payable less discount") %]</td></tr>
+   <tr><td>&lt;%invtotal%&gt;</td><td>[% LxERP.t8("Invoice total") %]</td></tr>
+   <tr><td>&lt;%invtotal_wo_skonto%&gt;</td><td>[% LxERP.t8("Invoice total less discount") %]</td></tr>
+   <tr><td>&lt;%currency%&gt;</td><td>[% LxERP.t8("The selected currency") %]</td></tr>
+   <tr><td>&lt;%terms_netto%&gt;</td><td>[% LxERP.t8("The number of days for full payment") %]</td></tr>
+   <tr><td>&lt;%account_number%&gt;</td><td>[% LxERP.t8("Your account number") %]</td></tr>
+   <tr><td>&lt;%bank%&gt;</td><td>[% LxERP.t8("Your bank") %]</td></tr>
+   <tr><td>&lt;%bank_code%&gt;</td><td>[% LxERP.t8("Your bank code") %]</td></tr>
+  </table>
+ </form>
+
+</body>
+</html>
diff --git a/templates/webpages/payment_term/list.html b/templates/webpages/payment_term/list.html
new file mode 100644 (file)
index 0000000..e26e07f
--- /dev/null
@@ -0,0 +1,57 @@
+[% USE HTML %][% USE T8 %][% USE L %][% USE LxERP %]
+<body>
+ <div class="listtop">[% FORM.title %]</div>
+
+[%- INCLUDE 'common/flash.html' %]
+
+ <form method="post" action="controller.pl">
+  [% IF !PAYMENT_TERMS.size %]
+   <p>
+    [%- 'No payment term has been created yet.' | $T8 %]
+   </p>
+
+  [%- ELSE %]
+   <table>
+    <tr class="listheading">
+     <th align="center"><img src="image/up.png"></th>
+     <th align="center"><img src="image/down.png"></th>
+     <th>[%- 'Description' | $T8 %]</th>
+     <th>[%- 'Long Description' | $T8 %]</th>
+     <th align="right">[%- 'Netto Terms' | $T8 %]</th>
+     <th align="right">[%- 'Skonto Terms' | $T8 %]</th>
+     <th align="right">[%- 'Skonto' | $T8 %]</th>
+    </tr>
+
+    [%- FOREACH payment_term = PAYMENT_TERMS %]
+    <tr class="listrow[% loop.count % 2 %]">
+     <td align="center">
+      [%- UNLESS loop.first %]
+       <a href="[% SELF.url_for(action => 'move_up', id => payment_term.id) %]"><img src="image/up.png" border="0"></a>
+      [%- END %]
+     </td>
+     <td align="center">
+      [%- UNLESS loop.last %]
+       <a href="[% SELF.url_for(action => 'move_down', id => payment_term.id) %]"><img src="image/down.png" border="0"></a>
+      [%- END %]
+     </td>
+     <td>
+      <a href="[% SELF.url_for(action => 'edit', id => payment_term.id) %]">
+       [%- HTML.escape(payment_term.description) %]
+      </a>
+     </td>
+     <td>[%- HTML.escape(payment_term.description_long) %]</td>
+     <td align="right">[%- HTML.escape(payment_term.terms_netto_as_number) %]</td>
+     <td align="right">[%- HTML.escape(payment_term.terms_skonto_as_number) %]</td>
+     <td align="right">[%- HTML.escape(payment_term.percent_skonto_as_number) %] %</td>
+    </tr>
+    [%- END %]
+   </table>
+  [%- END %]
+
+  <p>
+   <a href="[% SELF.url_for(action => 'new') %]">[%- 'Create new payment term' | $T8 %]</a>
+  </p>
+ </form>
+
+</body>
+</html>