591e02df3e61108a6debb8da5bde601b298d4a5e
[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::Locale;
60 use SL::Mailer;
61 use SL::Menu;
62 use SL::MoreCommon qw(uri_encode uri_decode);
63 use SL::OE;
64 use SL::Request;
65 use SL::Template;
66 use SL::User;
67 use SL::X;
68 use Template;
69 use URI;
70 use List::Util qw(first max min sum);
71 use List::MoreUtils qw(all any apply);
72
73 use strict;
74
75 my $standard_dbh;
76
77 END {
78   disconnect_standard_dbh();
79 }
80
81 sub disconnect_standard_dbh {
82   return unless $standard_dbh;
83   $standard_dbh->disconnect();
84   undef $standard_dbh;
85 }
86
87 sub new {
88   $main::lxdebug->enter_sub();
89
90   my $type = shift;
91
92   my $self = {};
93
94   no warnings 'once';
95   if ($LXDebug::watch_form) {
96     require SL::Watchdog;
97     tie %{ $self }, 'SL::Watchdog';
98   }
99
100   bless $self, $type;
101
102   open VERSION_FILE, "VERSION";                 # New but flexible code reads version from VERSION-file
103   $self->{version} =  <VERSION_FILE>;
104   close VERSION_FILE;
105   $self->{version}  =~ s/[^0-9A-Za-z\.\_\-]//g; # only allow numbers, letters, points, underscores and dashes. Prevents injecting of malicious code.
106
107   $main::lxdebug->leave_sub();
108
109   return $self;
110 }
111
112 sub read_cgi_input {
113   my ($self) = @_;
114   SL::Request::read_cgi_input($self);
115 }
116
117 sub _flatten_variables_rec {
118   $main::lxdebug->enter_sub(2);
119
120   my $self   = shift;
121   my $curr   = shift;
122   my $prefix = shift;
123   my $key    = shift;
124
125   my @result;
126
127   if ('' eq ref $curr->{$key}) {
128     @result = ({ 'key' => $prefix . $key, 'value' => $curr->{$key} });
129
130   } elsif ('HASH' eq ref $curr->{$key}) {
131     foreach my $hash_key (sort keys %{ $curr->{$key} }) {
132       push @result, $self->_flatten_variables_rec($curr->{$key}, $prefix . $key . '.', $hash_key);
133     }
134
135   } else {
136     foreach my $idx (0 .. scalar @{ $curr->{$key} } - 1) {
137       my $first_array_entry = 1;
138
139       foreach my $hash_key (sort keys %{ $curr->{$key}->[$idx] }) {
140         push @result, $self->_flatten_variables_rec($curr->{$key}->[$idx], $prefix . $key . ($first_array_entry ? '[+].' : '[].'), $hash_key);
141         $first_array_entry = 0;
142       }
143     }
144   }
145
146   $main::lxdebug->leave_sub(2);
147
148   return @result;
149 }
150
151 sub flatten_variables {
152   $main::lxdebug->enter_sub(2);
153
154   my $self = shift;
155   my @keys = @_;
156
157   my @variables;
158
159   foreach (@keys) {
160     push @variables, $self->_flatten_variables_rec($self, '', $_);
161   }
162
163   $main::lxdebug->leave_sub(2);
164
165   return @variables;
166 }
167
168 sub flatten_standard_variables {
169   $main::lxdebug->enter_sub(2);
170
171   my $self      = shift;
172   my %skip_keys = map { $_ => 1 } (qw(login password header stylesheet titlebar version), @_);
173
174   my @variables;
175
176   foreach (grep { ! $skip_keys{$_} } keys %{ $self }) {
177     push @variables, $self->_flatten_variables_rec($self, '', $_);
178   }
179
180   $main::lxdebug->leave_sub(2);
181
182   return @variables;
183 }
184
185 sub debug {
186   $main::lxdebug->enter_sub();
187
188   my ($self) = @_;
189
190   print "\n";
191
192   map { print "$_ = $self->{$_}\n" } (sort keys %{$self});
193
194   $main::lxdebug->leave_sub();
195 }
196
197 sub dumper {
198   $main::lxdebug->enter_sub(2);
199
200   my $self          = shift;
201   my $password      = $self->{password};
202
203   $self->{password} = 'X' x 8;
204
205   local $Data::Dumper::Sortkeys = 1;
206   my $output                    = Dumper($self);
207
208   $self->{password} = $password;
209
210   $main::lxdebug->leave_sub(2);
211
212   return $output;
213 }
214
215 sub escape {
216   my ($self, $str) = @_;
217
218   return uri_encode($str);
219 }
220
221 sub unescape {
222   my ($self, $str) = @_;
223
224   return uri_decode($str);
225 }
226
227 sub quote {
228   $main::lxdebug->enter_sub();
229   my ($self, $str) = @_;
230
231   if ($str && !ref($str)) {
232     $str =~ s/\"/&quot;/g;
233   }
234
235   $main::lxdebug->leave_sub();
236
237   return $str;
238 }
239
240 sub unquote {
241   $main::lxdebug->enter_sub();
242   my ($self, $str) = @_;
243
244   if ($str && !ref($str)) {
245     $str =~ s/&quot;/\"/g;
246   }
247
248   $main::lxdebug->leave_sub();
249
250   return $str;
251 }
252
253 sub hide_form {
254   $main::lxdebug->enter_sub();
255   my $self = shift;
256
257   if (@_) {
258     map({ print($::request->{cgi}->hidden("-name" => $_, "-default" => $self->{$_}) . "\n"); } @_);
259   } else {
260     for (sort keys %$self) {
261       next if (($_ eq "header") || (ref($self->{$_}) ne ""));
262       print($::request->{cgi}->hidden("-name" => $_, "-default" => $self->{$_}) . "\n");
263     }
264   }
265   $main::lxdebug->leave_sub();
266 }
267
268 sub throw_on_error {
269   my ($self, $code) = @_;
270   local $self->{__ERROR_HANDLER} = sub { die SL::X::FormError->new($_[0]) };
271   $code->();
272 }
273
274 sub error {
275   $main::lxdebug->enter_sub();
276
277   $main::lxdebug->show_backtrace();
278
279   my ($self, $msg) = @_;
280
281   if ($self->{__ERROR_HANDLER}) {
282     $self->{__ERROR_HANDLER}->($msg);
283
284   } elsif ($ENV{HTTP_USER_AGENT}) {
285     $msg =~ s/\n/<br>/g;
286     $self->show_generic_error($msg);
287
288   } else {
289     print STDERR "Error: $msg\n";
290     ::end_of_request();
291   }
292
293   $main::lxdebug->leave_sub();
294 }
295
296 sub info {
297   $main::lxdebug->enter_sub();
298
299   my ($self, $msg) = @_;
300
301   if ($ENV{HTTP_USER_AGENT}) {
302     $msg =~ s/\n/<br>/g;
303
304     if (!$self->{header}) {
305       $self->header;
306       print qq|<body>|;
307     }
308
309     print qq|
310     <p class="message_ok"><b>$msg</b></p>
311
312     <script type="text/javascript">
313     <!--
314     // If JavaScript is enabled, the whole thing will be reloaded.
315     // The reason is: When one changes his menu setup (HTML / CSS ...)
316     // it now loads the correct code into the browser instead of do nothing.
317     setTimeout("top.frames.location.href='login.pl'",500);
318     //-->
319     </script>
320
321 </body>
322     |;
323
324   } else {
325
326     if ($self->{info_function}) {
327       &{ $self->{info_function} }($msg);
328     } else {
329       print "$msg\n";
330     }
331   }
332
333   $main::lxdebug->leave_sub();
334 }
335
336 # calculates the number of rows in a textarea based on the content and column number
337 # can be capped with maxrows
338 sub numtextrows {
339   $main::lxdebug->enter_sub();
340   my ($self, $str, $cols, $maxrows, $minrows) = @_;
341
342   $minrows ||= 1;
343
344   my $rows   = sum map { int((length() - 2) / $cols) + 1 } split /\r/, $str;
345   $maxrows ||= $rows;
346
347   $main::lxdebug->leave_sub();
348
349   return max(min($rows, $maxrows), $minrows);
350 }
351
352 sub dberror {
353   $main::lxdebug->enter_sub();
354
355   my ($self, $msg) = @_;
356
357   $self->error("$msg\n" . $DBI::errstr);
358
359   $main::lxdebug->leave_sub();
360 }
361
362 sub isblank {
363   $main::lxdebug->enter_sub();
364
365   my ($self, $name, $msg) = @_;
366
367   my $curr = $self;
368   foreach my $part (split m/\./, $name) {
369     if (!$curr->{$part} || ($curr->{$part} =~ /^\s*$/)) {
370       $self->error($msg);
371     }
372     $curr = $curr->{$part};
373   }
374
375   $main::lxdebug->leave_sub();
376 }
377
378 sub _get_request_uri {
379   my $self = shift;
380
381   return URI->new($ENV{HTTP_REFERER})->canonical() if $ENV{HTTP_X_FORWARDED_FOR};
382
383   my $scheme =  $ENV{HTTPS} && (lc $ENV{HTTPS} eq 'on') ? 'https' : 'http';
384   my $port   =  $ENV{SERVER_PORT} || '';
385   $port      =  undef if (($scheme eq 'http' ) && ($port == 80))
386                       || (($scheme eq 'https') && ($port == 443));
387
388   my $uri    =  URI->new("${scheme}://");
389   $uri->scheme($scheme);
390   $uri->port($port);
391   $uri->host($ENV{HTTP_HOST} || $ENV{SERVER_ADDR});
392   $uri->path_query($ENV{REQUEST_URI});
393   $uri->query('');
394
395   return $uri;
396 }
397
398 sub _add_to_request_uri {
399   my $self              = shift;
400
401   my $relative_new_path = shift;
402   my $request_uri       = shift || $self->_get_request_uri;
403   my $relative_new_uri  = URI->new($relative_new_path);
404   my @request_segments  = $request_uri->path_segments;
405
406   my $new_uri           = $request_uri->clone;
407   $new_uri->path_segments(@request_segments[0..scalar(@request_segments) - 2], $relative_new_uri->path_segments);
408
409   return $new_uri;
410 }
411
412 sub create_http_response {
413   $main::lxdebug->enter_sub();
414
415   my $self     = shift;
416   my %params   = @_;
417
418   my $cgi      = $::request->{cgi};
419
420   my $session_cookie;
421   if (defined $main::auth) {
422     my $uri      = $self->_get_request_uri;
423     my @segments = $uri->path_segments;
424     pop @segments;
425     $uri->path_segments(@segments);
426
427     my $session_cookie_value = $main::auth->get_session_id();
428
429     if ($session_cookie_value) {
430       $session_cookie = $cgi->cookie('-name'   => $main::auth->get_session_cookie_name(),
431                                      '-value'  => $session_cookie_value,
432                                      '-path'   => $uri->path,
433                                      '-secure' => $ENV{HTTPS});
434     }
435   }
436
437   my %cgi_params = ('-type' => $params{content_type});
438   $cgi_params{'-charset'} = $params{charset} if ($params{charset});
439   $cgi_params{'-cookie'}  = $session_cookie  if ($session_cookie);
440
441   map { $cgi_params{'-' . $_} = $params{$_} if exists $params{$_} } qw(content_disposition content_length);
442
443   my $output = $cgi->header(%cgi_params);
444
445   $main::lxdebug->leave_sub();
446
447   return $output;
448 }
449
450 sub header {
451   $::lxdebug->enter_sub;
452
453   my ($self, %params) = @_;
454   my $db_charset = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
455   my @header;
456
457   $::lxdebug->leave_sub and return if !$ENV{HTTP_USER_AGENT} || $self->{header}++;
458
459   if ($params{no_layout}) {
460     $::request->{layout} = SL::Controller::Layout->new(style => 'none');
461   }
462
463   my $layout = $::request->{layout};
464
465   # standard css for all
466   $layout->use_stylesheet("$_.css") for qw(
467     main menu tabcontent list_accounts jquery.autocomplete
468     jquery.multiselect2side frame_header/header
469     ui-lightness/jquery-ui-1.8.12.custom
470     js/jscalendar/calendar-win2k-1
471   );
472
473   $layout->use_javascript("$_.js") for qw(
474     jquery common jscalendar/calendar jscalendar/lang/calendar-de
475     jscalendar/calendar-setup part_selection jquery-ui jquery.cookie jqModal
476     switchmenuframe
477   );
478
479   $self->{favicon} ||= "favicon.ico";
480   $self->{titlebar} = join ' - ', grep $_, $self->{title}, $self->{login}, $::myconfig{dbname}, $self->{version} if $self->{title};
481
482   # build includes
483   if ($self->{refresh_url} || $self->{refresh_time}) {
484     my $refresh_time = $self->{refresh_time} || 3;
485     my $refresh_url  = $self->{refresh_url}  || $ENV{REFERER};
486     push @header, "<meta http-equiv='refresh' content='$refresh_time;$refresh_url'>";
487   }
488
489   push @header, map { qq|<link rel="stylesheet" href="$_" type="text/css" title="Stylesheet">| } $layout->stylesheets;
490   push @header, "<style type='text/css'>\@page { size:landscape; }</style> "                     if $self->{landscape};
491   push @header, "<link rel='shortcut icon' href='$self->{favicon}' type='image/x-icon'>"         if -f $self->{favicon};
492   push @header, map { qq|<script type="text/javascript" src="$_"></script>| }                    $layout->javascripts;
493   push @header, $self->{javascript} if $self->{javascript};
494   push @header, map { $_->show_javascript } @{ $self->{AJAX} || [] };
495
496   my  %doctypes = (
497     strict       => qq|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">|,
498     transitional => qq|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">|,
499     frameset     => qq|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">|,
500     html5        => qq|<!DOCTYPE html>|,
501   );
502
503   # output
504   print $self->create_http_response(content_type => 'text/html', charset => $db_charset);
505   print $doctypes{$params{doctype} || 'transitional'}, $/;
506   print <<EOT;
507 <html>
508  <head>
509   <meta http-equiv="Content-Type" content="text/html; charset=$db_charset">
510   <title>$self->{titlebar}</title>
511 EOT
512   print "  $_\n" for @header;
513   print <<EOT;
514   <meta name="robots" content="noindex,nofollow">
515   <script type="text/javascript" src="js/tabcontent.js">
516
517   /***********************************************
518    * Tab Content script v2.2- Â© Dynamic Drive DHTML code library (www.dynamicdrive.com)
519    * This notice MUST stay intact for legal use
520    * Visit Dynamic Drive at http://www.dynamicdrive.com/ for full source code
521    ***********************************************/
522
523   </script>
524  </head>
525  <body>
526
527 EOT
528   print $::request->{layout}->pre_content;
529   print $::request->{layout}->start_content;
530
531   $layout->header_done;
532
533   $::lxdebug->leave_sub;
534 }
535
536 sub footer {
537   return unless $::request->{layout}->need_footer;
538
539   print $::request->{layout}->end_content;
540   print $::request->{layout}->post_content;
541
542   if (my @inline_scripts = $::request->{layout}->javascripts_inline) {
543     print "<script type='text/javascript'>@inline_scripts</script>\n";
544   }
545
546   print <<EOL
547  </body>
548 </html>
549 EOL
550 }
551
552 sub ajax_response_header {
553   $main::lxdebug->enter_sub();
554
555   my ($self) = @_;
556
557   my $db_charset = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
558   my $output     = $::request->{cgi}->header('-charset' => $db_charset);
559
560   $main::lxdebug->leave_sub();
561
562   return $output;
563 }
564
565 sub redirect_header {
566   my $self     = shift;
567   my $new_url  = shift;
568
569   my $base_uri = $self->_get_request_uri;
570   my $new_uri  = URI->new_abs($new_url, $base_uri);
571
572   die "Headers already sent" if $self->{header};
573   $self->{header} = 1;
574
575   return $::request->{cgi}->redirect($new_uri);
576 }
577
578 sub set_standard_title {
579   $::lxdebug->enter_sub;
580   my $self = shift;
581
582   $self->{titlebar}  = "kivitendo " . $::locale->text('Version') . " $self->{version}";
583   $self->{titlebar} .= "- $::myconfig{name}"   if $::myconfig{name};
584   $self->{titlebar} .= "- $::myconfig{dbname}" if $::myconfig{name};
585
586   $::lxdebug->leave_sub;
587 }
588
589 sub _prepare_html_template {
590   $main::lxdebug->enter_sub();
591
592   my ($self, $file, $additional_params) = @_;
593   my $language;
594
595   if (!%::myconfig || !$::myconfig{"countrycode"}) {
596     $language = $::lx_office_conf{system}->{language};
597   } else {
598     $language = $main::myconfig{"countrycode"};
599   }
600   $language = "de" unless ($language);
601
602   if (-f "templates/webpages/${file}.html") {
603     $file = "templates/webpages/${file}.html";
604
605   } else {
606     my $info = "Web page template '${file}' not found.\n";
607     print qq|<pre>$info</pre>|;
608     ::end_of_request();
609   }
610
611   if ($self->{"DEBUG"}) {
612     $additional_params->{"DEBUG"} = $self->{"DEBUG"};
613   }
614
615   if ($additional_params->{"DEBUG"}) {
616     $additional_params->{"DEBUG"} =
617       "<br><em>DEBUG INFORMATION:</em><pre>" . $additional_params->{"DEBUG"} . "</pre>";
618   }
619
620   if (%main::myconfig) {
621     $::myconfig{jsc_dateformat} = apply {
622       s/d+/\%d/gi;
623       s/m+/\%m/gi;
624       s/y+/\%Y/gi;
625     } $::myconfig{"dateformat"};
626     $additional_params->{"myconfig"} ||= \%::myconfig;
627     map { $additional_params->{"myconfig_${_}"} = $main::myconfig{$_}; } keys %::myconfig;
628   }
629
630   $additional_params->{"conf_dbcharset"}              = $::lx_office_conf{system}->{dbcharset};
631   $additional_params->{"conf_webdav"}                 = $::lx_office_conf{features}->{webdav};
632   $additional_params->{"conf_latex_templates"}        = $::lx_office_conf{print_templates}->{latex};
633   $additional_params->{"conf_opendocument_templates"} = $::lx_office_conf{print_templates}->{opendocument};
634   $additional_params->{"conf_vertreter"}              = $::lx_office_conf{features}->{vertreter};
635   $additional_params->{"conf_show_best_before"}       = $::lx_office_conf{features}->{show_best_before};
636   $additional_params->{"conf_parts_image_css"}        = $::lx_office_conf{features}->{parts_image_css};
637   $additional_params->{"conf_parts_listing_images"}   = $::lx_office_conf{features}->{parts_listing_images};
638   $additional_params->{"conf_parts_show_image"}       = $::lx_office_conf{features}->{parts_show_image};
639   $additional_params->{"conf_payments_changeable"}    = $::lx_office_conf{features}->{payments_changeable};
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
1099   if ($template->uses_temp_file() || $self->{media} eq 'email') {
1100     $out              = $self->{OUT};
1101     $out_mode         = $self->{OUT_MODE} || '>';
1102     $self->{OUT}      = "$self->{tmpfile}";
1103     $self->{OUT_MODE} = '>';
1104   }
1105
1106   my $result;
1107   my $command_formatter = sub {
1108     my ($out_mode, $out) = @_;
1109     return $out_mode eq '|-' ? SL::Template::create(type => 'ShellCommand', form => $self)->parse($out) : $out;
1110   };
1111
1112   if ($self->{OUT}) {
1113     $self->{OUT} = $command_formatter->($self->{OUT_MODE}, $self->{OUT});
1114     open(OUT, $self->{OUT_MODE}, $self->{OUT}) or $self->error("error on opening $self->{OUT} with mode $self->{OUT_MODE} : $!");
1115   } else {
1116     *OUT = ($::dispatcher->get_standard_filehandles)[1];
1117     $self->header;
1118   }
1119
1120   if (!$template->parse(*OUT)) {
1121     $self->cleanup();
1122     $self->error("$self->{IN} : " . $template->get_error());
1123   }
1124
1125   close OUT if $self->{OUT};
1126
1127   if ($self->{media} eq 'file') {
1128     copy(join('/', $self->{cwd}, $userspath, $self->{tmpfile}), $out =~ m|^/| ? $out : join('/', $self->{cwd}, $out)) if $template->uses_temp_file;
1129     $self->cleanup;
1130     chdir("$self->{cwd}");
1131
1132     $::lxdebug->leave_sub();
1133
1134     return;
1135   }
1136
1137   if ($template->uses_temp_file() || $self->{media} eq 'email') {
1138
1139     if ($self->{media} eq 'email') {
1140
1141       my $mail = new Mailer;
1142
1143       map { $mail->{$_} = $self->{$_} }
1144         qw(cc bcc subject message version format);
1145       $mail->{charset} = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
1146       $mail->{to} = $self->{EMAIL_RECIPIENT} ? $self->{EMAIL_RECIPIENT} : $self->{email};
1147       $mail->{from}   = qq|"$myconfig->{name}" <$myconfig->{email}>|;
1148       $mail->{fileid} = time() . '.' . $$ . '.';
1149       $myconfig->{signature} =~ s/\r//g;
1150
1151       # if we send html or plain text inline
1152       if (($self->{format} eq 'html') && ($self->{sendmode} eq 'inline')) {
1153         $mail->{contenttype}    =  "text/html";
1154         $mail->{message}        =~ s/\r//g;
1155         $mail->{message}        =~ s/\n/<br>\n/g;
1156         $myconfig->{signature}  =~ s/\n/<br>\n/g;
1157         $mail->{message}       .=  "<br>\n-- <br>\n$myconfig->{signature}\n<br>";
1158
1159         open(IN, "<", $self->{tmpfile})
1160           or $self->error($self->cleanup . "$self->{tmpfile} : $!");
1161         $mail->{message} .= $_ while <IN>;
1162         close(IN);
1163
1164       } else {
1165
1166         if (!$self->{"do_not_attach"}) {
1167           my $attachment_name  =  $self->{attachment_filename} || $self->{tmpfile};
1168           $attachment_name     =~ s/\.(.+?)$/.${ext_for_format}/ if ($ext_for_format);
1169           $mail->{attachments} =  [{ "filename" => $self->{tmpfile},
1170                                      "name"     => $attachment_name }];
1171         }
1172
1173         $mail->{message}  =~ s/\r//g;
1174         $mail->{message} .=  "\n-- \n$myconfig->{signature}";
1175
1176       }
1177
1178       my $err = $mail->send();
1179       $self->error($self->cleanup . "$err") if ($err);
1180
1181     } else {
1182
1183       $self->{OUT}      = $out;
1184       $self->{OUT_MODE} = $out_mode;
1185
1186       my $numbytes = (-s $self->{tmpfile});
1187       open(IN, "<", $self->{tmpfile})
1188         or $self->error($self->cleanup . "$self->{tmpfile} : $!");
1189       binmode IN;
1190
1191       $self->{copies} = 1 unless $self->{media} eq 'printer';
1192
1193       chdir("$self->{cwd}");
1194       #print(STDERR "Kopien $self->{copies}\n");
1195       #print(STDERR "OUT $self->{OUT}\n");
1196       for my $i (1 .. $self->{copies}) {
1197         if ($self->{OUT}) {
1198           $self->{OUT} = $command_formatter->($self->{OUT_MODE}, $self->{OUT});
1199
1200           open  OUT, $self->{OUT_MODE}, $self->{OUT} or $self->error($self->cleanup . "$self->{OUT} : $!");
1201           print OUT $_ while <IN>;
1202           close OUT;
1203           seek  IN, 0, 0;
1204
1205         } else {
1206           $self->{attachment_filename} = ($self->{attachment_filename})
1207                                        ? $self->{attachment_filename}
1208                                        : $self->generate_attachment_filename();
1209
1210           # launch application
1211           print qq|Content-Type: | . $template->get_mime_type() . qq|
1212 Content-Disposition: attachment; filename="$self->{attachment_filename}"
1213 Content-Length: $numbytes
1214
1215 |;
1216
1217           $::locale->with_raw_io(\*STDOUT, sub { print while <IN> });
1218         }
1219       }
1220
1221       close(IN);
1222     }
1223
1224   }
1225
1226   $self->cleanup;
1227
1228   chdir("$self->{cwd}");
1229   $main::lxdebug->leave_sub();
1230 }
1231
1232 sub get_formname_translation {
1233   $main::lxdebug->enter_sub();
1234   my ($self, $formname) = @_;
1235
1236   $formname ||= $self->{formname};
1237
1238   $self->{recipient_locale} ||=  Locale->lang_to_locale($self->{language});
1239   local $::locale = Locale->new($self->{recipient_locale});
1240
1241   my %formname_translations = (
1242     bin_list                => $main::locale->text('Bin List'),
1243     credit_note             => $main::locale->text('Credit Note'),
1244     invoice                 => $main::locale->text('Invoice'),
1245     pick_list               => $main::locale->text('Pick List'),
1246     proforma                => $main::locale->text('Proforma Invoice'),
1247     purchase_order          => $main::locale->text('Purchase Order'),
1248     request_quotation       => $main::locale->text('RFQ'),
1249     sales_order             => $main::locale->text('Confirmation'),
1250     sales_quotation         => $main::locale->text('Quotation'),
1251     storno_invoice          => $main::locale->text('Storno Invoice'),
1252     sales_delivery_order    => $main::locale->text('Delivery Order'),
1253     purchase_delivery_order => $main::locale->text('Delivery Order'),
1254     dunning                 => $main::locale->text('Dunning'),
1255   );
1256
1257   $main::lxdebug->leave_sub();
1258   return $formname_translations{$formname};
1259 }
1260
1261 sub get_number_prefix_for_type {
1262   $main::lxdebug->enter_sub();
1263   my ($self) = @_;
1264
1265   my $prefix =
1266       (first { $self->{type} eq $_ } qw(invoice credit_note)) ? 'inv'
1267     : ($self->{type} =~ /_quotation$/)                        ? 'quo'
1268     : ($self->{type} =~ /_delivery_order$/)                   ? 'do'
1269     :                                                           'ord';
1270
1271   $main::lxdebug->leave_sub();
1272   return $prefix;
1273 }
1274
1275 sub get_extension_for_format {
1276   $main::lxdebug->enter_sub();
1277   my ($self)    = @_;
1278
1279   my $extension = $self->{format} =~ /pdf/i          ? ".pdf"
1280                 : $self->{format} =~ /postscript/i   ? ".ps"
1281                 : $self->{format} =~ /opendocument/i ? ".odt"
1282                 : $self->{format} =~ /excel/i        ? ".xls"
1283                 : $self->{format} =~ /html/i         ? ".html"
1284                 :                                      "";
1285
1286   $main::lxdebug->leave_sub();
1287   return $extension;
1288 }
1289
1290 sub generate_attachment_filename {
1291   $main::lxdebug->enter_sub();
1292   my ($self) = @_;
1293
1294   $self->{recipient_locale} ||=  Locale->lang_to_locale($self->{language});
1295   my $recipient_locale = Locale->new($self->{recipient_locale});
1296
1297   my $attachment_filename = $main::locale->unquote_special_chars('HTML', $self->get_formname_translation());
1298   my $prefix              = $self->get_number_prefix_for_type();
1299
1300   if ($self->{preview} && (first { $self->{type} eq $_ } qw(invoice credit_note))) {
1301     $attachment_filename .= ' (' . $recipient_locale->text('Preview') . ')' . $self->get_extension_for_format();
1302
1303   } elsif ($attachment_filename && $self->{"${prefix}number"}) {
1304     $attachment_filename .=  "_" . $self->{"${prefix}number"} . $self->get_extension_for_format();
1305
1306   } else {
1307     $attachment_filename = "";
1308   }
1309
1310   $attachment_filename =  $main::locale->quote_special_chars('filenames', $attachment_filename);
1311   $attachment_filename =~ s|[\s/\\]+|_|g;
1312
1313   $main::lxdebug->leave_sub();
1314   return $attachment_filename;
1315 }
1316
1317 sub generate_email_subject {
1318   $main::lxdebug->enter_sub();
1319   my ($self) = @_;
1320
1321   my $subject = $main::locale->unquote_special_chars('HTML', $self->get_formname_translation());
1322   my $prefix  = $self->get_number_prefix_for_type();
1323
1324   if ($subject && $self->{"${prefix}number"}) {
1325     $subject .= " " . $self->{"${prefix}number"}
1326   }
1327
1328   $main::lxdebug->leave_sub();
1329   return $subject;
1330 }
1331
1332 sub cleanup {
1333   $main::lxdebug->enter_sub();
1334
1335   my ($self, $application) = @_;
1336
1337   my $error_code = $?;
1338
1339   chdir("$self->{tmpdir}");
1340
1341   my @err = ();
1342   if ((-1 == $error_code) || (127 == (($error_code) >> 8))) {
1343     push @err, $::locale->text('The application "#1" was not found on the system.', $application || 'pdflatex') . ' ' . $::locale->text('Please contact your administrator.');
1344
1345   } elsif (-f "$self->{tmpfile}.err") {
1346     open(FH, "$self->{tmpfile}.err");
1347     @err = <FH>;
1348     close(FH);
1349   }
1350
1351   if ($self->{tmpfile} && !($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})) {
1352     $self->{tmpfile} =~ s|.*/||g;
1353     # strip extension
1354     $self->{tmpfile} =~ s/\.\w+$//g;
1355     my $tmpfile = $self->{tmpfile};
1356     unlink(<$tmpfile.*>);
1357   }
1358
1359   chdir("$self->{cwd}");
1360
1361   $main::lxdebug->leave_sub();
1362
1363   return "@err";
1364 }
1365
1366 sub datetonum {
1367   $main::lxdebug->enter_sub();
1368
1369   my ($self, $date, $myconfig) = @_;
1370   my ($yy, $mm, $dd);
1371
1372   if ($date && $date =~ /\D/) {
1373
1374     if ($myconfig->{dateformat} =~ /^yy/) {
1375       ($yy, $mm, $dd) = split /\D/, $date;
1376     }
1377     if ($myconfig->{dateformat} =~ /^mm/) {
1378       ($mm, $dd, $yy) = split /\D/, $date;
1379     }
1380     if ($myconfig->{dateformat} =~ /^dd/) {
1381       ($dd, $mm, $yy) = split /\D/, $date;
1382     }
1383
1384     $dd *= 1;
1385     $mm *= 1;
1386     $yy = ($yy < 70) ? $yy + 2000 : $yy;
1387     $yy = ($yy >= 70 && $yy <= 99) ? $yy + 1900 : $yy;
1388
1389     $dd = "0$dd" if ($dd < 10);
1390     $mm = "0$mm" if ($mm < 10);
1391
1392     $date = "$yy$mm$dd";
1393   }
1394
1395   $main::lxdebug->leave_sub();
1396
1397   return $date;
1398 }
1399
1400 # Database routines used throughout
1401
1402 sub _dbconnect_options {
1403   my $self    = shift;
1404   my $options = { pg_enable_utf8 => $::locale->is_utf8,
1405                   @_ };
1406
1407   return $options;
1408 }
1409
1410 sub dbconnect {
1411   $main::lxdebug->enter_sub(2);
1412
1413   my ($self, $myconfig) = @_;
1414
1415   # connect to database
1416   my $dbh = SL::DBConnect->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, $self->_dbconnect_options)
1417     or $self->dberror;
1418
1419   # set db options
1420   if ($myconfig->{dboptions}) {
1421     $dbh->do($myconfig->{dboptions}) || $self->dberror($myconfig->{dboptions});
1422   }
1423
1424   $main::lxdebug->leave_sub(2);
1425
1426   return $dbh;
1427 }
1428
1429 sub dbconnect_noauto {
1430   $main::lxdebug->enter_sub();
1431
1432   my ($self, $myconfig) = @_;
1433
1434   # connect to database
1435   my $dbh = SL::DBConnect->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, $self->_dbconnect_options(AutoCommit => 0))
1436     or $self->dberror;
1437
1438   # set db options
1439   if ($myconfig->{dboptions}) {
1440     $dbh->do($myconfig->{dboptions}) || $self->dberror($myconfig->{dboptions});
1441   }
1442
1443   $main::lxdebug->leave_sub();
1444
1445   return $dbh;
1446 }
1447
1448 sub get_standard_dbh {
1449   $main::lxdebug->enter_sub(2);
1450
1451   my $self     = shift;
1452   my $myconfig = shift || \%::myconfig;
1453
1454   if ($standard_dbh && !$standard_dbh->{Active}) {
1455     $main::lxdebug->message(LXDebug->INFO(), "get_standard_dbh: \$standard_dbh is defined but not Active anymore");
1456     undef $standard_dbh;
1457   }
1458
1459   $standard_dbh ||= $self->dbconnect_noauto($myconfig);
1460
1461   $main::lxdebug->leave_sub(2);
1462
1463   return $standard_dbh;
1464 }
1465
1466 sub date_closed {
1467   $main::lxdebug->enter_sub();
1468
1469   my ($self, $date, $myconfig) = @_;
1470   my $dbh = $self->dbconnect($myconfig);
1471
1472   my $query = "SELECT 1 FROM defaults WHERE ? < closedto";
1473   my $sth = prepare_execute_query($self, $dbh, $query, conv_date($date));
1474
1475   # Falls $date = '' - Fehlermeldung aus der Datenbank. Ich denke,
1476   # es ist sicher ein conv_date vorher IMMER auszuführen.
1477   # Testfälle ohne definiertes closedto:
1478   #   Leere Datumseingabe i.O.
1479   #     SELECT 1 FROM defaults WHERE '' < closedto
1480   #   normale Zahlungsbuchung Ã¼ber Rechnungsmaske i.O.
1481   #     SELECT 1 FROM defaults WHERE '10.05.2011' < closedto
1482   # Testfälle mit definiertem closedto (30.04.2011):
1483   #  Leere Datumseingabe i.O.
1484   #   SELECT 1 FROM defaults WHERE '' < closedto
1485   # normale Buchung im geschloßenem Zeitraum i.O.
1486   #   SELECT 1 FROM defaults WHERE '21.04.2011' < closedto
1487   #     Fehlermeldung: Es können keine Zahlungen für abgeschlossene Bücher gebucht werden!
1488   # normale Buchung in aktiver Buchungsperiode i.O.
1489   #   SELECT 1 FROM defaults WHERE '01.05.2011' < closedto
1490
1491   my ($closed) = $sth->fetchrow_array;
1492
1493   $main::lxdebug->leave_sub();
1494
1495   return $closed;
1496 }
1497
1498 sub update_balance {
1499   $main::lxdebug->enter_sub();
1500
1501   my ($self, $dbh, $table, $field, $where, $value, @values) = @_;
1502
1503   # if we have a value, go do it
1504   if ($value != 0) {
1505
1506     # retrieve balance from table
1507     my $query = "SELECT $field FROM $table WHERE $where FOR UPDATE";
1508     my $sth = prepare_execute_query($self, $dbh, $query, @values);
1509     my ($balance) = $sth->fetchrow_array;
1510     $sth->finish;
1511
1512     $balance += $value;
1513
1514     # update balance
1515     $query = "UPDATE $table SET $field = $balance WHERE $where";
1516     do_query($self, $dbh, $query, @values);
1517   }
1518   $main::lxdebug->leave_sub();
1519 }
1520
1521 sub update_exchangerate {
1522   $main::lxdebug->enter_sub();
1523
1524   my ($self, $dbh, $curr, $transdate, $buy, $sell) = @_;
1525   my ($query);
1526   # some sanity check for currency
1527   if ($curr eq '') {
1528     $main::lxdebug->leave_sub();
1529     return;
1530   }
1531   $query = qq|SELECT curr FROM defaults|;
1532
1533   my ($currency) = selectrow_query($self, $dbh, $query);
1534   my ($defaultcurrency) = split m/:/, $currency;
1535
1536
1537   if ($curr eq $defaultcurrency) {
1538     $main::lxdebug->leave_sub();
1539     return;
1540   }
1541
1542   $query = qq|SELECT e.curr FROM exchangerate e
1543                  WHERE e.curr = ? AND e.transdate = ?
1544                  FOR UPDATE|;
1545   my $sth = prepare_execute_query($self, $dbh, $query, $curr, $transdate);
1546
1547   if ($buy == 0) {
1548     $buy = "";
1549   }
1550   if ($sell == 0) {
1551     $sell = "";
1552   }
1553
1554   $buy = conv_i($buy, "NULL");
1555   $sell = conv_i($sell, "NULL");
1556
1557   my $set;
1558   if ($buy != 0 && $sell != 0) {
1559     $set = "buy = $buy, sell = $sell";
1560   } elsif ($buy != 0) {
1561     $set = "buy = $buy";
1562   } elsif ($sell != 0) {
1563     $set = "sell = $sell";
1564   }
1565
1566   if ($sth->fetchrow_array) {
1567     $query = qq|UPDATE exchangerate
1568                 SET $set
1569                 WHERE curr = ?
1570                 AND transdate = ?|;
1571
1572   } else {
1573     $query = qq|INSERT INTO exchangerate (curr, buy, sell, transdate)
1574                 VALUES (?, $buy, $sell, ?)|;
1575   }
1576   $sth->finish;
1577   do_query($self, $dbh, $query, $curr, $transdate);
1578
1579   $main::lxdebug->leave_sub();
1580 }
1581
1582 sub save_exchangerate {
1583   $main::lxdebug->enter_sub();
1584
1585   my ($self, $myconfig, $currency, $transdate, $rate, $fld) = @_;
1586
1587   my $dbh = $self->dbconnect($myconfig);
1588
1589   my ($buy, $sell);
1590
1591   $buy  = $rate if $fld eq 'buy';
1592   $sell = $rate if $fld eq 'sell';
1593
1594
1595   $self->update_exchangerate($dbh, $currency, $transdate, $buy, $sell);
1596
1597
1598   $dbh->disconnect;
1599
1600   $main::lxdebug->leave_sub();
1601 }
1602
1603 sub get_exchangerate {
1604   $main::lxdebug->enter_sub();
1605
1606   my ($self, $dbh, $curr, $transdate, $fld) = @_;
1607   my ($query);
1608
1609   unless ($transdate) {
1610     $main::lxdebug->leave_sub();
1611     return 1;
1612   }
1613
1614   $query = qq|SELECT curr FROM defaults|;
1615
1616   my ($currency) = selectrow_query($self, $dbh, $query);
1617   my ($defaultcurrency) = split m/:/, $currency;
1618
1619   if ($currency eq $defaultcurrency) {
1620     $main::lxdebug->leave_sub();
1621     return 1;
1622   }
1623
1624   $query = qq|SELECT e.$fld FROM exchangerate e
1625                  WHERE e.curr = ? AND e.transdate = ?|;
1626   my ($exchangerate) = selectrow_query($self, $dbh, $query, $curr, $transdate);
1627
1628
1629
1630   $main::lxdebug->leave_sub();
1631
1632   return $exchangerate;
1633 }
1634
1635 sub check_exchangerate {
1636   $main::lxdebug->enter_sub();
1637
1638   my ($self, $myconfig, $currency, $transdate, $fld) = @_;
1639
1640   if ($fld !~/^buy|sell$/) {
1641     $self->error('Fatal: check_exchangerate called with invalid buy/sell argument');
1642   }
1643
1644   unless ($transdate) {
1645     $main::lxdebug->leave_sub();
1646     return "";
1647   }
1648
1649   my ($defaultcurrency) = $self->get_default_currency($myconfig);
1650
1651   if ($currency eq $defaultcurrency) {
1652     $main::lxdebug->leave_sub();
1653     return 1;
1654   }
1655
1656   my $dbh   = $self->get_standard_dbh($myconfig);
1657   my $query = qq|SELECT e.$fld FROM exchangerate e
1658                  WHERE e.curr = ? AND e.transdate = ?|;
1659
1660   my ($exchangerate) = selectrow_query($self, $dbh, $query, $currency, $transdate);
1661
1662   $main::lxdebug->leave_sub();
1663
1664   return $exchangerate;
1665 }
1666
1667 sub get_all_currencies {
1668   $main::lxdebug->enter_sub();
1669
1670   my $self     = shift;
1671   my $myconfig = shift || \%::myconfig;
1672   my $dbh      = $self->get_standard_dbh($myconfig);
1673
1674   my $query = qq|SELECT curr FROM defaults|;
1675
1676   my ($curr)     = selectrow_query($self, $dbh, $query);
1677   my @currencies = grep { $_ } map { s/\s//g; $_ } split m/:/, $curr;
1678
1679   $main::lxdebug->leave_sub();
1680
1681   return @currencies;
1682 }
1683
1684 sub get_default_currency {
1685   $main::lxdebug->enter_sub();
1686
1687   my ($self, $myconfig) = @_;
1688   my @currencies        = $self->get_all_currencies($myconfig);
1689
1690   $main::lxdebug->leave_sub();
1691
1692   return $currencies[0];
1693 }
1694
1695 sub set_payment_options {
1696   $main::lxdebug->enter_sub();
1697
1698   my ($self, $myconfig, $transdate) = @_;
1699
1700   return $main::lxdebug->leave_sub() unless ($self->{payment_id});
1701
1702   my $dbh = $self->get_standard_dbh($myconfig);
1703
1704   my $query =
1705     qq|SELECT p.terms_netto, p.terms_skonto, p.percent_skonto, p.description_long , p.description | .
1706     qq|FROM payment_terms p | .
1707     qq|WHERE p.id = ?|;
1708
1709   ($self->{terms_netto}, $self->{terms_skonto}, $self->{percent_skonto},
1710    $self->{payment_terms}, $self->{payment_description}) =
1711      selectrow_query($self, $dbh, $query, $self->{payment_id});
1712
1713   if ($transdate eq "") {
1714     if ($self->{invdate}) {
1715       $transdate = $self->{invdate};
1716     } else {
1717       $transdate = $self->{transdate};
1718     }
1719   }
1720
1721   $query =
1722     qq|SELECT ?::date + ?::integer AS netto_date, ?::date + ?::integer AS skonto_date | .
1723     qq|FROM payment_terms|;
1724   ($self->{netto_date}, $self->{skonto_date}) =
1725     selectrow_query($self, $dbh, $query, $transdate, $self->{terms_netto}, $transdate, $self->{terms_skonto});
1726
1727   my ($invtotal, $total);
1728   my (%amounts, %formatted_amounts);
1729
1730   if ($self->{type} =~ /_order$/) {
1731     $amounts{invtotal} = $self->{ordtotal};
1732     $amounts{total}    = $self->{ordtotal};
1733
1734   } elsif ($self->{type} =~ /_quotation$/) {
1735     $amounts{invtotal} = $self->{quototal};
1736     $amounts{total}    = $self->{quototal};
1737
1738   } else {
1739     $amounts{invtotal} = $self->{invtotal};
1740     $amounts{total}    = $self->{total};
1741   }
1742   $amounts{skonto_in_percent} = 100.0 * $self->{percent_skonto};
1743
1744   map { $amounts{$_} = $self->parse_amount($myconfig, $amounts{$_}) } keys %amounts;
1745
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;
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   my $query       = qq|SELECT ${reference_date} + terms_netto FROM payment_terms WHERE id = ?|;
1983   my ($duedate)   = selectrow_query($self, $dbh, $query, $payment_id);
1984
1985   $main::lxdebug->leave_sub();
1986
1987   return $duedate;
1988 }
1989
1990 sub _get_contacts {
1991   $main::lxdebug->enter_sub();
1992
1993   my ($self, $dbh, $id, $key) = @_;
1994
1995   $key = "all_contacts" unless ($key);
1996
1997   if (!$id) {
1998     $self->{$key} = [];
1999     $main::lxdebug->leave_sub();
2000     return;
2001   }
2002
2003   my $query =
2004     qq|SELECT cp_id, cp_cv_id, cp_name, cp_givenname, cp_abteilung | .
2005     qq|FROM contacts | .
2006     qq|WHERE cp_cv_id = ? | .
2007     qq|ORDER BY lower(cp_name)|;
2008
2009   $self->{$key} = selectall_hashref_query($self, $dbh, $query, $id);
2010
2011   $main::lxdebug->leave_sub();
2012 }
2013
2014 sub _get_projects {
2015   $main::lxdebug->enter_sub();
2016
2017   my ($self, $dbh, $key) = @_;
2018
2019   my ($all, $old_id, $where, @values);
2020
2021   if (ref($key) eq "HASH") {
2022     my $params = $key;
2023
2024     $key = "ALL_PROJECTS";
2025
2026     foreach my $p (keys(%{$params})) {
2027       if ($p eq "all") {
2028         $all = $params->{$p};
2029       } elsif ($p eq "old_id") {
2030         $old_id = $params->{$p};
2031       } elsif ($p eq "key") {
2032         $key = $params->{$p};
2033       }
2034     }
2035   }
2036
2037   if (!$all) {
2038     $where = "WHERE active ";
2039     if ($old_id) {
2040       if (ref($old_id) eq "ARRAY") {
2041         my @ids = grep({ $_ } @{$old_id});
2042         if (@ids) {
2043           $where .= " OR id IN (" . join(",", map({ "?" } @ids)) . ") ";
2044           push(@values, @ids);
2045         }
2046       } else {
2047         $where .= " OR (id = ?) ";
2048         push(@values, $old_id);
2049       }
2050     }
2051   }
2052
2053   my $query =
2054     qq|SELECT id, projectnumber, description, active | .
2055     qq|FROM project | .
2056     $where .
2057     qq|ORDER BY lower(projectnumber)|;
2058
2059   $self->{$key} = selectall_hashref_query($self, $dbh, $query, @values);
2060
2061   $main::lxdebug->leave_sub();
2062 }
2063
2064 sub _get_shipto {
2065   $main::lxdebug->enter_sub();
2066
2067   my ($self, $dbh, $vc_id, $key) = @_;
2068
2069   $key = "all_shipto" unless ($key);
2070
2071   if ($vc_id) {
2072     # get shipping addresses
2073     my $query = qq|SELECT * FROM shipto WHERE trans_id = ?|;
2074
2075     $self->{$key} = selectall_hashref_query($self, $dbh, $query, $vc_id);
2076
2077   } else {
2078     $self->{$key} = [];
2079   }
2080
2081   $main::lxdebug->leave_sub();
2082 }
2083
2084 sub _get_printers {
2085   $main::lxdebug->enter_sub();
2086
2087   my ($self, $dbh, $key) = @_;
2088
2089   $key = "all_printers" unless ($key);
2090
2091   my $query = qq|SELECT id, printer_description, printer_command, template_code FROM printers|;
2092
2093   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2094
2095   $main::lxdebug->leave_sub();
2096 }
2097
2098 sub _get_charts {
2099   $main::lxdebug->enter_sub();
2100
2101   my ($self, $dbh, $params) = @_;
2102   my ($key);
2103
2104   $key = $params->{key};
2105   $key = "all_charts" unless ($key);
2106
2107   my $transdate = quote_db_date($params->{transdate});
2108
2109   my $query =
2110     qq|SELECT c.id, c.accno, c.description, c.link, c.charttype, tk.taxkey_id, tk.tax_id | .
2111     qq|FROM chart c | .
2112     qq|LEFT JOIN taxkeys tk ON | .
2113     qq|(tk.id = (SELECT id FROM taxkeys | .
2114     qq|          WHERE taxkeys.chart_id = c.id AND startdate <= $transdate | .
2115     qq|          ORDER BY startdate DESC LIMIT 1)) | .
2116     qq|ORDER BY c.accno|;
2117
2118   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2119
2120   $main::lxdebug->leave_sub();
2121 }
2122
2123 sub _get_taxcharts {
2124   $main::lxdebug->enter_sub();
2125
2126   my ($self, $dbh, $params) = @_;
2127
2128   my $key = "all_taxcharts";
2129   my @where;
2130
2131   if (ref $params eq 'HASH') {
2132     $key = $params->{key} if ($params->{key});
2133     if ($params->{module} eq 'AR') {
2134       push @where, 'taxkey NOT IN (8, 9, 18, 19)';
2135
2136     } elsif ($params->{module} eq 'AP') {
2137       push @where, 'taxkey NOT IN (1, 2, 3, 12, 13)';
2138     }
2139
2140   } elsif ($params) {
2141     $key = $params;
2142   }
2143
2144   my $where = @where ? ' WHERE ' . join(' AND ', map { "($_)" } @where) : '';
2145
2146   my $query = qq|SELECT * FROM tax $where ORDER BY taxkey|;
2147
2148   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2149
2150   $main::lxdebug->leave_sub();
2151 }
2152
2153 sub _get_taxzones {
2154   $main::lxdebug->enter_sub();
2155
2156   my ($self, $dbh, $key) = @_;
2157
2158   $key = "all_taxzones" unless ($key);
2159
2160   my $query = qq|SELECT * FROM tax_zones ORDER BY id|;
2161
2162   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2163
2164   $main::lxdebug->leave_sub();
2165 }
2166
2167 sub _get_employees {
2168   $main::lxdebug->enter_sub();
2169
2170   my ($self, $dbh, $default_key, $key) = @_;
2171
2172   $key = $default_key unless ($key);
2173   $self->{$key} = selectall_hashref_query($self, $dbh, qq|SELECT * FROM employee ORDER BY lower(name)|);
2174
2175   $main::lxdebug->leave_sub();
2176 }
2177
2178 sub _get_business_types {
2179   $main::lxdebug->enter_sub();
2180
2181   my ($self, $dbh, $key) = @_;
2182
2183   my $options       = ref $key eq 'HASH' ? $key : { key => $key };
2184   $options->{key} ||= "all_business_types";
2185   my $where         = '';
2186
2187   if (exists $options->{salesman}) {
2188     $where = 'WHERE ' . ($options->{salesman} ? '' : 'NOT ') . 'COALESCE(salesman)';
2189   }
2190
2191   $self->{ $options->{key} } = selectall_hashref_query($self, $dbh, qq|SELECT * FROM business $where ORDER BY lower(description)|);
2192
2193   $main::lxdebug->leave_sub();
2194 }
2195
2196 sub _get_languages {
2197   $main::lxdebug->enter_sub();
2198
2199   my ($self, $dbh, $key) = @_;
2200
2201   $key = "all_languages" unless ($key);
2202
2203   my $query = qq|SELECT * FROM language ORDER BY id|;
2204
2205   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2206
2207   $main::lxdebug->leave_sub();
2208 }
2209
2210 sub _get_dunning_configs {
2211   $main::lxdebug->enter_sub();
2212
2213   my ($self, $dbh, $key) = @_;
2214
2215   $key = "all_dunning_configs" unless ($key);
2216
2217   my $query = qq|SELECT * FROM dunning_config ORDER BY dunning_level|;
2218
2219   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2220
2221   $main::lxdebug->leave_sub();
2222 }
2223
2224 sub _get_currencies {
2225 $main::lxdebug->enter_sub();
2226
2227   my ($self, $dbh, $key) = @_;
2228
2229   $key = "all_currencies" unless ($key);
2230
2231   my $query = qq|SELECT curr AS currency FROM defaults|;
2232
2233   $self->{$key} = [split(/\:/ , selectfirst_hashref_query($self, $dbh, $query)->{currency})];
2234
2235   $main::lxdebug->leave_sub();
2236 }
2237
2238 sub _get_payments {
2239 $main::lxdebug->enter_sub();
2240
2241   my ($self, $dbh, $key) = @_;
2242
2243   $key = "all_payments" unless ($key);
2244
2245   my $query = qq|SELECT * FROM payment_terms ORDER BY sortkey|;
2246
2247   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2248
2249   $main::lxdebug->leave_sub();
2250 }
2251
2252 sub _get_customers {
2253   $main::lxdebug->enter_sub();
2254
2255   my ($self, $dbh, $key) = @_;
2256
2257   my $options        = ref $key eq 'HASH' ? $key : { key => $key };
2258   $options->{key}  ||= "all_customers";
2259   my $limit_clause   = $options->{limit} ? "LIMIT $options->{limit}" : '';
2260
2261   my @where;
2262   push @where, qq|business_id IN (SELECT id FROM business WHERE salesman)| if  $options->{business_is_salesman};
2263   push @where, qq|NOT obsolete|                                            if !$options->{with_obsolete};
2264   my $where_str = @where ? "WHERE " . join(" AND ", map { "($_)" } @where) : '';
2265
2266   my $query = qq|SELECT * FROM customer $where_str ORDER BY name $limit_clause|;
2267   $self->{ $options->{key} } = selectall_hashref_query($self, $dbh, $query);
2268
2269   $main::lxdebug->leave_sub();
2270 }
2271
2272 sub _get_vendors {
2273   $main::lxdebug->enter_sub();
2274
2275   my ($self, $dbh, $key) = @_;
2276
2277   $key = "all_vendors" unless ($key);
2278
2279   my $query = qq|SELECT * FROM vendor WHERE NOT obsolete ORDER BY name|;
2280
2281   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2282
2283   $main::lxdebug->leave_sub();
2284 }
2285
2286 sub _get_departments {
2287   $main::lxdebug->enter_sub();
2288
2289   my ($self, $dbh, $key) = @_;
2290
2291   $key = "all_departments" unless ($key);
2292
2293   my $query = qq|SELECT * FROM department ORDER BY description|;
2294
2295   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2296
2297   $main::lxdebug->leave_sub();
2298 }
2299
2300 sub _get_warehouses {
2301   $main::lxdebug->enter_sub();
2302
2303   my ($self, $dbh, $param) = @_;
2304
2305   my ($key, $bins_key);
2306
2307   if ('' eq ref $param) {
2308     $key = $param;
2309
2310   } else {
2311     $key      = $param->{key};
2312     $bins_key = $param->{bins};
2313   }
2314
2315   my $query = qq|SELECT w.* FROM warehouse w
2316                  WHERE (NOT w.invalid) AND
2317                    ((SELECT COUNT(b.*) FROM bin b WHERE b.warehouse_id = w.id) > 0)
2318                  ORDER BY w.sortkey|;
2319
2320   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2321
2322   if ($bins_key) {
2323     $query = qq|SELECT id, description FROM bin WHERE warehouse_id = ?
2324                 ORDER BY description|;
2325     my $sth = prepare_query($self, $dbh, $query);
2326
2327     foreach my $warehouse (@{ $self->{$key} }) {
2328       do_statement($self, $sth, $query, $warehouse->{id});
2329       $warehouse->{$bins_key} = [];
2330
2331       while (my $ref = $sth->fetchrow_hashref()) {
2332         push @{ $warehouse->{$bins_key} }, $ref;
2333       }
2334     }
2335     $sth->finish();
2336   }
2337
2338   $main::lxdebug->leave_sub();
2339 }
2340
2341 sub _get_simple {
2342   $main::lxdebug->enter_sub();
2343
2344   my ($self, $dbh, $table, $key, $sortkey) = @_;
2345
2346   my $query  = qq|SELECT * FROM $table|;
2347   $query    .= qq| ORDER BY $sortkey| if ($sortkey);
2348
2349   $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2350
2351   $main::lxdebug->leave_sub();
2352 }
2353
2354 #sub _get_groups {
2355 #  $main::lxdebug->enter_sub();
2356 #
2357 #  my ($self, $dbh, $key) = @_;
2358 #
2359 #  $key ||= "all_groups";
2360 #
2361 #  my $groups = $main::auth->read_groups();
2362 #
2363 #  $self->{$key} = selectall_hashref_query($self, $dbh, $query);
2364 #
2365 #  $main::lxdebug->leave_sub();
2366 #}
2367
2368 sub get_lists {
2369   $main::lxdebug->enter_sub();
2370
2371   my $self = shift;
2372   my %params = @_;
2373
2374   my $dbh = $self->get_standard_dbh(\%main::myconfig);
2375   my ($sth, $query, $ref);
2376
2377   my $vc = $self->{"vc"} eq "customer" ? "customer" : "vendor";
2378   my $vc_id = $self->{"${vc}_id"};
2379
2380   if ($params{"contacts"}) {
2381     $self->_get_contacts($dbh, $vc_id, $params{"contacts"});
2382   }
2383
2384   if ($params{"shipto"}) {
2385     $self->_get_shipto($dbh, $vc_id, $params{"shipto"});
2386   }
2387
2388   if ($params{"projects"} || $params{"all_projects"}) {
2389     $self->_get_projects($dbh, $params{"all_projects"} ?
2390                          $params{"all_projects"} : $params{"projects"},
2391                          $params{"all_projects"} ? 1 : 0);
2392   }
2393
2394   if ($params{"printers"}) {
2395     $self->_get_printers($dbh, $params{"printers"});
2396   }
2397
2398   if ($params{"languages"}) {
2399     $self->_get_languages($dbh, $params{"languages"});
2400   }
2401
2402   if ($params{"charts"}) {
2403     $self->_get_charts($dbh, $params{"charts"});
2404   }
2405
2406   if ($params{"taxcharts"}) {
2407     $self->_get_taxcharts($dbh, $params{"taxcharts"});
2408   }
2409
2410   if ($params{"taxzones"}) {
2411     $self->_get_taxzones($dbh, $params{"taxzones"});
2412   }
2413
2414   if ($params{"employees"}) {
2415     $self->_get_employees($dbh, "all_employees", $params{"employees"});
2416   }
2417
2418   if ($params{"salesmen"}) {
2419     $self->_get_employees($dbh, "all_salesmen", $params{"salesmen"});
2420   }
2421
2422   if ($params{"business_types"}) {
2423     $self->_get_business_types($dbh, $params{"business_types"});
2424   }
2425
2426   if ($params{"dunning_configs"}) {
2427     $self->_get_dunning_configs($dbh, $params{"dunning_configs"});
2428   }
2429
2430   if($params{"currencies"}) {
2431     $self->_get_currencies($dbh, $params{"currencies"});
2432   }
2433
2434   if($params{"customers"}) {
2435     $self->_get_customers($dbh, $params{"customers"});
2436   }
2437
2438   if($params{"vendors"}) {
2439     if (ref $params{"vendors"} eq 'HASH') {
2440       $self->_get_vendors($dbh, $params{"vendors"}{key}, $params{"vendors"}{limit});
2441     } else {
2442       $self->_get_vendors($dbh, $params{"vendors"});
2443     }
2444   }
2445
2446   if($params{"payments"}) {
2447     $self->_get_payments($dbh, $params{"payments"});
2448   }
2449
2450   if($params{"departments"}) {
2451     $self->_get_departments($dbh, $params{"departments"});
2452   }
2453
2454   if ($params{price_factors}) {
2455     $self->_get_simple($dbh, 'price_factors', $params{price_factors}, 'sortkey');
2456   }
2457
2458   if ($params{warehouses}) {
2459     $self->_get_warehouses($dbh, $params{warehouses});
2460   }
2461
2462 #  if ($params{groups}) {
2463 #    $self->_get_groups($dbh, $params{groups});
2464 #  }
2465
2466   if ($params{partsgroup}) {
2467     $self->get_partsgroup(\%main::myconfig, { all => 1, target => $params{partsgroup} });
2468   }
2469
2470   $main::lxdebug->leave_sub();
2471 }
2472
2473 # this sub gets the id and name from $table
2474 sub get_name {
2475   $main::lxdebug->enter_sub();
2476
2477   my ($self, $myconfig, $table) = @_;
2478
2479   # connect to database
2480   my $dbh = $self->get_standard_dbh($myconfig);
2481
2482   $table = $table eq "customer" ? "customer" : "vendor";
2483   my $arap = $self->{arap} eq "ar" ? "ar" : "ap";
2484
2485   my ($query, @values);
2486
2487   if (!$self->{openinvoices}) {
2488     my $where;
2489     if ($self->{customernumber} ne "") {
2490       $where = qq|(vc.customernumber ILIKE ?)|;
2491       push(@values, '%' . $self->{customernumber} . '%');
2492     } else {
2493       $where = qq|(vc.name ILIKE ?)|;
2494       push(@values, '%' . $self->{$table} . '%');
2495     }
2496
2497     $query =
2498       qq~SELECT vc.id, vc.name,
2499            vc.street || ' ' || vc.zipcode || ' ' || vc.city || ' ' || vc.country AS address
2500          FROM $table vc
2501          WHERE $where AND (NOT vc.obsolete)
2502          ORDER BY vc.name~;
2503   } else {
2504     $query =
2505       qq~SELECT DISTINCT vc.id, vc.name,
2506            vc.street || ' ' || vc.zipcode || ' ' || vc.city || ' ' || vc.country AS address
2507          FROM $arap a
2508          JOIN $table vc ON (a.${table}_id = vc.id)
2509          WHERE NOT (a.amount = a.paid) AND (vc.name ILIKE ?)
2510          ORDER BY vc.name~;
2511     push(@values, '%' . $self->{$table} . '%');
2512   }
2513
2514   $self->{name_list} = selectall_hashref_query($self, $dbh, $query, @values);
2515
2516   $main::lxdebug->leave_sub();
2517
2518   return scalar(@{ $self->{name_list} });
2519 }
2520
2521 # the selection sub is used in the AR, AP, IS, IR, DO and OE module
2522 #
2523 sub all_vc {
2524   $main::lxdebug->enter_sub();
2525
2526   my ($self, $myconfig, $table, $module) = @_;
2527
2528   my $ref;
2529   my $dbh = $self->get_standard_dbh;
2530
2531   $table = $table eq "customer" ? "customer" : "vendor";
2532
2533   # build selection list
2534   # Hotfix für Bug 1837 - Besser wäre es alte Buchungsbelege
2535   # OHNE Auswahlliste (reines Textfeld) zu laden. Hilft aber auch
2536   # nicht für veränderbare Belege (oe, do, ...)
2537   my $obsolete = "WHERE NOT obsolete" unless $self->{id};
2538   my $query = qq|SELECT count(*) FROM $table $obsolete|;
2539   my ($count) = selectrow_query($self, $dbh, $query);
2540
2541   if ($count < $myconfig->{vclimit}) {
2542     $query = qq|SELECT id, name, salesman_id
2543                 FROM $table $obsolete
2544                 ORDER BY name|;
2545     $self->{"all_$table"} = selectall_hashref_query($self, $dbh, $query);
2546   }
2547
2548   # get self
2549   $self->get_employee($dbh);
2550
2551   # setup sales contacts
2552   $query = qq|SELECT e.id, e.name
2553               FROM employee e
2554               WHERE (e.sales = '1') AND (NOT e.id = ?)
2555               ORDER BY name|;
2556   $self->{all_employees} = selectall_hashref_query($self, $dbh, $query, $self->{employee_id});
2557
2558   # this is for self
2559   push(@{ $self->{all_employees} },
2560        { id   => $self->{employee_id},
2561          name => $self->{employee} });
2562
2563     # prepare query for departments
2564     $query = qq|SELECT id, description
2565                 FROM department
2566                 ORDER BY description|;
2567
2568   $self->{all_departments} = selectall_hashref_query($self, $dbh, $query);
2569
2570   # get languages
2571   $query = qq|SELECT id, description
2572               FROM language
2573               ORDER BY id|;
2574
2575   $self->{languages} = selectall_hashref_query($self, $dbh, $query);
2576
2577   # get printer
2578   $query = qq|SELECT printer_description, id
2579               FROM printers
2580               ORDER BY printer_description|;
2581
2582   $self->{printers} = selectall_hashref_query($self, $dbh, $query);
2583
2584   # get payment terms
2585   $query = qq|SELECT id, description
2586               FROM payment_terms
2587               ORDER BY sortkey|;
2588
2589   $self->{payment_terms} = selectall_hashref_query($self, $dbh, $query);
2590
2591   $main::lxdebug->leave_sub();
2592 }
2593
2594 sub language_payment {
2595   $main::lxdebug->enter_sub();
2596
2597   my ($self, $myconfig) = @_;
2598
2599   my $dbh = $self->get_standard_dbh($myconfig);
2600   # get languages
2601   my $query = qq|SELECT id, description
2602                  FROM language
2603                  ORDER BY id|;
2604
2605   $self->{languages} = selectall_hashref_query($self, $dbh, $query);
2606
2607   # get printer
2608   $query = qq|SELECT printer_description, id
2609               FROM printers
2610               ORDER BY printer_description|;
2611
2612   $self->{printers} = selectall_hashref_query($self, $dbh, $query);
2613
2614   # get payment terms
2615   $query = qq|SELECT id, description
2616               FROM payment_terms
2617               ORDER BY sortkey|;
2618
2619   $self->{payment_terms} = selectall_hashref_query($self, $dbh, $query);
2620
2621   # get buchungsgruppen
2622   $query = qq|SELECT id, description
2623               FROM buchungsgruppen|;
2624
2625   $self->{BUCHUNGSGRUPPEN} = selectall_hashref_query($self, $dbh, $query);
2626
2627   $main::lxdebug->leave_sub();
2628 }
2629
2630 # this is only used for reports
2631 sub all_departments {
2632   $main::lxdebug->enter_sub();
2633
2634   my ($self, $myconfig, $table) = @_;
2635
2636   my $dbh = $self->get_standard_dbh($myconfig);
2637
2638   my $query = qq|SELECT id, description
2639                  FROM department
2640                  ORDER BY description|;
2641   $self->{all_departments} = selectall_hashref_query($self, $dbh, $query);
2642
2643   delete($self->{all_departments}) unless (@{ $self->{all_departments} || [] });
2644
2645   $main::lxdebug->leave_sub();
2646 }
2647
2648 sub create_links {
2649   $main::lxdebug->enter_sub();
2650
2651   my ($self, $module, $myconfig, $table, $provided_dbh) = @_;
2652
2653   my ($fld, $arap);
2654   if ($table eq "customer") {
2655     $fld = "buy";
2656     $arap = "ar";
2657   } else {
2658     $table = "vendor";
2659     $fld = "sell";
2660     $arap = "ap";
2661   }
2662
2663   $self->all_vc($myconfig, $table, $module);
2664
2665   # get last customers or vendors
2666   my ($query, $sth, $ref);
2667
2668   my $dbh = $provided_dbh ? $provided_dbh : $self->get_standard_dbh($myconfig);
2669   my %xkeyref = ();
2670
2671   if (!$self->{id}) {
2672
2673     my $transdate = "current_date";
2674     if ($self->{transdate}) {
2675       $transdate = $dbh->quote($self->{transdate});
2676     }
2677
2678     # now get the account numbers
2679 #    $query = qq|SELECT c.accno, c.description, c.link, c.taxkey_id, tk.tax_id
2680 #                FROM chart c, taxkeys tk
2681 #                WHERE (c.link LIKE ?) AND (c.id = tk.chart_id) AND tk.id =
2682 #                  (SELECT id FROM taxkeys WHERE (taxkeys.chart_id = c.id) AND (startdate <= $transdate) ORDER BY startdate DESC LIMIT 1)
2683 #                ORDER BY c.accno|;
2684
2685 #  same query as above, but without expensive subquery for each row. about 80% faster
2686     $query = qq|
2687       SELECT c.accno, c.description, c.link, c.taxkey_id, tk2.tax_id
2688         FROM chart c
2689         -- find newest entries in taxkeys
2690         INNER JOIN (
2691           SELECT chart_id, MAX(startdate) AS startdate
2692           FROM taxkeys
2693           WHERE (startdate <= $transdate)
2694           GROUP BY chart_id
2695         ) tk ON (c.id = tk.chart_id)
2696         -- and load all of those entries
2697         INNER JOIN taxkeys tk2
2698            ON (tk.chart_id = tk2.chart_id AND tk.startdate = tk2.startdate)
2699        WHERE (c.link LIKE ?)
2700       ORDER BY c.accno|;
2701
2702     $sth = $dbh->prepare($query);
2703
2704     do_statement($self, $sth, $query, '%' . $module . '%');
2705
2706     $self->{accounts} = "";
2707     while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
2708
2709       foreach my $key (split(/:/, $ref->{link})) {
2710         if ($key =~ /\Q$module\E/) {
2711
2712           # cross reference for keys
2713           $xkeyref{ $ref->{accno} } = $key;
2714
2715           push @{ $self->{"${module}_links"}{$key} },
2716             { accno       => $ref->{accno},
2717               description => $ref->{description},
2718               taxkey      => $ref->{taxkey_id},
2719               tax_id      => $ref->{tax_id} };
2720
2721           $self->{accounts} .= "$ref->{accno} " unless $key =~ /tax/;
2722         }
2723       }
2724     }
2725   }
2726
2727   # get taxkeys and description
2728   $query = qq|SELECT id, taxkey, taxdescription FROM tax|;
2729   $self->{TAXKEY} = selectall_hashref_query($self, $dbh, $query);
2730
2731   if (($module eq "AP") || ($module eq "AR")) {
2732     # get tax rates and description
2733     $query = qq|SELECT * FROM tax|;
2734     $self->{TAX} = selectall_hashref_query($self, $dbh, $query);
2735   }
2736
2737   if ($self->{id}) {
2738     $query =
2739       qq|SELECT
2740            a.cp_id, a.invnumber, a.transdate, a.${table}_id, a.datepaid,
2741            a.duedate, a.ordnumber, a.taxincluded, a.curr AS currency, a.notes,
2742            a.intnotes, a.department_id, a.amount AS oldinvtotal,
2743            a.paid AS oldtotalpaid, a.employee_id, a.gldate, a.type,
2744            a.globalproject_id,
2745            c.name AS $table,
2746            d.description AS department,
2747            e.name AS employee
2748          FROM $arap a
2749          JOIN $table c ON (a.${table}_id = c.id)
2750          LEFT JOIN employee e ON (e.id = a.employee_id)
2751          LEFT JOIN department d ON (d.id = a.department_id)
2752          WHERE a.id = ?|;
2753     $ref = selectfirst_hashref_query($self, $dbh, $query, $self->{id});
2754
2755     foreach my $key (keys %$ref) {
2756       $self->{$key} = $ref->{$key};
2757     }
2758
2759     # remove any trailing whitespace
2760     $self->{currency} =~ s/\s*$//;
2761
2762     my $transdate = "current_date";
2763     if ($self->{transdate}) {
2764       $transdate = $dbh->quote($self->{transdate});
2765     }
2766
2767     # now get the account numbers
2768     $query = qq|SELECT c.accno, c.description, c.link, c.taxkey_id, tk.tax_id
2769                 FROM chart c
2770                 LEFT JOIN taxkeys tk ON (tk.chart_id = c.id)
2771                 WHERE c.link LIKE ?
2772                   AND (tk.id = (SELECT id FROM taxkeys WHERE taxkeys.chart_id = c.id AND startdate <= $transdate ORDER BY startdate DESC LIMIT 1)
2773                     OR c.link LIKE '%_tax%' OR c.taxkey_id IS NULL)
2774                 ORDER BY c.accno|;
2775
2776     $sth = $dbh->prepare($query);
2777     do_statement($self, $sth, $query, "%$module%");
2778
2779     $self->{accounts} = "";
2780     while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
2781
2782       foreach my $key (split(/:/, $ref->{link})) {
2783         if ($key =~ /\Q$module\E/) {
2784
2785           # cross reference for keys
2786           $xkeyref{ $ref->{accno} } = $key;
2787
2788           push @{ $self->{"${module}_links"}{$key} },
2789             { accno       => $ref->{accno},
2790               description => $ref->{description},
2791               taxkey      => $ref->{taxkey_id},
2792               tax_id      => $ref->{tax_id} };
2793
2794           $self->{accounts} .= "$ref->{accno} " unless $key =~ /tax/;
2795         }
2796       }
2797     }
2798
2799
2800     # get amounts from individual entries
2801     $query =
2802       qq|SELECT
2803            c.accno, c.description,
2804            a.acc_trans_id, a.source, a.amount, a.memo, a.transdate, a.gldate, a.cleared, a.project_id, a.taxkey,
2805            p.projectnumber,
2806            t.rate, t.id
2807          FROM acc_trans a
2808          LEFT JOIN chart c ON (c.id = a.chart_id)
2809          LEFT JOIN project p ON (p.id = a.project_id)
2810          LEFT JOIN tax t ON (t.id= (SELECT tk.tax_id FROM taxkeys tk
2811                                     WHERE (tk.taxkey_id=a.taxkey) AND
2812                                       ((CASE WHEN a.chart_id IN (SELECT chart_id FROM taxkeys WHERE taxkey_id = a.taxkey)
2813                                         THEN tk.chart_id = a.chart_id
2814                                         ELSE 1 = 1
2815                                         END)
2816                                        OR (c.link='%tax%')) AND
2817                                       (startdate <= a.transdate) ORDER BY startdate DESC LIMIT 1))
2818          WHERE a.trans_id = ?
2819          AND a.fx_transaction = '0'
2820          ORDER BY a.acc_trans_id, a.transdate|;
2821     $sth = $dbh->prepare($query);
2822     do_statement($self, $sth, $query, $self->{id});
2823
2824     # get exchangerate for currency
2825     $self->{exchangerate} =
2826       $self->get_exchangerate($dbh, $self->{currency}, $self->{transdate}, $fld);
2827     my $index = 0;
2828
2829     # store amounts in {acc_trans}{$key} for multiple accounts
2830     while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
2831       $ref->{exchangerate} =
2832         $self->get_exchangerate($dbh, $self->{currency}, $ref->{transdate}, $fld);
2833       if (!($xkeyref{ $ref->{accno} } =~ /tax/)) {
2834         $index++;
2835       }
2836       if (($xkeyref{ $ref->{accno} } =~ /paid/) && ($self->{type} eq "credit_note")) {
2837         $ref->{amount} *= -1;
2838       }
2839       $ref->{index} = $index;
2840
2841       push @{ $self->{acc_trans}{ $xkeyref{ $ref->{accno} } } }, $ref;
2842     }
2843
2844     $sth->finish;
2845     $query =
2846       qq|SELECT
2847            d.curr AS currencies, d.closedto, d.revtrans,
2848            (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno,
2849            (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno
2850          FROM defaults d|;
2851     $ref = selectfirst_hashref_query($self, $dbh, $query);
2852     map { $self->{$_} = $ref->{$_} } keys %$ref;
2853
2854   } else {
2855
2856     # get date
2857     $query =
2858        qq|SELECT
2859             current_date AS transdate, d.curr AS currencies, d.closedto, d.revtrans,
2860             (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno,
2861             (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno
2862           FROM defaults d|;
2863     $ref = selectfirst_hashref_query($self, $dbh, $query);
2864     map { $self->{$_} = $ref->{$_} } keys %$ref;
2865
2866     if ($self->{"$self->{vc}_id"}) {
2867
2868       # only setup currency
2869       ($self->{currency}) = split(/:/, $self->{currencies}) if !$self->{currency};
2870
2871     } else {
2872
2873       $self->lastname_used($dbh, $myconfig, $table, $module);
2874
2875       # get exchangerate for currency
2876       $self->{exchangerate} =
2877         $self->get_exchangerate($dbh, $self->{currency}, $self->{transdate}, $fld);
2878
2879     }
2880
2881   }
2882
2883   $main::lxdebug->leave_sub();
2884 }
2885
2886 sub lastname_used {
2887   $main::lxdebug->enter_sub();
2888
2889   my ($self, $dbh, $myconfig, $table, $module) = @_;
2890
2891   my ($arap, $where);
2892
2893   $table         = $table eq "customer" ? "customer" : "vendor";
2894   my %column_map = ("a.curr"                  => "currency",
2895                     "a.${table}_id"           => "${table}_id",
2896                     "a.department_id"         => "department_id",
2897                     "d.description"           => "department",
2898                     "ct.name"                 => $table,
2899                     "ct.curr"                 => "cv_curr",
2900                     "current_date + ct.terms" => "duedate",
2901     );
2902
2903   if ($self->{type} =~ /delivery_order/) {
2904     $arap  = 'delivery_orders';
2905     delete $column_map{"a.curr"};
2906     delete $column_map{"ct.curr"};
2907
2908   } elsif ($self->{type} =~ /_order/) {
2909     $arap  = 'oe';
2910     $where = "quotation = '0'";
2911
2912   } elsif ($self->{type} =~ /_quotation/) {
2913     $arap  = 'oe';
2914     $where = "quotation = '1'";
2915
2916   } elsif ($table eq 'customer') {
2917     $arap  = 'ar';
2918
2919   } else {
2920     $arap  = 'ap';
2921
2922   }
2923
2924   $where           = "($where) AND" if ($where);
2925   my $query        = qq|SELECT MAX(id) FROM $arap
2926                         WHERE $where ${table}_id > 0|;
2927   my ($trans_id)   = selectrow_query($self, $dbh, $query);
2928   $trans_id       *= 1;
2929
2930   my $column_spec  = join(', ', map { "${_} AS $column_map{$_}" } keys %column_map);
2931   $query           = qq|SELECT $column_spec
2932                         FROM $arap a
2933                         LEFT JOIN $table     ct ON (a.${table}_id = ct.id)
2934                         LEFT JOIN department d  ON (a.department_id = d.id)
2935                         WHERE a.id = ?|;
2936   my $ref          = selectfirst_hashref_query($self, $dbh, $query, $trans_id);
2937
2938   map { $self->{$_} = $ref->{$_} } values %column_map;
2939
2940   # remove any trailing whitespace
2941   $self->{currency} =~ s/\s*$// if $self->{currency};
2942   $self->{cv_curr} =~ s/\s*$// if $self->{cv_curr};
2943
2944   # if customer/vendor currency is set use this
2945   $self->{currency} = $self->{cv_curr} if $self->{cv_curr};
2946
2947   $main::lxdebug->leave_sub();
2948 }
2949
2950 sub current_date {
2951   $main::lxdebug->enter_sub();
2952
2953   my $self     = shift;
2954   my $myconfig = shift || \%::myconfig;
2955   my ($thisdate, $days) = @_;
2956
2957   my $dbh = $self->get_standard_dbh($myconfig);
2958   my $query;
2959
2960   $days *= 1;
2961   if ($thisdate) {
2962     my $dateformat = $myconfig->{dateformat};
2963     $dateformat .= "yy" if $myconfig->{dateformat} !~ /^y/;
2964     $thisdate = $dbh->quote($thisdate);
2965     $query = qq|SELECT to_date($thisdate, '$dateformat') + $days AS thisdate|;
2966   } else {
2967     $query = qq|SELECT current_date AS thisdate|;
2968   }
2969
2970   ($thisdate) = selectrow_query($self, $dbh, $query);
2971
2972   $main::lxdebug->leave_sub();
2973
2974   return $thisdate;
2975 }
2976
2977 sub like {
2978   $main::lxdebug->enter_sub();
2979
2980   my ($self, $string) = @_;
2981
2982   if ($string !~ /%/) {
2983     $string = "%$string%";
2984   }
2985
2986   $string =~ s/\'/\'\'/g;
2987
2988   $main::lxdebug->leave_sub();
2989
2990   return $string;
2991 }
2992
2993 sub redo_rows {
2994   $main::lxdebug->enter_sub();
2995
2996   my ($self, $flds, $new, $count, $numrows) = @_;
2997
2998   my @ndx = ();
2999
3000   map { push @ndx, { num => $new->[$_ - 1]->{runningnumber}, ndx => $_ } } 1 .. $count;
3001
3002   my $i = 0;
3003
3004   # fill rows
3005   foreach my $item (sort { $a->{num} <=> $b->{num} } @ndx) {
3006     $i++;
3007     my $j = $item->{ndx} - 1;
3008     map { $self->{"${_}_$i"} = $new->[$j]->{$_} } @{$flds};
3009   }
3010
3011   # delete empty rows
3012   for $i ($count + 1 .. $numrows) {
3013     map { delete $self->{"${_}_$i"} } @{$flds};
3014   }
3015
3016   $main::lxdebug->leave_sub();
3017 }
3018
3019 sub update_status {
3020   $main::lxdebug->enter_sub();
3021
3022   my ($self, $myconfig) = @_;
3023
3024   my ($i, $id);
3025
3026   my $dbh = $self->dbconnect_noauto($myconfig);
3027
3028   my $query = qq|DELETE FROM status
3029                  WHERE (formname = ?) AND (trans_id = ?)|;
3030   my $sth = prepare_query($self, $dbh, $query);
3031
3032   if ($self->{formname} =~ /(check|receipt)/) {
3033     for $i (1 .. $self->{rowcount}) {
3034       do_statement($self, $sth, $query, $self->{formname}, $self->{"id_$i"} * 1);
3035     }
3036   } else {
3037     do_statement($self, $sth, $query, $self->{formname}, $self->{id});
3038   }
3039   $sth->finish();
3040
3041   my $printed = ($self->{printed} =~ /\Q$self->{formname}\E/) ? "1" : "0";
3042   my $emailed = ($self->{emailed} =~ /\Q$self->{formname}\E/) ? "1" : "0";
3043
3044   my %queued = split / /, $self->{queued};
3045   my @values;
3046
3047   if ($self->{formname} =~ /(check|receipt)/) {
3048
3049     # this is a check or receipt, add one entry for each lineitem
3050     my ($accno) = split /--/, $self->{account};
3051     $query = qq|INSERT INTO status (trans_id, printed, spoolfile, formname, chart_id)
3052                 VALUES (?, ?, ?, ?, (SELECT c.id FROM chart c WHERE c.accno = ?))|;
3053     @values = ($printed, $queued{$self->{formname}}, $self->{prinform}, $accno);
3054     $sth = prepare_query($self, $dbh, $query);
3055
3056     for $i (1 .. $self->{rowcount}) {
3057       if ($self->{"checked_$i"}) {
3058         do_statement($self, $sth, $query, $self->{"id_$i"}, @values);
3059       }
3060     }
3061     $sth->finish();
3062
3063   } else {
3064     $query = qq|INSERT INTO status (trans_id, printed, emailed, spoolfile, formname)
3065                 VALUES (?, ?, ?, ?, ?)|;
3066     do_query($self, $dbh, $query, $self->{id}, $printed, $emailed,
3067              $queued{$self->{formname}}, $self->{formname});
3068   }
3069
3070   $dbh->commit;
3071   $dbh->disconnect;
3072
3073   $main::lxdebug->leave_sub();
3074 }
3075
3076 sub save_status {
3077   $main::lxdebug->enter_sub();
3078
3079   my ($self, $dbh) = @_;
3080
3081   my ($query, $printed, $emailed);
3082
3083   my $formnames  = $self->{printed};
3084   my $emailforms = $self->{emailed};
3085
3086   $query = qq|DELETE FROM status
3087                  WHERE (formname = ?) AND (trans_id = ?)|;
3088   do_query($self, $dbh, $query, $self->{formname}, $self->{id});
3089
3090   # this only applies to the forms
3091   # checks and receipts are posted when printed or queued
3092
3093   if ($self->{queued}) {
3094     my %queued = split / /, $self->{queued};
3095
3096     foreach my $formname (keys %queued) {
3097       $printed = ($self->{printed} =~ /\Q$self->{formname}\E/) ? "1" : "0";
3098       $emailed = ($self->{emailed} =~ /\Q$self->{formname}\E/) ? "1" : "0";
3099
3100       $query = qq|INSERT INTO status (trans_id, printed, emailed, spoolfile, formname)
3101                   VALUES (?, ?, ?, ?, ?)|;
3102       do_query($self, $dbh, $query, $self->{id}, $printed, $emailed, $queued{$formname}, $formname);
3103
3104       $formnames  =~ s/\Q$self->{formname}\E//;
3105       $emailforms =~ s/\Q$self->{formname}\E//;
3106
3107     }
3108   }
3109
3110   # save printed, emailed info
3111   $formnames  =~ s/^ +//g;
3112   $emailforms =~ s/^ +//g;
3113
3114   my %status = ();
3115   map { $status{$_}{printed} = 1 } split / +/, $formnames;
3116   map { $status{$_}{emailed} = 1 } split / +/, $emailforms;
3117
3118   foreach my $formname (keys %status) {
3119     $printed = ($formnames  =~ /\Q$self->{formname}\E/) ? "1" : "0";
3120     $emailed = ($emailforms =~ /\Q$self->{formname}\E/) ? "1" : "0";
3121
3122     $query = qq|INSERT INTO status (trans_id, printed, emailed, formname)
3123                 VALUES (?, ?, ?, ?)|;
3124     do_query($self, $dbh, $query, $self->{id}, $printed, $emailed, $formname);
3125   }
3126
3127   $main::lxdebug->leave_sub();
3128 }
3129
3130 #--- 4 locale ---#
3131 # $main::locale->text('SAVED')
3132 # $main::locale->text('DELETED')
3133 # $main::locale->text('ADDED')
3134 # $main::locale->text('PAYMENT POSTED')
3135 # $main::locale->text('POSTED')
3136 # $main::locale->text('POSTED AS NEW')
3137 # $main::locale->text('ELSE')
3138 # $main::locale->text('SAVED FOR DUNNING')
3139 # $main::locale->text('DUNNING STARTED')
3140 # $main::locale->text('PRINTED')
3141 # $main::locale->text('MAILED')
3142 # $main::locale->text('SCREENED')
3143 # $main::locale->text('CANCELED')
3144 # $main::locale->text('invoice')
3145 # $main::locale->text('proforma')
3146 # $main::locale->text('sales_order')
3147 # $main::locale->text('pick_list')
3148 # $main::locale->text('purchase_order')
3149 # $main::locale->text('bin_list')
3150 # $main::locale->text('sales_quotation')
3151 # $main::locale->text('request_quotation')
3152
3153 sub save_history {
3154   $main::lxdebug->enter_sub();
3155
3156   my $self = shift;
3157   my $dbh  = shift || $self->get_standard_dbh;
3158
3159   if(!exists $self->{employee_id}) {
3160     &get_employee($self, $dbh);
3161   }
3162
3163   my $query =
3164    qq|INSERT INTO history_erp (trans_id, employee_id, addition, what_done, snumbers) | .
3165    qq|VALUES (?, (SELECT id FROM employee WHERE login = ?), ?, ?, ?)|;
3166   my @values = (conv_i($self->{id}), $self->{login},
3167                 $self->{addition}, $self->{what_done}, "$self->{snumbers}");
3168   do_query($self, $dbh, $query, @values);
3169
3170   $dbh->commit;
3171
3172   $main::lxdebug->leave_sub();
3173 }
3174
3175 sub get_history {
3176   $main::lxdebug->enter_sub();
3177
3178   my ($self, $dbh, $trans_id, $restriction, $order) = @_;
3179   my ($orderBy, $desc) = split(/\-\-/, $order);
3180   $order = " ORDER BY " . ($order eq "" ? " h.itime " : ($desc == 1 ? $orderBy . " DESC " : $orderBy . " "));
3181   my @tempArray;
3182   my $i = 0;
3183   if ($trans_id ne "") {
3184     my $query =
3185       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 | .
3186       qq|FROM history_erp h | .
3187       qq|LEFT JOIN employee emp ON (emp.id = h.employee_id) | .
3188       qq|WHERE (trans_id = | . $trans_id . qq|) $restriction | .
3189       $order;
3190
3191     my $sth = $dbh->prepare($query) || $self->dberror($query);
3192
3193     $sth->execute() || $self->dberror("$query");
3194
3195     while(my $hash_ref = $sth->fetchrow_hashref()) {
3196       $hash_ref->{addition} = $main::locale->text($hash_ref->{addition});
3197       $hash_ref->{what_done} = $main::locale->text($hash_ref->{what_done});
3198       $hash_ref->{snumbers} =~ s/^.+_(.*)$/$1/g;
3199       $tempArray[$i++] = $hash_ref;
3200     }
3201     $main::lxdebug->leave_sub() and return \@tempArray
3202       if ($i > 0 && $tempArray[0] ne "");
3203   }
3204   $main::lxdebug->leave_sub();
3205   return 0;
3206 }
3207
3208 sub update_defaults {
3209   $main::lxdebug->enter_sub();
3210
3211   my ($self, $myconfig, $fld, $provided_dbh) = @_;
3212
3213   my $dbh;
3214   if ($provided_dbh) {
3215     $dbh = $provided_dbh;
3216   } else {
3217     $dbh = $self->dbconnect_noauto($myconfig);
3218   }
3219   my $query = qq|SELECT $fld FROM defaults FOR UPDATE|;
3220   my $sth   = $dbh->prepare($query);
3221
3222   $sth->execute || $self->dberror($query);
3223   my ($var) = $sth->fetchrow_array;
3224   $sth->finish;
3225
3226   if ($var =~ m/\d+$/) {
3227     my $new_var  = (substr $var, $-[0]) * 1 + 1;
3228     my $len_diff = length($var) - $-[0] - length($new_var);
3229     $var         = substr($var, 0, $-[0]) . ($len_diff > 0 ? '0' x $len_diff : '') . $new_var;
3230
3231   } else {
3232     $var = $var . '1';
3233   }
3234
3235   $query = qq|UPDATE defaults SET $fld = ?|;
3236   do_query($self, $dbh, $query, $var);
3237
3238   if (!$provided_dbh) {
3239     $dbh->commit;
3240     $dbh->disconnect;
3241   }
3242
3243   $main::lxdebug->leave_sub();
3244
3245   return $var;
3246 }
3247
3248 sub update_business {
3249   $main::lxdebug->enter_sub();
3250
3251   my ($self, $myconfig, $business_id, $provided_dbh) = @_;
3252
3253   my $dbh;
3254   if ($provided_dbh) {
3255     $dbh = $provided_dbh;
3256   } else {
3257     $dbh = $self->dbconnect_noauto($myconfig);
3258   }
3259   my $query =
3260     qq|SELECT customernumberinit FROM business
3261        WHERE id = ? FOR UPDATE|;
3262   my ($var) = selectrow_query($self, $dbh, $query, $business_id);
3263
3264   return undef unless $var;
3265
3266   if ($var =~ m/\d+$/) {
3267     my $new_var  = (substr $var, $-[0]) * 1 + 1;
3268     my $len_diff = length($var) - $-[0] - length($new_var);
3269     $var         = substr($var, 0, $-[0]) . ($len_diff > 0 ? '0' x $len_diff : '') . $new_var;
3270
3271   } else {
3272     $var = $var . '1';
3273   }
3274
3275   $query = qq|UPDATE business
3276               SET customernumberinit = ?
3277               WHERE id = ?|;
3278   do_query($self, $dbh, $query, $var, $business_id);
3279
3280   if (!$provided_dbh) {
3281     $dbh->commit;
3282     $dbh->disconnect;
3283   }
3284
3285   $main::lxdebug->leave_sub();
3286
3287   return $var;
3288 }
3289
3290 sub get_partsgroup {
3291   $main::lxdebug->enter_sub();
3292
3293   my ($self, $myconfig, $p) = @_;
3294   my $target = $p->{target} || 'all_partsgroup';
3295
3296   my $dbh = $self->get_standard_dbh($myconfig);
3297
3298   my $query = qq|SELECT DISTINCT pg.id, pg.partsgroup
3299                  FROM partsgroup pg
3300                  JOIN parts p ON (p.partsgroup_id = pg.id) |;
3301   my @values;
3302
3303   if ($p->{searchitems} eq 'part') {
3304     $query .= qq|WHERE p.inventory_accno_id > 0|;
3305   }
3306   if ($p->{searchitems} eq 'service') {
3307     $query .= qq|WHERE p.inventory_accno_id IS NULL|;
3308   }
3309   if ($p->{searchitems} eq 'assembly') {
3310     $query .= qq|WHERE p.assembly = '1'|;
3311   }
3312   if ($p->{searchitems} eq 'labor') {
3313     $query .= qq|WHERE (p.inventory_accno_id > 0) AND (p.income_accno_id IS NULL)|;
3314   }
3315
3316   $query .= qq|ORDER BY partsgroup|;
3317
3318   if ($p->{all}) {
3319     $query = qq|SELECT id, partsgroup FROM partsgroup
3320                 ORDER BY partsgroup|;
3321   }
3322
3323   if ($p->{language_code}) {
3324     $query = qq|SELECT DISTINCT pg.id, pg.partsgroup,
3325                   t.description AS translation
3326                 FROM partsgroup pg
3327                 JOIN parts p ON (p.partsgroup_id = pg.id)
3328                 LEFT JOIN translation t ON ((t.trans_id = pg.id) AND (t.language_code = ?))
3329                 ORDER BY translation|;
3330     @values = ($p->{language_code});
3331   }
3332
3333   $self->{$target} = selectall_hashref_query($self, $dbh, $query, @values);
3334
3335   $main::lxdebug->leave_sub();
3336 }
3337
3338 sub get_pricegroup {
3339   $main::lxdebug->enter_sub();
3340
3341   my ($self, $myconfig, $p) = @_;
3342
3343   my $dbh = $self->get_standard_dbh($myconfig);
3344
3345   my $query = qq|SELECT p.id, p.pricegroup
3346                  FROM pricegroup p|;
3347
3348   $query .= qq| ORDER BY pricegroup|;
3349
3350   if ($p->{all}) {
3351     $query = qq|SELECT id, pricegroup FROM pricegroup
3352                 ORDER BY pricegroup|;
3353   }
3354
3355   $self->{all_pricegroup} = selectall_hashref_query($self, $dbh, $query);
3356
3357   $main::lxdebug->leave_sub();
3358 }
3359
3360 sub all_years {
3361 # usage $form->all_years($myconfig, [$dbh])
3362 # return list of all years where bookings found
3363 # (@all_years)
3364
3365   $main::lxdebug->enter_sub();
3366
3367   my ($self, $myconfig, $dbh) = @_;
3368
3369   $dbh ||= $self->get_standard_dbh($myconfig);
3370
3371   # get years
3372   my $query = qq|SELECT (SELECT MIN(transdate) FROM acc_trans),
3373                    (SELECT MAX(transdate) FROM acc_trans)|;
3374   my ($startdate, $enddate) = selectrow_query($self, $dbh, $query);
3375
3376   if ($myconfig->{dateformat} =~ /^yy/) {
3377     ($startdate) = split /\W/, $startdate;
3378     ($enddate) = split /\W/, $enddate;
3379   } else {
3380     (@_) = split /\W/, $startdate;
3381     $startdate = $_[2];
3382     (@_) = split /\W/, $enddate;
3383     $enddate = $_[2];
3384   }
3385
3386   my @all_years;
3387   $startdate = substr($startdate,0,4);
3388   $enddate = substr($enddate,0,4);
3389
3390   while ($enddate >= $startdate) {
3391     push @all_years, $enddate--;
3392   }
3393
3394   return @all_years;
3395
3396   $main::lxdebug->leave_sub();
3397 }
3398
3399 sub backup_vars {
3400   $main::lxdebug->enter_sub();
3401   my $self = shift;
3402   my @vars = @_;
3403
3404   map { $self->{_VAR_BACKUP}->{$_} = $self->{$_} if exists $self->{$_} } @vars;
3405
3406   $main::lxdebug->leave_sub();
3407 }
3408
3409 sub restore_vars {
3410   $main::lxdebug->enter_sub();
3411
3412   my $self = shift;
3413   my @vars = @_;
3414
3415   map { $self->{$_} = $self->{_VAR_BACKUP}->{$_} if exists $self->{_VAR_BACKUP}->{$_} } @vars;
3416
3417   $main::lxdebug->leave_sub();
3418 }
3419
3420 sub prepare_for_printing {
3421   my ($self) = @_;
3422
3423   $self->{templates} ||= $::myconfig{templates};
3424   $self->{formname}  ||= $self->{type};
3425   $self->{media}     ||= 'email';
3426
3427   die "'media' other than 'email', 'file', 'printer' is not supported yet" unless $self->{media} =~ m/^(?:email|file|printer)$/;
3428
3429   # set shipto from billto unless set
3430   my $has_shipto = any { $self->{"shipto$_"} } qw(name street zipcode city country contact);
3431   if (!$has_shipto && ($self->{type} =~ m/^(?:purchase_order|request_quotation)$/)) {
3432     $self->{shiptoname}   = $::myconfig{company};
3433     $self->{shiptostreet} = $::myconfig{address};
3434   }
3435
3436   my $language = $self->{language} ? '_' . $self->{language} : '';
3437
3438   my ($language_tc, $output_numberformat, $output_dateformat, $output_longdates);
3439   if ($self->{language_id}) {
3440     ($language_tc, $output_numberformat, $output_dateformat, $output_longdates) = AM->get_language_details(\%::myconfig, $self, $self->{language_id});
3441   } else {
3442     $output_dateformat   = $::myconfig{dateformat};
3443     $output_numberformat = $::myconfig{numberformat};
3444     $output_longdates    = 1;
3445   }
3446
3447   # Retrieve accounts for tax calculation.
3448   IC->retrieve_accounts(\%::myconfig, $self, map { $_ => $self->{"id_$_"} } 1 .. $self->{rowcount});
3449
3450   if ($self->{type} =~ /_delivery_order$/) {
3451     DO->order_details();
3452   } elsif ($self->{type} =~ /sales_order|sales_quotation|request_quotation|purchase_order/) {
3453     OE->order_details(\%::myconfig, $self);
3454   } else {
3455     IS->invoice_details(\%::myconfig, $self, $::locale);
3456   }
3457
3458   # Chose extension & set source file name
3459   my $extension = 'html';
3460   if ($self->{format} eq 'postscript') {
3461     $self->{postscript}   = 1;
3462     $extension            = 'tex';
3463   } elsif ($self->{"format"} =~ /pdf/) {
3464     $self->{pdf}          = 1;
3465     $extension            = $self->{'format'} =~ m/opendocument/i ? 'odt' : 'tex';
3466   } elsif ($self->{"format"} =~ /opendocument/) {
3467     $self->{opendocument} = 1;
3468     $extension            = 'odt';
3469   } elsif ($self->{"format"} =~ /excel/) {
3470     $self->{excel}        = 1;
3471     $extension            = 'xls';
3472   }
3473
3474   my $printer_code    = $self->{printer_code} ? '_' . $self->{printer_code} : '';
3475   my $email_extension = -f "$::myconfig{templates}/$self->{formname}_email${language}.${extension}" ? '_email' : '';
3476   $self->{IN}         = "$self->{formname}${email_extension}${language}${printer_code}.${extension}";
3477
3478   # Format dates.
3479   $self->format_dates($output_dateformat, $output_longdates,
3480                       qw(invdate orddate quodate pldate duedate reqdate transdate shippingdate deliverydate validitydate paymentdate datepaid
3481                          transdate_oe deliverydate_oe employee_startdate employee_enddate),
3482                       grep({ /^(?:datepaid|transdate_oe|reqdate|deliverydate|deliverydate_oe|transdate)_\d+$/ } keys(%{$self})));
3483
3484   $self->reformat_numbers($output_numberformat, 2,
3485                           qw(invtotal ordtotal quototal subtotal linetotal listprice sellprice netprice discount tax taxbase total paid),
3486                           grep({ /^(?:linetotal|listprice|sellprice|netprice|taxbase|discount|paid|subtotal|total|tax)_\d+$/ } keys(%{$self})));
3487
3488   $self->reformat_numbers($output_numberformat, undef, qw(qty price_factor), grep({ /^qty_\d+$/} keys(%{$self})));
3489
3490   my ($cvar_date_fields, $cvar_number_fields) = CVar->get_field_format_list('module' => 'CT', 'prefix' => 'vc_');
3491
3492   if (scalar @{ $cvar_date_fields }) {
3493     $self->format_dates($output_dateformat, $output_longdates, @{ $cvar_date_fields });
3494   }
3495
3496   while (my ($precision, $field_list) = each %{ $cvar_number_fields }) {
3497     $self->reformat_numbers($output_numberformat, $precision, @{ $field_list });
3498   }
3499
3500   return $self;
3501 }
3502
3503 sub format_dates {
3504   my ($self, $dateformat, $longformat, @indices) = @_;
3505
3506   $dateformat ||= $::myconfig{dateformat};
3507
3508   foreach my $idx (@indices) {
3509     if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) {
3510       for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) {
3511         $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $::locale->reformat_date(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i], $dateformat, $longformat);
3512       }
3513     }
3514
3515     next unless defined $self->{$idx};
3516
3517     if (!ref($self->{$idx})) {
3518       $self->{$idx} = $::locale->reformat_date(\%::myconfig, $self->{$idx}, $dateformat, $longformat);
3519
3520     } elsif (ref($self->{$idx}) eq "ARRAY") {
3521       for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) {
3522         $self->{$idx}->[$i] = $::locale->reformat_date(\%::myconfig, $self->{$idx}->[$i], $dateformat, $longformat);
3523       }
3524     }
3525   }
3526 }
3527
3528 sub reformat_numbers {
3529   my ($self, $numberformat, $places, @indices) = @_;
3530
3531   return if !$numberformat || ($numberformat eq $::myconfig{numberformat});
3532
3533   foreach my $idx (@indices) {
3534     if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) {
3535       for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) {
3536         $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $self->parse_amount(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i]);
3537       }
3538     }
3539
3540     next unless defined $self->{$idx};
3541
3542     if (!ref($self->{$idx})) {
3543       $self->{$idx} = $self->parse_amount(\%::myconfig, $self->{$idx});
3544
3545     } elsif (ref($self->{$idx}) eq "ARRAY") {
3546       for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) {
3547         $self->{$idx}->[$i] = $self->parse_amount(\%::myconfig, $self->{$idx}->[$i]);
3548       }
3549     }
3550   }
3551
3552   my $saved_numberformat    = $::myconfig{numberformat};
3553   $::myconfig{numberformat} = $numberformat;
3554
3555   foreach my $idx (@indices) {
3556     if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) {
3557       for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) {
3558         $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $self->format_amount(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i], $places);
3559       }
3560     }
3561
3562     next unless defined $self->{$idx};
3563
3564     if (!ref($self->{$idx})) {
3565       $self->{$idx} = $self->format_amount(\%::myconfig, $self->{$idx}, $places);
3566
3567     } elsif (ref($self->{$idx}) eq "ARRAY") {
3568       for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) {
3569         $self->{$idx}->[$i] = $self->format_amount(\%::myconfig, $self->{$idx}->[$i], $places);
3570       }
3571     }
3572   }
3573
3574   $::myconfig{numberformat} = $saved_numberformat;
3575 }
3576
3577 sub layout {
3578   my ($self) = @_;
3579   $::lxdebug->enter_sub;
3580
3581   my %style_to_script_map = (
3582     v3  => 'v3',
3583     neu => 'new',
3584     v4  => 'v4',
3585   );
3586
3587   my $menu_script = $style_to_script_map{$::myconfig{menustyle}} || '';
3588
3589   package main;
3590   require "bin/mozilla/menu$menu_script.pl";
3591   package Form;
3592   require SL::Controller::FrameHeader;
3593
3594
3595   my $layout = SL::Controller::FrameHeader->new->action_header . ::render();
3596
3597   $::lxdebug->leave_sub;
3598   return $layout;
3599 }
3600
3601 1;
3602
3603 __END__
3604
3605 =head1 NAME
3606
3607 SL::Form.pm - main data object.
3608
3609 =head1 SYNOPSIS
3610
3611 This is the main data object of Lx-Office.
3612 Unfortunately it also acts as a god object for certain data retrieval procedures used in the entry points.
3613 Points of interest for a beginner are:
3614
3615  - $form->error            - renders a generic error in html. accepts an error message
3616  - $form->get_standard_dbh - returns a database connection for the
3617
3618 =head1 SPECIAL FUNCTIONS
3619
3620 =head2 C<update_business> PARAMS
3621
3622 PARAMS (not named):
3623  \%config,     - config hashref
3624  $business_id, - business id
3625  $dbh          - optional database handle
3626
3627 handles business (thats customer/vendor types) sequences.
3628
3629 special behaviour for empty strings in customerinitnumber field:
3630 will in this case not increase the value, and return undef.
3631
3632 =head2 C<redirect_header> $url
3633
3634 Generates a HTTP redirection header for the new C<$url>. Constructs an
3635 absolute URL including scheme, host name and port. If C<$url> is a
3636 relative URL then it is considered relative to Lx-Office base URL.
3637
3638 This function C<die>s if headers have already been created with
3639 C<$::form-E<gt>header>.
3640
3641 Examples:
3642
3643   print $::form->redirect_header('oe.pl?action=edit&id=1234');
3644   print $::form->redirect_header('http://www.lx-office.org/');
3645
3646 =head2 C<header>
3647
3648 Generates a general purpose http/html header and includes most of the scripts
3649 and stylesheets needed. Stylesheets can be added with L<use_stylesheet>.
3650
3651 Only one header will be generated. If the method was already called in this
3652 request it will not output anything and return undef. Also if no
3653 HTTP_USER_AGENT is found, no header is generated.
3654
3655 Although header does not accept parameters itself, it will honor special
3656 hashkeys of its Form instance:
3657
3658 =over 4
3659
3660 =item refresh_time
3661
3662 =item refresh_url
3663
3664 If one of these is set, a http-equiv refresh is generated. Missing parameters
3665 default to 3 seconds and the refering url.
3666
3667 =item stylesheet
3668
3669 Either a scalar or an array ref. Will be inlined into the header. Add
3670 stylesheets with the L<use_stylesheet> function.
3671
3672 =item landscape
3673
3674 If true, a css snippet will be generated that sets the page in landscape mode.
3675
3676 =item favicon
3677
3678 Used to override the default favicon.
3679
3680 =item title
3681
3682 A html page title will be generated from this
3683
3684 =back
3685
3686 =cut