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