]> wagnertech.de Git - mfinanz.git/commitdiff
Merge branch 'master' of vc.linet-services.de:public/lx-office-erp
authorMoritz Bunkus <m.bunkus@linet-services.de>
Sat, 28 Apr 2012 10:59:54 +0000 (12:59 +0200)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Sat, 28 Apr 2012 10:59:54 +0000 (12:59 +0200)
21 files changed:
SL/CVar.pm
SL/Controller/DeliveryPlan.pm [new file with mode: 0644]
SL/Controller/Helper/ParseFilter.pm
SL/DB/OrderItem.pm
SL/InstallationCheck.pm
SL/LxOfficeConf.pm
SL/Template/Plugin/L.pm
bin/mozilla/amcvar.pl
bin/mozilla/ct.pl
bin/mozilla/ic.pl
locale/de/all
locale/de_DE/all
menu.ini
scripts/installation_check.pl
templates/webpages/amcvar/render_inputs.html
templates/webpages/amcvar/render_inputs_block.html
templates/webpages/delivery_plan/_filter.html [new file with mode: 0644]
templates/webpages/delivery_plan/_list.html [new file with mode: 0644]
templates/webpages/delivery_plan/list.html [new file with mode: 0644]
templates/webpages/delivery_plan/report_bottom.html [new file with mode: 0644]
templates/webpages/delivery_plan/report_top.html [new file with mode: 0644]

index 714744a1acbddf79f859c19a1ac8942c04d0d374..c0b3f43f07944590e13f608c8e2dde05dd27b1de 100644 (file)
@@ -7,7 +7,7 @@ use Scalar::Util qw(blessed);
 use Data::Dumper;
 
 use SL::DBUtils;
-use SL::MoreCommon qw(listify);
+use SL::MoreCommon qw(any listify);
 
 sub get_configs {
   $main::lxdebug->enter_sub();
@@ -243,6 +243,8 @@ sub get_custom_variables {
                      : $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;
@@ -279,6 +281,12 @@ sub get_custom_variables {
     } 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);
     }
   }
 
@@ -340,7 +348,7 @@ sub save_custom_variables {
 
     } 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;
     }
 
@@ -498,10 +506,16 @@ sub build_filter_query {
 
       $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}%";
     }
 
@@ -577,6 +591,8 @@ sub add_custom_variables_to_report {
         : $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};
     }
diff --git a/SL/Controller/DeliveryPlan.pm b/SL/Controller/DeliveryPlan.pm
new file mode 100644 (file)
index 0000000..e280a1b
--- /dev/null
@@ -0,0 +1,313 @@
+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;
index 7c393b01ce8126d56af1011dcb09f636ab9a395a..3bf1b65957fda6b0dada84372763096184edda34 100644 (file)
@@ -20,11 +20,13 @@ my %filters = (
 );
 
 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 {
index d193393bb667c61bbf53af04a6566bd768349338..f24abcaf12f4a6ee19b3dc9576155da1f79b7ecd 100644 (file)
@@ -68,11 +68,12 @@ sub _sort_spec {
                         qty           => [ 'qty'                  ],
                         ordnumber     => [ 'order.ordnumber'      ],
                         customer      => [ 'lower(customer.name)', ],
-                        position      => [ 'trans_id' ],
-                        reqdate       => [ 'COALESCE(orderitems.reqdate, order.transdate)' ],
+                        position      => [ 'trans_id', 'runningnumber' ],
+                        reqdate       => [ 'COALESCE(orderitems.reqdate, order.reqdate)' ],
                         orddate       => [ 'order.orddate' ],
                         sellprice     => [ 'sellprice' ],
                         discount      => [ 'discount' ],
+                        transdate     => [ 'transdate::date', 'order.reqdate' ],
                       },
            default => [ 'position', 1 ],
            nulls   => { }
index 3100ac67f0f5d4061ab65d4bdaebbb61ccc64381..935d131cdafe8fd0862b7ac228cbdbb5055fc7dc 100644 (file)
@@ -11,6 +11,7 @@ BEGIN {
 @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' },
@@ -36,6 +37,8 @@ BEGIN {
 
 @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 = (
@@ -97,7 +100,7 @@ my %conditional_dependencies;
 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');
 }
 
index 51ad991d916cfad4ed80d65233bef4320355c01a..3cd462cff1a743e4ec3c3bd4cc06f068f31bbf30 100644 (file)
@@ -2,27 +2,51 @@ package SL::LxOfficeConf;
 
 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 {
index f7ffc11b2d046493c957b99a5f90ee0cc46c4e22..0b4967d3484f77c125cba4547d7e5811829d7d6b 100644 (file)
@@ -342,6 +342,36 @@ autocomplete_customer('#$name_e\_name');
 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 = '';
index 25e3ec738eeeccf1318918a0be017a1574fc26fd..343350a242de0151da1c0d795c9178cd93cc0418 100644 (file)
@@ -55,9 +55,11 @@ our %translations = ('text'      => $locale->text('Free-form text'),
                      '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')                },
index 01bf4fe859f1b2c3ce629f9a3bdb164794a8807a..56fad897f7b5c85cc4870b88bcbde7f25c685dfe 100644 (file)
@@ -115,7 +115,6 @@ 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},
@@ -123,6 +122,7 @@ sub search_contact {
                                                                            'filter_prefix'  => 'filter.',
                                                                            'include_value'  => 'Y');
 
+  $::form->{title} = $::locale->text('Search contacts');
   $::form->header;
   print $::form->parse_html_template('ct/search_contact');
 
@@ -329,8 +329,8 @@ sub list_contacts {
     'cp_street'    => { 'text' => $::locale->text('Street'), },
     'cp_phone1'    => { 'text' => $::locale->text('Phone1'), },
     'cp_phone2'    => { 'text' => $::locale->text('Phone2'), },
-    'cp_mobile1'   => { 'text' => $::locale->text('Mobile 1'), },
-    'cp_mobile2'   => { 'text' => $::locale->text('Mobile 2'), },
+    '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'), },
index 0b607da1d5a95f789fe7bb337b18dc97b3368ef9..3eb67383cdc96123082fca1418aa998da70e4637 100644 (file)
@@ -1335,9 +1335,16 @@ sub generate_report {
 
     # 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');
index 9e595516cc468c2b90c10e4b7eeaa1f502e5dee9..7b16fc0f0bffd88b791c827ded0bea62a3bf8759 100644 (file)
@@ -208,6 +208,7 @@ $self->{texts} = {
   '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&ouml;nnen die gespeicherten Mengen entweder in ein Lager &uuml;berf&uuml;hrt werden, oder f&uuml;r eine frische Lagerverwaltung resettet werden.',
   'Assemblies'                  => 'Erzeugnisse',
+  'Assembly'                    => 'Erzeugnis',
   'Assembly Description'        => 'Erzeugnis-Beschreibung',
   'Assembly Number'             => 'Erzeugnis-Nummer',
   'Assembly Number missing!'    => 'Erzeugnisnummer fehlt!',
@@ -592,6 +593,11 @@ $self->{texts} = {
   'Delivery Order created'      => 'Lieferschein erstellt',
   'Delivery Order deleted!'     => 'Lieferschein gel&ouml;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)',
@@ -903,6 +909,7 @@ $self->{texts} = {
   '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&auml;&szlig;ig verstecken',
   'Hide help text'              => 'Hilfetext verbergen',
   'History'                     => 'Historie',
@@ -1140,10 +1147,8 @@ $self->{texts} = {
   'Mitarbeiter'                 => 'Mitarbeiter',
   'Mixed (requires column "type")' => 'Gemischt (erfordert Spalte "type")',
   'Mobile'                      => 'Mobiltelefon',
-  'Mobile 1'                    => '',
-  'Mobile 2'                    => '',
-  'Mobile1'                     => 'Mobile 1',
-  'Mobile2'                     => 'Mobile 2',
+  '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',
@@ -1520,6 +1525,7 @@ $self->{texts} = {
   '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',
@@ -1637,6 +1643,7 @@ $self->{texts} = {
   'Shopartikel'                 => 'Shopartikel',
   'Short'                       => 'Knapp',
   'Show'                        => 'Zeigen',
+  'Show Filter'                 => 'Filter zeigen',
   'Show Salesman'               => 'Verkäufer anzeigen',
   'Show TODO list'              => 'Aufgabenliste anzeigen',
   'Show by default'             => 'Standardm&auml;&szlig;ig anzeigen',
index df19dbaaf1db961ca7b0b33d1fd99104047d8269..8d090b85d8ae10e69231e5d817012fc51686eb91 100644 (file)
@@ -1144,8 +1144,9 @@ $self->{texts} = {
   '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',
index da54506645635ca6c9eea90e92683e650d515870..ac8f9b9b17d12b1c17874b73db745a2d26360f4e 100644 (file)
--- a/menu.ini
+++ b/menu.ini
@@ -166,6 +166,10 @@ ACCESS=dunning_edit
 module=dn.pl
 action=search
 
+[AR--Reports--Delivery Plan]
+ACCESS=sales_order_edit
+module=controller.pl
+action=DeliveryPlan/list
 
 [AP]
 
index def85906c9f3b88bc9ec34a88e1aecbb1063fe51..a50d184d2863e06a91d2e05c5e34dae7b92fb1a0 100755 (executable)
@@ -14,6 +14,7 @@ BEGIN {
 }
 
 use SL::InstallationCheck;
+use SL::LxOfficeConf;
 
 my %check;
 Getopt::Long::Configure ("bundling");
@@ -47,6 +48,12 @@ if ($check{a}) {
 
 $| = 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;
index a69101bf00c0cb069881102a0b8d2f9c870fdf9c..dc9c09fe8369e5fe9eaf22af5656056a090e714c 100644 (file)
 [%- 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 %]">
index 3f7461cc8972f2bd92693656aa01c860dbba8943..33623f61d43026feba817efe66536d1e6f79bbb0 100644 (file)
 [%- 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)) %]
 
diff --git a/templates/webpages/delivery_plan/_filter.html b/templates/webpages/delivery_plan/_filter.html
new file mode 100644 (file)
index 0000000..9bba140
--- /dev/null
@@ -0,0 +1,66 @@
+[%- 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>
diff --git a/templates/webpages/delivery_plan/_list.html b/templates/webpages/delivery_plan/_list.html
new file mode 100644 (file)
index 0000000..f0bb277
--- /dev/null
@@ -0,0 +1,44 @@
+[% 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>
diff --git a/templates/webpages/delivery_plan/list.html b/templates/webpages/delivery_plan/list.html
new file mode 100644 (file)
index 0000000..16ec656
--- /dev/null
@@ -0,0 +1,7 @@
+[%- USE T8 %]
+
+<h1>[% 'Delivery Plan' | $T8 %]</h1>
+
+[%- PROCESS 'delivery_plan/_filter.html' filter=FORM.filter %]
+ <hr>
+[%- PROCESS 'delivery_plan/_list.html' %]
diff --git a/templates/webpages/delivery_plan/report_bottom.html b/templates/webpages/delivery_plan/report_bottom.html
new file mode 100644 (file)
index 0000000..3193961
--- /dev/null
@@ -0,0 +1,4 @@
+[% 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>
diff --git a/templates/webpages/delivery_plan/report_top.html b/templates/webpages/delivery_plan/report_top.html
new file mode 100644 (file)
index 0000000..cc35146
--- /dev/null
@@ -0,0 +1,3 @@
+[%- USE L %]
+[%- PROCESS 'delivery_plan/_filter.html' filter=SELF.filter %]
+ <hr>