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