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