S/GL: Typo im POD (t/006spellcheck.t)
[kivitendo-erp.git] / SL / GL.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) 2001
10 #
11 #  Author: Dieter Simader
12 #   Email: dsimader@sql-ledger.org
13 #     Web: http://www.sql-ledger.org
14 #
15 #  Contributors:
16 #
17 # This program is free software; you can redistribute it and/or modify
18 # it under the terms of the GNU General Public License as published by
19 # the Free Software Foundation; either version 2 of the License, or
20 # (at your option) any later version.
21 #
22 # This program is distributed in the hope that it will be useful,
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25 # GNU General Public License for more details.
26 # You should have received a copy of the GNU General Public License
27 # along with this program; if not, write to the Free Software
28 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
29 # MA 02110-1335, USA.
30 #======================================================================
31 #
32 # General ledger backend code
33 #
34 # CHANGE LOG:
35 #   DS. 2000-07-04  Created
36 #   DS. 2001-06-12  Changed relations from accno to chart_id
37 #
38 #======================================================================
39
40 package GL;
41
42 use List::Util qw(first);
43
44 use Data::Dumper;
45 use SL::DATEV qw(:CONSTANTS);
46 use SL::DBUtils;
47 use SL::DB::Chart;
48 use SL::DB::Draft;
49 use SL::Util qw(trim);
50 use SL::DB;
51
52 use strict;
53
54 sub delete_transaction {
55   my ($self, $myconfig, $form) = @_;
56   $main::lxdebug->enter_sub();
57
58   SL::DB->client->with_transaction(sub {
59     do_query($form, SL::DB->client->dbh, qq|DELETE FROM gl WHERE id = ?|, conv_i($form->{id}));
60     1;
61   }) or do { die SL::DB->client->error };
62
63   $main::lxdebug->leave_sub();
64 }
65
66 sub post_transaction {
67   my ($self, $myconfig, $form) = @_;
68   $main::lxdebug->enter_sub();
69
70   my $rc = SL::DB->client->with_transaction(\&_post_transaction, $self, $myconfig, $form);
71
72   $::lxdebug->leave_sub;
73   return $rc;
74 }
75
76 sub _post_transaction {
77   my ($self, $myconfig, $form) = @_;
78   $main::lxdebug->enter_sub();
79
80   my ($debit, $credit) = (0, 0);
81   my $project_id;
82
83   my $i;
84
85   my $dbh = SL::DB->client->dbh;
86
87   # post the transaction
88   # make up a unique handle and store in reference field
89   # then retrieve the record based on the unique handle to get the id
90   # replace the reference field with the actual variable
91   # add records to acc_trans
92
93   # if there is a $form->{id} replace the old transaction
94   # delete all acc_trans entries and add the new ones
95
96   if (!$form->{taxincluded}) {
97     $form->{taxincluded} = 0;
98   }
99
100   my ($query, $sth, @values, $taxkey, $rate, $posted);
101
102   if ($form->{id}) {
103
104     # delete individual transactions
105     $query = qq|DELETE FROM acc_trans WHERE trans_id = ?|;
106     @values = (conv_i($form->{id}));
107     do_query($form, $dbh, $query, @values);
108
109   } else {
110     $query = qq|SELECT nextval('glid')|;
111     ($form->{id}) = selectrow_query($form, $dbh, $query);
112
113     $query =
114       qq|INSERT INTO gl (id, employee_id) | .
115       qq|VALUES (?, (SELECT id FROM employee WHERE login = ?))|;
116     @values = ($form->{id}, $::myconfig{login});
117     do_query($form, $dbh, $query, @values);
118   }
119
120   $form->{ob_transaction} *= 1;
121   $form->{cb_transaction} *= 1;
122
123   $query =
124     qq|UPDATE gl SET
125          reference = ?, description = ?, notes = ?,
126          transdate = ?, deliverydate = ?, department_id = ?, taxincluded = ?,
127          storno = ?, storno_id = ?, ob_transaction = ?, cb_transaction = ?
128        WHERE id = ?|;
129
130   @values = ($form->{reference}, $form->{description}, $form->{notes},
131              conv_date($form->{transdate}), conv_date($form->{deliverydate}), conv_i($form->{department_id}), $form->{taxincluded} ? 't' : 'f',
132              $form->{storno} ? 't' : 'f', conv_i($form->{storno_id}), $form->{ob_transaction} ? 't' : 'f', $form->{cb_transaction} ? 't' : 'f',
133              conv_i($form->{id}));
134   do_query($form, $dbh, $query, @values);
135
136   # insert acc_trans transactions
137   for $i (1 .. $form->{rowcount}) {
138     ($form->{"tax_id_$i"}) = split(/--/, $form->{"taxchart_$i"});
139     if ($form->{"tax_id_$i"} ne "") {
140       $query = qq|SELECT taxkey, rate FROM tax WHERE id = ?|;
141       ($taxkey, $rate) = selectrow_query($form, $dbh, $query, conv_i($form->{"tax_id_$i"}));
142     }
143
144     my $amount = 0;
145     my $debit  = $form->{"debit_$i"};
146     my $credit = $form->{"credit_$i"};
147     my $tax    = $form->{"tax_$i"};
148
149     if ($credit) {
150       $amount = $credit;
151       $posted = 0;
152     }
153     if ($debit) {
154       $amount = $debit * -1;
155       $tax    = $tax * -1;
156       $posted = 0;
157     }
158
159     $project_id = conv_i($form->{"project_id_$i"});
160
161     # if there is an amount, add the record
162     if ($amount != 0) {
163       $query =
164         qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate,
165                                   source, memo, project_id, taxkey, ob_transaction, cb_transaction, tax_id, chart_link)
166            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, (SELECT link FROM chart WHERE id = ?))|;
167       @values = (conv_i($form->{id}), $form->{"accno_id_$i"}, $amount, conv_date($form->{transdate}),
168                  $form->{"source_$i"}, $form->{"memo_$i"}, $project_id, $taxkey, $form->{ob_transaction} ? 't' : 'f', $form->{cb_transaction} ? 't' : 'f', conv_i($form->{"tax_id_$i"}), $form->{"accno_id_$i"});
169       do_query($form, $dbh, $query, @values);
170     }
171
172     if ($tax != 0) {
173       # add taxentry
174       $query =
175         qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate,
176                                   source, memo, project_id, taxkey, tax_id, chart_link)
177            VALUES (?, (SELECT chart_id FROM tax WHERE id = ?),
178                    ?, ?, ?, ?, ?, ?, ?, (SELECT link
179                                          FROM chart
180                                          WHERE id = (SELECT chart_id
181                                                      FROM tax
182                                                      WHERE id = ?)))|;
183       @values = (conv_i($form->{id}), conv_i($form->{"tax_id_$i"}),
184                  $tax, conv_date($form->{transdate}), $form->{"source_$i"},
185                  $form->{"memo_$i"}, $project_id, $taxkey, conv_i($form->{"tax_id_$i"}), conv_i($form->{"tax_id_$i"}));
186       do_query($form, $dbh, $query, @values);
187     }
188   }
189
190   if ($form->{storno} && $form->{storno_id}) {
191     do_query($form, $dbh, qq|UPDATE gl SET storno = 't' WHERE id = ?|, conv_i($form->{storno_id}));
192   }
193
194   if ($form->{draft_id}) {
195     SL::DB::Manager::Draft->delete_all(where => [ id => delete($form->{draft_id}) ]);
196   }
197
198   # safety check datev export
199   if ($::instance_conf->get_datev_check_on_gl_transaction) {
200
201     # create datev object
202     my $datev = SL::DATEV->new(
203       dbh        => $dbh,
204       trans_id   => $form->{id},
205     );
206
207     $datev->generate_datev_data;
208
209     if ($datev->errors) {
210       die join "\n", $::locale->text('DATEV check returned errors:'), $datev->errors;
211     }
212   }
213
214   return 1;
215 }
216
217 sub all_transactions {
218   my ($self, $myconfig, $form) = @_;
219   $main::lxdebug->enter_sub();
220
221   my $dbh = SL::DB->client->dbh;
222   my ($query, $sth, $source, $null, $space);
223
224   my ($glwhere, $arwhere, $apwhere) = ("1 = 1", "1 = 1", "1 = 1");
225   my (@glvalues, @arvalues, @apvalues);
226
227   if ($form->{reference}) {
228     $glwhere .= qq| AND g.reference ILIKE ?|;
229     $arwhere .= qq| AND a.invnumber ILIKE ?|;
230     $apwhere .= qq| AND a.invnumber ILIKE ?|;
231     push(@glvalues, like($form->{reference}));
232     push(@arvalues, like($form->{reference}));
233     push(@apvalues, like($form->{reference}));
234   }
235
236   if ($form->{department_id}) {
237     $glwhere .= qq| AND g.department_id = ?|;
238     $arwhere .= qq| AND a.department_id = ?|;
239     $apwhere .= qq| AND a.department_id = ?|;
240     push(@glvalues, $form->{department_id});
241     push(@arvalues, $form->{department_id});
242     push(@apvalues, $form->{department_id});
243   }
244
245   if ($form->{source}) {
246     $glwhere .= " AND ac.trans_id IN (SELECT trans_id from acc_trans WHERE source ILIKE ?)";
247     $arwhere .= " AND ac.trans_id IN (SELECT trans_id from acc_trans WHERE source ILIKE ?)";
248     $apwhere .= " AND ac.trans_id IN (SELECT trans_id from acc_trans WHERE source ILIKE ?)";
249     push(@glvalues, like($form->{source}));
250     push(@arvalues, like($form->{source}));
251     push(@apvalues, like($form->{source}));
252   }
253
254   # default Datumseinschränkung falls nicht oder falsch Ã¼bergeben (sollte nie passieren)
255   $form->{datesort} = 'transdate' unless $form->{datesort} =~ /^(transdate|gldate)$/;
256
257   if (trim($form->{datefrom})) {
258     $glwhere .= " AND ac.$form->{datesort} >= ?";
259     $arwhere .= " AND ac.$form->{datesort} >= ?";
260     $apwhere .= " AND ac.$form->{datesort} >= ?";
261     push(@glvalues, trim($form->{datefrom}));
262     push(@arvalues, trim($form->{datefrom}));
263     push(@apvalues, trim($form->{datefrom}));
264   }
265
266   if (trim($form->{dateto})) {
267     $glwhere .= " AND ac.$form->{datesort} <= ?";
268     $arwhere .= " AND ac.$form->{datesort} <= ?";
269     $apwhere .= " AND ac.$form->{datesort} <= ?";
270     push(@glvalues, trim($form->{dateto}));
271     push(@arvalues, trim($form->{dateto}));
272     push(@apvalues, trim($form->{dateto}));
273   }
274
275   if (trim($form->{description})) {
276     $glwhere .= " AND g.description ILIKE ?";
277     $arwhere .= " AND ct.name ILIKE ?";
278     $apwhere .= " AND ct.name ILIKE ?";
279     push(@glvalues, like($form->{description}));
280     push(@arvalues, like($form->{description}));
281     push(@apvalues, like($form->{description}));
282   }
283
284   if ($form->{employee_id}) {
285     $glwhere .= " AND g.employee_id = ? ";
286     $arwhere .= " AND a.employee_id = ? ";
287     $apwhere .= " AND a.employee_id = ? ";
288     push(@glvalues, conv_i($form->{employee_id}));
289     push(@arvalues, conv_i($form->{employee_id}));
290     push(@apvalues, conv_i($form->{employee_id}));
291   }
292
293   if (trim($form->{notes})) {
294     $glwhere .= " AND g.notes ILIKE ?";
295     $arwhere .= " AND a.notes ILIKE ?";
296     $apwhere .= " AND a.notes ILIKE ?";
297     push(@glvalues, like($form->{notes}));
298     push(@arvalues, like($form->{notes}));
299     push(@apvalues, like($form->{notes}));
300   }
301
302   if ($form->{accno}) {
303     $glwhere .= " AND c.accno = '$form->{accno}'";
304     $arwhere .= " AND c.accno = '$form->{accno}'";
305     $apwhere .= " AND c.accno = '$form->{accno}'";
306   }
307
308   if ($form->{category} ne 'X') {
309     $glwhere .= qq| AND g.id in (SELECT trans_id FROM acc_trans ac2 WHERE ac2.chart_id IN (SELECT id FROM chart c2 WHERE c2.category = ?))|;
310     $arwhere .= qq| AND a.id in (SELECT trans_id FROM acc_trans ac2 WHERE ac2.chart_id IN (SELECT id FROM chart c2 WHERE c2.category = ?))|;
311     $apwhere .= qq| AND a.id in (SELECT trans_id FROM acc_trans ac2 WHERE ac2.chart_id IN (SELECT id FROM chart c2 WHERE c2.category = ?))|;
312     push(@glvalues, $form->{category});
313     push(@arvalues, $form->{category});
314     push(@apvalues, $form->{category});
315   }
316
317   if ($form->{project_id}) {
318     $glwhere .= qq| AND g.id IN (SELECT DISTINCT trans_id FROM acc_trans WHERE project_id = ?)|;
319     $arwhere .=
320       qq| AND ((a.globalproject_id = ?) OR
321                (a.id IN (SELECT DISTINCT trans_id FROM acc_trans WHERE project_id = ?)))|;
322     $apwhere .=
323       qq| AND ((a.globalproject_id = ?) OR
324                (a.id IN (SELECT DISTINCT trans_id FROM acc_trans WHERE project_id = ?)))|;
325     my $project_id = conv_i($form->{project_id});
326     push(@glvalues, $project_id);
327     push(@arvalues, $project_id, $project_id);
328     push(@apvalues, $project_id, $project_id);
329   }
330
331   my ($project_columns, $project_join);
332   if ($form->{"l_projectnumbers"}) {
333     $project_columns = qq|, ac.project_id, pr.projectnumber|;
334     $project_join = qq|LEFT JOIN project pr ON (ac.project_id = pr.id)|;
335   }
336
337   if ($form->{accno}) {
338     # get category for account
339     $query = qq|SELECT category FROM chart WHERE accno = ?|;
340     ($form->{ml}) = selectrow_query($form, $dbh, $query, $form->{accno});
341
342     if ($form->{datefrom}) {
343       $query =
344         qq|SELECT SUM(ac.amount)
345            FROM acc_trans ac
346            LEFT JOIN chart c ON (ac.chart_id = c.id)
347            WHERE (c.accno = ?) AND (ac.$form->{datesort} < ?)|;
348       ($form->{balance}) = selectrow_query($form, $dbh, $query, $form->{accno}, conv_date($form->{datefrom}));
349     }
350   }
351
352   my %sort_columns =  (
353     'id'           => [ qw(id)                   ],
354     'transdate'    => [ qw(transdate id)         ],
355     'gldate'       => [ qw(gldate id)         ],
356     'reference'    => [ qw(lower_reference id)   ],
357     'description'  => [ qw(lower_description id) ],
358     'accno'        => [ qw(accno transdate id)   ],
359     );
360   my %lowered_columns =  (
361     'reference'       => { 'gl' => 'g.reference',   'arap' => 'a.invnumber', },
362     'source'          => { 'gl' => 'ac.source',     'arap' => 'ac.source',   },
363     'description'     => { 'gl' => 'g.description', 'arap' => 'ct.name',     },
364     );
365
366   # sortdir = sort direction (ascending or descending)
367   my $sortdir   = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
368   my $sortkey   = $sort_columns{$form->{sort}} ? $form->{sort} : $form->{datesort};  # default used to be transdate
369   my $sortorder = join ', ', map { "$_ $sortdir" } @{ $sort_columns{$sortkey} };
370
371   my %columns_for_sorting = ( 'gl' => '', 'arap' => '', );
372   foreach my $spec (@{ $sort_columns{$sortkey} }) {
373     next if ($spec !~ m/^lower_(.*)$/);
374
375     my $column = $1;
376     map { $columns_for_sorting{$_} .= sprintf(', lower(%s) AS lower_%s', $lowered_columns{$column}->{$_}, $column) } qw(gl arap);
377   }
378
379   $query =
380     qq|SELECT
381         ac.acc_trans_id, g.id, 'gl' AS type, FALSE AS invoice, g.reference, ac.taxkey, c.link,
382         g.description, ac.transdate, ac.gldate, ac.source, ac.trans_id,
383         ac.amount, c.accno, g.notes, t.chart_id,
384         CASE WHEN (COALESCE(e.name, '') = '') THEN e.login ELSE e.name END AS employee
385         $project_columns
386         $columns_for_sorting{gl}
387       FROM gl g
388       LEFT JOIN employee e ON (g.employee_id = e.id),
389       acc_trans ac $project_join, chart c
390       LEFT JOIN tax t ON (t.chart_id = c.id)
391       WHERE $glwhere
392         AND (ac.chart_id = c.id)
393         AND (g.id = ac.trans_id)
394
395       UNION
396
397       SELECT ac.acc_trans_id, a.id, 'ar' AS type, a.invoice, a.invnumber, ac.taxkey, c.link,
398         ct.name, ac.transdate, ac.gldate, ac.source, ac.trans_id,
399         ac.amount, c.accno, a.notes, t.chart_id,
400         CASE WHEN (COALESCE(e.name, '') = '') THEN e.login ELSE e.name END AS employee
401         $project_columns
402         $columns_for_sorting{arap}
403       FROM ar a
404       LEFT JOIN employee e ON (a.employee_id = e.id),
405       acc_trans ac $project_join, customer ct, chart c
406       LEFT JOIN tax t ON (t.chart_id=c.id)
407       WHERE $arwhere
408         AND (ac.chart_id = c.id)
409         AND (a.customer_id = ct.id)
410         AND (a.id = ac.trans_id)
411
412       UNION
413
414       SELECT ac.acc_trans_id, a.id, 'ap' AS type, a.invoice, a.invnumber, ac.taxkey, c.link,
415         ct.name, ac.transdate, ac.gldate, ac.source, ac.trans_id,
416         ac.amount, c.accno, a.notes, t.chart_id,
417         CASE WHEN (COALESCE(e.name, '') = '') THEN e.login ELSE e.name END AS employee
418         $project_columns
419         $columns_for_sorting{arap}
420       FROM ap a
421       LEFT JOIN employee e ON (a.employee_id = e.id),
422       acc_trans ac $project_join, vendor ct, chart c
423       LEFT JOIN tax t ON (t.chart_id=c.id)
424       WHERE $apwhere
425         AND (ac.chart_id = c.id)
426         AND (a.vendor_id = ct.id)
427         AND (a.id = ac.trans_id)
428
429       ORDER BY $sortorder, acc_trans_id $sortdir|;
430 #      ORDER BY gldate DESC, id DESC, acc_trans_id DESC
431
432   my @values = (@glvalues, @arvalues, @apvalues);
433
434   # Show all $query in Debuglevel LXDebug::QUERY
435   my $callingdetails = (caller (0))[3];
436   dump_query(LXDebug->QUERY(), "$callingdetails", $query, @values);
437
438   $sth = prepare_execute_query($form, $dbh, $query, @values);
439   my $trans_id  = "";
440   my $trans_id2 = "";
441   my $balance;
442
443   my ($i, $j, $k, $l, $ref, $ref2);
444
445   $form->{GL} = [];
446   while (my $ref0 = $sth->fetchrow_hashref("NAME_lc")) {
447
448     $trans_id = $ref0->{id};
449
450     my $source = $ref0->{source};
451     undef($ref0->{source});
452
453     if ($trans_id != $trans_id2) { # first line of a booking
454
455       if ($trans_id2) {
456         push(@{ $form->{GL} }, $ref);
457         $balance = 0;
458       }
459
460       $ref       = $ref0;
461       $trans_id2 = $ref->{id};
462
463       # gl
464       if ($ref->{type} eq "gl") {
465         $ref->{module} = "gl";
466       }
467
468       # ap
469       if ($ref->{type} eq "ap") {
470         if ($ref->{invoice}) {
471           $ref->{module} = "ir";
472         } else {
473           $ref->{module} = "ap";
474         }
475       }
476
477       # ar
478       if ($ref->{type} eq "ar") {
479         if ($ref->{invoice}) {
480           $ref->{module} = "is";
481         } else {
482           $ref->{module} = "ar";
483         }
484       }
485
486       $ref->{"projectnumbers"} = {};
487       $ref->{"projectnumbers"}->{$ref->{"projectnumber"}} = 1 if ($ref->{"projectnumber"});
488
489       $balance = $ref->{amount};
490
491       # Linenumbers of General Ledger
492       $k       = 0; # Debit      # AP      # Soll
493       $l       = 0; # Credit     # AR      # Haben
494       $i       = 0; # Debit Tax  # AP_tax  # VSt
495       $j       = 0; # Credit Tax # AR_tax  # USt
496
497       if ($ref->{chart_id} > 0) { # all tax accounts first line, no line increasing
498         if ($ref->{amount} < 0) {
499           if ($ref->{link} =~ /AR_tax/) {
500             $ref->{credit_tax}{$j}       = $ref->{amount};
501             $ref->{credit_tax_accno}{$j} = $ref->{accno};
502          }
503           if ($ref->{link} =~ /AP_tax/) {
504             $ref->{debit_tax}{$i}       = $ref->{amount} * -1;
505             $ref->{debit_tax_accno}{$i} = $ref->{accno};
506           }
507         } else {
508           if ($ref->{link} =~ /AR_tax/) {
509             $ref->{credit_tax}{$j}       = $ref->{amount};
510             $ref->{credit_tax_accno}{$j} = $ref->{accno};
511           }
512           if ($ref->{link} =~ /AP_tax/) {
513             $ref->{debit_tax}{$i}       = $ref->{amount} * -1;
514             $ref->{debit_tax_accno}{$i} = $ref->{accno};
515           }
516         }
517       } else { #all other accounts first line
518
519         if ($ref->{amount} < 0) {
520           $ref->{debit}{$k}        = $ref->{amount} * -1;
521           $ref->{debit_accno}{$k}  = $ref->{accno};
522           $ref->{debit_taxkey}{$k} = $ref->{taxkey};
523           $ref->{ac_transdate}{$k} = $ref->{transdate};
524           $ref->{source}{$k}       = $source;
525         } else {
526           $ref->{credit}{$l}        = $ref->{amount} * 1;
527           $ref->{credit_accno}{$l}  = $ref->{accno};
528           $ref->{credit_taxkey}{$l} = $ref->{taxkey};
529           $ref->{ac_transdate}{$l}  = $ref->{transdate};
530           $ref->{source}{$l}        = $source;
531         }
532       }
533
534     } else { # following lines of a booking, line increasing
535
536       $ref2      = $ref0;
537 #      $trans_old = $trans_id2;   # doesn't seem to be used anymore
538       $trans_id2 = $ref2->{id};
539
540       $balance =
541         (int($balance * 100000) + int(100000 * $ref2->{amount})) / 100000;
542
543       $ref->{"projectnumbers"}->{$ref2->{"projectnumber"}} = 1 if ($ref2->{"projectnumber"});
544
545       if ($ref2->{chart_id} > 0) { # all tax accounts, following lines
546         if ($ref2->{amount} < 0) {
547           if ($ref2->{link} =~ /AR_tax/) {
548             if ($ref->{credit_tax_accno}{$j} ne "") {
549               $j++;
550             }
551             $ref->{credit_tax}{$j}       = $ref2->{amount};
552             $ref->{credit_tax_accno}{$j} = $ref2->{accno};
553           }
554           if ($ref2->{link} =~ /AP_tax/) {
555             if ($ref->{debit_tax_accno}{$i} ne "") {
556               $i++;
557             }
558             $ref->{debit_tax}{$i}       = $ref2->{amount} * -1;
559             $ref->{debit_tax_accno}{$i} = $ref2->{accno};
560           }
561         } else {
562           if ($ref2->{link} =~ /AR_tax/) {
563             if ($ref->{credit_tax_accno}{$j} ne "") {
564               $j++;
565             }
566             $ref->{credit_tax}{$j}       = $ref2->{amount};
567             $ref->{credit_tax_accno}{$j} = $ref2->{accno};
568           }
569           if ($ref2->{link} =~ /AP_tax/) {
570             if ($ref->{debit_tax_accno}{$i} ne "") {
571               $i++;
572             }
573             $ref->{debit_tax}{$i}       = $ref2->{amount} * -1;
574             $ref->{debit_tax_accno}{$i} = $ref2->{accno};
575           }
576         }
577       } else { # all other accounts, following lines
578         if ($ref2->{amount} < 0) {
579           if ($ref->{debit_accno}{$k} ne "") {
580             $k++;
581           }
582           if ($ref->{source}{$k} ne "") {
583             $space = " | ";
584           } else {
585             $space = "";
586           }
587           $ref->{debit}{$k}        = $ref2->{amount} * - 1;
588           $ref->{debit_accno}{$k}  = $ref2->{accno};
589           $ref->{debit_taxkey}{$k} = $ref2->{taxkey};
590           $ref->{ac_transdate}{$k} = $ref2->{transdate};
591           $ref->{source}{$k}       = $source . $space . $ref->{source}{$k};
592         } else {
593           if ($ref->{credit_accno}{$l} ne "") {
594             $l++;
595           }
596           if ($ref->{source}{$l} ne "") {
597             $space = " | ";
598           } else {
599             $space = "";
600           }
601           $ref->{credit}{$l}        = $ref2->{amount};
602           $ref->{credit_accno}{$l}  = $ref2->{accno};
603           $ref->{credit_taxkey}{$l} = $ref2->{taxkey};
604           $ref->{ac_transdate}{$l}  = $ref2->{transdate};
605           $ref->{source}{$l}        = $ref->{source}{$l} . $space . $source;
606         }
607       }
608     }
609   }
610
611   push @{ $form->{GL} }, $ref;
612   $sth->finish;
613
614   if ($form->{accno}) {
615     $query = qq|SELECT c.description FROM chart c WHERE c.accno = ?|;
616     ($form->{account_description}) = selectrow_query($form, $dbh, $query, $form->{accno});
617   }
618
619   $main::lxdebug->leave_sub();
620 }
621
622 sub transaction {
623   my ($self, $myconfig, $form) = @_;
624   $main::lxdebug->enter_sub();
625
626   my ($query, $sth, $ref, @values);
627
628   my $dbh = SL::DB->client->dbh;
629
630   $query = qq|SELECT closedto, revtrans FROM defaults|;
631   ($form->{closedto}, $form->{revtrans}) = selectrow_query($form, $dbh, $query);
632
633   $query = qq|SELECT id, gldate
634               FROM gl
635               WHERE id = (SELECT max(id) FROM gl)|;
636   ($form->{previous_id}, $form->{previous_gldate}) = selectrow_query($form, $dbh, $query);
637
638   if ($form->{id}) {
639     $query =
640       qq|SELECT g.reference, g.description, g.notes, g.transdate, g.deliverydate,
641            g.storno, g.storno_id,
642            g.department_id, d.description AS department,
643            e.name AS employee, g.taxincluded, g.gldate,
644          g.ob_transaction, g.cb_transaction
645          FROM gl g
646          LEFT JOIN department d ON (d.id = g.department_id)
647          LEFT JOIN employee e ON (e.id = g.employee_id)
648          WHERE g.id = ?|;
649     $ref = selectfirst_hashref_query($form, $dbh, $query, conv_i($form->{id}));
650     map { $form->{$_} = $ref->{$_} } keys %$ref;
651
652     # retrieve individual rows
653     $query =
654       qq|SELECT c.accno, t.taxkey AS accnotaxkey, a.amount, a.memo, a.source,
655            a.transdate, a.cleared, a.project_id, p.projectnumber,
656            a.taxkey, t.rate AS taxrate, t.id, a.chart_id,
657            (SELECT c1.accno
658             FROM chart c1, tax t1
659             WHERE (t1.id = t.id) AND (c1.id = t.chart_id)) AS taxaccno,
660            (SELECT tk.tax_id
661             FROM taxkeys tk
662             WHERE (tk.chart_id = a.chart_id) AND (tk.startdate <= a.transdate)
663             ORDER BY tk.startdate desc LIMIT 1) AS tax_id
664          FROM acc_trans a
665          JOIN chart c ON (c.id = a.chart_id)
666          LEFT JOIN project p ON (p.id = a.project_id)
667          LEFT JOIN tax t ON (t.id = a.tax_id)
668          WHERE (a.trans_id = ?)
669            AND (a.fx_transaction = '0')
670          ORDER BY a.acc_trans_id, a.transdate|;
671     $form->{GL} = selectall_hashref_query($form, $dbh, $query, conv_i($form->{id}));
672
673   } else {
674     $query =
675       qq|SELECT COALESCE(
676            (SELECT transdate
677             FROM gl
678             WHERE id = (SELECT MAX(id) FROM gl)
679             LIMIT 1),
680            current_date)|;
681     ($form->{transdate}) = selectrow_query($form, $dbh, $query);
682   }
683
684   # get tax description
685   $query = qq|SELECT * FROM tax ORDER BY taxkey|;
686   $form->{TAX} = selectall_hashref_query($form, $dbh, $query);
687
688   # get chart of accounts
689   $query =
690     qq|SELECT c.accno, c.description, c.link, tk.taxkey_id, tk.tax_id
691        FROM chart c
692        LEFT JOIN taxkeys tk ON (tk.id =
693          (SELECT id
694           FROM taxkeys
695           WHERE (taxkeys.chart_id = c.id)
696             AND (startdate <= ?)
697           ORDER BY startdate DESC
698           LIMIT 1))
699        ORDER BY c.accno|;
700   $form->{chart} = selectall_hashref_query($form, $dbh, $query, conv_date($form->{transdate}));
701
702   $main::lxdebug->leave_sub();
703 }
704
705 sub storno {
706   my ($self, $form, $myconfig, $id) = @_;
707   $main::lxdebug->enter_sub();
708
709   my $rc = SL::DB->client->with_transaction(\&_storno, $self, $form, $myconfig, $id);
710
711   $::lxdebug->leave_sub;
712   return $rc;
713 }
714
715 sub _storno {
716   my ($self, $form, $myconfig, $id) = @_;
717
718   my ($query, $new_id, $storno_row, $acc_trans_rows);
719   my $dbh = SL::DB->client->dbh;
720
721   $query = qq|SELECT nextval('glid')|;
722   ($new_id) = selectrow_query($form, $dbh, $query);
723
724   $query = qq|SELECT * FROM gl WHERE id = ?|;
725   $storno_row = selectfirst_hashref_query($form, $dbh, $query, $id);
726
727   $storno_row->{id}          = $new_id;
728   $storno_row->{storno_id}   = $id;
729   $storno_row->{storno}      = 't';
730   $storno_row->{reference}   = 'Storno-' . $storno_row->{reference};
731
732   $query = qq|SELECT id FROM employee WHERE login = ?|;
733   my ($employee_id) = selectrow_query($form, $dbh, $query, $::myconfig{login});
734   $storno_row->{employee_id} = $employee_id;
735
736   delete @$storno_row{qw(itime mtime gldate)};
737
738   $query = sprintf 'INSERT INTO gl (%s) VALUES (%s)', join(', ', keys %$storno_row), join(', ', map '?', values %$storno_row);
739   do_query($form, $dbh, $query, (values %$storno_row));
740
741   $query = qq|UPDATE gl SET storno = 't' WHERE id = ?|;
742   do_query($form, $dbh, $query, $id);
743
744   # now copy acc_trans entries
745   $query = qq|SELECT * FROM acc_trans WHERE trans_id = ?|;
746   my $rowref = selectall_hashref_query($form, $dbh, $query, $id);
747
748   for my $row (@$rowref) {
749     delete @$row{qw(itime mtime acc_trans_id gldate)};
750     $query = sprintf 'INSERT INTO acc_trans (%s) VALUES (%s)', join(', ', keys %$row), join(', ', map '?', values %$row);
751     $row->{trans_id}   = $new_id;
752     $row->{amount}    *= -1;
753     do_query($form, $dbh, $query, (values %$row));
754   }
755
756   return 1;
757 }
758
759 sub get_chart_balances {
760   my ($self, @chart_ids) = @_;
761
762   return () unless @chart_ids;
763
764   my $placeholders = join ', ', ('?') x scalar(@chart_ids);
765   my $query = qq|SELECT chart_id, SUM(amount) AS sum
766                  FROM acc_trans
767                  WHERE chart_id IN (${placeholders})
768                  GROUP BY chart_id|;
769
770   my %balances = selectall_as_map($::form, $::form->get_standard_dbh(\%::myconfig), $query, 'chart_id', 'sum', @chart_ids);
771
772   return %balances;
773 }
774
775 sub get_active_taxes_for_chart {
776   my ($self, $chart_id, $transdate, $tax_id) = @_;
777
778   my $chart         = SL::DB::Chart->new(id => $chart_id)->load;
779   my $active_taxkey = $chart->get_active_taxkey($transdate);
780
781   my $where = [ chart_categories => { like => '%' . $chart->category . '%' } ];
782
783   if ( defined $tax_id && $tax_id >= 0 ) {
784     $where = [ or => [ chart_categories => { like => '%' . $chart->category . '%' },
785                        id               => $tax_id
786                      ]
787              ];
788   }
789
790   my $taxes         = SL::DB::Manager::Tax->get_all(
791     where   => $where,
792     sort_by => 'taxkey, rate',
793   );
794
795   my $default_tax            = first { $active_taxkey->tax_id == $_->id } @{ $taxes };
796   $default_tax->{is_default} = 1 if $default_tax;
797
798   return @{ $taxes };
799 }
800
801 1;
802
803 __END__
804
805 =pod
806
807 =encoding utf8
808
809 =head1 NAME
810
811 SL::GL - some useful GL functions
812
813 =head1 FUNCTIONS
814
815 =over 4
816
817 =item C<get_active_taxes_for_chart> $transdate $tax_id
818
819 Returns a list of valid taxes for a certain chart.
820
821 If the optional param transdate exists one entry in the returning list
822 may get the attribute C<is_default> for this specific tax-dependent date.
823 The possible entries are filtered by the charttype of the tax, i.e. only taxes
824 whose chart_categories match the category of the chart will be shown.
825
826 In the case of existing records, e.g. when opening an old ar record, due to
827 changes in the configurations the desired tax might not be available in the
828 dropdown anymore. If we are loading an old record and know its tax_id (from
829 acc_trans), we can pass $tax_id as the third parameter and be sure that the
830 original tax always appears in the dropdown.
831
832 The functions returns an array which may be used for building dropdowns in ar/ap/gl code.
833 =back
834
835 =head1 TODO
836
837 =head1 BUGS
838
839 Nothing here yet.
840
841 =head1 AUTHOR
842
843 G. Richardson E<lt>grichardson@kivitec.de<gt>
844
845 =cut