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
__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'),
);
#
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
__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'),
);
#
use SL::Request qw(flatten);
use SL::MoreCommon qw(uri_encode);
+use Rose::Object::MakeMethods::Generic
+(
+ scalar => [ qw(action_name) ],
+);
+
#
# public/helper functions
#
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;
$file->close;
}
+sub controller_name {
+ my $class = ref($_[0]) || $_[0];
+ $class =~ s/^SL::Controller:://;
+ return $class;
+}
+
#
# Before/after run hooks
#
$::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;
my $sub = "action_${action}";
if ($self->can($sub)) {
+ $self->action_name($action);
$self->_run_hooks('before', $action);
$self->$sub(@_);
$self->_run_hooks('after', $action);
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};
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>.
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
=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
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;
}
with_objects => [ 'order', 'order.customer', 'part' ],
launder_to => $self->{filter},
),
- sort_by => $self->set_sort_params(%params),
- page => $params{page},
);
$args{query} = [ @{ $args{query} || [] },
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,
};
}
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') ],
);
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';
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) = @_;
=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
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';
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;
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};
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},
return %paginate_params;
}
+sub disable_pagination {
+ my ($self) = @_;
+ _priv($self)->{disabled} = 1;
+}
+
#
# private functions
#
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,
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};
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__
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.
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
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';
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;
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
#
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
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
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,
'INCLUDE_PATH' => '.:templates/webpages',
'COMPILE_EXT' => '.tcc',
'COMPILE_DIR' => $::lx_office_conf{paths}->{userspath} . '/templates-cache',
+ 'ERROR' => 'templates/webpages/generic/exception.html',
})) || die;
}
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 {
--- /dev/null
+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
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';
'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.',
'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ür lokale Verbindungen "Rechner" und "Port" freilassen.',
'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üssel in Rechnungen mit Steuern',
'Missing user id!' => 'Benutzer ID fehlt!',
'Mitarbeiter' => 'Mitarbeiter',
'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!',
}
}
- my ($found) = /locale->text.*?\(/;
+ my ($found) = / (?: locale->text | \b t8 ) \b .*? \(/x;
$postmatch = "$'";
if ($found) {
-[% 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 %]
--- /dev/null
+[%- 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>