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