19f2b5cf59342f490a9085148d136b3720980153
[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   if ($self->{id}) {
2740     $query =
2741       qq|SELECT
2742            a.cp_id, a.invnumber, a.transdate, a.${table}_id, a.datepaid,
2743            a.duedate, a.ordnumber, a.taxincluded, a.curr AS currency, a.notes,
2744            a.intnotes, a.department_id, a.amount AS oldinvtotal,
2745            a.paid AS oldtotalpaid, a.employee_id, a.gldate, a.type,
2746            a.globalproject_id,
2747            c.name AS $table,
2748            d.description AS department,
2749            e.name AS employee
2750          FROM $arap a
2751          JOIN $table c ON (a.${table}_id = c.id)
2752          LEFT JOIN employee e ON (e.id = a.employee_id)
2753          LEFT JOIN department d ON (d.id = a.department_id)
2754          WHERE a.id = ?|;
2755     $ref = selectfirst_hashref_query($self, $dbh, $query, $self->{id});
2756
2757     foreach my $key (keys %$ref) {
2758       $self->{$key} = $ref->{$key};
2759     }
2760
2761     # remove any trailing whitespace
2762     $self->{currency} =~ s/\s*$//;
2763
2764     my $transdate = "current_date";
2765     if ($self->{transdate}) {
2766       $transdate = $dbh->quote($self->{transdate});
2767     }
2768
2769     # now get the account numbers
2770     $query = qq|SELECT c.accno, c.description, c.link, c.taxkey_id, tk.tax_id
2771                 FROM chart c
2772                 LEFT JOIN taxkeys tk ON (tk.chart_id = c.id)
2773                 WHERE c.link LIKE ?
2774                   AND (tk.id = (SELECT id FROM taxkeys WHERE taxkeys.chart_id = c.id AND startdate <= $transdate ORDER BY startdate DESC LIMIT 1)
2775                     OR c.link LIKE '%_tax%' OR c.taxkey_id IS NULL)
2776                 ORDER BY c.accno|;
2777
2778     $sth = $dbh->prepare($query);
2779     do_statement($self, $sth, $query, "%$module%");
2780
2781     $self->{accounts} = "";
2782     while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
2783
2784       foreach my $key (split(/:/, $ref->{link})) {
2785         if ($key =~ /\Q$module\E/) {
2786
2787           # cross reference for keys
2788           $xkeyref{ $ref->{accno} } = $key;
2789
2790           push @{ $self->{"${module}_links"}{$key} },
2791             { accno       => $ref->{accno},
2792               description => $ref->{description},
2793               taxkey      => $ref->{taxkey_id},
2794               tax_id      => $ref->{tax_id} };
2795
2796           $self->{accounts} .= "$ref->{accno} " unless $key =~ /tax/;
2797         }
2798       }
2799     }
2800
2801
2802     # get amounts from individual entries
2803     $query =
2804       qq|SELECT
2805            c.accno, c.description,
2806            a.acc_trans_id, a.source, a.amount, a.memo, a.transdate, a.gldate, a.cleared, a.project_id, a.taxkey,
2807            p.projectnumber,
2808            t.rate, t.id
2809          FROM acc_trans a
2810          LEFT JOIN chart c ON (c.id = a.chart_id)
2811          LEFT JOIN project p ON (p.id = a.project_id)
2812          LEFT JOIN tax t ON (t.id= (SELECT tk.tax_id FROM taxkeys tk
2813                                     WHERE (tk.taxkey_id=a.taxkey) AND
2814                                       ((CASE WHEN a.chart_id IN (SELECT chart_id FROM taxkeys WHERE taxkey_id = a.taxkey)
2815                                         THEN tk.chart_id = a.chart_id
2816                                         ELSE 1 = 1
2817                                         END)
2818                                        OR (c.link='%tax%')) AND
2819                                       (startdate <= a.transdate) ORDER BY startdate DESC LIMIT 1))
2820          WHERE a.trans_id = ?
2821          AND a.fx_transaction = '0'
2822          ORDER BY a.acc_trans_id, a.transdate|;
2823     $sth = $dbh->prepare($query);
2824     do_statement($self, $sth, $query, $self->{id});
2825
2826     # get exchangerate for currency
2827     $self->{exchangerate} =
2828       $self->get_exchangerate($dbh, $self->{currency}, $self->{transdate}, $fld);
2829     my $index = 0;
2830
2831     # store amounts in {acc_trans}{$key} for multiple accounts
2832     while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
2833       $ref->{exchangerate} =
2834         $self->get_exchangerate($dbh, $self->{currency}, $ref->{transdate}, $fld);
2835       if (!($xkeyref{ $ref->{accno} } =~ /tax/)) {
2836         $index++;
2837       }
2838       if (($xkeyref{ $ref->{accno} } =~ /paid/) && ($self->{type} eq "credit_note")) {
2839         $ref->{amount} *= -1;
2840       }
2841       $ref->{index} = $index;
2842
2843       push @{ $self->{acc_trans}{ $xkeyref{ $ref->{accno} } } }, $ref;
2844     }
2845
2846     $sth->finish;
2847     $query =
2848       qq|SELECT
2849            d.curr AS currencies, d.closedto, d.revtrans,
2850            (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno,
2851            (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno
2852          FROM defaults d|;
2853     $ref = selectfirst_hashref_query($self, $dbh, $query);
2854     map { $self->{$_} = $ref->{$_} } keys %$ref;
2855
2856   } else {
2857
2858     # get date
2859     $query =
2860        qq|SELECT
2861             current_date AS transdate, d.curr AS currencies, d.closedto, d.revtrans,
2862             (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno,
2863             (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno
2864           FROM defaults d|;
2865     $ref = selectfirst_hashref_query($self, $dbh, $query);
2866     map { $self->{$_} = $ref->{$_} } keys %$ref;
2867
2868     if ($self->{"$self->{vc}_id"}) {
2869
2870       # only setup currency
2871       ($self->{currency}) = split(/:/, $self->{currencies}) if !$self->{currency};
2872
2873     } else {
2874
2875       $self->lastname_used($dbh, $myconfig, $table, $module);
2876
2877       # get exchangerate for currency
2878       $self->{exchangerate} =
2879         $self->get_exchangerate($dbh, $self->{currency}, $self->{transdate}, $fld);
2880
2881     }
2882
2883   }
2884
2885   $main::lxdebug->leave_sub();
2886 }
2887
2888 sub lastname_used {
2889   $main::lxdebug->enter_sub();
2890
2891   my ($self, $dbh, $myconfig, $table, $module) = @_;
2892
2893   my ($arap, $where);
2894
2895   $table         = $table eq "customer" ? "customer" : "vendor";
2896   my %column_map = ("a.curr"                  => "currency",
2897                     "a.${table}_id"           => "${table}_id",
2898                     "a.department_id"         => "department_id",
2899                     "d.description"           => "department",
2900                     "ct.name"                 => $table,
2901                     "ct.curr"                 => "cv_curr",
2902                     "current_date + ct.terms" => "duedate",
2903     );
2904
2905   if ($self->{type} =~ /delivery_order/) {
2906     $arap  = 'delivery_orders';
2907     delete $column_map{"a.curr"};
2908     delete $column_map{"ct.curr"};
2909
2910   } elsif ($self->{type} =~ /_order/) {
2911     $arap  = 'oe';
2912     $where = "quotation = '0'";
2913
2914   } elsif ($self->{type} =~ /_quotation/) {
2915     $arap  = 'oe';
2916     $where = "quotation = '1'";
2917
2918   } elsif ($table eq 'customer') {
2919     $arap  = 'ar';
2920
2921   } else {
2922     $arap  = 'ap';
2923
2924   }
2925
2926   $where           = "($where) AND" if ($where);
2927   my $query        = qq|SELECT MAX(id) FROM $arap
2928                         WHERE $where ${table}_id > 0|;
2929   my ($trans_id)   = selectrow_query($self, $dbh, $query);
2930   $trans_id       *= 1;
2931
2932   my $column_spec  = join(', ', map { "${_} AS $column_map{$_}" } keys %column_map);
2933   $query           = qq|SELECT $column_spec
2934                         FROM $arap a
2935                         LEFT JOIN $table     ct ON (a.${table}_id = ct.id)
2936                         LEFT JOIN department d  ON (a.department_id = d.id)
2937                         WHERE a.id = ?|;
2938   my $ref          = selectfirst_hashref_query($self, $dbh, $query, $trans_id);
2939
2940   map { $self->{$_} = $ref->{$_} } values %column_map;
2941
2942   # remove any trailing whitespace
2943   $self->{currency} =~ s/\s*$// if $self->{currency};
2944   $self->{cv_curr} =~ s/\s*$// if $self->{cv_curr};
2945
2946   # if customer/vendor currency is set use this
2947   $self->{currency} = $self->{cv_curr} if $self->{cv_curr};
2948
2949   $main::lxdebug->leave_sub();
2950 }
2951
2952 sub current_date {
2953   $main::lxdebug->enter_sub();
2954
2955   my $self     = shift;
2956   my $myconfig = shift || \%::myconfig;
2957   my ($thisdate, $days) = @_;
2958
2959   my $dbh = $self->get_standard_dbh($myconfig);
2960   my $query;
2961
2962   $days *= 1;
2963   if ($thisdate) {
2964     my $dateformat = $myconfig->{dateformat};
2965     $dateformat .= "yy" if $myconfig->{dateformat} !~ /^y/;
2966     $thisdate = $dbh->quote($thisdate);
2967     $query = qq|SELECT to_date($thisdate, '$dateformat') + $days AS thisdate|;
2968   } else {
2969     $query = qq|SELECT current_date AS thisdate|;
2970   }
2971
2972   ($thisdate) = selectrow_query($self, $dbh, $query);
2973
2974   $main::lxdebug->leave_sub();
2975
2976   return $thisdate;
2977 }
2978
2979 sub like {
2980   $main::lxdebug->enter_sub();
2981
2982   my ($self, $string) = @_;
2983
2984   if ($string !~ /%/) {
2985     $string = "%$string%";
2986   }
2987
2988   $string =~ s/\'/\'\'/g;
2989
2990   $main::lxdebug->leave_sub();
2991
2992   return $string;
2993 }
2994
2995 sub redo_rows {
2996   $main::lxdebug->enter_sub();
2997
2998   my ($self, $flds, $new, $count, $numrows) = @_;
2999
3000   my @ndx = ();
3001
3002   map { push @ndx, { num => $new->[$_ - 1]->{runningnumber}, ndx => $_ } } 1 .. $count;
3003
3004   my $i = 0;
3005
3006   # fill rows
3007   foreach my $item (sort { $a->{num} <=> $b->{num} } @ndx) {
3008     $i++;
3009     my $j = $item->{ndx} - 1;
3010     map { $self->{"${_}_$i"} = $new->[$j]->{$_} } @{$flds};
3011   }
3012
3013   # delete empty rows
3014   for $i ($count + 1 .. $numrows) {
3015     map { delete $self->{"${_}_$i"} } @{$flds};
3016   }
3017
3018   $main::lxdebug->leave_sub();
3019 }
3020
3021 sub update_status {
3022   $main::lxdebug->enter_sub();
3023
3024   my ($self, $myconfig) = @_;
3025
3026   my ($i, $id);
3027
3028   my $dbh = $self->dbconnect_noauto($myconfig);
3029
3030   my $query = qq|DELETE FROM status
3031                  WHERE (formname = ?) AND (trans_id = ?)|;
3032   my $sth = prepare_query($self, $dbh, $query);
3033
3034   if ($self->{formname} =~ /(check|receipt)/) {
3035     for $i (1 .. $self->{rowcount}) {
3036       do_statement($self, $sth, $query, $self->{formname}, $self->{"id_$i"} * 1);
3037     }
3038   } else {
3039     do_statement($self, $sth, $query, $self->{formname}, $self->{id});
3040   }
3041   $sth->finish();
3042
3043   my $printed = ($self->{printed} =~ /\Q$self->{formname}\E/) ? "1" : "0";
3044   my $emailed = ($self->{emailed} =~ /\Q$self->{formname}\E/) ? "1" : "0";
3045
3046   my %queued = split / /, $self->{queued};
3047   my @values;
3048
3049   if ($self->{formname} =~ /(check|receipt)/) {
3050
3051     # this is a check or receipt, add one entry for each lineitem
3052     my ($accno) = split /--/, $self->{account};
3053     $query = qq|INSERT INTO status (trans_id, printed, spoolfile, formname, chart_id)
3054                 VALUES (?, ?, ?, ?, (SELECT c.id FROM chart c WHERE c.accno = ?))|;
3055     @values = ($printed, $queued{$self->{formname}}, $self->{prinform}, $accno);
3056     $sth = prepare_query($self, $dbh, $query);
3057
3058     for $i (1 .. $self->{rowcount}) {
3059       if ($self->{"checked_$i"}) {
3060         do_statement($self, $sth, $query, $self->{"id_$i"}, @values);
3061       }
3062     }
3063     $sth->finish();
3064
3065   } else {
3066     $query = qq|INSERT INTO status (trans_id, printed, emailed, spoolfile, formname)
3067                 VALUES (?, ?, ?, ?, ?)|;
3068     do_query($self, $dbh, $query, $self->{id}, $printed, $emailed,
3069              $queued{$self->{formname}}, $self->{formname});
3070   }
3071
3072   $dbh->commit;
3073   $dbh->disconnect;
3074
3075   $main::lxdebug->leave_sub();
3076 }
3077
3078 sub save_status {
3079   $main::lxdebug->enter_sub();
3080
3081   my ($self, $dbh) = @_;
3082
3083   my ($query, $printed, $emailed);
3084
3085   my $formnames  = $self->{printed};
3086   my $emailforms = $self->{emailed};
3087
3088   $query = qq|DELETE FROM status
3089                  WHERE (formname = ?) AND (trans_id = ?)|;
3090   do_query($self, $dbh, $query, $self->{formname}, $self->{id});
3091
3092   # this only applies to the forms
3093   # checks and receipts are posted when printed or queued
3094
3095   if ($self->{queued}) {
3096     my %queued = split / /, $self->{queued};
3097
3098     foreach my $formname (keys %queued) {
3099       $printed = ($self->{printed} =~ /\Q$self->{formname}\E/) ? "1" : "0";
3100       $emailed = ($self->{emailed} =~ /\Q$self->{formname}\E/) ? "1" : "0";
3101
3102       $query = qq|INSERT INTO status (trans_id, printed, emailed, spoolfile, formname)
3103                   VALUES (?, ?, ?, ?, ?)|;
3104       do_query($self, $dbh, $query, $self->{id}, $printed, $emailed, $queued{$formname}, $formname);
3105
3106       $formnames  =~ s/\Q$self->{formname}\E//;
3107       $emailforms =~ s/\Q$self->{formname}\E//;
3108
3109     }
3110   }
3111
3112   # save printed, emailed info
3113   $formnames  =~ s/^ +//g;
3114   $emailforms =~ s/^ +//g;
3115
3116   my %status = ();
3117   map { $status{$_}{printed} = 1 } split / +/, $formnames;
3118   map { $status{$_}{emailed} = 1 } split / +/, $emailforms;
3119
3120   foreach my $formname (keys %status) {
3121     $printed = ($formnames  =~ /\Q$self->{formname}\E/) ? "1" : "0";
3122     $emailed = ($emailforms =~ /\Q$self->{formname}\E/) ? "1" : "0";
3123
3124     $query = qq|INSERT INTO status (trans_id, printed, emailed, formname)
3125                 VALUES (?, ?, ?, ?)|;
3126     do_query($self, $dbh, $query, $self->{id}, $printed, $emailed, $formname);
3127   }
3128
3129   $main::lxdebug->leave_sub();
3130 }
3131
3132 #--- 4 locale ---#
3133 # $main::locale->text('SAVED')
3134 # $main::locale->text('DELETED')
3135 # $main::locale->text('ADDED')
3136 # $main::locale->text('PAYMENT POSTED')
3137 # $main::locale->text('POSTED')
3138 # $main::locale->text('POSTED AS NEW')
3139 # $main::locale->text('ELSE')
3140 # $main::locale->text('SAVED FOR DUNNING')
3141 # $main::locale->text('DUNNING STARTED')
3142 # $main::locale->text('PRINTED')
3143 # $main::locale->text('MAILED')
3144 # $main::locale->text('SCREENED')
3145 # $main::locale->text('CANCELED')
3146 # $main::locale->text('invoice')
3147 # $main::locale->text('proforma')
3148 # $main::locale->text('sales_order')
3149 # $main::locale->text('pick_list')
3150 # $main::locale->text('purchase_order')
3151 # $main::locale->text('bin_list')
3152 # $main::locale->text('sales_quotation')
3153 # $main::locale->text('request_quotation')
3154
3155 sub save_history {
3156   $main::lxdebug->enter_sub();
3157
3158   my $self = shift;
3159   my $dbh  = shift || $self->get_standard_dbh;
3160
3161   if(!exists $self->{employee_id}) {
3162     &get_employee($self, $dbh);
3163   }
3164
3165   my $query =
3166    qq|INSERT INTO history_erp (trans_id, employee_id, addition, what_done, snumbers) | .
3167    qq|VALUES (?, (SELECT id FROM employee WHERE login = ?), ?, ?, ?)|;
3168   my @values = (conv_i($self->{id}), $self->{login},
3169                 $self->{addition}, $self->{what_done}, "$self->{snumbers}");
3170   do_query($self, $dbh, $query, @values);
3171
3172   $dbh->commit;
3173
3174   $main::lxdebug->leave_sub();
3175 }
3176
3177 sub get_history {
3178   $main::lxdebug->enter_sub();
3179
3180   my ($self, $dbh, $trans_id, $restriction, $order) = @_;
3181   my ($orderBy, $desc) = split(/\-\-/, $order);
3182   $order = " ORDER BY " . ($order eq "" ? " h.itime " : ($desc == 1 ? $orderBy . " DESC " : $orderBy . " "));
3183   my @tempArray;
3184   my $i = 0;
3185   if ($trans_id ne "") {
3186     my $query =
3187       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 | .
3188       qq|FROM history_erp h | .
3189       qq|LEFT JOIN employee emp ON (emp.id = h.employee_id) | .
3190       qq|WHERE (trans_id = | . $trans_id . qq|) $restriction | .
3191       $order;
3192
3193     my $sth = $dbh->prepare($query) || $self->dberror($query);
3194
3195     $sth->execute() || $self->dberror("$query");
3196
3197     while(my $hash_ref = $sth->fetchrow_hashref()) {
3198       $hash_ref->{addition} = $main::locale->text($hash_ref->{addition});
3199       $hash_ref->{what_done} = $main::locale->text($hash_ref->{what_done});
3200       $hash_ref->{snumbers} =~ s/^.+_(.*)$/$1/g;
3201       $tempArray[$i++] = $hash_ref;
3202     }
3203     $main::lxdebug->leave_sub() and return \@tempArray
3204       if ($i > 0 && $tempArray[0] ne "");
3205   }
3206   $main::lxdebug->leave_sub();
3207   return 0;
3208 }
3209
3210 sub update_defaults {
3211   $main::lxdebug->enter_sub();
3212
3213   my ($self, $myconfig, $fld, $provided_dbh) = @_;
3214
3215   my $dbh;
3216   if ($provided_dbh) {
3217     $dbh = $provided_dbh;
3218   } else {
3219     $dbh = $self->dbconnect_noauto($myconfig);
3220   }
3221   my $query = qq|SELECT $fld FROM defaults FOR UPDATE|;
3222   my $sth   = $dbh->prepare($query);
3223
3224   $sth->execute || $self->dberror($query);
3225   my ($var) = $sth->fetchrow_array;
3226   $sth->finish;
3227
3228   if ($var =~ m/\d+$/) {
3229     my $new_var  = (substr $var, $-[0]) * 1 + 1;
3230     my $len_diff = length($var) - $-[0] - length($new_var);
3231     $var         = substr($var, 0, $-[0]) . ($len_diff > 0 ? '0' x $len_diff : '') . $new_var;
3232
3233   } else {
3234     $var = $var . '1';
3235   }
3236
3237   $query = qq|UPDATE defaults SET $fld = ?|;
3238   do_query($self, $dbh, $query, $var);
3239
3240   if (!$provided_dbh) {
3241     $dbh->commit;
3242     $dbh->disconnect;
3243   }
3244
3245   $main::lxdebug->leave_sub();
3246
3247   return $var;
3248 }
3249
3250 sub update_business {
3251   $main::lxdebug->enter_sub();
3252
3253   my ($self, $myconfig, $business_id, $provided_dbh) = @_;
3254
3255   my $dbh;
3256   if ($provided_dbh) {
3257     $dbh = $provided_dbh;
3258   } else {
3259     $dbh = $self->dbconnect_noauto($myconfig);
3260   }
3261   my $query =
3262     qq|SELECT customernumberinit FROM business
3263        WHERE id = ? FOR UPDATE|;
3264   my ($var) = selectrow_query($self, $dbh, $query, $business_id);
3265
3266   return undef unless $var;
3267
3268   if ($var =~ m/\d+$/) {
3269     my $new_var  = (substr $var, $-[0]) * 1 + 1;
3270     my $len_diff = length($var) - $-[0] - length($new_var);
3271     $var         = substr($var, 0, $-[0]) . ($len_diff > 0 ? '0' x $len_diff : '') . $new_var;
3272
3273   } else {
3274     $var = $var . '1';
3275   }
3276
3277   $query = qq|UPDATE business
3278               SET customernumberinit = ?
3279               WHERE id = ?|;
3280   do_query($self, $dbh, $query, $var, $business_id);
3281
3282   if (!$provided_dbh) {
3283     $dbh->commit;
3284     $dbh->disconnect;
3285   }
3286
3287   $main::lxdebug->leave_sub();
3288
3289   return $var;
3290 }
3291
3292 sub get_partsgroup {
3293   $main::lxdebug->enter_sub();
3294
3295   my ($self, $myconfig, $p) = @_;
3296   my $target = $p->{target} || 'all_partsgroup';
3297
3298   my $dbh = $self->get_standard_dbh($myconfig);
3299
3300   my $query = qq|SELECT DISTINCT pg.id, pg.partsgroup
3301                  FROM partsgroup pg
3302                  JOIN parts p ON (p.partsgroup_id = pg.id) |;
3303   my @values;
3304
3305   if ($p->{searchitems} eq 'part') {
3306     $query .= qq|WHERE p.inventory_accno_id > 0|;
3307   }
3308   if ($p->{searchitems} eq 'service') {
3309     $query .= qq|WHERE p.inventory_accno_id IS NULL|;
3310   }
3311   if ($p->{searchitems} eq 'assembly') {
3312     $query .= qq|WHERE p.assembly = '1'|;
3313   }
3314   if ($p->{searchitems} eq 'labor') {
3315     $query .= qq|WHERE (p.inventory_accno_id > 0) AND (p.income_accno_id IS NULL)|;
3316   }
3317
3318   $query .= qq|ORDER BY partsgroup|;
3319
3320   if ($p->{all}) {
3321     $query = qq|SELECT id, partsgroup FROM partsgroup
3322                 ORDER BY partsgroup|;
3323   }
3324
3325   if ($p->{language_code}) {
3326     $query = qq|SELECT DISTINCT pg.id, pg.partsgroup,
3327                   t.description AS translation
3328                 FROM partsgroup pg
3329                 JOIN parts p ON (p.partsgroup_id = pg.id)
3330                 LEFT JOIN translation t ON ((t.trans_id = pg.id) AND (t.language_code = ?))
3331                 ORDER BY translation|;
3332     @values = ($p->{language_code});
3333   }
3334
3335   $self->{$target} = selectall_hashref_query($self, $dbh, $query, @values);
3336
3337   $main::lxdebug->leave_sub();
3338 }
3339
3340 sub get_pricegroup {
3341   $main::lxdebug->enter_sub();
3342
3343   my ($self, $myconfig, $p) = @_;
3344
3345   my $dbh = $self->get_standard_dbh($myconfig);
3346
3347   my $query = qq|SELECT p.id, p.pricegroup
3348                  FROM pricegroup p|;
3349
3350   $query .= qq| ORDER BY pricegroup|;
3351
3352   if ($p->{all}) {
3353     $query = qq|SELECT id, pricegroup FROM pricegroup
3354                 ORDER BY pricegroup|;
3355   }
3356
3357   $self->{all_pricegroup} = selectall_hashref_query($self, $dbh, $query);
3358
3359   $main::lxdebug->leave_sub();
3360 }
3361
3362 sub all_years {
3363 # usage $form->all_years($myconfig, [$dbh])
3364 # return list of all years where bookings found
3365 # (@all_years)
3366
3367   $main::lxdebug->enter_sub();
3368
3369   my ($self, $myconfig, $dbh) = @_;
3370
3371   $dbh ||= $self->get_standard_dbh($myconfig);
3372
3373   # get years
3374   my $query = qq|SELECT (SELECT MIN(transdate) FROM acc_trans),
3375                    (SELECT MAX(transdate) FROM acc_trans)|;
3376   my ($startdate, $enddate) = selectrow_query($self, $dbh, $query);
3377
3378   if ($myconfig->{dateformat} =~ /^yy/) {
3379     ($startdate) = split /\W/, $startdate;
3380     ($enddate) = split /\W/, $enddate;
3381   } else {
3382     (@_) = split /\W/, $startdate;
3383     $startdate = $_[2];
3384     (@_) = split /\W/, $enddate;
3385     $enddate = $_[2];
3386   }
3387
3388   my @all_years;
3389   $startdate = substr($startdate,0,4);
3390   $enddate = substr($enddate,0,4);
3391
3392   while ($enddate >= $startdate) {
3393     push @all_years, $enddate--;
3394   }
3395
3396   return @all_years;
3397
3398   $main::lxdebug->leave_sub();
3399 }
3400
3401 sub backup_vars {
3402   $main::lxdebug->enter_sub();
3403   my $self = shift;
3404   my @vars = @_;
3405
3406   map { $self->{_VAR_BACKUP}->{$_} = $self->{$_} if exists $self->{$_} } @vars;
3407
3408   $main::lxdebug->leave_sub();
3409 }
3410
3411 sub restore_vars {
3412   $main::lxdebug->enter_sub();
3413
3414   my $self = shift;
3415   my @vars = @_;
3416
3417   map { $self->{$_} = $self->{_VAR_BACKUP}->{$_} if exists $self->{_VAR_BACKUP}->{$_} } @vars;
3418
3419   $main::lxdebug->leave_sub();
3420 }
3421
3422 sub prepare_for_printing {
3423   my ($self) = @_;
3424
3425   $self->{templates} ||= $::myconfig{templates};
3426   $self->{formname}  ||= $self->{type};
3427   $self->{media}     ||= 'email';
3428
3429   die "'media' other than 'email', 'file', 'printer' is not supported yet" unless $self->{media} =~ m/^(?:email|file|printer)$/;
3430
3431   # set shipto from billto unless set
3432   my $has_shipto = any { $self->{"shipto$_"} } qw(name street zipcode city country contact);
3433   if (!$has_shipto && ($self->{type} =~ m/^(?:purchase_order|request_quotation)$/)) {
3434     $self->{shiptoname}   = $::myconfig{company};
3435     $self->{shiptostreet} = $::myconfig{address};
3436   }
3437
3438   my $language = $self->{language} ? '_' . $self->{language} : '';
3439
3440   my ($language_tc, $output_numberformat, $output_dateformat, $output_longdates);
3441   if ($self->{language_id}) {
3442     ($language_tc, $output_numberformat, $output_dateformat, $output_longdates) = AM->get_language_details(\%::myconfig, $self, $self->{language_id});
3443   } else {
3444     $output_dateformat   = $::myconfig{dateformat};
3445     $output_numberformat = $::myconfig{numberformat};
3446     $output_longdates    = 1;
3447   }
3448
3449   # Retrieve accounts for tax calculation.
3450   IC->retrieve_accounts(\%::myconfig, $self, map { $_ => $self->{"id_$_"} } 1 .. $self->{rowcount});
3451
3452   if ($self->{type} =~ /_delivery_order$/) {
3453     DO->order_details();
3454   } elsif ($self->{type} =~ /sales_order|sales_quotation|request_quotation|purchase_order/) {
3455     OE->order_details(\%::myconfig, $self);
3456   } else {
3457     IS->invoice_details(\%::myconfig, $self, $::locale);
3458   }
3459
3460   # Chose extension & set source file name
3461   my $extension = 'html';
3462   if ($self->{format} eq 'postscript') {
3463     $self->{postscript}   = 1;
3464     $extension            = 'tex';
3465   } elsif ($self->{"format"} =~ /pdf/) {
3466     $self->{pdf}          = 1;
3467     $extension            = $self->{'format'} =~ m/opendocument/i ? 'odt' : 'tex';
3468   } elsif ($self->{"format"} =~ /opendocument/) {
3469     $self->{opendocument} = 1;
3470     $extension            = 'odt';
3471   } elsif ($self->{"format"} =~ /excel/) {
3472     $self->{excel}        = 1;
3473     $extension            = 'xls';
3474   }
3475
3476   my $printer_code    = $self->{printer_code} ? '_' . $self->{printer_code} : '';
3477   my $email_extension = -f "$::myconfig{templates}/$self->{formname}_email${language}.${extension}" ? '_email' : '';
3478   $self->{IN}         = "$self->{formname}${email_extension}${language}${printer_code}.${extension}";
3479
3480   # Format dates.
3481   $self->format_dates($output_dateformat, $output_longdates,
3482                       qw(invdate orddate quodate pldate duedate reqdate transdate shippingdate deliverydate validitydate paymentdate datepaid
3483                          transdate_oe deliverydate_oe employee_startdate employee_enddate),
3484                       grep({ /^(?:datepaid|transdate_oe|reqdate|deliverydate|deliverydate_oe|transdate)_\d+$/ } keys(%{$self})));
3485
3486   $self->reformat_numbers($output_numberformat, 2,
3487                           qw(invtotal ordtotal quototal subtotal linetotal listprice sellprice netprice discount tax taxbase total paid),
3488                           grep({ /^(?:linetotal|listprice|sellprice|netprice|taxbase|discount|paid|subtotal|total|tax)_\d+$/ } keys(%{$self})));
3489
3490   $self->reformat_numbers($output_numberformat, undef, qw(qty price_factor), grep({ /^qty_\d+$/} keys(%{$self})));
3491
3492   my ($cvar_date_fields, $cvar_number_fields) = CVar->get_field_format_list('module' => 'CT', 'prefix' => 'vc_');
3493
3494   if (scalar @{ $cvar_date_fields }) {
3495     $self->format_dates($output_dateformat, $output_longdates, @{ $cvar_date_fields });
3496   }
3497
3498   while (my ($precision, $field_list) = each %{ $cvar_number_fields }) {
3499     $self->reformat_numbers($output_numberformat, $precision, @{ $field_list });
3500   }
3501
3502   return $self;
3503 }
3504
3505 sub format_dates {
3506   my ($self, $dateformat, $longformat, @indices) = @_;
3507
3508   $dateformat ||= $::myconfig{dateformat};
3509
3510   foreach my $idx (@indices) {
3511     if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) {
3512       for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) {
3513         $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $::locale->reformat_date(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i], $dateformat, $longformat);
3514       }
3515     }
3516
3517     next unless defined $self->{$idx};
3518
3519     if (!ref($self->{$idx})) {
3520       $self->{$idx} = $::locale->reformat_date(\%::myconfig, $self->{$idx}, $dateformat, $longformat);
3521
3522     } elsif (ref($self->{$idx}) eq "ARRAY") {
3523       for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) {
3524         $self->{$idx}->[$i] = $::locale->reformat_date(\%::myconfig, $self->{$idx}->[$i], $dateformat, $longformat);
3525       }
3526     }
3527   }
3528 }
3529
3530 sub reformat_numbers {
3531   my ($self, $numberformat, $places, @indices) = @_;
3532
3533   return if !$numberformat || ($numberformat eq $::myconfig{numberformat});
3534
3535   foreach my $idx (@indices) {
3536     if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) {
3537       for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) {
3538         $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $self->parse_amount(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i]);
3539       }
3540     }
3541
3542     next unless defined $self->{$idx};
3543
3544     if (!ref($self->{$idx})) {
3545       $self->{$idx} = $self->parse_amount(\%::myconfig, $self->{$idx});
3546
3547     } elsif (ref($self->{$idx}) eq "ARRAY") {
3548       for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) {
3549         $self->{$idx}->[$i] = $self->parse_amount(\%::myconfig, $self->{$idx}->[$i]);
3550       }
3551     }
3552   }
3553
3554   my $saved_numberformat    = $::myconfig{numberformat};
3555   $::myconfig{numberformat} = $numberformat;
3556
3557   foreach my $idx (@indices) {
3558     if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) {
3559       for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) {
3560         $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $self->format_amount(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i], $places);
3561       }
3562     }
3563
3564     next unless defined $self->{$idx};
3565
3566     if (!ref($self->{$idx})) {
3567       $self->{$idx} = $self->format_amount(\%::myconfig, $self->{$idx}, $places);
3568
3569     } elsif (ref($self->{$idx}) eq "ARRAY") {
3570       for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) {
3571         $self->{$idx}->[$i] = $self->format_amount(\%::myconfig, $self->{$idx}->[$i], $places);
3572       }
3573     }
3574   }
3575
3576   $::myconfig{numberformat} = $saved_numberformat;
3577 }
3578
3579 sub layout {
3580   my ($self) = @_;
3581   $::lxdebug->enter_sub;
3582
3583   my %style_to_script_map = (
3584     v3  => 'v3',
3585     neu => 'new',
3586   );
3587
3588   my $menu_script = $style_to_script_map{$::myconfig{menustyle}} || '';
3589
3590   package main;
3591   require "bin/mozilla/menu$menu_script.pl";
3592   package Form;
3593   require SL::Controller::FrameHeader;
3594
3595
3596   my $layout = SL::Controller::FrameHeader->new->action_header . ::render();
3597
3598   $::lxdebug->leave_sub;
3599   return $layout;
3600 }
3601
3602 1;
3603
3604 __END__
3605
3606 =head1 NAME
3607
3608 SL::Form.pm - main data object.
3609
3610 =head1 SYNOPSIS
3611
3612 This is the main data object of kivitendo.
3613 Unfortunately it also acts as a god object for certain data retrieval procedures used in the entry points.
3614 Points of interest for a beginner are:
3615
3616  - $form->error            - renders a generic error in html. accepts an error message
3617  - $form->get_standard_dbh - returns a database connection for the
3618
3619 =head1 SPECIAL FUNCTIONS
3620
3621 =head2 C<update_business> PARAMS
3622
3623 PARAMS (not named):
3624  \%config,     - config hashref
3625  $business_id, - business id
3626  $dbh          - optional database handle
3627
3628 handles business (thats customer/vendor types) sequences.
3629
3630 special behaviour for empty strings in customerinitnumber field:
3631 will in this case not increase the value, and return undef.
3632
3633 =head2 C<redirect_header> $url
3634
3635 Generates a HTTP redirection header for the new C<$url>. Constructs an
3636 absolute URL including scheme, host name and port. If C<$url> is a
3637 relative URL then it is considered relative to kivitendo base URL.
3638
3639 This function C<die>s if headers have already been created with
3640 C<$::form-E<gt>header>.
3641
3642 Examples:
3643
3644   print $::form->redirect_header('oe.pl?action=edit&id=1234');
3645   print $::form->redirect_header('http://www.lx-office.org/');
3646
3647 =head2 C<header>
3648
3649 Generates a general purpose http/html header and includes most of the scripts
3650 and stylesheets needed. Stylesheets can be added with L<use_stylesheet>.
3651
3652 Only one header will be generated. If the method was already called in this
3653 request it will not output anything and return undef. Also if no
3654 HTTP_USER_AGENT is found, no header is generated.
3655
3656 Although header does not accept parameters itself, it will honor special
3657 hashkeys of its Form instance:
3658
3659 =over 4
3660
3661 =item refresh_time
3662
3663 =item refresh_url
3664
3665 If one of these is set, a http-equiv refresh is generated. Missing parameters
3666 default to 3 seconds and the refering url.
3667
3668 =item stylesheet
3669
3670 Either a scalar or an array ref. Will be inlined into the header. Add
3671 stylesheets with the L<use_stylesheet> function.
3672
3673 =item landscape
3674
3675 If true, a css snippet will be generated that sets the page in landscape mode.
3676
3677 =item favicon
3678
3679 Used to override the default favicon.
3680
3681 =item title
3682
3683 A html page title will be generated from this
3684
3685 =back
3686
3687 =cut