Merge branch 'master' of ssh://git-mbunkus@lx-office.linet-services.de/~/lx-office-erp
[kivitendo-erp.git] / SL / Form.pm
index 2d4d5e4..32a82e0 100644 (file)
@@ -37,8 +37,6 @@
 
 package Form;
 
-#use strict;
-
 use Data::Dumper;
 
 use CGI;
@@ -57,6 +55,9 @@ use SL::Template;
 use SL::User;
 use Template;
 use List::Util qw(first max min sum);
+use List::MoreUtils qw(any);
+
+use strict;
 
 my $standard_dbh;
 
@@ -70,45 +71,46 @@ END {
 sub _store_value {
   $main::lxdebug->enter_sub(2);
 
-  my $curr  = shift;
+  my $self  = shift;
   my $key   = shift;
   my $value = shift;
 
-  while ($key =~ /\[\+?\]\.|\./) {
-    substr($key, 0, $+[0]) = '';
+  my @tokens = split /((?:\[\+?\])?(?:\.|$))/, $key;
 
-    if ($& eq '.') {
-      $curr->{$`} ||= { };
-      $curr         = $curr->{$`};
+  my $curr;
 
-    } else {
-      $curr->{$`} ||= [ ];
-      if (!scalar @{ $curr->{$`} } || $& eq '[+].') {
-        push @{ $curr->{$`} }, { };
-      }
+  if (scalar @tokens) {
+     $curr = \ $self->{ shift @tokens };
+  }
 
-      $curr = $curr->{$`}->[-1];
-    }
+  while (@tokens) {
+    my $sep = shift @tokens;
+    my $key = shift @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->{$key} = $value;
+  $$curr = $value;
 
   $main::lxdebug->leave_sub(2);
 
-  return \$curr->{$key};
+  return $curr;
 }
 
 sub _input_to_hash {
   $main::lxdebug->enter_sub(2);
 
-  my $params = shift;
-  my $input  = shift;
+  my $self  = shift;
+  my $input = shift;
 
-  my @pairs  = split(/&/, $input);
+  my @pairs = split(/&/, $input);
 
   foreach (@pairs) {
     my ($key, $value) = split(/=/, $_, 2);
-    _store_value($params, unescape(undef, $key), unescape(undef, $value));
+    $self->_store_value($self->unescape($key), $self->unescape($value)) if ($key);
   }
 
   $main::lxdebug->leave_sub(2);
@@ -117,13 +119,13 @@ sub _input_to_hash {
 sub _request_to_hash {
   $main::lxdebug->enter_sub(2);
 
-  my $params = shift;
-  my $input  = shift;
+  my $self  = shift;
+  my $input = shift;
 
   if (!$ENV{'CONTENT_TYPE'}
       || ($ENV{'CONTENT_TYPE'} !~ /multipart\/form-data\s*;\s*boundary\s*=\s*(.+)$/)) {
 
-    _input_to_hash($params, $input);
+    $self->_input_to_hash($input);
 
     $main::lxdebug->leave_sub(2);
     return;
@@ -171,8 +173,8 @@ sub _request_to_hash {
           substr $line, $-[0], $+[0] - $-[0], "";
         }
 
-        $previous           = _store_value($params, $name, '');
-        $params->{FILENAME} = $filename if ($filename);
+        $previous         = $self->_store_value($name, '') if ($name);
+        $self->{FILENAME} = $filename if ($filename);
 
         next;
       }
@@ -195,12 +197,16 @@ sub _request_to_hash {
 }
 
 sub _recode_recursively {
+  $main::lxdebug->enter_sub();
   my ($iconv, $param) = @_;
 
-  if (ref $param eq 'HASH') {
+  if (any { ref $param eq $_ } qw(Form HASH)) {
     foreach my $key (keys %{ $param }) {
       if (!ref $param->{$key}) {
-        $param->{$key} = $iconv->convert($param->{$key});
+        # Workaround for a bug: converting $param->{$key} directly
+        # leads to 'undef'. I don't know why. Converting a copy works,
+        # though.
+        $param->{$key} = $iconv->convert("" . $param->{$key});
       } else {
         _recode_recursively($iconv, $param->{$key});
       }
@@ -209,12 +215,16 @@ sub _recode_recursively {
   } elsif (ref $param eq 'ARRAY') {
     foreach my $idx (0 .. scalar(@{ $param }) - 1) {
       if (!ref $param->[$idx]) {
-        $param->[$idx] = $iconv->convert($param->[$idx]);
+        # Workaround for a bug: converting $param->[$idx] directly
+        # leads to 'undef'. I don't know why. Converting a copy works,
+        # though.
+        $param->[$idx] = $iconv->convert("" . $param->[$idx]);
       } else {
         _recode_recursively($iconv, $param->[$idx]);
       }
     }
   }
+  $main::lxdebug->leave_sub();
 }
 
 sub new {
@@ -241,27 +251,26 @@ sub new {
 
   bless $self, $type;
 
-  my $parameters = { };
-  _request_to_hash($parameters, $_);
+  $self->_request_to_hash($_);
 
   my $db_charset   = $main::dbcharset;
   $db_charset    ||= Common::DEFAULT_CHARSET;
 
-  if ($parameters->{INPUT_ENCODING} && (lc $parameters->{INPUT_ENCODING} ne $db_charset)) {
-    require Text::Iconv;
-    my $iconv = Text::Iconv->new($parameters->{INPUT_ENCODING}, $db_charset);
+  if ($self->{INPUT_ENCODING}) {
+    if (lc $self->{INPUT_ENCODING} ne lc $db_charset) {
+      require Text::Iconv;
+      my $iconv = Text::Iconv->new($self->{INPUT_ENCODING}, $db_charset);
 
-    _recode_recursively($iconv, $parameters);
+      _recode_recursively($iconv, $self);
+    }
 
-    delete $parameters{INPUT_ENCODING};
+    delete $self->{INPUT_ENCODING};
   }
 
-  map { $self->{$_} = $parameters->{$_}; } keys %{ $parameters };
-
   $self->{action}  =  lc $self->{action};
   $self->{action}  =~ s/( |-|,|\#)/_/g;
 
-  $self->{version} =  "2.6.0 beta 1";
+  $self->{version} =  "2.6.1";
 
   $main::lxdebug->leave_sub();
 
@@ -394,28 +403,33 @@ sub unescape {
 }
 
 sub quote {
+  $main::lxdebug->enter_sub();
   my ($self, $str) = @_;
 
   if ($str && !ref($str)) {
     $str =~ s/\"/"/g;
   }
 
-  $str;
+  $main::lxdebug->leave_sub();
 
+  return $str;
 }
 
 sub unquote {
+  $main::lxdebug->enter_sub();
   my ($self, $str) = @_;
 
   if ($str && !ref($str)) {
     $str =~ s/"/\"/g;
   }
 
-  $str;
+  $main::lxdebug->leave_sub();
 
+  return $str;
 }
 
 sub hide_form {
+  $main::lxdebug->enter_sub();
   my $self = shift;
 
   if (@_) {
@@ -426,7 +440,7 @@ sub hide_form {
       print($main::cgi->hidden("-name" => $_, "-default" => $self->{$_}) . "\n");
     }
   }
-
+  $main::lxdebug->leave_sub();
 }
 
 sub error {
@@ -545,9 +559,10 @@ sub create_http_response {
     my $session_cookie_value   = $main::auth->get_session_id();
     $session_cookie_value    ||= 'NO_SESSION';
 
-    $session_cookie = $cgi->cookie('-name'  => $main::auth->get_session_cookie_name(),
-                                   '-value' => $session_cookie_value,
-                                   '-path'  => $base_path);
+    $session_cookie = $cgi->cookie('-name'   => $main::auth->get_session_cookie_name(),
+                                   '-value'  => $session_cookie_value,
+                                   '-path'   => $base_path,
+                                   '-secure' => $ENV{HTTPS});
   }
 
   my %cgi_params = ('-type' => $params{content_type});
@@ -565,6 +580,8 @@ sub create_http_response {
 sub header {
   $main::lxdebug->enter_sub();
 
+  # extra code ist currently only used by menuv3 and menuv4 to set their css.
+  # it is strongly deprecated, and will be changed in a future version.
   my ($self, $extra_code) = @_;
 
   if ($self->{header}) {
@@ -609,13 +626,22 @@ sub header {
                         </style>|;
     }
 
-    my $fokus = qq|  document.$self->{fokus}.focus();| if ($self->{"fokus"});
+    my $fokus = qq|
+    <script type="text/javascript">
+    <!--
+      function fokus() {
+        document.$self->{fokus}.focus();
+      }
+    //-->
+    </script>
+    | if $self->{"fokus"};
 
     #Set Calendar
     my $jsscript = "";
     if ($self->{jsscript} == 1) {
 
       $jsscript = qq|
+        <script type="text/javascript" src="js/jquery.js"></script>
         <script type="text/javascript" src="js/common.js"></script>
         <style type="text/css">\@import url(js/jscalendar/calendar-win2k-1.css);</style>
         <script type="text/javascript" src="js/jscalendar/calendar.js"></script>
@@ -646,13 +672,9 @@ sub header {
   $jsscript
   $ajax
 
-  <script type="text/javascript">
-  <!--
-    function focus() {
-      $fokus
-    }
-  //-->
-  </script>
+  $fokus
+
+  <link rel="stylesheet" href="css/jquery.autocomplete.css" type="text/css" />
 
   <meta name="robots" content="noindex,nofollow" />
   <script type="text/javascript" src="js/highlight_input.js"></script>
@@ -698,7 +720,7 @@ sub _prepare_html_template {
   my ($self, $file, $additional_params) = @_;
   my $language;
 
-  if (!defined(%main::myconfig) || !defined($main::myconfig{"countrycode"})) {
+  if (!%::myconfig || !$::myconfig{"countrycode"}) {
     $language = $main::language;
   } else {
     $language = $main::myconfig{"countrycode"};
@@ -749,6 +771,8 @@ sub _prepare_html_template {
   $additional_params->{"conf_lizenzen"}               = $main::lizenzen;
   $additional_params->{"conf_latex_templates"}        = $main::latex;
   $additional_params->{"conf_opendocument_templates"} = $main::opendocument_templates;
+  $additional_params->{"conf_vertreter"}              = $main::vertreter;
+  $additional_params->{"conf_show_best_before"}       = $main::show_best_before;
 
   if (%main::debug_options) {
     map { $additional_params->{'DEBUG_' . uc($_)} = $main::debug_options{$_} } keys %main::debug_options;
@@ -979,9 +1003,9 @@ sub format_amount {
   $amount .= $d[0].$p[1].(0 x ($places - length $p[1])) if ($places || $p[1] ne '');
 
   $amount = do {
-    ($dash =~ /-/)    ? ($neg ? "($amount)"  : "$amount" )    :
-    ($dash =~ /DRCR/) ? ($neg ? "$amount DR" : "$amount CR" ) :
-                        ($neg ? "-$amount"   : "$amount" )    ;
+    ($dash =~ /-/)    ? ($neg ? "($amount)"                            : "$amount" )                              :
+    ($dash =~ /DRCR/) ? ($neg ? "$amount " . $main::locale->text('DR') : "$amount " . $main::locale->text('CR') ) :
+                        ($neg ? "-$amount"                             : "$amount" )                              ;
   };
 
 
@@ -1103,13 +1127,13 @@ sub round_amount {
   my ($self, $amount, $places) = @_;
   my $round_amount;
 
-  # Rounding like "Kaufmannsrunden"
-  # Descr. http://de.wikipedia.org/wiki/Rundung
-  # Inspired by
-  # http://www.perl.com/doc/FAQs/FAQ/oldfaq-html/Q4.13.html
-  # Solves Bug: 189
-  # Udo Spallek
-  $amount = $amount * (10**($places));
+  # Rounding like "Kaufmannsrunden" (see http://de.wikipedia.org/wiki/Rundung )
+
+  # Round amounts to eight places before rounding to the requested
+  # number of places. This gets rid of errors due to internal floating
+  # point representation.
+  $amount       = $self->round_amount($amount, 8) if $places < 8;
+  $amount       = $amount * (10**($places));
   $round_amount = int($amount + .5 * ($amount <=> 0)) / (10**($places));
 
   $main::lxdebug->leave_sub(2);
@@ -1133,7 +1157,7 @@ sub parse_template {
 
   if ($self->{"format"} =~ /(opendocument|oasis)/i) {
     $template       = OpenDocumentTemplate->new($self->{"IN"}, $self, $myconfig, $userspath);
-    $ext_for_format = 'odt';
+    $ext_for_format = $self->{"format"} =~ m/pdf/ ? 'pdf' : 'odt';
 
   } elsif ($self->{"format"} =~ /(postscript|pdf)/i) {
     $ENV{"TEXINPUTS"} = ".:" . getcwd() . "/" . $myconfig->{"templates"} . ":" . $ENV{"TEXINPUTS"};
@@ -1293,6 +1317,7 @@ Content-Length: $numbytes
 
         while (<IN>) {
           print OUT $_;
+
         }
 
         close(OUT);
@@ -1312,6 +1337,7 @@ Content-Length: $numbytes
 }
 
 sub get_formname_translation {
+  $main::lxdebug->enter_sub();
   my ($self, $formname) = @_;
 
   $formname ||= $self->{formname};
@@ -1331,12 +1357,15 @@ sub get_formname_translation {
     storno_packing_list     => $main::locale->text('Storno Packing List'),
     sales_delivery_order    => $main::locale->text('Delivery Order'),
     purchase_delivery_order => $main::locale->text('Delivery Order'),
+    dunning                 => $main::locale->text('Dunning'),
   );
 
+  $main::lxdebug->leave_sub();
   return $formname_translations{$formname}
 }
 
 sub get_number_prefix_for_type {
+  $main::lxdebug->enter_sub();
   my ($self) = @_;
 
   my $prefix =
@@ -1345,10 +1374,12 @@ sub get_number_prefix_for_type {
     : ($self->{type} =~ /_delivery_order$/)                   ? 'do'
     :                                                           'ord';
 
+  $main::lxdebug->leave_sub();
   return $prefix;
 }
 
 sub get_extension_for_format {
+  $main::lxdebug->enter_sub();
   my ($self)    = @_;
 
   my $extension = $self->{format} =~ /pdf/i          ? ".pdf"
@@ -1357,10 +1388,12 @@ sub get_extension_for_format {
                 : $self->{format} =~ /html/i         ? ".html"
                 :                                      "";
 
+  $main::lxdebug->leave_sub();
   return $extension;
 }
 
 sub generate_attachment_filename {
+  $main::lxdebug->enter_sub();
   my ($self) = @_;
 
   my $attachment_filename = $main::locale->unquote_special_chars('HTML', $self->get_formname_translation());
@@ -1379,10 +1412,12 @@ sub generate_attachment_filename {
   $attachment_filename =  $main::locale->quote_special_chars('filenames', $attachment_filename);
   $attachment_filename =~ s|[\s/\\]+|_|g;
 
+  $main::lxdebug->leave_sub();
   return $attachment_filename;
 }
 
 sub generate_email_subject {
+  $main::lxdebug->enter_sub();
   my ($self) = @_;
 
   my $subject = $main::locale->unquote_special_chars('HTML', $self->get_formname_translation());
@@ -1392,6 +1427,7 @@ sub generate_email_subject {
     $subject .= " " . $self->{"${prefix}number"}
   }
 
+  $main::lxdebug->leave_sub();
   return $subject;
 }
 
@@ -1409,7 +1445,7 @@ sub cleanup {
     close(FH);
   }
 
-  if ($self->{tmpfile}) {
+  if ($self->{tmpfile} && ! $::keep_temp_files) {
     $self->{tmpfile} =~ s|.*/||g;
     # strip extension
     $self->{tmpfile} =~ s/\.\w+$//g;
@@ -1508,7 +1544,7 @@ sub get_standard_dbh {
   my ($self, $myconfig) = @_;
 
   if ($standard_dbh && !$standard_dbh->{Active}) {
-    $main::lxdebug->message(LXDebug::INFO, "get_standard_dbh: \$standard_dbh is defined but not Active anymore");
+    $main::lxdebug->message(LXDebug->INFO(), "get_standard_dbh: \$standard_dbh is defined but not Active anymore");
     undef $standard_dbh;
   }
 
@@ -1703,7 +1739,7 @@ sub check_exchangerate {
   return $exchangerate;
 }
 
-sub get_default_currency {
+sub get_all_currencies {
   $main::lxdebug->enter_sub();
 
   my ($self, $myconfig) = @_;
@@ -1711,14 +1747,24 @@ sub get_default_currency {
 
   my $query = qq|SELECT curr FROM defaults|;
 
-  my ($curr)            = selectrow_query($self, $dbh, $query);
-  my ($defaultcurrency) = split m/:/, $curr;
+  my ($curr)     = selectrow_query($self, $dbh, $query);
+  my @currencies = grep { $_ } map { s/\s//g; $_ } split m/:/, $curr;
 
   $main::lxdebug->leave_sub();
 
-  return $defaultcurrency;
+  return @currencies;
 }
 
+sub get_default_currency {
+  $main::lxdebug->enter_sub();
+
+  my ($self, $myconfig) = @_;
+  my @currencies        = $self->get_all_currencies($myconfig);
+
+  $main::lxdebug->leave_sub();
+
+  return $currencies[0];
+}
 
 sub set_payment_options {
   $main::lxdebug->enter_sub();
@@ -1944,6 +1990,8 @@ sub get_employee {
 
   my ($self, $dbh) = @_;
 
+  $dbh ||= $self->get_standard_dbh(\%main::myconfig);
+
   my $query = qq|SELECT id, name FROM employee WHERE login = ?|;
   ($self->{"employee_id"}, $self->{"employee"}) = selectrow_query($self, $dbh, $query, $self->{login});
   $self->{"employee_id"} *= 1;
@@ -1986,11 +2034,11 @@ sub get_duedate {
 
   my ($self, $myconfig, $reference_date) = @_;
 
-  my $reference_date = $reference_date ? conv_dateq($reference_date) . '::DATE' : 'current_date';
+  $reference_date = $reference_date ? conv_dateq($reference_date) . '::DATE' : 'current_date';
 
-  my $dbh            = $self->get_standard_dbh($myconfig);
-  my $query          = qq|SELECT ${reference_date} + terms_netto FROM payment_terms WHERE id = ?|;
-  my ($duedate)      = selectrow_query($self, $dbh, $query, $self->{payment_id});
+  my $dbh         = $self->get_standard_dbh($myconfig);
+  my $query       = qq|SELECT ${reference_date} + terms_netto FROM payment_terms WHERE id = ?|;
+  my ($duedate)   = selectrow_query($self, $dbh, $query, $self->{payment_id});
 
   $main::lxdebug->leave_sub();
 
@@ -2117,7 +2165,7 @@ sub _get_charts {
   my $transdate = quote_db_date($params->{transdate});
 
   my $query =
-    qq|SELECT c.id, c.accno, c.description, c.link, tk.taxkey_id, tk.tax_id | .
+    qq|SELECT c.id, c.accno, c.description, c.link, c.charttype, tk.taxkey_id, tk.tax_id | .
     qq|FROM chart c | .
     qq|LEFT JOIN taxkeys tk ON | .
     qq|(tk.id = (SELECT id FROM taxkeys | .
@@ -2190,9 +2238,15 @@ sub _get_business_types {
 
   my ($self, $dbh, $key) = @_;
 
-  $key = "all_business_types" unless ($key);
-  $self->{$key} =
-    selectall_hashref_query($self, $dbh, qq|SELECT * FROM business|);
+  my $options       = ref $key eq 'HASH' ? $key : { key => $key };
+  $options->{key} ||= "all_business_types";
+  my $where         = '';
+
+  if (exists $options->{salesman}) {
+    $where = 'WHERE ' . ($options->{salesman} ? '' : 'NOT ') . 'COALESCE(salesman)';
+  }
+
+  $self->{ $options->{key} } = selectall_hashref_query($self, $dbh, qq|SELECT * FROM business $where ORDER BY lower(description)|);
 
   $main::lxdebug->leave_sub();
 }
@@ -2256,14 +2310,15 @@ $main::lxdebug->enter_sub();
 sub _get_customers {
   $main::lxdebug->enter_sub();
 
-  my ($self, $dbh, $key, $limit) = @_;
-
-  $key = "all_customers" unless ($key);
-  my $limit_clause = "LIMIT $limit" if $limit;
+  my ($self, $dbh, $key) = @_;
 
-  my $query = qq|SELECT * FROM customer WHERE NOT obsolete ORDER BY name $limit_clause|;
+  my $options        = ref $key eq 'HASH' ? $key : { key => $key };
+  $options->{key}  ||= "all_customers";
+  my $limit_clause   = "LIMIT $options->{limit}" if $options->{limit};
+  my $where          = $options->{business_is_salesman} ? qq| AND business_id IN (SELECT id FROM business WHERE salesman)| : '';
 
-  $self->{$key} = selectall_hashref_query($self, $dbh, $query);
+  my $query = qq|SELECT * FROM customer WHERE NOT obsolete $where ORDER BY name $limit_clause|;
+  $self->{ $options->{key} } = selectall_hashref_query($self, $dbh, $query);
 
   $main::lxdebug->leave_sub();
 }
@@ -2430,11 +2485,7 @@ sub get_lists {
   }
 
   if($params{"customers"}) {
-    if (ref $params{"customers"} eq 'HASH') {
-      $self->_get_customers($dbh, $params{"customers"}{key}, $params{"customers"}{limit});
-    } else {
-      $self->_get_customers($dbh, $params{"customers"});
-    }
+    $self->_get_customers($dbh, $params{"customers"});
   }
 
   if($params{"vendors"}) {
@@ -2536,7 +2587,7 @@ sub all_vc {
   my ($count) = selectrow_query($self, $dbh, $query);
 
   # build selection list
-  if ($count < $myconfig->{vclimit}) {
+  if ($count <= $myconfig->{vclimit}) {
     $query = qq|SELECT id, name, salesman_id
                 FROM $table WHERE NOT obsolete
                 ORDER BY name|;
@@ -2656,7 +2707,7 @@ sub all_departments {
                  ORDER BY description|;
   $self->{all_departments} = selectall_hashref_query($self, $dbh, $query);
 
-  delete($self->{all_departments}) unless (@{ $self->{all_departments} });
+  delete($self->{all_departments}) unless (@{ $self->{all_departments} || [] });
 
   $main::lxdebug->leave_sub();
 }
@@ -2812,7 +2863,7 @@ sub create_links {
                                       (startdate <= a.transdate) ORDER BY startdate DESC LIMIT 1))
          WHERE a.trans_id = ?
          AND a.fx_transaction = '0'
-         ORDER BY a.oid, a.transdate|;
+         ORDER BY a.acc_trans_id, a.transdate|;
     $sth = $dbh->prepare($query);
     do_statement($self, $sth, $query, $self->{id});
 
@@ -2936,7 +2987,9 @@ sub lastname_used {
 sub current_date {
   $main::lxdebug->enter_sub();
 
-  my ($self, $myconfig, $thisdate, $days) = @_;
+  my $self              = shift;
+  my $myconfig          = shift  || \%::myconfig;
+  my ($thisdate, $days) = @_;
 
   my $dbh = $self->get_standard_dbh($myconfig);
   my $query;
@@ -3168,9 +3221,8 @@ sub get_history {
       qq|SELECT h.employee_id, h.itime::timestamp(0) AS itime, h.addition, h.what_done, emp.name, h.snumbers, h.trans_id AS id | .
       qq|FROM history_erp h | .
       qq|LEFT JOIN employee emp ON (emp.id = h.employee_id) | .
-      qq|WHERE trans_id = | . $trans_id
-      . $restriction . qq| |
-      . $order;
+      qq|WHERE (trans_id = | . $trans_id . qq|) $restriction | .
+      $order;
 
     my $sth = $dbh->prepare($query) || $self->dberror($query);
 
@@ -3245,6 +3297,8 @@ sub update_business {
        WHERE id = ? FOR UPDATE|;
   my ($var) = selectrow_query($self, $dbh, $query, $business_id);
 
+  return undef unless $var;
+
   if ($var =~ m/\d+$/) {
     my $new_var  = (substr $var, $-[0]) * 1 + 1;
     my $len_diff = length($var) - $-[0] - length($new_var);
@@ -3400,3 +3454,93 @@ sub restore_vars {
 }
 
 1;
+
+__END__
+
+=head1 NAME
+
+SL::Form.pm - main data object.
+
+=head1 SYNOPSIS
+
+This is the main data object of Lx-Office.
+Unfortunately it also acts as a god object for certain data retrieval procedures used in the entry points.
+Points of interest for a beginner are:
+
+ - $form->error            - renders a generic error in html. accepts an error message
+ - $form->get_standard_dbh - returns a database connection for the
+
+=head1 SPECIAL FUNCTIONS
+
+=over 4
+
+=item _store_value()
+
+parses a complex var name, and stores it in the form.
+
+syntax:
+  $form->_store_value($key, $value);
+
+keys must start with a string, and can contain various tokens.
+supported key structures are:
+
+1. simple access
+  simple key strings work as expected
+
+  id => $form->{id}
+
+2. hash access.
+  separating two keys by a dot (.) will result in a hash lookup for the inner value
+  this is similar to the behaviour of java and templating mechanisms.
+
+  filter.description => $form->{filter}->{description}
+
+3. array+hashref access
+
+  adding brackets ([]) before the dot will cause the next hash to be put into an array.
+  using [+] instead of [] will force a new array index. this is useful for recurring
+  data structures like part lists. put a [+] into the first varname, and use [] on the
+  following ones.
+
+  repeating these names in your template:
+
+    invoice.items[+].id
+    invoice.items[].parts_id
+
+  will result in:
+
+    $form->{invoice}->{items}->[
+      {
+        id       => ...
+        parts_id => ...
+      },
+      {
+        id       => ...
+        parts_id => ...
+      }
+      ...
+    ]
+
+4. arrays
+
+  using brackets at the end of a name will result in a pure array to be created.
+  note that you mustn't use [+], which is reserved for array+hash access and will
+  result in undefined behaviour in array context.
+
+  filter.status[]  => $form->{status}->[ val1, val2, ... ]
+
+=item update_business PARAMS
+
+PARAMS (not named):
+ \%config,     - config hashref
+ $business_id, - business id
+ $dbh          - optional database handle
+
+handles business (thats customer/vendor types) sequences.
+
+special behaviour for empty strings in customerinitnumber field:
+will in this case not increase the value, and return undef.
+
+=back
+
+=cut