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