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