ClientJS: AJAX-Form-Submit mit jQuery-Form-Plugin
[kivitendo-erp.git] / SL / Form.pm
1 #========= ===========================================================
2 # LX-Office ERP
3 # Copyright (C) 2004
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
6 #
7 #=====================================================================
8 # SQL-Ledger Accounting
9 # Copyright (C) 1998-2002
10 #
11 #  Author: Dieter Simader
12 #   Email: dsimader@sql-ledger.org
13 #     Web: http://www.sql-ledger.org
14 #
15 # Contributors: Thomas Bayen <bayen@gmx.de>
16 #               Antti Kaihola <akaihola@siba.fi>
17 #               Moritz Bunkus (tex code)
18 #
19 # This program is free software; you can redistribute it and/or modify
20 # it under the terms of the GNU General Public License as published by
21 # the Free Software Foundation; either version 2 of the License, or
22 # (at your option) any later version.
23 #
24 # This program is distributed in the hope that it will be useful,
25 # but WITHOUT ANY WARRANTY; without even the implied warranty of
26 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27 # GNU General Public License for more details.
28 # You should have received a copy of the GNU General Public License
29 # along with this program; if not, write to the Free Software
30 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
31 #======================================================================
32 # Utilities for parsing forms
33 # and supporting routines for linking account numbers
34 # used in AR, AP and IS, IR modules
35 #
36 #======================================================================
37
38 package Form;
39
40 use Carp;
41 use Data::Dumper;
42
43 use CGI;
44 use Cwd;
45 use Encode;
46 use File::Copy;
47 use IO::File;
48 use SL::Auth;
49 use SL::Auth::DB;
50 use SL::Auth::LDAP;
51 use SL::AM;
52 use SL::Common;
53 use SL::CVar;
54 use SL::DB;
55 use SL::DBConnect;
56 use SL::DBUtils;
57 use SL::DB::Customer;
58 use SL::DB::Default;
59 use SL::DB::PaymentTerm;
60 use SL::DB::Vendor;
61 use SL::DO;
62 use SL::IC;
63 use SL::IS;
64 use SL::Layout::Dispatcher;
65 use SL::Locale;
66 use SL::Mailer;
67 use SL::Menu;
68 use SL::MoreCommon qw(uri_encode uri_decode);
69 use SL::OE;
70 use SL::PrefixedNumber;
71 use SL::Request;
72 use SL::Template;
73 use SL::User;
74 use SL::X;
75 use Template;
76 use URI;
77 use List::Util qw(first max min sum);
78 use List::MoreUtils qw(all any apply);
79
80 use strict;
81
82 my $standard_dbh;
83
84 END {
85   disconnect_standard_dbh();
86 }
87
88 sub disconnect_standard_dbh {
89   return unless $standard_dbh;
90   $standard_dbh->disconnect();
91   undef $standard_dbh;
92 }
93
94 sub read_version {
95   my ($self) = @_;
96
97   open VERSION_FILE, "VERSION";                 # New but flexible code reads version from VERSION-file
98   my $version =  <VERSION_FILE>;
99   $version    =~ s/[^0-9A-Za-z\.\_\-]//g; # only allow numbers, letters, points, underscores and dashes. Prevents injecting of malicious code.
100   close VERSION_FILE;
101
102   return $version;
103 }
104
105 sub new {
106   $main::lxdebug->enter_sub();
107
108   my $type = shift;
109
110   my $self = {};
111
112   no warnings 'once';
113   if ($LXDebug::watch_form) {
114     require SL::Watchdog;
115     tie %{ $self }, 'SL::Watchdog';
116   }
117
118   bless $self, $type;
119
120   $self->{version} = $self->read_version;
121
122   $main::lxdebug->leave_sub();
123
124   return $self;
125 }
126
127 sub read_cgi_input {
128   my ($self) = @_;
129   SL::Request::read_cgi_input($self);
130 }
131
132 sub _flatten_variables_rec {
133   $main::lxdebug->enter_sub(2);
134
135   my $self   = shift;
136   my $curr   = shift;
137   my $prefix = shift;
138   my $key    = shift;
139
140   my @result;
141
142   if ('' eq ref $curr->{$key}) {
143     @result = ({ 'key' => $prefix . $key, 'value' => $curr->{$key} });
144
145   } elsif ('HASH' eq ref $curr->{$key}) {
146     foreach my $hash_key (sort keys %{ $curr->{$key} }) {
147       push @result, $self->_flatten_variables_rec($curr->{$key}, $prefix . $key . '.', $hash_key);
148     }
149
150   } else {
151     foreach my $idx (0 .. scalar @{ $curr->{$key} } - 1) {
152       my $first_array_entry = 1;
153
154       my $element = $curr->{$key}[$idx];
155
156       if ('HASH' eq ref $element) {
157         foreach my $hash_key (sort keys %{ $element }) {
158           push @result, $self->_flatten_variables_rec($element, $prefix . $key . ($first_array_entry ? '[+].' : '[].'), $hash_key);
159           $first_array_entry = 0;
160         }
161       } else {
162         @result = ({ 'key' => $prefix . $key . ($first_array_entry ? '[+]' : '[]'), 'value' => $element });
163       }
164     }
165   }
166
167   $main::lxdebug->leave_sub(2);
168
169   return @result;
170 }
171
172 sub flatten_variables {
173   $main::lxdebug->enter_sub(2);
174
175   my $self = shift;
176   my @keys = @_;
177
178   my @variables;
179
180   foreach (@keys) {
181     push @variables, $self->_flatten_variables_rec($self, '', $_);
182   }
183
184   $main::lxdebug->leave_sub(2);
185
186   return @variables;
187 }
188
189 sub flatten_standard_variables {
190   $main::lxdebug->enter_sub(2);
191
192   my $self      = shift;
193   my %skip_keys = map { $_ => 1 } (qw(login password header stylesheet titlebar version), @_);
194
195   my @variables;
196
197   foreach (grep { ! $skip_keys{$_} } keys %{ $self }) {
198     push @variables, $self->_flatten_variables_rec($self, '', $_);
199   }
200
201   $main::lxdebug->leave_sub(2);
202
203   return @variables;
204 }
205
206 sub debug {
207   $main::lxdebug->enter_sub();
208
209   my ($self) = @_;
210
211   print "\n";
212
213   map { print "$_ = $self->{$_}\n" } (sort keys %{$self});
214
215   $main::lxdebug->leave_sub();
216 }
217
218 sub dumper {
219   $main::lxdebug->enter_sub(2);
220
221   my $self          = shift;
222   my $password      = $self->{password};
223
224   $self->{password} = 'X' x 8;
225
226   local $Data::Dumper::Sortkeys = 1;
227   my $output                    = Dumper($self);
228
229   $self->{password} = $password;
230
231   $main::lxdebug->leave_sub(2);
232
233   return $output;
234 }
235
236 sub escape {
237   my ($self, $str) = @_;
238
239   return uri_encode($str);
240 }
241
242 sub unescape {
243   my ($self, $str) = @_;
244
245   return uri_decode($str);
246 }
247
248 sub quote {
249   $main::lxdebug->enter_sub();
250   my ($self, $str) = @_;
251
252   if ($str && !ref($str)) {
253     $str =~ s/\"/&quot;/g;
254   }
255
256   $main::lxdebug->leave_sub();
257
258   return $str;
259 }
260
261 sub unquote {
262   $main::lxdebug->enter_sub();
263   my ($self, $str) = @_;
264
265   if ($str && !ref($str)) {
266     $str =~ s/&quot;/\"/g;
267   }
268
269   $main::lxdebug->leave_sub();
270
271   return $str;
272 }
273
274 sub hide_form {
275   $main::lxdebug->enter_sub();
276   my $self = shift;
277
278   if (@_) {
279     map({ print($::request->{cgi}->hidden("-name" => $_, "-default" => $self->{$_}) . "\n"); } @_);
280   } else {
281     for (sort keys %$self) {
282       next if (($_ eq "header") || (ref($self->{$_}) ne ""));
283       print($::request->{cgi}->hidden("-name" => $_, "-default" => $self->{$_}) . "\n");
284     }
285   }
286   $main::lxdebug->leave_sub();
287 }
288
289 sub throw_on_error {
290   my ($self, $code) = @_;
291   local $self->{__ERROR_HANDLER} = sub { die SL::X::FormError->new($_[0]) };
292   $code->();
293 }
294
295 sub error {
296   $main::lxdebug->enter_sub();
297
298   $main::lxdebug->show_backtrace();
299
300   my ($self, $msg) = @_;
301
302   if ($self->{__ERROR_HANDLER}) {
303     $self->{__ERROR_HANDLER}->($msg);
304
305   } elsif ($ENV{HTTP_USER_AGENT}) {
306     $msg =~ s/\n/<br>/g;
307     $self->show_generic_error($msg);
308
309   } else {
310     print STDERR "Error: $msg\n";
311     ::end_of_request();
312   }
313
314   $main::lxdebug->leave_sub();
315 }
316
317 sub info {
318   $main::lxdebug->enter_sub();
319
320   my ($self, $msg) = @_;
321
322   if ($ENV{HTTP_USER_AGENT}) {
323     $self->header;
324     print $self->parse_html_template('generic/form_info', { message => $msg });
325
326   } elsif ($self->{info_function}) {
327     &{ $self->{info_function} }($msg);
328   } else {
329     print "$msg\n";
330   }
331
332   $main::lxdebug->leave_sub();
333 }
334
335 # calculates the number of rows in a textarea based on the content and column number
336 # can be capped with maxrows
337 sub numtextrows {
338   $main::lxdebug->enter_sub();
339   my ($self, $str, $cols, $maxrows, $minrows) = @_;
340
341   $minrows ||= 1;
342
343   my $rows   = sum map { int((length() - 2) / $cols) + 1 } split /\r/, $str;
344   $maxrows ||= $rows;
345
346   $main::lxdebug->leave_sub();
347
348   return max(min($rows, $maxrows), $minrows);
349 }
350
351 sub dberror {
352   $main::lxdebug->enter_sub();
353
354   my ($self, $msg) = @_;
355
356   $self->error("$msg\n" . $DBI::errstr);
357
358   $main::lxdebug->leave_sub();
359 }
360
361 sub isblank {
362   $main::lxdebug->enter_sub();
363
364   my ($self, $name, $msg) = @_;
365
366   my $curr = $self;
367   foreach my $part (split m/\./, $name) {
368     if (!$curr->{$part} || ($curr->{$part} =~ /^\s*$/)) {
369       $self->error($msg);
370     }
371     $curr = $curr->{$part};
372   }
373
374   $main::lxdebug->leave_sub();
375 }
376
377 sub _get_request_uri {
378   my $self = shift;
379
380   return URI->new($ENV{HTTP_REFERER})->canonical() if $ENV{HTTP_X_FORWARDED_FOR};
381   return URI->new                                  if !$ENV{REQUEST_URI}; # for testing
382
383   my $scheme =  $ENV{HTTPS} && (lc $ENV{HTTPS} eq 'on') ? 'https' : 'http';
384   my $port   =  $ENV{SERVER_PORT};
385   $port      =  undef if (($scheme eq 'http' ) && ($port == 80))
386                       || (($scheme eq 'https') && ($port == 443));
387
388   my $uri    =  URI->new("${scheme}://");
389   $uri->scheme($scheme);
390   $uri->port($port);
391   $uri->host($ENV{HTTP_HOST} || $ENV{SERVER_ADDR});
392   $uri->path_query($ENV{REQUEST_URI});
393   $uri->query('');
394
395   return $uri;
396 }
397
398 sub _add_to_request_uri {
399   my $self              = shift;
400
401   my $relative_new_path = shift;
402   my $request_uri       = shift || $self->_get_request_uri;
403   my $relative_new_uri  = URI->new($relative_new_path);
404   my @request_segments  = $request_uri->path_segments;
405
406   my $new_uri           = $request_uri->clone;
407   $new_uri->path_segments(@request_segments[0..scalar(@request_segments) - 2], $relative_new_uri->path_segments);
408
409   return $new_uri;
410 }
411
412 sub create_http_response {
413   $main::lxdebug->enter_sub();
414
415   my $self     = shift;
416   my %params   = @_;
417
418   my $cgi      = $::request->{cgi};
419
420   my $session_cookie;
421   if (defined $main::auth) {
422     my $uri      = $self->_get_request_uri;
423     my @segments = $uri->path_segments;
424     pop @segments;
425     $uri->path_segments(@segments);
426
427     my $session_cookie_value = $main::auth->get_session_id();
428
429     if ($session_cookie_value) {
430       $session_cookie = $cgi->cookie('-name'   => $main::auth->get_session_cookie_name(),
431                                      '-value'  => $session_cookie_value,
432                                      '-path'   => $uri->path,
433                                      '-secure' => $ENV{HTTPS});
434     }
435   }
436
437   my %cgi_params = ('-type' => $params{content_type});
438   $cgi_params{'-charset'} = $params{charset} if ($params{charset});
439   $cgi_params{'-cookie'}  = $session_cookie  if ($session_cookie);
440
441   map { $cgi_params{'-' . $_} = $params{$_} if exists $params{$_} } qw(content_disposition content_length);
442
443   my $output = $cgi->header(%cgi_params);
444
445   $main::lxdebug->leave_sub();
446
447   return $output;
448 }
449
450 sub header {
451   $::lxdebug->enter_sub;
452
453   my ($self, %params) = @_;
454   my @header;
455
456   $::lxdebug->leave_sub and return if !$ENV{HTTP_USER_AGENT} || $self->{header}++;
457
458   if ($params{no_layout}) {
459     $::request->{layout} = SL::Layout::Dispatcher->new(style => 'none');
460   }
461
462   my $layout = $::request->{layout};
463
464   # standard css for all
465   # this should gradually move to the layouts that need it
466   $layout->use_stylesheet("$_.css") for qw(
467     main menu list_accounts jquery.autocomplete
468     jquery.multiselect2side
469     ui-lightness/jquery-ui
470     jquery-ui.custom
471   );
472
473   $layout->use_javascript("$_.js") for (qw(
474     jquery jquery-ui jquery.cookie jquery.checkall jquery.download
475     jquery/jquery.form client_js
476     common part_selection switchmenuframe autocomplete_part
477   ), "jquery/ui/i18n/jquery.ui.datepicker-$::myconfig{countrycode}");
478
479   $self->{favicon} ||= "favicon.ico";
480   $self->{titlebar} = join ' - ', grep $_, $self->{title}, $self->{login}, $::myconfig{dbname}, $self->{version} if $self->{title} || !$self->{titlebar};
481
482   # build includes
483   if ($self->{refresh_url} || $self->{refresh_time}) {
484     my $refresh_time = $self->{refresh_time} || 3;
485     my $refresh_url  = $self->{refresh_url}  || $ENV{REFERER};
486     push @header, "<meta http-equiv='refresh' content='$refresh_time;$refresh_url'>";
487   }
488
489   my $auto_reload_resources_param = $layout->auto_reload_resources_param;
490
491   push @header, map { qq|<link rel="stylesheet" href="${_}${auto_reload_resources_param}" type="text/css" title="Stylesheet">| } $layout->stylesheets;
492   push @header, "<style type='text/css'>\@page { size:landscape; }</style> "                     if $self->{landscape};
493   push @header, "<link rel='shortcut icon' href='$self->{favicon}' type='image/x-icon'>"         if -f $self->{favicon};
494   push @header, map { qq|<script type="text/javascript" src="${_}${auto_reload_resources_param}"></script>| }                    $layout->javascripts;
495   push @header, $self->{javascript} if $self->{javascript};
496   push @header, map { $_->show_javascript } @{ $self->{AJAX} || [] };
497
498   my  %doctypes = (
499     strict       => qq|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">|,
500     transitional => qq|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">|,
501     frameset     => qq|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">|,
502     html5        => qq|<!DOCTYPE html>|,
503   );
504
505   # output
506   print $self->create_http_response(content_type => 'text/html', charset => 'UTF-8');
507   print $doctypes{$params{doctype} || 'transitional'}, $/;
508   print <<EOT;
509 <html>
510  <head>
511   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
512   <title>$self->{titlebar}</title>
513 EOT
514   print "  $_\n" for @header;
515   print <<EOT;
516   <meta name="robots" content="noindex,nofollow">
517  </head>
518  <body>
519
520 EOT
521   print $::request->{layout}->pre_content;
522   print $::request->{layout}->start_content;
523
524   $layout->header_done;
525
526   $::lxdebug->leave_sub;
527 }
528
529 sub footer {
530   return unless $::request->{layout}->need_footer;
531
532   print $::request->{layout}->end_content;
533   print $::request->{layout}->post_content;
534
535   if (my @inline_scripts = $::request->{layout}->javascripts_inline) {
536     print "<script type='text/javascript'>@inline_scripts</script>\n";
537   }
538
539   print <<EOL
540  </body>
541 </html>
542 EOL
543 }
544
545 sub ajax_response_header {
546   $main::lxdebug->enter_sub();
547
548   my ($self) = @_;
549
550   my $output = $::request->{cgi}->header('-charset' => 'UTF-8');
551
552   $main::lxdebug->leave_sub();
553
554   return $output;
555 }
556
557 sub redirect_header {
558   my $self     = shift;
559   my $new_url  = shift;
560
561   my $base_uri = $self->_get_request_uri;
562   my $new_uri  = URI->new_abs($new_url, $base_uri);
563
564   die "Headers already sent" if $self->{header};
565   $self->{header} = 1;
566
567   return $::request->{cgi}->redirect($new_uri);
568 }
569
570 sub set_standard_title {
571   $::lxdebug->enter_sub;
572   my $self = shift;
573
574   $self->{titlebar}  = "kivitendo " . $::locale->text('Version') . " $self->{version}";
575   $self->{titlebar} .= "- $::myconfig{name}"   if $::myconfig{name};
576   $self->{titlebar} .= "- $::myconfig{dbname}" if $::myconfig{name};
577
578   $::lxdebug->leave_sub;
579 }
580
581 sub _prepare_html_template {
582   $main::lxdebug->enter_sub();
583
584   my ($self, $file, $additional_params) = @_;
585   my $language;
586
587   if (!%::myconfig || !$::myconfig{"countrycode"}) {
588     $language = $::lx_office_conf{system}->{language};
589   } else {
590     $language = $main::myconfig{"countrycode"};
591   }
592   $language = "de" unless ($language);
593
594   if (-f "templates/webpages/${file}.html") {
595     $file = "templates/webpages/${file}.html";
596
597   } else {
598     my $info = "Web page template '${file}' not found.\n";
599     print qq|<pre>$info</pre>|;
600     ::end_of_request();
601   }
602
603   if ($self->{"DEBUG"}) {
604     $additional_params->{"DEBUG"} = $self->{"DEBUG"};
605   }
606
607   if ($additional_params->{"DEBUG"}) {
608     $additional_params->{"DEBUG"} =
609       "<br><em>DEBUG INFORMATION:</em><pre>" . $additional_params->{"DEBUG"} . "</pre>";
610   }
611
612   if (%main::myconfig) {
613     $::myconfig{jsc_dateformat} = apply {
614       s/d+/\%d/gi;
615       s/m+/\%m/gi;
616       s/y+/\%Y/gi;
617     } $::myconfig{"dateformat"};
618     $additional_params->{"myconfig"} ||= \%::myconfig;
619     map { $additional_params->{"myconfig_${_}"} = $main::myconfig{$_}; } keys %::myconfig;
620   }
621
622   $additional_params->{INSTANCE_CONF} = $::instance_conf;
623
624   if (my $debug_options = $::lx_office_conf{debug}{options}) {
625     map { $additional_params->{'DEBUG_' . uc($_)} = $debug_options->{$_} } keys %$debug_options;
626   }
627
628   if ($main::auth && $main::auth->{RIGHTS} && $main::auth->{RIGHTS}->{$self->{login}}) {
629     while (my ($key, $value) = each %{ $main::auth->{RIGHTS}->{$self->{login}} }) {
630       $additional_params->{"AUTH_RIGHTS_" . uc($key)} = $value;
631     }
632   }
633
634   $main::lxdebug->leave_sub();
635
636   return $file;
637 }
638
639 sub parse_html_template {
640   $main::lxdebug->enter_sub();
641
642   my ($self, $file, $additional_params) = @_;
643
644   $additional_params ||= { };
645
646   my $real_file = $self->_prepare_html_template($file, $additional_params);
647   my $template  = $self->template || $self->init_template;
648
649   map { $additional_params->{$_} ||= $self->{$_} } keys %{ $self };
650
651   my $output;
652   $template->process($real_file, $additional_params, \$output) || die $template->error;
653
654   $main::lxdebug->leave_sub();
655
656   return $output;
657 }
658
659 sub init_template {
660   my $self = shift;
661
662   return $self->template if $self->template;
663
664   # Force scripts/locales.pl to pick up the exception handling template.
665   # parse_html_template('generic/exception')
666   return $self->template(Template->new({
667      'INTERPOLATE'  => 0,
668      'EVAL_PERL'    => 0,
669      'ABSOLUTE'     => 1,
670      'CACHE_SIZE'   => 0,
671      'PLUGIN_BASE'  => 'SL::Template::Plugin',
672      'INCLUDE_PATH' => '.:templates/webpages',
673      'COMPILE_EXT'  => '.tcc',
674      'COMPILE_DIR'  => $::lx_office_conf{paths}->{userspath} . '/templates-cache',
675      'ERROR'        => 'templates/webpages/generic/exception.html',
676   })) || die;
677 }
678
679 sub template {
680   my $self = shift;
681   $self->{template_object} = shift if @_;
682   return $self->{template_object};
683 }
684
685 sub show_generic_error {
686   $main::lxdebug->enter_sub();
687
688   my ($self, $error, %params) = @_;
689
690   if ($self->{__ERROR_HANDLER}) {
691     $self->{__ERROR_HANDLER}->($error);
692     $main::lxdebug->leave_sub();
693     return;
694   }
695
696   if ($::request->is_ajax) {
697     $::lxdebug->message(0, "trying to render AJAX response...");
698     SL::ClientJS->new
699       ->error($error)
700       ->render(SL::Controller::Base->new);
701     ::end_of_request();
702   }
703
704   my $add_params = {
705     'title_error' => $params{title},
706     'label_error' => $error,
707   };
708
709   if ($params{action}) {
710     my @vars;
711
712     map { delete($self->{$_}); } qw(action);
713     map { push @vars, { "name" => $_, "value" => $self->{$_} } if (!ref($self->{$_})); } keys %{ $self };
714
715     $add_params->{SHOW_BUTTON}  = 1;
716     $add_params->{BUTTON_LABEL} = $params{label} || $params{action};
717     $add_params->{VARIABLES}    = \@vars;
718
719   } elsif ($params{back_button}) {
720     $add_params->{SHOW_BACK_BUTTON} = 1;
721   }
722
723   $self->{title} = $params{title} if $params{title};
724
725   $self->header();
726   print $self->parse_html_template("generic/error", $add_params);
727
728   print STDERR "Error: $error\n";
729
730   $main::lxdebug->leave_sub();
731
732   ::end_of_request();
733 }
734
735 sub show_generic_information {
736   $main::lxdebug->enter_sub();
737
738   my ($self, $text, $title) = @_;
739
740   my $add_params = {
741     'title_information' => $title,
742     'label_information' => $text,
743   };
744
745   $self->{title} = $title if ($title);
746
747   $self->header();
748   print $self->parse_html_template("generic/information", $add_params);
749
750   $main::lxdebug->leave_sub();
751
752   ::end_of_request();
753 }
754
755 sub _store_redirect_info_in_session {
756   my ($self) = @_;
757
758   return unless $self->{callback} =~ m:^ ( [^\?/]+ \.pl ) \? (.+) :x;
759
760   my ($controller, $params) = ($1, $2);
761   my $form                  = { map { map { $self->unescape($_) } split /=/, $_, 2 } split m/\&/, $params };
762   $self->{callback}         = "${controller}?RESTORE_FORM_FROM_SESSION_ID=" . $::auth->save_form_in_session(form => $form);
763 }
764
765 sub redirect {
766   $main::lxdebug->enter_sub();
767
768   my ($self, $msg) = @_;
769
770   if (!$self->{callback}) {
771     $self->info($msg);
772
773   } else {
774     $self->_store_redirect_info_in_session;
775     print $::form->redirect_header($self->{callback});
776   }
777
778   ::end_of_request();
779
780   $main::lxdebug->leave_sub();
781 }
782
783 # sort of columns removed - empty sub
784 sub sort_columns {
785   $main::lxdebug->enter_sub();
786
787   my ($self, @columns) = @_;
788
789   $main::lxdebug->leave_sub();
790
791   return @columns;
792 }
793 #
794 sub format_amount {
795   $main::lxdebug->enter_sub(2);
796
797   my ($self, $myconfig, $amount, $places, $dash) = @_;
798   $amount ||= 0;
799   $dash   ||= '';
800   my $neg = $amount < 0;
801   my $force_places = defined $places && $places >= 0;
802
803   $amount = $self->round_amount($amount, abs $places) if $force_places;
804   $amount = sprintf "%.*f", ($force_places ? $places : 10), abs $amount; # 6 is default for %fa
805
806   # before the sprintf amount was a number, afterwards it's a string. because of the dynamic nature of perl
807   # this is easy to confuse, so keep in mind: before this comment no s///, m//, concat or other strong ops on
808   # $amount. after this comment no +,-,*,/,abs. it will only introduce subtle bugs.
809
810   $amount =~ s/0*$// unless defined $places && $places == 0;             # cull trailing 0s
811
812   my @d = map { s/\d//g; reverse split // } my $tmp = $myconfig->{numberformat}; # get delim chars
813   my @p = split(/\./, $amount);                                          # split amount at decimal point
814
815   $p[0] =~ s/\B(?=(...)*$)/$d[1]/g if $d[1];                             # add 1,000 delimiters
816   $amount = $p[0];
817   if ($places || $p[1]) {
818     $amount .= $d[0]
819             .  ( $p[1] || '' )
820             .  (0 x (abs($places || 0) - length ($p[1]||'')));           # pad the fraction
821   }
822
823   $amount = do {
824     ($dash =~ /-/)    ? ($neg ? "($amount)"                            : "$amount" )                              :
825     ($dash =~ /DRCR/) ? ($neg ? "$amount " . $main::locale->text('DR') : "$amount " . $main::locale->text('CR') ) :
826                         ($neg ? "-$amount"                             : "$amount" )                              ;
827   };
828
829   $main::lxdebug->leave_sub(2);
830   return $amount;
831 }
832
833 sub format_amount_units {
834   $main::lxdebug->enter_sub();
835
836   my $self             = shift;
837   my %params           = @_;
838
839   my $myconfig         = \%main::myconfig;
840   my $amount           = $params{amount} * 1;
841   my $places           = $params{places};
842   my $part_unit_name   = $params{part_unit};
843   my $amount_unit_name = $params{amount_unit};
844   my $conv_units       = $params{conv_units};
845   my $max_places       = $params{max_places};
846
847   if (!$part_unit_name) {
848     $main::lxdebug->leave_sub();
849     return '';
850   }
851
852   my $all_units        = AM->retrieve_all_units;
853
854   if (('' eq ref $conv_units) && ($conv_units =~ /convertible/)) {
855     $conv_units = AM->convertible_units($all_units, $part_unit_name, $conv_units eq 'convertible_not_smaller');
856   }
857
858   if (!scalar @{ $conv_units }) {
859     my $result = $self->format_amount($myconfig, $amount, $places, undef, $max_places) . " " . $part_unit_name;
860     $main::lxdebug->leave_sub();
861     return $result;
862   }
863
864   my $part_unit  = $all_units->{$part_unit_name};
865   my $conv_unit  = ($amount_unit_name && ($amount_unit_name ne $part_unit_name)) ? $all_units->{$amount_unit_name} : $part_unit;
866
867   $amount       *= $conv_unit->{factor};
868
869   my @values;
870   my $num;
871
872   foreach my $unit (@$conv_units) {
873     my $last = $unit->{name} eq $part_unit->{name};
874     if (!$last) {
875       $num     = int($amount / $unit->{factor});
876       $amount -= $num * $unit->{factor};
877     }
878
879     if ($last ? $amount : $num) {
880       push @values, { "unit"   => $unit->{name},
881                       "amount" => $last ? $amount / $unit->{factor} : $num,
882                       "places" => $last ? $places : 0 };
883     }
884
885     last if $last;
886   }
887
888   if (!@values) {
889     push @values, { "unit"   => $part_unit_name,
890                     "amount" => 0,
891                     "places" => 0 };
892   }
893
894   my $result = join " ", map { $self->format_amount($myconfig, $_->{amount}, $_->{places}, undef, $max_places), $_->{unit} } @values;
895
896   $main::lxdebug->leave_sub();
897
898   return $result;
899 }
900
901 sub format_string {
902   $main::lxdebug->enter_sub(2);
903
904   my $self  = shift;
905   my $input = shift;
906
907   $input =~ s/(^|[^\#]) \#  (\d+)  /$1$_[$2 - 1]/gx;
908   $input =~ s/(^|[^\#]) \#\{(\d+)\}/$1$_[$2 - 1]/gx;
909   $input =~ s/\#\#/\#/g;
910
911   $main::lxdebug->leave_sub(2);
912
913   return $input;
914 }
915
916 #
917
918 sub parse_amount {
919   $main::lxdebug->enter_sub(2);
920
921   my ($self, $myconfig, $amount) = @_;
922
923   if (!defined($amount) || ($amount eq '')) {
924     $main::lxdebug->leave_sub(2);
925     return 0;
926   }
927
928   if (   ($myconfig->{numberformat} eq '1.000,00')
929       || ($myconfig->{numberformat} eq '1000,00')) {
930     $amount =~ s/\.//g;
931     $amount =~ s/,/\./g;
932   }
933
934   if ($myconfig->{numberformat} eq "1'000.00") {
935     $amount =~ s/\'//g;
936   }
937
938   $amount =~ s/,//g;
939
940   $main::lxdebug->leave_sub(2);
941
942   # Make sure no code wich is not a math expression ends up in eval().
943   return 0 unless $amount =~ /^ [\s \d \( \) \- \+ \* \/ \. ]* $/x;
944   return scalar(eval($amount)) * 1 ;
945 }
946
947 sub round_amount {
948   $main::lxdebug->enter_sub(2);
949
950   my ($self, $amount, $places) = @_;
951   my $round_amount;
952
953   # Rounding like "Kaufmannsrunden" (see http://de.wikipedia.org/wiki/Rundung )
954
955   # Round amounts to eight places before rounding to the requested
956   # number of places. This gets rid of errors due to internal floating
957   # point representation.
958   $amount       = $self->round_amount($amount, 8) if $places < 8;
959   $amount       = $amount * (10**($places));
960   $round_amount = int($amount + .5 * ($amount <=> 0)) / (10**($places));
961
962   $main::lxdebug->leave_sub(2);
963
964   return $round_amount;
965
966 }
967
968 sub parse_template {
969   $main::lxdebug->enter_sub();
970
971   my ($self, $myconfig) = @_;
972   my ($out, $out_mode);
973
974   local (*IN, *OUT);
975
976   my $defaults  = SL::DB::Default->get;
977   my $userspath = $::lx_office_conf{paths}->{userspath};
978
979   $self->{"cwd"} = getcwd();
980   $self->{"tmpdir"} = $self->{cwd} . "/${userspath}";
981
982   my $ext_for_format;
983
984   my $template_type;
985   if ($self->{"format"} =~ /(opendocument|oasis)/i) {
986     $template_type  = 'OpenDocument';
987     $ext_for_format = $self->{"format"} =~ m/pdf/ ? 'pdf' : 'odt';
988
989   } elsif ($self->{"format"} =~ /(postscript|pdf)/i) {
990     $template_type    = 'LaTeX';
991     $ext_for_format   = 'pdf';
992
993   } elsif (($self->{"format"} =~ /html/i) || (!$self->{"format"} && ($self->{"IN"} =~ /html$/i))) {
994     $template_type  = 'HTML';
995     $ext_for_format = 'html';
996
997   } elsif (($self->{"format"} =~ /xml/i) || (!$self->{"format"} && ($self->{"IN"} =~ /xml$/i))) {
998     $template_type  = 'XML';
999     $ext_for_format = 'xml';
1000
1001   } elsif ( $self->{"format"} =~ /elster(?:winston|taxbird)/i ) {
1002     $template_type = 'XML';
1003
1004   } elsif ( $self->{"format"} =~ /excel/i ) {
1005     $template_type  = 'Excel';
1006     $ext_for_format = 'xls';
1007
1008   } elsif ( defined $self->{'format'}) {
1009     $self->error("Outputformat not defined. This may be a future feature: $self->{'format'}");
1010
1011   } elsif ( $self->{'format'} eq '' ) {
1012     $self->error("No Outputformat given: $self->{'format'}");
1013
1014   } else { #Catch the rest
1015     $self->error("Outputformat not defined: $self->{'format'}");
1016   }
1017
1018   my $template = SL::Template::create(type      => $template_type,
1019                                       file_name => $self->{IN},
1020                                       form      => $self,
1021                                       myconfig  => $myconfig,
1022                                       userspath => $userspath);
1023
1024   # Copy the notes from the invoice/sales order etc. back to the variable "notes" because that is where most templates expect it to be.
1025   $self->{"notes"} = $self->{ $self->{"formname"} . "notes" };
1026
1027   if (!$self->{employee_id}) {
1028     $self->{"employee_${_}"} = $myconfig->{$_} for qw(email tel fax name signature);
1029     $self->{"employee_${_}"} = $defaults->$_   for qw(address businessnumber co_ustid company duns sepa_creditor_id taxnumber);
1030   }
1031
1032   $self->{"myconfig_${_}"} = $myconfig->{$_} for grep { $_ ne 'dbpasswd' } keys %{ $myconfig };
1033   $self->{$_}              = $defaults->$_   for qw(co_ustid);
1034   $self->{"myconfig_${_}"} = $defaults->$_   for qw(address businessnumber co_ustid company duns sepa_creditor_id taxnumber);
1035
1036   $self->{copies} = 1 if (($self->{copies} *= 1) <= 0);
1037
1038   # OUT is used for the media, screen, printer, email
1039   # for postscript we store a copy in a temporary file
1040   my ($temp_fh, $suffix);
1041   $suffix =  $self->{IN};
1042   $suffix =~ s/.*\.//;
1043   ($temp_fh, $self->{tmpfile}) = File::Temp::tempfile(
1044     'kivitendo-printXXXXXX',
1045     SUFFIX => '.' . ($suffix || 'tex'),
1046     DIR    => $userspath,
1047     UNLINK => ($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})? 0 : 1,
1048   );
1049   close $temp_fh;
1050   (undef, undef, $self->{template_meta}{tmpfile}) = File::Spec->splitpath( $self->{tmpfile} );
1051
1052   $out              = $self->{OUT};
1053   $out_mode         = $self->{OUT_MODE} || '>';
1054   $self->{OUT}      = "$self->{tmpfile}";
1055   $self->{OUT_MODE} = '>';
1056
1057   my $result;
1058   my $command_formatter = sub {
1059     my ($out_mode, $out) = @_;
1060     return $out_mode eq '|-' ? SL::Template::create(type => 'ShellCommand', form => $self)->parse($out) : $out;
1061   };
1062
1063   if ($self->{OUT}) {
1064     $self->{OUT} = $command_formatter->($self->{OUT_MODE}, $self->{OUT});
1065     open(OUT, $self->{OUT_MODE}, $self->{OUT}) or $self->error("error on opening $self->{OUT} with mode $self->{OUT_MODE} : $!");
1066   } else {
1067     *OUT = ($::dispatcher->get_standard_filehandles)[1];
1068     $self->header;
1069   }
1070
1071   if (!$template->parse(*OUT)) {
1072     $self->cleanup();
1073     $self->error("$self->{IN} : " . $template->get_error());
1074   }
1075
1076   close OUT if $self->{OUT};
1077
1078   my $copy_to_webdav = $::instance_conf->get_webdav && $::instance_conf->get_webdav_documents && !$self->{preview};
1079
1080   if ($self->{media} eq 'file') {
1081     copy(join('/', $self->{cwd}, $userspath, $self->{tmpfile}), $out =~ m|^/| ? $out : join('/', $self->{cwd}, $out)) if $template->uses_temp_file;
1082     Common::copy_file_to_webdav_folder($self)                                                                         if $copy_to_webdav;
1083     $self->cleanup;
1084     chdir("$self->{cwd}");
1085
1086     $::lxdebug->leave_sub();
1087
1088     return;
1089   }
1090
1091   Common::copy_file_to_webdav_folder($self) if $copy_to_webdav;
1092
1093   if ($self->{media} eq 'email') {
1094
1095     my $mail = new Mailer;
1096
1097     map { $mail->{$_} = $self->{$_} }
1098       qw(cc bcc subject message version format);
1099     $mail->{to} = $self->{EMAIL_RECIPIENT} ? $self->{EMAIL_RECIPIENT} : $self->{email};
1100     $mail->{from}   = qq|"$myconfig->{name}" <$myconfig->{email}>|;
1101     $mail->{fileid} = time() . '.' . $$ . '.';
1102     $myconfig->{signature} =~ s/\r//g;
1103
1104     # if we send html or plain text inline
1105     if (($self->{format} eq 'html') && ($self->{sendmode} eq 'inline')) {
1106       $mail->{contenttype}    =  "text/html";
1107       $mail->{message}        =~ s/\r//g;
1108       $mail->{message}        =~ s/\n/<br>\n/g;
1109       $myconfig->{signature}  =~ s/\n/<br>\n/g;
1110       $mail->{message}       .=  "<br>\n-- <br>\n$myconfig->{signature}\n<br>";
1111
1112       open(IN, "<", $self->{tmpfile})
1113         or $self->error($self->cleanup . "$self->{tmpfile} : $!");
1114       $mail->{message} .= $_ while <IN>;
1115       close(IN);
1116
1117     } else {
1118
1119       if (!$self->{"do_not_attach"}) {
1120         my $attachment_name  =  $self->{attachment_filename} || $self->{tmpfile};
1121         $attachment_name     =~ s/\.(.+?)$/.${ext_for_format}/ if ($ext_for_format);
1122         $mail->{attachments} =  [{ "filename" => $self->{tmpfile},
1123                                    "name"     => $attachment_name }];
1124       }
1125
1126       $mail->{message}  =~ s/\r//g;
1127       $mail->{message} .=  "\n-- \n$myconfig->{signature}";
1128
1129     }
1130
1131     my $err = $mail->send();
1132     $self->error($self->cleanup . "$err") if ($err);
1133
1134   } else {
1135
1136     $self->{OUT}      = $out;
1137     $self->{OUT_MODE} = $out_mode;
1138
1139     my $numbytes = (-s $self->{tmpfile});
1140     open(IN, "<", $self->{tmpfile})
1141       or $self->error($self->cleanup . "$self->{tmpfile} : $!");
1142     binmode IN;
1143
1144     $self->{copies} = 1 unless $self->{media} eq 'printer';
1145
1146     chdir("$self->{cwd}");
1147     #print(STDERR "Kopien $self->{copies}\n");
1148     #print(STDERR "OUT $self->{OUT}\n");
1149     for my $i (1 .. $self->{copies}) {
1150       if ($self->{OUT}) {
1151         $self->{OUT} = $command_formatter->($self->{OUT_MODE}, $self->{OUT});
1152
1153         open  OUT, $self->{OUT_MODE}, $self->{OUT} or $self->error($self->cleanup . "$self->{OUT} : $!");
1154         print OUT $_ while <IN>;
1155         close OUT;
1156         seek  IN, 0, 0;
1157
1158       } else {
1159         $self->{attachment_filename} = ($self->{attachment_filename})
1160                                      ? $self->{attachment_filename}
1161                                      : $self->generate_attachment_filename();
1162
1163         # launch application
1164         print qq|Content-Type: | . $template->get_mime_type() . qq|
1165 Content-Disposition: attachment; filename="$self->{attachment_filename}"
1166 Content-Length: $numbytes
1167
1168 |;
1169
1170         $::locale->with_raw_io(\*STDOUT, sub { print while <IN> });
1171       }
1172     }
1173
1174     close(IN);
1175   }
1176
1177   $self->cleanup;
1178
1179   chdir("$self->{cwd}");
1180   $main::lxdebug->leave_sub();
1181 }
1182
1183 sub get_formname_translation {
1184   $main::lxdebug->enter_sub();
1185   my ($self, $formname) = @_;
1186
1187   $formname ||= $self->{formname};
1188
1189   $self->{recipient_locale} ||=  Locale->lang_to_locale($self->{language});
1190   local $::locale = Locale->new($self->{recipient_locale});
1191
1192   my %formname_translations = (
1193     bin_list                => $main::locale->text('Bin List'),
1194     credit_note             => $main::locale->text('Credit Note'),
1195     invoice                 => $main::locale->text('Invoice'),
1196     pick_list               => $main::locale->text('Pick List'),
1197     proforma                => $main::locale->text('Proforma Invoice'),
1198     purchase_order          => $main::locale->text('Purchase Order'),
1199     request_quotation       => $main::locale->text('RFQ'),
1200     sales_order             => $main::locale->text('Confirmation'),
1201     sales_quotation         => $main::locale->text('Quotation'),
1202     storno_invoice          => $main::locale->text('Storno Invoice'),
1203     sales_delivery_order    => $main::locale->text('Delivery Order'),
1204     purchase_delivery_order => $main::locale->text('Delivery Order'),
1205     dunning                 => $main::locale->text('Dunning'),
1206   );
1207
1208   $main::lxdebug->leave_sub();
1209   return $formname_translations{$formname};
1210 }
1211
1212 sub get_number_prefix_for_type {
1213   $main::lxdebug->enter_sub();
1214   my ($self) = @_;
1215
1216   my $prefix =
1217       (first { $self->{type} eq $_ } qw(invoice credit_note)) ? 'inv'
1218     : ($self->{type} =~ /_quotation$/)                        ? 'quo'
1219     : ($self->{type} =~ /_delivery_order$/)                   ? 'do'
1220     :                                                           'ord';
1221
1222   $main::lxdebug->leave_sub();
1223   return $prefix;
1224 }
1225
1226 sub get_extension_for_format {
1227   $main::lxdebug->enter_sub();
1228   my ($self)    = @_;
1229
1230   my $extension = $self->{format} =~ /pdf/i          ? ".pdf"
1231                 : $self->{format} =~ /postscript/i   ? ".ps"
1232                 : $self->{format} =~ /opendocument/i ? ".odt"
1233                 : $self->{format} =~ /excel/i        ? ".xls"
1234                 : $self->{format} =~ /html/i         ? ".html"
1235                 :                                      "";
1236
1237   $main::lxdebug->leave_sub();
1238   return $extension;
1239 }
1240
1241 sub generate_attachment_filename {
1242   $main::lxdebug->enter_sub();
1243   my ($self) = @_;
1244
1245   $self->{recipient_locale} ||=  Locale->lang_to_locale($self->{language});
1246   my $recipient_locale = Locale->new($self->{recipient_locale});
1247
1248   my $attachment_filename = $main::locale->unquote_special_chars('HTML', $self->get_formname_translation());
1249   my $prefix              = $self->get_number_prefix_for_type();
1250
1251   if ($self->{preview} && (first { $self->{type} eq $_ } qw(invoice credit_note))) {
1252     $attachment_filename .= ' (' . $recipient_locale->text('Preview') . ')' . $self->get_extension_for_format();
1253
1254   } elsif ($attachment_filename && $self->{"${prefix}number"}) {
1255     $attachment_filename .=  "_" . $self->{"${prefix}number"} . $self->get_extension_for_format();
1256
1257   } else {
1258     $attachment_filename = "";
1259   }
1260
1261   $attachment_filename =  $main::locale->quote_special_chars('filenames', $attachment_filename);
1262   $attachment_filename =~ s|[\s/\\]+|_|g;
1263
1264   $main::lxdebug->leave_sub();
1265   return $attachment_filename;
1266 }
1267
1268 sub generate_email_subject {
1269   $main::lxdebug->enter_sub();
1270   my ($self) = @_;
1271
1272   my $subject = $main::locale->unquote_special_chars('HTML', $self->get_formname_translation());
1273   my $prefix  = $self->get_number_prefix_for_type();
1274
1275   if ($subject && $self->{"${prefix}number"}) {
1276     $subject .= " " . $self->{"${prefix}number"}
1277   }
1278
1279   $main::lxdebug->leave_sub();
1280   return $subject;
1281 }
1282
1283 sub cleanup {
1284   $main::lxdebug->enter_sub();
1285
1286   my ($self, $application) = @_;
1287
1288   my $error_code = $?;
1289
1290   chdir("$self->{tmpdir}");
1291
1292   my @err = ();
1293   if ((-1 == $error_code) || (127 == (($error_code) >> 8))) {
1294     push @err, $::locale->text('The application "#1" was not found on the system.', $application || 'pdflatex') . ' ' . $::locale->text('Please contact your administrator.');
1295
1296   } elsif (-f "$self->{tmpfile}.err") {
1297     open(FH, "$self->{tmpfile}.err");
1298     @err = <FH>;
1299     close(FH);
1300   }
1301
1302   if ($self->{tmpfile} && !($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})) {
1303     $self->{tmpfile} =~ s|.*/||g;
1304     # strip extension
1305     $self->{tmpfile} =~ s/\.\w+$//g;
1306     my $tmpfile = $self->{tmpfile};
1307     unlink(<$tmpfile.*>);
1308   }
1309
1310   chdir("$self->{cwd}");
1311
1312   $main::lxdebug->leave_sub();
1313
1314   return "@err";
1315 }
1316
1317 sub datetonum {
1318   $main::lxdebug->enter_sub();
1319
1320   my ($self, $date, $myconfig) = @_;
1321   my ($yy, $mm, $dd);
1322
1323   if ($date && $date =~ /\D/) {
1324
1325     if ($myconfig->{dateformat} =~ /^yy/) {
1326       ($yy, $mm, $dd) = split /\D/, $date;
1327     }
1328     if ($myconfig->{dateformat} =~ /^mm/) {
1329       ($mm, $dd, $yy) = split /\D/, $date;
1330     }
1331     if ($myconfig->{dateformat} =~ /^dd/) {
1332       ($dd, $mm, $yy) = split /\D/, $date;
1333     }
1334
1335     $dd *= 1;
1336     $mm *= 1;
1337     $yy = ($yy < 70) ? $yy + 2000 : $yy;
1338     $yy = ($yy >= 70 && $yy <= 99) ? $yy + 1900 : $yy;
1339
1340     $dd = "0$dd" if ($dd < 10);
1341     $mm = "0$mm" if ($mm < 10);
1342
1343     $date = "$yy$mm$dd";
1344   }
1345
1346   $main::lxdebug->leave_sub();
1347
1348   return $date;
1349 }
1350
1351 # Database routines used throughout
1352
1353 sub dbconnect {
1354   $main::lxdebug->enter_sub(2);
1355
1356   my ($self, $myconfig) = @_;
1357
1358   # connect to database
1359   my $dbh = SL::DBConnect->connect or $self->dberror;
1360
1361   # set db options
1362   if ($myconfig->{dboptions}) {
1363     $dbh->do($myconfig->{dboptions}) || $self->dberror($myconfig->{dboptions});
1364   }
1365
1366   $main::lxdebug->leave_sub(2);
1367
1368   return $dbh;
1369 }
1370
1371 sub dbconnect_noauto {
1372   $main::lxdebug->enter_sub();
1373
1374   my ($self, $myconfig) = @_;
1375
1376   # connect to database
1377   my $dbh = SL::DBConnect->connect(SL::DBConnect->get_connect_args(AutoCommit => 0)) or $self->dberror;
1378
1379   # set db options
1380   if ($myconfig->{dboptions}) {
1381     $dbh->do($myconfig->{dboptions}) || $self->dberror($myconfig->{dboptions});
1382   }
1383
1384   $main::lxdebug->leave_sub();
1385
1386   return $dbh;
1387 }
1388
1389 sub get_standard_dbh {
1390   $main::lxdebug->enter_sub(2);
1391
1392   my $self     = shift;
1393   my $myconfig = shift || \%::myconfig;
1394
1395   if ($standard_dbh && !$standard_dbh->{Active}) {
1396     $main::lxdebug->message(LXDebug->INFO(), "get_standard_dbh: \$standard_dbh is defined but not Active anymore");
1397     undef $standard_dbh;
1398   }
1399
1400   $standard_dbh ||= $self->dbconnect_noauto($myconfig);
1401
1402   $main::lxdebug->leave_sub(2);
1403
1404   return $standard_dbh;
1405 }
1406
1407 sub set_standard_dbh {
1408   my ($self, $dbh) = @_;
1409   my $old_dbh      = $standard_dbh;
1410   $standard_dbh    = $dbh;
1411
1412   return $old_dbh;
1413 }
1414
1415 sub date_closed {
1416   $main::lxdebug->enter_sub();
1417
1418   my ($self, $date, $myconfig) = @_;
1419   my $dbh = $self->dbconnect($myconfig);
1420
1421   my $query = "SELECT 1 FROM defaults WHERE ? < closedto";
1422   my $sth = prepare_execute_query($self, $dbh, $query, conv_date($date));
1423
1424   # Falls $date = '' - Fehlermeldung aus der Datenbank. Ich denke,
1425   # es ist sicher ein conv_date vorher IMMER auszuführen.
1426   # Testfälle ohne definiertes closedto:
1427   #   Leere Datumseingabe i.O.
1428   #     SELECT 1 FROM defaults WHERE '' < closedto
1429   #   normale Zahlungsbuchung Ã¼ber Rechnungsmaske i.O.
1430   #     SELECT 1 FROM defaults WHERE '10.05.2011' < closedto
1431   # Testfälle mit definiertem closedto (30.04.2011):
1432   #  Leere Datumseingabe i.O.
1433   #   SELECT 1 FROM defaults WHERE '' < closedto
1434   # normale Buchung im geschloßenem Zeitraum i.O.
1435   #   SELECT 1 FROM defaults WHERE '21.04.2011' < closedto
1436   #     Fehlermeldung: Es können keine Zahlungen für abgeschlossene Bücher gebucht werden!
1437   # normale Buchung in aktiver Buchungsperiode i.O.
1438   #   SELECT 1 FROM defaults WHERE '01.05.2011' < closedto
1439
1440   my ($closed) = $sth->fetchrow_array;
1441
1442   $main::lxdebug->leave_sub();
1443
1444   return $closed;
1445 }
1446
1447 # prevents bookings to the to far away future
1448 sub date_max_future {
1449   $main::lxdebug->enter_sub();
1450
1451   my ($self, $date, $myconfig) = @_;
1452   my $dbh = $self->dbconnect($myconfig);
1453
1454   my $query = "SELECT 1 FROM defaults WHERE ? - current_date > max_future_booking_interval";
1455   my $sth = prepare_execute_query($self, $dbh, $query, conv_date($date));
1456
1457   my ($max_future_booking_interval) = $sth->fetchrow_array;
1458
1459   $main::lxdebug->leave_sub();
1460
1461   return $max_future_booking_interval;
1462 }
1463
1464
1465 sub update_balance {
1466   $main::lxdebug->enter_sub();
1467
1468   my ($self, $dbh, $table, $field, $where, $value, @values) = @_;
1469
1470   # if we have a value, go do it
1471   if ($value != 0) {
1472
1473     # retrieve balance from table
1474     my $query = "SELECT $field FROM $table WHERE $where FOR UPDATE";
1475     my $sth = prepare_execute_query($self, $dbh, $query, @values);
1476     my ($balance) = $sth->fetchrow_array;
1477     $sth->finish;
1478
1479     $balance += $value;
1480
1481     # update balance
1482     $query = "UPDATE $table SET $field = $balance WHERE $where";
1483     do_query($self, $dbh, $query, @values);
1484   }
1485   $main::lxdebug->leave_sub();
1486 }
1487
1488 sub update_exchangerate {
1489   $main::lxdebug->enter_sub();
1490
1491   my ($self, $dbh, $curr, $transdate, $buy, $sell) = @_;
1492   my ($query);
1493   # some sanity check for currency
1494   if ($curr eq '') {
1495     $main::lxdebug->leave_sub();
1496     return;
1497   }
1498   $query = qq|SELECT name AS curr FROM currencies WHERE id=(SELECT currency_id FROM defaults)|;
1499
1500   my ($defaultcurrency) = selectrow_query($self, $dbh, $query);
1501
1502   if ($curr eq $defaultcurrency) {
1503     $main::lxdebug->leave_sub();
1504     return;
1505   }
1506
1507   $query = qq|SELECT e.currency_id FROM exchangerate e
1508                  WHERE e.currency_id = (SELECT cu.id FROM currencies cu WHERE cu.name=?) AND e.transdate = ?
1509                  FOR UPDATE|;
1510   my $sth = prepare_execute_query($self, $dbh, $query, $curr, $transdate);
1511
1512   if ($buy == 0) {
1513     $buy = "";
1514   }
1515   if ($sell == 0) {
1516     $sell = "";
1517   }
1518
1519   $buy = conv_i($buy, "NULL");
1520   $sell = conv_i($sell, "NULL");
1521
1522   my $set;
1523   if ($buy != 0 && $sell != 0) {
1524     $set = "buy = $buy, sell = $sell";
1525   } elsif ($buy != 0) {
1526     $set = "buy = $buy";
1527   } elsif ($sell != 0) {
1528     $set = "sell = $sell";
1529   }
1530
1531   if ($sth->fetchrow_array) {
1532     $query = qq|UPDATE exchangerate
1533                 SET $set
1534                 WHERE currency_id = (SELECT id FROM currencies WHERE name = ?)
1535                 AND transdate = ?|;
1536
1537   } else {
1538     $query = qq|INSERT INTO exchangerate (currency_id, buy, sell, transdate)
1539                 VALUES ((SELECT id FROM currencies WHERE name = ?), $buy, $sell, ?)|;
1540   }
1541   $sth->finish;
1542   do_query($self, $dbh, $query, $curr, $transdate);
1543
1544   $main::lxdebug->leave_sub();
1545 }
1546
1547 sub save_exchangerate {
1548   $main::lxdebug->enter_sub();
1549
1550   my ($self, $myconfig, $currency, $transdate, $rate, $fld) = @_;
1551
1552   my $dbh = $self->dbconnect($myconfig);
1553
1554   my ($buy, $sell);
1555
1556   $buy  = $rate if $fld eq 'buy';
1557   $sell = $rate if $fld eq 'sell';
1558
1559
1560   $self->update_exchangerate($dbh, $currency, $transdate, $buy, $sell);
1561
1562
1563   $dbh->disconnect;
1564
1565   $main::lxdebug->leave_sub();
1566 }
1567
1568 sub get_exchangerate {
1569   $main::lxdebug->enter_sub();
1570
1571   my ($self, $dbh, $curr, $transdate, $fld) = @_;
1572   my ($query);
1573
1574   unless ($transdate && $curr) {
1575     $main::lxdebug->leave_sub();
1576     return 1;
1577   }
1578
1579   $query = qq|SELECT name AS curr FROM currencies WHERE id = (SELECT currency_id FROM defaults)|;
1580
1581   my ($defaultcurrency) = selectrow_query($self, $dbh, $query);
1582
1583   if ($curr eq $defaultcurrency) {
1584     $main::lxdebug->leave_sub();
1585     return 1;
1586   }
1587
1588   $query = qq|SELECT e.$fld FROM exchangerate e
1589                  WHERE e.currency_id = (SELECT id FROM currencies WHERE name = ?) AND e.transdate = ?|;
1590   my ($exchangerate) = selectrow_query($self, $dbh, $query, $curr, $transdate);
1591
1592
1593
1594   $main::lxdebug->leave_sub();
1595
1596   return $exchangerate;
1597 }
1598
1599 sub check_exchangerate {
1600   $main::lxdebug->enter_sub();
1601
1602   my ($self, $myconfig, $currency, $transdate, $fld) = @_;
1603
1604   if ($fld !~/^buy|sell$/) {
1605     $self->error('Fatal: check_exchangerate called with invalid buy/sell argument');
1606   }
1607
1608   unless ($transdate) {
1609     $main::lxdebug->leave_sub();
1610     return "";
1611   }
1612
1613   my ($defaultcurrency) = $self->get_default_currency($myconfig);
1614
1615   if ($currency eq $defaultcurrency) {
1616     $main::lxdebug->leave_sub();
1617     return 1;
1618   }
1619
1620   my $dbh   = $self->get_standard_dbh($myconfig);
1621   my $query = qq|SELECT e.$fld FROM exchangerate e
1622                  WHERE e.currency_id = (SELECT id FROM currencies WHERE name = ?) AND e.transdate = ?|;
1623
1624   my ($exchangerate) = selectrow_query($self, $dbh, $query, $currency, $transdate);
1625
1626   $main::lxdebug->leave_sub();
1627
1628   return $exchangerate;
1629 }
1630
1631 sub get_all_currencies {
1632   $main::lxdebug->enter_sub();
1633
1634   my $self     = shift;
1635   my $myconfig = shift || \%::myconfig;
1636   my $dbh      = $self->get_standard_dbh($myconfig);
1637
1638   my $query = qq|SELECT name FROM currencies|;
1639   my @currencies = map { $_->{name} } selectall_hashref_query($self, $dbh, $query);
1640
1641   $main::lxdebug->leave_sub();
1642
1643   return @currencies;
1644 }
1645
1646 sub get_default_currency {
1647   $main::lxdebug->enter_sub();
1648
1649   my ($self, $myconfig) = @_;
1650   my $dbh      = $self->get_standard_dbh($myconfig);
1651   my $query = qq|SELECT name AS curr FROM currencies WHERE id = (SELECT currency_id FROM defaults)|;
1652
1653   my ($defaultcurrency) = selectrow_query($self, $dbh, $query);
1654
1655   $main::lxdebug->leave_sub();
1656
1657   return $defaultcurrency;
1658 }
1659
1660 sub set_payment_options {
1661   $main::lxdebug->enter_sub();
1662
1663   my ($self, $myconfig, $transdate) = @_;
1664
1665   return $main::lxdebug->leave_sub() unless ($self->{payment_id});
1666
1667   my $dbh = $self->get_standard_dbh($myconfig);
1668
1669   my $query =
1670     qq|SELECT p.terms_netto, p.terms_skonto, p.percent_skonto, p.description_long , p.description | .
1671     qq|FROM payment_terms p | .
1672     qq|WHERE p.id = ?|;
1673
1674   ($self->{terms_netto}, $self->{terms_skonto}, $self->{percent_skonto},
1675    $self->{payment_terms}, $self->{payment_description}) =
1676      selectrow_query($self, $dbh, $query, $self->{payment_id});
1677
1678   if ($transdate eq "") {
1679     if ($self->{invdate}) {
1680       $transdate = $self->{invdate};
1681     } else {
1682       $transdate = $self->{transdate};
1683     }
1684   }
1685
1686   $query =
1687     qq|SELECT ?::date + ?::integer AS netto_date, ?::date + ?::integer AS skonto_date | .
1688     qq|FROM payment_terms|;
1689   ($self->{netto_date}, $self->{skonto_date}) =
1690     selectrow_query($self, $dbh, $query, $transdate, $self->{terms_netto}, $transdate, $self->{terms_skonto});
1691
1692   my ($invtotal, $total);
1693   my (%amounts, %formatted_amounts);
1694
1695   if ($self->{type} =~ /_order$/) {
1696     $amounts{invtotal} = $self->{ordtotal};
1697     $amounts{total}    = $self->{ordtotal};
1698
1699   } elsif ($self->{type} =~ /_quotation$/) {
1700     $amounts{invtotal} = $self->{quototal};
1701     $amounts{total}    = $self->{quototal};
1702
1703   } else {
1704     $amounts{invtotal} = $self->{invtotal};
1705     $amounts{total}    = $self->{total};
1706   }
1707   map { $amounts{$_} = $self->parse_amount($myconfig, $amounts{$_}) } keys %amounts;
1708
1709   $amounts{skonto_in_percent}  = 100.0 * $self->{percent_skonto};
1710   $amounts{skonto_amount}      = $amounts{invtotal} * $self->{percent_skonto};
1711   $amounts{invtotal_wo_skonto} = $amounts{invtotal} * (1 - $self->{percent_skonto});
1712   $amounts{total_wo_skonto}    = $amounts{total}    * (1 - $self->{percent_skonto});
1713
1714   foreach (keys %amounts) {
1715     $amounts{$_}           = $self->round_amount($amounts{$_}, 2);
1716     $formatted_amounts{$_} = $self->format_amount($myconfig, $amounts{$_}, 2);
1717   }
1718
1719   if ($self->{"language_id"}) {
1720     $query =
1721       qq|SELECT t.translation, l.output_numberformat, l.output_dateformat, l.output_longdates | .
1722       qq|FROM generic_translations t | .
1723       qq|LEFT JOIN language l ON t.language_id = l.id | .
1724       qq|WHERE (t.language_id = ?)
1725            AND (t.translation_id = ?)
1726            AND (t.translation_type = 'SL::DB::PaymentTerm/description_long')|;
1727     my ($description_long, $output_numberformat, $output_dateformat,
1728       $output_longdates) =
1729       selectrow_query($self, $dbh, $query,
1730                       $self->{"language_id"}, $self->{"payment_id"});
1731
1732     $self->{payment_terms} = $description_long if ($description_long);
1733
1734     if ($output_dateformat) {
1735       foreach my $key (qw(netto_date skonto_date)) {
1736         $self->{$key} =
1737           $main::locale->reformat_date($myconfig, $self->{$key},
1738                                        $output_dateformat,
1739                                        $output_longdates);
1740       }
1741     }
1742
1743     if ($output_numberformat &&
1744         ($output_numberformat ne $myconfig->{"numberformat"})) {
1745       my $saved_numberformat = $myconfig->{"numberformat"};
1746       $myconfig->{"numberformat"} = $output_numberformat;
1747       map { $formatted_amounts{$_} = $self->format_amount($myconfig, $amounts{$_}) } keys %amounts;
1748       $myconfig->{"numberformat"} = $saved_numberformat;
1749     }
1750   }
1751
1752   $self->{payment_terms} =~ s/<%netto_date%>/$self->{netto_date}/g;
1753   $self->{payment_terms} =~ s/<%skonto_date%>/$self->{skonto_date}/g;
1754   $self->{payment_terms} =~ s/<%currency%>/$self->{currency}/g;
1755   $self->{payment_terms} =~ s/<%terms_netto%>/$self->{terms_netto}/g;
1756   $self->{payment_terms} =~ s/<%account_number%>/$self->{account_number}/g;
1757   $self->{payment_terms} =~ s/<%bank%>/$self->{bank}/g;
1758   $self->{payment_terms} =~ s/<%bank_code%>/$self->{bank_code}/g;
1759
1760   map { $self->{payment_terms} =~ s/<%${_}%>/$formatted_amounts{$_}/g; } keys %formatted_amounts;
1761
1762   $self->{skonto_in_percent} = $formatted_amounts{skonto_in_percent};
1763
1764   $main::lxdebug->leave_sub();
1765
1766 }
1767
1768 sub get_template_language {
1769   $main::lxdebug->enter_sub();
1770
1771   my ($self, $myconfig) = @_;
1772
1773   my $template_code = "";
1774
1775   if ($self->{language_id}) {
1776     my $dbh = $self->get_standard_dbh($myconfig);
1777     my $query = qq|SELECT template_code FROM language WHERE id = ?|;
1778     ($template_code) = selectrow_query($self, $dbh, $query, $self->{language_id});
1779   }
1780
1781   $main::lxdebug->leave_sub();
1782
1783   return $template_code;
1784 }
1785
1786 sub get_printer_code {
1787   $main::lxdebug->enter_sub();
1788
1789   my ($self, $myconfig) = @_;
1790
1791   my $template_code = "";
1792
1793   if ($self->{printer_id}) {
1794     my $dbh = $self->get_standard_dbh($myconfig);
1795     my $query = qq|SELECT template_code, printer_command FROM printers WHERE id = ?|;
1796     ($template_code, $self->{printer_command}) = selectrow_query($self, $dbh, $query, $self->{printer_id});
1797   }
1798
1799   $main::lxdebug->leave_sub();
1800
1801   return $template_code;
1802 }
1803
1804 sub get_shipto {
1805   $main::lxdebug->enter_sub();
1806
1807   my ($self, $myconfig) = @_;
1808
1809   my $template_code = "";
1810
1811   if ($self->{shipto_id}) {
1812     my $dbh = $self->get_standard_dbh($myconfig);
1813     my $query = qq|SELECT * FROM shipto WHERE shipto_id = ?|;
1814     my $ref = selectfirst_hashref_query($self, $dbh, $query, $self->{shipto_id});
1815     map({ $self->{$_} = $ref->{$_} } keys(%$ref));
1816   }
1817
1818   $main::lxdebug->leave_sub();
1819 }
1820
1821 sub add_shipto {
1822   $main::lxdebug->enter_sub();
1823
1824   my ($self, $dbh, $id, $module) = @_;
1825
1826   my $shipto;
1827   my @values;
1828
1829   foreach my $item (qw(name department_1 department_2 street zipcode city country
1830                        contact cp_gender phone fax email)) {
1831     if ($self->{"shipto$item"}) {
1832       $shipto = 1 if ($self->{$item} ne $self->{"shipto$item"});
1833     }
1834     push(@values, $self->{"shipto${item}"});
1835   }
1836
1837   if ($shipto) {
1838     if ($self->{shipto_id}) {
1839       my $query = qq|UPDATE shipto set
1840                        shiptoname = ?,
1841                        shiptodepartment_1 = ?,
1842                        shiptodepartment_2 = ?,
1843                        shiptostreet = ?,
1844                        shiptozipcode = ?,
1845                        shiptocity = ?,
1846                        shiptocountry = ?,
1847                        shiptocontact = ?,
1848                        shiptocp_gender = ?,
1849                        shiptophone = ?,
1850                        shiptofax = ?,
1851                        shiptoemail = ?
1852                      WHERE shipto_id = ?|;
1853       do_query($self, $dbh, $query, @values, $self->{shipto_id});
1854     } else {
1855       my $query = qq|SELECT * FROM shipto
1856                      WHERE shiptoname = ? AND
1857                        shiptodepartment_1 = ? AND
1858                        shiptodepartment_2 = ? AND
1859                        shiptostreet = ? AND
1860                        shiptozipcode = ? AND
1861                        shiptocity = ? AND
1862                        shiptocountry = ? AND
1863                        shiptocontact = ? AND
1864                        shiptocp_gender = ? AND
1865                        shiptophone = ? AND
1866                        shiptofax = ? AND
1867                        shiptoemail = ? AND
1868                        module = ? AND
1869                        trans_id = ?|;
1870       my $insert_check = selectfirst_hashref_query($self, $dbh, $query, @values, $module, $id);
1871       if(!$insert_check){
1872         $query =
1873           qq|INSERT INTO shipto (trans_id, shiptoname, shiptodepartment_1, shiptodepartment_2,
1874                                  shiptostreet, shiptozipcode, shiptocity, shiptocountry,
1875                                  shiptocontact, shiptocp_gender, shiptophone, shiptofax, shiptoemail, module)
1876              VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)|;
1877         do_query($self, $dbh, $query, $id, @values, $module);
1878       }
1879     }
1880   }
1881
1882   $main::lxdebug->leave_sub();
1883 }
1884
1885 sub get_employee {
1886   $main::lxdebug->enter_sub();
1887
1888   my ($self, $dbh) = @_;
1889
1890   $dbh ||= $self->get_standard_dbh(\%main::myconfig);
1891
1892   my $query = qq|SELECT id, name FROM employee WHERE login = ?|;
1893   ($self->{"employee_id"}, $self->{"employee"}) = selectrow_query($self, $dbh, $query, $self->{login});
1894   $self->{"employee_id"} *= 1;
1895
1896   $main::lxdebug->leave_sub();
1897 }
1898
1899 sub get_employee_data {
1900   $main::lxdebug->enter_sub();
1901
1902   my $self     = shift;
1903   my %params   = @_;
1904   my $defaults = SL::DB::Default->get;
1905
1906   Common::check_params(\%params, qw(prefix));
1907   Common::check_params_x(\%params, qw(id));
1908
1909   if (!$params{id}) {
1910     $main::lxdebug->leave_sub();
1911     return;
1912   }
1913
1914   my $myconfig = \%main::myconfig;
1915   my $dbh      = $params{dbh} || $self->get_standard_dbh($myconfig);
1916
1917   my ($login)  = selectrow_query($self, $dbh, qq|SELECT login FROM employee WHERE id = ?|, conv_i($params{id}));
1918
1919   if ($login) {
1920     my $user = User->new(login => $login);
1921     $self->{$params{prefix} . "_${_}"}    = $user->{$_}   for qw(email fax name signature tel);
1922     $self->{$params{prefix} . "_${_}"}    = $defaults->$_ for qw(address businessnumber co_ustid company duns taxnumber);
1923
1924     $self->{$params{prefix} . '_login'}   = $login;
1925     $self->{$params{prefix} . '_name'}  ||= $login;
1926   }
1927
1928   $main::lxdebug->leave_sub();
1929 }
1930
1931 sub get_duedate {
1932   $main::lxdebug->enter_sub();
1933
1934   my ($self, $myconfig, $reference_date) = @_;
1935
1936   my $terms   = $self->{payment_id}  ? SL::DB::PaymentTerm->new(id => $self->{payment_id}) ->load
1937               : $self->{customer_id} ? SL::DB::Customer   ->new(id => $self->{customer_id})->load->payment
1938               : $self->{vendor_id}   ? SL::DB::Vendor     ->new(id => $self->{vendor_id})  ->load->payment
1939               :                        croak("Missing field in \$::form: payment_id, customer_id or vendor_id");
1940
1941   my $duedate = $terms->calc_date(reference_date => $reference_date)->to_kivitendo;
1942
1943   $main::lxdebug->leave_sub();
1944
1945   return $duedate;
1946 }
1947
1948 sub _get_contacts {
1949   $main::lxdebug->enter_sub();
1950
1951   my ($self, $dbh, $id, $key) = @_;
1952
1953   $key = "all_contacts" unless ($key);
1954
1955   if (!$id) {
1956     $self->{$key} = [];
1957     $main::lxdebug->leave_sub();
1958     return;
1959   }
1960
1961   my $query =
1962     qq|SELECT cp_id, cp_cv_id, cp_name, cp_givenname, cp_abteilung | .
1963     qq|FROM contacts | .
1964     qq|WHERE cp_cv_id = ? | .
1965     qq|ORDER BY lower(cp_name)|;
1966
1967   $self->{$key} = selectall_hashref_query($self, $dbh, $query, $id);
1968
1969   $main::lxdebug->leave_sub();
1970 }
1971
1972 sub _get_projects {
1973   $main::lxdebug->enter_sub();
1974
1975   my ($self, $dbh, $key) = @_;
1976
1977   my ($all, $old_id, $where, @values);
1978
1979   if (ref($key) eq "HASH") {
1980     my $params = $key;
1981
1982     $key = "ALL_PROJECTS";
1983
1984     foreach my $p (keys(%{$params})) {
1985       if ($p eq "all") {
1986         $all = $params->{$p};
1987       } elsif ($p eq "old_id") {
1988         $old_id = $params->{$p};
1989       } elsif ($p eq "key") {
1990         $key = $params->{$p};
1991       }
1992     }
1993   }
1994
1995   if (!$all) {
1996     $where = "WHERE active ";
1997     if ($old_id) {
1998       if (ref($old_id) eq "ARRAY") {
1999         my @ids = grep({ $_ } @{$old_id});
2000         if (@ids) {
2001           $where .= " OR id IN (" . join(",", map({ "?" } @ids)) . ") ";
2002           push(@values, @ids);
2003         }
2004       } else {
2005         $where .= " OR (id = ?) ";
2006         push(@values, $old_id);
2007       }
2008     }
2009   }
2010
2011   my $query =
2012     qq|SELECT id, projectnumber, description, active | .
2013     qq|FROM project | .
2014     $where .
2015     qq|ORDER BY lower(projectnumber)|;
2016
2017   $self->{$key} = selectall_hashref_query($self, $dbh, $query, @values);
2018
2019   $main::lxdebug->leave_sub();
2020 }
2021
2022 sub _get_shipto {
2023   $main::lxdebug->enter_sub();
2024
2025   my ($self, $dbh, $vc_id, $key) = @_;
2026
2027   $key = "all_shipto" unless ($key);
2028
2029   if ($vc_id) {
2030     # get shipping addresses
2031     my $query = qq|SELECT * FROM shipto WHERE trans_id = ?|;
2032
2033     $self->{$key} = selectall_hashref_query($self, $dbh, $query, $vc_id);
2034
2035   } else {
2036     $self->{$key} = [];
2037   }
2038
2039   $main::lxdebug->leave_sub();
2040 }
2041
2042 sub _get_printers {
2043   $main::lxdebug->enter_sub();
2044
2045   my ($self, $dbh, $key) = @_;
2046
2047   $key = "all_printers" unless ($key);
2048
2049   my $query = qq|SELECT id, printer_description, printer_command, template_code FROM printers|;
2050
2051   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2052
2053   $main::lxdebug->leave_sub();
2054 }
2055
2056 sub _get_charts {
2057   $main::lxdebug->enter_sub();
2058
2059   my ($self, $dbh, $params) = @_;
2060   my ($key);
2061
2062   $key = $params->{key};
2063   $key = "all_charts" unless ($key);
2064
2065   my $transdate = quote_db_date($params->{transdate});
2066
2067   my $query =
2068     qq|SELECT c.id, c.accno, c.description, c.link, c.charttype, tk.taxkey_id, tk.tax_id | .
2069     qq|FROM chart c | .
2070     qq|LEFT JOIN taxkeys tk ON | .
2071     qq|(tk.id = (SELECT id FROM taxkeys | .
2072     qq|          WHERE taxkeys.chart_id = c.id AND startdate <= $transdate | .
2073     qq|          ORDER BY startdate DESC LIMIT 1)) | .
2074     qq|ORDER BY c.accno|;
2075
2076   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2077
2078   $main::lxdebug->leave_sub();
2079 }
2080
2081 sub _get_taxcharts {
2082   $main::lxdebug->enter_sub();
2083
2084   my ($self, $dbh, $params) = @_;
2085
2086   my $key = "all_taxcharts";
2087   my @where;
2088
2089   if (ref $params eq 'HASH') {
2090     $key = $params->{key} if ($params->{key});
2091     if ($params->{module} eq 'AR') {
2092       push @where, 'taxkey NOT IN (8, 9, 18, 19)';
2093
2094     } elsif ($params->{module} eq 'AP') {
2095       push @where, 'taxkey NOT IN (1, 2, 3, 12, 13)';
2096     }
2097
2098   } elsif ($params) {
2099     $key = $params;
2100   }
2101
2102   my $where = @where ? ' WHERE ' . join(' AND ', map { "($_)" } @where) : '';
2103
2104   my $query = qq|SELECT * FROM tax $where ORDER BY taxkey, rate|;
2105
2106   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2107
2108   $main::lxdebug->leave_sub();
2109 }
2110
2111 sub _get_taxzones {
2112   $main::lxdebug->enter_sub();
2113
2114   my ($self, $dbh, $key) = @_;
2115
2116   $key = "all_taxzones" unless ($key);
2117
2118   my $query = qq|SELECT * FROM tax_zones ORDER BY id|;
2119
2120   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2121
2122   $main::lxdebug->leave_sub();
2123 }
2124
2125 sub _get_employees {
2126   $main::lxdebug->enter_sub();
2127
2128   my ($self, $dbh, $default_key, $key) = @_;
2129
2130   $key = $default_key unless ($key);
2131   $self->{$key} = selectall_hashref_query($self, $dbh, qq|SELECT * FROM employee ORDER BY lower(name)|);
2132
2133   $main::lxdebug->leave_sub();
2134 }
2135
2136 sub _get_business_types {
2137   $main::lxdebug->enter_sub();
2138
2139   my ($self, $dbh, $key) = @_;
2140
2141   my $options       = ref $key eq 'HASH' ? $key : { key => $key };
2142   $options->{key} ||= "all_business_types";
2143   my $where         = '';
2144
2145   if (exists $options->{salesman}) {
2146     $where = 'WHERE ' . ($options->{salesman} ? '' : 'NOT ') . 'COALESCE(salesman)';
2147   }
2148
2149   $self->{ $options->{key} } = selectall_hashref_query($self, $dbh, qq|SELECT * FROM business $where ORDER BY lower(description)|);
2150
2151   $main::lxdebug->leave_sub();
2152 }
2153
2154 sub _get_languages {
2155   $main::lxdebug->enter_sub();
2156
2157   my ($self, $dbh, $key) = @_;
2158
2159   $key = "all_languages" unless ($key);
2160
2161   my $query = qq|SELECT * FROM language ORDER BY id|;
2162
2163   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2164
2165   $main::lxdebug->leave_sub();
2166 }
2167
2168 sub _get_dunning_configs {
2169   $main::lxdebug->enter_sub();
2170
2171   my ($self, $dbh, $key) = @_;
2172
2173   $key = "all_dunning_configs" unless ($key);
2174
2175   my $query = qq|SELECT * FROM dunning_config ORDER BY dunning_level|;
2176
2177   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2178
2179   $main::lxdebug->leave_sub();
2180 }
2181
2182 sub _get_currencies {
2183 $main::lxdebug->enter_sub();
2184
2185   my ($self, $dbh, $key) = @_;
2186
2187   $key = "all_currencies" unless ($key);
2188
2189   $self->{$key} = [$self->get_all_currencies()];
2190
2191   $main::lxdebug->leave_sub();
2192 }
2193
2194 sub _get_payments {
2195 $main::lxdebug->enter_sub();
2196
2197   my ($self, $dbh, $key) = @_;
2198
2199   $key = "all_payments" unless ($key);
2200
2201   my $query = qq|SELECT * FROM payment_terms ORDER BY sortkey|;
2202
2203   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2204
2205   $main::lxdebug->leave_sub();
2206 }
2207
2208 sub _get_customers {
2209   $main::lxdebug->enter_sub();
2210
2211   my ($self, $dbh, $key) = @_;
2212
2213   my $options        = ref $key eq 'HASH' ? $key : { key => $key };
2214   $options->{key}  ||= "all_customers";
2215   my $limit_clause   = $options->{limit} ? "LIMIT $options->{limit}" : '';
2216
2217   my @where;
2218   push @where, qq|business_id IN (SELECT id FROM business WHERE salesman)| if  $options->{business_is_salesman};
2219   push @where, qq|NOT obsolete|                                            if !$options->{with_obsolete};
2220   my $where_str = @where ? "WHERE " . join(" AND ", map { "($_)" } @where) : '';
2221
2222   my $query = qq|SELECT * FROM customer $where_str ORDER BY name $limit_clause|;
2223   $self->{ $options->{key} } = selectall_hashref_query($self, $dbh, $query);
2224
2225   $main::lxdebug->leave_sub();
2226 }
2227
2228 sub _get_vendors {
2229   $main::lxdebug->enter_sub();
2230
2231   my ($self, $dbh, $key) = @_;
2232
2233   $key = "all_vendors" unless ($key);
2234
2235   my $query = qq|SELECT * FROM vendor WHERE NOT obsolete ORDER BY name|;
2236
2237   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2238
2239   $main::lxdebug->leave_sub();
2240 }
2241
2242 sub _get_departments {
2243   $main::lxdebug->enter_sub();
2244
2245   my ($self, $dbh, $key) = @_;
2246
2247   $key = "all_departments" unless ($key);
2248
2249   my $query = qq|SELECT * FROM department ORDER BY description|;
2250
2251   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2252
2253   $main::lxdebug->leave_sub();
2254 }
2255
2256 sub _get_warehouses {
2257   $main::lxdebug->enter_sub();
2258
2259   my ($self, $dbh, $param) = @_;
2260
2261   my ($key, $bins_key);
2262
2263   if ('' eq ref $param) {
2264     $key = $param;
2265
2266   } else {
2267     $key      = $param->{key};
2268     $bins_key = $param->{bins};
2269   }
2270
2271   my $query = qq|SELECT w.* FROM warehouse w
2272                  WHERE (NOT w.invalid) AND
2273                    ((SELECT COUNT(b.*) FROM bin b WHERE b.warehouse_id = w.id) > 0)
2274                  ORDER BY w.sortkey|;
2275
2276   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2277
2278   if ($bins_key) {
2279     $query = qq|SELECT id, description FROM bin WHERE warehouse_id = ?
2280                 ORDER BY description|;
2281     my $sth = prepare_query($self, $dbh, $query);
2282
2283     foreach my $warehouse (@{ $self->{$key} }) {
2284       do_statement($self, $sth, $query, $warehouse->{id});
2285       $warehouse->{$bins_key} = [];
2286
2287       while (my $ref = $sth->fetchrow_hashref()) {
2288         push @{ $warehouse->{$bins_key} }, $ref;
2289       }
2290     }
2291     $sth->finish();
2292   }
2293
2294   $main::lxdebug->leave_sub();
2295 }
2296
2297 sub _get_simple {
2298   $main::lxdebug->enter_sub();
2299
2300   my ($self, $dbh, $table, $key, $sortkey) = @_;
2301
2302   my $query  = qq|SELECT * FROM $table|;
2303   $query    .= qq| ORDER BY $sortkey| if ($sortkey);
2304
2305   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2306
2307   $main::lxdebug->leave_sub();
2308 }
2309
2310 #sub _get_groups {
2311 #  $main::lxdebug->enter_sub();
2312 #
2313 #  my ($self, $dbh, $key) = @_;
2314 #
2315 #  $key ||= "all_groups";
2316 #
2317 #  my $groups = $main::auth->read_groups();
2318 #
2319 #  $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2320 #
2321 #  $main::lxdebug->leave_sub();
2322 #}
2323
2324 sub get_lists {
2325   $main::lxdebug->enter_sub();
2326
2327   my $self = shift;
2328   my %params = @_;
2329
2330   my $dbh = $self->get_standard_dbh(\%main::myconfig);
2331   my ($sth, $query, $ref);
2332
2333   my $vc = $self->{"vc"} eq "customer" ? "customer" : "vendor";
2334   my $vc_id = $self->{"${vc}_id"};
2335
2336   if ($params{"contacts"}) {
2337     $self->_get_contacts($dbh, $vc_id, $params{"contacts"});
2338   }
2339
2340   if ($params{"shipto"}) {
2341     $self->_get_shipto($dbh, $vc_id, $params{"shipto"});
2342   }
2343
2344   if ($params{"projects"} || $params{"all_projects"}) {
2345     $self->_get_projects($dbh, $params{"all_projects"} ?
2346                          $params{"all_projects"} : $params{"projects"},
2347                          $params{"all_projects"} ? 1 : 0);
2348   }
2349
2350   if ($params{"printers"}) {
2351     $self->_get_printers($dbh, $params{"printers"});
2352   }
2353
2354   if ($params{"languages"}) {
2355     $self->_get_languages($dbh, $params{"languages"});
2356   }
2357
2358   if ($params{"charts"}) {
2359     $self->_get_charts($dbh, $params{"charts"});
2360   }
2361
2362   if ($params{"taxcharts"}) {
2363     $self->_get_taxcharts($dbh, $params{"taxcharts"});
2364   }
2365
2366   if ($params{"taxzones"}) {
2367     $self->_get_taxzones($dbh, $params{"taxzones"});
2368   }
2369
2370   if ($params{"employees"}) {
2371     $self->_get_employees($dbh, "all_employees", $params{"employees"});
2372   }
2373
2374   if ($params{"salesmen"}) {
2375     $self->_get_employees($dbh, "all_salesmen", $params{"salesmen"});
2376   }
2377
2378   if ($params{"business_types"}) {
2379     $self->_get_business_types($dbh, $params{"business_types"});
2380   }
2381
2382   if ($params{"dunning_configs"}) {
2383     $self->_get_dunning_configs($dbh, $params{"dunning_configs"});
2384   }
2385
2386   if($params{"currencies"}) {
2387     $self->_get_currencies($dbh, $params{"currencies"});
2388   }
2389
2390   if($params{"customers"}) {
2391     $self->_get_customers($dbh, $params{"customers"});
2392   }
2393
2394   if($params{"vendors"}) {
2395     if (ref $params{"vendors"} eq 'HASH') {
2396       $self->_get_vendors($dbh, $params{"vendors"}{key}, $params{"vendors"}{limit});
2397     } else {
2398       $self->_get_vendors($dbh, $params{"vendors"});
2399     }
2400   }
2401
2402   if($params{"payments"}) {
2403     $self->_get_payments($dbh, $params{"payments"});
2404   }
2405
2406   if($params{"departments"}) {
2407     $self->_get_departments($dbh, $params{"departments"});
2408   }
2409
2410   if ($params{price_factors}) {
2411     $self->_get_simple($dbh, 'price_factors', $params{price_factors}, 'sortkey');
2412   }
2413
2414   if ($params{warehouses}) {
2415     $self->_get_warehouses($dbh, $params{warehouses});
2416   }
2417
2418 #  if ($params{groups}) {
2419 #    $self->_get_groups($dbh, $params{groups});
2420 #  }
2421
2422   if ($params{partsgroup}) {
2423     $self->get_partsgroup(\%main::myconfig, { all => 1, target => $params{partsgroup} });
2424   }
2425
2426   $main::lxdebug->leave_sub();
2427 }
2428
2429 # this sub gets the id and name from $table
2430 sub get_name {
2431   $main::lxdebug->enter_sub();
2432
2433   my ($self, $myconfig, $table) = @_;
2434
2435   # connect to database
2436   my $dbh = $self->get_standard_dbh($myconfig);
2437
2438   $table = $table eq "customer" ? "customer" : "vendor";
2439   my $arap = $self->{arap} eq "ar" ? "ar" : "ap";
2440
2441   my ($query, @values);
2442
2443   if (!$self->{openinvoices}) {
2444     my $where;
2445     if ($self->{customernumber} ne "") {
2446       $where = qq|(vc.customernumber ILIKE ?)|;
2447       push(@values, '%' . $self->{customernumber} . '%');
2448     } else {
2449       $where = qq|(vc.name ILIKE ?)|;
2450       push(@values, '%' . $self->{$table} . '%');
2451     }
2452
2453     $query =
2454       qq~SELECT vc.id, vc.name,
2455            vc.street || ' ' || vc.zipcode || ' ' || vc.city || ' ' || vc.country AS address
2456          FROM $table vc
2457          WHERE $where AND (NOT vc.obsolete)
2458          ORDER BY vc.name~;
2459   } else {
2460     $query =
2461       qq~SELECT DISTINCT vc.id, vc.name,
2462            vc.street || ' ' || vc.zipcode || ' ' || vc.city || ' ' || vc.country AS address
2463          FROM $arap a
2464          JOIN $table vc ON (a.${table}_id = vc.id)
2465          WHERE NOT (a.amount = a.paid) AND (vc.name ILIKE ?)
2466          ORDER BY vc.name~;
2467     push(@values, '%' . $self->{$table} . '%');
2468   }
2469
2470   $self->{name_list} = selectall_hashref_query($self, $dbh, $query, @values);
2471
2472   $main::lxdebug->leave_sub();
2473
2474   return scalar(@{ $self->{name_list} });
2475 }
2476
2477 # the selection sub is used in the AR, AP, IS, IR, DO and OE module
2478 #
2479 sub all_vc {
2480   $main::lxdebug->enter_sub();
2481
2482   my ($self, $myconfig, $table, $module) = @_;
2483
2484   my $ref;
2485   my $dbh = $self->get_standard_dbh;
2486
2487   $table = $table eq "customer" ? "customer" : "vendor";
2488
2489   # build selection list
2490   # Hotfix für Bug 1837 - Besser wäre es alte Buchungsbelege
2491   # OHNE Auswahlliste (reines Textfeld) zu laden. Hilft aber auch
2492   # nicht für veränderbare Belege (oe, do, ...)
2493   my $obsolete = $self->{id} ? '' : "WHERE NOT obsolete";
2494   my $query = qq|SELECT count(*) FROM $table $obsolete|;
2495   my ($count) = selectrow_query($self, $dbh, $query);
2496
2497   if ($count <= $myconfig->{vclimit}) {
2498     $query = qq|SELECT id, name, salesman_id
2499                 FROM $table $obsolete
2500                 ORDER BY name|;
2501     $self->{"all_$table"} = selectall_hashref_query($self, $dbh, $query);
2502   }
2503
2504   # get self
2505   $self->get_employee($dbh);
2506
2507   # setup sales contacts
2508   $query = qq|SELECT e.id, e.name
2509               FROM employee e
2510               WHERE (e.sales = '1') AND (NOT e.id = ?)
2511               ORDER BY name|;
2512   $self->{all_employees} = selectall_hashref_query($self, $dbh, $query, $self->{employee_id});
2513
2514   # this is for self
2515   push(@{ $self->{all_employees} },
2516        { id   => $self->{employee_id},
2517          name => $self->{employee} });
2518
2519     # prepare query for departments
2520     $query = qq|SELECT id, description
2521                 FROM department
2522                 ORDER BY description|;
2523
2524   $self->{all_departments} = selectall_hashref_query($self, $dbh, $query);
2525
2526   # get languages
2527   $query = qq|SELECT id, description
2528               FROM language
2529               ORDER BY id|;
2530
2531   $self->{languages} = selectall_hashref_query($self, $dbh, $query);
2532
2533   # get printer
2534   $query = qq|SELECT printer_description, id
2535               FROM printers
2536               ORDER BY printer_description|;
2537
2538   $self->{printers} = selectall_hashref_query($self, $dbh, $query);
2539
2540   # get payment terms
2541   $query = qq|SELECT id, description
2542               FROM payment_terms
2543               ORDER BY sortkey|;
2544
2545   $self->{payment_terms} = selectall_hashref_query($self, $dbh, $query);
2546
2547   $main::lxdebug->leave_sub();
2548 }
2549
2550 sub language_payment {
2551   $main::lxdebug->enter_sub();
2552
2553   my ($self, $myconfig) = @_;
2554
2555   my $dbh = $self->get_standard_dbh($myconfig);
2556   # get languages
2557   my $query = qq|SELECT id, description
2558                  FROM language
2559                  ORDER BY id|;
2560
2561   $self->{languages} = selectall_hashref_query($self, $dbh, $query);
2562
2563   # get printer
2564   $query = qq|SELECT printer_description, id
2565               FROM printers
2566               ORDER BY printer_description|;
2567
2568   $self->{printers} = selectall_hashref_query($self, $dbh, $query);
2569
2570   # get payment terms
2571   $query = qq|SELECT id, description
2572               FROM payment_terms
2573               ORDER BY sortkey|;
2574
2575   $self->{payment_terms} = selectall_hashref_query($self, $dbh, $query);
2576
2577   # get buchungsgruppen
2578   $query = qq|SELECT id, description
2579               FROM buchungsgruppen|;
2580
2581   $self->{BUCHUNGSGRUPPEN} = selectall_hashref_query($self, $dbh, $query);
2582
2583   $main::lxdebug->leave_sub();
2584 }
2585
2586 # this is only used for reports
2587 sub all_departments {
2588   $main::lxdebug->enter_sub();
2589
2590   my ($self, $myconfig, $table) = @_;
2591
2592   my $dbh = $self->get_standard_dbh($myconfig);
2593
2594   my $query = qq|SELECT id, description
2595                  FROM department
2596                  ORDER BY description|;
2597   $self->{all_departments} = selectall_hashref_query($self, $dbh, $query);
2598
2599   delete($self->{all_departments}) unless (@{ $self->{all_departments} || [] });
2600
2601   $main::lxdebug->leave_sub();
2602 }
2603
2604 sub create_links {
2605   $main::lxdebug->enter_sub();
2606
2607   my ($self, $module, $myconfig, $table, $provided_dbh) = @_;
2608
2609   my ($fld, $arap);
2610   if ($table eq "customer") {
2611     $fld = "buy";
2612     $arap = "ar";
2613   } else {
2614     $table = "vendor";
2615     $fld = "sell";
2616     $arap = "ap";
2617   }
2618
2619   $self->all_vc($myconfig, $table, $module);
2620
2621   # get last customers or vendors
2622   my ($query, $sth, $ref);
2623
2624   my $dbh = $provided_dbh ? $provided_dbh : $self->get_standard_dbh($myconfig);
2625   my %xkeyref = ();
2626
2627   if (!$self->{id}) {
2628
2629     my $transdate = "current_date";
2630     if ($self->{transdate}) {
2631       $transdate = $dbh->quote($self->{transdate});
2632     }
2633
2634     # now get the account numbers
2635 #    $query = qq|SELECT c.accno, c.description, c.link, c.taxkey_id, tk.tax_id
2636 #                FROM chart c, taxkeys tk
2637 #                WHERE (c.link LIKE ?) AND (c.id = tk.chart_id) AND tk.id =
2638 #                  (SELECT id FROM taxkeys WHERE (taxkeys.chart_id = c.id) AND (startdate <= $transdate) ORDER BY startdate DESC LIMIT 1)
2639 #                ORDER BY c.accno|;
2640
2641 #  same query as above, but without expensive subquery for each row. about 80% faster
2642     $query = qq|
2643       SELECT c.accno, c.description, c.link, c.taxkey_id, tk2.tax_id
2644         FROM chart c
2645         -- find newest entries in taxkeys
2646         INNER JOIN (
2647           SELECT chart_id, MAX(startdate) AS startdate
2648           FROM taxkeys
2649           WHERE (startdate <= $transdate)
2650           GROUP BY chart_id
2651         ) tk ON (c.id = tk.chart_id)
2652         -- and load all of those entries
2653         INNER JOIN taxkeys tk2
2654            ON (tk.chart_id = tk2.chart_id AND tk.startdate = tk2.startdate)
2655        WHERE (c.link LIKE ?)
2656       ORDER BY c.accno|;
2657
2658     $sth = $dbh->prepare($query);
2659
2660     do_statement($self, $sth, $query, '%' . $module . '%');
2661
2662     $self->{accounts} = "";
2663     while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
2664
2665       foreach my $key (split(/:/, $ref->{link})) {
2666         if ($key =~ /\Q$module\E/) {
2667
2668           # cross reference for keys
2669           $xkeyref{ $ref->{accno} } = $key;
2670
2671           push @{ $self->{"${module}_links"}{$key} },
2672             { accno       => $ref->{accno},
2673               description => $ref->{description},
2674               taxkey      => $ref->{taxkey_id},
2675               tax_id      => $ref->{tax_id} };
2676
2677           $self->{accounts} .= "$ref->{accno} " unless $key =~ /tax/;
2678         }
2679       }
2680     }
2681   }
2682
2683   # get taxkeys and description
2684   $query = qq|SELECT id, taxkey, taxdescription FROM tax|;
2685   $self->{TAXKEY} = selectall_hashref_query($self, $dbh, $query);
2686
2687   if (($module eq "AP") || ($module eq "AR")) {
2688     # get tax rates and description
2689     $query = qq|SELECT * FROM tax|;
2690     $self->{TAX} = selectall_hashref_query($self, $dbh, $query);
2691   }
2692
2693   my $extra_columns = '';
2694   $extra_columns   .= 'a.direct_debit, ' if ($module eq 'AR') || ($module eq 'AP');
2695
2696   if ($self->{id}) {
2697     $query =
2698       qq|SELECT
2699            a.cp_id, a.invnumber, a.transdate, a.${table}_id, a.datepaid,
2700            a.duedate, a.ordnumber, a.taxincluded, (SELECT cu.name FROM currencies cu WHERE cu.id=a.currency_id) AS currency, a.notes,
2701            a.intnotes, a.department_id, a.amount AS oldinvtotal,
2702            a.paid AS oldtotalpaid, a.employee_id, a.gldate, a.type,
2703            a.globalproject_id, ${extra_columns}
2704            c.name AS $table,
2705            d.description AS department,
2706            e.name AS employee
2707          FROM $arap a
2708          JOIN $table c ON (a.${table}_id = c.id)
2709          LEFT JOIN employee e ON (e.id = a.employee_id)
2710          LEFT JOIN department d ON (d.id = a.department_id)
2711          WHERE a.id = ?|;
2712     $ref = selectfirst_hashref_query($self, $dbh, $query, $self->{id});
2713
2714     foreach my $key (keys %$ref) {
2715       $self->{$key} = $ref->{$key};
2716     }
2717
2718     my $transdate = "current_date";
2719     if ($self->{transdate}) {
2720       $transdate = $dbh->quote($self->{transdate});
2721     }
2722
2723     # now get the account numbers
2724     $query = qq|SELECT c.accno, c.description, c.link, c.taxkey_id, tk.tax_id
2725                 FROM chart c
2726                 LEFT JOIN taxkeys tk ON (tk.chart_id = c.id)
2727                 WHERE c.link LIKE ?
2728                   AND (tk.id = (SELECT id FROM taxkeys WHERE taxkeys.chart_id = c.id AND startdate <= $transdate ORDER BY startdate DESC LIMIT 1)
2729                     OR c.link LIKE '%_tax%' OR c.taxkey_id IS NULL)
2730                 ORDER BY c.accno|;
2731
2732     $sth = $dbh->prepare($query);
2733     do_statement($self, $sth, $query, "%$module%");
2734
2735     $self->{accounts} = "";
2736     while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
2737
2738       foreach my $key (split(/:/, $ref->{link})) {
2739         if ($key =~ /\Q$module\E/) {
2740
2741           # cross reference for keys
2742           $xkeyref{ $ref->{accno} } = $key;
2743
2744           push @{ $self->{"${module}_links"}{$key} },
2745             { accno       => $ref->{accno},
2746               description => $ref->{description},
2747               taxkey      => $ref->{taxkey_id},
2748               tax_id      => $ref->{tax_id} };
2749
2750           $self->{accounts} .= "$ref->{accno} " unless $key =~ /tax/;
2751         }
2752       }
2753     }
2754
2755
2756     # get amounts from individual entries
2757     $query =
2758       qq|SELECT
2759            c.accno, c.description,
2760            a.acc_trans_id, a.source, a.amount, a.memo, a.transdate, a.gldate, a.cleared, a.project_id, a.taxkey,
2761            p.projectnumber,
2762            t.rate, t.id
2763          FROM acc_trans a
2764          LEFT JOIN chart c ON (c.id = a.chart_id)
2765          LEFT JOIN project p ON (p.id = a.project_id)
2766          LEFT JOIN tax t ON (t.id= a.tax_id)
2767          WHERE a.trans_id = ?
2768          AND a.fx_transaction = '0'
2769          ORDER BY a.acc_trans_id, a.transdate|;
2770     $sth = $dbh->prepare($query);
2771     do_statement($self, $sth, $query, $self->{id});
2772
2773     # get exchangerate for currency
2774     $self->{exchangerate} =
2775       $self->get_exchangerate($dbh, $self->{currency}, $self->{transdate}, $fld);
2776     my $index = 0;
2777
2778     # store amounts in {acc_trans}{$key} for multiple accounts
2779     while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
2780       $ref->{exchangerate} =
2781         $self->get_exchangerate($dbh, $self->{currency}, $ref->{transdate}, $fld);
2782       if (!($xkeyref{ $ref->{accno} } =~ /tax/)) {
2783         $index++;
2784       }
2785       if (($xkeyref{ $ref->{accno} } =~ /paid/) && ($self->{type} eq "credit_note")) {
2786         $ref->{amount} *= -1;
2787       }
2788       $ref->{index} = $index;
2789
2790       push @{ $self->{acc_trans}{ $xkeyref{ $ref->{accno} } } }, $ref;
2791     }
2792
2793     $sth->finish;
2794     #check das:
2795     $query =
2796       qq|SELECT
2797            d.closedto, d.revtrans,
2798            (SELECT cu.name FROM currencies cu WHERE cu.id=d.currency_id) AS defaultcurrency,
2799            (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno,
2800            (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno
2801          FROM defaults d|;
2802     $ref = selectfirst_hashref_query($self, $dbh, $query);
2803     map { $self->{$_} = $ref->{$_} } keys %$ref;
2804
2805   } else {
2806
2807     # get date
2808     $query =
2809        qq|SELECT
2810             current_date AS transdate, d.closedto, d.revtrans,
2811             (SELECT cu.name FROM currencies cu WHERE cu.id=d.currency_id) AS defaultcurrency,
2812             (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno,
2813             (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno
2814           FROM defaults d|;
2815     $ref = selectfirst_hashref_query($self, $dbh, $query);
2816     map { $self->{$_} = $ref->{$_} } keys %$ref;
2817
2818     if ($self->{"$self->{vc}_id"}) {
2819
2820       # only setup currency
2821       ($self->{currency}) = $self->{defaultcurrency} if !$self->{currency};
2822
2823     } else {
2824
2825       $self->lastname_used($dbh, $myconfig, $table, $module);
2826
2827       # get exchangerate for currency
2828       $self->{exchangerate} =
2829         $self->get_exchangerate($dbh, $self->{currency}, $self->{transdate}, $fld);
2830
2831     }
2832
2833   }
2834
2835   $main::lxdebug->leave_sub();
2836 }
2837
2838 sub lastname_used {
2839   $main::lxdebug->enter_sub();
2840
2841   my ($self, $dbh, $myconfig, $table, $module) = @_;
2842
2843   my ($arap, $where);
2844
2845   $table         = $table eq "customer" ? "customer" : "vendor";
2846   my %column_map = ("a.${table}_id"           => "${table}_id",
2847                     "a.department_id"         => "department_id",
2848                     "d.description"           => "department",
2849                     "ct.name"                 => $table,
2850                     "cu.name"                 => "currency",
2851                     "current_date + ct.terms" => "duedate",
2852     );
2853
2854   if ($self->{type} =~ /delivery_order/) {
2855     $arap  = 'delivery_orders';
2856     delete $column_map{"cu.currency"};
2857
2858   } elsif ($self->{type} =~ /_order/) {
2859     $arap  = 'oe';
2860     $where = "quotation = '0'";
2861
2862   } elsif ($self->{type} =~ /_quotation/) {
2863     $arap  = 'oe';
2864     $where = "quotation = '1'";
2865
2866   } elsif ($table eq 'customer') {
2867     $arap  = 'ar';
2868
2869   } else {
2870     $arap  = 'ap';
2871
2872   }
2873
2874   $where           = "($where) AND" if ($where);
2875   my $query        = qq|SELECT MAX(id) FROM $arap
2876                         WHERE $where ${table}_id > 0|;
2877   my ($trans_id)   = selectrow_query($self, $dbh, $query);
2878   $trans_id       *= 1;
2879
2880   my $column_spec  = join(', ', map { "${_} AS $column_map{$_}" } keys %column_map);
2881   $query           = qq|SELECT $column_spec
2882                         FROM $arap a
2883                         LEFT JOIN $table     ct ON (a.${table}_id = ct.id)
2884                         LEFT JOIN department d  ON (a.department_id = d.id)
2885                         LEFT JOIN currencies cu ON (cu.id=ct.currency_id)
2886                         WHERE a.id = ?|;
2887   my $ref          = selectfirst_hashref_query($self, $dbh, $query, $trans_id);
2888
2889   map { $self->{$_} = $ref->{$_} } values %column_map;
2890
2891   $main::lxdebug->leave_sub();
2892 }
2893
2894 sub current_date {
2895   $main::lxdebug->enter_sub();
2896
2897   my $self     = shift;
2898   my $myconfig = shift || \%::myconfig;
2899   my ($thisdate, $days) = @_;
2900
2901   my $dbh = $self->get_standard_dbh($myconfig);
2902   my $query;
2903
2904   $days *= 1;
2905   if ($thisdate) {
2906     my $dateformat = $myconfig->{dateformat};
2907     $dateformat .= "yy" if $myconfig->{dateformat} !~ /^y/;
2908     $thisdate = $dbh->quote($thisdate);
2909     $query = qq|SELECT to_date($thisdate, '$dateformat') + $days AS thisdate|;
2910   } else {
2911     $query = qq|SELECT current_date AS thisdate|;
2912   }
2913
2914   ($thisdate) = selectrow_query($self, $dbh, $query);
2915
2916   $main::lxdebug->leave_sub();
2917
2918   return $thisdate;
2919 }
2920
2921 sub like {
2922   $main::lxdebug->enter_sub();
2923
2924   my ($self, $string) = @_;
2925
2926   if ($string !~ /%/) {
2927     $string = "%$string%";
2928   }
2929
2930   $string =~ s/\'/\'\'/g;
2931
2932   $main::lxdebug->leave_sub();
2933
2934   return $string;
2935 }
2936
2937 sub redo_rows {
2938   $main::lxdebug->enter_sub();
2939
2940   my ($self, $flds, $new, $count, $numrows) = @_;
2941
2942   my @ndx = ();
2943
2944   map { push @ndx, { num => $new->[$_ - 1]->{runningnumber}, ndx => $_ } } 1 .. $count;
2945
2946   my $i = 0;
2947
2948   # fill rows
2949   foreach my $item (sort { $a->{num} <=> $b->{num} } @ndx) {
2950     $i++;
2951     my $j = $item->{ndx} - 1;
2952     map { $self->{"${_}_$i"} = $new->[$j]->{$_} } @{$flds};
2953   }
2954
2955   # delete empty rows
2956   for $i ($count + 1 .. $numrows) {
2957     map { delete $self->{"${_}_$i"} } @{$flds};
2958   }
2959
2960   $main::lxdebug->leave_sub();
2961 }
2962
2963 sub update_status {
2964   $main::lxdebug->enter_sub();
2965
2966   my ($self, $myconfig) = @_;
2967
2968   my ($i, $id);
2969
2970   my $dbh = $self->dbconnect_noauto($myconfig);
2971
2972   my $query = qq|DELETE FROM status
2973                  WHERE (formname = ?) AND (trans_id = ?)|;
2974   my $sth = prepare_query($self, $dbh, $query);
2975
2976   if ($self->{formname} =~ /(check|receipt)/) {
2977     for $i (1 .. $self->{rowcount}) {
2978       do_statement($self, $sth, $query, $self->{formname}, $self->{"id_$i"} * 1);
2979     }
2980   } else {
2981     do_statement($self, $sth, $query, $self->{formname}, $self->{id});
2982   }
2983   $sth->finish();
2984
2985   my $printed = ($self->{printed} =~ /\Q$self->{formname}\E/) ? "1" : "0";
2986   my $emailed = ($self->{emailed} =~ /\Q$self->{formname}\E/) ? "1" : "0";
2987
2988   my %queued = split / /, $self->{queued};
2989   my @values;
2990
2991   if ($self->{formname} =~ /(check|receipt)/) {
2992
2993     # this is a check or receipt, add one entry for each lineitem
2994     my ($accno) = split /--/, $self->{account};
2995     $query = qq|INSERT INTO status (trans_id, printed, spoolfile, formname, chart_id)
2996                 VALUES (?, ?, ?, ?, (SELECT c.id FROM chart c WHERE c.accno = ?))|;
2997     @values = ($printed, $queued{$self->{formname}}, $self->{prinform}, $accno);
2998     $sth = prepare_query($self, $dbh, $query);
2999
3000     for $i (1 .. $self->{rowcount}) {
3001       if ($self->{"checked_$i"}) {
3002         do_statement($self, $sth, $query, $self->{"id_$i"}, @values);
3003       }
3004     }
3005     $sth->finish();
3006
3007   } else {
3008     $query = qq|INSERT INTO status (trans_id, printed, emailed, spoolfile, formname)
3009                 VALUES (?, ?, ?, ?, ?)|;
3010     do_query($self, $dbh, $query, $self->{id}, $printed, $emailed,
3011              $queued{$self->{formname}}, $self->{formname});
3012   }
3013
3014   $dbh->commit;
3015   $dbh->disconnect;
3016
3017   $main::lxdebug->leave_sub();
3018 }
3019
3020 sub save_status {
3021   $main::lxdebug->enter_sub();
3022
3023   my ($self, $dbh) = @_;
3024
3025   my ($query, $printed, $emailed);
3026
3027   my $formnames  = $self->{printed};
3028   my $emailforms = $self->{emailed};
3029
3030   $query = qq|DELETE FROM status
3031                  WHERE (formname = ?) AND (trans_id = ?)|;
3032   do_query($self, $dbh, $query, $self->{formname}, $self->{id});
3033
3034   # this only applies to the forms
3035   # checks and receipts are posted when printed or queued
3036
3037   if ($self->{queued}) {
3038     my %queued = split / /, $self->{queued};
3039
3040     foreach my $formname (keys %queued) {
3041       $printed = ($self->{printed} =~ /\Q$self->{formname}\E/) ? "1" : "0";
3042       $emailed = ($self->{emailed} =~ /\Q$self->{formname}\E/) ? "1" : "0";
3043
3044       $query = qq|INSERT INTO status (trans_id, printed, emailed, spoolfile, formname)
3045                   VALUES (?, ?, ?, ?, ?)|;
3046       do_query($self, $dbh, $query, $self->{id}, $printed, $emailed, $queued{$formname}, $formname);
3047
3048       $formnames  =~ s/\Q$self->{formname}\E//;
3049       $emailforms =~ s/\Q$self->{formname}\E//;
3050
3051     }
3052   }
3053
3054   # save printed, emailed info
3055   $formnames  =~ s/^ +//g;
3056   $emailforms =~ s/^ +//g;
3057
3058   my %status = ();
3059   map { $status{$_}{printed} = 1 } split / +/, $formnames;
3060   map { $status{$_}{emailed} = 1 } split / +/, $emailforms;
3061
3062   foreach my $formname (keys %status) {
3063     $printed = ($formnames  =~ /\Q$self->{formname}\E/) ? "1" : "0";
3064     $emailed = ($emailforms =~ /\Q$self->{formname}\E/) ? "1" : "0";
3065
3066     $query = qq|INSERT INTO status (trans_id, printed, emailed, formname)
3067                 VALUES (?, ?, ?, ?)|;
3068     do_query($self, $dbh, $query, $self->{id}, $printed, $emailed, $formname);
3069   }
3070
3071   $main::lxdebug->leave_sub();
3072 }
3073
3074 #--- 4 locale ---#
3075 # $main::locale->text('SAVED')
3076 # $main::locale->text('DELETED')
3077 # $main::locale->text('ADDED')
3078 # $main::locale->text('PAYMENT POSTED')
3079 # $main::locale->text('POSTED')
3080 # $main::locale->text('POSTED AS NEW')
3081 # $main::locale->text('ELSE')
3082 # $main::locale->text('SAVED FOR DUNNING')
3083 # $main::locale->text('DUNNING STARTED')
3084 # $main::locale->text('PRINTED')
3085 # $main::locale->text('MAILED')
3086 # $main::locale->text('SCREENED')
3087 # $main::locale->text('CANCELED')
3088 # $main::locale->text('invoice')
3089 # $main::locale->text('proforma')
3090 # $main::locale->text('sales_order')
3091 # $main::locale->text('pick_list')
3092 # $main::locale->text('purchase_order')
3093 # $main::locale->text('bin_list')
3094 # $main::locale->text('sales_quotation')
3095 # $main::locale->text('request_quotation')
3096
3097 sub save_history {
3098   $main::lxdebug->enter_sub();
3099
3100   my $self = shift;
3101   my $dbh  = shift || $self->get_standard_dbh;
3102
3103   if(!exists $self->{employee_id}) {
3104     &get_employee($self, $dbh);
3105   }
3106
3107   my $query =
3108    qq|INSERT INTO history_erp (trans_id, employee_id, addition, what_done, snumbers) | .
3109    qq|VALUES (?, (SELECT id FROM employee WHERE login = ?), ?, ?, ?)|;
3110   my @values = (conv_i($self->{id}), $self->{login},
3111                 $self->{addition}, $self->{what_done}, "$self->{snumbers}");
3112   do_query($self, $dbh, $query, @values);
3113
3114   $dbh->commit;
3115
3116   $main::lxdebug->leave_sub();
3117 }
3118
3119 sub get_history {
3120   $main::lxdebug->enter_sub();
3121
3122   my ($self, $dbh, $trans_id, $restriction, $order) = @_;
3123   my ($orderBy, $desc) = split(/\-\-/, $order);
3124   $order = " ORDER BY " . ($order eq "" ? " h.itime " : ($desc == 1 ? $orderBy . " DESC " : $orderBy . " "));
3125   my @tempArray;
3126   my $i = 0;
3127   if ($trans_id ne "") {
3128     my $query =
3129       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 | .
3130       qq|FROM history_erp h | .
3131       qq|LEFT JOIN employee emp ON (emp.id = h.employee_id) | .
3132       qq|WHERE (trans_id = | . $trans_id . qq|) $restriction | .
3133       $order;
3134
3135     my $sth = $dbh->prepare($query) || $self->dberror($query);
3136
3137     $sth->execute() || $self->dberror("$query");
3138
3139     while(my $hash_ref = $sth->fetchrow_hashref()) {
3140       $hash_ref->{addition} = $main::locale->text($hash_ref->{addition});
3141       $hash_ref->{what_done} = $main::locale->text($hash_ref->{what_done});
3142       $hash_ref->{snumbers} =~ s/^.+_(.*)$/$1/g;
3143       $tempArray[$i++] = $hash_ref;
3144     }
3145     $main::lxdebug->leave_sub() and return \@tempArray
3146       if ($i > 0 && $tempArray[0] ne "");
3147   }
3148   $main::lxdebug->leave_sub();
3149   return 0;
3150 }
3151
3152 sub update_defaults {
3153   $main::lxdebug->enter_sub();
3154
3155   my ($self, $myconfig, $fld, $provided_dbh) = @_;
3156
3157   my $dbh;
3158   if ($provided_dbh) {
3159     $dbh = $provided_dbh;
3160   } else {
3161     $dbh = $self->dbconnect_noauto($myconfig);
3162   }
3163   my $query = qq|SELECT $fld FROM defaults FOR UPDATE|;
3164   my $sth   = $dbh->prepare($query);
3165
3166   $sth->execute || $self->dberror($query);
3167   my ($var) = $sth->fetchrow_array;
3168   $sth->finish;
3169
3170   $var   = 0 if !defined($var) || ($var eq '');
3171   $var   = SL::PrefixedNumber->new(number => $var)->get_next;
3172   $query = qq|UPDATE defaults SET $fld = ?|;
3173   do_query($self, $dbh, $query, $var);
3174
3175   if (!$provided_dbh) {
3176     $dbh->commit;
3177     $dbh->disconnect;
3178   }
3179
3180   $main::lxdebug->leave_sub();
3181
3182   return $var;
3183 }
3184
3185 sub update_business {
3186   $main::lxdebug->enter_sub();
3187
3188   my ($self, $myconfig, $business_id, $provided_dbh) = @_;
3189
3190   my $dbh;
3191   if ($provided_dbh) {
3192     $dbh = $provided_dbh;
3193   } else {
3194     $dbh = $self->dbconnect_noauto($myconfig);
3195   }
3196   my $query =
3197     qq|SELECT customernumberinit FROM business
3198        WHERE id = ? FOR UPDATE|;
3199   my ($var) = selectrow_query($self, $dbh, $query, $business_id);
3200
3201   return undef unless $var;
3202
3203   if ($var =~ m/\d+$/) {
3204     my $new_var  = (substr $var, $-[0]) * 1 + 1;
3205     my $len_diff = length($var) - $-[0] - length($new_var);
3206     $var         = substr($var, 0, $-[0]) . ($len_diff > 0 ? '0' x $len_diff : '') . $new_var;
3207
3208   } else {
3209     $var = $var . '1';
3210   }
3211
3212   $query = qq|UPDATE business
3213               SET customernumberinit = ?
3214               WHERE id = ?|;
3215   do_query($self, $dbh, $query, $var, $business_id);
3216
3217   if (!$provided_dbh) {
3218     $dbh->commit;
3219     $dbh->disconnect;
3220   }
3221
3222   $main::lxdebug->leave_sub();
3223
3224   return $var;
3225 }
3226
3227 sub get_partsgroup {
3228   $main::lxdebug->enter_sub();
3229
3230   my ($self, $myconfig, $p) = @_;
3231   my $target = $p->{target} || 'all_partsgroup';
3232
3233   my $dbh = $self->get_standard_dbh($myconfig);
3234
3235   my $query = qq|SELECT DISTINCT pg.id, pg.partsgroup
3236                  FROM partsgroup pg
3237                  JOIN parts p ON (p.partsgroup_id = pg.id) |;
3238   my @values;
3239
3240   if ($p->{searchitems} eq 'part') {
3241     $query .= qq|WHERE p.inventory_accno_id > 0|;
3242   }
3243   if ($p->{searchitems} eq 'service') {
3244     $query .= qq|WHERE p.inventory_accno_id IS NULL|;
3245   }
3246   if ($p->{searchitems} eq 'assembly') {
3247     $query .= qq|WHERE p.assembly = '1'|;
3248   }
3249   if ($p->{searchitems} eq 'labor') {
3250     $query .= qq|WHERE (p.inventory_accno_id > 0) AND (p.income_accno_id IS NULL)|;
3251   }
3252
3253   $query .= qq|ORDER BY partsgroup|;
3254
3255   if ($p->{all}) {
3256     $query = qq|SELECT id, partsgroup FROM partsgroup
3257                 ORDER BY partsgroup|;
3258   }
3259
3260   if ($p->{language_code}) {
3261     $query = qq|SELECT DISTINCT pg.id, pg.partsgroup,
3262                   t.description AS translation
3263                 FROM partsgroup pg
3264                 JOIN parts p ON (p.partsgroup_id = pg.id)
3265                 LEFT JOIN translation t ON ((t.trans_id = pg.id) AND (t.language_code = ?))
3266                 ORDER BY translation|;
3267     @values = ($p->{language_code});
3268   }
3269
3270   $self->{$target} = selectall_hashref_query($self, $dbh, $query, @values);
3271
3272   $main::lxdebug->leave_sub();
3273 }
3274
3275 sub get_pricegroup {
3276   $main::lxdebug->enter_sub();
3277
3278   my ($self, $myconfig, $p) = @_;
3279
3280   my $dbh = $self->get_standard_dbh($myconfig);
3281
3282   my $query = qq|SELECT p.id, p.pricegroup
3283                  FROM pricegroup p|;
3284
3285   $query .= qq| ORDER BY pricegroup|;
3286
3287   if ($p->{all}) {
3288     $query = qq|SELECT id, pricegroup FROM pricegroup
3289                 ORDER BY pricegroup|;
3290   }
3291
3292   $self->{all_pricegroup} = selectall_hashref_query($self, $dbh, $query);
3293
3294   $main::lxdebug->leave_sub();
3295 }
3296
3297 sub all_years {
3298 # usage $form->all_years($myconfig, [$dbh])
3299 # return list of all years where bookings found
3300 # (@all_years)
3301
3302   $main::lxdebug->enter_sub();
3303
3304   my ($self, $myconfig, $dbh) = @_;
3305
3306   $dbh ||= $self->get_standard_dbh($myconfig);
3307
3308   # get years
3309   my $query = qq|SELECT (SELECT MIN(transdate) FROM acc_trans),
3310                    (SELECT MAX(transdate) FROM acc_trans)|;
3311   my ($startdate, $enddate) = selectrow_query($self, $dbh, $query);
3312
3313   if ($myconfig->{dateformat} =~ /^yy/) {
3314     ($startdate) = split /\W/, $startdate;
3315     ($enddate) = split /\W/, $enddate;
3316   } else {
3317     (@_) = split /\W/, $startdate;
3318     $startdate = $_[2];
3319     (@_) = split /\W/, $enddate;
3320     $enddate = $_[2];
3321   }
3322
3323   my @all_years;
3324   $startdate = substr($startdate,0,4);
3325   $enddate = substr($enddate,0,4);
3326
3327   while ($enddate >= $startdate) {
3328     push @all_years, $enddate--;
3329   }
3330
3331   return @all_years;
3332
3333   $main::lxdebug->leave_sub();
3334 }
3335
3336 sub backup_vars {
3337   $main::lxdebug->enter_sub();
3338   my $self = shift;
3339   my @vars = @_;
3340
3341   map { $self->{_VAR_BACKUP}->{$_} = $self->{$_} if exists $self->{$_} } @vars;
3342
3343   $main::lxdebug->leave_sub();
3344 }
3345
3346 sub restore_vars {
3347   $main::lxdebug->enter_sub();
3348
3349   my $self = shift;
3350   my @vars = @_;
3351
3352   map { $self->{$_} = $self->{_VAR_BACKUP}->{$_} if exists $self->{_VAR_BACKUP}->{$_} } @vars;
3353
3354   $main::lxdebug->leave_sub();
3355 }
3356
3357 sub prepare_for_printing {
3358   my ($self) = @_;
3359
3360   my $defaults         = SL::DB::Default->get;
3361
3362   $self->{templates} ||= $defaults->templates;
3363   $self->{formname}  ||= $self->{type};
3364   $self->{media}     ||= 'email';
3365
3366   die "'media' other than 'email', 'file', 'printer' is not supported yet" unless $self->{media} =~ m/^(?:email|file|printer)$/;
3367
3368   # Several fields that used to reside in %::myconfig (stored in
3369   # auth.user_config) are now stored in defaults. Copy them over for
3370   # compatibility.
3371   $self->{$_} = $defaults->$_ for qw(company address taxnumber co_ustid duns sepa_creditor_id);
3372
3373   # set shipto from billto unless set
3374   my $has_shipto = any { $self->{"shipto$_"} } qw(name street zipcode city country contact);
3375   if (!$has_shipto && ($self->{type} =~ m/^(?:purchase_order|request_quotation)$/)) {
3376     $self->{shiptoname}   = $defaults->company;
3377     $self->{shiptostreet} = $defaults->address;
3378   }
3379
3380   my $language = $self->{language} ? '_' . $self->{language} : '';
3381
3382   my ($language_tc, $output_numberformat, $output_dateformat, $output_longdates);
3383   if ($self->{language_id}) {
3384     ($language_tc, $output_numberformat, $output_dateformat, $output_longdates) = AM->get_language_details(\%::myconfig, $self, $self->{language_id});
3385   } else {
3386     $output_dateformat   = $::myconfig{dateformat};
3387     $output_numberformat = $::myconfig{numberformat};
3388     $output_longdates    = 1;
3389   }
3390
3391   # Retrieve accounts for tax calculation.
3392   IC->retrieve_accounts(\%::myconfig, $self, map { $_ => $self->{"id_$_"} } 1 .. $self->{rowcount});
3393
3394   if ($self->{type} =~ /_delivery_order$/) {
3395     DO->order_details(\%::myconfig, $self);
3396   } elsif ($self->{type} =~ /sales_order|sales_quotation|request_quotation|purchase_order/) {
3397     OE->order_details(\%::myconfig, $self);
3398   } else {
3399     IS->invoice_details(\%::myconfig, $self, $::locale);
3400   }
3401
3402   # Chose extension & set source file name
3403   my $extension = 'html';
3404   if ($self->{format} eq 'postscript') {
3405     $self->{postscript}   = 1;
3406     $extension            = 'tex';
3407   } elsif ($self->{"format"} =~ /pdf/) {
3408     $self->{pdf}          = 1;
3409     $extension            = $self->{'format'} =~ m/opendocument/i ? 'odt' : 'tex';
3410   } elsif ($self->{"format"} =~ /opendocument/) {
3411     $self->{opendocument} = 1;
3412     $extension            = 'odt';
3413   } elsif ($self->{"format"} =~ /excel/) {
3414     $self->{excel}        = 1;
3415     $extension            = 'xls';
3416   }
3417
3418   my $printer_code    = $self->{printer_code} ? '_' . $self->{printer_code} : '';
3419   my $email_extension = $self->{media} eq 'email' && -f ($defaults->templates . "/$self->{formname}_email${language}.${extension}") ? '_email' : '';
3420   $self->{IN}         = "$self->{formname}${email_extension}${language}${printer_code}.${extension}";
3421
3422   # Format dates.
3423   $self->format_dates($output_dateformat, $output_longdates,
3424                       qw(invdate orddate quodate pldate duedate reqdate transdate shippingdate deliverydate validitydate paymentdate datepaid
3425                          transdate_oe deliverydate_oe employee_startdate employee_enddate),
3426                       grep({ /^(?:datepaid|transdate_oe|reqdate|deliverydate|deliverydate_oe|transdate)_\d+$/ } keys(%{$self})));
3427
3428   $self->reformat_numbers($output_numberformat, 2,
3429                           qw(invtotal ordtotal quototal subtotal linetotal listprice sellprice netprice discount tax taxbase total paid),
3430                           grep({ /^(?:linetotal|listprice|sellprice|netprice|taxbase|discount|paid|subtotal|total|tax)_\d+$/ } keys(%{$self})));
3431
3432   $self->reformat_numbers($output_numberformat, undef, qw(qty price_factor), grep({ /^qty_\d+$/} keys(%{$self})));
3433
3434   my ($cvar_date_fields, $cvar_number_fields) = CVar->get_field_format_list('module' => 'CT', 'prefix' => 'vc_');
3435
3436   if (scalar @{ $cvar_date_fields }) {
3437     $self->format_dates($output_dateformat, $output_longdates, @{ $cvar_date_fields });
3438   }
3439
3440   while (my ($precision, $field_list) = each %{ $cvar_number_fields }) {
3441     $self->reformat_numbers($output_numberformat, $precision, @{ $field_list });
3442   }
3443
3444   return $self;
3445 }
3446
3447 sub format_dates {
3448   my ($self, $dateformat, $longformat, @indices) = @_;
3449
3450   $dateformat ||= $::myconfig{dateformat};
3451
3452   foreach my $idx (@indices) {
3453     if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) {
3454       for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) {
3455         $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $::locale->reformat_date(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i], $dateformat, $longformat);
3456       }
3457     }
3458
3459     next unless defined $self->{$idx};
3460
3461     if (!ref($self->{$idx})) {
3462       $self->{$idx} = $::locale->reformat_date(\%::myconfig, $self->{$idx}, $dateformat, $longformat);
3463
3464     } elsif (ref($self->{$idx}) eq "ARRAY") {
3465       for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) {
3466         $self->{$idx}->[$i] = $::locale->reformat_date(\%::myconfig, $self->{$idx}->[$i], $dateformat, $longformat);
3467       }
3468     }
3469   }
3470 }
3471
3472 sub reformat_numbers {
3473   my ($self, $numberformat, $places, @indices) = @_;
3474
3475   return if !$numberformat || ($numberformat eq $::myconfig{numberformat});
3476
3477   foreach my $idx (@indices) {
3478     if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) {
3479       for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) {
3480         $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $self->parse_amount(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i]);
3481       }
3482     }
3483
3484     next unless defined $self->{$idx};
3485
3486     if (!ref($self->{$idx})) {
3487       $self->{$idx} = $self->parse_amount(\%::myconfig, $self->{$idx});
3488
3489     } elsif (ref($self->{$idx}) eq "ARRAY") {
3490       for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) {
3491         $self->{$idx}->[$i] = $self->parse_amount(\%::myconfig, $self->{$idx}->[$i]);
3492       }
3493     }
3494   }
3495
3496   my $saved_numberformat    = $::myconfig{numberformat};
3497   $::myconfig{numberformat} = $numberformat;
3498
3499   foreach my $idx (@indices) {
3500     if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) {
3501       for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) {
3502         $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $self->format_amount(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i], $places);
3503       }
3504     }
3505
3506     next unless defined $self->{$idx};
3507
3508     if (!ref($self->{$idx})) {
3509       $self->{$idx} = $self->format_amount(\%::myconfig, $self->{$idx}, $places);
3510
3511     } elsif (ref($self->{$idx}) eq "ARRAY") {
3512       for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) {
3513         $self->{$idx}->[$i] = $self->format_amount(\%::myconfig, $self->{$idx}->[$i], $places);
3514       }
3515     }
3516   }
3517
3518   $::myconfig{numberformat} = $saved_numberformat;
3519 }
3520
3521 sub layout {
3522   my ($self) = @_;
3523   $::lxdebug->enter_sub;
3524
3525   my %style_to_script_map = (
3526     v3  => 'v3',
3527     neu => 'new',
3528   );
3529
3530   my $menu_script = $style_to_script_map{$::myconfig{menustyle}} || '';
3531
3532   package main;
3533   require "bin/mozilla/menu$menu_script.pl";
3534   package Form;
3535   require SL::Controller::FrameHeader;
3536
3537
3538   my $layout = SL::Controller::FrameHeader->new->action_header . ::render();
3539
3540   $::lxdebug->leave_sub;
3541   return $layout;
3542 }
3543
3544 1;
3545
3546 __END__
3547
3548 =head1 NAME
3549
3550 SL::Form.pm - main data object.
3551
3552 =head1 SYNOPSIS
3553
3554 This is the main data object of kivitendo.
3555 Unfortunately it also acts as a god object for certain data retrieval procedures used in the entry points.
3556 Points of interest for a beginner are:
3557
3558  - $form->error            - renders a generic error in html. accepts an error message
3559  - $form->get_standard_dbh - returns a database connection for the
3560
3561 =head1 SPECIAL FUNCTIONS
3562
3563 =head2 C<update_business> PARAMS
3564
3565 PARAMS (not named):
3566  \%config,     - config hashref
3567  $business_id, - business id
3568  $dbh          - optional database handle
3569
3570 handles business (thats customer/vendor types) sequences.
3571
3572 special behaviour for empty strings in customerinitnumber field:
3573 will in this case not increase the value, and return undef.
3574
3575 =head2 C<redirect_header> $url
3576
3577 Generates a HTTP redirection header for the new C<$url>. Constructs an
3578 absolute URL including scheme, host name and port. If C<$url> is a
3579 relative URL then it is considered relative to kivitendo base URL.
3580
3581 This function C<die>s if headers have already been created with
3582 C<$::form-E<gt>header>.
3583
3584 Examples:
3585
3586   print $::form->redirect_header('oe.pl?action=edit&id=1234');
3587   print $::form->redirect_header('http://www.lx-office.org/');
3588
3589 =head2 C<header>
3590
3591 Generates a general purpose http/html header and includes most of the scripts
3592 and stylesheets needed. Stylesheets can be added with L<use_stylesheet>.
3593
3594 Only one header will be generated. If the method was already called in this
3595 request it will not output anything and return undef. Also if no
3596 HTTP_USER_AGENT is found, no header is generated.
3597
3598 Although header does not accept parameters itself, it will honor special
3599 hashkeys of its Form instance:
3600
3601 =over 4
3602
3603 =item refresh_time
3604
3605 =item refresh_url
3606
3607 If one of these is set, a http-equiv refresh is generated. Missing parameters
3608 default to 3 seconds and the refering url.
3609
3610 =item stylesheet
3611
3612 Either a scalar or an array ref. Will be inlined into the header. Add
3613 stylesheets with the L<use_stylesheet> function.
3614
3615 =item landscape
3616
3617 If true, a css snippet will be generated that sets the page in landscape mode.
3618
3619 =item favicon
3620
3621 Used to override the default favicon.
3622
3623 =item title
3624
3625 A html page title will be generated from this
3626
3627 =back
3628
3629 =cut