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