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