1 package AccTransCorrections;
 
   5 use List::Util qw(first);
 
  17   $self->{taxkeys} = Taxkeys->new();
 
  22 sub _fetch_transactions {
 
  23   $main::lxdebug->enter_sub();
 
  28   my $myconfig = \%main::myconfig;
 
  29   my $form     = $main::form;
 
  31   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
 
  33   my (@where, @values) = ((), ());
 
  35   if ($params{transdate_from}) {
 
  36     push @where,  qq|at.transdate >= ?|;
 
  37     push @values, $params{transdate_from};
 
  40   if ($params{transdate_to}) {
 
  41     push @where,  qq|at.transdate <= ?|;
 
  42     push @values, $params{transdate_to};
 
  45   if ($params{trans_id}) {
 
  46     push @where,  qq|at.trans_id = ?|;
 
  47     push @values, $params{trans_id};
 
  52     $where = 'WHERE ' . join(' AND ', map { "($_)" } @where);
 
  57       c.accno, c.description AS chartdescription, c.charttype, c.category AS chartcategory, c.link AS chartlink,
 
  58       COALESCE(gl.reference, COALESCE(ap.invnumber, ar.invnumber)) AS reference,
 
  59       COALESCE(ap.invoice, COALESCE(ar.invoice, FALSE)) AS invoice,
 
  61         WHEN gl.id IS NOT NULL THEN gl.storno AND (gl.storno_id IS NOT NULL)
 
  62         WHEN ap.id IS NOT NULL THEN ap.storno AND (ap.storno_id IS NOT NULL)
 
  63         ELSE                        ar.storno AND (ar.storno_id IS NOT NULL)
 
  66         WHEN gl.id IS NOT NULL THEN 'gl'
 
  67         WHEN ap.id IS NOT NULL THEN 'ap'
 
  72     LEFT JOIN chart c ON (at.chart_id = c.id)
 
  73     LEFT JOIN gl      ON (at.trans_id = gl.id)
 
  74     LEFT JOIN ap      ON (at.trans_id = ap.id)
 
  75     LEFT JOIN ar      ON (at.trans_id = ar.id)
 
  77     ORDER BY at.trans_id, at.acc_trans_id
 
  80   my @transactions = ();
 
  81   my $last_trans   = undef;
 
  83   foreach my $entry (@{ selectall_hashref_query($form, $dbh, $query, @values) }) {
 
  84     if (!$last_trans || ($last_trans->[0]->{trans_id} != $entry->{trans_id})) {
 
  86       push @transactions, $last_trans;
 
  89     push @{ $last_trans }, $entry;
 
  92   $main::lxdebug->leave_sub();
 
  98   $main::lxdebug->enter_sub();
 
 103   my $transaction = $params{transaction};
 
 104   my $callback    = $params{callback};
 
 106   my $myconfig    = \%main::myconfig;
 
 107   my $form        = $main::form;
 
 127   foreach my $entry (@{ $transaction }) {
 
 128     $entry->{chartlinks} = { map { $_ => 1 } split(m/:/, $entry->{chartlink}) };
 
 129     delete $entry->{chartlink};
 
 132   # Verknüpfungen zwischen Steuerschlüsseln und zum Zeitpunkt der Transaktion
 
 133   # gültigen Steuersätze
 
 134   my %all_taxes = $self->{taxkeys}->get_full_tax_info('transdate' => $transaction->[0]->{transdate});
 
 136   my ($trans_type, $previous_non_tax_entry);
 
 138   my $first_sub_trans = 1;
 
 140   my $storno_mult     = $transaction->[0]->{is_storno} ? -1 : 1;
 
 142   # Aufteilung der Buchungspositionen in Soll-, Habenseite sowie
 
 143   # getrennte Auflistung der Positionen, die auf Steuerkonten gebucht werden.
 
 144   foreach my $entry (@{ $transaction }) {
 
 145     if (!$first_sub_trans && ($entry->{chartlinks}->{AP_paid} || $entry->{chartlinks}->{AR_paid})) {
 
 146       push @{ $data->{payments} }, $entry;
 
 150     my $tax_info = $all_taxes{taxkeys}->{ $entry->{taxkey} };
 
 152       $entry->{taxdescription} = $tax_info->{taxdescription} . ' ' . $form->format_amount($myconfig, $tax_info->{taxrate} * 100) . ' %';
 
 155     if ($entry->{chartlinks}->{AP}) {
 
 157     } elsif ($entry->{chartlinks}->{AR}) {
 
 161     my $idx = 0 < ($entry->{amount} * $storno_mult) ? 'credit' : 'debit';
 
 163     if ($entry->{chartlinks}->{AP_tax} || $entry->{chartlinks}->{AR_tax}) {
 
 164       $data->{$idx}->{tax_sum} += $entry->{amount};
 
 165       push @{ $data->{$idx}->{tax_entries} }, $entry;
 
 167       if ($previous_non_tax_entry) {
 
 168         $previous_non_tax_entry->{tax_entry} = $entry;
 
 169         undef $previous_non_tax_entry;
 
 173       $data->{$idx}->{sum} += $entry->{amount};
 
 174       push @{ $data->{$idx}->{entries} }, $entry;
 
 176       $previous_non_tax_entry = $entry;
 
 179     $sum += $entry->{amount};
 
 181     if (abs($sum) < 0.02) {
 
 183       $first_sub_trans = 0;
 
 187   # Alle Einträge entfernen, die die Gegenkonten zu Zahlungsein- und
 
 188   # -ausgängen darstellen.
 
 189   foreach my $payment (@{ $data->{payments} }) {
 
 190     my $idx = 0 < $payment->{amount} ? 'debit' : 'credit';
 
 192     foreach my $i (0 .. scalar(@{ $data->{$idx}->{entries} }) - 1) {
 
 193       my $entry = $data->{$idx}->{entries}->[$i];
 
 195       next if ((($payment->{amount} * -1) != $entry->{amount}) || ($payment->{transdate} ne $entry->{transdate}));
 
 197       splice @{ $data->{$idx}->{entries} }, $i, 1;
 
 203   delete $data->{payments};
 
 205   map { $data->{$_}->{num} = scalar @{ $data->{$_}->{entries} } } qw(credit debit);
 
 207   my $info   = $transaction->[0];
 
 208   my $script = ($info->{module} eq 'ar') && $info->{invoice} ? 'is'
 
 209              : ($info->{module} eq 'ap') && $info->{invoice} ? 'ir'
 
 214     'trans_type'    => $trans_type,
 
 215     'all_taxes'     => { %all_taxes },
 
 216     'transaction'   => $transaction,
 
 217     'full_analysis' => $params{full_analysis},
 
 220       'link'        => $script . ".pl?action=edit${callback}&id=" . $info->{trans_id},
 
 224   $main::lxdebug->leave_sub();
 
 229 sub _group_sub_transactions {
 
 230   $main::lxdebug->enter_sub();
 
 233   my $transaction      = shift;
 
 235   my @sub_transactions = ();
 
 238   foreach my $i (0 .. scalar(@{ $transaction }) - 1) {
 
 239     my $entry = $transaction->[$i];
 
 241     if (abs($sum) <= 0.01) {
 
 242       push @sub_transactions, [];
 
 245     $sum += $entry->{amount};
 
 247     push @{ $sub_transactions[-1] }, $entry;
 
 250   $main::lxdebug->leave_sub();
 
 252   return @sub_transactions;
 
 255 # Problemfall: Verkaufsrechnungen, bei denen Buchungen auf Warenbestandskonten
 
 256 # mit Steuerschlüssel != 0 durchgeführt wurden. Richtig wäre, dass alle
 
 257 # Steuerschlüssel für solche Warenbestandsbuchungen 0 sind.
 
 258 sub _check_trans_invoices_inventory_with_taxkeys {
 
 259   $main::lxdebug->enter_sub();
 
 264   if (!$params{transaction}->[0]->{invoice}) {
 
 265     $main::lxdebug->leave_sub();
 
 269   my @sub_transactions = $self->_group_sub_transactions($params{transaction});
 
 271   foreach my $sub_transaction (@sub_transactions) {
 
 272     my $is_cogs = first { $_->{chartlinks}->{IC_cogs} } @{ $sub_transaction };
 
 273     next unless ($is_cogs);
 
 275     my $needs_fixing = first { $_->{taxkey} != 0 } @{ $sub_transaction };
 
 276     next unless ($needs_fixing);
 
 278     $params{problem}->{type} = 'invoice_inventory_with_taxkeys';
 
 279     push @{ $self->{invoice_inventory_taxkey_problems} }, $params{problem};
 
 281     $main::lxdebug->leave_sub();
 
 286   $main::lxdebug->leave_sub();
 
 291 # Problemfall: Kreditorenbuchungen, bei denen mit Umsatzsteuerschlüsseln
 
 292 # gebucht wurde und Debitorenbuchungen, bei denen mit Vorsteuerschlüsseln
 
 294 sub _check_trans_ap_ar_wrong_taxkeys {
 
 295   $main::lxdebug->enter_sub();
 
 302   if (!$params{transaction}->[0]->{invoice}
 
 303       && ((   ($params{transaction}->[0]->{module} eq 'ap')
 
 304           && (first { my $taxkey = $_->{taxkey}; first { $taxkey == $_ } (2, 3, 12, 13) } @{ $params{transaction} }))
 
 306          (   ($params{transaction}->[0]->{module} eq 'ar')
 
 307           && (first { my $taxkey = $_->{taxkey}; first { $taxkey == $_ } (8, 9, 18, 19) } @{ $params{transaction} })))) {
 
 308     $params{problem}->{type} = 'ap_ar_wrong_taxkeys';
 
 309     push @{ $self->{ap_ar_taxkey_problems} }, $params{problem};
 
 314   $main::lxdebug->leave_sub();
 
 319 # Problemfall: Splitbuchungen, die mehrere Haben- und Sollkonten ansprechen.
 
 320 # Aber nur für Debitoren- und Kreditorenbuchungen, weil das bei Einkaufs- und
 
 321 # Verkaufsrechnungen hingegen völlig normal ist.
 
 322 sub _check_trans_split_multiple_credit_and_debit {
 
 323   $main::lxdebug->enter_sub();
 
 330   if (   !$params{transaction}->[0]->{invoice}
 
 331       && (1 < $params{data}->{credit}->{num})
 
 332       && (1 < $params{data}->{debit}->{num})) {
 
 333     $params{problem}->{type} = 'split_multiple_credit_and_debit';
 
 334     push @{ $self->{problems} }, $params{problem};
 
 339   $main::lxdebug->leave_sub();
 
 344 # Problemfall: Buchungen, bei denen Steuersummen nicht mit den Summen
 
 345 # übereinstimmen, die nach ausgewähltem Steuerschlüssel hätten auftreten müssen.
 
 346 sub _check_trans_wrong_taxkeys {
 
 347   $main::lxdebug->enter_sub();
 
 352   my $form        = $main::form;
 
 354   my %data        = %{ $params{data} };
 
 355   my $transaction = $params{transaction};
 
 357   if (   $transaction->[0]->{invoice}
 
 358       || $transaction->[0]->{ob_transaction}
 
 359       || $transaction->[0]->{cb_transaction}
 
 360       || (!scalar @{ $data{credit}->{entries} } && !scalar @{ $data{debit}->{entries} })
 
 361       || (   ($transaction->[0]->{module} eq 'gl')
 
 362           && (!scalar @{ $data{credit}->{entries} } || !scalar @{ $data{debit}->{entries} }))) {
 
 363     $main::lxdebug->leave_sub();
 
 369   my ($side, $other_side);
 
 370   if (   (grep { $_->{taxkey} * 1 } @{ $data{credit}->{entries} })
 
 371       || (scalar @{ $data{credit}->{tax_entries} })) {
 
 373     $other_side = 'debit';
 
 375   } elsif (   (grep { $_->{taxkey} * 1 } @{ $data{debit}->{entries} })
 
 376            || (scalar @{ $data{debit}->{tax_entries} })) {
 
 378     $other_side = 'credit';
 
 382     $main::lxdebug->leave_sub();
 
 386   my $expected_tax          = 0;
 
 387   my %num_entries_per_chart = ();
 
 388   my $num_taxed_entries     = 0;
 
 390   foreach my $entry (@{ $data{$side}->{entries} }) {
 
 391     my $taxinfo             = $params{all_taxes}->{taxkeys}->{$entry->{taxkey}} || { };
 
 392     $entry->{expected_tax}  = $entry->{amount} * $taxinfo->{taxrate};
 
 393     $expected_tax          += $entry->{expected_tax};
 
 395     $num_taxed_entries++ if ($taxinfo->{taxrate} * 1);
 
 397     my $chart_key = $entry->{chart_id} . "-" . $entry->{taxkey};
 
 398     $num_entries_per_chart{$chart_key} ||= 0;
 
 399     $num_entries_per_chart{$chart_key}++;
 
 402 #   $main::lxdebug->message(0, "side $side trans_id $transaction->[0]->{trans_id} expected tax $expected_tax actual tax $data{$side}->{tax_sum}");
 
 404   if (abs($expected_tax - $data{$side}->{tax_sum}) >= (0.01 * ($num_taxed_entries + 1))) {
 
 405     if ($params{full_analysis}) {
 
 406       my $storno_mult = $data{$side}->{entries}->[0]->{is_storno} ? -1 : 1;
 
 408       foreach my $entry (@{ $data{$other_side}->{entries} }) {
 
 409         $entry->{display_amount} = $form->round_amount(abs($entry->{amount}) * $storno_mult, 2);
 
 412       foreach my $entry (@{ $data{$side}->{entries} }) {
 
 413         $entry->{actual_tax}              = $form->round_amount(abs($entry->{tax_entry} ? $entry->{tax_entry}->{amount} : 0), 2);
 
 414         $entry->{expected_tax}            = $form->round_amount(abs($entry->{expected_tax}), 2);
 
 415         $entry->{taxkey_error}            =    ( $entry->{taxkey} && !$entry->{tax_entry})
 
 416                                             || (!$entry->{taxkey} &&  $entry->{tax_entry})
 
 417                                             || (abs($entry->{expected_tax} - $entry->{actual_tax}) >= 0.02);
 
 418         $entry->{tax_entry_acc_trans_id}  = $entry->{tax_entry}->{acc_trans_id};
 
 419         delete $entry->{tax_entry};
 
 421         $entry->{display_amount}       = $form->round_amount(abs($entry->{amount}) * $storno_mult, 2);
 
 422         $entry->{display_actual_tax}   = $entry->{actual_tax}   * $storno_mult;
 
 423         $entry->{display_expected_tax} = $entry->{expected_tax} * $storno_mult;
 
 425         if ($entry->{taxkey_error}) {
 
 426           $self->{negative_taxkey_filter} ||= {
 
 427             'ar' => { map { $_ => 1 } (   8, 9, 18, 19) },
 
 428             'ap' => { map { $_ => 1 } (1, 2, 3, 12, 13) },
 
 432           $entry->{correct_taxkeys} = [];
 
 434           my %all_taxes = $self->{taxkeys}->get_full_tax_info('transdate' => $entry->{transdate});
 
 436           foreach my $taxkey (sort { $a <=> $b } keys %{ $all_taxes{taxkeys} }) {
 
 437             next if ($self->{negative_taxkey_filter}->{ $entry->{module} }->{$taxkey});
 
 439             my $tax_info = $all_taxes{taxkeys}->{$taxkey};
 
 441             next if ((!$tax_info || (0 == $tax_info->{taxrate} * 1)) && $entry->{tax_entry_acc_trans_id});
 
 443             push @{ $entry->{correct_taxkeys} }, {
 
 445               'tax'         => $form->round_amount(abs($entry->{amount}) * $tax_info->{taxrate}, 2),
 
 446               'description' => sprintf("%s %d%%", $tax_info->{taxdescription}, int($tax_info->{taxrate} * 100)),
 
 453     if (first { $_ > 1 } values %num_entries_per_chart) {
 
 454       $params{problem}->{type} = 'wrong_taxkeys';
 
 456       $params{problem}->{type} = 'wrong_taxes';
 
 459     $params{problem}->{acc_trans} = { %data };
 
 460     push @{ $self->{problems} }, $params{problem};
 
 465   $main::lxdebug->leave_sub();
 
 470 # Inaktiver Code für das Erraten möglicher Verteilungen von
 
 471 # Steuerschlüsseln. Deaktiviert, weil er exponentiell Zeit
 
 474 #       if (abs($expected_tax - $data{$side}->{tax_sum}) >= 0.02) {
 
 475 #         my @potential_taxkeys = $trans_type eq 'AP' ? (0, 8, 9) : (0, 1, 2, 3);
 
 477 #         $main::lxdebug->dump(0, "pota", \@potential_taxkeys);
 
 479 #         # Über alle Kombinationen aus Buchungssätzen und potenziellen Steuerschlüsseln
 
 480 #         # iterieren und jeweils die Summe ermitteln.
 
 481 #         my $num_entries    = scalar @{ $data{$side}->{entries} };
 
 482 #         my @taxkey_indices = (0) x $num_entries;
 
 484 #         my @solutions      = ();
 
 486 #         my $start_time     = time();
 
 488 #         $main::lxdebug->message(0, "num_entries $num_entries");
 
 490 #         while ($num_entries == scalar @taxkey_indices) {
 
 491 #           my @tax_cache = ();
 
 493 #           # Berechnen der Steuersumme für die aktuell angenommenen Steuerschlüssel.
 
 495 #           foreach my $i (0 .. $num_entries - 1) {
 
 496 #             my $taxkey      = $potential_taxkeys[$taxkey_indices[$i]];
 
 497 #             my $entry       = $data{$side}->{entries}->[$i];
 
 498 #             my $taxinfo     = $all_taxes{taxkeys}->{ $taxkey } || { };
 
 499 #             $tax_cache[$i]  = $entry->{amount} * $taxinfo->{taxrate};
 
 500 #             $tax_sum       += $tax_cache[$i];
 
 503 #           # Entspricht die Steuersumme mit den aktuell angenommenen Steuerschlüsseln
 
 504 #           # der verbuchten Steuersumme? Wenn ja, dann ist das eine potenzielle
 
 506 #           if (abs($tax_sum - $data{$side}->{tax_sum}) < 0.02) {
 
 508 #               'taxkeys' => [ @potential_taxkeys[@taxkey_indices] ],
 
 509 #               'taxes'   => [ @tax_cache ],
 
 513 #           # Weiterzählen der Steuerschlüsselindices zum Interieren über
 
 514 #           # alle möglichen Kombinationen.
 
 517 #             $taxkey_indices[$i]++;
 
 518 #             last if ($taxkey_indices[$i] < scalar @potential_taxkeys);
 
 520 #             $taxkey_indices[$i] = 0;
 
 525 #           if (($now - $start_time) >= 5) {
 
 526 #             $main::lxdebug->message(0, "  " . join("", @taxkey_indices));
 
 527 #             $start_time = $now;
 
 531 #         foreach my $solution (@solutions) {
 
 532 #           $solution->{rows}    = [];
 
 533 #           $solution->{changes} = [];
 
 536 #           foreach my $i (0 .. $num_entries - 1) {
 
 537 #             if ($solution->{taxes}->[$i]) {
 
 538 #               my $tax_rounded          = $form->round_amount($solution->{taxes}->[$i] + $error, 2);
 
 539 #               $error                   = $solution->{taxes}->[$i] + $error - $tax_rounded;
 
 540 #               $solution->{taxes}->[$i] = $tax_rounded;
 
 543 #             my $entry     = $data{$side}->{entries}->[$i];
 
 544 #             my $tax_entry = $all_taxes{taxkeys}->{ $solution->{taxkeys}->[$i] };
 
 546 #             push @{ $solution->{rows} }, {
 
 549 #               'taxamount' => $solution->{taxes}->[$i],
 
 552 #             $solution->{rows}->[$i]->{taxdescription} .= ' ' . $form->format_amount(\%myconfig, $tax_entry->{taxrate} * 100) . ' %';
 
 554 #             push @{ $solution->{changes} }, {
 
 555 #               'acc_trans_id'    => $entry->{acc_trans_id},
 
 556 #               'taxkey' => $solution->{taxkeys}->[$i],
 
 560 #           push @{ $solution->{rows} }, @{ $data{$other_side}->{entries} };
 
 562 #           delete @{ $solution }{ qw(taxes taxkeys) };
 
 565 #         $problem->{type}      = 'wrong_taxkeys';
 
 566 #         $problem->{solutions} = [ @solutions ];
 
 567 #         $problem->{acc_trans} = { %data };
 
 568 #         push @problems, $problem;
 
 574   $main::lxdebug->enter_sub();
 
 579   my $myconfig     = \%main::myconfig;
 
 580   my $form         = $main::form;
 
 582   my $dbh          = $params{dbh} || $form->get_standard_dbh($myconfig);
 
 584   my @transactions = $self->_fetch_transactions(%params, 'dbh' => $dbh);
 
 586   if (!scalar @transactions) {
 
 587     $main::lxdebug->leave_sub();
 
 591   my $callback = $params{callback} ? '&callback=' . $params{callback} : '';
 
 593   $self->{problems}                          = [];
 
 594   $self->{ap_ar_taxkey_problems}             = [];
 
 595   $self->{invoice_inventory_taxkey_problems} = [];
 
 597   foreach my $transaction (@transactions) {
 
 598     my %common_args = $self->_prepare_data('transaction' => $transaction, 'callback' => $callback, 'full_analysis' => $params{full_analysis});
 
 600     next if ($self->_check_trans_ap_ar_wrong_taxkeys(%common_args));
 
 601     next if ($self->_check_trans_invoices_inventory_with_taxkeys(%common_args));
 
 602     next if ($self->_check_trans_split_multiple_credit_and_debit(%common_args));
 
 603     next if ($self->_check_trans_wrong_taxkeys(%common_args));
 
 606   my @problems = @{ $self->{problems} };
 
 608   if (0 != scalar @{ $self->{ap_ar_taxkey_problems} }) {
 
 610       'type'        => 'ap_ar_wrong_taxkeys',
 
 611       'ap_problems' => [ grep { $_->{data}->{module} eq 'ap' } @{ $self->{ap_ar_taxkey_problems} } ],
 
 612       'ar_problems' => [ grep { $_->{data}->{module} eq 'ar' } @{ $self->{ap_ar_taxkey_problems} } ],
 
 614     unshift @problems, $problem;
 
 617   if (0 != scalar @{ $self->{invoice_inventory_taxkey_problems} }) {
 
 619       'type'        => 'invoice_inventory_with_taxkeys',
 
 620       'ap_problems' => [ grep { $_->{data}->{module} eq 'ap' } @{ $self->{invoice_inventory_taxkey_problems} } ],
 
 621       'ar_problems' => [ grep { $_->{data}->{module} eq 'ar' } @{ $self->{invoice_inventory_taxkey_problems} } ],
 
 623     unshift @problems, $problem;
 
 626   $main::lxdebug->leave_sub();
 
 632 sub fix_ap_ar_wrong_taxkeys {
 
 633   $main::lxdebug->enter_sub();
 
 638   my $myconfig = \%main::myconfig;
 
 639   my $form     = $main::form;
 
 641   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
 
 643   my $query    = qq|SELECT 'ap' AS module,
 
 644                       at.acc_trans_id, at.trans_id, at.chart_id, at.amount, at.taxkey, at.transdate,
 
 647                     LEFT JOIN chart c ON (at.chart_id = c.id)
 
 648                     WHERE (trans_id IN (SELECT id FROM ap WHERE NOT invoice))
 
 649                       AND (taxkey IN (2, 3, 12, 13))
 
 653                     SELECT 'ar' AS module,
 
 654                       at.acc_trans_id, at.trans_id, at.chart_id, at.amount, at.taxkey, at.transdate,
 
 657                     LEFT JOIN chart c ON (at.chart_id = c.id)
 
 658                     WHERE (trans_id IN (SELECT id FROM ar WHERE NOT invoice))
 
 659                       AND (taxkey IN (8, 9, 18, 19))
 
 661                     ORDER BY trans_id, acc_trans_id|;
 
 663   my $sth      = prepare_execute_query($form, $dbh, $query);
 
 666   while (my $ref = $sth->fetchrow_hashref()) {
 
 667     if ((!scalar @transactions) || ($ref->{trans_id} != $transactions[-1]->[0]->{trans_id})) {
 
 668       push @transactions, [];
 
 671     push @{ $transactions[-1] }, $ref;
 
 676   @transactions = grep { (scalar(@transactions) % 2) == 0 } @transactions;
 
 678   my %taxkey_replacements = (
 
 690     'ap' => { map { $_ => 1 } (2, 3, 12, 13) },
 
 691     'ar' => { map { $_ => 1 } (8, 9, 18, 19) },
 
 694   my @corrections = ();
 
 696   foreach my $transaction (@transactions) {
 
 698     for (my $i = 0; $i < scalar @{ $transaction }; $i += 2) {
 
 699       my ($non_tax_idx, $tax_idx) = abs($transaction->[$i]->{amount}) > abs($transaction->[$i + 1]->{amount}) ? ($i, $i + 1) : ($i + 1, $i);
 
 700       my ($non_tax,     $tax)     = @{ $transaction }[$non_tax_idx, $tax_idx];
 
 702       last if ($non_tax->{link} =~ m/(:?AP|AR)_tax(:?$|:)/);
 
 703       last if ($tax->{link}     !~ m/(:?AP|AR)_tax(:?$|:)/);
 
 705       next if (!$bad_taxkeys{ $non_tax->{module} }->{ $non_tax->{taxkey} });
 
 707       my %all_taxes = $self->{taxkeys}->get_full_tax_info('transdate' => $non_tax->{transdate});
 
 709       push @corrections, ({ 'acc_trans_id' => $non_tax->{acc_trans_id},
 
 710                             'taxkey'       => $taxkey_replacements{$non_tax->{taxkey}},
 
 713                             'acc_trans_id' => $tax->{acc_trans_id},
 
 714                             'taxkey'       => $taxkey_replacements{$non_tax->{taxkey}},
 
 715                             'chart_id'     => $all_taxes{taxkeys}->{ $taxkey_replacements{$non_tax->{taxkey}} }->{taxchart_id},
 
 720   if (scalar @corrections) {
 
 721     my $q_taxkey_only     = qq|UPDATE acc_trans SET taxkey = ? WHERE acc_trans_id = ?|;
 
 722     my $h_taxkey_only     = prepare_query($form, $dbh, $q_taxkey_only);
 
 724     my $q_taxkey_chart_id = qq|UPDATE acc_trans SET taxkey = ?, chart_id = ? WHERE acc_trans_id = ?|;
 
 725     my $h_taxkey_chart_id = prepare_query($form, $dbh, $q_taxkey_chart_id);
 
 727     foreach my $entry (@corrections) {
 
 728       if ($entry->{chart_id}) {
 
 729         do_statement($form, $h_taxkey_chart_id, $q_taxkey_chart_id, $entry->{taxkey}, $entry->{chart_id}, $entry->{acc_trans_id});
 
 731         do_statement($form, $h_taxkey_only, $q_taxkey_only, $entry->{taxkey}, $entry->{acc_trans_id});
 
 735     $h_taxkey_only->finish();
 
 736     $h_taxkey_chart_id->finish();
 
 738     $dbh->commit() unless ($params{dbh});
 
 741   $main::lxdebug->leave_sub();
 
 744 sub fix_invoice_inventory_with_taxkeys {
 
 745   $main::lxdebug->enter_sub();
 
 750   my $myconfig = \%main::myconfig;
 
 751   my $form     = $main::form;
 
 753   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
 
 755   my $query    = qq|SELECT at.*, c.link
 
 757                     LEFT JOIN ar      ON (at.trans_id = ar.id)
 
 758                     LEFT JOIN chart c ON (at.chart_id = c.id)
 
 765                     LEFT JOIN ap      ON (at.trans_id = ap.id)
 
 766                     LEFT JOIN chart c ON (at.chart_id = c.id)
 
 769                     ORDER BY trans_id, acc_trans_id|;
 
 771   my $sth      = prepare_execute_query($form, $dbh, $query);
 
 774   while (my $ref = $sth->fetchrow_hashref()) {
 
 775     if ((!scalar @transactions) || ($ref->{trans_id} != $transactions[-1]->[0]->{trans_id})) {
 
 776       push @transactions, [];
 
 779     push @{ $transactions[-1] }, $ref;
 
 784   my @corrections = ();
 
 786   foreach my $transaction (@transactions) {
 
 787     my @sub_transactions = $self->_group_sub_transactions($transaction);
 
 789     foreach my $sub_transaction (@sub_transactions) {
 
 790       my $is_cogs = first { $_->{link} =~ m/IC_cogs/ } @{ $sub_transaction };
 
 791       next unless ($is_cogs);
 
 793       foreach my $entry (@{ $sub_transaction }) {
 
 794         next if ($entry->{taxkey} == 0);
 
 795         push @corrections, $entry->{acc_trans_id};
 
 801     $query = qq|UPDATE acc_trans SET taxkey = 0 WHERE acc_trans_id = ?|;
 
 802     $sth   = prepare_query($form, $dbh, $query);
 
 804     foreach my $acc_trans_id (@corrections) {
 
 805       do_statement($form, $sth, $query, $acc_trans_id);
 
 810     $dbh->commit() unless ($params{dbh});
 
 814   $main::lxdebug->leave_sub();
 
 817 sub fix_wrong_taxkeys {
 
 818   $main::lxdebug->enter_sub();
 
 823   Common::check_params(\%params, qw(fixes));
 
 825   my $myconfig = \%main::myconfig;
 
 826   my $form     = $main::form;
 
 828   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
 
 830   my $q_taxkey_only  = qq|UPDATE acc_trans SET taxkey = ? WHERE acc_trans_id = ?|;
 
 831   my $h_taxkey_only  = prepare_query($form, $dbh, $q_taxkey_only);
 
 833   my $q_taxkey_chart = qq|UPDATE acc_trans SET taxkey = ?, chart_id = ? WHERE acc_trans_id = ?|;
 
 834   my $h_taxkey_chart = prepare_query($form, $dbh, $q_taxkey_chart);
 
 836   my $q_transdate    = qq|SELECT transdate FROM acc_trans WHERE acc_trans_id = ?|;
 
 837   my $h_transdate    = prepare_query($form, $dbh, $q_transdate);
 
 839   foreach my $fix (@{ $params{fixes} }) {
 
 840     next unless ($fix->{acc_trans_id});
 
 842     do_statement($form, $h_taxkey_only, $q_taxkey_only, conv_i($fix->{taxkey}), conv_i($fix->{acc_trans_id}));
 
 844     next unless ($fix->{tax_entry_acc_trans_id});
 
 846     do_statement($form, $h_transdate, $q_transdate, conv_i($fix->{tax_entry_acc_trans_id}));
 
 847     my ($transdate) = $h_transdate->fetchrow_array();
 
 849     my %all_taxes = $self->{taxkeys}->get_full_tax_info('transdate' => $transdate);
 
 850     my $tax_info  = $all_taxes{taxkeys}->{ $fix->{taxkey} };
 
 852     next unless ($tax_info);
 
 854     do_statement($form, $h_taxkey_chart, $q_taxkey_chart, conv_i($fix->{taxkey}), conv_i($tax_info->{taxchart_id}), conv_i($fix->{tax_entry_acc_trans_id}));
 
 857   $h_taxkey_only->finish();
 
 858   $h_taxkey_chart->finish();
 
 859   $h_transdate->finish();
 
 862   $dbh->commit() unless ($params{dbh});
 
 864   $main::lxdebug->leave_sub();
 
 867 sub delete_transaction {
 
 868   $main::lxdebug->enter_sub();
 
 873   Common::check_params(\%params, qw(trans_id));
 
 875   my $myconfig = \%main::myconfig;
 
 876   my $form     = $main::form;
 
 878   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
 
 880   do_query($form, $dbh, qq|UPDATE ar SET storno_id = NULL WHERE storno_id = ?|, conv_i($params{trans_id}));
 
 881   do_query($form, $dbh, qq|UPDATE ap SET storno_id = NULL WHERE storno_id = ?|, conv_i($params{trans_id}));
 
 882   do_query($form, $dbh, qq|UPDATE gl SET storno_id = NULL WHERE storno_id = ?|, conv_i($params{trans_id}));
 
 884   do_query($form, $dbh, qq|DELETE FROM ar        WHERE id       = ?|, conv_i($params{trans_id}));
 
 885   do_query($form, $dbh, qq|DELETE FROM ap        WHERE id       = ?|, conv_i($params{trans_id}));
 
 886   do_query($form, $dbh, qq|DELETE FROM gl        WHERE id       = ?|, conv_i($params{trans_id}));
 
 887   do_query($form, $dbh, qq|DELETE FROM acc_trans WHERE trans_id = ?|, conv_i($params{trans_id}));
 
 890   $dbh->commit() unless ($params{dbh});
 
 892   $main::lxdebug->leave_sub();