Form::parse_template: nur dann in WebDAV-Ordner kopieren, wenn Typ bekannt ist
[kivitendo-erp.git] / SL / Form.pm
1 #========= ===========================================================
2 # LX-Office ERP
3 # Copyright (C) 2004
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
6 #
7 #=====================================================================
8 # SQL-Ledger Accounting
9 # Copyright (C) 1998-2002
10 #
11 #  Author: Dieter Simader
12 #   Email: dsimader@sql-ledger.org
13 #     Web: http://www.sql-ledger.org
14 #
15 # Contributors: Thomas Bayen <bayen@gmx.de>
16 #               Antti Kaihola <akaihola@siba.fi>
17 #               Moritz Bunkus (tex code)
18 #
19 # This program is free software; you can redistribute it and/or modify
20 # it under the terms of the GNU General Public License as published by
21 # the Free Software Foundation; either version 2 of the License, or
22 # (at your option) any later version.
23 #
24 # This program is distributed in the hope that it will be useful,
25 # but WITHOUT ANY WARRANTY; without even the implied warranty of
26 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27 # GNU General Public License for more details.
28 # You should have received a copy of the GNU General Public License
29 # along with this program; if not, write to the Free Software
30 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
31 #======================================================================
32 # Utilities for parsing forms
33 # and supporting routines for linking account numbers
34 # used in AR, AP and IS, IR modules
35 #
36 #======================================================================
37
38 package Form;
39
40 use Carp;
41 use Data::Dumper;
42
43 use CGI;
44 use Cwd;
45 use Encode;
46 use File::Copy;
47 use IO::File;
48 use SL::Auth;
49 use SL::Auth::DB;
50 use SL::Auth::LDAP;
51 use SL::AM;
52 use SL::Common;
53 use SL::CVar;
54 use SL::DB;
55 use SL::DBConnect;
56 use SL::DBUtils;
57 use SL::DB::Customer;
58 use SL::DB::Default;
59 use SL::DB::PaymentTerm;
60 use SL::DB::Vendor;
61 use SL::DO;
62 use SL::IC;
63 use SL::IS;
64 use SL::Layout::Dispatcher;
65 use SL::Locale;
66 use SL::Mailer;
67 use SL::Menu;
68 use SL::MoreCommon qw(uri_encode uri_decode);
69 use SL::OE;
70 use SL::PrefixedNumber;
71 use SL::Request;
72 use SL::Template;
73 use SL::User;
74 use SL::X;
75 use Template;
76 use URI;
77 use List::Util qw(first max min sum);
78 use List::MoreUtils qw(all any apply);
79
80 use strict;
81
82 my $standard_dbh;
83
84 END {
85   disconnect_standard_dbh();
86 }
87
88 sub disconnect_standard_dbh {
89   return unless $standard_dbh;
90   $standard_dbh->disconnect();
91   undef $standard_dbh;
92 }
93
94 sub read_version {
95   my ($self) = @_;
96
97   open VERSION_FILE, "VERSION";                 # New but flexible code reads version from VERSION-file
98   my $version =  <VERSION_FILE>;
99   $version    =~ s/[^0-9A-Za-z\.\_\-]//g; # only allow numbers, letters, points, underscores and dashes. Prevents injecting of malicious code.
100   close VERSION_FILE;
101
102   return $version;
103 }
104
105 sub new {
106   $main::lxdebug->enter_sub();
107
108   my $type = shift;
109
110   my $self = {};
111
112   no warnings 'once';
113   if ($LXDebug::watch_form) {
114     require SL::Watchdog;
115     tie %{ $self }, 'SL::Watchdog';
116   }
117
118   bless $self, $type;
119
120   $self->{version} = $self->read_version;
121
122   $main::lxdebug->leave_sub();
123
124   return $self;
125 }
126
127 sub read_cgi_input {
128   my ($self) = @_;
129   SL::Request::read_cgi_input($self);
130 }
131
132 sub _flatten_variables_rec {
133   $main::lxdebug->enter_sub(2);
134
135   my $self   = shift;
136   my $curr   = shift;
137   my $prefix = shift;
138   my $key    = shift;
139
140   my @result;
141
142   if ('' eq ref $curr->{$key}) {
143     @result = ({ 'key' => $prefix . $key, 'value' => $curr->{$key} });
144
145   } elsif ('HASH' eq ref $curr->{$key}) {
146     foreach my $hash_key (sort keys %{ $curr->{$key} }) {
147       push @result, $self->_flatten_variables_rec($curr->{$key}, $prefix . $key . '.', $hash_key);
148     }
149
150   } else {
151     foreach my $idx (0 .. scalar @{ $curr->{$key} } - 1) {
152       my $first_array_entry = 1;
153
154       my $element = $curr->{$key}[$idx];
155
156       if ('HASH' eq ref $element) {
157         foreach my $hash_key (sort keys %{ $element }) {
158           push @result, $self->_flatten_variables_rec($element, $prefix . $key . ($first_array_entry ? '[+].' : '[].'), $hash_key);
159           $first_array_entry = 0;
160         }
161       } else {
162         @result = ({ 'key' => $prefix . $key . ($first_array_entry ? '[+]' : '[]'), 'value' => $element });
163       }
164     }
165   }
166
167   $main::lxdebug->leave_sub(2);
168
169   return @result;
170 }
171
172 sub flatten_variables {
173   $main::lxdebug->enter_sub(2);
174
175   my $self = shift;
176   my @keys = @_;
177
178   my @variables;
179
180   foreach (@keys) {
181     push @variables, $self->_flatten_variables_rec($self, '', $_);
182   }
183
184   $main::lxdebug->leave_sub(2);
185
186   return @variables;
187 }
188
189 sub flatten_standard_variables {
190   $main::lxdebug->enter_sub(2);
191
192   my $self      = shift;
193   my %skip_keys = map { $_ => 1 } (qw(login password header stylesheet titlebar version), @_);
194
195   my @variables;
196
197   foreach (grep { ! $skip_keys{$_} } keys %{ $self }) {
198     push @variables, $self->_flatten_variables_rec($self, '', $_);
199   }
200
201   $main::lxdebug->leave_sub(2);
202
203   return @variables;
204 }
205
206 sub debug {
207   $main::lxdebug->enter_sub();
208
209   my ($self) = @_;
210
211   print "\n";
212
213   map { print "$_ = $self->{$_}\n" } (sort keys %{$self});
214
215   $main::lxdebug->leave_sub();
216 }
217
218 sub dumper {
219   $main::lxdebug->enter_sub(2);
220
221   my $self          = shift;
222   my $password      = $self->{password};
223
224   $self->{password} = 'X' x 8;
225
226   local $Data::Dumper::Sortkeys = 1;
227   my $output                    = Dumper($self);
228
229   $self->{password} = $password;
230
231   $main::lxdebug->leave_sub(2);
232
233   return $output;
234 }
235
236 sub escape {
237   my ($self, $str) = @_;
238
239   return uri_encode($str);
240 }
241
242 sub unescape {
243   my ($self, $str) = @_;
244
245   return uri_decode($str);
246 }
247
248 sub quote {
249   $main::lxdebug->enter_sub();
250   my ($self, $str) = @_;
251
252   if ($str && !ref($str)) {
253     $str =~ s/\"/&quot;/g;
254   }
255
256   $main::lxdebug->leave_sub();
257
258   return $str;
259 }
260
261 sub unquote {
262   $main::lxdebug->enter_sub();
263   my ($self, $str) = @_;
264
265   if ($str && !ref($str)) {
266     $str =~ s/&quot;/\"/g;
267   }
268
269   $main::lxdebug->leave_sub();
270
271   return $str;
272 }
273
274 sub hide_form {
275   $main::lxdebug->enter_sub();
276   my $self = shift;
277
278   if (@_) {
279     map({ print($::request->{cgi}->hidden("-name" => $_, "-default" => $self->{$_}) . "\n"); } @_);
280   } else {
281     for (sort keys %$self) {
282       next if (($_ eq "header") || (ref($self->{$_}) ne ""));
283       print($::request->{cgi}->hidden("-name" => $_, "-default" => $self->{$_}) . "\n");
284     }
285   }
286   $main::lxdebug->leave_sub();
287 }
288
289 sub throw_on_error {
290   my ($self, $code) = @_;
291   local $self->{__ERROR_HANDLER} = sub { die SL::X::FormError->new($_[0]) };
292   $code->();
293 }
294
295 sub error {
296   $main::lxdebug->enter_sub();
297
298   $main::lxdebug->show_backtrace();
299
300   my ($self, $msg) = @_;
301
302   if ($self->{__ERROR_HANDLER}) {
303     $self->{__ERROR_HANDLER}->($msg);
304
305   } elsif ($ENV{HTTP_USER_AGENT}) {
306     $msg =~ s/\n/<br>/g;
307     $self->show_generic_error($msg);
308
309   } else {
310     print STDERR "Error: $msg\n";
311     ::end_of_request();
312   }
313
314   $main::lxdebug->leave_sub();
315 }
316
317 sub info {
318   $main::lxdebug->enter_sub();
319
320   my ($self, $msg) = @_;
321
322   if ($ENV{HTTP_USER_AGENT}) {
323     $self->header;
324     print $self->parse_html_template('generic/form_info', { message => $msg });
325
326   } elsif ($self->{info_function}) {
327     &{ $self->{info_function} }($msg);
328   } else {
329     print "$msg\n";
330   }
331
332   $main::lxdebug->leave_sub();
333 }
334
335 # calculates the number of rows in a textarea based on the content and column number
336 # can be capped with maxrows
337 sub numtextrows {
338   $main::lxdebug->enter_sub();
339   my ($self, $str, $cols, $maxrows, $minrows) = @_;
340
341   $minrows ||= 1;
342
343   my $rows   = sum map { int((length() - 2) / $cols) + 1 } split /\r/, $str;
344   $maxrows ||= $rows;
345
346   $main::lxdebug->leave_sub();
347
348   return max(min($rows, $maxrows), $minrows);
349 }
350
351 sub dberror {
352   $main::lxdebug->enter_sub();
353
354   my ($self, $msg) = @_;
355
356   $self->error("$msg\n" . $DBI::errstr);
357
358   $main::lxdebug->leave_sub();
359 }
360
361 sub isblank {
362   $main::lxdebug->enter_sub();
363
364   my ($self, $name, $msg) = @_;
365
366   my $curr = $self;
367   foreach my $part (split m/\./, $name) {
368     if (!$curr->{$part} || ($curr->{$part} =~ /^\s*$/)) {
369       $self->error($msg);
370     }
371     $curr = $curr->{$part};
372   }
373
374   $main::lxdebug->leave_sub();
375 }
376
377 sub _get_request_uri {
378   my $self = shift;
379
380   return URI->new($ENV{HTTP_REFERER})->canonical() if $ENV{HTTP_X_FORWARDED_FOR};
381   return URI->new                                  if !$ENV{REQUEST_URI}; # for testing
382
383   my $scheme =  $ENV{HTTPS} && (lc $ENV{HTTPS} eq 'on') ? 'https' : 'http';
384   my $port   =  $ENV{SERVER_PORT};
385   $port      =  undef if (($scheme eq 'http' ) && ($port == 80))
386                       || (($scheme eq 'https') && ($port == 443));
387
388   my $uri    =  URI->new("${scheme}://");
389   $uri->scheme($scheme);
390   $uri->port($port);
391   $uri->host($ENV{HTTP_HOST} || $ENV{SERVER_ADDR});
392   $uri->path_query($ENV{REQUEST_URI});
393   $uri->query('');
394
395   return $uri;
396 }
397
398 sub _add_to_request_uri {
399   my $self              = shift;
400
401   my $relative_new_path = shift;
402   my $request_uri       = shift || $self->_get_request_uri;
403   my $relative_new_uri  = URI->new($relative_new_path);
404   my @request_segments  = $request_uri->path_segments;
405
406   my $new_uri           = $request_uri->clone;
407   $new_uri->path_segments(@request_segments[0..scalar(@request_segments) - 2], $relative_new_uri->path_segments);
408
409   return $new_uri;
410 }
411
412 sub create_http_response {
413   $main::lxdebug->enter_sub();
414
415   my $self     = shift;
416   my %params   = @_;
417
418   my $cgi      = $::request->{cgi};
419
420   my $session_cookie;
421   if (defined $main::auth) {
422     my $uri      = $self->_get_request_uri;
423     my @segments = $uri->path_segments;
424     pop @segments;
425     $uri->path_segments(@segments);
426
427     my $session_cookie_value = $main::auth->get_session_id();
428
429     if ($session_cookie_value) {
430       $session_cookie = $cgi->cookie('-name'   => $main::auth->get_session_cookie_name(),
431                                      '-value'  => $session_cookie_value,
432                                      '-path'   => $uri->path,
433                                      '-secure' => $ENV{HTTPS});
434     }
435   }
436
437   my %cgi_params = ('-type' => $params{content_type});
438   $cgi_params{'-charset'} = $params{charset} if ($params{charset});
439   $cgi_params{'-cookie'}  = $session_cookie  if ($session_cookie);
440
441   map { $cgi_params{'-' . $_} = $params{$_} if exists $params{$_} } qw(content_disposition content_length);
442
443   my $output = $cgi->header(%cgi_params);
444
445   $main::lxdebug->leave_sub();
446
447   return $output;
448 }
449
450 sub header {
451   $::lxdebug->enter_sub;
452
453   my ($self, %params) = @_;
454   my @header;
455
456   $::lxdebug->leave_sub and return if !$ENV{HTTP_USER_AGENT} || $self->{header}++;
457
458   if ($params{no_layout}) {
459     $::request->{layout} = SL::Layout::Dispatcher->new(style => 'none');
460   }
461
462   my $layout = $::request->{layout};
463
464   # standard css for all
465   # this should gradually move to the layouts that need it
466   $layout->use_stylesheet("$_.css") for qw(
467     main menu list_accounts jquery.autocomplete
468     jquery.multiselect2side
469     ui-lightness/jquery-ui
470     jquery-ui.custom
471   );
472
473   $layout->use_javascript("$_.js") for (qw(
474     jquery jquery-ui jquery.cookie jquery.checkall jquery.download
475     jquery/jquery.form client_js
476     common part_selection switchmenuframe autocomplete_part
477   ), "jquery/ui/i18n/jquery.ui.datepicker-$::myconfig{countrycode}");
478
479   $self->{favicon} ||= "favicon.ico";
480   $self->{titlebar} = join ' - ', grep $_, $self->{title}, $self->{login}, $::myconfig{dbname}, $self->{version} if $self->{title} || !$self->{titlebar};
481
482   # build includes
483   if ($self->{refresh_url} || $self->{refresh_time}) {
484     my $refresh_time = $self->{refresh_time} || 3;
485     my $refresh_url  = $self->{refresh_url}  || $ENV{REFERER};
486     push @header, "<meta http-equiv='refresh' content='$refresh_time;$refresh_url'>";
487   }
488
489   my $auto_reload_resources_param = $layout->auto_reload_resources_param;
490
491   push @header, map { qq|<link rel="stylesheet" href="${_}${auto_reload_resources_param}" type="text/css" title="Stylesheet">| } $layout->stylesheets;
492   push @header, "<style type='text/css'>\@page { size:landscape; }</style> "                     if $self->{landscape};
493   push @header, "<link rel='shortcut icon' href='$self->{favicon}' type='image/x-icon'>"         if -f $self->{favicon};
494   push @header, map { qq|<script type="text/javascript" src="${_}${auto_reload_resources_param}"></script>| }                    $layout->javascripts;
495   push @header, $self->{javascript} if $self->{javascript};
496   push @header, map { $_->show_javascript } @{ $self->{AJAX} || [] };
497
498   my  %doctypes = (
499     strict       => qq|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">|,
500     transitional => qq|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">|,
501     frameset     => qq|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">|,
502     html5        => qq|<!DOCTYPE html>|,
503   );
504
505   # output
506   print $self->create_http_response(content_type => 'text/html', charset => 'UTF-8');
507   print $doctypes{$params{doctype} || 'transitional'}, $/;
508   print <<EOT;
509 <html>
510  <head>
511   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
512   <title>$self->{titlebar}</title>
513 EOT
514   print "  $_\n" for @header;
515   print <<EOT;
516   <meta name="robots" content="noindex,nofollow">
517  </head>
518  <body>
519
520 EOT
521   print $::request->{layout}->pre_content;
522   print $::request->{layout}->start_content;
523
524   $layout->header_done;
525
526   $::lxdebug->leave_sub;
527 }
528
529 sub footer {
530   return unless $::request->{layout}->need_footer;
531
532   print $::request->{layout}->end_content;
533   print $::request->{layout}->post_content;
534
535   if (my @inline_scripts = $::request->{layout}->javascripts_inline) {
536     print "<script type='text/javascript'>@inline_scripts</script>\n";
537   }
538
539   print <<EOL
540  </body>
541 </html>
542 EOL
543 }
544
545 sub ajax_response_header {
546   $main::lxdebug->enter_sub();
547
548   my ($self) = @_;
549
550   my $output = $::request->{cgi}->header('-charset' => 'UTF-8');
551
552   $main::lxdebug->leave_sub();
553
554   return $output;
555 }
556
557 sub redirect_header {
558   my $self     = shift;
559   my $new_url  = shift;
560
561   my $base_uri = $self->_get_request_uri;
562   my $new_uri  = URI->new_abs($new_url, $base_uri);
563
564   die "Headers already sent" if $self->{header};
565   $self->{header} = 1;
566
567   return $::request->{cgi}->redirect($new_uri);
568 }
569
570 sub set_standard_title {
571   $::lxdebug->enter_sub;
572   my $self = shift;
573
574   $self->{titlebar}  = "kivitendo " . $::locale->text('Version') . " $self->{version}";
575   $self->{titlebar} .= "- $::myconfig{name}"   if $::myconfig{name};
576   $self->{titlebar} .= "- $::myconfig{dbname}" if $::myconfig{name};
577
578   $::lxdebug->leave_sub;
579 }
580
581 sub _prepare_html_template {
582   $main::lxdebug->enter_sub();
583
584   my ($self, $file, $additional_params) = @_;
585   my $language;
586
587   if (!%::myconfig || !$::myconfig{"countrycode"}) {
588     $language = $::lx_office_conf{system}->{language};
589   } else {
590     $language = $main::myconfig{"countrycode"};
591   }
592   $language = "de" unless ($language);
593
594   if (-f "templates/webpages/${file}.html") {
595     $file = "templates/webpages/${file}.html";
596
597   } else {
598     my $info = "Web page template '${file}' not found.\n";
599     print qq|<pre>$info</pre>|;
600     ::end_of_request();
601   }
602
603   if ($self->{"DEBUG"}) {
604     $additional_params->{"DEBUG"} = $self->{"DEBUG"};
605   }
606
607   if ($additional_params->{"DEBUG"}) {
608     $additional_params->{"DEBUG"} =
609       "<br><em>DEBUG INFORMATION:</em><pre>" . $additional_params->{"DEBUG"} . "</pre>";
610   }
611
612   if (%main::myconfig) {
613     $::myconfig{jsc_dateformat} = apply {
614       s/d+/\%d/gi;
615       s/m+/\%m/gi;
616       s/y+/\%Y/gi;
617     } $::myconfig{"dateformat"};
618     $additional_params->{"myconfig"} ||= \%::myconfig;
619     map { $additional_params->{"myconfig_${_}"} = $main::myconfig{$_}; } keys %::myconfig;
620   }
621
622   $additional_params->{INSTANCE_CONF} = $::instance_conf;
623
624   if (my $debug_options = $::lx_office_conf{debug}{options}) {
625     map { $additional_params->{'DEBUG_' . uc($_)} = $debug_options->{$_} } keys %$debug_options;
626   }
627
628   if ($main::auth && $main::auth->{RIGHTS} && $main::auth->{RIGHTS}->{$self->{login}}) {
629     while (my ($key, $value) = each %{ $main::auth->{RIGHTS}->{$self->{login}} }) {
630       $additional_params->{"AUTH_RIGHTS_" . uc($key)} = $value;
631     }
632   }
633
634   $main::lxdebug->leave_sub();
635
636   return $file;
637 }
638
639 sub parse_html_template {
640   $main::lxdebug->enter_sub();
641
642   my ($self, $file, $additional_params) = @_;
643
644   $additional_params ||= { };
645
646   my $real_file = $self->_prepare_html_template($file, $additional_params);
647   my $template  = $self->template || $self->init_template;
648
649   map { $additional_params->{$_} ||= $self->{$_} } keys %{ $self };
650
651   my $output;
652   $template->process($real_file, $additional_params, \$output) || die $template->error;
653
654   $main::lxdebug->leave_sub();
655
656   return $output;
657 }
658
659 sub init_template {
660   my $self = shift;
661
662   return $self->template if $self->template;
663
664   # Force scripts/locales.pl to pick up the exception handling template.
665   # parse_html_template('generic/exception')
666   return $self->template(Template->new({
667      'INTERPOLATE'  => 0,
668      'EVAL_PERL'    => 0,
669      'ABSOLUTE'     => 1,
670      'CACHE_SIZE'   => 0,
671      'PLUGIN_BASE'  => 'SL::Template::Plugin',
672      'INCLUDE_PATH' => '.:templates/webpages',
673      'COMPILE_EXT'  => '.tcc',
674      'COMPILE_DIR'  => $::lx_office_conf{paths}->{userspath} . '/templates-cache',
675      'ERROR'        => 'templates/webpages/generic/exception.html',
676   })) || die;
677 }
678
679 sub template {
680   my $self = shift;
681   $self->{template_object} = shift if @_;
682   return $self->{template_object};
683 }
684
685 sub show_generic_error {
686   $main::lxdebug->enter_sub();
687
688   my ($self, $error, %params) = @_;
689
690   if ($self->{__ERROR_HANDLER}) {
691     $self->{__ERROR_HANDLER}->($error);
692     $main::lxdebug->leave_sub();
693     return;
694   }
695
696   if ($::request->is_ajax) {
697     $::lxdebug->message(0, "trying to render AJAX response...");
698     SL::ClientJS->new
699       ->error($error)
700       ->render(SL::Controller::Base->new);
701     ::end_of_request();
702   }
703
704   my $add_params = {
705     'title_error' => $params{title},
706     'label_error' => $error,
707   };
708
709   if ($params{action}) {
710     my @vars;
711
712     map { delete($self->{$_}); } qw(action);
713     map { push @vars, { "name" => $_, "value" => $self->{$_} } if (!ref($self->{$_})); } keys %{ $self };
714
715     $add_params->{SHOW_BUTTON}  = 1;
716     $add_params->{BUTTON_LABEL} = $params{label} || $params{action};
717     $add_params->{VARIABLES}    = \@vars;
718
719   } elsif ($params{back_button}) {
720     $add_params->{SHOW_BACK_BUTTON} = 1;
721   }
722
723   $self->{title} = $params{title} if $params{title};
724
725   $self->header();
726   print $self->parse_html_template("generic/error", $add_params);
727
728   print STDERR "Error: $error\n";
729
730   $main::lxdebug->leave_sub();
731
732   ::end_of_request();
733 }
734
735 sub show_generic_information {
736   $main::lxdebug->enter_sub();
737
738   my ($self, $text, $title) = @_;
739
740   my $add_params = {
741     'title_information' => $title,
742     'label_information' => $text,
743   };
744
745   $self->{title} = $title if ($title);
746
747   $self->header();
748   print $self->parse_html_template("generic/information", $add_params);
749
750   $main::lxdebug->leave_sub();
751
752   ::end_of_request();
753 }
754
755 sub _store_redirect_info_in_session {
756   my ($self) = @_;
757
758   return unless $self->{callback} =~ m:^ ( [^\?/]+ \.pl ) \? (.+) :x;
759
760   my ($controller, $params) = ($1, $2);
761   my $form                  = { map { map { $self->unescape($_) } split /=/, $_, 2 } split m/\&/, $params };
762   $self->{callback}         = "${controller}?RESTORE_FORM_FROM_SESSION_ID=" . $::auth->save_form_in_session(form => $form);
763 }
764
765 sub redirect {
766   $main::lxdebug->enter_sub();
767
768   my ($self, $msg) = @_;
769
770   if (!$self->{callback}) {
771     $self->info($msg);
772
773   } else {
774     $self->_store_redirect_info_in_session;
775     print $::form->redirect_header($self->{callback});
776   }
777
778   ::end_of_request();
779
780   $main::lxdebug->leave_sub();
781 }
782
783 # sort of columns removed - empty sub
784 sub sort_columns {
785   $main::lxdebug->enter_sub();
786
787   my ($self, @columns) = @_;
788
789   $main::lxdebug->leave_sub();
790
791   return @columns;
792 }
793 #
794 sub format_amount {
795   $main::lxdebug->enter_sub(2);
796
797   my ($self, $myconfig, $amount, $places, $dash) = @_;
798   $amount ||= 0;
799   $dash   ||= '';
800   my $neg = $amount < 0;
801   my $force_places = defined $places && $places >= 0;
802
803   $amount = $self->round_amount($amount, abs $places) if $force_places;
804   $amount = sprintf "%.*f", ($force_places ? $places : 10), abs $amount; # 6 is default for %fa
805
806   # before the sprintf amount was a number, afterwards it's a string. because of the dynamic nature of perl
807   # this is easy to confuse, so keep in mind: before this comment no s///, m//, concat or other strong ops on
808   # $amount. after this comment no +,-,*,/,abs. it will only introduce subtle bugs.
809
810   $amount =~ s/0*$// unless defined $places && $places == 0;             # cull trailing 0s
811
812   my @d = map { s/\d//g; reverse split // } my $tmp = $myconfig->{numberformat}; # get delim chars
813   my @p = split(/\./, $amount);                                          # split amount at decimal point
814
815   $p[0] =~ s/\B(?=(...)*$)/$d[1]/g if $d[1];                             # add 1,000 delimiters
816   $amount = $p[0];
817   if ($places || $p[1]) {
818     $amount .= $d[0]
819             .  ( $p[1] || '' )
820             .  (0 x (abs($places || 0) - length ($p[1]||'')));           # pad the fraction
821   }
822
823   $amount = do {
824     ($dash =~ /-/)    ? ($neg ? "($amount)"                            : "$amount" )                              :
825     ($dash =~ /DRCR/) ? ($neg ? "$amount " . $main::locale->text('DR') : "$amount " . $main::locale->text('CR') ) :
826                         ($neg ? "-$amount"                             : "$amount" )                              ;
827   };
828
829   $main::lxdebug->leave_sub(2);
830   return $amount;
831 }
832
833 sub format_amount_units {
834   $main::lxdebug->enter_sub();
835
836   my $self             = shift;
837   my %params           = @_;
838
839   my $myconfig         = \%main::myconfig;
840   my $amount           = $params{amount} * 1;
841   my $places           = $params{places};
842   my $part_unit_name   = $params{part_unit};
843   my $amount_unit_name = $params{amount_unit};
844   my $conv_units       = $params{conv_units};
845   my $max_places       = $params{max_places};
846
847   if (!$part_unit_name) {
848     $main::lxdebug->leave_sub();
849     return '';
850   }
851
852   my $all_units        = AM->retrieve_all_units;
853
854   if (('' eq ref $conv_units) && ($conv_units =~ /convertible/)) {
855     $conv_units = AM->convertible_units($all_units, $part_unit_name, $conv_units eq 'convertible_not_smaller');
856   }
857
858   if (!scalar @{ $conv_units }) {
859     my $result = $self->format_amount($myconfig, $amount, $places, undef, $max_places) . " " . $part_unit_name;
860     $main::lxdebug->leave_sub();
861     return $result;
862   }
863
864   my $part_unit  = $all_units->{$part_unit_name};
865   my $conv_unit  = ($amount_unit_name && ($amount_unit_name ne $part_unit_name)) ? $all_units->{$amount_unit_name} : $part_unit;
866
867   $amount       *= $conv_unit->{factor};
868
869   my @values;
870   my $num;
871
872   foreach my $unit (@$conv_units) {
873     my $last = $unit->{name} eq $part_unit->{name};
874     if (!$last) {
875       $num     = int($amount / $unit->{factor});
876       $amount -= $num * $unit->{factor};
877     }
878
879     if ($last ? $amount : $num) {
880       push @values, { "unit"   => $unit->{name},
881                       "amount" => $last ? $amount / $unit->{factor} : $num,
882                       "places" => $last ? $places : 0 };
883     }
884
885     last if $last;
886   }
887
888   if (!@values) {
889     push @values, { "unit"   => $part_unit_name,
890                     "amount" => 0,
891                     "places" => 0 };
892   }
893
894   my $result = join " ", map { $self->format_amount($myconfig, $_->{amount}, $_->{places}, undef, $max_places), $_->{unit} } @values;
895
896   $main::lxdebug->leave_sub();
897
898   return $result;
899 }
900
901 sub format_string {
902   $main::lxdebug->enter_sub(2);
903
904   my $self  = shift;
905   my $input = shift;
906
907   $input =~ s/(^|[^\#]) \#  (\d+)  /$1$_[$2 - 1]/gx;
908   $input =~ s/(^|[^\#]) \#\{(\d+)\}/$1$_[$2 - 1]/gx;
909   $input =~ s/\#\#/\#/g;
910
911   $main::lxdebug->leave_sub(2);
912
913   return $input;
914 }
915
916 #
917
918 sub parse_amount {
919   $main::lxdebug->enter_sub(2);
920
921   my ($self, $myconfig, $amount) = @_;
922
923   if (!defined($amount) || ($amount eq '')) {
924     $main::lxdebug->leave_sub(2);
925     return 0;
926   }
927
928   if (   ($myconfig->{numberformat} eq '1.000,00')
929       || ($myconfig->{numberformat} eq '1000,00')) {
930     $amount =~ s/\.//g;
931     $amount =~ s/,/\./g;
932   }
933
934   if ($myconfig->{numberformat} eq "1'000.00") {
935     $amount =~ s/\'//g;
936   }
937
938   $amount =~ s/,//g;
939
940   $main::lxdebug->leave_sub(2);
941
942   # Make sure no code wich is not a math expression ends up in eval().
943   return 0 unless $amount =~ /^ [\s \d \( \) \- \+ \* \/ \. ]* $/x;
944   return scalar(eval($amount)) * 1 ;
945 }
946
947 sub round_amount {
948   $main::lxdebug->enter_sub(2);
949
950   my ($self, $amount, $places) = @_;
951   my $round_amount;
952
953   # Rounding like "Kaufmannsrunden" (see http://de.wikipedia.org/wiki/Rundung )
954
955   # Round amounts to eight places before rounding to the requested
956   # number of places. This gets rid of errors due to internal floating
957   # point representation.
958   $amount       = $self->round_amount($amount, 8) if $places < 8;
959   $amount       = $amount * (10**($places));
960   $round_amount = int($amount + .5 * ($amount <=> 0)) / (10**($places));
961
962   $main::lxdebug->leave_sub(2);
963
964   return $round_amount;
965
966 }
967
968 sub parse_template {
969   $main::lxdebug->enter_sub();
970
971   my ($self, $myconfig) = @_;
972   my ($out, $out_mode);
973
974   local (*IN, *OUT);
975
976   my $defaults  = SL::DB::Default->get;
977   my $userspath = $::lx_office_conf{paths}->{userspath};
978
979   $self->{"cwd"} = getcwd();
980   $self->{"tmpdir"} = $self->{cwd} . "/${userspath}";
981
982   my $ext_for_format;
983
984   my $template_type;
985   if ($self->{"format"} =~ /(opendocument|oasis)/i) {
986     $template_type  = 'OpenDocument';
987     $ext_for_format = $self->{"format"} =~ m/pdf/ ? 'pdf' : 'odt';
988
989   } elsif ($self->{"format"} =~ /(postscript|pdf)/i) {
990     $template_type    = 'LaTeX';
991     $ext_for_format   = 'pdf';
992
993   } elsif (($self->{"format"} =~ /html/i) || (!$self->{"format"} && ($self->{"IN"} =~ /html$/i))) {
994     $template_type  = 'HTML';
995     $ext_for_format = 'html';
996
997   } elsif (($self->{"format"} =~ /xml/i) || (!$self->{"format"} && ($self->{"IN"} =~ /xml$/i))) {
998     $template_type  = 'XML';
999     $ext_for_format = 'xml';
1000
1001   } elsif ( $self->{"format"} =~ /elster(?:winston|taxbird)/i ) {
1002     $template_type = 'XML';
1003
1004   } elsif ( $self->{"format"} =~ /excel/i ) {
1005     $template_type  = 'Excel';
1006     $ext_for_format = 'xls';
1007
1008   } elsif ( defined $self->{'format'}) {
1009     $self->error("Outputformat not defined. This may be a future feature: $self->{'format'}");
1010
1011   } elsif ( $self->{'format'} eq '' ) {
1012     $self->error("No Outputformat given: $self->{'format'}");
1013
1014   } else { #Catch the rest
1015     $self->error("Outputformat not defined: $self->{'format'}");
1016   }
1017
1018   my $template = SL::Template::create(type      => $template_type,
1019                                       file_name => $self->{IN},
1020                                       form      => $self,
1021                                       myconfig  => $myconfig,
1022                                       userspath => $userspath);
1023
1024   # Copy the notes from the invoice/sales order etc. back to the variable "notes" because that is where most templates expect it to be.
1025   $self->{"notes"} = $self->{ $self->{"formname"} . "notes" };
1026
1027   if (!$self->{employee_id}) {
1028     $self->{"employee_${_}"} = $myconfig->{$_} for qw(email tel fax name signature);
1029     $self->{"employee_${_}"} = $defaults->$_   for qw(address businessnumber co_ustid company duns sepa_creditor_id taxnumber);
1030   }
1031
1032   $self->{"myconfig_${_}"} = $myconfig->{$_} for grep { $_ ne 'dbpasswd' } keys %{ $myconfig };
1033   $self->{$_}              = $defaults->$_   for qw(co_ustid);
1034   $self->{"myconfig_${_}"} = $defaults->$_   for qw(address businessnumber co_ustid company duns sepa_creditor_id taxnumber);
1035
1036   $self->{copies} = 1 if (($self->{copies} *= 1) <= 0);
1037
1038   # OUT is used for the media, screen, printer, email
1039   # for postscript we store a copy in a temporary file
1040   my ($temp_fh, $suffix);
1041   $suffix =  $self->{IN};
1042   $suffix =~ s/.*\.//;
1043   ($temp_fh, $self->{tmpfile}) = File::Temp::tempfile(
1044     'kivitendo-printXXXXXX',
1045     SUFFIX => '.' . ($suffix || 'tex'),
1046     DIR    => $userspath,
1047     UNLINK => ($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})? 0 : 1,
1048   );
1049   close $temp_fh;
1050   (undef, undef, $self->{template_meta}{tmpfile}) = File::Spec->splitpath( $self->{tmpfile} );
1051
1052   $out              = $self->{OUT};
1053   $out_mode         = $self->{OUT_MODE} || '>';
1054   $self->{OUT}      = "$self->{tmpfile}";
1055   $self->{OUT_MODE} = '>';
1056
1057   my $result;
1058   my $command_formatter = sub {
1059     my ($out_mode, $out) = @_;
1060     return $out_mode eq '|-' ? SL::Template::create(type => 'ShellCommand', form => $self)->parse($out) : $out;
1061   };
1062
1063   if ($self->{OUT}) {
1064     $self->{OUT} = $command_formatter->($self->{OUT_MODE}, $self->{OUT});
1065     open(OUT, $self->{OUT_MODE}, $self->{OUT}) or $self->error("error on opening $self->{OUT} with mode $self->{OUT_MODE} : $!");
1066   } else {
1067     *OUT = ($::dispatcher->get_standard_filehandles)[1];
1068     $self->header;
1069   }
1070
1071   if (!$template->parse(*OUT)) {
1072     $self->cleanup();
1073     $self->error("$self->{IN} : " . $template->get_error());
1074   }
1075
1076   close OUT if $self->{OUT};
1077
1078   my $copy_to_webdav = $::instance_conf->get_webdav && $::instance_conf->get_webdav_documents && !$self->{preview} && $self->{tmpdir} && $self->{tmpfile} && $self->{type};
1079
1080   if ($self->{media} eq 'file') {
1081     copy(join('/', $self->{cwd}, $userspath, $self->{tmpfile}), $out =~ m|^/| ? $out : join('/', $self->{cwd}, $out)) if $template->uses_temp_file;
1082     Common::copy_file_to_webdav_folder($self)                                                                         if $copy_to_webdav;
1083     $self->cleanup;
1084     chdir("$self->{cwd}");
1085
1086     $::lxdebug->leave_sub();
1087
1088     return;
1089   }
1090
1091   Common::copy_file_to_webdav_folder($self) if $copy_to_webdav;
1092
1093   if ($self->{media} eq 'email') {
1094
1095     my $mail = new Mailer;
1096
1097     map { $mail->{$_} = $self->{$_} }
1098       qw(cc bcc subject message version format);
1099     $mail->{to} = $self->{EMAIL_RECIPIENT} ? $self->{EMAIL_RECIPIENT} : $self->{email};
1100     $mail->{from}   = qq|"$myconfig->{name}" <$myconfig->{email}>|;
1101     $mail->{fileid} = time() . '.' . $$ . '.';
1102     $myconfig->{signature} =~ s/\r//g;
1103
1104     # if we send html or plain text inline
1105     if (($self->{format} eq 'html') && ($self->{sendmode} eq 'inline')) {
1106       $mail->{contenttype}    =  "text/html";
1107       $mail->{message}        =~ s/\r//g;
1108       $mail->{message}        =~ s/\n/<br>\n/g;
1109       $myconfig->{signature}  =~ s/\n/<br>\n/g;
1110       $mail->{message}       .=  "<br>\n-- <br>\n$myconfig->{signature}\n<br>";
1111
1112       open(IN, "<", $self->{tmpfile})
1113         or $self->error($self->cleanup . "$self->{tmpfile} : $!");
1114       $mail->{message} .= $_ while <IN>;
1115       close(IN);
1116
1117     } else {
1118
1119       if (!$self->{"do_not_attach"}) {
1120         my $attachment_name  =  $self->{attachment_filename} || $self->{tmpfile};
1121         $attachment_name     =~ s/\.(.+?)$/.${ext_for_format}/ if ($ext_for_format);
1122         $mail->{attachments} =  [{ "filename" => $self->{tmpfile},
1123                                    "name"     => $attachment_name }];
1124       }
1125
1126       $mail->{message}  =~ s/\r//g;
1127       $mail->{message} .=  "\n-- \n$myconfig->{signature}";
1128
1129     }
1130
1131     my $err = $mail->send();
1132     $self->error($self->cleanup . "$err") if ($err);
1133
1134   } else {
1135
1136     $self->{OUT}      = $out;
1137     $self->{OUT_MODE} = $out_mode;
1138
1139     my $numbytes = (-s $self->{tmpfile});
1140     open(IN, "<", $self->{tmpfile})
1141       or $self->error($self->cleanup . "$self->{tmpfile} : $!");
1142     binmode IN;
1143
1144     $self->{copies} = 1 unless $self->{media} eq 'printer';
1145
1146     chdir("$self->{cwd}");
1147     #print(STDERR "Kopien $self->{copies}\n");
1148     #print(STDERR "OUT $self->{OUT}\n");
1149     for my $i (1 .. $self->{copies}) {
1150       if ($self->{OUT}) {
1151         $self->{OUT} = $command_formatter->($self->{OUT_MODE}, $self->{OUT});
1152
1153         open  OUT, $self->{OUT_MODE}, $self->{OUT} or $self->error($self->cleanup . "$self->{OUT} : $!");
1154         print OUT $_ while <IN>;
1155         close OUT;
1156         seek  IN, 0, 0;
1157
1158       } else {
1159         my %headers = ('-type'       => $template->get_mime_type,
1160                        '-connection' => 'close',
1161                        '-charset'    => 'UTF-8');
1162
1163         $self->{attachment_filename} ||= $self->generate_attachment_filename;
1164
1165         if ($self->{attachment_filename}) {
1166           %headers = (
1167             %headers,
1168             '-attachment'     => $self->{attachment_filename},
1169             '-content-length' => $numbytes,
1170             '-charset'        => '',
1171           );
1172         }
1173
1174         print $::request->cgi->header(%headers);
1175
1176         $::locale->with_raw_io(\*STDOUT, sub { print while <IN> });
1177       }
1178     }
1179
1180     close(IN);
1181   }
1182
1183   $self->cleanup;
1184
1185   chdir("$self->{cwd}");
1186   $main::lxdebug->leave_sub();
1187 }
1188
1189 sub get_formname_translation {
1190   $main::lxdebug->enter_sub();
1191   my ($self, $formname) = @_;
1192
1193   $formname ||= $self->{formname};
1194
1195   $self->{recipient_locale} ||=  Locale->lang_to_locale($self->{language});
1196   local $::locale = Locale->new($self->{recipient_locale});
1197
1198   my %formname_translations = (
1199     bin_list                => $main::locale->text('Bin List'),
1200     credit_note             => $main::locale->text('Credit Note'),
1201     invoice                 => $main::locale->text('Invoice'),
1202     pick_list               => $main::locale->text('Pick List'),
1203     proforma                => $main::locale->text('Proforma Invoice'),
1204     purchase_order          => $main::locale->text('Purchase Order'),
1205     request_quotation       => $main::locale->text('RFQ'),
1206     sales_order             => $main::locale->text('Confirmation'),
1207     sales_quotation         => $main::locale->text('Quotation'),
1208     storno_invoice          => $main::locale->text('Storno Invoice'),
1209     sales_delivery_order    => $main::locale->text('Delivery Order'),
1210     purchase_delivery_order => $main::locale->text('Delivery Order'),
1211     dunning                 => $main::locale->text('Dunning'),
1212   );
1213
1214   $main::lxdebug->leave_sub();
1215   return $formname_translations{$formname};
1216 }
1217
1218 sub get_number_prefix_for_type {
1219   $main::lxdebug->enter_sub();
1220   my ($self) = @_;
1221
1222   my $prefix =
1223       (first { $self->{type} eq $_ } qw(invoice credit_note)) ? 'inv'
1224     : ($self->{type} =~ /_quotation$/)                        ? 'quo'
1225     : ($self->{type} =~ /_delivery_order$/)                   ? 'do'
1226     :                                                           'ord';
1227
1228   $main::lxdebug->leave_sub();
1229   return $prefix;
1230 }
1231
1232 sub get_extension_for_format {
1233   $main::lxdebug->enter_sub();
1234   my ($self)    = @_;
1235
1236   my $extension = $self->{format} =~ /pdf/i          ? ".pdf"
1237                 : $self->{format} =~ /postscript/i   ? ".ps"
1238                 : $self->{format} =~ /opendocument/i ? ".odt"
1239                 : $self->{format} =~ /excel/i        ? ".xls"
1240                 : $self->{format} =~ /html/i         ? ".html"
1241                 :                                      "";
1242
1243   $main::lxdebug->leave_sub();
1244   return $extension;
1245 }
1246
1247 sub generate_attachment_filename {
1248   $main::lxdebug->enter_sub();
1249   my ($self) = @_;
1250
1251   $self->{recipient_locale} ||=  Locale->lang_to_locale($self->{language});
1252   my $recipient_locale = Locale->new($self->{recipient_locale});
1253
1254   my $attachment_filename = $main::locale->unquote_special_chars('HTML', $self->get_formname_translation());
1255   my $prefix              = $self->get_number_prefix_for_type();
1256
1257   if ($self->{preview} && (first { $self->{type} eq $_ } qw(invoice credit_note))) {
1258     $attachment_filename .= ' (' . $recipient_locale->text('Preview') . ')' . $self->get_extension_for_format();
1259
1260   } elsif ($attachment_filename && $self->{"${prefix}number"}) {
1261     $attachment_filename .=  "_" . $self->{"${prefix}number"} . $self->get_extension_for_format();
1262
1263   } else {
1264     $attachment_filename = "";
1265   }
1266
1267   $attachment_filename =  $main::locale->quote_special_chars('filenames', $attachment_filename);
1268   $attachment_filename =~ s|[\s/\\]+|_|g;
1269
1270   $main::lxdebug->leave_sub();
1271   return $attachment_filename;
1272 }
1273
1274 sub generate_email_subject {
1275   $main::lxdebug->enter_sub();
1276   my ($self) = @_;
1277
1278   my $subject = $main::locale->unquote_special_chars('HTML', $self->get_formname_translation());
1279   my $prefix  = $self->get_number_prefix_for_type();
1280
1281   if ($subject && $self->{"${prefix}number"}) {
1282     $subject .= " " . $self->{"${prefix}number"}
1283   }
1284
1285   $main::lxdebug->leave_sub();
1286   return $subject;
1287 }
1288
1289 sub cleanup {
1290   $main::lxdebug->enter_sub();
1291
1292   my ($self, $application) = @_;
1293
1294   my $error_code = $?;
1295
1296   chdir("$self->{tmpdir}");
1297
1298   my @err = ();
1299   if ((-1 == $error_code) || (127 == (($error_code) >> 8))) {
1300     push @err, $::locale->text('The application "#1" was not found on the system.', $application || 'pdflatex') . ' ' . $::locale->text('Please contact your administrator.');
1301
1302   } elsif (-f "$self->{tmpfile}.err") {
1303     open(FH, "$self->{tmpfile}.err");
1304     @err = <FH>;
1305     close(FH);
1306   }
1307
1308   if ($self->{tmpfile} && !($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})) {
1309     $self->{tmpfile} =~ s|.*/||g;
1310     # strip extension
1311     $self->{tmpfile} =~ s/\.\w+$//g;
1312     my $tmpfile = $self->{tmpfile};
1313     unlink(<$tmpfile.*>);
1314   }
1315
1316   chdir("$self->{cwd}");
1317
1318   $main::lxdebug->leave_sub();
1319
1320   return "@err";
1321 }
1322
1323 sub datetonum {
1324   $main::lxdebug->enter_sub();
1325
1326   my ($self, $date, $myconfig) = @_;
1327   my ($yy, $mm, $dd);
1328
1329   if ($date && $date =~ /\D/) {
1330
1331     if ($myconfig->{dateformat} =~ /^yy/) {
1332       ($yy, $mm, $dd) = split /\D/, $date;
1333     }
1334     if ($myconfig->{dateformat} =~ /^mm/) {
1335       ($mm, $dd, $yy) = split /\D/, $date;
1336     }
1337     if ($myconfig->{dateformat} =~ /^dd/) {
1338       ($dd, $mm, $yy) = split /\D/, $date;
1339     }
1340
1341     $dd *= 1;
1342     $mm *= 1;
1343     $yy = ($yy < 70) ? $yy + 2000 : $yy;
1344     $yy = ($yy >= 70 && $yy <= 99) ? $yy + 1900 : $yy;
1345
1346     $dd = "0$dd" if ($dd < 10);
1347     $mm = "0$mm" if ($mm < 10);
1348
1349     $date = "$yy$mm$dd";
1350   }
1351
1352   $main::lxdebug->leave_sub();
1353
1354   return $date;
1355 }
1356
1357 # Database routines used throughout
1358
1359 sub dbconnect {
1360   $main::lxdebug->enter_sub(2);
1361
1362   my ($self, $myconfig) = @_;
1363
1364   # connect to database
1365   my $dbh = SL::DBConnect->connect or $self->dberror;
1366
1367   # set db options
1368   if ($myconfig->{dboptions}) {
1369     $dbh->do($myconfig->{dboptions}) || $self->dberror($myconfig->{dboptions});
1370   }
1371
1372   $main::lxdebug->leave_sub(2);
1373
1374   return $dbh;
1375 }
1376
1377 sub dbconnect_noauto {
1378   $main::lxdebug->enter_sub();
1379
1380   my ($self, $myconfig) = @_;
1381
1382   # connect to database
1383   my $dbh = SL::DBConnect->connect(SL::DBConnect->get_connect_args(AutoCommit => 0)) or $self->dberror;
1384
1385   # set db options
1386   if ($myconfig->{dboptions}) {
1387     $dbh->do($myconfig->{dboptions}) || $self->dberror($myconfig->{dboptions});
1388   }
1389
1390   $main::lxdebug->leave_sub();
1391
1392   return $dbh;
1393 }
1394
1395 sub get_standard_dbh {
1396   $main::lxdebug->enter_sub(2);
1397
1398   my $self     = shift;
1399   my $myconfig = shift || \%::myconfig;
1400
1401   if ($standard_dbh && !$standard_dbh->{Active}) {
1402     $main::lxdebug->message(LXDebug->INFO(), "get_standard_dbh: \$standard_dbh is defined but not Active anymore");
1403     undef $standard_dbh;
1404   }
1405
1406   $standard_dbh ||= $self->dbconnect_noauto($myconfig);
1407
1408   $main::lxdebug->leave_sub(2);
1409
1410   return $standard_dbh;
1411 }
1412
1413 sub set_standard_dbh {
1414   my ($self, $dbh) = @_;
1415   my $old_dbh      = $standard_dbh;
1416   $standard_dbh    = $dbh;
1417
1418   return $old_dbh;
1419 }
1420
1421 sub date_closed {
1422   $main::lxdebug->enter_sub();
1423
1424   my ($self, $date, $myconfig) = @_;
1425   my $dbh = $self->dbconnect($myconfig);
1426
1427   my $query = "SELECT 1 FROM defaults WHERE ? < closedto";
1428   my $sth = prepare_execute_query($self, $dbh, $query, conv_date($date));
1429
1430   # Falls $date = '' - Fehlermeldung aus der Datenbank. Ich denke,
1431   # es ist sicher ein conv_date vorher IMMER auszuführen.
1432   # Testfälle ohne definiertes closedto:
1433   #   Leere Datumseingabe i.O.
1434   #     SELECT 1 FROM defaults WHERE '' < closedto
1435   #   normale Zahlungsbuchung Ã¼ber Rechnungsmaske i.O.
1436   #     SELECT 1 FROM defaults WHERE '10.05.2011' < closedto
1437   # Testfälle mit definiertem closedto (30.04.2011):
1438   #  Leere Datumseingabe i.O.
1439   #   SELECT 1 FROM defaults WHERE '' < closedto
1440   # normale Buchung im geschloßenem Zeitraum i.O.
1441   #   SELECT 1 FROM defaults WHERE '21.04.2011' < closedto
1442   #     Fehlermeldung: Es können keine Zahlungen für abgeschlossene Bücher gebucht werden!
1443   # normale Buchung in aktiver Buchungsperiode i.O.
1444   #   SELECT 1 FROM defaults WHERE '01.05.2011' < closedto
1445
1446   my ($closed) = $sth->fetchrow_array;
1447
1448   $main::lxdebug->leave_sub();
1449
1450   return $closed;
1451 }
1452
1453 # prevents bookings to the to far away future
1454 sub date_max_future {
1455   $main::lxdebug->enter_sub();
1456
1457   my ($self, $date, $myconfig) = @_;
1458   my $dbh = $self->dbconnect($myconfig);
1459
1460   my $query = "SELECT 1 FROM defaults WHERE ? - current_date > max_future_booking_interval";
1461   my $sth = prepare_execute_query($self, $dbh, $query, conv_date($date));
1462
1463   my ($max_future_booking_interval) = $sth->fetchrow_array;
1464
1465   $main::lxdebug->leave_sub();
1466
1467   return $max_future_booking_interval;
1468 }
1469
1470
1471 sub update_balance {
1472   $main::lxdebug->enter_sub();
1473
1474   my ($self, $dbh, $table, $field, $where, $value, @values) = @_;
1475
1476   # if we have a value, go do it
1477   if ($value != 0) {
1478
1479     # retrieve balance from table
1480     my $query = "SELECT $field FROM $table WHERE $where FOR UPDATE";
1481     my $sth = prepare_execute_query($self, $dbh, $query, @values);
1482     my ($balance) = $sth->fetchrow_array;
1483     $sth->finish;
1484
1485     $balance += $value;
1486
1487     # update balance
1488     $query = "UPDATE $table SET $field = $balance WHERE $where";
1489     do_query($self, $dbh, $query, @values);
1490   }
1491   $main::lxdebug->leave_sub();
1492 }
1493
1494 sub update_exchangerate {
1495   $main::lxdebug->enter_sub();
1496
1497   my ($self, $dbh, $curr, $transdate, $buy, $sell) = @_;
1498   my ($query);
1499   # some sanity check for currency
1500   if ($curr eq '') {
1501     $main::lxdebug->leave_sub();
1502     return;
1503   }
1504   $query = qq|SELECT name AS curr FROM currencies WHERE id=(SELECT currency_id FROM defaults)|;
1505
1506   my ($defaultcurrency) = selectrow_query($self, $dbh, $query);
1507
1508   if ($curr eq $defaultcurrency) {
1509     $main::lxdebug->leave_sub();
1510     return;
1511   }
1512
1513   $query = qq|SELECT e.currency_id FROM exchangerate e
1514                  WHERE e.currency_id = (SELECT cu.id FROM currencies cu WHERE cu.name=?) AND e.transdate = ?
1515                  FOR UPDATE|;
1516   my $sth = prepare_execute_query($self, $dbh, $query, $curr, $transdate);
1517
1518   if ($buy == 0) {
1519     $buy = "";
1520   }
1521   if ($sell == 0) {
1522     $sell = "";
1523   }
1524
1525   $buy = conv_i($buy, "NULL");
1526   $sell = conv_i($sell, "NULL");
1527
1528   my $set;
1529   if ($buy != 0 && $sell != 0) {
1530     $set = "buy = $buy, sell = $sell";
1531   } elsif ($buy != 0) {
1532     $set = "buy = $buy";
1533   } elsif ($sell != 0) {
1534     $set = "sell = $sell";
1535   }
1536
1537   if ($sth->fetchrow_array) {
1538     $query = qq|UPDATE exchangerate
1539                 SET $set
1540                 WHERE currency_id = (SELECT id FROM currencies WHERE name = ?)
1541                 AND transdate = ?|;
1542
1543   } else {
1544     $query = qq|INSERT INTO exchangerate (currency_id, buy, sell, transdate)
1545                 VALUES ((SELECT id FROM currencies WHERE name = ?), $buy, $sell, ?)|;
1546   }
1547   $sth->finish;
1548   do_query($self, $dbh, $query, $curr, $transdate);
1549
1550   $main::lxdebug->leave_sub();
1551 }
1552
1553 sub save_exchangerate {
1554   $main::lxdebug->enter_sub();
1555
1556   my ($self, $myconfig, $currency, $transdate, $rate, $fld) = @_;
1557
1558   my $dbh = $self->dbconnect($myconfig);
1559
1560   my ($buy, $sell);
1561
1562   $buy  = $rate if $fld eq 'buy';
1563   $sell = $rate if $fld eq 'sell';
1564
1565
1566   $self->update_exchangerate($dbh, $currency, $transdate, $buy, $sell);
1567
1568
1569   $dbh->disconnect;
1570
1571   $main::lxdebug->leave_sub();
1572 }
1573
1574 sub get_exchangerate {
1575   $main::lxdebug->enter_sub();
1576
1577   my ($self, $dbh, $curr, $transdate, $fld) = @_;
1578   my ($query);
1579
1580   unless ($transdate && $curr) {
1581     $main::lxdebug->leave_sub();
1582     return 1;
1583   }
1584
1585   $query = qq|SELECT name AS curr FROM currencies WHERE id = (SELECT currency_id FROM defaults)|;
1586
1587   my ($defaultcurrency) = selectrow_query($self, $dbh, $query);
1588
1589   if ($curr eq $defaultcurrency) {
1590     $main::lxdebug->leave_sub();
1591     return 1;
1592   }
1593
1594   $query = qq|SELECT e.$fld FROM exchangerate e
1595                  WHERE e.currency_id = (SELECT id FROM currencies WHERE name = ?) AND e.transdate = ?|;
1596   my ($exchangerate) = selectrow_query($self, $dbh, $query, $curr, $transdate);
1597
1598
1599
1600   $main::lxdebug->leave_sub();
1601
1602   return $exchangerate;
1603 }
1604
1605 sub check_exchangerate {
1606   $main::lxdebug->enter_sub();
1607
1608   my ($self, $myconfig, $currency, $transdate, $fld) = @_;
1609
1610   if ($fld !~/^buy|sell$/) {
1611     $self->error('Fatal: check_exchangerate called with invalid buy/sell argument');
1612   }
1613
1614   unless ($transdate) {
1615     $main::lxdebug->leave_sub();
1616     return "";
1617   }
1618
1619   my ($defaultcurrency) = $self->get_default_currency($myconfig);
1620
1621   if ($currency eq $defaultcurrency) {
1622     $main::lxdebug->leave_sub();
1623     return 1;
1624   }
1625
1626   my $dbh   = $self->get_standard_dbh($myconfig);
1627   my $query = qq|SELECT e.$fld FROM exchangerate e
1628                  WHERE e.currency_id = (SELECT id FROM currencies WHERE name = ?) AND e.transdate = ?|;
1629
1630   my ($exchangerate) = selectrow_query($self, $dbh, $query, $currency, $transdate);
1631
1632   $main::lxdebug->leave_sub();
1633
1634   return $exchangerate;
1635 }
1636
1637 sub get_all_currencies {
1638   $main::lxdebug->enter_sub();
1639
1640   my $self     = shift;
1641   my $myconfig = shift || \%::myconfig;
1642   my $dbh      = $self->get_standard_dbh($myconfig);
1643
1644   my $query = qq|SELECT name FROM currencies|;
1645   my @currencies = map { $_->{name} } selectall_hashref_query($self, $dbh, $query);
1646
1647   $main::lxdebug->leave_sub();
1648
1649   return @currencies;
1650 }
1651
1652 sub get_default_currency {
1653   $main::lxdebug->enter_sub();
1654
1655   my ($self, $myconfig) = @_;
1656   my $dbh      = $self->get_standard_dbh($myconfig);
1657   my $query = qq|SELECT name AS curr FROM currencies WHERE id = (SELECT currency_id FROM defaults)|;
1658
1659   my ($defaultcurrency) = selectrow_query($self, $dbh, $query);
1660
1661   $main::lxdebug->leave_sub();
1662
1663   return $defaultcurrency;
1664 }
1665
1666 sub set_payment_options {
1667   $main::lxdebug->enter_sub();
1668
1669   my ($self, $myconfig, $transdate) = @_;
1670
1671   return $main::lxdebug->leave_sub() unless ($self->{payment_id});
1672
1673   my $dbh = $self->get_standard_dbh($myconfig);
1674
1675   my $query =
1676     qq|SELECT p.terms_netto, p.terms_skonto, p.percent_skonto, p.description_long , p.description | .
1677     qq|FROM payment_terms p | .
1678     qq|WHERE p.id = ?|;
1679
1680   ($self->{terms_netto}, $self->{terms_skonto}, $self->{percent_skonto},
1681    $self->{payment_terms}, $self->{payment_description}) =
1682      selectrow_query($self, $dbh, $query, $self->{payment_id});
1683
1684   if ($transdate eq "") {
1685     if ($self->{invdate}) {
1686       $transdate = $self->{invdate};
1687     } else {
1688       $transdate = $self->{transdate};
1689     }
1690   }
1691
1692   $query =
1693     qq|SELECT ?::date + ?::integer AS netto_date, ?::date + ?::integer AS skonto_date | .
1694     qq|FROM payment_terms|;
1695   ($self->{netto_date}, $self->{skonto_date}) =
1696     selectrow_query($self, $dbh, $query, $transdate, $self->{terms_netto}, $transdate, $self->{terms_skonto});
1697
1698   my ($invtotal, $total);
1699   my (%amounts, %formatted_amounts);
1700
1701   if ($self->{type} =~ /_order$/) {
1702     $amounts{invtotal} = $self->{ordtotal};
1703     $amounts{total}    = $self->{ordtotal};
1704
1705   } elsif ($self->{type} =~ /_quotation$/) {
1706     $amounts{invtotal} = $self->{quototal};
1707     $amounts{total}    = $self->{quototal};
1708
1709   } else {
1710     $amounts{invtotal} = $self->{invtotal};
1711     $amounts{total}    = $self->{total};
1712   }
1713   map { $amounts{$_} = $self->parse_amount($myconfig, $amounts{$_}) } keys %amounts;
1714
1715   $amounts{skonto_in_percent}  = 100.0 * $self->{percent_skonto};
1716   $amounts{skonto_amount}      = $amounts{invtotal} * $self->{percent_skonto};
1717   $amounts{invtotal_wo_skonto} = $amounts{invtotal} * (1 - $self->{percent_skonto});
1718   $amounts{total_wo_skonto}    = $amounts{total}    * (1 - $self->{percent_skonto});
1719
1720   foreach (keys %amounts) {
1721     $amounts{$_}           = $self->round_amount($amounts{$_}, 2);
1722     $formatted_amounts{$_} = $self->format_amount($myconfig, $amounts{$_}, 2);
1723   }
1724
1725   if ($self->{"language_id"}) {
1726     $query =
1727       qq|SELECT t.translation, l.output_numberformat, l.output_dateformat, l.output_longdates | .
1728       qq|FROM generic_translations t | .
1729       qq|LEFT JOIN language l ON t.language_id = l.id | .
1730       qq|WHERE (t.language_id = ?)
1731            AND (t.translation_id = ?)
1732            AND (t.translation_type = 'SL::DB::PaymentTerm/description_long')|;
1733     my ($description_long, $output_numberformat, $output_dateformat,
1734       $output_longdates) =
1735       selectrow_query($self, $dbh, $query,
1736                       $self->{"language_id"}, $self->{"payment_id"});
1737
1738     $self->{payment_terms} = $description_long if ($description_long);
1739
1740     if ($output_dateformat) {
1741       foreach my $key (qw(netto_date skonto_date)) {
1742         $self->{$key} =
1743           $main::locale->reformat_date($myconfig, $self->{$key},
1744                                        $output_dateformat,
1745                                        $output_longdates);
1746       }
1747     }
1748
1749     if ($output_numberformat &&
1750         ($output_numberformat ne $myconfig->{"numberformat"})) {
1751       my $saved_numberformat = $myconfig->{"numberformat"};
1752       $myconfig->{"numberformat"} = $output_numberformat;
1753       map { $formatted_amounts{$_} = $self->format_amount($myconfig, $amounts{$_}) } keys %amounts;
1754       $myconfig->{"numberformat"} = $saved_numberformat;
1755     }
1756   }
1757
1758   $self->{payment_terms} =~ s/<%netto_date%>/$self->{netto_date}/g;
1759   $self->{payment_terms} =~ s/<%skonto_date%>/$self->{skonto_date}/g;
1760   $self->{payment_terms} =~ s/<%currency%>/$self->{currency}/g;
1761   $self->{payment_terms} =~ s/<%terms_netto%>/$self->{terms_netto}/g;
1762   $self->{payment_terms} =~ s/<%account_number%>/$self->{account_number}/g;
1763   $self->{payment_terms} =~ s/<%bank%>/$self->{bank}/g;
1764   $self->{payment_terms} =~ s/<%bank_code%>/$self->{bank_code}/g;
1765
1766   map { $self->{payment_terms} =~ s/<%${_}%>/$formatted_amounts{$_}/g; } keys %formatted_amounts;
1767
1768   $self->{skonto_in_percent} = $formatted_amounts{skonto_in_percent};
1769
1770   $main::lxdebug->leave_sub();
1771
1772 }
1773
1774 sub get_template_language {
1775   $main::lxdebug->enter_sub();
1776
1777   my ($self, $myconfig) = @_;
1778
1779   my $template_code = "";
1780
1781   if ($self->{language_id}) {
1782     my $dbh = $self->get_standard_dbh($myconfig);
1783     my $query = qq|SELECT template_code FROM language WHERE id = ?|;
1784     ($template_code) = selectrow_query($self, $dbh, $query, $self->{language_id});
1785   }
1786
1787   $main::lxdebug->leave_sub();
1788
1789   return $template_code;
1790 }
1791
1792 sub get_printer_code {
1793   $main::lxdebug->enter_sub();
1794
1795   my ($self, $myconfig) = @_;
1796
1797   my $template_code = "";
1798
1799   if ($self->{printer_id}) {
1800     my $dbh = $self->get_standard_dbh($myconfig);
1801     my $query = qq|SELECT template_code, printer_command FROM printers WHERE id = ?|;
1802     ($template_code, $self->{printer_command}) = selectrow_query($self, $dbh, $query, $self->{printer_id});
1803   }
1804
1805   $main::lxdebug->leave_sub();
1806
1807   return $template_code;
1808 }
1809
1810 sub get_shipto {
1811   $main::lxdebug->enter_sub();
1812
1813   my ($self, $myconfig) = @_;
1814
1815   my $template_code = "";
1816
1817   if ($self->{shipto_id}) {
1818     my $dbh = $self->get_standard_dbh($myconfig);
1819     my $query = qq|SELECT * FROM shipto WHERE shipto_id = ?|;
1820     my $ref = selectfirst_hashref_query($self, $dbh, $query, $self->{shipto_id});
1821     map({ $self->{$_} = $ref->{$_} } keys(%$ref));
1822   }
1823
1824   $main::lxdebug->leave_sub();
1825 }
1826
1827 sub add_shipto {
1828   $main::lxdebug->enter_sub();
1829
1830   my ($self, $dbh, $id, $module) = @_;
1831
1832   my $shipto;
1833   my @values;
1834
1835   foreach my $item (qw(name department_1 department_2 street zipcode city country
1836                        contact cp_gender phone fax email)) {
1837     if ($self->{"shipto$item"}) {
1838       $shipto = 1 if ($self->{$item} ne $self->{"shipto$item"});
1839     }
1840     push(@values, $self->{"shipto${item}"});
1841   }
1842
1843   if ($shipto) {
1844     if ($self->{shipto_id}) {
1845       my $query = qq|UPDATE shipto set
1846                        shiptoname = ?,
1847                        shiptodepartment_1 = ?,
1848                        shiptodepartment_2 = ?,
1849                        shiptostreet = ?,
1850                        shiptozipcode = ?,
1851                        shiptocity = ?,
1852                        shiptocountry = ?,
1853                        shiptocontact = ?,
1854                        shiptocp_gender = ?,
1855                        shiptophone = ?,
1856                        shiptofax = ?,
1857                        shiptoemail = ?
1858                      WHERE shipto_id = ?|;
1859       do_query($self, $dbh, $query, @values, $self->{shipto_id});
1860     } else {
1861       my $query = qq|SELECT * FROM shipto
1862                      WHERE shiptoname = ? AND
1863                        shiptodepartment_1 = ? AND
1864                        shiptodepartment_2 = ? AND
1865                        shiptostreet = ? AND
1866                        shiptozipcode = ? AND
1867                        shiptocity = ? AND
1868                        shiptocountry = ? AND
1869                        shiptocontact = ? AND
1870                        shiptocp_gender = ? AND
1871                        shiptophone = ? AND
1872                        shiptofax = ? AND
1873                        shiptoemail = ? AND
1874                        module = ? AND
1875                        trans_id = ?|;
1876       my $insert_check = selectfirst_hashref_query($self, $dbh, $query, @values, $module, $id);
1877       if(!$insert_check){
1878         $query =
1879           qq|INSERT INTO shipto (trans_id, shiptoname, shiptodepartment_1, shiptodepartment_2,
1880                                  shiptostreet, shiptozipcode, shiptocity, shiptocountry,
1881                                  shiptocontact, shiptocp_gender, shiptophone, shiptofax, shiptoemail, module)
1882              VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)|;
1883         do_query($self, $dbh, $query, $id, @values, $module);
1884       }
1885     }
1886   }
1887
1888   $main::lxdebug->leave_sub();
1889 }
1890
1891 sub get_employee {
1892   $main::lxdebug->enter_sub();
1893
1894   my ($self, $dbh) = @_;
1895
1896   $dbh ||= $self->get_standard_dbh(\%main::myconfig);
1897
1898   my $query = qq|SELECT id, name FROM employee WHERE login = ?|;
1899   ($self->{"employee_id"}, $self->{"employee"}) = selectrow_query($self, $dbh, $query, $self->{login});
1900   $self->{"employee_id"} *= 1;
1901
1902   $main::lxdebug->leave_sub();
1903 }
1904
1905 sub get_employee_data {
1906   $main::lxdebug->enter_sub();
1907
1908   my $self     = shift;
1909   my %params   = @_;
1910   my $defaults = SL::DB::Default->get;
1911
1912   Common::check_params(\%params, qw(prefix));
1913   Common::check_params_x(\%params, qw(id));
1914
1915   if (!$params{id}) {
1916     $main::lxdebug->leave_sub();
1917     return;
1918   }
1919
1920   my $myconfig = \%main::myconfig;
1921   my $dbh      = $params{dbh} || $self->get_standard_dbh($myconfig);
1922
1923   my ($login)  = selectrow_query($self, $dbh, qq|SELECT login FROM employee WHERE id = ?|, conv_i($params{id}));
1924
1925   if ($login) {
1926     my $user = User->new(login => $login);
1927     $self->{$params{prefix} . "_${_}"}    = $user->{$_}   for qw(email fax name signature tel);
1928     $self->{$params{prefix} . "_${_}"}    = $defaults->$_ for qw(address businessnumber co_ustid company duns taxnumber);
1929
1930     $self->{$params{prefix} . '_login'}   = $login;
1931     $self->{$params{prefix} . '_name'}  ||= $login;
1932   }
1933
1934   $main::lxdebug->leave_sub();
1935 }
1936
1937 sub get_duedate {
1938   $main::lxdebug->enter_sub();
1939
1940   my ($self, $myconfig, $reference_date) = @_;
1941
1942   my $terms   = $self->{payment_id}  ? SL::DB::PaymentTerm->new(id => $self->{payment_id}) ->load
1943               : $self->{customer_id} ? SL::DB::Customer   ->new(id => $self->{customer_id})->load->payment
1944               : $self->{vendor_id}   ? SL::DB::Vendor     ->new(id => $self->{vendor_id})  ->load->payment
1945               :                        croak("Missing field in \$::form: payment_id, customer_id or vendor_id");
1946
1947   my $duedate = $terms->calc_date(reference_date => $reference_date)->to_kivitendo;
1948
1949   $main::lxdebug->leave_sub();
1950
1951   return $duedate;
1952 }
1953
1954 sub _get_contacts {
1955   $main::lxdebug->enter_sub();
1956
1957   my ($self, $dbh, $id, $key) = @_;
1958
1959   $key = "all_contacts" unless ($key);
1960
1961   if (!$id) {
1962     $self->{$key} = [];
1963     $main::lxdebug->leave_sub();
1964     return;
1965   }
1966
1967   my $query =
1968     qq|SELECT cp_id, cp_cv_id, cp_name, cp_givenname, cp_abteilung | .
1969     qq|FROM contacts | .
1970     qq|WHERE cp_cv_id = ? | .
1971     qq|ORDER BY lower(cp_name)|;
1972
1973   $self->{$key} = selectall_hashref_query($self, $dbh, $query, $id);
1974
1975   $main::lxdebug->leave_sub();
1976 }
1977
1978 sub _get_projects {
1979   $main::lxdebug->enter_sub();
1980
1981   my ($self, $dbh, $key) = @_;
1982
1983   my ($all, $old_id, $where, @values);
1984
1985   if (ref($key) eq "HASH") {
1986     my $params = $key;
1987
1988     $key = "ALL_PROJECTS";
1989
1990     foreach my $p (keys(%{$params})) {
1991       if ($p eq "all") {
1992         $all = $params->{$p};
1993       } elsif ($p eq "old_id") {
1994         $old_id = $params->{$p};
1995       } elsif ($p eq "key") {
1996         $key = $params->{$p};
1997       }
1998     }
1999   }
2000
2001   if (!$all) {
2002     $where = "WHERE active ";
2003     if ($old_id) {
2004       if (ref($old_id) eq "ARRAY") {
2005         my @ids = grep({ $_ } @{$old_id});
2006         if (@ids) {
2007           $where .= " OR id IN (" . join(",", map({ "?" } @ids)) . ") ";
2008           push(@values, @ids);
2009         }
2010       } else {
2011         $where .= " OR (id = ?) ";
2012         push(@values, $old_id);
2013       }
2014     }
2015   }
2016
2017   my $query =
2018     qq|SELECT id, projectnumber, description, active | .
2019     qq|FROM project | .
2020     $where .
2021     qq|ORDER BY lower(projectnumber)|;
2022
2023   $self->{$key} = selectall_hashref_query($self, $dbh, $query, @values);
2024
2025   $main::lxdebug->leave_sub();
2026 }
2027
2028 sub _get_shipto {
2029   $main::lxdebug->enter_sub();
2030
2031   my ($self, $dbh, $vc_id, $key) = @_;
2032
2033   $key = "all_shipto" unless ($key);
2034
2035   if ($vc_id) {
2036     # get shipping addresses
2037     my $query = qq|SELECT * FROM shipto WHERE trans_id = ?|;
2038
2039     $self->{$key} = selectall_hashref_query($self, $dbh, $query, $vc_id);
2040
2041   } else {
2042     $self->{$key} = [];
2043   }
2044
2045   $main::lxdebug->leave_sub();
2046 }
2047
2048 sub _get_printers {
2049   $main::lxdebug->enter_sub();
2050
2051   my ($self, $dbh, $key) = @_;
2052
2053   $key = "all_printers" unless ($key);
2054
2055   my $query = qq|SELECT id, printer_description, printer_command, template_code FROM printers|;
2056
2057   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2058
2059   $main::lxdebug->leave_sub();
2060 }
2061
2062 sub _get_charts {
2063   $main::lxdebug->enter_sub();
2064
2065   my ($self, $dbh, $params) = @_;
2066   my ($key);
2067
2068   $key = $params->{key};
2069   $key = "all_charts" unless ($key);
2070
2071   my $transdate = quote_db_date($params->{transdate});
2072
2073   my $query =
2074     qq|SELECT c.id, c.accno, c.description, c.link, c.charttype, tk.taxkey_id, tk.tax_id | .
2075     qq|FROM chart c | .
2076     qq|LEFT JOIN taxkeys tk ON | .
2077     qq|(tk.id = (SELECT id FROM taxkeys | .
2078     qq|          WHERE taxkeys.chart_id = c.id AND startdate <= $transdate | .
2079     qq|          ORDER BY startdate DESC LIMIT 1)) | .
2080     qq|ORDER BY c.accno|;
2081
2082   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2083
2084   $main::lxdebug->leave_sub();
2085 }
2086
2087 sub _get_taxcharts {
2088   $main::lxdebug->enter_sub();
2089
2090   my ($self, $dbh, $params) = @_;
2091
2092   my $key = "all_taxcharts";
2093   my @where;
2094
2095   if (ref $params eq 'HASH') {
2096     $key = $params->{key} if ($params->{key});
2097     if ($params->{module} eq 'AR') {
2098       push @where, 'taxkey NOT IN (8, 9, 18, 19)';
2099
2100     } elsif ($params->{module} eq 'AP') {
2101       push @where, 'taxkey NOT IN (1, 2, 3, 12, 13)';
2102     }
2103
2104   } elsif ($params) {
2105     $key = $params;
2106   }
2107
2108   my $where = @where ? ' WHERE ' . join(' AND ', map { "($_)" } @where) : '';
2109
2110   my $query = qq|SELECT * FROM tax $where ORDER BY taxkey, rate|;
2111
2112   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2113
2114   $main::lxdebug->leave_sub();
2115 }
2116
2117 sub _get_taxzones {
2118   $main::lxdebug->enter_sub();
2119
2120   my ($self, $dbh, $key) = @_;
2121
2122   $key = "all_taxzones" unless ($key);
2123
2124   my $query = qq|SELECT * FROM tax_zones ORDER BY id|;
2125
2126   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2127
2128   $main::lxdebug->leave_sub();
2129 }
2130
2131 sub _get_employees {
2132   $main::lxdebug->enter_sub();
2133
2134   my ($self, $dbh, $default_key, $key) = @_;
2135
2136   $key = $default_key unless ($key);
2137   $self->{$key} = selectall_hashref_query($self, $dbh, qq|SELECT * FROM employee ORDER BY lower(name)|);
2138
2139   $main::lxdebug->leave_sub();
2140 }
2141
2142 sub _get_business_types {
2143   $main::lxdebug->enter_sub();
2144
2145   my ($self, $dbh, $key) = @_;
2146
2147   my $options       = ref $key eq 'HASH' ? $key : { key => $key };
2148   $options->{key} ||= "all_business_types";
2149   my $where         = '';
2150
2151   if (exists $options->{salesman}) {
2152     $where = 'WHERE ' . ($options->{salesman} ? '' : 'NOT ') . 'COALESCE(salesman)';
2153   }
2154
2155   $self->{ $options->{key} } = selectall_hashref_query($self, $dbh, qq|SELECT * FROM business $where ORDER BY lower(description)|);
2156
2157   $main::lxdebug->leave_sub();
2158 }
2159
2160 sub _get_languages {
2161   $main::lxdebug->enter_sub();
2162
2163   my ($self, $dbh, $key) = @_;
2164
2165   $key = "all_languages" unless ($key);
2166
2167   my $query = qq|SELECT * FROM language ORDER BY id|;
2168
2169   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2170
2171   $main::lxdebug->leave_sub();
2172 }
2173
2174 sub _get_dunning_configs {
2175   $main::lxdebug->enter_sub();
2176
2177   my ($self, $dbh, $key) = @_;
2178
2179   $key = "all_dunning_configs" unless ($key);
2180
2181   my $query = qq|SELECT * FROM dunning_config ORDER BY dunning_level|;
2182
2183   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2184
2185   $main::lxdebug->leave_sub();
2186 }
2187
2188 sub _get_currencies {
2189 $main::lxdebug->enter_sub();
2190
2191   my ($self, $dbh, $key) = @_;
2192
2193   $key = "all_currencies" unless ($key);
2194
2195   $self->{$key} = [$self->get_all_currencies()];
2196
2197   $main::lxdebug->leave_sub();
2198 }
2199
2200 sub _get_payments {
2201 $main::lxdebug->enter_sub();
2202
2203   my ($self, $dbh, $key) = @_;
2204
2205   $key = "all_payments" unless ($key);
2206
2207   my $query = qq|SELECT * FROM payment_terms ORDER BY sortkey|;
2208
2209   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2210
2211   $main::lxdebug->leave_sub();
2212 }
2213
2214 sub _get_customers {
2215   $main::lxdebug->enter_sub();
2216
2217   my ($self, $dbh, $key) = @_;
2218
2219   my $options        = ref $key eq 'HASH' ? $key : { key => $key };
2220   $options->{key}  ||= "all_customers";
2221   my $limit_clause   = $options->{limit} ? "LIMIT $options->{limit}" : '';
2222
2223   my @where;
2224   push @where, qq|business_id IN (SELECT id FROM business WHERE salesman)| if  $options->{business_is_salesman};
2225   push @where, qq|NOT obsolete|                                            if !$options->{with_obsolete};
2226   my $where_str = @where ? "WHERE " . join(" AND ", map { "($_)" } @where) : '';
2227
2228   my $query = qq|SELECT * FROM customer $where_str ORDER BY name $limit_clause|;
2229   $self->{ $options->{key} } = selectall_hashref_query($self, $dbh, $query);
2230
2231   $main::lxdebug->leave_sub();
2232 }
2233
2234 sub _get_vendors {
2235   $main::lxdebug->enter_sub();
2236
2237   my ($self, $dbh, $key) = @_;
2238
2239   $key = "all_vendors" unless ($key);
2240
2241   my $query = qq|SELECT * FROM vendor WHERE NOT obsolete ORDER BY name|;
2242
2243   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2244
2245   $main::lxdebug->leave_sub();
2246 }
2247
2248 sub _get_departments {
2249   $main::lxdebug->enter_sub();
2250
2251   my ($self, $dbh, $key) = @_;
2252
2253   $key = "all_departments" unless ($key);
2254
2255   my $query = qq|SELECT * FROM department ORDER BY description|;
2256
2257   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2258
2259   $main::lxdebug->leave_sub();
2260 }
2261
2262 sub _get_warehouses {
2263   $main::lxdebug->enter_sub();
2264
2265   my ($self, $dbh, $param) = @_;
2266
2267   my ($key, $bins_key);
2268
2269   if ('' eq ref $param) {
2270     $key = $param;
2271
2272   } else {
2273     $key      = $param->{key};
2274     $bins_key = $param->{bins};
2275   }
2276
2277   my $query = qq|SELECT w.* FROM warehouse w
2278                  WHERE (NOT w.invalid) AND
2279                    ((SELECT COUNT(b.*) FROM bin b WHERE b.warehouse_id = w.id) > 0)
2280                  ORDER BY w.sortkey|;
2281
2282   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2283
2284   if ($bins_key) {
2285     $query = qq|SELECT id, description FROM bin WHERE warehouse_id = ?
2286                 ORDER BY description|;
2287     my $sth = prepare_query($self, $dbh, $query);
2288
2289     foreach my $warehouse (@{ $self->{$key} }) {
2290       do_statement($self, $sth, $query, $warehouse->{id});
2291       $warehouse->{$bins_key} = [];
2292
2293       while (my $ref = $sth->fetchrow_hashref()) {
2294         push @{ $warehouse->{$bins_key} }, $ref;
2295       }
2296     }
2297     $sth->finish();
2298   }
2299
2300   $main::lxdebug->leave_sub();
2301 }
2302
2303 sub _get_simple {
2304   $main::lxdebug->enter_sub();
2305
2306   my ($self, $dbh, $table, $key, $sortkey) = @_;
2307
2308   my $query  = qq|SELECT * FROM $table|;
2309   $query    .= qq| ORDER BY $sortkey| if ($sortkey);
2310
2311   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2312
2313   $main::lxdebug->leave_sub();
2314 }
2315
2316 #sub _get_groups {
2317 #  $main::lxdebug->enter_sub();
2318 #
2319 #  my ($self, $dbh, $key) = @_;
2320 #
2321 #  $key ||= "all_groups";
2322 #
2323 #  my $groups = $main::auth->read_groups();
2324 #
2325 #  $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2326 #
2327 #  $main::lxdebug->leave_sub();
2328 #}
2329
2330 sub get_lists {
2331   $main::lxdebug->enter_sub();
2332
2333   my $self = shift;
2334   my %params = @_;
2335
2336   my $dbh = $self->get_standard_dbh(\%main::myconfig);
2337   my ($sth, $query, $ref);
2338
2339   my $vc = $self->{"vc"} eq "customer" ? "customer" : "vendor";
2340   my $vc_id = $self->{"${vc}_id"};
2341
2342   if ($params{"contacts"}) {
2343     $self->_get_contacts($dbh, $vc_id, $params{"contacts"});
2344   }
2345
2346   if ($params{"shipto"}) {
2347     $self->_get_shipto($dbh, $vc_id, $params{"shipto"});
2348   }
2349
2350   if ($params{"projects"} || $params{"all_projects"}) {
2351     $self->_get_projects($dbh, $params{"all_projects"} ?
2352                          $params{"all_projects"} : $params{"projects"},
2353                          $params{"all_projects"} ? 1 : 0);
2354   }
2355
2356   if ($params{"printers"}) {
2357     $self->_get_printers($dbh, $params{"printers"});
2358   }
2359
2360   if ($params{"languages"}) {
2361     $self->_get_languages($dbh, $params{"languages"});
2362   }
2363
2364   if ($params{"charts"}) {
2365     $self->_get_charts($dbh, $params{"charts"});
2366   }
2367
2368   if ($params{"taxcharts"}) {
2369     $self->_get_taxcharts($dbh, $params{"taxcharts"});
2370   }
2371
2372   if ($params{"taxzones"}) {
2373     $self->_get_taxzones($dbh, $params{"taxzones"});
2374   }
2375
2376   if ($params{"employees"}) {
2377     $self->_get_employees($dbh, "all_employees", $params{"employees"});
2378   }
2379
2380   if ($params{"salesmen"}) {
2381     $self->_get_employees($dbh, "all_salesmen", $params{"salesmen"});
2382   }
2383
2384   if ($params{"business_types"}) {
2385     $self->_get_business_types($dbh, $params{"business_types"});
2386   }
2387
2388   if ($params{"dunning_configs"}) {
2389     $self->_get_dunning_configs($dbh, $params{"dunning_configs"});
2390   }
2391
2392   if($params{"currencies"}) {
2393     $self->_get_currencies($dbh, $params{"currencies"});
2394   }
2395
2396   if($params{"customers"}) {
2397     $self->_get_customers($dbh, $params{"customers"});
2398   }
2399
2400   if($params{"vendors"}) {
2401     if (ref $params{"vendors"} eq 'HASH') {
2402       $self->_get_vendors($dbh, $params{"vendors"}{key}, $params{"vendors"}{limit});
2403     } else {
2404       $self->_get_vendors($dbh, $params{"vendors"});
2405     }
2406   }
2407
2408   if($params{"payments"}) {
2409     $self->_get_payments($dbh, $params{"payments"});
2410   }
2411
2412   if($params{"departments"}) {
2413     $self->_get_departments($dbh, $params{"departments"});
2414   }
2415
2416   if ($params{price_factors}) {
2417     $self->_get_simple($dbh, 'price_factors', $params{price_factors}, 'sortkey');
2418   }
2419
2420   if ($params{warehouses}) {
2421     $self->_get_warehouses($dbh, $params{warehouses});
2422   }
2423
2424 #  if ($params{groups}) {
2425 #    $self->_get_groups($dbh, $params{groups});
2426 #  }
2427
2428   if ($params{partsgroup}) {
2429     $self->get_partsgroup(\%main::myconfig, { all => 1, target => $params{partsgroup} });
2430   }
2431
2432   $main::lxdebug->leave_sub();
2433 }
2434
2435 # this sub gets the id and name from $table
2436 sub get_name {
2437   $main::lxdebug->enter_sub();
2438
2439   my ($self, $myconfig, $table) = @_;
2440
2441   # connect to database
2442   my $dbh = $self->get_standard_dbh($myconfig);
2443
2444   $table = $table eq "customer" ? "customer" : "vendor";
2445   my $arap = $self->{arap} eq "ar" ? "ar" : "ap";
2446
2447   my ($query, @values);
2448
2449   if (!$self->{openinvoices}) {
2450     my $where;
2451     if ($self->{customernumber} ne "") {
2452       $where = qq|(vc.customernumber ILIKE ?)|;
2453       push(@values, '%' . $self->{customernumber} . '%');
2454     } else {
2455       $where = qq|(vc.name ILIKE ?)|;
2456       push(@values, '%' . $self->{$table} . '%');
2457     }
2458
2459     $query =
2460       qq~SELECT vc.id, vc.name,
2461            vc.street || ' ' || vc.zipcode || ' ' || vc.city || ' ' || vc.country AS address
2462          FROM $table vc
2463          WHERE $where AND (NOT vc.obsolete)
2464          ORDER BY vc.name~;
2465   } else {
2466     $query =
2467       qq~SELECT DISTINCT vc.id, vc.name,
2468            vc.street || ' ' || vc.zipcode || ' ' || vc.city || ' ' || vc.country AS address
2469          FROM $arap a
2470          JOIN $table vc ON (a.${table}_id = vc.id)
2471          WHERE NOT (a.amount = a.paid) AND (vc.name ILIKE ?)
2472          ORDER BY vc.name~;
2473     push(@values, '%' . $self->{$table} . '%');
2474   }
2475
2476   $self->{name_list} = selectall_hashref_query($self, $dbh, $query, @values);
2477
2478   $main::lxdebug->leave_sub();
2479
2480   return scalar(@{ $self->{name_list} });
2481 }
2482
2483 # the selection sub is used in the AR, AP, IS, IR, DO and OE module
2484 #
2485 sub all_vc {
2486   $main::lxdebug->enter_sub();
2487
2488   my ($self, $myconfig, $table, $module) = @_;
2489
2490   my $ref;
2491   my $dbh = $self->get_standard_dbh;
2492
2493   $table = $table eq "customer" ? "customer" : "vendor";
2494
2495   # build selection list
2496   # Hotfix für Bug 1837 - Besser wäre es alte Buchungsbelege
2497   # OHNE Auswahlliste (reines Textfeld) zu laden. Hilft aber auch
2498   # nicht für veränderbare Belege (oe, do, ...)
2499   my $obsolete = $self->{id} ? '' : "WHERE NOT obsolete";
2500   my $query = qq|SELECT count(*) FROM $table $obsolete|;
2501   my ($count) = selectrow_query($self, $dbh, $query);
2502
2503   if ($count <= $myconfig->{vclimit}) {
2504     $query = qq|SELECT id, name, salesman_id
2505                 FROM $table $obsolete
2506                 ORDER BY name|;
2507     $self->{"all_$table"} = selectall_hashref_query($self, $dbh, $query);
2508   }
2509
2510   # get self
2511   $self->get_employee($dbh);
2512
2513   # setup sales contacts
2514   $query = qq|SELECT e.id, e.name
2515               FROM employee e
2516               WHERE (e.sales = '1') AND (NOT e.id = ?)
2517               ORDER BY name|;
2518   $self->{all_employees} = selectall_hashref_query($self, $dbh, $query, $self->{employee_id});
2519
2520   # this is for self
2521   push(@{ $self->{all_employees} },
2522        { id   => $self->{employee_id},
2523          name => $self->{employee} });
2524
2525     # prepare query for departments
2526     $query = qq|SELECT id, description
2527                 FROM department
2528                 ORDER BY description|;
2529
2530   $self->{all_departments} = selectall_hashref_query($self, $dbh, $query);
2531
2532   # get languages
2533   $query = qq|SELECT id, description
2534               FROM language
2535               ORDER BY id|;
2536
2537   $self->{languages} = selectall_hashref_query($self, $dbh, $query);
2538
2539   # get printer
2540   $query = qq|SELECT printer_description, id
2541               FROM printers
2542               ORDER BY printer_description|;
2543
2544   $self->{printers} = selectall_hashref_query($self, $dbh, $query);
2545
2546   # get payment terms
2547   $query = qq|SELECT id, description
2548               FROM payment_terms
2549               ORDER BY sortkey|;
2550
2551   $self->{payment_terms} = selectall_hashref_query($self, $dbh, $query);
2552
2553   $main::lxdebug->leave_sub();
2554 }
2555
2556 sub language_payment {
2557   $main::lxdebug->enter_sub();
2558
2559   my ($self, $myconfig) = @_;
2560
2561   my $dbh = $self->get_standard_dbh($myconfig);
2562   # get languages
2563   my $query = qq|SELECT id, description
2564                  FROM language
2565                  ORDER BY id|;
2566
2567   $self->{languages} = selectall_hashref_query($self, $dbh, $query);
2568
2569   # get printer
2570   $query = qq|SELECT printer_description, id
2571               FROM printers
2572               ORDER BY printer_description|;
2573
2574   $self->{printers} = selectall_hashref_query($self, $dbh, $query);
2575
2576   # get payment terms
2577   $query = qq|SELECT id, description
2578               FROM payment_terms
2579               ORDER BY sortkey|;
2580
2581   $self->{payment_terms} = selectall_hashref_query($self, $dbh, $query);
2582
2583   # get buchungsgruppen
2584   $query = qq|SELECT id, description
2585               FROM buchungsgruppen|;
2586
2587   $self->{BUCHUNGSGRUPPEN} = selectall_hashref_query($self, $dbh, $query);
2588
2589   $main::lxdebug->leave_sub();
2590 }
2591
2592 # this is only used for reports
2593 sub all_departments {
2594   $main::lxdebug->enter_sub();
2595
2596   my ($self, $myconfig, $table) = @_;
2597
2598   my $dbh = $self->get_standard_dbh($myconfig);
2599
2600   my $query = qq|SELECT id, description
2601                  FROM department
2602                  ORDER BY description|;
2603   $self->{all_departments} = selectall_hashref_query($self, $dbh, $query);
2604
2605   delete($self->{all_departments}) unless (@{ $self->{all_departments} || [] });
2606
2607   $main::lxdebug->leave_sub();
2608 }
2609
2610 sub create_links {
2611   $main::lxdebug->enter_sub();
2612
2613   my ($self, $module, $myconfig, $table, $provided_dbh) = @_;
2614
2615   my ($fld, $arap);
2616   if ($table eq "customer") {
2617     $fld = "buy";
2618     $arap = "ar";
2619   } else {
2620     $table = "vendor";
2621     $fld = "sell";
2622     $arap = "ap";
2623   }
2624
2625   $self->all_vc($myconfig, $table, $module);
2626
2627   # get last customers or vendors
2628   my ($query, $sth, $ref);
2629
2630   my $dbh = $provided_dbh ? $provided_dbh : $self->get_standard_dbh($myconfig);
2631   my %xkeyref = ();
2632
2633   if (!$self->{id}) {
2634
2635     my $transdate = "current_date";
2636     if ($self->{transdate}) {
2637       $transdate = $dbh->quote($self->{transdate});
2638     }
2639
2640     # now get the account numbers
2641 #    $query = qq|SELECT c.accno, c.description, c.link, c.taxkey_id, tk.tax_id
2642 #                FROM chart c, taxkeys tk
2643 #                WHERE (c.link LIKE ?) AND (c.id = tk.chart_id) AND tk.id =
2644 #                  (SELECT id FROM taxkeys WHERE (taxkeys.chart_id = c.id) AND (startdate <= $transdate) ORDER BY startdate DESC LIMIT 1)
2645 #                ORDER BY c.accno|;
2646
2647 #  same query as above, but without expensive subquery for each row. about 80% faster
2648     $query = qq|
2649       SELECT c.accno, c.description, c.link, c.taxkey_id, tk2.tax_id
2650         FROM chart c
2651         -- find newest entries in taxkeys
2652         INNER JOIN (
2653           SELECT chart_id, MAX(startdate) AS startdate
2654           FROM taxkeys
2655           WHERE (startdate <= $transdate)
2656           GROUP BY chart_id
2657         ) tk ON (c.id = tk.chart_id)
2658         -- and load all of those entries
2659         INNER JOIN taxkeys tk2
2660            ON (tk.chart_id = tk2.chart_id AND tk.startdate = tk2.startdate)
2661        WHERE (c.link LIKE ?)
2662       ORDER BY c.accno|;
2663
2664     $sth = $dbh->prepare($query);
2665
2666     do_statement($self, $sth, $query, '%' . $module . '%');
2667
2668     $self->{accounts} = "";
2669     while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
2670
2671       foreach my $key (split(/:/, $ref->{link})) {
2672         if ($key =~ /\Q$module\E/) {
2673
2674           # cross reference for keys
2675           $xkeyref{ $ref->{accno} } = $key;
2676
2677           push @{ $self->{"${module}_links"}{$key} },
2678             { accno       => $ref->{accno},
2679               description => $ref->{description},
2680               taxkey      => $ref->{taxkey_id},
2681               tax_id      => $ref->{tax_id} };
2682
2683           $self->{accounts} .= "$ref->{accno} " unless $key =~ /tax/;
2684         }
2685       }
2686     }
2687   }
2688
2689   # get taxkeys and description
2690   $query = qq|SELECT id, taxkey, taxdescription FROM tax|;
2691   $self->{TAXKEY} = selectall_hashref_query($self, $dbh, $query);
2692
2693   if (($module eq "AP") || ($module eq "AR")) {
2694     # get tax rates and description
2695     $query = qq|SELECT * FROM tax|;
2696     $self->{TAX} = selectall_hashref_query($self, $dbh, $query);
2697   }
2698
2699   my $extra_columns = '';
2700   $extra_columns   .= 'a.direct_debit, ' if ($module eq 'AR') || ($module eq 'AP');
2701
2702   if ($self->{id}) {
2703     $query =
2704       qq|SELECT
2705            a.cp_id, a.invnumber, a.transdate, a.${table}_id, a.datepaid,
2706            a.duedate, a.ordnumber, a.taxincluded, (SELECT cu.name FROM currencies cu WHERE cu.id=a.currency_id) AS currency, a.notes,
2707            a.intnotes, a.department_id, a.amount AS oldinvtotal,
2708            a.paid AS oldtotalpaid, a.employee_id, a.gldate, a.type,
2709            a.globalproject_id, ${extra_columns}
2710            c.name AS $table,
2711            d.description AS department,
2712            e.name AS employee
2713          FROM $arap a
2714          JOIN $table c ON (a.${table}_id = c.id)
2715          LEFT JOIN employee e ON (e.id = a.employee_id)
2716          LEFT JOIN department d ON (d.id = a.department_id)
2717          WHERE a.id = ?|;
2718     $ref = selectfirst_hashref_query($self, $dbh, $query, $self->{id});
2719
2720     foreach my $key (keys %$ref) {
2721       $self->{$key} = $ref->{$key};
2722     }
2723
2724     my $transdate = "current_date";
2725     if ($self->{transdate}) {
2726       $transdate = $dbh->quote($self->{transdate});
2727     }
2728
2729     # now get the account numbers
2730     $query = qq|SELECT c.accno, c.description, c.link, c.taxkey_id, tk.tax_id
2731                 FROM chart c
2732                 LEFT JOIN taxkeys tk ON (tk.chart_id = c.id)
2733                 WHERE c.link LIKE ?
2734                   AND (tk.id = (SELECT id FROM taxkeys WHERE taxkeys.chart_id = c.id AND startdate <= $transdate ORDER BY startdate DESC LIMIT 1)
2735                     OR c.link LIKE '%_tax%' OR c.taxkey_id IS NULL)
2736                 ORDER BY c.accno|;
2737
2738     $sth = $dbh->prepare($query);
2739     do_statement($self, $sth, $query, "%$module%");
2740
2741     $self->{accounts} = "";
2742     while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
2743
2744       foreach my $key (split(/:/, $ref->{link})) {
2745         if ($key =~ /\Q$module\E/) {
2746
2747           # cross reference for keys
2748           $xkeyref{ $ref->{accno} } = $key;
2749
2750           push @{ $self->{"${module}_links"}{$key} },
2751             { accno       => $ref->{accno},
2752               description => $ref->{description},
2753               taxkey      => $ref->{taxkey_id},
2754               tax_id      => $ref->{tax_id} };
2755
2756           $self->{accounts} .= "$ref->{accno} " unless $key =~ /tax/;
2757         }
2758       }
2759     }
2760
2761
2762     # get amounts from individual entries
2763     $query =
2764       qq|SELECT
2765            c.accno, c.description,
2766            a.acc_trans_id, a.source, a.amount, a.memo, a.transdate, a.gldate, a.cleared, a.project_id, a.taxkey,
2767            p.projectnumber,
2768            t.rate, t.id
2769          FROM acc_trans a
2770          LEFT JOIN chart c ON (c.id = a.chart_id)
2771          LEFT JOIN project p ON (p.id = a.project_id)
2772          LEFT JOIN tax t ON (t.id= a.tax_id)
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 = $self->{media} eq 'email' && -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