acc6c6b58ff827a2bdafdbc6d91d5bd179a430d7
[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 sub _input_to_hash {
41   $main::lxdebug->enter_sub();
42
43   my $input = $_[0];
44   my %in    = ();
45   my @pairs = split(/&/, $input);
46
47   foreach (@pairs) {
48     my ($name, $value) = split(/=/, $_, 2);
49     $in{$name} = unescape(undef, $value);
50   }
51
52   $main::lxdebug->leave_sub();
53
54   return %in;
55 }
56
57 sub _request_to_hash {
58   $main::lxdebug->enter_sub();
59
60   my ($input) = @_;
61   my ($i,        $loc,  $key,    $val);
62   my (%ATTACH,   $f,    $header, $header_body, $len, $buf);
63   my ($boundary, @list, $size,   $body, $x, $blah, $name);
64
65   if ($ENV{'CONTENT_TYPE'}
66       && ($ENV{'CONTENT_TYPE'} =~ /multipart\/form-data; boundary=(.+)$/)) {
67     $boundary = quotemeta('--' . $1);
68     @list     = split(/$boundary/, $input);
69
70     # For some reason there are always 2 extra, that are empty
71     $size = @list - 2;
72
73     for ($x = 1; $x <= $size; $x++) {
74       $header_body = $list[$x];
75       $header_body =~ /\r\n\r\n|\n\n/;
76
77       # Here we split the header and body
78       $header = $`;
79       $body   = $';    #'
80       $body =~ s/\r\n$//;
81
82       # Now we try to get the file name
83       $name = $header;
84       $name =~ /name=\"(.+)\"/;
85       ($name, $blah) = split(/\"/, $1);
86
87       # If the form name is not attach, then we need to parse this like
88       # regular form data
89       if ($name ne "attach") {
90         $body =~ s/%([0-9a-fA-Z]{2})/pack("c",hex($1))/eg;
91         $ATTACH{$name} = $body;
92
93         # Otherwise it is an attachment and we need to finish it up
94       } elsif ($name eq "attach") {
95         $header =~ /filename=\"(.+)\"/;
96         $ATTACH{'FILE_NAME'} = $1;
97         $ATTACH{'FILE_NAME'} =~ s/\"//g;
98         $ATTACH{'FILE_NAME'} =~ s/\s//g;
99         $ATTACH{'FILE_CONTENT'} = $body;
100
101         for ($i = $x; $list[$i]; $i++) {
102           $list[$i] =~ s/^.+name=$//;
103           $list[$i] =~ /\"(\w+)\"/;
104           $ATTACH{$1} = $';    #'
105         }
106       }
107     }
108
109     $main::lxdebug->leave_sub();
110     return %ATTACH;
111
112       } else {
113     $main::lxdebug->leave_sub();
114     return _input_to_hash($input);
115   }
116 }
117
118 sub new {
119   $main::lxdebug->enter_sub();
120
121   my $type = shift;
122
123   my $self = {};
124
125   read(STDIN, $_, $ENV{CONTENT_LENGTH});
126
127   if ($ENV{QUERY_STRING}) {
128     $_ = $ENV{QUERY_STRING};
129   }
130
131   if ($ARGV[0]) {
132     $_ = $ARGV[0];
133   }
134
135   my %parameters = _request_to_hash($_);
136   map({ $self->{$_} = $parameters{$_}; } keys(%parameters));
137
138   $self->{menubar} = 1 if $self->{path} =~ /lynx/i;
139
140   $self->{action} = lc $self->{action};
141   $self->{action} =~ s/( |-|,|#)/_/g;
142
143   $self->{version}   = "2.2.0";
144   $self->{dbversion} = "2.2.0";
145
146   $main::lxdebug->leave_sub();
147
148   bless $self, $type;
149 }
150
151 sub debug {
152   $main::lxdebug->enter_sub();
153
154   my ($self) = @_;
155
156   print "\n";
157
158   map { print "$_ = $self->{$_}\n" } (sort keys %{$self});
159
160   $main::lxdebug->leave_sub();
161 }
162
163 sub escape {
164   $main::lxdebug->enter_sub();
165
166   my ($self, $str, $beenthere) = @_;
167
168   # for Apache 2 we escape strings twice
169   #if (($ENV{SERVER_SOFTWARE} =~ /Apache\/2/) && !$beenthere) {
170   #  $str = $self->escape($str, 1);
171   #}
172
173   $str =~ s/([^a-zA-Z0-9_.-])/sprintf("%%%02x", ord($1))/ge;
174
175   $main::lxdebug->leave_sub();
176
177   return $str;
178 }
179
180 sub unescape {
181   $main::lxdebug->enter_sub();
182
183   my ($self, $str) = @_;
184
185   $str =~ tr/+/ /;
186   $str =~ s/\\$//;
187
188   $str =~ s/%([0-9a-fA-Z]{2})/pack("c",hex($1))/eg;
189
190   $main::lxdebug->leave_sub();
191
192   return $str;
193 }
194
195 sub quote {
196   my ($self, $str) = @_;
197
198   if ($str && !ref($str)) {
199     $str =~ s/"/&quot;/g;
200   }
201
202   $str;
203
204 }
205
206 sub unquote {
207   my ($self, $str) = @_;
208
209   if ($str && !ref($str)) {
210     $str =~ s/&quot;/"/g;
211   }
212
213   $str;
214
215 }
216
217 sub hide_form {
218   my $self = shift;
219
220   if (@_) {
221     for (@_) {
222       print qq|<input type=hidden name="$_" value="|
223         . $self->quote($self->{$_})
224         . qq|">\n|;
225     }
226   } else {
227     delete $self->{header};
228     for (sort keys %$self) {
229       print qq|<input type=hidden name="$_" value="|
230         . $self->quote($self->{$_})
231         . qq|">\n|;
232     }
233   }
234
235 }
236
237 sub error {
238   $main::lxdebug->enter_sub();
239
240   my ($self, $msg) = @_;
241
242   if ($ENV{HTTP_USER_AGENT}) {
243     $msg =~ s/\n/<br>/g;
244
245     $self->header;
246
247     print qq|
248     <body>
249
250     <h2 class=error>Error!</h2>
251
252     <p><b>$msg</b>
253
254     </body>
255     </html>
256     |;
257
258     die "Error: $msg\n";
259
260   } else {
261
262     if ($self->{error_function}) {
263       &{ $self->{error_function} }($msg);
264     } else {
265       die "Error: $msg\n";
266     }
267   }
268
269   $main::lxdebug->leave_sub();
270 }
271
272 sub info {
273   $main::lxdebug->enter_sub();
274
275   my ($self, $msg) = @_;
276
277   if ($ENV{HTTP_USER_AGENT}) {
278     $msg =~ s/\n/<br>/g;
279
280     if (!$self->{header}) {
281       $self->header;
282       print qq|
283       <body>|;
284     }
285
286     print qq|
287
288     <p><b>$msg</b>
289     |;
290
291   } else {
292
293     if ($self->{info_function}) {
294       &{ $self->{info_function} }($msg);
295     } else {
296       print "$msg\n";
297     }
298   }
299
300   $main::lxdebug->leave_sub();
301 }
302
303 sub numtextrows {
304   $main::lxdebug->enter_sub();
305
306   my ($self, $str, $cols, $maxrows) = @_;
307
308   my $rows = 0;
309
310   map { $rows += int(((length) - 2) / $cols) + 1 } split /\r/, $str;
311
312   $maxrows = $rows unless defined $maxrows;
313
314   $main::lxdebug->leave_sub();
315
316   return ($rows > $maxrows) ? $maxrows : $rows;
317 }
318
319 sub dberror {
320   $main::lxdebug->enter_sub();
321
322   my ($self, $msg) = @_;
323
324   $self->error("$msg\n" . $DBI::errstr);
325
326   $main::lxdebug->leave_sub();
327 }
328
329 sub isblank {
330   $main::lxdebug->enter_sub();
331
332   my ($self, $name, $msg) = @_;
333
334   if ($self->{$name} =~ /^\s*$/) {
335     $self->error($msg);
336   }
337   $main::lxdebug->leave_sub();
338 }
339
340 sub header {
341   $main::lxdebug->enter_sub();
342
343   my ($self) = @_;
344
345   if ($self->{header}) {
346     $main::lxdebug->leave_sub();
347     return;
348   }
349
350   my ($stylesheet, $favicon, $charset);
351
352   if ($ENV{HTTP_USER_AGENT}) {
353
354     if ($self->{stylesheet} && (-f "css/$self->{stylesheet}")) {
355       $stylesheet =
356         qq|<LINK REL="stylesheet" HREF="css/$self->{stylesheet}" TYPE="text/css" TITLE="Lx-Office stylesheet">
357  |;
358     }
359
360     if ($self->{favicon} && (-f "$self->{favicon}")) {
361       $favicon =
362         qq|<LINK REL="shortcut icon" HREF="$self->{favicon}" TYPE="image/x-icon">
363   |;
364     }
365
366     if ($self->{charset}) {
367       $charset =
368         qq|<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=$self->{charset}">
369   |;
370     }
371     if ($self->{landscape}) {
372       $pagelayout = qq|<style type="text/css">
373                         \@page { size:landscape; }
374                         </style>|;
375     }
376     if ($self->{fokus}) {
377       $fokus = qq|<script type="text/javascript">
378 <!--
379 function fokus(){document.$self->{fokus}.focus();}
380 //-->
381 </script>|;
382     }
383
384     #Set Calendar
385     $jsscript = "";
386     if ($self->{jsscript} == 1) {
387
388       $jsscript = qq|
389         <style type="text/css">\@import url(js/jscalendar/calendar-win2k-1.css);</style>
390         <script type="text/javascript" src="js/jscalendar/calendar.js"></script>
391         <script type="text/javascript" src="js/jscalendar/lang/calendar-de.js"></script>
392         <script type="text/javascript" src="js/jscalendar/calendar-setup.js"></script>
393         $self->{javascript}
394        |;
395     }
396
397     $self->{titlebar} =
398       ($self->{title})
399       ? "$self->{title} - $self->{titlebar}"
400       : $self->{titlebar};
401
402     print qq|Content-Type: text/html
403
404 <html>
405 <head>
406   <title>$self->{titlebar}</title>
407   $stylesheet
408   $pagelayout
409   $favicon
410   $charset
411   $jsscript
412   $fokus
413 </head>
414
415 |;
416   }
417   $self->{header} = 1;
418
419   $main::lxdebug->leave_sub();
420 }
421
422 # write Trigger JavaScript-Code ($qty = quantity of Triggers)
423 # changed it to accept an arbitrary number of triggers - sschoeling
424 sub write_trigger {
425   $main::lxdebug->enter_sub();
426
427   my $self     = shift;
428   my $myconfig = shift;
429   my $qty      = shift;
430
431   # set dateform for jsscript
432   # default
433   $ifFormat = "%d.%m.%Y";
434   if ($myconfig->{dateformat} eq "dd.mm.yy") {
435     $ifFormat = "%d.%m.%Y";
436   } else {
437     if ($myconfig->{dateformat} eq "dd-mm-yy") {
438       $ifFormat = "%d-%m-%Y";
439     } else {
440       if ($myconfig->{dateformat} eq "dd/mm/yy") {
441         $ifFormat = "%d/%m/%Y";
442       } else {
443         if ($myconfig->{dateformat} eq "mm/dd/yy") {
444           $ifFormat = "%m/%d/%Y";
445         } else {
446           if ($myconfig->{dateformat} eq "mm-dd-yy") {
447             $ifFormat = "%m-%d-%Y";
448           } else {
449             if ($myconfig->{dateformat} eq "yyyy-mm-dd") {
450               $ifFormat = "%Y-%m-%d";
451             }
452           }
453         }
454       }
455     }
456   }
457
458   while ($#_ >= 2) {
459     push @triggers, qq|
460        Calendar.setup(
461       {
462       inputField : "| . (shift) . qq|",
463       ifFormat :"$ifFormat",
464       align : "| .  (shift) . qq|", 
465       button : "| . (shift) . qq|"
466       }
467       );
468        |;
469   }
470   $jsscript = qq|
471        <script type="text/javascript">
472        <!--| . join("", @triggers) . qq|//-->
473         </script>
474         |;
475
476   $main::lxdebug->leave_sub();
477
478   return $jsscript;
479 }    #end sub write_trigger
480
481 sub redirect {
482   $main::lxdebug->enter_sub();
483
484   my ($self, $msg) = @_;
485
486   if ($self->{callback}) {
487
488     ($script, $argv) = split(/\?/, $self->{callback});
489     exec("perl", "$script", $argv);
490
491   } else {
492
493     $self->info($msg);
494     exit;
495   }
496
497   $main::lxdebug->leave_sub();
498 }
499
500 # sort of columns removed - empty sub
501 sub sort_columns {
502   $main::lxdebug->enter_sub();
503
504   my ($self, @columns) = @_;
505
506   $main::lxdebug->leave_sub();
507
508   return @columns;
509 }
510
511 sub format_amount {
512   $main::lxdebug->enter_sub();
513
514   my ($self, $myconfig, $amount, $places, $dash) = @_;
515
516   #Workaround for $format_amount calls without $places
517   if (!defined $places) {
518     (my $dec) = ($amount =~ /\.(\d+)/);
519     $places = length $dec;
520   }
521
522   if ($places =~ /\d/) {
523     $amount = $self->round_amount($amount, $places);
524   }
525
526   # is the amount negative
527   my $negative = ($amount < 0);
528   my $fillup   = "";
529
530   if ($amount != 0) {
531     if ($myconfig->{numberformat} && ($myconfig->{numberformat} ne '1000.00'))
532     {
533       my ($whole, $dec) = split /\./, "$amount";
534       $whole =~ s/-//;
535       $amount = join '', reverse split //, $whole;
536       $fillup = "0" x ($places - length($dec));
537
538       if ($myconfig->{numberformat} eq '1,000.00') {
539         $amount =~ s/\d{3,}?/$&,/g;
540         $amount =~ s/,$//;
541         $amount = join '', reverse split //, $amount;
542         $amount .= "\.$dec" . $fillup if ($places ne '' && $places * 1 != 0);
543       }
544
545       if ($myconfig->{numberformat} eq '1.000,00') {
546         $amount =~ s/\d{3,}?/$&./g;
547         $amount =~ s/\.$//;
548         $amount = join '', reverse split //, $amount;
549         $amount .= ",$dec" . $fillup if ($places ne '' && $places * 1 != 0);
550       }
551
552       if ($myconfig->{numberformat} eq '1000,00') {
553         $amount = "$whole";
554         $amount .= ",$dec" . $fillup if ($places ne '' && $places * 1 != 0);
555       }
556
557       if ($dash =~ /-/) {
558         $amount = ($negative) ? "($amount)" : "$amount";
559       } elsif ($dash =~ /DRCR/) {
560         $amount = ($negative) ? "$amount DR" : "$amount CR";
561       } else {
562         $amount = ($negative) ? "-$amount" : "$amount";
563       }
564     }
565   } else {
566     if ($dash eq "0" && $places) {
567       if ($myconfig->{numberformat} eq '1.000,00') {
568         $amount = "0" . "," . "0" x $places;
569       } else {
570         $amount = "0" . "." . "0" x $places;
571       }
572     } else {
573       $amount = ($dash ne "") ? "$dash" : "0";
574     }
575   }
576
577   $main::lxdebug->leave_sub();
578
579   return $amount;
580 }
581
582 sub parse_amount {
583   $main::lxdebug->enter_sub();
584
585   my ($self, $myconfig, $amount) = @_;
586   $main::lxdebug->message(LXDebug::DEBUG2, "Start amount: $amount");
587
588   if ($myconfig->{in_numberformat} == 1) {
589
590     # Extra input number format 1000.00 or 1000,00
591     $main::lxdebug->message(LXDebug::DEBUG2,
592               "in_numberformat: " . $main::locale->text('1000,00 or 1000.00'));
593     $amount =~ s/,/\./g;
594
595     #$main::lxdebug->message(LXDebug::DEBUG2, "1.Parsed Number: $amount") if ($amount);
596     $amount = scalar reverse $amount;
597
598     #$main::lxdebug->message(LXDebug::DEBUG2, "2.Parsed Number: $amount") if ($amount);
599     $amount =~ s/\./DOT/;
600
601     #$main::lxdebug->message(LXDebug::DEBUG2, "3.Parsed Number: $amount") if ($amount);
602     $amount =~ s/\.//g;
603
604     #$main::lxdebug->message(LXDebug::DEBUG2, "4.Parsed Number: $amount") if ($amount);
605     $amount =~ s/DOT/\./;
606
607     #$main::lxdebug->message(LXDebug::DEBUG2, "5.Parsed Number:" . $amount) if ($amount);
608     $amount = scalar reverse $amount;
609     $main::lxdebug->message(LXDebug::DEBUG2,
610                             "Parsed amount:" . $amount . "\n");
611
612     return ($amount * 1);
613
614   }
615   $main::lxdebug->message(LXDebug::DEBUG2,
616               "in_numberformat: " . $main::locale->text('equal Outputformat'));
617   $main::lxdebug->message(LXDebug::DEBUG2,
618                           " = numberformat: $myconfig->{numberformat}");
619   if (   ($myconfig->{numberformat} eq '1.000,00')
620       || ($myconfig->{numberformat} eq '1000,00')) {
621     $amount =~ s/\.//g;
622     $amount =~ s/,/\./;
623   }
624
625   if ($myconfig->{numberformat} eq "1'000.00") {
626     $amount =~ s/'//g;
627   }
628
629   $amount =~ s/,//g;
630
631   $main::lxdebug->message(LXDebug::DEBUG2, "Parsed amount:" . $amount . "\n")
632     if ($amount);
633   $main::lxdebug->leave_sub();
634
635   return ($amount * 1);
636 }
637
638 sub round_amount {
639   $main::lxdebug->enter_sub();
640
641   my ($self, $amount, $places) = @_;
642   my $round_amount;
643
644   # Rounding like "Kaufmannsrunden"
645   # Descr. http://de.wikipedia.org/wiki/Rundung
646   # Inspired by
647   # http://www.perl.com/doc/FAQs/FAQ/oldfaq-html/Q4.13.html
648   # Solves Bug: 189
649   # Udo Spallek
650   $amount = $amount * (10**($places));
651   $round_amount = int($amount + .5 * ($amount <=> 0)) / (10**($places));
652
653   $main::lxdebug->leave_sub();
654
655   return $round_amount;
656
657 }
658
659 sub parse_template {
660   $main::lxdebug->enter_sub();
661
662   my ($self, $myconfig, $userspath) = @_;
663
664   # { Moritz Bunkus
665   # Some variables used for page breaks
666   my ($chars_per_line, $lines_on_first_page, $lines_on_second_page) =
667     (0, 0, 0);
668   my ($current_page, $current_line, $current_row) = (1, 1, 0);
669   my $pagebreak = "";
670   my $sum       = 0;
671
672   # } Moritz Bunkus
673
674   # Make sure that all *notes* (intnotes, partnotes_*, notes etc) are converted to markup correctly.
675   $self->format_string(grep(/notes/, keys(%{$self})));
676
677   # Copy the notes from the invoice/sales order etc. back to the variable "notes" because that is where most templates expect it to be.
678   $self->{"notes"} = $self->{ $self->{"formname"} . "notes" };
679
680   map({ $self->{"employee_${_}"} = $myconfig->{$_}; }
681       qw(email tel fax name signature));
682
683   open(IN, "$self->{templates}/$self->{IN}")
684     or $self->error("$self->{IN} : $!");
685
686   @_ = <IN>;
687   close(IN);
688
689   $self->{copies} = 1 if (($self->{copies} *= 1) <= 0);
690
691   # OUT is used for the media, screen, printer, email
692   # for postscript we store a copy in a temporary file
693   my $fileid = time;
694   $self->{tmpfile} = "$userspath/${fileid}.$self->{IN}";
695   if ($self->{format} =~ /(postscript|pdf)/ || $self->{media} eq 'email') {
696     $out = $self->{OUT};
697     $self->{OUT} = ">$self->{tmpfile}";
698   }
699
700   if ($self->{OUT}) {
701     open(OUT, "$self->{OUT}") or $self->error("$self->{OUT} : $!");
702   } else {
703     open(OUT, ">-") or $self->error("STDOUT : $!");
704     $self->header;
705   }
706
707   # Do we have to run LaTeX two times? This is needed if
708   # the template contains page references.
709   $two_passes = 0;
710
711   # first we generate a tmpfile
712   # read file and replace <%variable%>
713   while ($_ = shift) {
714
715     $par = "";
716     $var = $_;
717
718     $two_passes = 1 if (/\\pageref/);
719
720     # { Moritz Bunkus
721     # detect pagebreak block and its parameters
722     if (/\s*<%pagebreak ([0-9]+) ([0-9]+) ([0-9]+)%>/) {
723       $chars_per_line       = $1;
724       $lines_on_first_page  = $2;
725       $lines_on_second_page = $3;
726
727       while ($_ = shift) {
728         last if (/\s*<%end pagebreak%>/);
729         $pagebreak .= $_;
730       }
731     }
732
733     # } Moritz Bunkus
734
735     if (/\s*<%foreach /) {
736
737       # this one we need for the count
738       chomp $var;
739       $var =~ s/\s*<%foreach (.+?)%>/$1/;
740       while ($_ = shift) {
741         last if (/\s*<%end /);
742
743         # store line in $par
744         $par .= $_;
745       }
746
747       # display contents of $self->{number}[] array
748       for $i (0 .. $#{ $self->{$var} }) {
749
750         # { Moritz Bunkus
751         # Try to detect whether a manual page break is necessary
752         # but only if there was a <%pagebreak ...%> block before
753
754         if ($chars_per_line) {
755           my $lines =
756             int(length($self->{"description"}[$i]) / $chars_per_line + 0.95);
757           my $lpp;
758
759           my $_description = $self->{"description"}[$i];
760           while ($_description =~ /\\newline/) {
761             $lines++;
762             $_description =~ s/\\newline//;
763           }
764           $self->{"description"}[$i] =~ s/(\\newline\s?)*$//;
765
766           if ($current_page == 1) {
767             $lpp = $lines_on_first_page;
768           } else {
769             $lpp = $lines_on_second_page;
770           }
771
772           # Yes we need a manual page break -- or the user has forced one
773           if (
774              (($current_line + $lines) > $lpp)
775              || ($self->{"_forced_pagebreaks"}
776                && grep(/^${current_row}$/, @{ $self->{"_forced_pagebreaks"} }))
777             ) {
778             my $pb = $pagebreak;
779
780             # replace the special variables <%sumcarriedforward%>
781             # and <%lastpage%>
782
783             my $psum = $self->format_amount($myconfig, $sum, 2);
784             $pb =~ s/<%sumcarriedforward%>/$psum/g;
785             $pb =~ s/<%lastpage%>/$current_page/g;
786
787             # only "normal" variables are supported here
788             # (no <%if, no <%foreach, no <%include)
789
790             $pb =~ s/<%(.+?)%>/$self->{$1}/g;
791
792             # page break block is ready to rock
793             print(OUT $pb);
794             $current_page++;
795             $current_line = 1;
796           }
797           $current_line += $lines;
798           $current_row++;
799         }
800         $sum += $self->parse_amount($myconfig, $self->{"linetotal"}[$i]);
801
802         # } Moritz Bunkus
803
804         # don't parse par, we need it for each line
805         $_ = $par;
806         s/<%(.+?)%>/$self->{$1}[$i]/mg;
807         print OUT;
808       }
809       next;
810     }
811
812     # if not comes before if!
813     if (/\s*<%if not /) {
814
815       # check if it is not set and display
816       chop;
817       s/\s*<%if not (.+?)%>/$1/;
818
819       unless ($self->{$_}) {
820         while ($_ = shift) {
821           last if (/\s*<%end /);
822
823           # store line in $par
824           $par .= $_;
825         }
826
827         $_ = $par;
828
829       } else {
830         while ($_ = shift) {
831           last if (/\s*<%end /);
832         }
833         next;
834       }
835     }
836
837     if (/\s*<%if /) {
838
839       # check if it is set and display
840       chop;
841       s/\s*<%if (.+?)%>/$1/;
842
843       if ($self->{$_}) {
844         while ($_ = shift) {
845           last if (/\s*<%end /);
846
847           # store line in $par
848           $par .= $_;
849         }
850
851         $_ = $par;
852
853       } else {
854         while ($_ = shift) {
855           last if (/\s*<%end /);
856         }
857         next;
858       }
859     }
860
861     # check for <%include filename%>
862     if (/\s*<%include /) {
863
864       # get the filename
865       chomp $var;
866       $var =~ s/\s*<%include (.+?)%>/$1/;
867
868       # mangle filename
869       $var =~ s/(\/|\.\.)//g;
870
871       # prevent the infinite loop!
872       next if ($self->{"$var"});
873
874       open(INC, "$self->{templates}/$var")
875         or $self->error($self->cleanup . "$self->{templates}/$var : $!");
876       unshift(@_, <INC>);
877       close(INC);
878
879       $self->{"$var"} = 1;
880
881       next;
882     }
883
884     s/<%(.+?)%>/$self->{$1}/g;
885     s/<nobr><\/nobr>/&nbsp;/g;
886     print OUT;
887   }
888
889   close(OUT);
890
891   # { Moritz Bunkus
892   # Convert the tex file to postscript
893   if ($self->{format} =~ /(postscript|pdf)/) {
894
895     use Cwd;
896     $self->{cwd}    = cwd();
897     $self->{tmpdir} = "$self->{cwd}/$userspath";
898
899     chdir("$userspath") or $self->error($self->cleanup . "chdir : $!");
900
901     $self->{tmpfile} =~ s/$userspath\///g;
902
903     if ($self->{format} eq 'postscript') {
904       system(
905         "latex --interaction=nonstopmode $self->{tmpfile} > $self->{tmpfile}.err"
906       );
907       $self->error($self->cleanup) if ($?);
908       if ($two_passes) {
909         system(
910           "latex --interaction=nonstopmode $self->{tmpfile} > $self->{tmpfile}.err"
911         );
912         $self->error($self->cleanup) if ($?);
913       }
914
915       $self->{tmpfile} =~ s/tex$/dvi/;
916
917       system("dvips $self->{tmpfile} -o -q > /dev/null");
918       $self->error($self->cleanup . "dvips : $!") if ($?);
919       $self->{tmpfile} =~ s/dvi$/ps/;
920     }
921     if ($self->{format} eq 'pdf') {
922       system(
923         "pdflatex --interaction=nonstopmode $self->{tmpfile} > $self->{tmpfile}.err"
924       );
925       $self->error($self->cleanup) if ($?);
926       if ($two_passes) {
927         system(
928           "pdflatex --interaction=nonstopmode $self->{tmpfile} > $self->{tmpfile}.err"
929         );
930         $self->error($self->cleanup) if ($?);
931       }
932       $self->{tmpfile} =~ s/tex$/pdf/;
933     }
934
935   }
936
937   if ($self->{format} =~ /(postscript|pdf)/ || $self->{media} eq 'email') {
938
939     if ($self->{media} eq 'email') {
940
941       use SL::Mailer;
942
943       my $mail = new Mailer;
944
945       map { $mail->{$_} = $self->{$_} }
946         qw(cc bcc subject message version format charset);
947       $mail->{to}     = qq|$self->{email}|;
948       $mail->{from}   = qq|"$myconfig->{name}" <$myconfig->{email}>|;
949       $mail->{fileid} = "$fileid.";
950
951       # if we send html or plain text inline
952       if (($self->{format} eq 'html') && ($self->{sendmode} eq 'inline')) {
953         $mail->{contenttype} = "text/html";
954
955         $mail->{message}       =~ s/\r\n/<br>\n/g;
956         $myconfig->{signature} =~ s/\\n/<br>\n/g;
957         $mail->{message} .= "<br>\n--<br>\n$myconfig->{signature}\n<br>";
958
959         open(IN, $self->{tmpfile})
960           or $self->error($self->cleanup . "$self->{tmpfile} : $!");
961         while (<IN>) {
962           $mail->{message} .= $_;
963         }
964
965         close(IN);
966
967       } else {
968
969         @{ $mail->{attachments} } = ($self->{tmpfile});
970
971         $myconfig->{signature} =~ s/\\n/\r\n/g;
972         $mail->{message} .= "\r\n--\r\n$myconfig->{signature}";
973
974       }
975
976       my $err = $mail->send($out);
977       $self->error($self->cleanup . "$err") if ($err);
978
979     } else {
980
981       $self->{OUT} = $out;
982
983       my $numbytes = (-s $self->{tmpfile});
984       open(IN, $self->{tmpfile})
985         or $self->error($self->cleanup . "$self->{tmpfile} : $!");
986
987       $self->{copies} = 1 unless $self->{media} eq 'printer';
988
989       chdir("$self->{cwd}");
990
991       for my $i (1 .. $self->{copies}) {
992         if ($self->{OUT}) {
993           open(OUT, $self->{OUT})
994             or $self->error($self->cleanup . "$self->{OUT} : $!");
995         } else {
996
997           # launch application
998           print qq|Content-Type: application/$self->{format}
999 Content-Disposition: attachment; filename="$self->{tmpfile}"
1000 Content-Length: $numbytes
1001
1002 |;
1003
1004           open(OUT, ">-") or $self->error($self->cleanup . "$!: STDOUT");
1005
1006         }
1007
1008         while (<IN>) {
1009           print OUT $_;
1010         }
1011
1012         close(OUT);
1013
1014         seek IN, 0, 0;
1015       }
1016
1017       close(IN);
1018     }
1019
1020     $self->cleanup;
1021
1022   }
1023
1024   chdir("$self->{cwd}");
1025   $main::lxdebug->leave_sub();
1026 }
1027
1028 sub cleanup {
1029   $main::lxdebug->enter_sub();
1030
1031   my $self = shift;
1032
1033   chdir("$self->{tmpdir}");
1034
1035   my @err = ();
1036   if (-f "$self->{tmpfile}.err") {
1037     open(FH, "$self->{tmpfile}.err");
1038     @err = <FH>;
1039     close(FH);
1040   }
1041
1042   if ($self->{tmpfile}) {
1043
1044     # strip extension
1045     $self->{tmpfile} =~ s/\.\w+$//g;
1046     my $tmpfile = $self->{tmpfile};
1047     unlink(<$tmpfile.*>);
1048   }
1049
1050   chdir("$self->{cwd}");
1051
1052   $main::lxdebug->leave_sub();
1053
1054   return "@err";
1055 }
1056
1057 sub format_string {
1058   $main::lxdebug->enter_sub();
1059
1060   my ($self, @fields) = @_;
1061   my %unique_fields;
1062
1063   %unique_fields = map({ $_ => 1 } @fields);
1064   @fields        = keys(%unique_fields);
1065
1066   foreach my $field (@fields) {
1067     next unless ($self->{$field} =~ /\<pagebreak\>/);
1068     $self->{$field} =~ s/\<pagebreak\>//g;
1069     if ($field =~ /.*_(\d+)$/) {
1070       if (!$self->{"_forced_pagebreaks"}) {
1071         $self->{"_forced_pagebreaks"} = [];
1072       }
1073       push(@{ $self->{"_forced_pagebreaks"} }, "$1");
1074     }
1075   }
1076
1077   my $format = $self->{format};
1078   if ($self->{format} =~ /(postscript|pdf)/) {
1079     $format = 'tex';
1080   }
1081
1082   my %replace = (
1083     'order' => {
1084       'html' => [
1085         '<', '>', quotemeta('\n'), '
1086 '
1087       ],
1088       'tex' => [
1089         '&', quotemeta('\n'), '
1090 ',
1091         '"', '\$', '%', '_', '#', quotemeta('^'),
1092         '{', '}',  '<', '>', '£', "\r"
1093       ]
1094     },
1095     'html' => {
1096       '<'             => '&lt;',
1097       '>'             => '&gt;',
1098       quotemeta('\n') => '<br>',
1099       '
1100 ' => '<br>'
1101     },
1102     'tex' => {
1103       '"'             => "''",
1104       '&'             => '\&',
1105       '\$'            => '\$',
1106       '%'             => '\%',
1107       '_'             => '\_',
1108       '#'             => '\#',
1109       quotemeta('^')  => '\^\\',
1110       '{'             => '\{',
1111       '}'             => '\}',
1112       '<'             => '$<$',
1113       '>'             => '$>$',
1114       quotemeta('\n') => '\newline ',
1115       '
1116 '          => '\newline ',
1117       '£'  => '\pounds ',
1118       "\r" => ""
1119     });
1120
1121   foreach my $key (@{ $replace{order}{$format} }) {
1122     map { $self->{$_} =~ s/$key/$replace{$format}{$key}/g; } @fields;
1123   }
1124
1125   # Allow some HTML markup to be converted into the output format's
1126   # corresponding markup code, e.g. bold or italic.
1127   if ('html' eq $format) {
1128     my @markup_replace = ('b', 'i', 's', 'u');
1129
1130     foreach my $key (@markup_replace) {
1131       map({ $self->{$_} =~ s/\&lt;(\/?)${key}\&gt;/<$1${key}>/g } @fields);
1132     }
1133
1134   } elsif ('tex' eq $format) {
1135     my %markup_replace = ('b' => 'textbf',
1136                           'i' => 'textit',
1137                           'u' => 'underline');
1138
1139     foreach my $field (@fields) {
1140       foreach my $key (keys(%markup_replace)) {
1141         my $new = $markup_replace{$key};
1142         $self->{$field} =~
1143           s/\$\<\$${key}\$\>\$(.*?)\$<\$\/${key}\$>\$/\\${new}\{$1\}/gi;
1144       }
1145     }
1146   }
1147
1148   $main::lxdebug->leave_sub();
1149 }
1150
1151 sub datetonum {
1152   $main::lxdebug->enter_sub();
1153
1154   my ($self, $date, $myconfig) = @_;
1155
1156   if ($date && $date =~ /\D/) {
1157
1158     if ($myconfig->{dateformat} =~ /^yy/) {
1159       ($yy, $mm, $dd) = split /\D/, $date;
1160     }
1161     if ($myconfig->{dateformat} =~ /^mm/) {
1162       ($mm, $dd, $yy) = split /\D/, $date;
1163     }
1164     if ($myconfig->{dateformat} =~ /^dd/) {
1165       ($dd, $mm, $yy) = split /\D/, $date;
1166     }
1167
1168     $dd *= 1;
1169     $mm *= 1;
1170     $yy = ($yy < 70) ? $yy + 2000 : $yy;
1171     $yy = ($yy >= 70 && $yy <= 99) ? $yy + 1900 : $yy;
1172
1173     $dd = "0$dd" if ($dd < 10);
1174     $mm = "0$mm" if ($mm < 10);
1175
1176     $date = "$yy$mm$dd";
1177   }
1178
1179   $main::lxdebug->leave_sub();
1180
1181   return $date;
1182 }
1183
1184 # Database routines used throughout
1185
1186 sub dbconnect {
1187   $main::lxdebug->enter_sub();
1188
1189   my ($self, $myconfig) = @_;
1190
1191   # connect to database
1192   my $dbh =
1193     DBI->connect($myconfig->{dbconnect},
1194                  $myconfig->{dbuser}, $myconfig->{dbpasswd})
1195     or $self->dberror;
1196
1197   # set db options
1198   if ($myconfig->{dboptions}) {
1199     $dbh->do($myconfig->{dboptions}) || $self->dberror($myconfig->{dboptions});
1200   }
1201
1202   $main::lxdebug->leave_sub();
1203
1204   return $dbh;
1205 }
1206
1207 sub dbconnect_noauto {
1208   $main::lxdebug->enter_sub();
1209
1210   my ($self, $myconfig) = @_;
1211
1212   # connect to database
1213   $dbh =
1214     DBI->connect($myconfig->{dbconnect}, $myconfig->{dbuser},
1215                  $myconfig->{dbpasswd}, { AutoCommit => 0 })
1216     or $self->dberror;
1217
1218   # set db options
1219   if ($myconfig->{dboptions}) {
1220     $dbh->do($myconfig->{dboptions}) || $self->dberror($myconfig->{dboptions});
1221   }
1222
1223   $main::lxdebug->leave_sub();
1224
1225   return $dbh;
1226 }
1227
1228 sub update_balance {
1229   $main::lxdebug->enter_sub();
1230
1231   my ($self, $dbh, $table, $field, $where, $value) = @_;
1232
1233   # if we have a value, go do it
1234   if ($value != 0) {
1235
1236     # retrieve balance from table
1237     my $query = "SELECT $field FROM $table WHERE $where FOR UPDATE";
1238     my $sth   = $dbh->prepare($query);
1239
1240     $sth->execute || $self->dberror($query);
1241     my ($balance) = $sth->fetchrow_array;
1242     $sth->finish;
1243
1244     $balance += $value;
1245
1246     # update balance
1247     $query = "UPDATE $table SET $field = $balance WHERE $where";
1248     $dbh->do($query) || $self->dberror($query);
1249   }
1250   $main::lxdebug->leave_sub();
1251 }
1252
1253 sub update_exchangerate {
1254   $main::lxdebug->enter_sub();
1255
1256   my ($self, $dbh, $curr, $transdate, $buy, $sell) = @_;
1257
1258   # some sanity check for currency
1259   if ($curr eq '') {
1260     $main::lxdebug->leave_sub();
1261     return;
1262   }
1263
1264   my $query = qq|SELECT e.curr FROM exchangerate e
1265                  WHERE e.curr = '$curr'
1266                  AND e.transdate = '$transdate'
1267                  FOR UPDATE|;
1268   my $sth = $dbh->prepare($query);
1269   $sth->execute || $self->dberror($query);
1270
1271   my $set;
1272   if ($buy != 0 && $sell != 0) {
1273     $set = "buy = $buy, sell = $sell";
1274   } elsif ($buy != 0) {
1275     $set = "buy = $buy";
1276   } elsif ($sell != 0) {
1277     $set = "sell = $sell";
1278   }
1279
1280   if ($sth->fetchrow_array) {
1281     $query = qq|UPDATE exchangerate
1282                 SET $set
1283                 WHERE curr = '$curr'
1284                 AND transdate = '$transdate'|;
1285   } else {
1286     $query = qq|INSERT INTO exchangerate (curr, buy, sell, transdate)
1287                 VALUES ('$curr', $buy, $sell, '$transdate')|;
1288   }
1289   $sth->finish;
1290   $dbh->do($query) || $self->dberror($query);
1291
1292   $main::lxdebug->leave_sub();
1293 }
1294
1295 sub save_exchangerate {
1296   $main::lxdebug->enter_sub();
1297
1298   my ($self, $myconfig, $currency, $transdate, $rate, $fld) = @_;
1299
1300   my $dbh = $self->dbconnect($myconfig);
1301
1302   my ($buy, $sell) = (0, 0);
1303   $buy  = $rate if $fld eq 'buy';
1304   $sell = $rate if $fld eq 'sell';
1305
1306   $self->update_exchangerate($dbh, $currency, $transdate, $buy, $sell);
1307
1308   $dbh->disconnect;
1309
1310   $main::lxdebug->leave_sub();
1311 }
1312
1313 sub get_exchangerate {
1314   $main::lxdebug->enter_sub();
1315
1316   my ($self, $dbh, $curr, $transdate, $fld) = @_;
1317
1318   unless ($transdate) {
1319     $main::lxdebug->leave_sub();
1320     return "";
1321   }
1322
1323   my $query = qq|SELECT e.$fld FROM exchangerate e
1324                  WHERE e.curr = '$curr'
1325                  AND e.transdate = '$transdate'|;
1326   my $sth = $dbh->prepare($query);
1327   $sth->execute || $self->dberror($query);
1328
1329   my ($exchangerate) = $sth->fetchrow_array;
1330   $sth->finish;
1331
1332   $main::lxdebug->leave_sub();
1333
1334   return $exchangerate;
1335 }
1336
1337 sub check_exchangerate {
1338   $main::lxdebug->enter_sub();
1339
1340   my ($self, $myconfig, $currency, $transdate, $fld) = @_;
1341
1342   unless ($transdate) {
1343     $main::lxdebug->leave_sub();
1344     return "";
1345   }
1346
1347   my $dbh = $self->dbconnect($myconfig);
1348
1349   my $query = qq|SELECT e.$fld FROM exchangerate e
1350                  WHERE e.curr = '$currency'
1351                  AND e.transdate = '$transdate'|;
1352   my $sth = $dbh->prepare($query);
1353   $sth->execute || $self->dberror($query);
1354
1355   my ($exchangerate) = $sth->fetchrow_array;
1356   $sth->finish;
1357   $dbh->disconnect;
1358
1359   $main::lxdebug->leave_sub();
1360
1361   return $exchangerate;
1362 }
1363
1364 sub add_shipto {
1365   $main::lxdebug->enter_sub();
1366
1367   my ($self, $dbh, $id) = @_;
1368 ##LINET
1369   my $shipto;
1370   foreach my $item (
1371     qw(name department_1 department_2 street zipcode city country contact phone fax email)
1372     ) {
1373     if ($self->{"shipto$item"}) {
1374       $shipto = 1 if ($self->{$item} ne $self->{"shipto$item"});
1375     }
1376     $self->{"shipto$item"} =~ s/\'/\'\'/g;
1377   }
1378
1379   if ($shipto) {
1380     my $query =
1381       qq|INSERT INTO shipto (trans_id, shiptoname, shiptodepartment_1, shiptodepartment_2, shiptostreet,
1382                    shiptozipcode, shiptocity, shiptocountry, shiptocontact,
1383                    shiptophone, shiptofax, shiptoemail) VALUES ($id,
1384                    '$self->{shiptoname}', '$self->{shiptodepartment_1}', '$self->{shiptodepartment_2}', '$self->{shiptostreet}',
1385                    '$self->{shiptozipcode}', '$self->{shiptocity}',
1386                    '$self->{shiptocountry}', '$self->{shiptocontact}',
1387                    '$self->{shiptophone}', '$self->{shiptofax}',
1388                    '$self->{shiptoemail}')|;
1389     $dbh->do($query) || $self->dberror($query);
1390   }
1391 ##/LINET
1392   $main::lxdebug->leave_sub();
1393 }
1394
1395 sub get_employee {
1396   $main::lxdebug->enter_sub();
1397
1398   my ($self, $dbh) = @_;
1399
1400   my $query = qq|SELECT e.id, e.name FROM employee e
1401                  WHERE e.login = '$self->{login}'|;
1402   my $sth = $dbh->prepare($query);
1403   $sth->execute || $self->dberror($query);
1404
1405   ($self->{employee_id}, $self->{employee}) = $sth->fetchrow_array;
1406   $self->{employee_id} *= 1;
1407
1408   $sth->finish;
1409
1410   $main::lxdebug->leave_sub();
1411 }
1412
1413 # get other contact for transaction and form - html/tex
1414 sub get_contact {
1415   $main::lxdebug->enter_sub();
1416
1417   my ($self, $dbh, $id) = @_;
1418
1419   my $query = qq|SELECT c.*
1420               FROM contacts c
1421               WHERE cp_id=$id|;
1422   $sth = $dbh->prepare($query);
1423   $sth->execute || $self->dberror($query);
1424
1425   $ref = $sth->fetchrow_hashref(NAME_lc);
1426
1427   push @{ $self->{$_} }, $ref;
1428
1429   $sth->finish;
1430   $main::lxdebug->leave_sub();
1431 }
1432
1433 # get contacts for id, if no contact return {"","","","",""}
1434 sub get_contacts {
1435   $main::lxdebug->enter_sub();
1436
1437   my ($self, $dbh, $id) = @_;
1438
1439   my $query = qq|SELECT c.cp_id, c.cp_cv_id, c.cp_name, c.cp_givenname
1440               FROM contacts c
1441               WHERE cp_cv_id=$id|;
1442   my $sth = $dbh->prepare($query);
1443   $sth->execute || $self->dberror($query);
1444
1445   my $i = 0;
1446   while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
1447     push @{ $self->{all_contacts} }, $ref;
1448     $i++;
1449   }
1450
1451   if ($i == 0) {
1452     push @{ $self->{all_contacts} }, { { "", "", "", "", "" } };
1453   }
1454   $sth->finish;
1455   $main::lxdebug->leave_sub();
1456 }
1457
1458 # this sub gets the id and name from $table
1459 sub get_name {
1460   $main::lxdebug->enter_sub();
1461
1462   my ($self, $myconfig, $table) = @_;
1463
1464   # connect to database
1465   my $dbh = $self->dbconnect($myconfig);
1466
1467   my $name           = $self->like(lc $self->{$table});
1468   my $customernumber = $self->like(lc $self->{customernumber});
1469
1470   if ($self->{customernumber} ne "") {
1471     $query = qq~SELECT c.id, c.name,
1472                   c.street || ' ' || c.zipcode || ' ' || c.city || ' ' || c.country AS address
1473                   FROM $table c
1474                   WHERE (lower(c.customernumber) LIKE '$customernumber') AND (not c.obsolete)
1475                   ORDER BY c.name~;
1476   } else {
1477     $query = qq~SELECT c.id, c.name,
1478                  c.street || ' ' || c.zipcode || ' ' || c.city || ' ' || c.country AS address
1479                  FROM $table c
1480                  WHERE (lower(c.name) LIKE '$name') AND (not c.obsolete)
1481                  ORDER BY c.name~;
1482   }
1483
1484   if ($self->{openinvoices}) {
1485     $query = qq~SELECT DISTINCT c.id, c.name,
1486                 c.street || ' ' || c.zipcode || ' ' || c.city || ' ' || c.country AS address
1487                 FROM $self->{arap} a
1488                 JOIN $table c ON (a.${table}_id = c.id)
1489                 WHERE NOT a.amount = a.paid
1490                 AND lower(c.name) LIKE '$name'
1491                 ORDER BY c.name~;
1492   }
1493   my $sth = $dbh->prepare($query);
1494
1495   $sth->execute || $self->dberror($query);
1496
1497   my $i = 0;
1498   while ($ref = $sth->fetchrow_hashref(NAME_lc)) {
1499     push(@{ $self->{name_list} }, $ref);
1500     $i++;
1501   }
1502   $sth->finish;
1503   $dbh->disconnect;
1504
1505   $main::lxdebug->leave_sub();
1506
1507   return $i;
1508 }
1509
1510 # the selection sub is used in the AR, AP, IS, IR and OE module
1511 #
1512 sub all_vc {
1513   $main::lxdebug->enter_sub();
1514
1515   my ($self, $myconfig, $table, $module) = @_;
1516
1517   my $ref;
1518   my $dbh = $self->dbconnect($myconfig);
1519
1520   my $query = qq|SELECT count(*) FROM $table|;
1521   my $sth   = $dbh->prepare($query);
1522   $sth->execute || $self->dberror($query);
1523   my ($count) = $sth->fetchrow_array;
1524   $sth->finish;
1525
1526   # build selection list
1527   if ($count < $myconfig->{vclimit}) {
1528     $query = qq|SELECT id, name
1529                 FROM $table WHERE not obsolete
1530                 ORDER BY name|;
1531     $sth = $dbh->prepare($query);
1532     $sth->execute || $self->dberror($query);
1533
1534     while ($ref = $sth->fetchrow_hashref(NAME_lc)) {
1535       push @{ $self->{"all_$table"} }, $ref;
1536     }
1537
1538     $sth->finish;
1539
1540   }
1541
1542   # get self
1543   $self->get_employee($dbh);
1544
1545   # setup sales contacts
1546   $query = qq|SELECT e.id, e.name
1547               FROM employee e
1548               WHERE e.sales = '1'
1549               AND NOT e.id = $self->{employee_id}|;
1550   $sth = $dbh->prepare($query);
1551   $sth->execute || $self->dberror($query);
1552
1553   while ($ref = $sth->fetchrow_hashref(NAME_lc)) {
1554     push @{ $self->{all_employees} }, $ref;
1555   }
1556   $sth->finish;
1557
1558   # this is for self
1559   push @{ $self->{all_employees} },
1560     { id   => $self->{employee_id},
1561       name => $self->{employee} };
1562
1563   # sort the whole thing
1564   @{ $self->{all_employees} } =
1565     sort { $a->{name} cmp $b->{name} } @{ $self->{all_employees} };
1566
1567   if ($module eq 'AR') {
1568
1569     # prepare query for departments
1570     $query = qq|SELECT d.id, d.description
1571                 FROM department d
1572                 WHERE d.role = 'P'
1573                 ORDER BY 2|;
1574
1575   } else {
1576     $query = qq|SELECT d.id, d.description
1577                 FROM department d
1578                 ORDER BY 2|;
1579   }
1580
1581   $sth = $dbh->prepare($query);
1582   $sth->execute || $self->dberror($query);
1583
1584   while ($ref = $sth->fetchrow_hashref(NAME_lc)) {
1585     push @{ $self->{all_departments} }, $ref;
1586   }
1587   $sth->finish;
1588
1589   $dbh->disconnect;
1590   $main::lxdebug->leave_sub();
1591 }
1592
1593 # this is only used for reports
1594 sub all_departments {
1595   $main::lxdebug->enter_sub();
1596
1597   my ($self, $myconfig, $table) = @_;
1598
1599   my $dbh   = $self->dbconnect($myconfig);
1600   my $where = "1 = 1";
1601
1602   if (defined $table) {
1603     if ($table eq 'customer') {
1604       $where = " d.role = 'P'";
1605     }
1606   }
1607
1608   my $query = qq|SELECT d.id, d.description
1609                  FROM department d
1610                  WHERE $where
1611                  ORDER BY 2|;
1612   my $sth = $dbh->prepare($query);
1613   $sth->execute || $self->dberror($query);
1614
1615   while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
1616     push @{ $self->{all_departments} }, $ref;
1617   }
1618   $sth->finish;
1619
1620   $dbh->disconnect;
1621
1622   $main::lxdebug->leave_sub();
1623 }
1624
1625 sub create_links {
1626   $main::lxdebug->enter_sub();
1627
1628   my ($self, $module, $myconfig, $table) = @_;
1629
1630   $self->all_vc($myconfig, $table, $module);
1631
1632   # get last customers or vendors
1633   my ($query, $sth);
1634
1635   my $dbh = $self->dbconnect($myconfig);
1636
1637   my %xkeyref = ();
1638
1639   # now get the account numbers
1640   $query = qq|SELECT c.accno, c.description, c.link, c.taxkey_id
1641               FROM chart c
1642               WHERE c.link LIKE '%$module%'
1643               ORDER BY c.accno|;
1644
1645   $sth = $dbh->prepare($query);
1646   $sth->execute || $self->dberror($query);
1647
1648   $self->{accounts} = "";
1649   while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
1650
1651     foreach my $key (split /:/, $ref->{link}) {
1652       if ($key =~ /$module/) {
1653
1654         # cross reference for keys
1655         $xkeyref{ $ref->{accno} } = $key;
1656
1657         push @{ $self->{"${module}_links"}{$key} },
1658           { accno       => $ref->{accno},
1659             description => $ref->{description},
1660             taxkey      => $ref->{taxkey_id} };
1661
1662         $self->{accounts} .= "$ref->{accno} " unless $key =~ /tax/;
1663       }
1664     }
1665   }
1666   $sth->finish;
1667
1668   if (($module eq "AP") || ($module eq "AR")) {
1669
1670     # get tax rates and description
1671     $query = qq| SELECT * FROM tax t|;
1672     $sth   = $dbh->prepare($query);
1673     $sth->execute || $self->dberror($query);
1674     $form->{TAX} = ();
1675     while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
1676       push @{ $self->{TAX} }, $ref;
1677     }
1678     $sth->finish;
1679   }
1680
1681   if ($self->{id}) {
1682     my $arap = ($table eq 'customer') ? 'ar' : 'ap';
1683
1684     $query = qq|SELECT a.cp_id, a.invnumber, a.transdate,
1685                 a.${table}_id, a.datepaid, a.duedate, a.ordnumber,
1686                 a.taxincluded, a.curr AS currency, a.notes, a.intnotes,
1687                 c.name AS $table, a.department_id, d.description AS department,
1688                 a.amount AS oldinvtotal, a.paid AS oldtotalpaid,
1689                 a.employee_id, e.name AS employee, a.gldate
1690                 FROM $arap a
1691                 JOIN $table c ON (a.${table}_id = c.id)
1692                 LEFT JOIN employee e ON (e.id = a.employee_id)
1693                 LEFT JOIN department d ON (d.id = a.department_id)
1694                 WHERE a.id = $self->{id}|;
1695     $sth = $dbh->prepare($query);
1696     $sth->execute || $self->dberror($query);
1697
1698     $ref = $sth->fetchrow_hashref(NAME_lc);
1699     foreach $key (keys %$ref) {
1700       $self->{$key} = $ref->{$key};
1701     }
1702     $sth->finish;
1703
1704     # get amounts from individual entries
1705     $query = qq|SELECT c.accno, c.description, a.source, a.amount, a.memo,
1706                 a.transdate, a.cleared, a.project_id, p.projectnumber, a.taxkey, t.rate
1707                 FROM acc_trans a
1708                 JOIN chart c ON (c.id = a.chart_id)
1709                 LEFT JOIN project p ON (p.id = a.project_id)
1710                 LEFT Join tax t ON (a.taxkey = t.taxkey)
1711                 WHERE a.trans_id = $self->{id}
1712                 AND a.fx_transaction = '0'
1713                 ORDER BY a.oid,a.transdate|;
1714     $sth = $dbh->prepare($query);
1715     $sth->execute || $self->dberror($query);
1716
1717     my $fld = ($table eq 'customer') ? 'buy' : 'sell';
1718
1719     # get exchangerate for currency
1720     $self->{exchangerate} =
1721       $self->get_exchangerate($dbh, $self->{currency}, $self->{transdate},
1722                               $fld);
1723     my $index = 0;
1724
1725     # store amounts in {acc_trans}{$key} for multiple accounts
1726     while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
1727       $ref->{exchangerate} =
1728         $self->get_exchangerate($dbh, $self->{currency}, $ref->{transdate},
1729                                 $fld);
1730       if (!($xkeyref{ $ref->{accno} } =~ /tax/)) {
1731         $index++;
1732       }
1733       $ref->{index} = $index;
1734
1735       push @{ $self->{acc_trans}{ $xkeyref{ $ref->{accno} } } }, $ref;
1736     }
1737     $sth->finish;
1738
1739     $query = qq|SELECT d.curr AS currencies, d.closedto, d.revtrans,
1740                   (SELECT c.accno FROM chart c
1741                    WHERE d.fxgain_accno_id = c.id) AS fxgain_accno,
1742                   (SELECT c.accno FROM chart c
1743                    WHERE d.fxloss_accno_id = c.id) AS fxloss_accno
1744                 FROM defaults d|;
1745     $sth = $dbh->prepare($query);
1746     $sth->execute || $self->dberror($query);
1747
1748     $ref = $sth->fetchrow_hashref(NAME_lc);
1749     map { $self->{$_} = $ref->{$_} } keys %$ref;
1750     $sth->finish;
1751
1752   } else {
1753
1754     # get date
1755     $query = qq|SELECT current_date AS transdate,
1756                 d.curr AS currencies, d.closedto, d.revtrans,
1757                   (SELECT c.accno FROM chart c
1758                    WHERE d.fxgain_accno_id = c.id) AS fxgain_accno,
1759                   (SELECT c.accno FROM chart c
1760                    WHERE d.fxloss_accno_id = c.id) AS fxloss_accno
1761                 FROM defaults d|;
1762     $sth = $dbh->prepare($query);
1763     $sth->execute || $self->dberror($query);
1764
1765     $ref = $sth->fetchrow_hashref(NAME_lc);
1766     map { $self->{$_} = $ref->{$_} } keys %$ref;
1767     $sth->finish;
1768
1769     if ($self->{"$self->{vc}_id"}) {
1770
1771       # only setup currency
1772       ($self->{currency}) = split /:/, $self->{currencies};
1773
1774     } else {
1775
1776       $self->lastname_used($dbh, $myconfig, $table, $module);
1777
1778       my $fld = ($table eq 'customer') ? 'buy' : 'sell';
1779
1780       # get exchangerate for currency
1781       $self->{exchangerate} =
1782         $self->get_exchangerate($dbh, $self->{currency}, $self->{transdate},
1783                                 $fld);
1784
1785     }
1786
1787   }
1788
1789   $dbh->disconnect;
1790
1791   $main::lxdebug->leave_sub();
1792 }
1793
1794 sub lastname_used {
1795   $main::lxdebug->enter_sub();
1796
1797   my ($self, $dbh, $myconfig, $table, $module) = @_;
1798
1799   my $arap  = ($table eq 'customer') ? "ar" : "ap";
1800   my $where = "1 = 1";
1801
1802   if ($self->{type} =~ /_order/) {
1803     $arap  = 'oe';
1804     $where = "quotation = '0'";
1805   }
1806   if ($self->{type} =~ /_quotation/) {
1807     $arap  = 'oe';
1808     $where = "quotation = '1'";
1809   }
1810
1811   my $query = qq|SELECT MAX(id) FROM $arap
1812                               WHERE $where
1813                               AND ${table}_id > 0|;
1814   my $sth = $dbh->prepare($query);
1815   $sth->execute || $self->dberror($query);
1816
1817   my ($trans_id) = $sth->fetchrow_array;
1818   $sth->finish;
1819
1820   $trans_id *= 1;
1821   $query = qq|SELECT ct.name, a.curr, a.${table}_id,
1822               current_date + ct.terms AS duedate, a.department_id,
1823               d.description AS department
1824               FROM $arap a
1825               JOIN $table ct ON (a.${table}_id = ct.id)
1826               LEFT JOIN department d ON (a.department_id = d.id)
1827               WHERE a.id = $trans_id|;
1828   $sth = $dbh->prepare($query);
1829   $sth->execute || $self->dberror($query);
1830
1831   ($self->{$table},  $self->{currency},      $self->{"${table}_id"},
1832    $self->{duedate}, $self->{department_id}, $self->{department})
1833     = $sth->fetchrow_array;
1834   $sth->finish;
1835
1836   $main::lxdebug->leave_sub();
1837 }
1838
1839 sub current_date {
1840   $main::lxdebug->enter_sub();
1841
1842   my ($self, $myconfig, $thisdate, $days) = @_;
1843
1844   my $dbh = $self->dbconnect($myconfig);
1845   my ($sth, $query);
1846
1847   $days *= 1;
1848   if ($thisdate) {
1849     my $dateformat = $myconfig->{dateformat};
1850     $dateformat .= "yy" if $myconfig->{dateformat} !~ /^y/;
1851
1852     $query = qq|SELECT to_date('$thisdate', '$dateformat') + $days AS thisdate
1853                 FROM defaults|;
1854     $sth = $dbh->prepare($query);
1855     $sth->execute || $self->dberror($query);
1856   } else {
1857     $query = qq|SELECT current_date AS thisdate
1858                 FROM defaults|;
1859     $sth = $dbh->prepare($query);
1860     $sth->execute || $self->dberror($query);
1861   }
1862
1863   ($thisdate) = $sth->fetchrow_array;
1864   $sth->finish;
1865
1866   $dbh->disconnect;
1867
1868   $main::lxdebug->leave_sub();
1869
1870   return $thisdate;
1871 }
1872
1873 sub like {
1874   $main::lxdebug->enter_sub();
1875
1876   my ($self, $string) = @_;
1877
1878   if ($string !~ /%/) {
1879     $string = "%$string%";
1880   }
1881
1882   $string =~ s/\'/\'\'/g;
1883
1884   $main::lxdebug->leave_sub();
1885
1886   return $string;
1887 }
1888
1889 sub redo_rows {
1890   $main::lxdebug->enter_sub();
1891
1892   my ($self, $flds, $new, $count, $numrows) = @_;
1893
1894   my @ndx = ();
1895
1896   map { push @ndx, { num => $new->[$_ - 1]->{runningnumber}, ndx => $_ } }
1897     (1 .. $count);
1898
1899   my $i = 0;
1900
1901   # fill rows
1902   foreach my $item (sort { $a->{num} <=> $b->{num} } @ndx) {
1903     $i++;
1904     $j = $item->{ndx} - 1;
1905     map { $self->{"${_}_$i"} = $new->[$j]->{$_} } @{$flds};
1906   }
1907
1908   # delete empty rows
1909   for $i ($count + 1 .. $numrows) {
1910     map { delete $self->{"${_}_$i"} } @{$flds};
1911   }
1912
1913   $main::lxdebug->leave_sub();
1914 }
1915
1916 sub update_status {
1917   $main::lxdebug->enter_sub();
1918
1919   my ($self, $myconfig) = @_;
1920
1921   my ($i, $id);
1922
1923   my $dbh = $self->dbconnect_noauto($myconfig);
1924
1925   my $query = qq|DELETE FROM status
1926                  WHERE formname = '$self->{formname}'
1927                  AND trans_id = ?|;
1928   my $sth = $dbh->prepare($query) || $self->dberror($query);
1929
1930   if ($self->{formname} =~ /(check|receipt)/) {
1931     for $i (1 .. $self->{rowcount}) {
1932       $sth->execute($self->{"id_$i"} * 1) || $self->dberror($query);
1933       $sth->finish;
1934     }
1935   } else {
1936     $sth->execute($self->{id}) || $self->dberror($query);
1937     $sth->finish;
1938   }
1939
1940   my $printed = ($self->{printed} =~ /$self->{formname}/) ? "1" : "0";
1941   my $emailed = ($self->{emailed} =~ /$self->{formname}/) ? "1" : "0";
1942
1943   my %queued = split / /, $self->{queued};
1944
1945   if ($self->{formname} =~ /(check|receipt)/) {
1946
1947     # this is a check or receipt, add one entry for each lineitem
1948     my ($accno) = split /--/, $self->{account};
1949     $query = qq|INSERT INTO status (trans_id, printed, spoolfile, formname,
1950                 chart_id) VALUES (?, '$printed',
1951                 '$queued{$self->{formname}}', '$self->{prinform}',
1952                 (SELECT c.id FROM chart c WHERE c.accno = '$accno'))|;
1953     $sth = $dbh->prepare($query) || $self->dberror($query);
1954
1955     for $i (1 .. $self->{rowcount}) {
1956       if ($self->{"checked_$i"}) {
1957         $sth->execute($self->{"id_$i"}) || $self->dberror($query);
1958         $sth->finish;
1959       }
1960     }
1961   } else {
1962     $query = qq|INSERT INTO status (trans_id, printed, emailed,
1963                 spoolfile, formname)
1964                 VALUES ($self->{id}, '$printed', '$emailed',
1965                 '$queued{$self->{formname}}', '$self->{formname}')|;
1966     $dbh->do($query) || $self->dberror($query);
1967   }
1968
1969   $dbh->commit;
1970   $dbh->disconnect;
1971
1972   $main::lxdebug->leave_sub();
1973 }
1974
1975 sub save_status {
1976   $main::lxdebug->enter_sub();
1977
1978   my ($self, $dbh) = @_;
1979
1980   my ($query, $printed, $emailed);
1981
1982   my $formnames  = $self->{printed};
1983   my $emailforms = $self->{emailed};
1984
1985   my $query = qq|DELETE FROM status
1986                  WHERE formname = '$self->{formname}'
1987                  AND trans_id = $self->{id}|;
1988   $dbh->do($query) || $self->dberror($query);
1989
1990   # this only applies to the forms
1991   # checks and receipts are posted when printed or queued
1992
1993   if ($self->{queued}) {
1994     my %queued = split / /, $self->{queued};
1995
1996     foreach my $formname (keys %queued) {
1997       $printed = ($self->{printed} =~ /$self->{formname}/) ? "1" : "0";
1998       $emailed = ($self->{emailed} =~ /$self->{formname}/) ? "1" : "0";
1999
2000       $query = qq|INSERT INTO status (trans_id, printed, emailed,
2001                   spoolfile, formname)
2002                   VALUES ($self->{id}, '$printed', '$emailed',
2003                   '$queued{$formname}', '$formname')|;
2004       $dbh->do($query) || $self->dberror($query);
2005
2006       $formnames  =~ s/$self->{formname}//;
2007       $emailforms =~ s/$self->{formname}//;
2008
2009     }
2010   }
2011
2012   # save printed, emailed info
2013   $formnames  =~ s/^ +//g;
2014   $emailforms =~ s/^ +//g;
2015
2016   my %status = ();
2017   map { $status{$_}{printed} = 1 } split / +/, $formnames;
2018   map { $status{$_}{emailed} = 1 } split / +/, $emailforms;
2019
2020   foreach my $formname (keys %status) {
2021     $printed = ($formnames  =~ /$self->{formname}/) ? "1" : "0";
2022     $emailed = ($emailforms =~ /$self->{formname}/) ? "1" : "0";
2023
2024     $query = qq|INSERT INTO status (trans_id, printed, emailed, formname)
2025                 VALUES ($self->{id}, '$printed', '$emailed', '$formname')|;
2026     $dbh->do($query) || $self->dberror($query);
2027   }
2028
2029   $main::lxdebug->leave_sub();
2030 }
2031
2032 sub update_defaults {
2033   $main::lxdebug->enter_sub();
2034
2035   my ($self, $myconfig, $fld) = @_;
2036
2037   my $dbh   = $self->dbconnect_noauto($myconfig);
2038   my $query = qq|SELECT $fld FROM defaults FOR UPDATE|;
2039   my $sth   = $dbh->prepare($query);
2040
2041   $sth->execute || $self->dberror($query);
2042   my ($var) = $sth->fetchrow_array;
2043   $sth->finish;
2044
2045   $var++;
2046
2047   $query = qq|UPDATE defaults
2048               SET $fld = '$var'|;
2049   $dbh->do($query) || $form->dberror($query);
2050
2051   $dbh->commit;
2052   $dbh->disconnect;
2053
2054   $main::lxdebug->leave_sub();
2055
2056   return $var;
2057 }
2058
2059 sub update_business {
2060   $main::lxdebug->enter_sub();
2061
2062   my ($self, $myconfig, $business_id) = @_;
2063
2064   my $dbh   = $self->dbconnect_noauto($myconfig);
2065   my $query =
2066     qq|SELECT customernumberinit FROM business  WHERE id=$business_id FOR UPDATE|;
2067   my $sth = $dbh->prepare($query);
2068
2069   $sth->execute || $self->dberror($query);
2070   my ($var) = $sth->fetchrow_array;
2071   $sth->finish;
2072   if ($var ne "") {
2073     $var++;
2074   }
2075   $query = qq|UPDATE business
2076               SET customernumberinit = '$var' WHERE id=$business_id|;
2077   $dbh->do($query) || $form->dberror($query);
2078
2079   $dbh->commit;
2080   $dbh->disconnect;
2081
2082   $main::lxdebug->leave_sub();
2083
2084   return $var;
2085 }
2086
2087 sub get_salesman {
2088   $main::lxdebug->enter_sub();
2089
2090   my ($self, $myconfig, $salesman) = @_;
2091
2092   my $dbh   = $self->dbconnect($myconfig);
2093   my $query =
2094     qq|SELECT id, name FROM customer  WHERE (customernumber ilike '%$salesman%' OR name ilike '%$salesman%') AND business_id in (SELECT id from business WHERE salesman)|;
2095   my $sth = $dbh->prepare($query);
2096   $sth->execute || $self->dberror($query);
2097
2098   my $i = 0;
2099   while ($ref = $sth->fetchrow_hashref(NAME_lc)) {
2100     push(@{ $self->{salesman_list} }, $ref);
2101     $i++;
2102   }
2103   $dbh->commit;
2104   $main::lxdebug->leave_sub();
2105
2106   return $i;
2107 }
2108
2109 sub get_partsgroup {
2110   $main::lxdebug->enter_sub();
2111
2112   my ($self, $myconfig, $p) = @_;
2113
2114   my $dbh = $self->dbconnect($myconfig);
2115
2116   my $query = qq|SELECT DISTINCT pg.id, pg.partsgroup
2117                  FROM partsgroup pg
2118                  JOIN parts p ON (p.partsgroup_id = pg.id)|;
2119
2120   if ($p->{searchitems} eq 'part') {
2121     $query .= qq|
2122                  WHERE p.inventory_accno_id > 0|;
2123   }
2124   if ($p->{searchitems} eq 'service') {
2125     $query .= qq|
2126                  WHERE p.inventory_accno_id IS NULL|;
2127   }
2128   if ($p->{searchitems} eq 'assembly') {
2129     $query .= qq|
2130                  WHERE p.assembly = '1'|;
2131   }
2132   if ($p->{searchitems} eq 'labor') {
2133     $query .= qq|
2134                  WHERE p.inventory_accno_id > 0 AND p.income_accno_id IS NULL|;
2135   }
2136
2137   $query .= qq|
2138                  ORDER BY partsgroup|;
2139
2140   if ($p->{all}) {
2141     $query = qq|SELECT id, partsgroup FROM partsgroup
2142                 ORDER BY partsgroup|;
2143   }
2144
2145   if ($p->{language_code}) {
2146     $query = qq|SELECT DISTINCT pg.id, pg.partsgroup,
2147                 t.description AS translation
2148                 FROM partsgroup pg
2149                 JOIN parts p ON (p.partsgroup_id = pg.id)
2150                 LEFT JOIN translation t ON (t.trans_id = pg.id AND t.language_code = '$p->{language_code}')
2151                 ORDER BY translation|;
2152   }
2153
2154   my $sth = $dbh->prepare($query);
2155   $sth->execute || $self->dberror($query);
2156
2157   $self->{all_partsgroup} = ();
2158   while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
2159     push @{ $self->{all_partsgroup} }, $ref;
2160   }
2161   $sth->finish;
2162   $dbh->disconnect;
2163   $main::lxdebug->leave_sub();
2164 }
2165
2166 sub get_pricegroup {
2167   $main::lxdebug->enter_sub();
2168
2169   my ($self, $myconfig, $p) = @_;
2170
2171   my $dbh = $self->dbconnect($myconfig);
2172
2173   my $query = qq|SELECT p.id, p.pricegroup
2174                  FROM pricegroup p|;
2175
2176   $query .= qq|
2177                  ORDER BY pricegroup|;
2178
2179   if ($p->{all}) {
2180     $query = qq|SELECT id, pricegroup FROM pricegroup
2181                 ORDER BY pricegroup|;
2182   }
2183
2184   my $sth = $dbh->prepare($query);
2185   $sth->execute || $self->dberror($query);
2186
2187   $self->{all_pricegroup} = ();
2188   while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
2189     push @{ $self->{all_pricegroup} }, $ref;
2190   }
2191   $sth->finish;
2192   $dbh->disconnect;
2193
2194   $main::lxdebug->leave_sub();
2195 }
2196
2197 sub audittrail {
2198   my ($self, $dbh, $myconfig, $audittrail) = @_;
2199
2200   # table, $reference, $formname, $action, $id, $transdate) = @_;
2201
2202   my $query;
2203   my $rv;
2204   my $disconnect;
2205
2206   if (!$dbh) {
2207     $dbh        = $self->dbconnect($myconfig);
2208     $disconnect = 1;
2209   }
2210
2211   # if we have an id add audittrail, otherwise get a new timestamp
2212
2213   if ($audittrail->{id}) {
2214
2215     $query = qq|SELECT audittrail FROM defaults|;
2216
2217     if ($dbh->selectrow_array($query)) {
2218       my ($null, $employee_id) = $self->get_employee($dbh);
2219
2220       if ($self->{audittrail} && !$myconfig) {
2221         chop $self->{audittrail};
2222
2223         my @a = split /\|/, $self->{audittrail};
2224         my %newtrail = ();
2225         my $key;
2226         my $i;
2227         my @flds = qw(tablename reference formname action transdate);
2228
2229         # put into hash and remove dups
2230         while (@a) {
2231           $key = "$a[2]$a[3]";
2232           $i   = 0;
2233           $newtrail{$key} = { map { $_ => $a[$i++] } @flds };
2234           splice @a, 0, 5;
2235         }
2236
2237         $query = qq|INSERT INTO audittrail (trans_id, tablename, reference,
2238                     formname, action, employee_id, transdate)
2239                     VALUES ($audittrail->{id}, ?, ?,
2240                     ?, ?, $employee_id, ?)|;
2241         my $sth = $dbh->prepare($query) || $self->dberror($query);
2242
2243         foreach $key (
2244           sort {
2245             $newtrail{$a}{transdate} cmp $newtrail{$b}{transdate}
2246           } keys %newtrail
2247           ) {
2248           $i = 1;
2249           for (@flds) { $sth->bind_param($i++, $newtrail{$key}{$_}) }
2250
2251           $sth->execute || $self->dberror;
2252           $sth->finish;
2253         }
2254       }
2255
2256       if ($audittrail->{transdate}) {
2257         $query = qq|INSERT INTO audittrail (trans_id, tablename, reference,
2258                     formname, action, employee_id, transdate) VALUES (
2259                     $audittrail->{id}, '$audittrail->{tablename}', |
2260           . $dbh->quote($audittrail->{reference}) . qq|,
2261                     '$audittrail->{formname}', '$audittrail->{action}',
2262                     $employee_id, '$audittrail->{transdate}')|;
2263       } else {
2264         $query = qq|INSERT INTO audittrail (trans_id, tablename, reference,
2265                     formname, action, employee_id) VALUES ($audittrail->{id},
2266                     '$audittrail->{tablename}', |
2267           . $dbh->quote($audittrail->{reference}) . qq|,
2268                     '$audittrail->{formname}', '$audittrail->{action}',
2269                     $employee_id)|;
2270       }
2271       $dbh->do($query);
2272     }
2273   } else {
2274
2275     $query = qq|SELECT current_timestamp FROM defaults|;
2276     my ($timestamp) = $dbh->selectrow_array($query);
2277
2278     $rv =
2279       "$audittrail->{tablename}|$audittrail->{reference}|$audittrail->{formname}|$audittrail->{action}|$timestamp|";
2280   }
2281
2282   $dbh->disconnect if $disconnect;
2283
2284   $rv;
2285
2286 }
2287
2288 package Locale;
2289
2290 sub new {
2291   $main::lxdebug->enter_sub();
2292
2293   my ($type, $country, $NLS_file) = @_;
2294   my $self = {};
2295
2296   %self = ();
2297   if ($country && -d "locale/$country") {
2298     $self->{countrycode} = $country;
2299     eval { require "locale/$country/$NLS_file"; };
2300   }
2301
2302   $self->{NLS_file} = $NLS_file;
2303
2304   push @{ $self->{LONG_MONTH} },
2305     ("January",   "February", "March",    "April",
2306      "May ",      "June",     "July",     "August",
2307      "September", "October",  "November", "December");
2308   push @{ $self->{SHORT_MONTH} },
2309     (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec));
2310
2311   $main::lxdebug->leave_sub();
2312
2313   bless $self, $type;
2314 }
2315
2316 sub text {
2317   my ($self, $text) = @_;
2318
2319   return (exists $self{texts}{$text}) ? $self{texts}{$text} : $text;
2320 }
2321
2322 sub findsub {
2323   $main::lxdebug->enter_sub();
2324
2325   my ($self, $text) = @_;
2326
2327   if (exists $self{subs}{$text}) {
2328     $text = $self{subs}{$text};
2329   } else {
2330     if ($self->{countrycode} && $self->{NLS_file}) {
2331       Form->error(
2332          "$text not defined in locale/$self->{countrycode}/$self->{NLS_file}");
2333     }
2334   }
2335
2336   $main::lxdebug->leave_sub();
2337
2338   return $text;
2339 }
2340
2341 sub date {
2342   $main::lxdebug->enter_sub();
2343
2344   my ($self, $myconfig, $date, $longformat) = @_;
2345
2346   my $longdate  = "";
2347   my $longmonth = ($longformat) ? 'LONG_MONTH' : 'SHORT_MONTH';
2348
2349   if ($date) {
2350
2351     # get separator
2352     $spc = $myconfig->{dateformat};
2353     $spc =~ s/\w//g;
2354     $spc = substr($spc, 1, 1);
2355
2356     if ($date =~ /\D/) {
2357       if ($myconfig->{dateformat} =~ /^yy/) {
2358         ($yy, $mm, $dd) = split /\D/, $date;
2359       }
2360       if ($myconfig->{dateformat} =~ /^mm/) {
2361         ($mm, $dd, $yy) = split /\D/, $date;
2362       }
2363       if ($myconfig->{dateformat} =~ /^dd/) {
2364         ($dd, $mm, $yy) = split /\D/, $date;
2365       }
2366     } else {
2367       $date = substr($date, 2);
2368       ($yy, $mm, $dd) = ($date =~ /(..)(..)(..)/);
2369     }
2370
2371     $dd *= 1;
2372     $mm--;
2373     $yy = ($yy < 70) ? $yy + 2000 : $yy;
2374     $yy = ($yy >= 70 && $yy <= 99) ? $yy + 1900 : $yy;
2375
2376     if ($myconfig->{dateformat} =~ /^dd/) {
2377       if (defined $longformat && $longformat == 0) {
2378         $mm++;
2379         $dd = "0$dd" if ($dd < 10);
2380         $mm = "0$mm" if ($mm < 10);
2381         $longdate = "$dd$spc$mm$spc$yy";
2382       } else {
2383         $longdate = "$dd";
2384         $longdate .= ($spc eq '.') ? ". " : " ";
2385         $longdate .= &text($self, $self->{$longmonth}[$mm]) . " $yy";
2386       }
2387     } elsif ($myconfig->{dateformat} eq "yyyy-mm-dd") {
2388
2389       # Use German syntax with the ISO date style "yyyy-mm-dd" because
2390       # Lx-Office is mainly used in Germany or German speaking countries.
2391       if (defined $longformat && $longformat == 0) {
2392         $mm++;
2393         $dd = "0$dd" if ($dd < 10);
2394         $mm = "0$mm" if ($mm < 10);
2395         $longdate = "$yy-$mm-$dd";
2396       } else {
2397         $longdate = "$dd. ";
2398         $longdate .= &text($self, $self->{$longmonth}[$mm]) . " $yy";
2399       }
2400     } else {
2401       if (defined $longformat && $longformat == 0) {
2402         $mm++;
2403         $dd = "0$dd" if ($dd < 10);
2404         $mm = "0$mm" if ($mm < 10);
2405         $longdate = "$mm$spc$dd$spc$yy";
2406       } else {
2407         $longdate = &text($self, $self->{$longmonth}[$mm]) . " $dd, $yy";
2408       }
2409     }
2410
2411   }
2412
2413   $main::lxdebug->leave_sub();
2414
2415   return $longdate;
2416 }
2417
2418 sub parse_date {
2419   $main::lxdebug->enter_sub();
2420
2421   my ($self, $myconfig, $date, $longformat) = @_;
2422
2423   unless ($date) {
2424     $main::lxdebug->leave_sub();
2425     return ();
2426   }
2427
2428   # get separator
2429   $spc = $myconfig->{dateformat};
2430   $spc =~ s/\w//g;
2431   $spc = substr($spc, 1, 1);
2432
2433   if ($date =~ /\D/) {
2434     if ($myconfig->{dateformat} =~ /^yy/) {
2435       ($yy, $mm, $dd) = split /\D/, $date;
2436     } elsif ($myconfig->{dateformat} =~ /^mm/) {
2437       ($mm, $dd, $yy) = split /\D/, $date;
2438     } elsif ($myconfig->{dateformat} =~ /^dd/) {
2439       ($dd, $mm, $yy) = split /\D/, $date;
2440     }
2441   } else {
2442     $date = substr($date, 2);
2443     ($yy, $mm, $dd) = ($date =~ /(..)(..)(..)/);
2444   }
2445
2446   $dd *= 1;
2447   $mm *= 1;
2448   $yy = ($yy < 70) ? $yy + 2000 : $yy;
2449   $yy = ($yy >= 70 && $yy <= 99) ? $yy + 1900 : $yy;
2450
2451   $main::lxdebug->leave_sub();
2452   return ($yy, $mm, $dd);
2453 }
2454
2455 1;