]> wagnertech.de Git - kivitendo-erp.git/commitdiff
Merge branch 'master' of github.com:kivitendo/kivitendo-erp
authorJan Büren <jan@kivitendo-premium.de>
Thu, 16 Jul 2015 11:13:01 +0000 (13:13 +0200)
committerJan Büren <jan@kivitendo-premium.de>
Thu, 16 Jul 2015 11:13:01 +0000 (13:13 +0200)
24 files changed:
SL/Controller/Chart.pm
SL/Controller/Helper/GetModels.pm
SL/Controller/Helper/GetModels/Filtered.pm
SL/Controller/Helper/ParseFilter.pm
SL/DB/Helper/AttrDuration.pm
SL/DBConnect/Cache.pm
bin/mozilla/is.pl
bin/mozilla/oe.pl
js/common.js
t/controllers/helpers/parse_filter.t
t/db_helper/attr_duration.t
templates/print/RB/deutsch.tex
templates/print/RB/english.tex
templates/print/RB/letter.tex
templates/webpages/background_job_history/list.html
templates/webpages/bank_transactions/report_top.html
templates/webpages/delivery_plan/report_top.html
templates/webpages/do/search.html
templates/webpages/financial_controlling_report/report_top.html
templates/webpages/part/part_picker_search.html
templates/webpages/price_rule/report_top.html
templates/webpages/project/report_top.html
templates/webpages/reconciliation/tabs/overview.html
templates/webpages/requirement_spec/report_top.html

index 274f2ea5671516c62e6b567fb7d528bd8a0824ec..624a65b1da52324e90bbbf890aa5ff822752d2fa 100644 (file)
@@ -10,7 +10,7 @@ use SL::Locale::String qw(t8);
 use SL::JSON;
 
 use Rose::Object::MakeMethods::Generic (
-  'scalar --get_set_init' => [ qw(charts models chart) ],
+  'scalar --get_set_init' => [ qw(charts models chart filter) ],
 );
 
 sub action_ajax_autocomplete {
@@ -111,4 +111,6 @@ sub init_models {
   );
 }
 
+sub init_filter { $_[0]->models->filtered->laundered }
+
 1;
index 861834b83c9091748423c69e63fe5fa0d605d0e2..0e7f5bb80ceaac0c5c47591c264ce6ab876d1613 100644 (file)
@@ -11,7 +11,7 @@ use Scalar::Util qw(weaken);
 
 use Rose::Object::MakeMethods::Generic (
   scalar => [ qw(controller model query with_objects filtered sorted paginated finalized final_params) ],
-  'scalar --get_set_init' => [ qw(handlers source additional_url_params) ],
+  'scalar --get_set_init' => [ qw(handlers source list_action additional_url_params) ],
   array => [ qw(plugins) ],
 );
 
@@ -95,7 +95,9 @@ sub init {
 
   my @plugins;
   for my $plugin (qw(filtered sorted paginated)) {
-    next unless my $spec = delete $params{$plugin} // {};
+    next if exists($params{$plugin}) && !$params{$plugin};
+
+    my $spec         = delete $params{$plugin} // {};
     my $plugin_class = "SL::Controller::Helper::GetModels::" . ucfirst $plugin;
     push @plugins, $self->$plugin($plugin_class->new(%$spec, get_models => $self));
   }
@@ -160,7 +162,7 @@ sub get_models_url_params {
 sub get_callback_params {
   my ($self, %override_params) = @_;
 
-  my %default_params = $self->_run_handlers('callback', action => $self->controller->action_name);
+  my %default_params = $self->_run_handlers('callback', action => $self->list_action);
 }
 
 sub get_callback {
@@ -206,6 +208,10 @@ sub init_source {
   $::form
 }
 
+sub init_list_action {
+  $_[0]->controller->action_name
+}
+
 sub init_additional_url_params { +{} }
 
 1;
@@ -400,6 +406,13 @@ The creating controller. Currently this is mandatory.
 The name of the model for this GetModels instance. If none is given, the model
 is inferred from the name of the controller class.
 
+=item list_action ACTION
+
+If callbacks are generated, use this action instead of the current action.
+Usually you can omit this. In case the reporting is done without redirecting
+from a mutating action, this is necessary to have callbacks for paginating and
+sorting point to the correct action.
+
 =item sorted PARAMS
 
 =item paginated PARAMS
index 642d542a00f78f63a4e65c5beababe2c84b2b1c5..63258154b370417b453a3a168a3292ee3fdf65a5 100644 (file)
@@ -8,8 +8,8 @@ use SL::Controller::Helper::ParseFilter ();
 use List::MoreUtils qw(uniq);
 
 use Rose::Object::MakeMethods::Generic (
-  scalar => [ qw(filter_args filter_params orig_filter filter) ],
-  'scalar --get_set_init' => [ qw(form_params launder_to) ],
+  scalar => [ qw(filter_args filter_params orig_filter filter no_launder) ],
+  'scalar --get_set_init' => [ qw(form_params laundered) ],
 );
 
 sub init {
@@ -39,27 +39,20 @@ sub read_params {
     class        => $self->get_models->manager,
     with_objects => $params{with_objects},
   );
-  my $laundered;
-  if ($self->launder_to eq '__INPLACE__') {
-    # nothing to do
-  } elsif ($self->launder_to) {
-    $laundered = {};
-    $parse_filter_args{launder_to} = $laundered;
+
+  # Store laundered result in $self->laundered.
+
+  if (!$self->no_launder) {
+    $self->laundered({});
+    $parse_filter_args{launder_to} = $self->laundered;
   } else {
+    $self->laundered(undef);
     $parse_filter_args{no_launder} = 1;
   }
 
   my %calculated_params = SL::Controller::Helper::ParseFilter::parse_filter($filter, %parse_filter_args);
   %calculated_params = $self->merge_args(\%calculated_params, \%filter_args, \%params);
 
-  if ($laundered) {
-    if ($self->get_models->controller->can($self->launder_to)) {
-      $self->get_models->controller->${\ $self->launder_to }($laundered);
-    } else {
-      $self->get_models->controller->{$self->launder_to} = $laundered;
-    }
-  }
-
   # $::lxdebug->dump(0, "get_current_filter_params: ", \%calculated_params);
 
   $self->filter_params(\%calculated_params);
@@ -107,10 +100,12 @@ sub init_form_params {
   'filter'
 }
 
-sub init_launder_to {
-  'filter'
-}
+sub init_laundered {
+  my ($self) = @_;
 
+  $self->get_models->finalize;
+  return $self->{laundered};
+}
 
 1;
 
@@ -132,7 +127,7 @@ In a controller:
     ...
     filtered => {
       filter      => HASHREF,
-      launder_to  => HASHREF | SUBNAME | '__INPLACE__',
+      no_launder  => 0 | 1,
     }
 
     OR
@@ -162,17 +157,9 @@ Optional. Indicates a key in C<source> to be used as filter.
 
 Defaults to the value C<filter> if missing.
 
-=item * C<launder_to>
-
-Optional. Indicates a target for laundered filter arguments in the controller.
-Can be set to C<undef> to disable laundering, and can be set to method named or
-hash keys of the controller. In the latter case the laundered structure will be
-put there.
+=item * C<no_launder>
 
-Defaults to the controller. Laundered values will end up in C<SELF.filter> for
-template purposes.
-
-Setting this to the special value C<__INPLACE__> will cause inplace laundering.
+Optional. If given and trueish then laundering is disabled.
 
 =back
 
@@ -185,6 +172,18 @@ See L<SL::Controller::Helper::ParseFilter> for a description of the filter forma
 C<Filtered> will honor custom filters defined in RDBO managers. See
 L<SL::DB::Helper::Filtered> for an explanation fo those.
 
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<laundered>
+
+Finalizes the object (which causes laundering of the filter structure)
+and returns a hashref of the laundered filter. If the plugin is
+configured not to launder then C<undef> will be returned.
+
+=back
+
 =head1 BUGS
 
 =over 4
index cdd3eb7a75db7b7af7d080dbd03f8e487ee67622..2ff97987c727fef8b0520b4edd1add46a711201a 100644 (file)
@@ -12,6 +12,14 @@ use SL::MoreCommon qw(listify);
 use Data::Dumper;
 use Text::ParseWords;
 
+sub _lazy_bool_eq {
+  my ($key, $value) = @_;
+
+  return ()                                   if ($value // '') eq '';
+  return (or => [ $key => undef, $key => 0 ]) if !$value;
+  return ($key => 1);
+}
+
 my %filters = (
   date    => sub { DateTime->from_lxoffice($_[0]) },
   number  => sub { $::form->parse_amount(\%::myconfig, $_[0]) },
@@ -32,6 +40,10 @@ my %methods = (
   } qw(similar match imatch regex regexp like ilike rlike is is_not ne eq lt gt le ge),
 );
 
+my %complex_methods = (
+  lazy_bool_eq => \&_lazy_bool_eq,
+);
+
 sub parse_filter {
   my ($filter, %params) = @_;
 
@@ -99,31 +111,40 @@ sub _parse_filter {
 
   $flattened = _collapse_indirect_filters($flattened);
 
+  my $all_filters = { %filters,         %{ $params{filters}         || {} } };
+  my $all_methods = { %methods,         %{ $params{methods}         || {} } };
+  my $all_complex = { %complex_methods, %{ $params{complex_methods} || {} } };
+
   my @result;
   for (my $i = 0; $i < scalar @$flattened; $i += 2) {
+    my (@args, @filters, $method);
+
     my ($key, $value) = ($flattened->[$i], $flattened->[$i+1]);
     my ($type, $op)   = $key =~ m{:(.+)::(.+)};
 
-    if ($key =~ s/:multi//) {
-      my @multi;
-      my $orig_key = $key;
-      for my $value (parse_line('\s+', 0, $value)) {
-        ($key, $value) = _apply_all($key, $value, qr/\b:(\w+)/,  { %filters, %{ $params{filters} || {} } });
-        ($key, $value) = _apply_all($key, $value, qr/\b::(\w+)/, { %methods, %{ $params{methods} || {} } });
-        ($key, $value) = _dispatch_custom_filters($params{class}, $with_objects, $key, $value) if $params{class};
-        ($key, $value) = _apply_value_filters($key, $value, $type, $op);
-        push @multi, $key, $value;
-        $key = $orig_key;
-      }
-      ($key, $value) = (and => \@multi);
-    } else {
-      ($key, $value) = _apply_all($key, $value, qr/\b:(\w+)/,  { %filters, %{ $params{filters} || {} } });
-      ($key, $value) = _apply_all($key, $value, qr/\b::(\w+)/, { %methods, %{ $params{methods} || {} } });
-      ($key, $value) = _dispatch_custom_filters($params{class}, $with_objects, $key, $value) if $params{class};
-      ($key, $value) = _apply_value_filters($key, $value, $type, $op);
+    my $is_multi      = $key =~ s/:multi//;
+    my @value_tokens  = $is_multi ? parse_line('\s+', 0, $value) : ($value);
+
+    ($key, $method)   = split m{::}, $key, 2;
+    ($key, @filters)  = split m{:},  $key;
+
+    my $orig_key      = $key;
+
+    for my $value_token (@value_tokens) {
+      $key                 = $orig_key;
+
+      $value_token         = _apply($value_token, $_, $all_filters) for @filters;
+      $value_token         = _apply($value_token, $method, $all_methods)                                 if $method && exists $all_methods->{$method};
+      ($key, $value_token) = _apply_complex($key, $value_token, $method, $all_complex)                   if $method && exists $all_complex->{$method};
+      ($key, $value_token) = _dispatch_custom_filters($params{class}, $with_objects, $key, $value_token) if $params{class};
+      ($key, $value_token) = _apply_value_filters($key, $value_token, $type, $op);
+
+      push @args, $key, $value_token;
     }
 
-    push @result, $key, $value if defined $key;
+    next unless defined $key;
+
+    push @result, $is_multi ? (and => [ @args ]) : @args;
   }
   return \@result;
 }
@@ -239,14 +260,10 @@ sub _apply {
   return $filters->{$name}->($value);
 }
 
-sub _apply_all {
-  my ($key, $value, $re, $subs) = @_;
-
-  while ($key =~ s/$re//) {
-    $value = _apply($value, $1, $subs);
-  }
-
-  return $key, $value;
+sub _apply_complex {
+  my ($key, $value, $name, $filters) = @_;
+  return $key, $value unless $name && $filters->{$name};
+  return $filters->{$name}->($key, $value);
 }
 
 1;
@@ -448,6 +465,13 @@ standard SQL C<=> operator.
 
 All these are recognized like the L<Rose::DB::Object> methods.
 
+=item lazu_bool_eq
+
+If the value is undefined or an empty string then this parameter will
+be completely removed from the query. Otherwise a falsish filter value
+will match for C<NULL> and C<FALSE>; trueish values will only match
+C<TRUE>.
+
 =back
 
 =head1 BUGS AND CAVEATS
index 704c340ae1d0ff301e310caef20f08e1be8a037d..5bdd4bf7d146ecb516b48acf7e3878cd0bd0f395 100644 (file)
@@ -3,7 +3,7 @@ package SL::DB::Helper::AttrDuration;
 use strict;
 
 use parent qw(Exporter);
-our @EXPORT = qw(attr_duration);
+our @EXPORT = qw(attr_duration attr_duration_minutes);
 
 use Carp;
 
@@ -13,6 +13,12 @@ sub attr_duration {
   _make($package, $_) for @attributes;
 }
 
+sub attr_duration_minutes {
+  my ($package, @attributes) = @_;
+
+  _make_minutes($package, $_) for @attributes;
+}
+
 sub _make {
   my ($package, $attribute) = @_;
 
@@ -75,6 +81,43 @@ sub _make {
   };
 }
 
+sub _make_minutes {
+  my ($package, $attribute) = @_;
+
+  no strict 'refs';
+
+  *{ $package . '::' . $attribute . '_as_hours' } = sub {
+    my ($self, $value) = @_;
+
+    $self->$attribute($value * 60 + ($self->$attribute % 60)) if @_ > 1;
+    return int(($self->$attribute // 0) / 60);
+  };
+
+  *{ $package . '::' . $attribute . '_as_minutes' } = sub {
+    my ($self, $value) = @_;
+
+    $self->$attribute(int($self->$attribute) - (int($self->$attribute) % 60) + ($value // 0)) if @_ > 1;
+    return ($self->$attribute // 0) % 60;
+  };
+
+  *{ $package . '::' . $attribute . '_as_duration_string' } = sub {
+    my ($self, $value) = @_;
+
+    if (@_ > 1) {
+      if (!defined($value) || ($value eq '')) {
+        $self->$attribute(undef);
+      } else {
+        croak $::locale->text("Invalid duration format") if $value !~ m{^(?:(\d*):)?(\d+)$};
+        $self->$attribute(($1 // 0) * 60 + ($2 // 0));
+      }
+    }
+
+    my $as_hours   = "${attribute}_as_hours";
+    my $as_minutes = "${attribute}_as_minutes";
+    return defined($self->$attribute) ? sprintf('%d:%02d', $self->$as_hours, $self->$as_minutes) : undef;
+  };
+}
+
 1;
 __END__
 
@@ -92,6 +135,7 @@ numeric columns
   # In a Rose model:
   use SL::DB::Helper::AttrDuration;
   __PACKAGE__->attr_duration('time_estimation');
+  __PACKAGE__->attr_duration_minutes('hours');
 
   # Read access:
   print "Minutes: " . $obj->time_estimation_as_minutes . " hours: " . $obj->time_estimation_as_hours . "\n";
@@ -104,11 +148,23 @@ numeric columns
 
 =head1 OVERVIEW
 
-This is a helper for columns that store a duration as a numeric or
-floating point number representing a number of hours. So the value
-1.75 would stand for "1 hour, 45 minutes".
+This is a helper for columns that store a duration in one of two formats:
 
-The helper methods created are:
+=over 2
+
+=item 1. as a numeric or floating point number representing a number
+of hours
+
+=item 2. as an integer presenting a number of minutes
+
+=back
+
+In the first case the value 1.75 would stand for "1 hour, 45
+minutes". In the second case the value 105 represents the same
+duration.
+
+The helper methods created depend on the mode. Calling
+C<attr_duration> makes the following methods available:
 
 =over 4
 
@@ -160,6 +216,26 @@ handles this case correctly.
 
 =back
 
+With C<attr_duration_minutes> the following methods are available:
+
+=over 4
+
+=item C<attribute_as_minutes [$new_value]>
+
+Access only the minutes. Return values are in the range [0 - 59].
+
+=item C<attribute_as_hours [$new_value]>
+
+Access only the hours. Returns an integer value.
+
+=item C<attribute_as_duration_string [$new_value]>
+
+Access the full value as a formatted string in the form C<h:mm>,
+e.g. C<1:30> for the value 90 minutes. Parsing such a string is
+supported, too.
+
+=back
+
 =head1 FUNCTIONS
 
 =over 4
@@ -169,6 +245,11 @@ handles this case correctly.
 Package method. Call with the names of attributes for which the helper
 methods should be created.
 
+=item C<attr_duration_minutes @attributes>
+
+Package method. Call with the names of attributes for which the helper
+methods should be created.
+
 =back
 
 =head1 BUGS
index 778785ce0d940ff3672f06aea22923362c8afd1c..262c113efd3be551818bf852874f4e2e27bdba62 100644 (file)
@@ -52,7 +52,7 @@ sub _args2str {
     map { $_ => $options->{$_} }
     sort keys %$options;                  # deterministic order
 
-  join ';', apply { s/([;\\])/\\$1/g } $dbconnect, $dbuser, $dbpasswd, $options_str, $initial_sql;
+  join ';', apply { $_ //= ''; s/([;\\])/\\$1/g } $dbconnect, $dbuser, $dbpasswd, $options_str, $initial_sql;
 }
 
 1;
index e184cd8cb86346ba7038aefd6d2b77492753d7ff..dec398d58882946f2b356034a2d701354954c769 100644 (file)
@@ -320,6 +320,7 @@ sub form_header {
     ]);
 
   $TMPL_VAR{ALL_PROJECTS}          = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
+  $form->{ALL_PROJECTS}            = $TMPL_VAR{ALL_PROJECTS}; # make projects available for second row drop-down in io.pl
   $TMPL_VAR{ALL_EMPLOYEES}         = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id},  deleted => 0 ] ]);
   $TMPL_VAR{ALL_SALESMEN}          = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id},  deleted => 0 ] ]);
   $TMPL_VAR{ALL_SHIPTO}            = SL::DB::Manager::Shipto->get_all_sorted(query => [
index c3242ec294a9e115dd11d16ef50bc7d28382c8d4..1d3fe7f90ea300cf6e6c0154832fe6725ca6e10d 100644 (file)
@@ -376,6 +376,7 @@ sub form_header {
     ]);
 
   $TMPL_VAR{ALL_PROJECTS}          = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
+  $form->{ALL_PROJECTS}            = $TMPL_VAR{ALL_PROJECTS}; # make projects available for second row drop-down in io.pl
 
   # label subs
   my $employee_list_query_gen      = sub { $::form->{$_[0]} ? [ or => [ id => $::form->{$_[0]}, deleted => 0 ] ] : [ deleted => 0 ] };
index 3a855299fceefa66b9cde5c8094b29bbdfcaf213..8ab2a5bc8f9c00c4ac95ac3b3ceca620db812951 100644 (file)
@@ -194,6 +194,10 @@ $(document).ready(function () {
     if (focussable(this)) window.focused_element = this;
   });
 
+  // Lowest priority: first focussable element in form.
+  set_cursor_to_first_element();
+
+  // Medium priority: class set in template
   var initial_focus = $(".initial_focus").filter(':visible')[0];
   if (initial_focus)
     $(initial_focus).focus();
@@ -201,7 +205,6 @@ $(document).ready(function () {
   // legacy. sone forms install these
   if (typeof fokus == 'function') { fokus(); return; }
   if (focus_by_name('cursor_fokus')) return;
-  set_cursor_to_first_element();
 });
 
 $('form').submit(function(){
index aa8856ab42d83af5372e45855ef3a7daea4cabc1..2c10d036b79f22ca7c6357ca36f491c9d7dfc74f 100644 (file)
@@ -1,6 +1,6 @@
 use lib 't';
 
-use Test::More tests => 37;
+use Test::More tests => 38;
 use Test::Deep;
 use Data::Dumper;
 
@@ -405,3 +405,18 @@ test {
     'orderitems.part.test' => { 'what', { ilike => '%2%' } },
   ]
 }, 'relationship + additional tokens + filters + methods', class => 'SL::DB::Manager::Order';
+
+test {
+  part => {
+    'obsolete::lazy_bool_eq' => '0',
+  },
+}, {
+  query => [
+      or => [
+        'part.obsolete' => undef,
+        'part.obsolete' => 0
+      ],
+  ],
+  with_objects => [ 'part' ],
+}, 'complex methods modifying the key';
+
index da05a32114024ba3cd9108a163682cbdde275d26..c2f2940d9124e68206d749616fe64a6d1ab438e4 100644 (file)
@@ -4,16 +4,20 @@ use base qw(SL::DB::Object);
 
 __PACKAGE__->meta->setup(
   table   => 'dummy',
-  columns => [ dummy => { type => 'numeric', precision => 2, scale => 12 }, ]
+  columns => [
+    dummy => { type => 'numeric', precision => 2, scale => 12 },
+    inty  => { type => 'integer' },
+  ]
 );
 
 use SL::DB::Helper::AttrDuration;
 
 __PACKAGE__->attr_duration('dummy');
+__PACKAGE__->attr_duration_minutes('inty');
 
 package main;
 
-use Test::More tests => 91;
+use Test::More tests => 120;
 use Test::Exception;
 
 use strict;
@@ -31,6 +35,8 @@ sub new_item {
 Support::TestSetup::login();
 my $item;
 
+### attr_duration
+
 # Wenn das Attribut undef ist:
 is(new_item->dummy,                    undef,  'uninitialized: raw');
 is(new_item->dummy_as_hours,           0,      'uninitialized: as_hours');
@@ -165,4 +171,51 @@ lives_ok  { new_item()->dummy_as_man_days_unit('h')       } 'known unit h';
 lives_ok  { new_item()->dummy_as_man_days_unit('hour')    } 'known unit hour';
 lives_ok  { new_item()->dummy_as_man_days_unit('man_day') } 'known unit man_day';
 
+### attr_duration_minutes
+
+# Wenn das Attribut undef ist:
+is(new_item->inty,                    undef,  'uninitialized: raw');
+is(new_item->inty_as_hours,           0,      'uninitialized: as_hours');
+is(new_item->inty_as_minutes,         0,      'uninitialized: as_minutes');
+is(new_item->inty_as_duration_string, undef,  'uninitialized: as_duration_string');
+
+# Auslesen kleiner 60 Minuten:
+is(new_item(inty => 37)->inty,                    37,     'initialized < 60: raw');
+is(new_item(inty => 37)->inty_as_hours,           0,      'initialized < 60: as_hours');
+is(new_item(inty => 37)->inty_as_minutes,         37,     'initialized < 60: as_minutes');
+is(new_item(inty => 37)->inty_as_duration_string, '0:37', 'initialized < 60: as_duration_string');
+
+# Auslesen größer 60 Minuten:
+is(new_item(inty => 145)->inty,                    145,    'initialized > 60: raw');
+is(new_item(inty => 145)->inty_as_hours,           2,      'initialized > 60: as_hours');
+is(new_item(inty => 145)->inty_as_minutes,         25,     'initialized > 60: as_minutes');
+is(new_item(inty => 145)->inty_as_duration_string, '2:25', 'initialized > 60: as_duration_string');
+
+$item = new_item(inty => 145); $item->inty_as_duration_string(undef);
+is($item->inty,                    undef, 'write as_duration_string undef read raw');
+is($item->inty_as_minutes,         0,     'write as_duration_string undef read as_minutes');
+is($item->inty_as_hours,           0,     'write as_duration_string undef read as_hours');
+is($item->inty_as_duration_string, undef, 'write as_duration_string undef read as_duration_string');
+
+$item = new_item(inty => 145); $item->inty_as_duration_string('');
+is($item->inty,                    undef, 'write as_duration_string "" read raw');
+is($item->inty_as_minutes,         0,     'write as_duration_string "" read as_minutes');
+is($item->inty_as_hours,           0,     'write as_duration_string "" read as_hours');
+is($item->inty_as_duration_string, undef, 'write as_duration_string "" read as_duration_string');
+
+$item = new_item(inty => 145); $item->inty_as_duration_string("3:21");
+is($item->inty,                    201,    'write as_duration_string 3:21 read raw');
+is($item->inty_as_minutes,         21,     'write as_duration_string 3:21 read as_minutes');
+is($item->inty_as_hours,           3,      'write as_duration_string 3:21 read as_hours');
+is($item->inty_as_duration_string, "3:21", 'write as_duration_string 3:21 read as_duration_string');
+
+$item = new_item(inty => 145); $item->inty_as_duration_string("03:1");
+is($item->inty,                    181,    'write as_duration_string 03:1 read raw');
+is($item->inty_as_minutes,         1,      'write as_duration_string 03:1 read as_minutes');
+is($item->inty_as_hours,           3,      'write as_duration_string 03:1 read as_hours');
+is($item->inty_as_duration_string, "3:01", 'write as_duration_string 03:1 read as_duration_string');
+
+# Parametervalidierung
+throws_ok { new_item()->inty_as_duration_string('invalid') } qr/invalid.*format/i, 'invalid duration format';
+
 done_testing();
index 7c850659bcbc16e3755f90f189a56190f61eaaa9..e53db193dbc89fc98386d2485af4395b45f6f4f9 100644 (file)
 
 % einkaufslieferschein (purchase_delivery_order)
 \newcommand{\einkaufslieferschein} {Eingangslieferschein}
+
+% Brief/letter
+\newcommand{\ihrzeichen}{Ihr Zeichen}
+\newcommand{\betreff}{Betreff}
index 2812389610b0f89c58e005ef2de2d12cc5c35841..8d393d512bb2cc35876c0bde2f0170b496cc9e47 100644 (file)
 
 % einkaufslieferschein (purchase_delivery_order)
 \newcommand{\einkaufslieferschein} {Purchase delivery order}
+
+% Brief/letter
+\newcommand{\ihrzeichen}{Your reference}
+\newcommand{\betreff}{Subject}
index b924152f91beaae78a239ff2d2974c3aa6a630cc..f783eca1a7fa6093f10aac6b011922b31ce1ac59 100644 (file)
@@ -26,7 +26,9 @@
 
   <%contact_formal%>
 
-  <%countrycode%> <%zipcode%> <%city%>
+  <%street%>
+
+  <%zipcode%> <%city%>
 
   <%country%>
 
 
 \vspace{2.5cm}
 \hfill<%date%>
-\textbf{<%reference%>}
 
-\vspace{1cm}
+<%if reference%>
+\textbf{\ihrzeichen : <%reference%>}
+<%end if%>
 
+\vspace{1cm}
 
-\textbf{<%subject%>}
+<%if subject%>
+\textbf{\betreff : <%subject%>}
+<%end if%>
 
 \vspace{1cm}
 
index 1a2b94887fed666f69155e637b8703f6825d8e6c..2f251e275ef01bfff88761f433163a32c35f572f 100644 (file)
@@ -4,7 +4,7 @@
 
 [%- INCLUDE 'common/flash.html' %]
 
-[%- PROCESS 'background_job_history/_filter.html' filter=SELF.filter %]
+[%- PROCESS 'background_job_history/_filter.html' filter=SELF.models.filtered.laundered %]
 
 [% IF !ENTRIES.size %]
  <p>
index f6fbce1d6ed8843c16aa13c5dee345289b88d404..8b98dcbb0f72163bf3ffedbc053e31f0e954c451 100644 (file)
@@ -1,3 +1,3 @@
 [%- USE L %]
-[%- PROCESS 'bank_transactions/_filter.html' filter=SELF.filter %]
+[%- PROCESS 'bank_transactions/_filter.html' filter=SELF.models.filtered.laundered %]
  <hr>
index cc35146be15600cabbda26658169ca70797cd4d1..01d0ff9af7b3cea9e009cac279c964d0406baedf 100644 (file)
@@ -1,3 +1,3 @@
 [%- USE L %]
-[%- PROCESS 'delivery_plan/_filter.html' filter=SELF.filter %]
+[%- PROCESS 'delivery_plan/_filter.html' filter=SELF.models.filtered.laundered %]
  <hr>
index baa00bb189cf45f42c6c09786ea37020e8804131..a5ecf752dd10863dcafefbfa67d5702f942780b8 100644 (file)
 
  [%- SET vctypelabel = vc == 'customer' ? LxERP.t8('Customer type') : LxERP.t8('Vendor type') %]
 
- <script type="text/javascript">
-   $(function(){ document.Form.donumber.focus(); });
- </script>
-
  <style type="text/css">
   .fixed_width {
     width: 250px;
@@ -29,9 +25,9 @@
      <th align="right">[% IF is_customer %][% 'Customer' | $T8 %][% ELSE %][% 'Vendor' | $T8 %][% END %]</th>
      <td colspan="3">
       [%- UNLESS SHOW_VC_DROP_DOWN %]
-      <input type="text" name="[% HTML.escape(vc) %]" class="fixed_width">
+      <input type="text" name="[% HTML.escape(vc) %]" class="fixed_width initial_focus">
       [%- ELSE %]
-      <select name="[% vc %]" class="fixed_width">
+      <select name="[% vc %]" class="fixed_width initial_focus">
        <option></option>
        [%- FOREACH row = ALL_VC %]
        <option>[% HTML.escape(row.name) %]--[% HTML.escape(row.id) %]</option>
index 5fb399cf99dbd5dd54ef62ab01c97f7b5d364a0f..1312d75a35a9572c83a38ac5ea4fabb9ff242e70 100644 (file)
@@ -1,3 +1,3 @@
 [%- USE L %]
-[%- PROCESS 'financial_controlling_report/_filter.html' filter=SELF.filter %]
+[%- PROCESS 'financial_controlling_report/_filter.html' filter=SELF.models.filtered.laundered %]
  <hr>
index 4d625c7aebfdbb83a93cd49830d60ed12ec84a02..5669d8eeface4b265c3851659a4324d20b8299bc 100644 (file)
@@ -5,7 +5,7 @@
 
 <div style='overflow:hidden'>
 
-[% LxERP.t8("Filter") %]: [% L.input_tag('part_picker_filter', SELF.filter.all_substr_multi__ilike, class='part_picker_filter') %]
+[% LxERP.t8("Filter") %]: [% L.input_tag('part_picker_filter', SELF.models.filtered.laundered.all_substr_multi__ilike, class='part_picker_filter') %]
 [% L.hidden_tag('part_picker_real_id', FORM.real_id) %]
 
 <div class='float-right'>
index 0af23b4d59a095dcffa2e183b8f25df30de55778..a534a87331aa58d420ab61527b6d4cff5f3f431c 100644 (file)
@@ -1,4 +1,4 @@
 [%- USE L %]
 [%- PROCESS 'common/flash.html' %]
-[%- PROCESS 'price_rule/_filter.html' filter=SELF.filter UNLESS FORM.inline %]
+[%- PROCESS 'price_rule/_filter.html' filter=SELF.models.filtered.laundered UNLESS FORM.inline %]
  <hr>
index df03f9ebe11a129b7a0bb1eab256109553b912fd..87c7efee551e489c136e1bef627b5e31bb2a20be 100644 (file)
@@ -9,7 +9,7 @@
 </div>
 <div class='filter_toggle' style='display:none'>
 <a href='#' onClick='javascript:$(".filter_toggle").toggle()'>[% 'Hide Filter' | $T8 %]</a>
-[%- PROCESS 'project/_filter.html' filter=SELF.filter %]
+[%- PROCESS 'project/_filter.html' filter=SELF.models.filtered.laundered %]
 
 [% L.hidden_tag('action', 'Project/dispatch') %]
 [% L.hidden_tag('sort_by', FORM.sort_by) %]
index 013ba540e940579834fff2a8da3ee3e7ef1d9eb4..18842b2eaebc493d0103aa951e256784e46ace06 100644 (file)
@@ -33,7 +33,6 @@
           <td class="top_border"></td>
           <td class="top_border"></td>
           <td class="top_border"></td>
-          <td class="top_border"></td>
           <td class="bt_balance top_border" align="right">[% LxERP.format_amount(SELF.bt_balance, 2) %]</td>
           <td class="bb_balance top_border" align="right">[% LxERP.format_amount(-1 * SELF.bb_balance, 2) %]</td>
           <td class="top_border"></td>
@@ -41,6 +40,7 @@
           <td class="top_border"></td>
           <td class="top_border"></td>
           <td class="top_border"></td>
+          <td class="top_border"></td>
         </tr>
       </tfoot>
     </table>
index 507bfaed649f44995af3fe10c764f65667eb7d61..457e05e14f0dcec775177746988bea7775a2580b 100644 (file)
@@ -1,3 +1,3 @@
 [%- USE L %]
-[%- PROCESS "requirement_spec/_filter.html" filter=SELF.filter %]
+[%- PROCESS "requirement_spec/_filter.html" filter=SELF.models.filtered.laundered %]
  <hr>