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