4c892e764713f886230f733968c1be7a88222ebf
[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        WHERE id = ?|;
129
130   @values = ($form->{reference}, $form->{description}, $form->{notes},
131              conv_date($form->{transdate}), conv_date($form->{deliverydate}), conv_date($form->{tax_point}), 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   my ($arap_globalproject_columns, $arap_globalproject_join);
333   my ($gl_globalproject_columns);
334   if ($form->{"l_projectnumbers"}) {
335     $project_columns            = qq|, ac.project_id, pr.projectnumber|;
336     $project_join               = qq|LEFT JOIN project pr ON (ac.project_id = pr.id)|;
337     $arap_globalproject_columns = qq|, a.globalproject_id, globalpr.projectnumber AS globalprojectnumber|;
338     $arap_globalproject_join    = qq|LEFT JOIN project globalpr ON (a.globalproject_id = globalpr.id)|;
339     $gl_globalproject_columns   = qq|, NULL AS globalproject_id, '' AS globalprojectnumber|;
340   }
341
342   if ($form->{accno}) {
343     # get category for account
344     $query = qq|SELECT category FROM chart WHERE accno = ?|;
345     ($form->{ml}) = selectrow_query($form, $dbh, $query, $form->{accno});
346
347     if ($form->{datefrom}) {
348       $query =
349         qq|SELECT SUM(ac.amount)
350            FROM acc_trans ac
351            LEFT JOIN chart c ON (ac.chart_id = c.id)
352            WHERE (c.accno = ?) AND (ac.$form->{datesort} < ?)|;
353       ($form->{balance}) = selectrow_query($form, $dbh, $query, $form->{accno}, conv_date($form->{datefrom}));
354     }
355   }
356
357   my %sort_columns =  (
358     'id'           => [ qw(id)                   ],
359     'transdate'    => [ qw(transdate id)         ],
360     'gldate'       => [ qw(gldate id)         ],
361     'reference'    => [ qw(lower_reference id)   ],
362     'description'  => [ qw(lower_description id) ],
363     'accno'        => [ qw(accno transdate id)   ],
364     'department'   => [ qw(department transdate id)   ],
365     );
366   my %lowered_columns =  (
367     'reference'       => { 'gl' => 'g.reference',   'arap' => 'a.invnumber', },
368     'source'          => { 'gl' => 'ac.source',     'arap' => 'ac.source',   },
369     'description'     => { 'gl' => 'g.description', 'arap' => 'ct.name',     },
370     );
371
372   # sortdir = sort direction (ascending or descending)
373   my $sortdir   = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
374   my $sortkey   = $sort_columns{$form->{sort}} ? $form->{sort} : $form->{datesort};  # default used to be transdate
375   my $sortorder = join ', ', map { "$_ $sortdir" } @{ $sort_columns{$sortkey} };
376
377   my %columns_for_sorting = ( 'gl' => '', 'arap' => '', );
378   foreach my $spec (@{ $sort_columns{$sortkey} }) {
379     next if ($spec !~ m/^lower_(.*)$/);
380
381     my $column = $1;
382     map { $columns_for_sorting{$_} .= sprintf(', lower(%s) AS lower_%s', $lowered_columns{$column}->{$_}, $column) } qw(gl arap);
383   }
384
385   $query =
386     qq|SELECT
387         ac.acc_trans_id, g.id, 'gl' AS type, FALSE AS invoice, g.reference, ac.taxkey, c.link,
388         g.description, ac.transdate, ac.gldate, ac.source, ac.trans_id,
389         ac.amount, c.accno, g.notes, t.chart_id,
390         d.description AS department,
391         CASE WHEN (COALESCE(e.name, '') = '') THEN e.login ELSE e.name END AS employee
392         $project_columns $gl_globalproject_columns
393         $columns_for_sorting{gl}
394       FROM gl g
395       LEFT JOIN employee e ON (g.employee_id = e.id)
396       LEFT JOIN department d ON (g.department_id = d.id),
397       acc_trans ac $project_join, chart c
398       LEFT JOIN tax t ON (t.chart_id = c.id)
399       WHERE $glwhere
400         AND (ac.chart_id = c.id)
401         AND (g.id = ac.trans_id)
402
403       UNION
404
405       SELECT ac.acc_trans_id, a.id, 'ar' AS type, a.invoice, a.invnumber, ac.taxkey, c.link,
406         ct.name, ac.transdate, ac.gldate, ac.source, ac.trans_id,
407         ac.amount, c.accno, a.notes, t.chart_id,
408         d.description AS department,
409         CASE WHEN (COALESCE(e.name, '') = '') THEN e.login ELSE e.name END AS employee
410         $project_columns $arap_globalproject_columns
411         $columns_for_sorting{arap}
412       FROM ar a
413       LEFT JOIN employee e ON (a.employee_id = e.id)
414       LEFT JOIN department d ON (a.department_id = d.id)
415       $arap_globalproject_join,
416       acc_trans ac $project_join, customer ct, chart c
417       LEFT JOIN tax t ON (t.chart_id=c.id)
418       WHERE $arwhere
419         AND (ac.chart_id = c.id)
420         AND (a.customer_id = ct.id)
421         AND (a.id = ac.trans_id)
422
423       UNION
424
425       SELECT ac.acc_trans_id, a.id, 'ap' AS type, a.invoice, a.invnumber, ac.taxkey, c.link,
426         ct.name, ac.transdate, ac.gldate, ac.source, ac.trans_id,
427         ac.amount, c.accno, a.notes, t.chart_id,
428         d.description AS department,
429         CASE WHEN (COALESCE(e.name, '') = '') THEN e.login ELSE e.name END AS employee
430         $project_columns $arap_globalproject_columns
431         $columns_for_sorting{arap}
432       FROM ap a
433       LEFT JOIN employee e ON (a.employee_id = e.id)
434       LEFT JOIN department d ON (a.department_id = d.id)
435       $arap_globalproject_join,
436       acc_trans ac $project_join, vendor ct, chart c
437       LEFT JOIN tax t ON (t.chart_id=c.id)
438       WHERE $apwhere
439         AND (ac.chart_id = c.id)
440         AND (a.vendor_id = ct.id)
441         AND (a.id = ac.trans_id)
442
443       ORDER BY $sortorder, acc_trans_id $sortdir|;
444 #      ORDER BY gldate DESC, id DESC, acc_trans_id DESC
445
446   my @values = (@glvalues, @arvalues, @apvalues);
447
448   # Show all $query in Debuglevel LXDebug::QUERY
449   my $callingdetails = (caller (0))[3];
450   dump_query(LXDebug->QUERY(), "$callingdetails", $query, @values);
451
452   $sth = prepare_execute_query($form, $dbh, $query, @values);
453   my $trans_id  = "";
454   my $trans_id2 = "";
455   my $balance;
456
457   my ($i, $j, $k, $l, $ref, $ref2);
458
459   $form->{GL} = [];
460   while (my $ref0 = $sth->fetchrow_hashref("NAME_lc")) {
461
462     $trans_id = $ref0->{id};
463
464     my $source = $ref0->{source};
465     undef($ref0->{source});
466
467     if ($trans_id != $trans_id2) { # first line of a booking
468
469       if ($trans_id2) {
470         push(@{ $form->{GL} }, $ref);
471         $balance = 0;
472       }
473
474       $ref       = $ref0;
475       $trans_id2 = $ref->{id};
476
477       # gl
478       if ($ref->{type} eq "gl") {
479         $ref->{module} = "gl";
480       }
481
482       # ap
483       if ($ref->{type} eq "ap") {
484         if ($ref->{invoice}) {
485           $ref->{module} = "ir";
486         } else {
487           $ref->{module} = "ap";
488         }
489       }
490
491       # ar
492       if ($ref->{type} eq "ar") {
493         if ($ref->{invoice}) {
494           $ref->{module} = "is";
495         } else {
496           $ref->{module} = "ar";
497         }
498       }
499
500       $ref->{"projectnumbers"} = {};
501       $ref->{"projectnumbers"}->{$ref->{"projectnumber"}}       = 1 if ($ref->{"projectnumber"});
502       $ref->{"projectnumbers"}->{$ref->{"globalprojectnumber"}} = 1 if ($ref->{"globalprojectnumber"});
503
504       $balance = $ref->{amount};
505
506       # Linenumbers of General Ledger
507       $k       = 0; # Debit      # AP      # Soll
508       $l       = 0; # Credit     # AR      # Haben
509       $i       = 0; # Debit Tax  # AP_tax  # VSt
510       $j       = 0; # Credit Tax # AR_tax  # USt
511
512       if ($ref->{chart_id} > 0) { # all tax accounts first line, no line increasing
513         if ($ref->{amount} < 0) {
514           if ($ref->{link} =~ /AR_tax/) {
515             $ref->{credit_tax}{$j}       = $ref->{amount};
516             $ref->{credit_tax_accno}{$j} = $ref->{accno};
517          }
518           if ($ref->{link} =~ /AP_tax/) {
519             $ref->{debit_tax}{$i}       = $ref->{amount} * -1;
520             $ref->{debit_tax_accno}{$i} = $ref->{accno};
521           }
522         } else {
523           if ($ref->{link} =~ /AR_tax/) {
524             $ref->{credit_tax}{$j}       = $ref->{amount};
525             $ref->{credit_tax_accno}{$j} = $ref->{accno};
526           }
527           if ($ref->{link} =~ /AP_tax/) {
528             $ref->{debit_tax}{$i}       = $ref->{amount} * -1;
529             $ref->{debit_tax_accno}{$i} = $ref->{accno};
530           }
531         }
532       } else { #all other accounts first line
533
534         if ($ref->{amount} < 0) {
535           $ref->{debit}{$k}        = $ref->{amount} * -1;
536           $ref->{debit_accno}{$k}  = $ref->{accno};
537           $ref->{debit_taxkey}{$k} = $ref->{taxkey};
538           $ref->{ac_transdate}{$k} = $ref->{transdate};
539           $ref->{source}{$k}       = $source;
540         } else {
541           $ref->{credit}{$l}        = $ref->{amount} * 1;
542           $ref->{credit_accno}{$l}  = $ref->{accno};
543           $ref->{credit_taxkey}{$l} = $ref->{taxkey};
544           $ref->{ac_transdate}{$l}  = $ref->{transdate};
545           $ref->{source}{$l}        = $source;
546         }
547       }
548
549     } else { # following lines of a booking, line increasing
550
551       $ref2      = $ref0;
552 #      $trans_old = $trans_id2;   # doesn't seem to be used anymore
553       $trans_id2 = $ref2->{id};
554
555       $balance =
556         (int($balance * 100000) + int(100000 * $ref2->{amount})) / 100000;
557
558       $ref->{"projectnumbers"}->{$ref2->{"projectnumber"}}       = 1 if ($ref2->{"projectnumber"});
559       $ref->{"projectnumbers"}->{$ref2->{"globalprojectnumber"}} = 1 if ($ref2->{"globalprojectnumber"});
560
561       if ($ref2->{chart_id} > 0) { # all tax accounts, following lines
562         if ($ref2->{amount} < 0) {
563           if ($ref2->{link} =~ /AR_tax/) {
564             if ($ref->{credit_tax_accno}{$j} ne "") {
565               $j++;
566             }
567             $ref->{credit_tax}{$j}       = $ref2->{amount};
568             $ref->{credit_tax_accno}{$j} = $ref2->{accno};
569           }
570           if ($ref2->{link} =~ /AP_tax/) {
571             if ($ref->{debit_tax_accno}{$i} ne "") {
572               $i++;
573             }
574             $ref->{debit_tax}{$i}       = $ref2->{amount} * -1;
575             $ref->{debit_tax_accno}{$i} = $ref2->{accno};
576           }
577         } else {
578           if ($ref2->{link} =~ /AR_tax/) {
579             if ($ref->{credit_tax_accno}{$j} ne "") {
580               $j++;
581             }
582             $ref->{credit_tax}{$j}       = $ref2->{amount};
583             $ref->{credit_tax_accno}{$j} = $ref2->{accno};
584           }
585           if ($ref2->{link} =~ /AP_tax/) {
586             if ($ref->{debit_tax_accno}{$i} ne "") {
587               $i++;
588             }
589             $ref->{debit_tax}{$i}       = $ref2->{amount} * -1;
590             $ref->{debit_tax_accno}{$i} = $ref2->{accno};
591           }
592         }
593       } else { # all other accounts, following lines
594         if ($ref2->{amount} < 0) {
595           if ($ref->{debit_accno}{$k} ne "") {
596             $k++;
597           }
598           if ($ref->{source}{$k} ne "") {
599             $space = " | ";
600           } else {
601             $space = "";
602           }
603           $ref->{debit}{$k}        = $ref2->{amount} * - 1;
604           $ref->{debit_accno}{$k}  = $ref2->{accno};
605           $ref->{debit_taxkey}{$k} = $ref2->{taxkey};
606           $ref->{ac_transdate}{$k} = $ref2->{transdate};
607           $ref->{source}{$k}       = $source . $space . $ref->{source}{$k};
608         } else {
609           if ($ref->{credit_accno}{$l} ne "") {
610             $l++;
611           }
612           if ($ref->{source}{$l} ne "") {
613             $space = " | ";
614           } else {
615             $space = "";
616           }
617           $ref->{credit}{$l}        = $ref2->{amount};
618           $ref->{credit_accno}{$l}  = $ref2->{accno};
619           $ref->{credit_taxkey}{$l} = $ref2->{taxkey};
620           $ref->{ac_transdate}{$l}  = $ref2->{transdate};
621           $ref->{source}{$l}        = $ref->{source}{$l} . $space . $source;
622         }
623       }
624     }
625   }
626
627   push @{ $form->{GL} }, $ref;
628   $sth->finish;
629
630   if ($form->{accno}) {
631     $query = qq|SELECT c.description FROM chart c WHERE c.accno = ?|;
632     ($form->{account_description}) = selectrow_query($form, $dbh, $query, $form->{accno});
633   }
634
635   $main::lxdebug->leave_sub();
636 }
637
638 sub transaction {
639   my ($self, $myconfig, $form) = @_;
640   $main::lxdebug->enter_sub();
641
642   my ($query, $sth, $ref, @values);
643
644   my $dbh = SL::DB->client->dbh;
645
646   $query = qq|SELECT closedto, revtrans FROM defaults|;
647   ($form->{closedto}, $form->{revtrans}) = selectrow_query($form, $dbh, $query);
648
649   $query = qq|SELECT id, gldate
650               FROM gl
651               WHERE id = (SELECT max(id) FROM gl)|;
652   ($form->{previous_id}, $form->{previous_gldate}) = selectrow_query($form, $dbh, $query);
653
654   if ($form->{id}) {
655     $query =
656       qq|SELECT g.reference, g.description, g.notes, g.transdate, g.deliverydate, g.tax_point,
657            g.storno, g.storno_id,
658            g.department_id, d.description AS department,
659            e.name AS employee, g.taxincluded, g.gldate,
660          g.ob_transaction, g.cb_transaction
661          FROM gl g
662          LEFT JOIN department d ON (d.id = g.department_id)
663          LEFT JOIN employee e ON (e.id = g.employee_id)
664          WHERE g.id = ?|;
665     $ref = selectfirst_hashref_query($form, $dbh, $query, conv_i($form->{id}));
666     map { $form->{$_} = $ref->{$_} } keys %$ref;
667
668     # retrieve individual rows
669     $query =
670       qq|SELECT c.accno, t.taxkey AS accnotaxkey, a.amount, a.memo, a.source,
671            a.transdate, a.cleared, a.project_id, p.projectnumber,
672            a.taxkey, t.rate AS taxrate, t.id, a.chart_id,
673            (SELECT c1.accno
674             FROM chart c1, tax t1
675             WHERE (t1.id = t.id) AND (c1.id = t.chart_id)) AS taxaccno,
676            (SELECT tk.tax_id
677             FROM taxkeys tk
678             WHERE (tk.chart_id = a.chart_id) AND (tk.startdate <= a.transdate)
679             ORDER BY tk.startdate desc LIMIT 1) AS tax_id
680          FROM acc_trans a
681          JOIN chart c ON (c.id = a.chart_id)
682          LEFT JOIN project p ON (p.id = a.project_id)
683          LEFT JOIN tax t ON (t.id = a.tax_id)
684          WHERE (a.trans_id = ?)
685            AND (a.fx_transaction = '0')
686          ORDER BY a.acc_trans_id, a.transdate|;
687     $form->{GL} = selectall_hashref_query($form, $dbh, $query, conv_i($form->{id}));
688
689   } else {
690     $query =
691       qq|SELECT COALESCE(
692            (SELECT transdate
693             FROM gl
694             WHERE id = (SELECT MAX(id) FROM gl)
695             LIMIT 1),
696            current_date)|;
697     ($form->{transdate}) = selectrow_query($form, $dbh, $query);
698   }
699
700   # get tax description
701   $query = qq|SELECT * FROM tax ORDER BY taxkey|;
702   $form->{TAX} = selectall_hashref_query($form, $dbh, $query);
703
704   # get chart of accounts
705   $query =
706     qq|SELECT c.accno, c.description, c.link, tk.taxkey_id, tk.tax_id
707        FROM chart c
708        LEFT JOIN taxkeys tk ON (tk.id =
709          (SELECT id
710           FROM taxkeys
711           WHERE (taxkeys.chart_id = c.id)
712             AND (startdate <= ?)
713           ORDER BY startdate DESC
714           LIMIT 1))
715        ORDER BY c.accno|;
716   $form->{chart} = selectall_hashref_query($form, $dbh, $query, conv_date($form->{transdate}));
717
718   $main::lxdebug->leave_sub();
719 }
720
721 sub storno {
722   my ($self, $form, $myconfig, $id) = @_;
723   $main::lxdebug->enter_sub();
724
725   my $rc = SL::DB->client->with_transaction(\&_storno, $self, $form, $myconfig, $id);
726
727   $::lxdebug->leave_sub;
728   return $rc;
729 }
730
731 sub _storno {
732   my ($self, $form, $myconfig, $id) = @_;
733
734   my ($query, $new_id, $storno_row, $acc_trans_rows);
735   my $dbh = SL::DB->client->dbh;
736
737   $query = qq|SELECT nextval('glid')|;
738   ($new_id) = selectrow_query($form, $dbh, $query);
739
740   $query = qq|SELECT * FROM gl WHERE id = ?|;
741   $storno_row = selectfirst_hashref_query($form, $dbh, $query, $id);
742
743   $storno_row->{id}          = $new_id;
744   $storno_row->{storno_id}   = $id;
745   $storno_row->{storno}      = 't';
746   $storno_row->{reference}   = 'Storno-' . $storno_row->{reference};
747
748   $query = qq|SELECT id FROM employee WHERE login = ?|;
749   my ($employee_id) = selectrow_query($form, $dbh, $query, $::myconfig{login});
750   $storno_row->{employee_id} = $employee_id;
751
752   delete @$storno_row{qw(itime mtime gldate)};
753
754   $query = sprintf 'INSERT INTO gl (%s) VALUES (%s)', join(', ', keys %$storno_row), join(', ', map '?', values %$storno_row);
755   do_query($form, $dbh, $query, (values %$storno_row));
756
757   $query = qq|UPDATE gl SET storno = 't' WHERE id = ?|;
758   do_query($form, $dbh, $query, $id);
759
760   # now copy acc_trans entries
761   $query = qq|SELECT * FROM acc_trans WHERE trans_id = ?|;
762   my $rowref = selectall_hashref_query($form, $dbh, $query, $id);
763
764   for my $row (@$rowref) {
765     delete @$row{qw(itime mtime acc_trans_id gldate)};
766     $query = sprintf 'INSERT INTO acc_trans (%s) VALUES (%s)', join(', ', keys %$row), join(', ', map '?', values %$row);
767     $row->{trans_id}   = $new_id;
768     $row->{amount}    *= -1;
769     do_query($form, $dbh, $query, (values %$row));
770   }
771
772   return 1;
773 }
774
775 sub get_chart_balances {
776   my ($self, @chart_ids) = @_;
777
778   return () unless @chart_ids;
779
780   my $placeholders = join ', ', ('?') x scalar(@chart_ids);
781   my $query = qq|SELECT chart_id, SUM(amount) AS sum
782                  FROM acc_trans
783                  WHERE chart_id IN (${placeholders})
784                  GROUP BY chart_id|;
785
786   my %balances = selectall_as_map($::form, $::form->get_standard_dbh(\%::myconfig), $query, 'chart_id', 'sum', @chart_ids);
787
788   return %balances;
789 }
790
791 sub get_active_taxes_for_chart {
792   my ($self, $chart_id, $transdate, $tax_id) = @_;
793
794   my $chart         = SL::DB::Chart->new(id => $chart_id)->load;
795   my $active_taxkey = $chart->get_active_taxkey($transdate);
796
797   my $where = [ chart_categories => { like => '%' . $chart->category . '%' } ];
798
799   if ( defined $tax_id && $tax_id >= 0 ) {
800     $where = [ or => [ chart_categories => { like => '%' . $chart->category . '%' },
801                        id               => $tax_id
802                      ]
803              ];
804   }
805
806   my $taxes         = SL::DB::Manager::Tax->get_all(
807     where   => $where,
808     sort_by => 'taxkey, rate',
809   );
810
811   my $default_tax            = first { $active_taxkey->tax_id == $_->id } @{ $taxes };
812   $default_tax->{is_default} = 1 if $default_tax;
813
814   return @{ $taxes };
815 }
816
817 1;
818
819 __END__
820
821 =pod
822
823 =encoding utf8
824
825 =head1 NAME
826
827 SL::GL - some useful GL functions
828
829 =head1 FUNCTIONS
830
831 =over 4
832
833 =item C<get_active_taxes_for_chart> $transdate $tax_id
834
835 Returns a list of valid taxes for a certain chart.
836
837 If the optional param transdate exists one entry in the returning list
838 may get the attribute C<is_default> for this specific tax-dependent date.
839 The possible entries are filtered by the charttype of the tax, i.e. only taxes
840 whose chart_categories match the category of the chart will be shown.
841
842 In the case of existing records, e.g. when opening an old ar record, due to
843 changes in the configurations the desired tax might not be available in the
844 dropdown anymore. If we are loading an old record and know its tax_id (from
845 acc_trans), we can pass $tax_id as the third parameter and be sure that the
846 original tax always appears in the dropdown.
847
848 The functions returns an array which may be used for building dropdowns in ar/ap/gl code.
849
850 =back
851
852 =head1 TODO
853
854 Nothing here yet.
855
856 =head1 BUGS
857
858 Nothing here yet.
859
860 =head1 AUTHOR
861
862 G. Richardson E<lt>grichardson@kivitec.deE<gt>
863
864 =cut