Merge branch 'master' of vc.linet-services.de:public/lx-office-erp
authorThomas Heck <theck@linet-services.de>
Thu, 6 Sep 2012 12:22:14 +0000 (14:22 +0200)
committerThomas Heck <theck@linet-services.de>
Thu, 6 Sep 2012 12:22:14 +0000 (14:22 +0200)
15 files changed:
SL/Controller/BackgroundJob.pm
SL/Controller/BackgroundJobHistory.pm
SL/Controller/Base.pm
SL/Controller/DeliveryPlan.pm
SL/Controller/Helper/GetModels.pm
SL/Controller/Helper/Paginated.pm
SL/Controller/Helper/Sorted.pm
SL/Form.pm
SL/Locale.pm
SL/Locale/String.pm [new file with mode: 0644]
SL/Template/Plugin/L.pm
locale/de/all
scripts/locales.pl
templates/webpages/delivery_plan/report_bottom.html
templates/webpages/generic/exception.html [new file with mode: 0644]

index f80f57a..7729daf 100644 (file)
@@ -9,6 +9,7 @@ use SL::Controller::Helper::Paginated;
 use SL::Controller::Helper::Sorted;
 use SL::DB::BackgroundJob;
 use SL::Helper::Flash;
+use SL::Locale::String;
 use SL::System::TaskServer;
 
 use Rose::Object::MakeMethods::Generic
@@ -26,12 +27,12 @@ __PACKAGE__->make_paginated(ONLY => [ qw(list) ]);
 __PACKAGE__->make_sorted(
   ONLY         => [ qw(list) ],
 
-  package_name => $::locale->text('Package name'),
-  type         => $::locale->text('Execution type'),
-  active       => $::locale->text('Active'),
-  cron_spec    => $::locale->text('Execution schedule'),
-  last_run_at  => $::locale->text('Last run at'),
-  next_run_at  => $::locale->text('Next run at'),
+  package_name => t8('Package name'),
+  type         => t8('Execution type'),
+  active       => t8('Active'),
+  cron_spec    => t8('Execution schedule'),
+  last_run_at  => t8('Last run at'),
+  next_run_at  => t8('Next run at'),
 );
 
 #
index 5992dc0..fbee5c0 100644 (file)
@@ -9,6 +9,7 @@ use SL::Controller::Helper::Paginated;
 use SL::Controller::Helper::Sorted;
 use SL::DB::BackgroundJobHistory;
 use SL::Helper::Flash;
+use SL::Locale::String;
 use SL::System::TaskServer;
 
 use Rose::Object::MakeMethods::Generic
@@ -26,11 +27,11 @@ __PACKAGE__->make_paginated(ONLY => [ qw(list) ]);
 __PACKAGE__->make_sorted(
   ONLY         => [ qw(list) ],
 
-  package_name => $::locale->text('Package name'),
-  run_at       => $::locale->text('Run at'),
-  status       => $::locale->text('Execution status'),
-  result       => $::locale->text('Result'),
-  error        => $::locale->text('Error'),
+  package_name => t8('Package name'),
+  run_at       => t8('Run at'),
+  status       => t8('Execution status'),
+  result       => t8('Result'),
+  error        => t8('Error'),
 );
 
 #
index 1188ffe..6d879e8 100644 (file)
@@ -10,6 +10,11 @@ use List::Util qw(first);
 use SL::Request qw(flatten);
 use SL::MoreCommon qw(uri_encode);
 
+use Rose::Object::MakeMethods::Generic
+(
+  scalar => [ qw(action_name) ],
+);
+
 #
 # public/helper functions
 #
@@ -20,7 +25,7 @@ sub url_for {
   return $_[0] if (scalar(@_) == 1) && !ref($_[0]);
 
   my %params      = ref($_[0]) eq 'HASH' ? %{ $_[0] } : @_;
-  my $controller  = delete($params{controller}) || $self->_controller_name;
+  my $controller  = delete($params{controller}) || $self->controller_name;
   my $action      = $params{action}             || 'dispatch';
 
   my $script;
@@ -124,6 +129,12 @@ sub send_file {
   $file->close;
 }
 
+sub controller_name {
+  my $class = ref($_[0]) || $_[0];
+  $class    =~ s/^SL::Controller:://;
+  return $class;
+}
+
 #
 # Before/after run hooks
 #
@@ -198,17 +209,12 @@ sub _run_action {
 
   $::form->error("Invalid action '${action}' for controller " . ref($self)) if !$self->can($sub);
 
+  $self->action_name($action);
   $self->_run_hooks('before', $action);
   $self->$sub(@_);
   $self->_run_hooks('after', $action);
 }
 
-sub _controller_name {
-  my $class = ref($_[0]) || $_[0];
-  $class    =~ s/^SL::Controller:://;
-  return $class;
-}
-
 sub _dispatch {
   my $self    = shift;
 
@@ -218,6 +224,7 @@ sub _dispatch {
   my $sub     = "action_${action}";
 
   if ($self->can($sub)) {
+    $self->action_name($action);
     $self->_run_hooks('before', $action);
     $self->$sub(@_);
     $self->_run_hooks('after', $action);
@@ -238,6 +245,7 @@ sub _template_obj {
                     INCLUDE_PATH => '.:templates/webpages',
                     COMPILE_EXT  => '.tcc',
                     COMPILE_DIR  => $::lx_office_conf{paths}->{userspath} . '/templates-cache',
+                    ERROR        => 'templates/webpages/generic/exception.html',
                   }) || croak;
 
   return $self->{__basepriv_template_obj};
@@ -470,7 +478,7 @@ parameter or as a normal hash.
 
 The controller to call is given by C<$params{controller}>. It defaults
 to the current controller as returned by
-L</_controller_name>.
+L</controller_name>.
 
 The action to call is given by C<$params{action}>. It defaults to
 C<dispatch>.
@@ -543,6 +551,18 @@ variables whose name starts with C<{AUTH}> are removed before the
 request is routed. Only controllers that handle login requests
 themselves should return trueish for this function.
 
+=item C<controller_name>
+
+Returns the name of the curernt controller package without the
+C<SL::Controller::> prefix. This method can be called both as a class
+method and an instance method.
+
+=item C<action_name>
+
+Returns the name of the currently executing action. If the dispatcher
+mechanism was used then this is not C<dispatch> but the actual method
+name the dispatching resolved to.
+
 =back
 
 =head2 PRIVATE FUNCTIONS
@@ -551,11 +571,6 @@ These functions are supposed to be used from this base class only.
 
 =over 4
 
-=item C<_controller_name>
-
-Returns the name of the curernt controller package without the
-C<SL::Controller::> prefix.
-
 =item C<_dispatch>
 
 Implements the method lookup for indirect dispatching mentioned in the
index e280a1b..5ce462b 100644 (file)
@@ -5,41 +5,52 @@ use parent qw(SL::Controller::Base);
 
 use Clone qw(clone);
 use SL::DB::OrderItem;
+use SL::Controller::Helper::GetModels;
+use SL::Controller::Helper::Paginated;
+use SL::Controller::Helper::Sorted;
 use SL::Controller::Helper::ParseFilter;
 use SL::Controller::Helper::ReportGenerator;
+use SL::Locale::String;
+
+use Rose::Object::MakeMethods::Generic (
+  scalar => [ qw(db_args flat_filter) ],
+);
 
 __PACKAGE__->run_before(sub { $::auth->assert('sales_order_edit'); });
 
+__PACKAGE__->get_models_url_params('flat_filter');
+__PACKAGE__->make_paginated(
+  MODEL         => 'OrderItem',
+  PAGINATE_ARGS => 'db_args',
+  ONLY          => [ qw(list) ],
+);
+
+__PACKAGE__->make_sorted(
+  MODEL       => 'OrderItem',
+  ONLY        => [ qw(list) ],
+
+  DEFAULT_BY  => 'reqdate',
+  DEFAULT_DIR => 1,
+
+  reqdate     => t8('Reqdate'),
+  description => t8('Description'),
+  partnumber  => t8('Part Number'),
+  qty         => t8('Qty'),
+  shipped_qty => t8('shipped'),
+  ordnumber   => t8('Order'),
+  customer    => t8('Customer'),
+);
+
 sub action_list {
   my ($self) = @_;
-  my %list_params = (
-    sort_by  => $::form->{sort_by} || 'reqdate',
-    sort_dir => $::form->{sort_dir},
-    filter   => $::form->{filter},
-    page     => $::form->{page},
-  );
 
-  my $db_args = $self->setup_for_list(%list_params);
-  $self->{pages} = SL::DB::Manager::OrderItem->paginate(%list_params, args => $db_args);
-  $self->{flat_filter} = { map { $_->{key} => $_->{value} } $::form->flatten_variables('filter') };
+  $self->db_args($self->setup_for_list(filter => $::form->{filter}));
+  $self->flat_filter({ map { $_->{key} => $_->{value} } $::form->flatten_variables('filter') });
   $self->make_filter_summary;
 
-  my $top    = $::form->parse_html_template('delivery_plan/report_top', { FORM => $::form, SELF => $self });
-  my $bottom = $::form->parse_html_template('delivery_plan/report_bottom', { SELF => $self });
-
-  $self->prepare_report(
-    report_generator_options => {
-      raw_top_info_text    => $top,
-      raw_bottom_info_text => $bottom,
-      controller_class     => 'DeliveryPlan',
-    },
-    report_generator_export_options => [
-      'list', qw(filter sort_by sort_dir),
-    ],
-    db_args => $db_args,
-  );
+  $self->prepare_report;
 
-  $self->{orderitems} = SL::DB::Manager::OrderItem->get_all(%$db_args);
+  $self->{orderitems} = $self->get_models(%{ $self->db_args });
 
   $self->list_objects;
 }
@@ -55,8 +66,6 @@ sub setup_for_list {
       with_objects => [ 'order', 'order.customer', 'part' ],
       launder_to => $self->{filter},
     ),
-    sort_by => $self->set_sort_params(%params),
-    page    => $params{page},
   );
 
   $args{query} = [ @{ $args{query} || [] },
@@ -116,81 +125,53 @@ sub setup_for_list {
   return \%args;
 }
 
-sub set_sort_params {
-  my ($self, %params) = @_;
-  my $sort_str;
-  ($self->{sort_by}, $self->{sort_dir}, $sort_str) =
-    SL::DB::Manager::OrderItem->make_sort_string(%params);
-  return $sort_str;
-}
-
 sub prepare_report {
-  my ($self, %params) = @_;
+  my ($self)      = @_;
 
-  my $objects  = $params{objects} || [];
-  my $report = SL::ReportGenerator->new(\%::myconfig, $::form);
+  my $report      = SL::ReportGenerator->new(\%::myconfig, $::form);
   $self->{report} = $report;
 
-  my @columns  = qw(reqdate customer ordnumber partnumber description qty shipped_qty);
-  my @visible  = qw(reqdate partnumber description qty shipped_qty ordnumber customer);
-  my @sortable = qw(reqdate partnumber description                 ordnumber customer);
+  my @columns     = qw(reqdate customer ordnumber partnumber description qty shipped_qty);
+  my @sortable    = qw(reqdate customer ordnumber partnumber description                );
 
   my %column_defs = (
-    reqdate                 => { text => $::locale->text('Reqdate'),
-                                  sub => sub { $_[0]->reqdate_as_date || $_[0]->order->reqdate_as_date }},
-    description             => { text => $::locale->text('Description'),
-                                  sub => sub { $_[0]->description },
-                             obj_link => sub { $self->link_to($_[0]->part) }},
-    partnumber              => { text => $::locale->text('Part Number'),
-                                  sub => sub { $_[0]->part->partnumber },
-                             obj_link => sub { $self->link_to($_[0]->part) }},
-    qty                     => { text => $::locale->text('Qty'),
-                                  sub => sub { $_[0]->qty_as_number . ' ' . $_[0]->unit }},
-    missing                 => { text => $::locale->text('Missing qty'),
-                                  sub => sub { $::form->format_amount(\%::myconfig, $_[0]->qty - $_[0]->shipped_qty, 2) . ' ' . $_[0]->unit }},
-    shipped_qty             => { text => $::locale->text('shipped'),
-                                  sub => sub { $::form->format_amount(\%::myconfig, $_[0]->shipped_qty, 2) . ' ' . $_[0]->unit }},
-    ordnumber               => { text => $::locale->text('Order'),
-                                  sub => sub { $_[0]->order->ordnumber },
-                             obj_link => sub { $self->link_to($_[0]->order) }},
-    customer                => { text => $::locale->text('Customer'),
-                                  sub => sub { $_[0]->order->customer->name },
-                             obj_link => sub { $self->link_to($_[0]->order->customer) }},
+    reqdate       => {      sub => sub { $_[0]->reqdate_as_date || $_[0]->order->reqdate_as_date                         } },
+    description   => {      sub => sub { $_[0]->description                                                              },
+                       obj_link => sub { $self->link_to($_[0]->part)                                                     } },
+    partnumber    => {      sub => sub { $_[0]->part->partnumber                                                         },
+                       obj_link => sub { $self->link_to($_[0]->part)                                                     } },
+    qty           => {      sub => sub { $_[0]->qty_as_number . ' ' . $_[0]->unit                                        } },
+    shipped_qty   => {      sub => sub { $::form->format_amount(\%::myconfig, $_[0]->shipped_qty, 2) . ' ' . $_[0]->unit } },
+    ordnumber     => {      sub => sub { $_[0]->order->ordnumber                                                         },
+                       obj_link => sub { $self->link_to($_[0]->order)                                                    } },
+    customer      => {      sub => sub { $_[0]->order->customer->name                                                    },
+                       obj_link => sub { $self->link_to($_[0]->order->customer)                                          } },
   );
 
+  map { $column_defs{$_}->{text} = $::locale->text( $self->get_sort_spec->{$_}->{title} ) } keys %column_defs;
 
-  for my $col (@sortable) {
-    $column_defs{$col}{link} = $self->url_for(
-      action   => 'list',
-      sort_by  => $col,
-      sort_dir => ($self->{sort_by} eq $col ? 1 - $self->{sort_dir} : $self->{sort_dir}),
-      page     => $self->{pages}{cur},
-      %{ $self->{flat_filter} },
-    );
-  }
-
-  map { $column_defs{$_}->{visible} = 1 } @visible;
-
-  $report->set_columns(%column_defs);
-  $report->set_column_order(@columns);
-  $report->set_options(allow_pdf_export => 1, allow_csv_export => 1);
-  $report->set_sort_indicator(%params);
-  $report->set_export_options(@{ $params{report_generator_export_options} || [] });
   $report->set_options(
-    %{ $params{report_generator_options} || {} },
-    output_format        => 'HTML',
-    top_info_text        => $::locale->text('Delivery Plan for currently outstanding sales orders'),
-    title                => $::locale->text('Delivery Plan'),
+    std_column_visibility => 1,
+    controller_class      => 'DeliveryPlan',
+    output_format         => 'HTML',
+    top_info_text         => $::locale->text('Delivery Plan for currently outstanding sales orders'),
+    raw_top_info_text     => $self->render('delivery_plan/report_top',    { no_output => 1, partial => 1 }),
+    raw_bottom_info_text  => $self->render('delivery_plan/report_bottom', { no_output => 1, partial => 1 }),
+    title                 => $::locale->text('Delivery Plan'),
+    allow_pdf_export      => 1,
+    allow_csv_export      => 1,
   );
+  $report->set_columns(%column_defs);
+  $report->set_column_order(@columns);
+  $report->set_export_options(qw(list filter));
   $report->set_options_from_form;
+  $self->set_report_generator_sort_options(report => $report, sortable_columns => \@sortable);
 
-  SL::DB::Manager::OrderItem->disable_paginating(args => $params{db_args}) if $report->{options}{output_format} =~ /^(pdf|csv)$/i;
+  $self->disable_pagination if $report->{options}{output_format} =~ /^(pdf|csv)$/i;
 
   $self->{report_data} = {
-    column_defs => \%column_defs,
-    columns     => \@columns,
-    visible     => \@visible,
-    sortable    => \@sortable,
+    column_defs        => \%column_defs,
+    columns            => \@columns,
   };
 }
 
@@ -220,19 +201,19 @@ sub make_filter_summary {
   my @filter_strings;
 
   my @filters = (
-    [ $filter->{order}{"ordnumber:substr::ilike"}, $::locale->text('Number') ],
-    [ $filter->{part}{"partnumber:substr::ilike"}, $::locale->text('Part Number') ],
-    [ $filter->{"description:substr::ilike"}, $::locale->text('Part Description') ],
-    [ $filter->{"reqdate:date::ge"}, $::locale->text('Delivery Date') . " " . $::locale->text('From Date') ],
-    [ $filter->{"reqdate:date::le"}, $::locale->text('Delivery Date') . " " . $::locale->text('To Date') ],
-    [ $filter->{"qty:number"}, $::locale->text('Quantity') ],
-    [ $filter->{order}{customer}{"name:substr::ilike"}, $::locale->text('Customer') ],
-    [ $filter->{order}{customer}{"customernumber:substr::ilike"}, $::locale->text('Customer Number') ],
+    [ $filter->{order}{"ordnumber:substr::ilike"},                $::locale->text('Number')                                             ],
+    [ $filter->{part}{"partnumber:substr::ilike"},                $::locale->text('Part Number')                                        ],
+    [ $filter->{"description:substr::ilike"},                     $::locale->text('Part Description')                                   ],
+    [ $filter->{"reqdate:date::ge"},                              $::locale->text('Delivery Date') . " " . $::locale->text('From Date') ],
+    [ $filter->{"reqdate:date::le"},                              $::locale->text('Delivery Date') . " " . $::locale->text('To Date')   ],
+    [ $filter->{"qty:number"},                                    $::locale->text('Quantity')                                           ],
+    [ $filter->{order}{customer}{"name:substr::ilike"},           $::locale->text('Customer')                                           ],
+    [ $filter->{order}{customer}{"customernumber:substr::ilike"}, $::locale->text('Customer Number')                                    ],
   );
 
   my @flags = (
-    [ $filter->{part}{type}{part}, $::locale->text('Parts') ],
-    [ $filter->{part}{type}{service}, $::locale->text('Services') ],
+    [ $filter->{part}{type}{part},     $::locale->text('Parts')      ],
+    [ $filter->{part}{type}{service},  $::locale->text('Services')   ],
     [ $filter->{part}{type}{assembly}, $::locale->text('Assemblies') ],
   );
 
index 00ef310..e353657 100644 (file)
@@ -3,7 +3,7 @@ package SL::Controller::Helper::GetModels;
 use strict;
 
 use Exporter qw(import);
-our @EXPORT = qw(get_callback get_models);
+our @EXPORT = qw(get_models_url_params get_callback get_models);
 
 use constant PRIV => '__getmodelshelperpriv';
 
@@ -21,6 +21,22 @@ sub register_get_models_handlers {
   map { push @{ $registered_handlers{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %registered_handlers;
 }
 
+sub get_models_url_params {
+  my ($class, $sub_name_or_code) = @_;
+
+  my $code     = (ref($sub_name_or_code) || '') eq 'CODE' ? $sub_name_or_code : sub { shift->$sub_name_or_code(@_) };
+  my $callback = sub {
+    my ($self, %params)   = @_;
+    my @additional_params = $code->($self);
+    return (
+      %params,
+      (scalar(@additional_params) == 1) && (ref($additional_params[0]) eq 'HASH') ? %{ $additional_params[0] } : @additional_params,
+    );
+  };
+
+  push @{ $registered_handlers{callback} }, $callback;
+}
+
 sub get_callback {
   my ($self, %override_params) = @_;
 
@@ -102,6 +118,19 @@ in L<get_callback>.
 
 =over 4
 
+=item C<get_models_url_params $class, $sub>
+
+Register one of the controller's subs to be called whenever an URL has
+to be generated (e.g. for sort and pagination links). This is a way
+for the controller to add additional parameters to the URL (e.g. for
+filter parameters).
+
+The C<$sub> parameter can be either a code reference or the name of
+one of the controller's functions.
+
+The value returned by this C<$sub> must be either a single hash
+reference or a hash of key/value pairs to add to the URL.
+
 =item C<register_get_models_handlers $class, %handlers>
 
 This function should only be called from other controller helpers like
index f4a319d..d3da20d 100644 (file)
@@ -3,7 +3,7 @@ package SL::Controller::Helper::Paginated;
 use strict;
 
 use Exporter qw(import);
-our @EXPORT = qw(make_paginated get_paginate_spec get_current_paginate_params _save_current_paginate_params _get_models_handler_for_paginated _callback_handler_for_paginated);
+our @EXPORT = qw(make_paginated get_paginate_spec get_current_paginate_params _save_current_paginate_params _get_models_handler_for_paginated _callback_handler_for_paginated disable_pagination);
 
 use constant PRIV => '__paginatedhelper_priv';
 
@@ -12,12 +12,13 @@ my $controller_paginate_spec;
 sub make_paginated {
   my ($class, %specs)       = @_;
 
-  $specs{MODEL}           ||=  $class->_controller_name;
+  $specs{MODEL}           ||=  $class->controller_name;
   $specs{MODEL}             =~ s{ ^ SL::DB:: (?: .* :: )? }{}x;
   $specs{PER_PAGE}        ||= "SL::DB::Manager::$specs{MODEL}"->default_objects_per_page;
   $specs{FORM_PARAMS}     ||= [ qw(page per_page) ];
   $specs{ONLY}            ||= [];
   $specs{ONLY}              = [ $specs{ONLY} ] if !ref $specs{ONLY};
+  $specs{ONLY_MAP}          = @{ $specs{ONLY} } ? { map { ($_ => 1) } @{ $specs{ONLY} } } : { '__ALL__' => 1 };
 
   $controller_paginate_spec = \%specs;
 
@@ -45,7 +46,7 @@ sub get_current_paginate_params {
 
   my $spec              = $self->get_paginate_spec;
 
-  my $priv              = $self->{PRIV()} || {};
+  my $priv              = _priv($self);
   $params{page}         = $priv->{page}     unless defined $params{page};
   $params{per_page}     = $priv->{per_page} unless defined $params{per_page};
 
@@ -54,7 +55,10 @@ sub get_current_paginate_params {
     per_page            => ($params{per_page} * 1) || $spec->{PER_PAGE},
   );
 
-  my $calculated_params = "SL::DB::Manager::$spec->{MODEL}"->paginate(%paginate_params, args => {});
+  my %paginate_args     = ref($spec->{PAGINATE_ARGS}) eq 'CODE' ? %{ $spec->{PAGINATE_ARGS}->($self) }
+                        :     $spec->{PAGINATE_ARGS}            ? do { my $sub = $spec->{PAGINATE_ARGS}; %{ $self->$sub() } }
+                        :                                         ();
+  my $calculated_params = "SL::DB::Manager::$spec->{MODEL}"->paginate(%paginate_params, args => \%paginate_args);
   %paginate_params      = (
     %paginate_params,
     num_pages    => $calculated_params->{max},
@@ -66,6 +70,11 @@ sub get_current_paginate_params {
   return %paginate_params;
 }
 
+sub disable_pagination {
+  my ($self)               = @_;
+  _priv($self)->{disabled} = 1;
+}
+
 #
 # private functions
 #
@@ -73,6 +82,8 @@ sub get_current_paginate_params {
 sub _save_current_paginate_params {
   my ($self)        = @_;
 
+  return if !_is_enabled($self);
+
   my $paginate_spec = $self->get_paginate_spec;
   $self->{PRIV()}   = {
     page            => $::form->{ $paginate_spec->{FORM_PARAMS}->[0] } || 1,
@@ -84,9 +95,9 @@ sub _save_current_paginate_params {
 
 sub _callback_handler_for_paginated {
   my ($self, %params) = @_;
-  my $priv            = $self->{PRIV()} || {};
+  my $priv            = _priv($self);
 
-  if ($priv->{page}) {
+  if (_is_enabled($self) && $priv->{page}) {
     my $paginate_spec                             = $self->get_paginate_spec;
     $params{ $paginate_spec->{FORM_PARAMS}->[0] } = $priv->{page};
     $params{ $paginate_spec->{FORM_PARAMS}->[1] } = $priv->{per_page} if $priv->{per_page};
@@ -99,15 +110,27 @@ sub _callback_handler_for_paginated {
 
 sub _get_models_handler_for_paginated {
   my ($self, %params)    = @_;
-  $params{model}       ||= $self->get_paginate_spec->{MODEL};
+  my $spec               = $self->get_paginate_spec;
+  $params{model}       ||= $spec->{MODEL};
 
-  "SL::DB::Manager::$params{model}"->paginate($self->get_current_paginate_params, args => \%params);
+  "SL::DB::Manager::$params{model}"->paginate($self->get_current_paginate_params, args => \%params) if _is_enabled($self);
 
-  # $::lxdebug->dump(0, "GM handler for paginated; params nach modif:", \%params);
+  # $::lxdebug->dump(0, "GM handler for paginated; params nach modif (is_enabled? " . _is_enabled($self) . ")", \%params);
 
   return %params;
 }
 
+sub _priv {
+  my ($self)        = @_;
+  $self->{PRIV()} ||= {};
+  return $self->{PRIV()};
+}
+
+sub _is_enabled {
+  my ($self) = @_;
+  return !_priv($self)->{disabled} && ($self->get_paginate_spec->{ONLY_MAP}->{$self->action_name} || $self->get_paginate_spec->{ONLY_MAP}->{'__ALL__'});
+}
+
 1;
 __END__
 
@@ -211,6 +234,19 @@ derived from the controller's package (e.g. for the controller
 C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
 C<BackgroundJobHistory>).
 
+=item * C<PAGINATE_ARGS>
+
+Optional. Either a code reference or the name of function to be called
+on the controller importing this helper.
+
+If this funciton is given then the paginate helper calls it whenever
+it has to count the total number of models for calculating the number
+of pages to display. The function must return a hash reference with
+elements suitable for passing to a Rose model manager's C<get_all>
+function.
+
+This can be used e.g. when filtering is used.
+
 =item * C<PER_PAGE>
 
 Optional. An integer: the number of models to return per page.
@@ -278,6 +314,12 @@ Returns a hash reference to the paginate spec structure given in the call
 to L<make_paginated> after normalization (hash reference construction,
 applying default parameters etc).
 
+=item C<disable_pagination>
+
+Disable pagination for the duration of the current action. Can be used
+when using the attribute C<ONLY> to L<make_paginated> does not
+cover all cases.
+
 =back
 
 =head1 BUGS
index 4afae9d..c9e5779 100644 (file)
@@ -2,8 +2,12 @@ package SL::Controller::Helper::Sorted;
 
 use strict;
 
+use Carp;
+use List::MoreUtils qw(uniq);
+
 use Exporter qw(import);
-our @EXPORT = qw(make_sorted get_sort_spec get_current_sort_params _save_current_sort_params _get_models_handler_for_sorted _callback_handler_for_sorted);
+our @EXPORT = qw(make_sorted get_sort_spec get_current_sort_params set_report_generator_sort_options
+                 _save_current_sort_params _get_models_handler_for_sorted _callback_handler_for_sorted);
 
 use constant PRIV => '__sortedhelperpriv';
 
@@ -12,13 +16,13 @@ my $controller_sort_spec;
 sub make_sorted {
   my ($class, %specs) = @_;
 
-  $specs{MODEL} ||=  $class->_controller_name;
+  $specs{MODEL} ||=  $class->controller_name;
   $specs{MODEL}   =~ s{ ^ SL::DB:: (?: .* :: )? }{}x;
 
   while (my ($column, $spec) = each %specs) {
     next if $column =~ m/^[A-Z_]+$/;
 
-    $spec = $specs{$column} = { title => $spec } if !ref $spec;
+    $spec = $specs{$column} = { title => $spec } if (ref($spec) || '') ne 'HASH';
 
     $spec->{model}        ||= $specs{MODEL};
     $spec->{model_column} ||= $column;
@@ -72,6 +76,30 @@ sub get_current_sort_params {
   return %sort_params;
 }
 
+sub set_report_generator_sort_options {
+  my ($self, %params) = @_;
+
+  $params{$_} or croak("Missing parameter '$_'") for qw(report sortable_columns);
+
+  my %current_sort_params = $self->get_current_sort_params;
+
+  foreach my $col (@{ $params{sortable_columns} }) {
+    $params{report}->{columns}->{$col}->{link} = $self->get_callback(
+      sort_by  => $col,
+      sort_dir => ($current_sort_params{by} eq $col ? 1 - $current_sort_params{dir} : $current_sort_params{dir}),
+    );
+  }
+
+  $params{report}->set_sort_indicator($current_sort_params{by}, 1 - $current_sort_params{dir});
+
+  if ($params{report}->{export}) {
+    $params{report}->{export}->{variable_list} = [ uniq(
+      @{ $params{report}->{export}->{variable_list} },
+      @{ $self->get_sort_spec->{FORM_PARAMS} }
+    )];
+  }
+}
+
 #
 # private functions
 #
@@ -277,6 +305,9 @@ Required. A user-displayable title to be used by functions like the
 layout helper's C<sortable_table_header>. Does not have a default
 value.
 
+Note that this string must be the untranslated English version of the
+string. The titles will be translated whenever they're requested.
+
 =item * C<model>
 
 Optional. The name of a Rose database model this sort index refers
@@ -317,6 +348,31 @@ Returns a hash reference to the sort spec structure given in the call
 to L<make_sorted> after normalization (hash reference construction,
 applying default parameters etc).
 
+=item C<set_report_generator_sort_options %params>
+
+This function does three things with an instance of
+L<SL::ReportGenerator>:
+
+=over 4
+
+=item 1. it sets the sort indicator,
+
+=item 2. it sets the the links for those column headers that are
+sortable and
+
+=item 3. it adds the C<FORM_PARAMS> fields to the list of variables in
+the report generator's export options.
+
+=back
+
+The report generator instance must be passed as the parameter
+C<report>. The parameter C<sortable_columns> must be an array
+reference of column names that are sortable.
+
+The report generator instance must already have its columns and export
+options set via calls to its L<SL::ReportGenerator::set_columns> and
+L<SL::ReportGenerator::set_export_options> functions.
+
 =back
 
 =head1 BUGS
index 547611d..55ee46d 100644 (file)
@@ -695,6 +695,8 @@ sub init_template {
 
   return $self->template if $self->template;
 
+  # Force scripts/locales.pl to pick up the exception handling template.
+  # parse_html_template('generic/exception')
   return $self->template(Template->new({
      'INTERPOLATE'  => 0,
      'EVAL_PERL'    => 0,
@@ -704,6 +706,7 @@ sub init_template {
      'INCLUDE_PATH' => '.:templates/webpages',
      'COMPILE_EXT'  => '.tcc',
      'COMPILE_DIR'  => $::lx_office_conf{paths}->{userspath} . '/templates-cache',
+     'ERROR'        => 'templates/webpages/generic/exception.html',
   })) || die;
 }
 
index 5016900..8fa3780 100644 (file)
@@ -214,6 +214,8 @@ sub text {
   my $self = shift;
   my $text = shift;
 
+  return $text->translated if (ref($text) || '') eq 'SL::Locale::String';
+
   if ($self->{texts}->{$text}) {
     $text = $self->{iconv}->convert($self->{texts}->{$text});
   } else {
diff --git a/SL/Locale/String.pm b/SL/Locale/String.pm
new file mode 100644 (file)
index 0000000..42ceb22
--- /dev/null
@@ -0,0 +1,120 @@
+package SL::Locale::String;
+
+use strict;
+
+use parent qw(Rose::Object Exporter);
+
+use Rose::Object::MakeMethods::Generic (
+  scalar => [ qw(untranslated) ],
+);
+
+our @EXPORT = qw(t8);
+
+use overload '""' => \&translated;
+
+sub translated {
+  my ($self) = @_;
+  return $::locale ? $::locale->text($self->untranslated) : $self->untranslated;
+}
+
+sub t8 {
+  my $string = $_[ ref($_[0]) || ($_[0] eq 'SL::Locale::String') ? 1 : 0 ];
+  return SL::Locale::String->new(untranslated => $string);
+}
+
+1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::Locale::String - Helper class for translating strings at a later
+date (e.g. use at compile time, actual translation during execution
+time)
+
+=head1 SYNOPSIS
+
+  use SL::Locale::String;
+
+  use SL::Controller::Helper::Sorted;
+
+  __PACKAGE__->make_sorted(
+    ...
+    qty => { title => t8("Quantity") },
+ );
+
+=head1 OVERVIEW
+
+Every string that should be translated must be recognized by our
+translation helper script C<script/locales.pl> somehow. It recognizes
+certain function calls as translation instructions and extracts its
+arguments for translation by developers/translators.
+
+This works well for calls that occur during execution time: C<<
+$::locale->text("Untranslated") >>. However, for untranslated strings
+that need to be used at compile time this fails in subtle and not so
+subtle ways. If it happens in a module that is C<use>d directly from
+the dispatcher then C<$::locale> is not defined and such a call would
+end in an error. For modules like controllers that are C<require>d
+during execution time it seems to work, but in FastCGI situations this
+means that the first call determines the language and all subsequent
+calls end up using the same language no matter which language the user
+has chosen.
+
+This class solves the issue by providing a small function called L<t8>
+which can be used instead of C<< $::locale->text() >>. It is
+recognized by C<script/locales.pl>. The untranslated string given to
+L<t8> is stored in an instance of C<SL::Locale::String> and translated
+only when requested either by calling L<translated> or by
+stringification.
+
+Instances of this class can safely be handed over to C<<
+$::locale->text() >> which knows how to handle them (and not to
+re-translate them).
+
+The function L<t8> is exported by default.
+
+=head1 FUNCTIONS
+
+=head2 EXPORTED FUNCTIONS
+
+=over 4
+
+=item C<t8 $untranslated_string>
+
+Returns a new instance of C<SL::Locale::String> and sets its
+L<untranslated> member to C<$untranslated_string>. This function is
+exported and cannot be called as a class or instance method.
+
+=back
+
+=head2 INSTANCE FUNCTIONS
+
+=over 4
+
+=item C<untranslated [$new_untranslated]>
+
+Gets or sets the untranslated string.
+
+=item C<translated>
+
+Returns the translated version of the untranslated string. Translation
+occurs when this function is called and not when the object instance
+is created.
+
+This function is also called during stringification.
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
index 39b520e..c25941c 100644 (file)
@@ -665,7 +665,7 @@ sub sortable_table_header {
   my $by_spec             = $sort_spec->{$by};
   my %current_sort_params = $controller->get_current_sort_params;
   my ($image, $new_dir)   = ('', $current_sort_params{dir});
-  my $title               = delete($params{title}) || $by_spec->{title};
+  my $title               = delete($params{title}) || $::locale->text($by_spec->{title});
 
   if ($current_sort_params{by} eq $by) {
     my $current_dir = $current_sort_params{dir} ? 'up' : 'down';
index a1f1c9b..5740fd0 100644 (file)
@@ -178,6 +178,7 @@ $self->{texts} = {
   '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 exception occurred during execution.' => 'Während der Ausführung trat eine Ausnahme auf.',
   '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.',
@@ -1080,7 +1081,7 @@ $self->{texts} = {
   'Last Transaction'            => 'Letzte Buchung',
   'Last Vendor Number'          => 'Letzte Lieferantennummer',
   'Last command output'         => 'Ausgabe des letzten Befehls',
-  'Last run at'                 => 'Zeitpunkt letzter Ausführung',
+  'Last run at'                 => 'Letzte Ausführung um',
   'Lastcost (with X being a number)' => 'Einkaufspreis (X ist eine fortlaufende Zahl)',
   'Lead'                        => 'Kundenquelle',
   'Leave host and port field empty unless you want to make a remote connection.' => 'F&uuml;r lokale Verbindungen "Rechner" und "Port" freilassen.',
@@ -1169,7 +1170,6 @@ $self->{texts} = {
   'Missing amount'              => 'Fehlbetrag',
   'Missing parameter #1 in call to sub #2.' => 'Fehlernder Parameter \'#1\' in Funktionsaufruf \'#2\'.',
   'Missing parameter (at least one of #1) in call to sub #2.' => 'Fehlernder Parameter (mindestens einer aus \'#1\') in Funktionsaufruf \'#2\'.',
-  'Missing qty'                 => 'Fehlende Stückzahl',
   'Missing taxkeys in invoices with taxes.' => 'Fehlende Steuerschl&uuml;ssel in Rechnungen mit Steuern',
   'Missing user id!'            => 'Benutzer ID fehlt!',
   'Mitarbeiter'                 => 'Mitarbeiter',
@@ -1213,7 +1213,7 @@ $self->{texts} = {
   'New vendor'                  => 'Neuer Lieferant',
   'New window/tab'              => 'Neues Fenster/Tab',
   'Next Dunning Level'          => 'Nächste Mahnstufe',
-  'Next run at'                 => 'Zeitpunkt nächster Ausführung',
+  'Next run at'                 => 'Nächste Ausführung um',
   'No'                          => 'Nein',
   'No %s was found matching the search parameters.' => 'Es wurde kein %s gefunden, auf den die Suchparameter zutreffen.',
   'No Company Address given'    => 'Keine Firmenadresse hinterlegt!',
index 0780a49..f663014 100755 (executable)
@@ -404,7 +404,7 @@ sub scanfile {
           }
         }
 
-        my ($found) = /locale->text.*?\(/;
+        my ($found) = / (?: locale->text | \b t8 ) \b .*? \(/x;
         $postmatch = "$'";
 
         if ($found) {
index 1843c05..79e1523 100644 (file)
@@ -1,4 +1,2 @@
-[% SET report_bottom_url_args = {} %]
-[%     report_bottom_url_args.import(SELF.flat_filter) %]
-[%     report_bottom_url_args.import({action='list', sort_dir=SELF.sort_dir, sort_by=SELF.sort_by}) %]
-[% PROCESS 'common/paginate.html' pages=SELF.pages, base_url=SELF.url_for(report_bottom_url_args) %]
+[% USE L %]
+[%- L.paginate_controls %]
diff --git a/templates/webpages/generic/exception.html b/templates/webpages/generic/exception.html
new file mode 100644 (file)
index 0000000..7c6deb9
--- /dev/null
@@ -0,0 +1,24 @@
+[%- USE LxERP %][% USE HTML %]<body>
+
+ <h1 class="message_error">[%- LxERP.t8('Error!') %]</h1>
+
+ <p>
+  [%- LxERP.t8('An exception occurred during execution.') %]
+ </p>
+
+ <div>
+  <table>
+   <tr>
+    <td valign="top">[%- LxERP.t8('Type') %]:</td>
+    <td valign="top">[%- HTML.escape(error.type) %]</td>
+   </tr>
+
+   <tr>
+    <td valign="top">[%- LxERP.t8('Information') %]:</td>
+    <td valign="top"><pre>[%- HTML.escape(error.info) %]</pre></td>
+   </tr>
+  </table>
+ </div>
+
+</body>
+</html>