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