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