'trans_id' => $form->{id},
'variables' => $form,
'always_valid' => 1);
+ CVar->save_custom_variables('dbh' => $dbh,
+ 'module' => 'Contacts',
+ 'trans_id' => $form->{cp_id},
+ 'variables' => $form,
+ 'name_prefix' => 'cp',
+ 'always_valid' => 1);
my $rc = $dbh->commit();
'trans_id' => $form->{id},
'variables' => $form,
'always_valid' => 1);
+ CVar->save_custom_variables('dbh' => $dbh,
+ 'module' => 'Contacts',
+ 'trans_id' => $form->{cp_id},
+ 'variables' => $form,
+ 'name_prefix' => 'cp',
+ 'always_valid' => 1);
my $rc = $dbh->commit();
$main::lxdebug->leave_sub();
}
+sub search_contacts {
+ $::lxdebug->enter_sub;
+
+ my $self = shift;
+ my %params = @_;
+
+ my $dbh = $params{dbh} || $::form->get_standard_dbh;
+ my $vc = $params{db} eq 'customer' ? 'customer' : 'vendor';
+
+ my %sortspecs = (
+ 'cp_name' => 'cp_name, cp_givenname',
+ 'vcname' => 'vcname, cp_name, cp_givenname',
+ 'vcnumber' => 'vcnumber, cp_name, cp_givenname',
+ );
+
+ my %sortcols = map { $_ => 1 } qw(cp_name cp_givenname cp_phone1 cp_phone2 cp_mobile1 cp_email vcname vcnumber);
+
+ my $order_by = $sortcols{$::form->{sort}} ? $::form->{sort} : 'cp_name';
+ $::form->{sort} = $order_by;
+ $order_by = $sortspecs{$order_by} if ($sortspecs{$order_by});
+
+ my $sortdir = $::form->{sortdir} ? 'ASC' : 'DESC';
+ $order_by =~ s/,/ ${sortdir},/g;
+ $order_by .= " $sortdir";
+
+ my @where_tokens = ();
+ my @values;
+
+ if ($params{search_term}) {
+ my @tokens;
+ push @tokens,
+ 'cp.cp_name ILIKE ?',
+ 'cp.cp_givenname ILIKE ?',
+ 'cp.cp_email ILIKE ?';
+ push @values, ('%' . $params{search_term} . '%') x 3;
+
+ if (($params{search_term} =~ m/\d/) && ($params{search_term} !~ m/[^\d \(\)+\-]/)) {
+ my $number = $params{search_term};
+ $number =~ s/[^\d]//g;
+ $number = join '[ /\(\)+\-]*', split(m//, $number);
+
+ push @tokens, map { "($_ ~ '$number')" } qw(cp_phone1 cp_phone2 cp_mobile1 cp_mobile2);
+ }
+
+ push @where_tokens, map { "($_)" } join ' OR ', @tokens;
+ }
+
+ my ($cvar_where, @cvar_values) = CVar->build_filter_query('module' => 'Contacts',
+ 'trans_id_field' => 'cp.cp_id',
+ 'filter' => $params{filter});
+
+ if ($cvar_where) {
+ push @where_tokens, $cvar_where;
+ push @values, @cvar_values;
+ }
+
+ if (my $filter = $params{filter}) {
+ for (qw(name title givenname email project abteilung)) {
+ next unless $filter->{"cp_$_"};
+ add_token(\@where_tokens, \@values, col => "cp.cp_$_", val => $filter->{"cp_$_"}, method => 'ILIKE', esc => 'substr');
+ }
+
+ push @where_tokens, 'cp.cp_cv_id IS NOT NULL' if $filter->{status} eq 'active';
+ push @where_tokens, 'cp.cp_cv_id IS NULL' if $filter->{status} eq 'orphaned';
+ }
+
+ my $where = @where_tokens ? 'WHERE ' . join ' AND ', @where_tokens : '';
+
+ my $query = qq|SELECT cp.*,
+ COALESCE(c.id, v.id) AS vcid,
+ COALESCE(c.name, v.name) AS vcname,
+ COALESCE(c.customernumber, v.vendornumber) AS vcnumber,
+ CASE WHEN c.name IS NULL THEN 'vendor' ELSE 'customer' END AS db
+ FROM contacts cp
+ LEFT JOIN customer c ON (cp.cp_cv_id = c.id)
+ LEFT JOIN vendor v ON (cp.cp_cv_id = v.id)
+ $where
+ ORDER BY $order_by|;
+
+ my $contacts = selectall_hashref_query($::form, $dbh, $query, @values);
+
+ $::lxdebug->leave_sub;
+
+ return @{ $contacts };
+}
+
+
1;
use Data::Dumper;
use SL::DBUtils;
-use SL::MoreCommon qw(listify);
+use SL::MoreCommon qw(any listify);
sub get_configs {
$main::lxdebug->enter_sub();
: $cvar->{type} eq 'timestamp' ? $act_var->{timestamp_value}
: $cvar->{type} eq 'number' ? $act_var->{number_value}
: $cvar->{type} eq 'customer' ? $act_var->{number_value}
+ : $cvar->{type} eq 'vendor' ? $act_var->{number_value}
+ : $cvar->{type} eq 'part' ? $act_var->{number_value}
: $cvar->{type} eq 'bool' ? $act_var->{bool_value}
: $act_var->{text_value};
$cvar->{valid} = $valid;
} elsif ($cvar->{type} eq 'customer') {
require SL::DB::Customer;
$cvar->{value} = SL::DB::Manager::Customer->find_by(id => $cvar->{value} * 1);
+ } elsif ($cvar->{type} eq 'vendor') {
+ require SL::DB::Vendor;
+ $cvar->{value} = SL::DB::Manager::Vendor->find_by(id => $cvar->{value} * 1);
+ } elsif ($cvar->{type} eq 'part') {
+ require SL::DB::Part;
+ $cvar->{value} = SL::DB::Manager::Part->find_by(id => $cvar->{value} * 1);
}
}
} elsif ($config->{type} eq 'bool') {
push @values, $value ? 't' : 'f', undef, undef, undef;
- } elsif ($config->{type} eq 'customer') {
+ } elsif (any { $config->{type} eq $_ } qw(customer vendor part)) {
push @values, undef, undef, undef, $value * 1;
}
$params{include_prefix} = 'l_' unless defined($params{include_prefix});
$params{include_value} ||= '1';
+ $params{filter_prefix} ||= '';
my $filter = $form->parse_html_template('amcvar/search_filter', \%params);
my $include = $form->parse_html_template('amcvar/search_include', \%params);
$not = 'NOT' if ($params{filter}->{$name} eq 'no');
push @sub_where, qq|COALESCE(cvar.bool_value, false) = TRUE|;
- } elsif ($config->{type} eq 'customer') {
+ } elsif (any { $config->{type} eq $_ } qw(customer vendor part)) {
next unless $params{filter}->{$name};
- push @sub_where, qq|cvar.number_value * 1 IN (SELECT id FROM customer WHERE name ILIKE ?)|;
+ my $table = $config->{type};
+ push @sub_where, qq|cvar.number_value * 1 IN (SELECT id FROM $table WHERE name ILIKE ?)|;
+ push @sub_values, "%$params{filter}->{$name}%";
+ } elsif ($config->{type} eq 'part') {
+ next unless $params{filter}->{$name};
+
+ push @sub_where, qq|cvar.number_value * 1 IN (SELECT id FROM parts WHERE partnumber ILIKE ?)|;
push @sub_values, "%$params{filter}->{$name}%";
}
: $cfg->{type} eq 'timestamp' ? $ref->{timestamp_value}
: $cfg->{type} eq 'number' ? $form->format_amount($myconfig, $ref->{number_value} * 1, $cfg->{precision})
: $cfg->{type} eq 'customer' ? (SL::DB::Manager::Customer->find_by(id => 1*$ref->{number_value}) || SL::DB::Customer->new)->name
+ : $cfg->{type} eq 'vendor' ? (SL::DB::Manager::Vendor->find_by(id => 1*$ref->{number_value}) || SL::DB::Vendor->new)->name
+ : $cfg->{type} eq 'part' ? (SL::DB::Manager::Part->find_by(id => 1*$ref->{number_value}) || SL::DB::Part->new)->partnumber
: $cfg->{type} eq 'bool' ? ($ref->{bool_value} ? $locale->text('Yes') : $locale->text('No'))
: $ref->{text_value};
}
use Carp;
use IO::File;
use List::Util qw(first);
+use SL::Request qw(flatten);
+use SL::MoreCommon qw(uri_encode);
#
# public/helper functions
my $controller = delete($params{controller}) || $self->_controller_name;
my $action = delete($params{action}) || 'dispatch';
$params{action} = "${controller}/${action}";
- my $query = join('&', map { $::form->escape($_) . '=' . $::form->escape($params{$_}) } keys %params);
+ my $query = join '&', map { uri_encode($_->[0]) . '=' . uri_encode($_->[1]) } @{ flatten(\%params) };
return "controller.pl?${query}";
}
--- /dev/null
+package SL::Controller::DeliveryPlan;
+
+use strict;
+use parent qw(SL::Controller::Base);
+
+use Clone qw(clone);
+use SL::DB::OrderItem;
+use SL::Controller::Helper::ParseFilter;
+use SL::Controller::Helper::ReportGenerator;
+
+__PACKAGE__->run_before(sub { $::auth->assert('sales_order_edit'); });
+
+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->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->{orderitems} = SL::DB::Manager::OrderItem->get_all(%$db_args);
+
+ $self->list_objects;
+}
+
+# private functions
+
+sub setup_for_list {
+ my ($self, %params) = @_;
+ $self->{filter} = {};
+ my %args = (
+ parse_filter(
+ $self->_pre_parse_filter($::form->{filter}, $self->{filter}),
+ with_objects => [ 'order', 'order.customer', 'part' ],
+ launder_to => $self->{filter},
+ ),
+ sort_by => $self->set_sort_params(%params),
+ page => $params{page},
+ );
+
+ $args{query} = [ @{ $args{query} || [] },
+ (
+ 'order.customer_id' => { gt => 0 },
+ 'order.closed' => 0,
+ or => [ 'order.quotation' => 0, 'order.quotation' => undef ],
+
+ # filter by shipped_qty < qty, read from innermost to outermost
+ 'id' => [ \"
+ -- 3. resolve the desired information about those
+ SELECT oi.id FROM (
+ -- 2. slice only part, orderitem and both quantities from it
+ SELECT parts_id, trans_id, qty, SUM(doi_qty) AS doi_qty FROM (
+ -- 1. join orderitems and deliverorder items via record_links.
+ -- also add customer data to filter for sales_orders
+ SELECT oi.parts_id, oi.trans_id, oi.id, oi.qty, doi.qty AS doi_qty
+ FROM orderitems oi, oe, record_links rl, delivery_order_items doi
+ WHERE
+ oe.id = oi.trans_id AND
+ oe.customer_id IS NOT NULL AND
+ (oe.quotation = 'f' OR oe.quotation IS NULL) AND
+ NOT oe.closed AND
+ rl.from_id = oe.id AND
+ rl.from_id = oi.trans_id AND
+ oe.id = oi.trans_id AND
+ rl.from_table = 'oe' AND
+ rl.to_table = 'delivery_orders' AND
+ rl.to_id = doi.delivery_order_id AND
+ oi.parts_id = doi.parts_id
+ ) tuples GROUP BY parts_id, trans_id, qty
+ ) partials
+ LEFT JOIN orderitems oi ON partials.parts_id = oi.parts_id AND partials.trans_id = oi.trans_id
+ WHERE oi.qty > doi_qty
+
+ UNION ALL
+
+ -- 4. since the join over record_links fails for sales_orders wihtout any delivery order
+ -- retrieve those without record_links at all
+ SELECT oi.id FROM orderitems oi, oe
+ WHERE
+ oe.id = oi.trans_id AND
+ oe.customer_id IS NOT NULL AND
+ (oe.quotation = 'f' OR oe.quotation IS NULL) AND
+ NOT oe.closed AND
+ oi.trans_id NOT IN (
+ SELECT from_id
+ FROM record_links rl
+ WHERE
+ rl.from_table ='oe' AND
+ rl.to_table = 'delivery_orders'
+ )
+ " ],
+ )
+ ];
+
+ 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 $objects = $params{objects} || [];
+ 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 %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) }},
+ );
+
+
+ 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'),
+ );
+ $report->set_options_from_form;
+
+ SL::DB::Manager::OrderItem->disable_paginating(args => $params{db_args}) if $report->{options}{output_format} =~ /^(pdf|csv)$/i;
+
+ $self->{report_data} = {
+ column_defs => \%column_defs,
+ columns => \@columns,
+ visible => \@visible,
+ sortable => \@sortable,
+ };
+}
+
+sub list_objects {
+ my ($self) = @_;
+ my $column_defs = $self->{report_data}{column_defs};
+ for my $obj (@{ $self->{orderitems} || [] }) {
+ $self->{report}->add_data({
+ map {
+ $_ => {
+ data => $column_defs->{$_}{sub} ? $column_defs->{$_}{sub}->($obj)
+ : $obj->can($_) ? $obj->$_
+ : $obj->{$_},
+ link => $column_defs->{$_}{obj_link} ? $column_defs->{$_}{obj_link}->($obj) : '',
+ },
+ } @{ $self->{report_data}{columns} || {} }
+ });
+ }
+
+ return $self->{report}->generate_with_headers;
+}
+
+sub make_filter_summary {
+ my ($self) = @_;
+
+ my $filter = $::form->{filter} || {};
+ 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') ],
+ );
+
+ my @flags = (
+ [ $filter->{part}{type}{part}, $::locale->text('Parts') ],
+ [ $filter->{part}{type}{service}, $::locale->text('Services') ],
+ [ $filter->{part}{type}{assembly}, $::locale->text('Assemblies') ],
+ );
+
+ for (@flags) {
+ push @filter_strings, "$_->[1]" if $_->[0];
+ }
+ for (@filters) {
+ push @filter_strings, "$_->[1]: $_->[0]" if $_->[0];
+ }
+
+ $self->{filter_summary} = join ', ', @filter_strings;
+}
+
+sub link_to {
+ my ($self, $object, %params) = @_;
+
+ return unless $object;
+ my $action = $params{action} || 'edit';
+
+ if ($object->isa('SL::DB::Order')) {
+ my $type = $object->type;
+ my $vc = $object->is_sales ? 'customer' : 'vendor';
+ my $id = $object->id;
+
+ return "oe.pl?action=$action&type=$type&vc=$vc&id=$id";
+ }
+ if ($object->isa('SL::DB::Part')) {
+ my $id = $object->id;
+ return "ic.pl?action=$action&id=$id";
+ }
+ if ($object->isa('SL::DB::Customer')) {
+ my $id = $object->id;
+ return "ct.pl?action=$action&id=$id&db=customer";
+ }
+}
+
+# unfortunately ParseFilter can't handle compount filters.
+# so we clone the original filter (still need that for serializing)
+# rip out the options we know an replace them with the compound options.
+# ParseFilter will take care of the prefixing then.
+sub _pre_parse_filter {
+ my ($self, $orig_filter, $launder_to) = @_;
+
+ return undef unless $orig_filter;
+
+ my $filter = clone($orig_filter);
+ if ($filter->{part} && $filter->{part}{type}) {
+ $launder_to->{part}{type} = delete $filter->{part}{type};
+ my @part_filters = grep $_, map {
+ $launder_to->{part}{type}{$_} ? SL::DB::Manager::Part->type_filter($_) : ()
+ } qw(part service assembly);
+
+ push @{ $filter->{and} }, or => [ @part_filters ] if @part_filters;
+ }
+
+ for my $op (qw(le ge)) {
+ if ($filter->{"reqdate:date::$op"}) {
+ $launder_to->{"reqdate_date__$op"} = delete $filter->{"reqdate:date::$op"};
+ my $parsed_date = DateTime->from_lxoffice($launder_to->{"reqdate_date__$op"});
+ push @{ $filter->{and} }, or => [
+ 'reqdate' => { $op => $parsed_date },
+ and => [
+ 'reqdate' => undef,
+ 'order.reqdate' => { $op => $parsed_date },
+ ]
+ ] if $parsed_date;
+ }
+ }
+
+ if (my $style = delete $filter->{searchstyle}) {
+ $self->{searchstyle} = $style;
+ $launder_to->{searchstyle} = $style;
+ }
+
+ return $filter;
+}
+
+1;
);
my %methods = (
- lt => sub { +{ lt => $_[0] } },
- gt => sub { +{ gt => $_[0] } },
- ilike => sub { +{ ilike => $_[0] } },
- like => sub { +{ like => $_[0] } },
enable => sub { ;;;; },
+ map {
+ # since $_ is an alias it can't be used in a closure. even "".$_ or "$_"
+ # does not work, we need a real copy.
+ my $_copy = "$_";
+ $_ => sub { +{ $_copy => $_[0] } },
+ } qw(similar match imatch regex regexp like ilike rlike is is_not ne eq lt gt le ge),
);
sub parse_filter {
--- /dev/null
+package SL::Controller::SellPriceInformation;
+
+use strict;
+use parent qw(SL::Controller::Base);
+
+use Clone qw(clone);
+use SL::DB::OrderItem;
+use SL::Controller::Helper::ParseFilter;
+use SL::Controller::Helper::ReportGenerator;
+
+sub action_list {
+ my ($self) = @_;
+ $self->{action} = 'list';
+
+ 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, per_page => 10);
+
+ my $bottom = $::form->parse_html_template('price_information/report_bottom', { SELF => $self });
+
+ $self->prepare_report(
+ report_generator_options => {
+ raw_bottom_info_text => $bottom,
+ controller_class => 'SellPriceInformation',
+ },
+ db_args => $db_args,
+ );
+
+ $self->{orderitems} = SL::DB::Manager::OrderItem->get_all(%$db_args);
+
+ $self->list_objects;
+}
+
+# private functions
+
+sub setup_for_list {
+ my ($self, %params) = @_;
+ $self->{filter} = _pre_parse_filter($params{filter});
+
+ my %args = (
+ parse_filter($self->{filter},
+ with_objects => [ 'order', 'order.customer', 'part' ],
+ ),
+ sort_by => $self->set_sort_params(%params),
+ page => $params{page},
+ );
+
+ 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 column_defs {
+ my ($self) = @_;
+ return {
+ transdate => { text => $::locale->text('Date'),
+ sub => sub { $_[0]->order->transdate_as_date }},
+ ordnumber => { text => $::locale->text('Number'),
+ sub => sub { $_[0]->order->number },
+ 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) }},
+ customer => { text => $::locale->text('Customer'),
+ sub => sub { $_[0]->order->customer->name },
+ obj_link => sub { $self->link_to($_[0]->order->customer) }},
+ ship => { text => $::locale->text('Delivered'),
+ sub => sub { $::form->format_amount(\%::myconfig, $_[0]->shipped_qty) . ' ' . $_[0]->unit }},
+ qty => { text => $::locale->text('Qty'),
+ sub => sub { $_[0]->qty_as_number . ' ' . $_[0]->unit }},
+ sellprice => { text => $::locale->text('Sell Price'),
+ sub => sub { $_[0]->sellprice_as_number }},
+ discount => { text => $::locale->text('Discount'),
+ sub => sub { $_[0]->discount_as_percent . "%" }},
+ amount => { text => $::locale->text('Amount'),
+ sub => sub { $::form->format_amount(\%::myconfig, $_[0]->qty * $_[0]->sellprice * (1 - $_[0]->discount), 2) }},
+ };
+}
+
+sub prepare_report {
+ my ($self, %params) = @_;
+
+ my $objects = $params{objects} || [];
+ my $report = SL::ReportGenerator->new(\%::myconfig, $::form);
+ $self->{report} = $report;
+
+ my @columns = qw(transdate ordnumber customer ship qty sellprice discount amount);
+ my @visible = qw(transdate ordnumber customer ship qty sellprice discount amount);
+ my @sortable = qw(transdate ordnumber customer sellprice discount );
+
+ my $column_defs = $self->column_defs;
+
+ for my $col (@sortable) {
+ $column_defs->{$col}{link} = $self->self_url(
+ sort_by => $col,
+ sort_dir => ($self->{sort_by} eq $col ? 1 - $self->{sort_dir} : $self->{sort_dir}),
+ page => $self->{pages}{cur},
+ );
+ }
+
+ map { $column_defs->{$_}{visible} = 1 } @visible;
+
+ $report->set_columns(%$column_defs);
+ $report->set_column_order(@columns);
+ $report->set_options(allow_pdf_export => 0, allow_csv_export => 0);
+ $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 => $self->displayable_filter($::form->{filter}),
+ title => $::locale->text('Sales Price information'),
+ );
+ $report->set_options_from_form;
+
+ $self->{report_data} = {
+ column_defs => $column_defs,
+ columns => \@columns,
+ visible => \@visible,
+ sortable => \@sortable,
+ };
+}
+
+sub list_objects {
+ my ($self) = @_;
+ my $column_defs = $self->{report_data}{column_defs};
+ for my $obj (@{ $self->{orderitems} || [] }) {
+ $self->{report}->add_data({
+ map {
+ $_ => {
+ data => $column_defs->{$_}{sub} ? $column_defs->{$_}{sub}->($obj)
+ : $obj->can($_) ? $obj->$_
+ : $obj->{$_},
+ link => $column_defs->{$_}{obj_link} ? $column_defs->{$_}{obj_link}->($obj) : '',
+ },
+ } @{ $self->{report_data}{columns} || {} }
+ });
+ }
+
+ return $self->{report}->generate_with_headers;
+}
+
+sub link_to {
+ my ($self, $object, %params) = @_;
+
+ return unless $object;
+ my $action = $params{action} || 'edit';
+
+ if ($object->isa('SL::DB::Order')) {
+ my $type = $object->type;
+ my $vc = $object->is_sales ? 'customer' : 'vendor';
+ my $id = $object->id;
+
+ return "oe.pl?action=$action&type=$type&vc=$vc&id=$id";
+ }
+ if ($object->isa('SL::DB::Customer')) {
+ my $id = $object->id;
+ return "ct.pl?action=$action&id=$id&db=customer";
+ }
+}
+
+sub _pre_parse_filter {
+ my $filter = clone(shift);
+
+ if ( exists $filter->{order}
+ && exists $filter->{order}{type}) {
+ push @{ $filter->{and} }, SL::DB::Manager::Order->type_filter(delete $filter->{order}{type}, "order."),
+ }
+
+ return $filter;
+}
+
+sub displayable_filter {
+ my ($self, $filter) = @_;
+
+ my $column_defs = $self->column_defs;
+ my @texts;
+
+ push @texts, [ $::locale->text('Type'), $::locale->text($filter->{order}{type}) ] if $filter->{order}{type};
+ push @texts, [ $::locale->text('Sort By'), $column_defs->{$self->{sort_by}}{text} ] if $column_defs->{$self->{sort_by}}{text};
+ push @texts, [ $::locale->text('Page'), $::locale->text($self->{pages}{cur}) ] if $self->{pages}{cur} != 1;
+
+ return join '; ', map { "$_->[0]: $_->[1]" } @texts;
+}
+
+sub self_url {
+ my ($self, %params) = @_;
+ %params = (
+ action => $self->{action},
+ sort_by => $self->{sort_by},
+ sort_dir => $self->{sort_dir},
+ page => $self->{pages}{cur},
+ filter => $::form->{filter},
+ %params,
+ );
+
+ return $self->url_for(%params);
+}
+
+1;
__PACKAGE__->make_manager_methods;
sub type_filter {
- my $class = shift;
- my $type = lc(shift || '');
-
- return (and => [ '!customer_id' => undef, quotation => 1 ]) if $type eq 'sales_quotation';
- return (and => [ '!vendor_id' => undef, quotation => 1 ]) if $type eq 'request_quotation';
- return (and => [ '!customer_id' => undef, or => [ quotation => 0, quotation => undef ] ]) if $type eq 'sales_order';
- return (and => [ '!vendor_id' => undef, or => [ quotation => 0, quotation => undef ] ]) if $type eq 'purchase_order';
+ my $class = shift;
+ my $type = lc(shift || '');
+ my $prefix = shift || '';
+
+ return (and => [ "!${prefix}customer_id" => undef, "${prefix}quotation" => 1 ]) if $type eq 'sales_quotation';
+ return (and => [ "!${prefix}vendor_id" => undef, "${prefix}quotation" => 1 ]) if $type eq 'request_quotation';
+ return (and => [ "!${prefix}customer_id" => undef, or => [ "${prefix}quotation" => 0, "${prefix}quotation" => undef ] ]) if $type eq 'sales_order';
+ return (and => [ "!${prefix}vendor_id" => undef, or => [ "${prefix}quotation" => 0, "${prefix}quotation" => undef ] ]) if $type eq 'purchase_order';
die "Unknown type $type";
}
+++ /dev/null
-# This file has been auto-generated. Do not modify it; it will be overwritten
-# by rose_auto_create_model.pl automatically.
-package SL::DB::Taxkey;
-
-use strict;
-
-use base qw(SL::DB::Object);
-
-__PACKAGE__->meta->setup(
- table => 'taxkeys',
-
- columns => [
- id => { type => 'integer', not_null => 1, sequence => 'id' },
- chart_id => { type => 'integer' },
- tax_id => { type => 'integer' },
- taxkey_id => { type => 'integer' },
- pos_ustva => { type => 'integer' },
- startdate => { type => 'date' },
- ],
-
- primary_key_columns => [ 'id' ],
-
- foreign_keys => [
- tax => {
- class => 'SL::DB::Tax',
- key_columns => { tax_id => 'id' },
- },
- ],
-);
-
-1;
-;
return $invoice;
}
+sub number {
+ my $self = shift;
+
+ my %number_method = (
+ sales_order => 'ordnumber',
+ sales_quotation => 'quonumber',
+ puchase_order => 'ordnumber',
+ request_quotation => 'quonumber',
+ );
+
+ return $self->${ \ $number_method{$self->type} }(@_);
+}
+
1;
__END__
use strict;
+use List::Util qw(sum);
+use SL::AM;
+
use SL::DB::MetaSetup::OrderItem;
use SL::DB::Helper::CustomVariables (
sub_module => 'orderitems',
return $self->origprice > $self->part->sellprice;
}
+sub shipped_qty {
+ my ($self) = @_;
+
+ my $d_orders = $self->order->linked_records(direction => 'to', to => 'SL::DB::DeliveryOrder');
+ my @doi = grep { $_->parts_id == $self->parts_id } map { $_->orderitems } @$d_orders;
+
+ return sum(map { AM->convert_unit($_->unit => $self->unit) * $_->qty } @doi);
+}
+
package SL::DB::Manager::OrderItem;
use SL::DB::Helper::Paginated;
ordnumber => [ 'order.ordnumber' ],
customer => [ 'lower(customer.name)', ],
position => [ 'trans_id', 'runningnumber' ],
- transdate => [ 'transdate', 'lower(order.reqdate::text)' ],
+ reqdate => [ 'COALESCE(orderitems.reqdate, order.reqdate)' ],
+ orddate => [ 'order.orddate' ],
+ sellprice => [ 'sellprice' ],
+ discount => [ 'discount' ],
+ transdate => [ 'transdate::date', 'order.reqdate' ],
},
default => [ 'position', 1 ],
nulls => { }
return (defined($value) && "$value" ne "") ? $value * 1 : $default;
}
+# boolean escape
+sub conv_b {
+ my ($value, $default) = @_;
+ return !defined $value && defined $default ? $default
+ : $value ? 't'
+ : 'f';
+}
+
sub conv_date {
my ($value) = @_;
return (defined($value) && "$value" ne "") ? $value : undef;
my %params = @_;
my $col = $params{col};
my $val = $params{val};
- my $method = $params{method} || '=';
my $escape = $params{esc} || sub { $_ };
+ my $method = $params{esc} =~ /^start|end|substr$/ ? 'ILIKE' : $params{method} || '=';
$val = [ $val ] unless ref $val eq 'ARRAY';
my %escapes = (
id => \&conv_i,
+ bool => \&conv_b,
date => \&conv_date,
+ start => sub { $_[0] . '%' },
+ end => sub { '%' . $_[0] },
+ substr => sub { '%' . $_[0] . '%' },
);
+ my $_long_token = sub {
+ my $op = shift;
+ sub {
+ my $col = shift;
+ return scalar @_ ? join ' OR ', ("$col $op ?") x scalar @_,
+ : undef;
+ }
+ };
+
my %methods = (
'=' => sub {
my $col = shift;
: scalar @_ == 1 ? sprintf '%s = ?', $col
: undef;
},
+ map({ $_ => $_long_token->($_) } qw(LIKE ILIKE >= <= > <)),
);
$method = $methods{$method} || $method;
@required_modules = (
{ name => "parent", url => "http://search.cpan.org/~corion/", debian => 'libparent-perl' },
{ name => "Archive::Zip", version => '1.16', url => "http://search.cpan.org/~adamk/", debian => 'libarchive-zip-perl' },
+ { name => "Clone", url => "http://search.cpan.org/~rdf/", debian => 'libclone-perl' },
{ name => "Config::Std", url => "http://search.cpan.org/~dconway/", debian => 'libconfig-std-perl' },
{ name => "DateTime", url => "http://search.cpan.org/~drolsky/", debian => 'libdatetime-perl' },
{ name => "DBI", version => '1.50', url => "http://search.cpan.org/~timb/", debian => 'libdbi-perl' },
@optional_modules = (
{ name => "Digest::SHA", url => "http://search.cpan.org/~mshelor/", debian => 'libdigest-sha-perl' },
+ { name => "IO::Socket::SSL", url => "http://search.cpan.org/~sullr/", debian => 'libio-socket-ssl-perl' },
+ { name => "Net::LDAP", url => "http://search.cpan.org/~gbarr/", debian => 'libnet-ldap-perl' },
);
@developer_modules = (
sub check_for_conditional_dependencies {
return if $conditional_dependencies{net_ldap}++;
- push @required_modules, { 'name' => 'Net::LDAP', 'url' => 'http://search.cpan.org/~gbarr/' }
+ push @required_modules, grep { $_->{name} eq 'Net::LDAP' } @optional_modules
if $::lx_office_conf{authentication} && ($::lx_office_conf{authentication}->{module} eq 'LDAP');
}
use strict;
-use Config::Std;
-use Encode;
-
my $environment_initialized;
+sub safe_require {
+ my ($class, $may_fail);
+ my $failed;
+ $failed = !eval {
+ require Config::Std;
+ require Encode;
+ };
+
+ if ($failed) {
+ if ($may_fail) {
+ warn $@;
+ return 0;
+ } else {
+ die $@;
+ }
+ }
+
+ Config::Std->import;
+ Encode->import;
+
+ return 1;
+}
+
sub read {
- my ($class, $file_name) = @_;
+ my ($class, $file_name, $may_fail) = @_;
- read_config 'config/lx_office.conf.default' => %::lx_office_conf;
+ return unless $class->safe_require($may_fail);
+
+ read_config('config/lx_office.conf.default' => \%::lx_office_conf);
_decode_recursively(\%::lx_office_conf);
$file_name ||= 'config/lx_office.conf';
if (-f $file_name) {
- read_config $file_name => my %local_conf;
+ read_config($file_name => \ my %local_conf);
_decode_recursively(\%local_conf);
_flat_merge(\%::lx_office_conf, \%local_conf);
}
_init_environment();
_determine_application_paths();
+
+ return 1;
}
sub _decode_recursively {
use SL::MoreCommon qw(uri_encode uri_decode);
use List::Util qw(first max min sum);
use List::MoreUtils qw(all any apply);
+use Exporter qw(import);
-sub _store_value {
- $::lxdebug->enter_sub(2);
+our @EXPORT_OK = qw(flatten unflatten read_cgi_input);
+sub _store_value {
my ($target, $key, $value) = @_;
- my @tokens = split /((?:\[\+?\])?(?:\.|$))/, $key;
+ my @tokens = split /((?:\[\+?\])?(?:\.)|(?:\[\+?\]))/, $key;
my $curr;
if (scalar @tokens) {
my $sep = shift @tokens;
my $key = shift @tokens;
- $curr = \ $$curr->[++$#$$curr], next if $sep eq '[]';
+ $curr = \ $$curr->[$#$$curr], next if $sep eq '[]' && @tokens;
+ $curr = \ $$curr->[++$#$$curr], next if $sep eq '[]' && !@tokens;
+ $curr = \ $$curr->[++$#$$curr], next if $sep eq '[+]';
$curr = \ $$curr->[max 0, $#$$curr] if $sep eq '[].';
$curr = \ $$curr->[++$#$$curr] if $sep eq '[+].';
$curr = \ $$curr->{$key}
$$curr = $value;
- $::lxdebug->leave_sub(2);
-
return $curr;
}
return $target;
}
+sub flatten {
+ my ($source, $target, $prefix, $in_array) = @_;
+ $target ||= [];
+
+ # there are two edge cases that need attention. first: more than one hash
+ # inside an array. only the first of each nested can have a [+]. second: if
+ # an array contains mixed values _store_value will rely on autovivification.
+ # so any type change must have a [+]
+ # this closure decides one recursion step AFTER an array has been found if a
+ # [+] needs to be generated
+ my $arr_prefix = sub {
+ return $_[0] ? '[+]' : '[]' if $in_array;
+ return '';
+ };
+
+ for (ref $source) {
+ /^HASH$/ && do {
+ my $first = 1;
+ for my $key (keys %$source) {
+ flatten($source->{$key} => $target, (defined $prefix ? $prefix . $arr_prefix->($first) . '.' : '') . $key);
+ $first = 0;
+ };
+ next;
+ };
+ /^ARRAY$/ && do {
+ for my $i (0 .. $#$source) {
+ flatten($source->[$i] => $target, $prefix . $arr_prefix->($i == 0), '1');
+ }
+ next;
+ };
+ !$_ && do {
+ die "can't flatten a pure scalar" unless defined $prefix;
+ push @$target, [ $prefix . $arr_prefix->(0) => $source ];
+ next;
+ };
+ die "unrecognized reference of a data structure $_. cannot serialize refs, globs and code yet. to serialize Form please use the method there";
+ }
+
+ return $target;
+}
+
+
+sub unflatten {
+ my ($data, $target) = @_;
+ $target ||= {};
+
+ for my $pair (@$data) {
+ _store_value($target, @$pair) if defined $pair->[0];
+ }
+
+ return $target;
+}
+
1;
__END__
=head1 NAME
-SL::Form.pm - main data object.
+SL::Request.pm - request parsing and data serialization
=head1 SYNOPSIS
-This module handles unpacking of cgi parameters. usually you donÄt want to call
-anything in here directly,
+This module handles unpacking of cgi parameters. usually you don't want to call
+anything in here directly.
+
+ use SL::Request qw(read_cgi_input);
+
+ # read cgi input depending on request type, unflatten and recode
+ read_cgi_input($target_hash_ref);
+
+ # $hashref and $new_hashref should be identical
+ my $new_arrayref = flatten($hashref);
+ my $new_hashref = unflatten($new_arrayref);
+
+
+=head1 DESCRIPTION
+
+This module handles flattening and unflattening of data for request
+roundtrip purposes. Lx-Office uses the format as described below:
+
+=over 4
+
+=item Hashes
+
+Hash entries will be connected with a dot (C<.>). A simple hash like this
+
+ order => {
+ item => 2,
+ customer => 5
+ }
+
+will be serialized to
+
+ [ order.item => 2 ],
+ [ order.customer => 5 ],
+
+=item Arrays
+
+Arrays will by trailing empty brackets (C<[]>). An hash like this
+
+ selected_id => [ 2, 6, 8, 9 ]
+
+will be flattened to
+
+ [ selected_id[] => 2 ],
+ [ selected_id[] => 6 ],
+ [ selected_id[] => 8 ],
+ [ selected_id[] => 9 ],
+
+Since this will produce identical keys, the resulting flattened list can not be
+used as a hash. It is however very easy to use this in a template to generate
+input:
+
+ [% FOREACH id = selected_ids %]
+ <input type="hidden" name="selected_id[]" value="[% id | html %]">
+ [% END %]
+
+=item Nested structures
+
+A special version of this are nested hashs in an array, which is very common.
+The combined operator (C<[].>) will be used. As a special case, every time a new
+array slice is started, the special convention (C<[+].>) will be used. Again this
+is because it's easy to write a template with it.
+
+So this
+
+ order => {
+ orderitems => [
+ {
+ id => 1,
+ part => 15
+ },
+ {
+ id => 2,
+ part => 7
+ },
+ ]
+ }
+
+will be
+
+ [ order.orderitems[+].id => 1 ],
+ [ order.orderitems[].part => 15 ],
+ [ order.orderitems[+].id => 2 ],
+ [ order.orderitems[].part => 7 ],
+
+=item Limitations
+
+ The format currently does have certain limitations when compared to other
+ serialization formats.
+
+=over 4
+
+=item Order
+
+The order of serialized values matters to reconstruct arrays properly. This
+should rarely be a problem if you just flatten and dump into a url or a field
+of hiddens.
+
+=item Empty Keys
+
+The current implementation of flatten does produce correct serialization of
+empty keys, but unflatten is unable to resolve these. Do no use C<''> or
+C<undef> as keys. C<0> is fine.
+
+=item Key Escaping
+
+You cannot use the tokens C<[]>, C<[+]> and C<.> in keys. No way around it.
+
+=item Sparse Arrays
+
+It is not possible to serialize somehing like
+
+ sparse_array => do { my $sa = []; $sa[100] = 1; $sa },
+
+This is a feature, as perl doesn't do well with very large arrays.
+
+=item Recursion
+
+There is currently no support nor prevention for flattening a circular structure.
+
+=item Custom Delimiter
+
+No support for other delimiters, sorry.
+
+=item Other References
+
+No support for globs, scalar refs, code refs, filehandles and the like. These will die.
+
+=back
+
+=back
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<flatten HASHREF [ ARRAYREF ]>
+
+This function will flatten the provided hash ref into the provided array ref.
+The array ref may be non empty, but will be changed in this case.
+
+Return value is the flattened array ref.
+
+=item C<unflatten ARRAYREF [ HASHREF ]>
+
+This function will parse the array ref, and will store the contents into the hash ref. The hash ref may be non empty, in this case any new keys will override the old ones only on leafs with same type. Type changes on a node will die.
- SL::Request::read_cgi_input($target_hash_ref);
+=back
=head1 SPECIAL FUNCTIONS
parses a complex var name, and stores it in the form.
syntax:
- $form->_store_value($key, $value);
+ _store_value($target, $key, $value);
keys must start with a string, and can contain various tokens.
supported key structures are:
JS
}
+# simple version with select_tag
+sub vendor_selector {
+ my ($self, $name, $value, %params) = @_;
+
+ my $actual_vendor_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"}) ? $::form->{"$name"}->id : $::form->{"$name"}) :
+ (ref $value && $value->can('id')) ? $value->id : '';
+ my $options_str = $self->options_for_select(SL::DB::Manager::Vendor->get_all(),
+ default => $actual_vendor_id,
+ title_sub => sub { $_[0]->vendornumber . " : " . $_[0]->name },
+ 'with_empty' => 1);
+
+ return $self->select_tag($name, $options_str, %params);
+}
+
+
+# simple version with select_tag
+sub part_selector {
+ my ($self, $name, $value, %params) = @_;
+
+ my $actual_part_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"})? $::form->{"$name"}->id : $::form->{"$name"}) :
+ (ref $value && $value->can('id')) ? $value->id : '';
+ my $options_str = $self->options_for_select(SL::DB::Manager::Part->get_all(),
+ default => $actual_part_id,
+ title_sub => sub { $_[0]->partnumber . " : " . $_[0]->description },
+ 'with_empty' => 1);
+
+ return $self->select_tag($name, $options_str, %params);
+}
+
+
sub javascript_tag {
my $self = shift;
my $code = '';
'bool' => $locale->text('Yes/No (Checkbox)'),
'select' => $locale->text('Selection'),
'customer' => $locale->text('Customer'),
+ 'vendor' => $locale->text('Vendor'),
+ 'part' => $locale->text('Part'),
);
-our @types = qw(text textfield number date bool select customer); # timestamp
+our @types = qw(text textfield number date bool select customer vendor part); # timestamp
our @modules = ({ module => 'CT', description => $locale->text('Customers and vendors') },
+ { module => 'Contacts', description => $locale->text('Contact persons') },
{ module => 'IC', description => $locale->text('Parts, services and assemblies') },
{ module => 'Projects', description => $locale->text('Projects') },
);
use SL::CT;
use SL::CVar;
+use SL::Request qw(flatten);
use SL::DB::Business;
use SL::DB::Default;
use SL::Helper::Flash;
$main::lxdebug->leave_sub();
}
+sub search_contact {
+ $::lxdebug->enter_sub;
+ $::auth->assert('customer_vendor_edit');
+
+ $::form->{CUSTOM_VARIABLES} = CVar->get_configs('module' => 'Contacts');
+ ($::form->{CUSTOM_VARIABLES_FILTER_CODE},
+ $::form->{CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables' => $::form->{CUSTOM_VARIABLES},
+ 'include_prefix' => 'l.',
+ 'filter_prefix' => 'filter.',
+ 'include_value' => 'Y');
+
+ $::form->{title} = $::locale->text('Search contacts');
+ $::form->header;
+ print $::form->parse_html_template('ct/search_contact');
+
+ $::lxdebug->leave_sub;
+}
+
sub list_names {
$main::lxdebug->enter_sub();
$main::lxdebug->leave_sub();
}
+sub list_contacts {
+ $::lxdebug->enter_sub;
+ $::auth->assert('customer_vendor_edit');
+
+ $::form->{sortdir} = 1 unless defined $::form->{sortdir};
+
+ my @contacts = CT->search_contacts(
+ search_term => $::form->{search_term},
+ filter => $::form->{filter},
+ );
+
+ my $cvar_configs = CVar->get_configs('module' => 'Contacts');
+
+ my @columns = qw(
+ cp_id vcname vcnumber cp_name cp_givenname cp_street cp_phone1 cp_phone2
+ cp_mobile1 cp_mobile2 cp_email cp_abteilung cp_birthday cp_gender
+ );
+
+ my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
+ my @searchable_custom_variables = grep { $_->{searchable} } @{ $cvar_configs };
+ my %column_defs_cvars = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
+
+ push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
+
+ my @visible_columns;
+ if ($::form->{l}) {
+ @visible_columns = grep { $::form->{l}{$_} } @columns;
+ push @visible_columns, qw(cp_phone1 cp_phone2) if $::form->{l}{cp_phone};
+ push @visible_columns, qw(cp_mobile1 cp_mobile2) if $::form->{l}{cp_mobile};
+ } else {
+ @visible_columns = qw(vcname vcnumber cp_name cp_givenname cp_phone1 cp_phone2 cp_mobile1 cp_email);
+ }
+
+ my %column_defs = (
+ 'cp_id' => { 'text' => $::locale->text('ID'), },
+ 'vcname' => { 'text' => $::locale->text('Customer/Vendor'), },
+ 'vcnumber' => { 'text' => $::locale->text('Customer/Vendor Number'), },
+ 'cp_name' => { 'text' => $::locale->text('Name'), },
+ 'cp_givenname' => { 'text' => $::locale->text('Given Name'), },
+ 'cp_street' => { 'text' => $::locale->text('Street'), },
+ 'cp_phone1' => { 'text' => $::locale->text('Phone1'), },
+ 'cp_phone2' => { 'text' => $::locale->text('Phone2'), },
+ 'cp_mobile1' => { 'text' => $::locale->text('Mobile1'), },
+ 'cp_mobile2' => { 'text' => $::locale->text('Mobile2'), },
+ 'cp_email' => { 'text' => $::locale->text('E-mail'), },
+ 'cp_abteilung' => { 'text' => $::locale->text('Department'), },
+ 'cp_birthday' => { 'text' => $::locale->text('Birthday'), },
+ 'cp_gender' => { 'text' => $::locale->text('Gender'), },
+ %column_defs_cvars,
+ );
+
+ map { $column_defs{$_}->{visible} = 1 } @visible_columns;
+
+ my @hidden_variables = (qw(search_term filter l));
+ my $hide_vars = { map { $_ => $::form->{$_} } @hidden_variables };
+ my @hidden_nondefault = grep({ $::form->{$_} } @hidden_variables);
+ my $callback = build_std_url('action=list_contacts', join '&', map { E($_->[0]) . '=' . E($_->[1]) } @{ flatten($hide_vars) });
+ $::form->{callback} = "$callback&sort=" . E($::form->{sort});
+
+ map { $column_defs{$_}->{link} = "${callback}&sort=${_}&sortdir=" . ($::form->{sort} eq $_ ? 1 - $::form->{sortdir} : $::form->{sortdir}) } @columns;
+
+ $::form->{title} = $::locale->text('Contacts');
+
+ my $report = SL::ReportGenerator->new(\%::myconfig, $::form);
+
+ my @options;
+ push @options, $::locale->text('Search term') . ': ' . $::form->{search_term} if $::form->{search_term};
+ for (qw(cp_name cp_givenname cp_title cp_email cp_abteilung cp_project)) {
+ push @options, $column_defs{$_}{text} . ': ' . $::form->{filter}{$_} if $::form->{filter}{$_};
+ }
+ if ($::form->{filter}{status}) {
+ push @options, $::locale->text('Status') . ': ' . (
+ $::form->{filter}{status} =~ /active/ ? $::locale->text('Active') :
+ $::form->{filter}{status} =~ /orphaned/ ? $::locale->text('Orphaned') :
+ $::form->{filter}{status} =~ /all/ ? $::locale->text('All') : ''
+ );
+ }
+
+
+ $report->set_options('top_info_text' => join("\n", @options),
+ 'output_format' => 'HTML',
+ 'title' => $::form->{title},
+ 'attachment_basename' => $::locale->text('contact_list') . strftime('_%Y%m%d', localtime time),
+ );
+ $report->set_options_from_form;
+
+ $report->set_columns(%column_defs);
+ $report->set_column_order(@columns);
+
+ $report->set_export_options('list_contacts', @hidden_variables);
+
+ $report->set_sort_indicator($::form->{sort}, $::form->{sortdir});
+
+ CVar->add_custom_variables_to_report('module' => 'Contacts',
+ 'trans_id_field' => 'cp_id',
+ 'configs' => $cvar_configs,
+ 'column_defs' => \%column_defs,
+ 'data' => \@contacts);
+
+
+ foreach my $ref (@contacts) {
+ my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
+
+ $row->{vcname}->{link} = build_std_url('action=edit', 'id=' . E($ref->{vcid}), 'db=' . E($ref->{db}), 'callback', @hidden_nondefault);
+ $row->{vcnumber}->{link} = $row->{vcname}->{link};
+ $row->{cp_email}->{link} = 'mailto:' . E($ref->{cp_email});
+
+ $report->add_data($row);
+ }
+
+ $report->generate_with_headers;
+
+ $::lxdebug->leave_sub;
+}
+
sub edit {
$main::lxdebug->enter_sub();
$form->{currency} = $form->{curr};
}
- $form->{CUSTOM_VARIABLES} = CVar->get_custom_variables('module' => 'CT', 'trans_id' => $form->{id});
+ $::form->{CUSTOM_VARIABLES} = { };
+ my %specs = ( CT => { field => 'id', name_prefix => '', },
+ Contacts => { field => 'cp_id', name_prefix => 'cp', },
+ );
+
+ for my $module (keys %specs) {
+ my $spec = $specs{$module};
- CVar->render_inputs('variables' => $form->{CUSTOM_VARIABLES}) if (scalar @{ $form->{CUSTOM_VARIABLES} });
+ $::form->{CUSTOM_VARIABLES}->{$module} = CVar->get_custom_variables(module => $module, trans_id => $::form->{ $spec->{field} });
+ CVar->render_inputs(variables => $::form->{CUSTOM_VARIABLES}->{$module}, name_prefix => $spec->{name_prefix})
+ if scalar @{ $::form->{CUSTOM_VARIABLES}->{$module} };
+ }
$form->header;
print $form->parse_html_template('ct/form_header');
CT->query_titles_and_greetings(\%::myconfig, $::form);
CT->get_contact(\%::myconfig, $::form) if $::form->{cp_id};
+ $::form->{CUSTOM_VARIABLES}{Contacts} = CVar->get_custom_variables(module => 'Contacts', trans_id => $::form->{cp_id});
+ CVar->render_inputs(variables => $::form->{CUSTOM_VARIABLES}{Contacts}, name_prefix => 'cp')
+ if scalar @{ $::form->{CUSTOM_VARIABLES}->{Contacts} };
+
$::form->{contacts_label} = \&_contacts_label;
print $::form->ajax_response_header(), $::form->parse_html_template('ct/_contact');
# set module stuff
if ($ref->{module} eq 'oe') {
- my $edit_oe_link = build_std_url("script=oe.pl", 'action=edit', 'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'), 'id=' . E($ref->{trans_id}), 'callback');
- $row->{ordnumber}{link} = $edit_oe_link;
- $row->{quonumber}{link} = $edit_oe_link if (!$ref->{ordnumber});
+ # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
+ #
+ # | ist bestellt | Vom Kunde bestellt | -> edit_oe_ord_link
+ # | Anfrage | Angebot | -> edit_oe_quo_link
+
+ my $edit_oe_ord_link = build_std_url("script=oe.pl", 'action=edit', 'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'), 'id=' . E($ref->{trans_id}), 'callback');
+ my $edit_oe_quo_link = build_std_url("script=oe.pl", 'action=edit', 'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback');
+
+ $row->{ordnumber}{link} = $edit_oe_ord_link;
+ $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
} else {
$row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback');
use Carp;
use CGI;
+use List::MoreUtils qw(uniq);
use List::Util qw(min max first);
use SL::CVar;
push @template_files, "$form->{formname}$form->{language}$printer_code.$extension";
push @template_files, "$form->{formname}.$extension";
push @template_files, "default.$extension";
-
- $form->{IN} = undef;
- for my $filename (@template_files) {
- if (-f "$myconfig{templates}/$filename") {
- $form->{IN} = $filename;
- last;
- }
- }
+ @template_files = uniq @template_files;
+ $form->{IN} = first { -f "$myconfig{templates}/$_" } @template_files;
if (!defined $form->{IN}) {
$::form->error($::locale->text('Cannot find matching template for this print request. Please contact your template maintainer. I tried these: #1.', join ', ', map { "'$_'"} @template_files));
.message_error {
padding: 5px;
background-color: #CC0000;
- color: black;
+ color: white;
font-weight: bolder;
text-align: center;
border-style: solid;
.message_error_label {
padding: 0.5em;
background-color: #E00000;
+ color: white;
font-weight: normal;
text-align: left;
border-style: solid;
'Article type (see below)' => 'Artikeltyp (siehe unten)',
'As a result, the saved onhand values of the present goods can be stored into a warehouse designated by you, or will be reset for a proper warehouse tracking' => 'Als Konsequenz können die gespeicherten Mengen entweder in ein Lager überführt werden, oder für eine frische Lagerverwaltung resettet werden.',
'Assemblies' => 'Erzeugnisse',
+ 'Assembly' => 'Erzeugnis',
'Assembly Description' => 'Erzeugnis-Beschreibung',
'Assembly Number' => 'Erzeugnis-Nummer',
'Assembly Number missing!' => 'Erzeugnisnummer fehlt!',
'Contact deleted.' => 'Ansprechpartner gelöscht.',
'Contact is in use and was flagged invalid.' => 'Ansprechpartner ist noch in Verwendung, und wurde als ungültig markiert.',
'Contact person (surname)' => 'Ansprechpartner (Nachname)',
+ 'Contact persons' => 'Ansprechpartner',
'Contacts' => 'Ansprechpartner',
'Continue' => 'Weiter',
'Contra' => 'gegen',
'Customer type' => 'Kundentyp',
'Customer/Vendor' => 'Kunde/Lieferant',
'Customer/Vendor (database ID)' => 'Kunde/Lieferant (Datenbank-ID)',
+ 'Customer/Vendor Name' => 'Kunde/Lieferant',
+ 'Customer/Vendor Number' => 'Kundennummer/Lieferantennummer',
'Customername' => 'Kundenname',
'Customernumberinit' => 'Kunden-/Lieferantennummernkreis',
'Customers' => 'Kunden',
'Delivery Order created' => 'Lieferschein erstellt',
'Delivery Order deleted!' => 'Lieferschein gelöscht!',
'Delivery Orders' => 'Lieferscheine',
+ 'Delivery Orders for this document' => 'Lieferscheine für dieses Dokument',
+ 'Delivery Plan' => 'Lieferplan',
+ 'Delivery Plan for currently outstanding sales orders' => 'Lieferplan für offene Verkaufsaufträge',
+ 'Delivery information deleted.' => 'Lieferinformation gelöscht.',
+ 'Delivery information saved.' => 'Lieferinformation gespeichert.',
'Department' => 'Abteilung',
'Department 1' => 'Abteilung (1)',
'Department 2' => 'Abteilung (2)',
'Help Template Variables' => 'Hilfe zu Dokumenten-Variablen',
'Help on column names' => 'Hilfe zu Spaltennamen',
'Here\'s an example command line:' => 'Hier ist eine Kommandozeile, die als Beispiel dient:',
+ 'Hide Filter' => 'Filter verbergen',
'Hide by default' => 'Standardmäßig verstecken',
'Hide help text' => 'Hilfetext verbergen',
'History' => 'Historie',
'Logout now' => 'Lx-Office jetzt verlassen',
'Long Dates' => 'Lange Monatsnamen',
'Long Description' => 'Langtext',
- 'Lx-Office' => 'Lx-Office',
'MAILED' => 'Gesendet',
'MSG_BROWSER_DOES_NOT_SUPPORT_IFRAMES' => 'Ihr Browser kann leider keine eingebetteten Frames anzeigen. Bitte wählen Sie ein anderes Menü in der Benutzerkonfiguration im Administrationsmenü aus.',
'Main Preferences' => 'Grundeinstellungen',
'Missing user id!' => 'Benutzer ID fehlt!',
'Mitarbeiter' => 'Mitarbeiter',
'Mixed (requires column "type")' => 'Gemischt (erfordert Spalte "type")',
- 'Mobile1' => 'Mobile 1',
- 'Mobile2' => 'Mobile 2',
+ 'Mobile' => 'Mobiltelefon',
+ 'Mobile1' => 'Mobil 1',
+ 'Mobile2' => 'Mobil 2',
'Model' => 'Lieferanten-Art-Nr.',
'Model (with X being a number)' => 'Lieferanten-Art-Nr. (X ist eine fortlaufende Zahl)',
'Module' => 'Modul',
'POSTED AS NEW' => 'Als neu gebucht',
'PRINTED' => 'Gedruckt',
'Packing Lists' => 'Lieferschein',
+ 'Page' => 'Seite',
'Page #1/#2' => 'Seite #1/#2',
'Paid' => 'bezahlt',
'Part' => 'Ware',
'Price factor (name)' => 'Preisfaktor (Name)',
'Price factor deleted!' => 'Preisfaktor gelöscht.',
'Price factor saved!' => 'Preisfaktor gespeichert.',
+ 'Price information' => 'Preisinformation',
'Pricegroup' => 'Preisgruppe',
'Pricegroup deleted!' => 'Preisgruppe gelöscht!',
'Pricegroup missing!' => 'Preisgruppe fehlt!',
'Requested execution date from' => 'Gewünschtes Ausführungsdatum von',
'Requested execution date to' => 'Gewünschtes Ausführungsdatum bis',
'Required by' => 'Lieferdatum',
+ 'Reset' => 'Zurücksetzen',
'Restore Dataset' => 'Datenbank wiederherstellen',
'Revenue' => 'Erlöskonto',
'Revenue Account' => 'Erlöskonto',
'Sales Invoices' => 'Kundenrechnung',
'Sales Order' => 'Kundenauftrag',
'Sales Orders' => 'Aufträge',
+ 'Sales Price information' => 'Verkaufspreisinformation',
'Sales Report' => 'Verkaufsbericht',
'Sales and purchase invoices with inventory transactions with taxkeys' => 'Einkaufs- und Verkaufsrechnungen mit Warenbestandsbuchungen mit Steuerschlüsseln',
'Sales delivery order' => 'Lieferschein (Verkauf)',
'Screen' => 'Bildschirm',
'Search AP Aging' => 'Offene Verbindlichkeiten',
'Search AR Aging' => 'Offene Forderungen',
+ 'Search contacts' => 'Ansprechpartnersuche',
+ 'Search term' => 'Suchbegriff',
'Searchable' => 'Durchsuchbar',
'Select' => 'auswählen',
'Select a Customer' => 'Endkunde auswählen',
'Shopartikel' => 'Shopartikel',
'Short' => 'Knapp',
'Show' => 'Zeigen',
+ 'Show Filter' => 'Filter zeigen',
'Show Salesman' => 'Verkäufer anzeigen',
'Show TODO list' => 'Aufgabenliste anzeigen',
'Show by default' => 'Standardmäßig anzeigen',
'Skonto Terms' => 'Zahlungsziel Skonto',
'Sold' => 'Verkauft',
'Solution' => 'Lösung',
+ 'Sort By' => 'Sortiert nach',
'Source' => 'Beleg',
'Source BIC' => 'Quell-BIC',
'Source IBAN' => 'Quell-IBAN',
'config/lx_office.conf: Key "authentication/ldap" is missing.' => 'config/lx_office.conf: Der Schlüssel "authentication/ldap" fehlt.',
'config/lx_office.conf: Missing parameters in "authentication/database". Required parameters are "host", "db" and "user".' => 'config/lx_office.conf: Fehlende Parameter in "authentication/database". Benötigte Parameter sind "host", "db" und "user".',
'config/lx_office.conf: Missing parameters in "authentication/ldap". Required parameters are "host", "attribute" and "base_dn".' => 'config/lx_office.conf: Fehlende Parameter in "authentication/ldap". Benötigt werden "host", "attribute" und "base_dn".',
+ 'contact_list' => 'ansprechpartner_liste',
'continue' => 'weiter',
'correction' => 'Korrektur',
'cp_greeting to cp_gender migration' => 'Datenumwandlung von Titel nach Geschlecht (cp_greeting to cp_gender)',
'customer_list' => 'kundenliste',
'debug' => 'Debug',
'delete' => 'Löschen',
+ 'delivered' => 'geliefert',
'deliverydate' => 'Lieferdatum',
'direct debit' => 'Lastschrift',
'disposed' => 'Entsorgung',
'month' => 'Monatliche Abgabe',
'monthly' => 'monatlich',
'new Window' => 'neues Fenster',
+ 'next' => 'vor',
'no' => 'nein',
'no bestbefore' => 'keine Mindesthaltbarkeit',
'no chargenumber' => 'keine Chargennummer',
'none (pricegroup)' => 'keine',
'not configured' => 'nicht konfiguriert',
+ 'not delivered' => 'nicht geliefert',
'not executed' => 'nicht ausgeführt',
'not transferred in yet' => 'noch nicht eingelagert',
'not transferred out yet' => 'noch nicht ausgelagert',
'pos_eur' => 'E/ÃœR',
'pos_ustva' => 'UStVA',
'posted!' => 'gebucht',
+ 'prev' => 'zurück',
'print' => 'drucken',
'proforma' => 'Proforma',
'project_list' => 'projektliste',
'Missing user id!' => 'Benutzer ID fehlt!',
'Mitarbeiter' => 'Mitarbeiter',
'Mixed (requires column "type")' => 'Gemischt (erfordert Spalte "type")',
- 'Mobile1' => 'Mobile 1',
- 'Mobile2' => 'Mobile 2',
+ 'Mobile' => 'Mobiltelefon',
+ 'Mobile1' => 'Mobil 1',
+ 'Mobile2' => 'Mobil 2',
'Model' => 'Lieferanten-Art-Nr.',
'Model (with X being a number)' => 'Lieferanten-Art-Nr. (X ist eine fortlaufende Zahl)',
'Module' => 'Modul',
'POSTED AS NEW' => 'Als neu gebucht',
'PRINTED' => 'Gedruckt',
'Packing Lists' => 'Lieferschein',
+ 'Page' => '',
'Page #1/#2' => 'Seite #1/#2',
'Paid' => 'bezahlt',
'Part' => 'Ware',
'Price factor (name)' => 'Preisfaktor (Name)',
'Price factor deleted!' => 'Preisfaktor gelöscht.',
'Price factor saved!' => 'Preisfaktor gespeichert.',
+ 'Price information' => '',
'Pricegroup' => 'Preisgruppe',
'Pricegroup deleted!' => 'Preisgruppe gelöscht!',
'Pricegroup missing!' => 'Preisgruppe fehlt!',
'Sales Invoices' => 'Ausgangsrechnungen',
'Sales Order' => 'Kundenauftrag',
'Sales Orders' => 'Aufträge',
+ 'Sales Price information' => '',
'Sales Report' => 'Verkaufsbericht',
'Sales and purchase invoices with inventory transactions with taxkeys' => 'Einkaufs- und Verkaufsrechnungen mit Warenbestandsbuchungen mit Steuerschlüsseln',
'Sales delivery order' => 'Lieferschein (Verkauf)',
'Skonto Terms' => 'Zahlungsziel Skonto',
'Sold' => 'Verkauft',
'Solution' => 'Lösung',
+ 'Sort By' => '',
'Source' => 'Beleg',
'Source BIC' => 'Quell-BIC',
'Source IBAN' => 'Quell-IBAN',
'customer_list' => 'kundenliste',
'debug' => 'Debug',
'delete' => 'Löschen',
+ 'delivered' => '',
'deliverydate' => 'Lieferdatum',
'direct debit' => 'Lastschrift',
'disposed' => 'Entsorgung',
'month' => 'Monatliche Abgabe',
'monthly' => 'monatlich',
'new Window' => 'neues Fenster',
+ 'next' => 'nächster',
'no' => 'nein',
'no bestbefore' => 'keine Mindesthaltbarkeit',
'no chargenumber' => 'keine Chargennummer',
'none (pricegroup)' => 'keine',
'not configured' => 'nicht konfiguriert',
+ 'not delivered' => '',
'not executed' => 'nicht ausgeführt',
'not transferred in yet' => 'noch nicht eingelagert',
'not transferred out yet' => 'noch nicht ausgelagert',
'pos_eur' => 'E/ÃœR',
'pos_ustva' => 'UStVA',
'posted!' => 'gebucht',
+ 'prev' => 'vorheriger',
'print' => 'drucken',
'proforma' => 'Proforma',
'project_list' => 'projektliste',
action=search
db=vendor
+[Master Data--Reports--Contacts]
+ACCESS=customer_vendor_edit
+module=ct.pl
+action=search_contact
+db=customer
+
[Master Data--Reports--Parts]
ACCESS=part_service_assembly_edit
module=ic.pl
module=dn.pl
action=search
+[AR--Reports--Delivery Plan]
+ACCESS=sales_order_edit
+module=controller.pl
+action=DeliveryPlan/list
[AP]
}
use SL::InstallationCheck;
+use SL::LxOfficeConf;
my %check;
Getopt::Long::Configure ("bundling");
$| = 1;
+if (!SL::LxOfficeConf->read(undef, 'may fail')) {
+ print_header('Could not load the config file. If you have dependancies from any features enabled in the configuration these will still show up as optional because of this. Please rerun this script after installing the dependancies needed to load the cofiguration.')
+} else {
+ SL::InstallationCheck::check_for_conditional_dependencies();
+}
+
if ($check{r}) {
print_header('Checking Required Modules');
check_module($_, required => 1) for @SL::InstallationCheck::required_modules;
- print_header('Standard check for required modules done. See additional parameters for more checks (-- help)') if $default_run;
+ print_header('Standard check for required modules done. See additional parameters for more checks (--help)') if $default_run;
}
if ($check{o}) {
print_header('Checking Optional Modules');
--- /dev/null
+use Test::More;
+use Test::Deep;
+use Data::Dumper;
+
+use_ok 'SL::Request', qw(flatten unflatten);
+
+use constant DEBUG => 0;
+
+sub f ($$$) {
+ my $flat = flatten($_[0]);
+ print Dumper($flat) if DEBUG;
+
+ my $unflat = unflatten($flat);
+ print Dumper($unflat) if DEBUG;
+
+ cmp_deeply($flat, $_[1], $_[2]);
+ cmp_deeply($unflat, $_[0], $_[2]);
+}
+
+f {
+ test => 1,
+ whut => 2
+},
+[
+ [ test => 1 ],
+ [ whut => 2 ],
+], 'simple case';
+
+f { a => { b => 2 } },
+[
+ [ 'a.b' => 2 ]
+], 'simple hash nesting';
+
+f { a => [ 2, 4 ] },
+[
+ [ 'a[]' => 2 ],
+ [ 'a[]' => 4 ],
+], 'simple array';
+
+f { a => [ { c => 1, d => 2 }, { c => 3, d => 4 }, ] },
+[
+ [ 'a[+].c', 1 ],
+ [ 'a[].d', 2 ],
+ [ 'a[+].c', 3 ],
+ [ 'a[].d', 4 ],
+], 'array of hashes';
+
+# tests from Hash::Flatten below
+f {
+ 'x' => 1,
+ 'y' => {
+ 'a' => 2,
+ 'b' => {
+ 'p' => 3,
+ 'q' => 4
+ },
+ }
+}, bag(
+ [ 'x' => 1, ],
+ [ 'y.a' => 2, ],
+ [ 'y.b.p' => 3, ],
+ [ 'y.b.q' => 4 ],
+), 'Hash::Flatten 1';
+
+
+f {
+ 'x' => 1,
+ '0' => {
+ '1' => 2,
+ },
+ 'a' => [1,2,3],
+},
+bag (
+ ['x' => 1, ],
+ ['0.1' => 2, ],
+ ['a[]' => 1, ],
+ ['a[]' => 2, ],
+ ['a[]' => 3, ],
+), 'Hash::Flatten 2 - weird keys and values';
+
+
+f {
+ 'x' => 1,
+ 'ay' => {
+ 'a' => 2,
+ 'b' => {
+ 'p' => 3,
+ 'q' => 4
+ },
+ },
+ 'y' => [
+ 'a', 2,
+ {
+ 'baz' => 'bum',
+ },
+ ]
+},
+bag(
+ [ 'ay.b.p' => 3, ],
+ [ 'ay.b.q' => 4, ],
+ [ 'ay.a' => 2, ],
+ [ 'x' => 1, ],
+ [ 'y[]' => 'a', ],
+ [ 'y[]' => 2 ],
+ [ 'y[+].baz' => 'bum', ],
+), 'Hash::Flatten 3 - mixed';
+
+f {
+ 'x' => 1,
+ 'y' => [
+ [
+ 'a', 'fool', 'is',
+ ],
+ [
+ 'easily', [ 'parted', 'from' ], 'his'
+ ],
+ 'money',
+ ]
+},
+bag(
+ [ 'x' => 1, ],
+ [ 'y[][]' => 'his', ],
+ [ 'y[][+][]' => 'parted', ],
+ [ 'y[][][]' => 'from', ],
+ [ 'y[+][]' => 'a', ],
+ [ 'y[+][]' => 'easily', ],
+ [ 'y[][]' => 'fool', ],
+ [ 'y[][]' => 'is' ],
+ [ 'y[]' => 'money', ],
+), 'Hash::Flatten 4 - array nesting';
+
+f {
+ 'x' => 1,
+ 'ay' => {
+ 'a' => 2,
+ 'b' => {
+ 'p' => 3,
+ 'q' => 4
+ },
+ },
+ 's' => 'hey',
+ 'y' => [
+ 'a', 2, {
+ 'baz' => 'bum',
+ },
+ ]
+},
+bag(
+ [ 'x' => 1, ],
+ [ 's' => 'hey', ],
+ [ 'ay.a' => 2, ],
+ [ 'y[+].baz' => 'bum', ],
+ [ 'ay.b.p' => 3, ],
+ [ 'y[]' => 'a', ],
+ [ 'ay.b.q' => 4, ],
+ [ 'y[]' => 2 ],
+), 'Hash::Flatten 5 - deep mix';
+
+done_testing();
[%- ELSIF var.type == 'customer' %]
[% L.customer_picker(var_name, var.value) %]
+[%- ELSIF var.type == 'vendor' %]
+[% L.vendor_selector(var_name, var.value) %]
+
+[%- ELSIF var.type == 'part' %]
+[% L.part_selector(var_name, var.value) %]
+
[%- ELSIF var.type == 'select' %]
<select name="[% var_name %]">
[%- ELSIF cvar.var.type == 'customer' %]
[%- L.customer_picker(render_input_blocks__cvar_name, cvar.value) %]
+[%- ELSIF cvar.var.type == 'vendor' %]
+[% L.vendor_selector(render_input_blocks__cvar_name, cvar.value) %]
+
+[%- ELSIF cvar.var.type == 'part' %]
+[% L.part_selector(render_input_blocks__cvar_name, cvar.value) %]
+
[%- ELSIF cvar.var.type == 'number' %]
[%- L.input_tag(render_input_blocks__cvar_name, LxERP.format_amount(cvar.value, -2)) %]
<td valign="top">
[%- IF var.type == 'bool' %]
- <select name="cvar_[% HTML.escape(var.name) %]">
+ <select name="[% filter_prefix %]cvar_[% HTML.escape(var.name) %]">
<option value="">---</option>
<option value="yes">[% 'Yes' | $T8 %]</option>
<option value="no">[% 'No' | $T8 %]</option>
[%- ELSIF var.type == 'date' %]
[% 'from (time)' | $T8 %]
- <input name="cvar_[% HTML.escape(var.name) %]_from" id="cvar_[% HTML.escape(var.name) %]_from" size="12">
+ <input name="[% filter_prefix %]cvar_[% HTML.escape(var.name) %]_from" id="cvar_[% HTML.escape(var.name) %]_from" size="12">
<input type="button" name="cvar_[% HTML.escape(var.name) %]_from_button" id="cvar_[% HTML.escape(var.name) %]_from_trigger" value="?">
[% 'to (time)' | $T8 %]
- <input name="cvar_[% HTML.escape(var.name) %]_to" id="cvar_[% HTML.escape(var.name) %]_to" size="12">
+ <input name="[% filter_prefix %]cvar_[% HTML.escape(var.name) %]_to" id="cvar_[% HTML.escape(var.name) %]_to" size="12">
<input type="button" name="cvar_[% HTML.escape(var.name) %]_to_button" id="cvar_[% HTML.escape(var.name) %]_to_trigger" value="?">
<script type="text/javascript">
</script>
[%- ELSIF var.type == 'number' %]
- <select name="cvar_[% HTML.escape(var.name) %]_qtyop">
+ <select name="[% filter_prefix %]cvar_[% HTML.escape(var.name) %]_qtyop">
<option selected>==</option>
<option>=/=</option>
<option>></option>
<input name="cvar_[% HTML.escape(var.name) %]"[% IF var.maxlength %]maxlength="[% HTML.escape(var.maxlength) %]"[% END %]>
[%- ELSIF var.type == 'customer' %]
- <input name="cvar_[% var.name | html %]">
+ <input name="[% filter_prefix %]cvar_[% var.name | html %]">
[% ELSIF var.type == 'select' %]
- <select name="cvar_[% HTML.escape(var.name) %]">
+ <select name="[% filter_prefix %]cvar_[% HTML.escape(var.name) %]">
<option value="" selected>---</option>
[%- FOREACH option = var.OPTIONS %]
<option>[% HTML.escape(option.value) %]</option>
</select>
[%- ELSE %]
- <input name="cvar_[% HTML.escape(var.name) %]"[% IF var.maxlength %]maxlength="[% HTML.escape(var.maxlength) %]"[% END %]>
+ <input name="[% filter_prefix %]cvar_[% HTML.escape(var.name) %]"[% IF var.maxlength %]maxlength="[% HTML.escape(var.maxlength) %]"[% END %]>
[%- END %]
</td>
<th align="left" nowrap>[% 'Bcc' | $T8 %]</th>
<td><input name="bcc" size="40" value="[% HTML.escape(bcc) %]"></td>
</tr>
+ [% IF CUSTOM_VARIABLES.Contacts.size %]
+ <tr>
+ <td colspan="2"><hr></td>
+ </tr>
+
+ [%- FOREACH var = CUSTOM_VARIABLES.Contacts %]
+ <tr>
+ <th align="left" valign="top" nowrap>[% HTML.escape(var.description) %]</th>
+ <td valign="top">[% var.HTML_CODE %]</td>
+ </tr>
+ [%- END %]
+ [%- END %]
+
</table>
[% IF cp_id %]
<li><a href="#" rel="deliveries">[% 'Supplies' | $T8 %]</a></li>
[%- END %]
<li><a href="#" rel="vcnotes">[% 'Notes' | $T8 %]</a></li>
- [%- IF CUSTOM_VARIABLES.size %]
+ [%- IF CUSTOM_VARIABLES.CT.size %]
<li><a href="#" rel="custom_variables">[% 'Custom Variables' | $T8 %]</a></li>
[%- END %]
</ul>
</td>
[%- IF is_customer && !conf_vertreter %]
<th align="right">[% 'Salesman' | $T8 %]</th>
- <td>[% L.select_tag('salesman_id', L.options_for_select(ALL_SALESMEN, default=salesman_id, show_empty=1, title='safe_name')) %]</td>
+ <td>[% L.select_tag('salesman_id', L.options_for_select(ALL_SALESMEN, default=salesman_id, with_empty=1, title='safe_name')) %]</td>
[%- END %]
</tr>
</table>
<br style="clear: left" />
</div>
- [%- IF CUSTOM_VARIABLES.size %]
+ [%- IF CUSTOM_VARIABLES.CT.size %]
<div id="custom_variables" class="tabcontent">
<p>
<table>
- [%- FOREACH var = CUSTOM_VARIABLES %]
+ [%- FOREACH var = CUSTOM_VARIABLES.CT %]
<tr>
<td align="right" valign="top">[% HTML.escape(var.description) %]</td>
<td valign="top">[% var.HTML_CODE %]</td>
--- /dev/null
+[%- USE HTML %]
+[%- USE T8 %]
+<body onload="fokus()">
+
+ <form method="post" action="ct.pl" name="Form">
+
+ <input type="hidden" name="db" value="[% db | html %]">
+
+ <div class="listtop">[% 'Contacts' | $T8 %]</div>
+
+ <table>
+ <tr>
+ <th align="right" nowrap>[% 'Name' | $T8 %]</th>
+ <td><input name="filter.cp_name" size="35"></td>
+ </tr>
+ <tr>
+ <th align="right" nowrap>[% 'Greeting' | $T8 %]</th>
+ <td><input name="filter.cp_greeting" size="35"></td>
+ </tr>
+ <tr>
+ <th align="right" nowrap>[% 'Title' | $T8 %]</th>
+ <td><input name="filter.cp_title" size="35"></td>
+ </tr>
+ <tr>
+ <th align="right" nowrap>[% 'E-mail' | $T8 %]</th>
+ <td><input name="filter.cp_email" size="35"></td>
+ </tr>
+ <tr>
+ <th align="right" nowrap>[% 'Department' | $T8 %]</th>
+ <td><input name="filter.cp_abteilung" size="35"></td>
+ </tr>
+ <tr>
+ <th align="right" nowrap>[% 'Project' | $T8 %]</th>
+ <td><input name="filter.cp_project" size="35"></td>
+ </tr>
+
+ [% CUSTOM_VARIABLES_FILTER_CODE %]
+
+ <tr>
+ <td></td>
+ <td>
+ <input name="filter.status" class="radio" type="radio" value="active" checked> [% 'Active' | $T8 %]
+ <input name="filter.status" class="radio" type="radio" value="all"> [% 'All' | $T8 %]
+ <input name="filter.status" class="radio" type="radio" value="orphaned"> [% 'Orphaned' | $T8 %]
+ </td>
+ </tr>
+
+ <tr>
+ <th align="right" nowrap>[% 'Include in Report' | $T8 %]</th>
+ <td>
+ <table border="0">
+ <tr>
+ <td>
+ <input name="l.cp_id" id="l_cp_id" type="checkbox" class="checkbox" value="Y">
+ <label for="l_cp_id">[% 'ID' | $T8 %]</label>
+ </td>
+ <td>
+ <input name="l.vcnumber" id="l_vcnumber" type="checkbox" class="checkbox" value="Y" checked>
+ <label for="l_vcnumber">[% 'Customer/Vendor Number' | $T8 %]</label>
+ </td>
+ <td>
+ <input name="l.vcname" id="l_vcname" type="checkbox" class="checkbox" value="Y" checked>
+ <label for="l_vcname">[% 'Customer/Vendor Name' | $T8 %]</label>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <input name="l.cp_name" id="l_cp_name" type="checkbox" class="checkbox" value="Y" checked>
+ <label for="l_cp_name">[% 'Name' | $T8 %]</label>
+ </td>
+ <td>
+ <input name="l.cp_givenname" id="l_cp_givenname" type="checkbox" class="checkbox" value="Y" checked>
+ <label for="l_cp_givenname">[% 'Given Name' | $T8 %]</label>
+ </td>
+ <td>
+ <input name="l.cp_street" id="l_cp_street" type="checkbox" class="checkbox" value="Y">
+ <label for="l_cp_street">[% 'Street' | $T8 %]</label>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input name="l.cp_phone" id="l_cp_phone" type="checkbox" class="checkbox" value="Y" checked>
+ <label for="l_cp_phone">[% 'Phone' | $T8 %]</label>
+ </td>
+ <td>
+ <input name="l.cp_mobile" id="l_cp_mobile" type="checkbox" class="checkbox" value="Y" checked>
+ <label for="l_cp_mobile">[% 'Mobile' | $T8 %]</label>
+ </td>
+ <td>
+ <input name="l.cp_email" id="l_cp_email" type="checkbox" class="checkbox" value="Y" checked>
+ <label for="l_cp_email">[% 'E-mail' | $T8 %]</label>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <input name="l.cp_birthday" id="l_cp_birthday" type="checkbox" class="checkbox" value="Y">
+ <label for="l_cp_birthday">[% 'Birthday' | $T8 %]</label>
+ </td>
+ <td>
+ <input name="l.cp_abteilung" id="l_cp_abteilung" type="checkbox" class="checkbox" value="Y">
+ <label for="l_cp_abteilung">[% 'Department' | $T8 %]</label>
+ </td>
+ <td>
+ <input name="l.cp_gender" id="l_cp_gender" type="checkbox" class="checkbox" value="Y">
+ <label for="l_cp_gender">[% 'Gender' | $T8 %]</label>
+ </td>
+ </tr>
+
+ [% CUSTOM_VARIABLES_INCLUSION_CODE %]
+
+ </table>
+ </td>
+ </tr>
+ </table>
+
+ <input type="hidden" name="nextsub" value="list_contacts">
+
+ <input type="submit" class="submit" name="action" value="[% 'Continue' | $T8 %]">
+ </form>
+
+</body>
+</html>
--- /dev/null
+[%- USE T8 %]
+[%- USE L %]
+[%- USE LxERP %]
+[%- USE HTML %]
+<form action='controller.pl' method='post'>
+<div class='filter_toggle'>
+<a href='#' onClick='javascript:$(".filter_toggle").toggle()'>[% 'Show Filter' | $T8 %]</a>
+ [% SELF.filter_summary %]
+</div>
+<div class='filter_toggle' style='display:none'>
+<a href='#' onClick='javascript:$(".filter_toggle").toggle()'>[% 'Hide Filter' | $T8 %]</a>
+ <table id='filter_table'>
+ <tr>
+ <th align="right">[% 'Number' | $T8 %]</th>
+ <td>[% L.input_tag('filter.order.ordnumber:substr::ilike', filter.order.ordnumber_substr__ilike, size = 20) %]</td>
+ </tr>
+ <tr>
+ <th align="right">[% 'Part Number' | $T8 %]</th>
+ <td>[% L.input_tag('filter.part.partnumber:substr::ilike', filter.part.partnumber_substr__ilike, size = 20) %]</td>
+ </tr>
+ <tr>
+ <th align="right">[% 'Part Description' | $T8 %]</th>
+ <td>[% L.input_tag('filter.description:substr::ilike', filter.description_substr__ilike, size = 20) %]</td>
+ </tr>
+ <tr>
+ <th align="right">[% 'Delivery Date' | $T8 %] [% 'From Date' | $T8 %]</th>
+ <td>[% L.date_tag('filter.reqdate:date::ge', filter.reqdate_date__ge, cal_align = 'BR') %]</td>
+ </tr>
+ <tr>
+ <th align="right">[% 'Delivery Date' | $T8 %] [% 'To Date' | $T8 %]</th>
+ <td>[% L.date_tag('filter.reqdate:date::le', filter.reqdate_date__le, cal_align = 'BR') %]</td>
+ </tr>
+ <tr>
+ <th align="right">[% 'Quantity' | $T8 %]</th>
+ <td>[% L.input_tag('filter.qty:number', filter.qty_number, size = 20) %]</td>
+ </tr>
+ <tr>
+ <th align="right">[% 'Customer' | $T8 %]</th>
+ <td>[% L.input_tag('filter.order.customer.name:substr::ilike', filter.order.customer.name_substr__ilike, size = 20) %]</td>
+ </tr>
+ <tr>
+ <th align="right">[% 'Customer Number' | $T8 %]</th>
+ <td>[% L.input_tag('filter.order.customer.customernumber:substr::ilike', filter.order.customer.customernumber_substr__ilike, size = 20) %]</td>
+ </tr>
+ <tr>
+ <th align="right">[% 'Type' | $T8 %]</th>
+ <td>
+ [% L.checkbox_tag('filter.part.type.part', checked=filter.part.type.part, label=LxERP.t8('Part')) %]
+ [% L.checkbox_tag('filter.part.type.service', checked=filter.part.type.service, label=LxERP.t8('Service')) %]
+ [% L.checkbox_tag('filter.part.type.assembly', checked=filter.part.type.assembly, label=LxERP.t8('Assembly')) %]
+ </td>
+ </tr>
+ </table>
+
+[% L.hidden_tag('action', 'DeliveryPlan/dispatch') %]
+[% L.hidden_tag('sort_by', FORM.sort_by) %]
+[% L.hidden_tag('sort_dir', FORM.sort_dir) %]
+[% L.hidden_tag('page', FORM.page) %]
+[% L.input_tag('action_list', LxERP.t8('Continue'), type = 'submit', class='submit')%]
+
+
+<a href='#' onClick='javascript:$("#filter_table input").attr("value","");$("#filter_table option").attr("selected","")'>[% 'Reset' | $T8 %]</a>
+
+</div>
+
+</form>
--- /dev/null
+[% USE HTML %][% USE T8 %][% USE L %][% USE LxERP %]
+
+[% BLOCK header %]
+ [% SET new_sort_dir = SELF.sort_by == sort_by ? 1 - SELF.sort_dir : SELF.sort_dir %]
+ <th width="[% size %]%">
+ <a href="[% SELF.url_for(action => 'list') %]&sort_by=[% sort_by %]&sort_dir=[% new_sort_dir %]&page=[% FORM.page %]">
+ [%- title %]
+ [%- IF SELF.sort_by == sort_by %]
+ <img src="image/[% IF SELF.sort_dir %]down[% ELSE %]up[% END %].png" border="0">
+ [%- END %]
+ </a>
+ </th>
+[% END %]
+
+<div id="orders">
+[%- IF !SELF.orderitems.size %]
+ <p>[%- 'There are no outstanding deliveries at the moment.' | $T8 %]</p>
+[%- ELSE %]
+
+ <table width=100%>
+ <tr class="listheading">
+ [% PROCESS header title=LxERP.t8('Date') sort_by='transdate', size=15 %]
+ [% PROCESS header title=LxERP.t8('Description') sort_by='description', size=15 %]
+ [% PROCESS header title=LxERP.t8('Part Number') sort_by='partnumber', size=15 %]
+ [% PROCESS header title=LxERP.t8('Qty') sort_by='qty', size=10 %]
+ [% PROCESS header title=LxERP.t8('Order') sort_by='ordnumber', size=10 %]
+ [% PROCESS header title=LxERP.t8('Customer') sort_by='customer', size=10 %]
+ </tr>
+
+ [%- FOREACH row = SELF.orderitems %]
+ <tr class="listrow[% loop.count % 2 %]">
+ <td>[% row.transdate ? row.transdate : row.order.reqdate_as_date %]</td>
+ <td>[% row.part.partnumber | html %]</td>
+ <td>[% row.description | html %]</td>
+ <td class='numeric'>[% LxERP.format_amount(row.qty, 2) | html %]</td>
+ <td>[% row.order.ordnumber | html %]</td>
+ <td>[% row.order.customer.name | html %]</td>
+ </tr>
+ [%- END %]
+ </table>
+ <p align=right>[% PROCESS 'common/paginate.html' pages=SELF.pages, base_url=SELF.url_for(action='list', sort_dir=SELF.sort_dir, sort_by=SELF.sort_by) %]</p>
+
+[%- END %]
+</div>
--- /dev/null
+[%- USE T8 %]
+
+<h1>[% 'Delivery Plan' | $T8 %]</h1>
+
+[%- PROCESS 'delivery_plan/_filter.html' filter=FORM.filter %]
+ <hr>
+[%- PROCESS 'delivery_plan/_list.html' %]
--- /dev/null
+[% 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}) %]
+<p align=right>[% PROCESS 'common/paginate.html' pages=SELF.pages, base_url=SELF.url_for(report_bottom_url_args) %]</p>
--- /dev/null
+[%- USE L %]
+[%- PROCESS 'delivery_plan/_filter.html' filter=SELF.filter %]
+ <hr>
<br style="clear: left" />
</div>
+[%- IF id %]
+<div id="sales_price_information" class="tabcontent">
+ [% PROCESS ic/sales_price_information.html id=id %]
+</div>
+[%- END %]
+
[%- IF CUSTOM_VARIABLES.size %]
<div id="custom_variables" class="tabcontent">
<ul id="maintab" class="shadetabs">
<li class="selected"><a href="#" rel="master_data">[% 'Basic Data' | $T8 %]</a></li>
+ [%- IF id %]
+ <li><a href="#" rel="sales_price_information">[% 'Price information' | $T8 %]</a></li>
+ [%- END %]
[%- IF CUSTOM_VARIABLES.size %]
<li><a href="#" rel="custom_variables">[% 'Custom Variables' | $T8 %]</a></li>
[%- END %]
--- /dev/null
+<div id='sales_price_information_sales_order'></div>
+<div id='sales_price_information_sales_quotation'></div>
+
+<script type='text/javascript'>
+ function get_report(target, source, data){
+ $.ajax({
+ url: source,
+// beforeSend: function () { $(target).html('<img src="image/spinner.gif">') },
+ success: function (rsp) {
+ $(target).html(rsp);
+ $(target).find('.paginate').find('a').click(function(event){ redirect_event(event, target) });
+ $(target).find('a.report-generator-header-link').click(function(event){ redirect_event(event, target) });
+ },
+ data: data,
+ });
+ };
+
+ function redirect_event(event, target){
+ event.preventDefault();
+ get_report(target, event.target + '', {});
+ }
+
+ $(document).ready(function(){
+ get_report('#sales_price_information_sales_order', 'controller.pl', { action: 'SellPriceInformation/list', 'filter.part.id': [% id %], 'filter.order.type': 'sales_order' });
+ get_report('#sales_price_information_sales_quotation', 'controller.pl', { action: 'SellPriceInformation/list', 'filter.part.id': [% id %], 'filter.order.type': 'sales_quotation' });
+ });
+
+
+</script>
[%- USE T8 %]
[% USE HTML %]<body style="padding:0px; margin:0px;">
+ <script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript">
-<!--
+ <!--
+function on_keydown_quicksearch(e) {
+ var key;
+
+ if (window.event)
+ key = window.event.keyCode; // IE
+ else
+ key = e.which; // Firefox
+
+ if (key != 13)
+ return true;
+
+ var search_term = $("#search_term");
+ var value = search_term.val();
+ if (!value)
+ return true;
+
+ var url = "ct.pl?action=list_contacts&INPUT_ENCODING=utf-8&filter.status=active&search_term=" + encodeURIComponent(value);
+
+ search_term.val('');
+ $("#win1").attr('src', url);
+
+ return false;
+}
+
function clockon() {
var now = new Date();
var h = now.getHours();
<table border="0" width="100%" background="image/bg_titel.gif" cellpadding="0" cellspacing="0">
<tr>
- <td style="color:white; font-family:verdana,arial,sans-serif; font-size: 12px;">
+ <td style="color:white; font-family:verdana,arial,sans-serif; font-size: 12px;" nowrap>
[<a href="menuv3.pl?action=display" target="_blank">[% 'new Window' | $T8 %]</a>]
[<a href="JavaScript:top.main_window.print()">[% 'print' | $T8 %]</a>]
+
+ [[% 'Search contacts' | $T8 %] <input size="15" name="search_term" id="search_term" onkeydown="return on_keydown_quicksearch(event)">]
</td>
<td align="right" style="vertical-align:middle; color:white; font-family:verdana,arial,sans-serif; font-size: 12px;" nowrap>
[[% 'User' | $T8 %]: [% HTML.escape(login) %] -
--- /dev/null
+[% USE HTML %]
+[% USE LxERP %]
+[% USE T8 %]
+[%- IF !TABDIALOG %]
+<body>
+
+ <p><div class="listtop">[% 'Price information' | $T8 %]</div></p>
+
+ <p>
+ <input type="button" class="submit" onclick="window.close()" value="[% 'Close window' | $T8 %]">
+ </p>
+
+ <p>
+ <table>
+ <tr>
+ <td>[% 'Part Number' | $T8 %]:</td>
+ <td>[% HTML.escape(part_info.partnumber) %]</td>
+ </tr>
+
+ <tr>
+ <td>[% 'Description' | $T8 %]:</td>
+ <td>[% HTML.escape(part_info.description) %]</td>
+ </tr>
+
+ [%- UNLESS part_info.type == 'service' %]
+ <tr>
+ <td>[% 'Stocked' | $T8 %]:</td>
+ <td>[% HTML.escape(LxERP.format_amount_units(stocked, part_info.unit, part_info.unit)) %]</td>
+ </tr>
+ [%- END %]
+ </table>
+ </p>
+
+[%- END %]
+
+ <p>
+ <table>
+ [%- SET custom_texts = {
+ sales_order => {
+ head => LxERP.t8('Sales Orders'),
+ empty => LxERP.t8('This part has not been used in a sales order yet.'),
+ date => LxERP.t8('Order Date'),
+ number => LxERP.t8('Order Number'),
+ vc => LxERP.t8('Customer'),
+ },
+ sales_quotation => {
+ head => LxERP.t8('Quotations'),
+ empty => LxERP.t8('This part has not been used in a quotation yet.'),
+ date => LxERP.t8('Quotation Date'),
+ number => LxERP.t8('Quotation Number'),
+ vc => LxERP.t8('Customer'),
+ },
+ purchase_order => {
+ head => LxERP.t8('Purchase Orders'),
+ empty => LxERP.t8('This part has not been used in a purchase order yet.'),
+ date => LxERP.t8('Order Date'),
+ number => LxERP.t8('Order Number'),
+ vc => LxERP.t8('Vendor'),
+ },
+ request_quotation => {
+ head => LxERP.t8('RFQs'),
+ empty => LxERP.t8('This part has not been used in a request quotation yet.'),
+ date => LxERP.t8('Quotation Date'),
+ number => LxERP.t8('Quotation Number'),
+ vc => LxERP.t8('Vendor'),
+ },
+ }
+ %]
+ [%- FOREACH type = ['sales_order', 'sales_quotation', 'purchase_order', 'request_quotation'] %]
+
+ <tr>
+ <th class="listtop" colspan="8">[% custom_texts.$type.head %]</th>
+ </tr>
+
+ [%- IF !PRICE_INFO.$type.size %]
+ <tr>
+ <td colspan="7">[% custom_texts.$type.empty %]</td>
+ </tr>
+
+ [%- ELSE %]
+
+ <tr>
+ <th class="listheading">[% custom_texts.$type.date %]</th>
+ <th class="listheading">[% custom_texts.$type.number %]</th>
+ <th class="listheading">[% custom_texts.$type.vc %]</th>
+ <th class="listheading">[% 'Delivered' | $T8 %]</th>
+ <th class="listheading" align="right">[% 'Quantity' | $T8 %]</th>
+ <th class="listheading" align="right">[% 'Unit price' | $T8 %]</th>
+ <th class="listheading" align="right">[% 'Discount' | $T8 %]</th>
+ <th class="listheading" align="right">[% 'Line total' | $T8 %]</th>
+ </tr>
+
+ [%- FOREACH row = PRICE_INFO.$type %]
+ <tr class="listrow[% loop.count % 2 %]">
+ <td>[% HTML.escape(row.date) %]</td>
+ <td><a href="oe.pl?action=edit&type=[% type | html %]&id=[% row.id | html %]">[% HTML.escape(row.number) %]</a></td>
+ <td>[% HTML.escape(row.vc) %]</td>
+ <td align="right">[% HTML.escape(LxERP.format_amount(row.ship)) %] [% HTML.escape(row.unit) %]</td>
+ <td align="right">[% HTML.escape(LxERP.format_amount(row.qty)) %] [% HTML.escape(row.unit) %]</td>
+ <td align="right">[% HTML.escape(LxERP.format_amount(row.sellprice, 2)) %]</td>
+ <td align="right">[% HTML.escape(LxERP.format_amount(row.discount * 100)) %]</td>
+ <td align="right">[% HTML.escape(LxERP.format_amount(row.linetotal, 2)) %]</td>
+ </tr>
+ [%- END %]
+ [%- END %]
+ [%- END %]
+ </table>
+ </p>
+
+[%- IF !TABDIALOG %]
+</body>
+</html>
+[%- END %]
--- /dev/null
+<p align=right>[% PROCESS 'common/paginate.html' pages=SELF.pages, base_url=SELF.self_url %]</p>