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