1 package AccTransCorrections;
 
   6 use List::Util qw(first);
 
  18   $self->{taxkeys} = Taxkeys->new();
 
  23 sub _fetch_transactions {
 
  24   $main::lxdebug->enter_sub();
 
  29   my $myconfig = \%main::myconfig;
 
  30   my $form     = $main::form;
 
  32   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
 
  34   my (@where, @values) = ((), ());
 
  36   if ($params{transdate_from}) {
 
  37     push @where,  qq|at.transdate >= ?|;
 
  38     push @values, $params{transdate_from};
 
  41   if ($params{transdate_to}) {
 
  42     push @where,  qq|at.transdate <= ?|;
 
  43     push @values, $params{transdate_to};
 
  46   if ($params{trans_id}) {
 
  47     push @where,  qq|at.trans_id = ?|;
 
  48     push @values, $params{trans_id};
 
  53     $where = 'WHERE ' . join(' AND ', map { "($_)" } @where);
 
  58       c.accno, c.description AS chartdescription, c.charttype, c.category AS chartcategory, c.link AS chartlink,
 
  59       COALESCE(gl.reference, COALESCE(ap.invnumber, ar.invnumber)) AS reference,
 
  60       COALESCE(ap.invoice, COALESCE(ar.invoice, FALSE)) AS invoice,
 
  62         WHEN gl.id IS NOT NULL THEN gl.storno AND (gl.storno_id IS NOT NULL)
 
  63         WHEN ap.id IS NOT NULL THEN ap.storno AND (ap.storno_id IS NOT NULL)
 
  64         ELSE                        ar.storno AND (ar.storno_id IS NOT NULL)
 
  67         WHEN gl.id IS NOT NULL THEN 'gl'
 
  68         WHEN ap.id IS NOT NULL THEN 'ap'
 
  73     LEFT JOIN chart c ON (at.chart_id = c.id)
 
  74     LEFT JOIN gl      ON (at.trans_id = gl.id)
 
  75     LEFT JOIN ap      ON (at.trans_id = ap.id)
 
  76     LEFT JOIN ar      ON (at.trans_id = ar.id)
 
  78     ORDER BY at.trans_id, at.acc_trans_id
 
  81   my @transactions = ();
 
  82   my $last_trans   = undef;
 
  84   foreach my $entry (@{ selectall_hashref_query($form, $dbh, $query, @values) }) {
 
  85     if (!$last_trans || ($last_trans->[0]->{trans_id} != $entry->{trans_id})) {
 
  87       push @transactions, $last_trans;
 
  90     push @{ $last_trans }, $entry;
 
  93   $main::lxdebug->leave_sub();
 
  99   $main::lxdebug->enter_sub();
 
 104   my $transaction = $params{transaction};
 
 105   my $callback    = $params{callback};
 
 107   my $myconfig    = \%main::myconfig;
 
 108   my $form        = $main::form;
 
 128   foreach my $entry (@{ $transaction }) {
 
 129     $entry->{chartlinks} = { map { $_ => 1 } split(m/:/, $entry->{chartlink}) };
 
 130     delete $entry->{chartlink};
 
 133   # Verknüpfungen zwischen Steuerschlüsseln und zum Zeitpunkt der Transaktion
 
 134   # gültigen Steuersätze
 
 135   my %all_taxes = $self->{taxkeys}->get_full_tax_info('transdate' => $transaction->[0]->{transdate});
 
 137   my ($trans_type, $previous_non_tax_entry);
 
 139   my $first_sub_trans = 1;
 
 141   my $storno_mult     = $transaction->[0]->{is_storno} ? -1 : 1;
 
 143   # Aufteilung der Buchungspositionen in Soll-, Habenseite sowie
 
 144   # getrennte Auflistung der Positionen, die auf Steuerkonten gebucht werden.
 
 145   foreach my $entry (@{ $transaction }) {
 
 146     if (!$first_sub_trans && ($entry->{chartlinks}->{AP_paid} || $entry->{chartlinks}->{AR_paid})) {
 
 147       push @{ $data->{payments} }, $entry;
 
 151     my $tax_info = $all_taxes{taxkeys}->{ $entry->{taxkey} };
 
 153       $entry->{taxdescription} = $tax_info->{taxdescription} . ' ' . $form->format_amount($myconfig, $tax_info->{taxrate} * 100) . ' %';
 
 156     if ($entry->{chartlinks}->{AP}) {
 
 158     } elsif ($entry->{chartlinks}->{AR}) {
 
 162     my $idx = 0 < ($entry->{amount} * $storno_mult) ? 'credit' : 'debit';
 
 164     if ($entry->{chartlinks}->{AP_tax} || $entry->{chartlinks}->{AR_tax}) {
 
 165       $data->{$idx}->{tax_sum} += $entry->{amount};
 
 166       push @{ $data->{$idx}->{tax_entries} }, $entry;
 
 168       if ($previous_non_tax_entry) {
 
 169         $previous_non_tax_entry->{tax_entry} = $entry;
 
 170         undef $previous_non_tax_entry;
 
 174       $data->{$idx}->{sum} += $entry->{amount};
 
 175       push @{ $data->{$idx}->{entries} }, $entry;
 
 177       $previous_non_tax_entry = $entry;
 
 180     $sum += $entry->{amount};
 
 182     if (abs($sum) < 0.02) {
 
 184       $first_sub_trans = 0;
 
 188   # Alle Einträge entfernen, die die Gegenkonten zu Zahlungsein- und
 
 189   # -ausgängen darstellen.
 
 190   foreach my $payment (@{ $data->{payments} }) {
 
 191     my $idx = 0 < $payment->{amount} ? 'debit' : 'credit';
 
 193     foreach my $i (0 .. scalar(@{ $data->{$idx}->{entries} }) - 1) {
 
 194       my $entry = $data->{$idx}->{entries}->[$i];
 
 196       next if ((($payment->{amount} * -1) != $entry->{amount}) || ($payment->{transdate} ne $entry->{transdate}));
 
 198       splice @{ $data->{$idx}->{entries} }, $i, 1;
 
 204   delete $data->{payments};
 
 206   map { $data->{$_}->{num} = scalar @{ $data->{$_}->{entries} } } qw(credit debit);
 
 208   my $info   = $transaction->[0];
 
 209   my $script = ($info->{module} eq 'ar') && $info->{invoice} ? 'is'
 
 210              : ($info->{module} eq 'ap') && $info->{invoice} ? 'ir'
 
 215     'trans_type'    => $trans_type,
 
 216     'all_taxes'     => { %all_taxes },
 
 217     'transaction'   => $transaction,
 
 218     'full_analysis' => $params{full_analysis},
 
 221       'link'        => $script . ".pl?action=edit${callback}&id=" . $info->{trans_id},
 
 225   $main::lxdebug->leave_sub();
 
 230 sub _group_sub_transactions {
 
 231   $main::lxdebug->enter_sub();
 
 234   my $transaction      = shift;
 
 236   my @sub_transactions = ();
 
 239   foreach my $i (0 .. scalar(@{ $transaction }) - 1) {
 
 240     my $entry = $transaction->[$i];
 
 242     if (abs($sum) <= 0.01) {
 
 243       push @sub_transactions, [];
 
 246     $sum += $entry->{amount};
 
 248     push @{ $sub_transactions[-1] }, $entry;
 
 251   $main::lxdebug->leave_sub();
 
 253   return @sub_transactions;
 
 256 # Problemfall: Verkaufsrechnungen, bei denen Buchungen auf Warenbestandskonten
 
 257 # mit Steuerschlüssel != 0 durchgeführt wurden. Richtig wäre, dass alle
 
 258 # Steuerschlüssel für solche Warenbestandsbuchungen 0 sind.
 
 259 sub _check_trans_invoices_inventory_with_taxkeys {
 
 260   $main::lxdebug->enter_sub();
 
 265   if (!$params{transaction}->[0]->{invoice}) {
 
 266     $main::lxdebug->leave_sub();
 
 270   my @sub_transactions = $self->_group_sub_transactions($params{transaction});
 
 272   foreach my $sub_transaction (@sub_transactions) {
 
 273     my $is_cogs = first { $_->{chartlinks}->{IC_cogs} } @{ $sub_transaction };
 
 274     next unless ($is_cogs);
 
 276     my $needs_fixing = first { $_->{taxkey} != 0 } @{ $sub_transaction };
 
 277     next unless ($needs_fixing);
 
 279     $params{problem}->{type} = 'invoice_inventory_with_taxkeys';
 
 280     push @{ $self->{invoice_inventory_taxkey_problems} }, $params{problem};
 
 282     $main::lxdebug->leave_sub();
 
 287   $main::lxdebug->leave_sub();
 
 292 # Problemfall: Verkaufsrechnungen, bei denen Steuern verbucht wurden, obwohl
 
 293 # kein Steuerschlüssel eingetragen ist.
 
 294 sub _check_missing_taxkeys_in_invoices {
 
 295   $::lxdebug->enter_sub;
 
 299   my $transaction = $params{transaction};
 
 300   my $found_broken = 0;
 
 302   $::lxdebug->leave_sub and return 0
 
 303     if    !$transaction->[0]->{invoice};
 
 305   my @sub_transactions = $self->_group_sub_transactions($transaction);
 
 307   for my $sub_transaction (@sub_transactions) {
 
 308     $::lxdebug->leave_sub and return 0
 
 309       if    _is_split_transaction($sub_transaction)
 
 310          || _is_simple_transaction($sub_transaction);
 
 312     my $split_side_entries = _get_splitted_side($sub_transaction);
 
 315     for my $entry (@{ $split_side_entries }) {
 
 316       my $is_tax = grep { m/(?:AP_tax|AR_tax)/ } keys %{ $entry->{chartlinks} };
 
 318       $num_tax_rows++   if  $is_tax;
 
 319       $num_taxed_rows++ if !$is_tax && $entry->{tax_key} != 0;
 
 322     # now if this has tax rows but NO taxed rows, something is wrong.
 
 323     if ($num_tax_rows > 0 && $num_taxed_rows == 0) {
 
 324       $params{problem}->{type} = 'missing_taxkeys_in_invoices';
 
 325       push @{ $self->{missing_taxkeys_in_invoices} ||= [] }, $params{problem};
 
 330   $::lxdebug->leave_sub;
 
 332   return $found_broken;
 
 335 # Problemfall: Kreditorenbuchungen, bei denen mit Umsatzsteuerschlüsseln
 
 336 # gebucht wurde und Debitorenbuchungen, bei denen mit Vorsteuerschlüsseln
 
 338 sub _check_trans_ap_ar_wrong_taxkeys {
 
 339   $main::lxdebug->enter_sub();
 
 346   if (!$params{transaction}->[0]->{invoice}
 
 347       && ((   ($params{transaction}->[0]->{module} eq 'ap')
 
 348           && (first { my $taxkey = $_->{taxkey}; first { $taxkey == $_ } (2, 3, 12, 13) } @{ $params{transaction} }))
 
 350          (   ($params{transaction}->[0]->{module} eq 'ar')
 
 351           && (first { my $taxkey = $_->{taxkey}; first { $taxkey == $_ } (8, 9, 18, 19) } @{ $params{transaction} })))) {
 
 352     $params{problem}->{type} = 'ap_ar_wrong_taxkeys';
 
 353     push @{ $self->{ap_ar_taxkey_problems} }, $params{problem};
 
 358   $main::lxdebug->leave_sub();
 
 363 # Problemfall: Splitbuchungen, die mehrere Haben- und Sollkonten ansprechen.
 
 364 # Aber nur für Debitoren- und Kreditorenbuchungen, weil das bei Einkaufs- und
 
 365 # Verkaufsrechnungen hingegen völlig normal ist.
 
 366 sub _check_trans_split_multiple_credit_and_debit {
 
 367   $main::lxdebug->enter_sub();
 
 374   if (   !$params{transaction}->[0]->{invoice}
 
 375       && (1 < $params{data}->{credit}->{num})
 
 376       && (1 < $params{data}->{debit}->{num})) {
 
 377     $params{problem}->{type} = 'split_multiple_credit_and_debit';
 
 378     push @{ $self->{problems} }, $params{problem};
 
 383   $main::lxdebug->leave_sub();
 
 388 # Problemfall: Buchungen, bei denen Steuersummen nicht mit den Summen
 
 389 # übereinstimmen, die nach ausgewähltem Steuerschlüssel hätten auftreten müssen.
 
 390 sub _check_trans_wrong_taxkeys {
 
 391   $main::lxdebug->enter_sub();
 
 396   my $form        = $main::form;
 
 398   my %data        = %{ $params{data} };
 
 399   my $transaction = $params{transaction};
 
 401   if (   $transaction->[0]->{invoice}
 
 402       || $transaction->[0]->{ob_transaction}
 
 403       || $transaction->[0]->{cb_transaction}
 
 404       || (!scalar @{ $data{credit}->{entries} } && !scalar @{ $data{debit}->{entries} })
 
 405       || (   ($transaction->[0]->{module} eq 'gl')
 
 406           && (!scalar @{ $data{credit}->{entries} } || !scalar @{ $data{debit}->{entries} }))) {
 
 407     $main::lxdebug->leave_sub();
 
 413   my ($side, $other_side);
 
 414   if (   (grep { $_->{taxkey} * 1 } @{ $data{credit}->{entries} })
 
 415       || (scalar @{ $data{credit}->{tax_entries} })) {
 
 417     $other_side = 'debit';
 
 419   } elsif (   (grep { $_->{taxkey} * 1 } @{ $data{debit}->{entries} })
 
 420            || (scalar @{ $data{debit}->{tax_entries} })) {
 
 422     $other_side = 'credit';
 
 426     $main::lxdebug->leave_sub();
 
 430   my $expected_tax          = 0;
 
 431   my %num_entries_per_chart = ();
 
 432   my $num_taxed_entries     = 0;
 
 434   foreach my $entry (@{ $data{$side}->{entries} }) {
 
 435     my $taxinfo             = $params{all_taxes}->{taxkeys}->{$entry->{taxkey}} || { };
 
 436     $entry->{expected_tax}  = $entry->{amount} * $taxinfo->{taxrate};
 
 437     $expected_tax          += $entry->{expected_tax};
 
 439     $num_taxed_entries++ if ($taxinfo->{taxrate} * 1);
 
 441     my $chart_key = $entry->{chart_id} . "-" . $entry->{taxkey};
 
 442     $num_entries_per_chart{$chart_key} ||= 0;
 
 443     $num_entries_per_chart{$chart_key}++;
 
 446 #   $main::lxdebug->message(0, "side $side trans_id $transaction->[0]->{trans_id} expected tax $expected_tax actual tax $data{$side}->{tax_sum}");
 
 448   if (abs($expected_tax - $data{$side}->{tax_sum}) >= (0.01 * ($num_taxed_entries + 1))) {
 
 449     if ($params{full_analysis}) {
 
 450       my $storno_mult = $data{$side}->{entries}->[0]->{is_storno} ? -1 : 1;
 
 452       foreach my $entry (@{ $data{$other_side}->{entries} }) {
 
 453         $entry->{display_amount} = $form->round_amount(abs($entry->{amount}) * $storno_mult, 2);
 
 456       foreach my $entry (@{ $data{$side}->{entries} }) {
 
 457         $entry->{actual_tax}              = $form->round_amount(abs($entry->{tax_entry} ? $entry->{tax_entry}->{amount} : 0), 2);
 
 458         $entry->{expected_tax}            = $form->round_amount(abs($entry->{expected_tax}), 2);
 
 459         $entry->{taxkey_error}            =    ( $entry->{taxkey} && !$entry->{tax_entry})
 
 460                                             || (!$entry->{taxkey} &&  $entry->{tax_entry})
 
 461                                             || (abs($entry->{expected_tax} - $entry->{actual_tax}) >= 0.02);
 
 462         $entry->{tax_entry_acc_trans_id}  = $entry->{tax_entry}->{acc_trans_id};
 
 463         delete $entry->{tax_entry};
 
 465         $entry->{display_amount}       = $form->round_amount(abs($entry->{amount}) * $storno_mult, 2);
 
 466         $entry->{display_actual_tax}   = $entry->{actual_tax}   * $storno_mult;
 
 467         $entry->{display_expected_tax} = $entry->{expected_tax} * $storno_mult;
 
 469         if ($entry->{taxkey_error}) {
 
 470           $self->{negative_taxkey_filter} ||= {
 
 471             'ar' => { map { $_ => 1 } (   8, 9, 18, 19) },
 
 472             'ap' => { map { $_ => 1 } (1, 2, 3, 12, 13) },
 
 476           $entry->{correct_taxkeys} = [];
 
 478           my %all_taxes = $self->{taxkeys}->get_full_tax_info('transdate' => $entry->{transdate});
 
 480           foreach my $taxkey (sort { $a <=> $b } keys %{ $all_taxes{taxkeys} }) {
 
 481             next if ($self->{negative_taxkey_filter}->{ $entry->{module} }->{$taxkey});
 
 483             my $tax_info = $all_taxes{taxkeys}->{$taxkey};
 
 485             next if ((!$tax_info || (0 == $tax_info->{taxrate} * 1)) && $entry->{tax_entry_acc_trans_id});
 
 487             push @{ $entry->{correct_taxkeys} }, {
 
 489               'tax'         => $form->round_amount(abs($entry->{amount}) * $tax_info->{taxrate}, 2),
 
 490               'description' => sprintf("%s %d%%", $tax_info->{taxdescription}, int($tax_info->{taxrate} * 100)),
 
 497     if (first { $_ > 1 } values %num_entries_per_chart) {
 
 498       $params{problem}->{type} = 'wrong_taxkeys';
 
 500       $params{problem}->{type} = 'wrong_taxes';
 
 503     $params{problem}->{acc_trans} = { %data };
 
 504     push @{ $self->{problems} }, $params{problem};
 
 509   $main::lxdebug->leave_sub();
 
 514 # Inaktiver Code für das Erraten möglicher Verteilungen von
 
 515 # Steuerschlüsseln. Deaktiviert, weil er exponentiell Zeit
 
 518 #       if (abs($expected_tax - $data{$side}->{tax_sum}) >= 0.02) {
 
 519 #         my @potential_taxkeys = $trans_type eq 'AP' ? (0, 8, 9) : (0, 1, 2, 3);
 
 521 #         $main::lxdebug->dump(0, "pota", \@potential_taxkeys);
 
 523 #         # Über alle Kombinationen aus Buchungssätzen und potenziellen Steuerschlüsseln
 
 524 #         # iterieren und jeweils die Summe ermitteln.
 
 525 #         my $num_entries    = scalar @{ $data{$side}->{entries} };
 
 526 #         my @taxkey_indices = (0) x $num_entries;
 
 528 #         my @solutions      = ();
 
 530 #         my $start_time     = time();
 
 532 #         $main::lxdebug->message(0, "num_entries $num_entries");
 
 534 #         while ($num_entries == scalar @taxkey_indices) {
 
 535 #           my @tax_cache = ();
 
 537 #           # Berechnen der Steuersumme für die aktuell angenommenen Steuerschlüssel.
 
 539 #           foreach my $i (0 .. $num_entries - 1) {
 
 540 #             my $taxkey      = $potential_taxkeys[$taxkey_indices[$i]];
 
 541 #             my $entry       = $data{$side}->{entries}->[$i];
 
 542 #             my $taxinfo     = $all_taxes{taxkeys}->{ $taxkey } || { };
 
 543 #             $tax_cache[$i]  = $entry->{amount} * $taxinfo->{taxrate};
 
 544 #             $tax_sum       += $tax_cache[$i];
 
 547 #           # Entspricht die Steuersumme mit den aktuell angenommenen Steuerschlüsseln
 
 548 #           # der verbuchten Steuersumme? Wenn ja, dann ist das eine potenzielle
 
 550 #           if (abs($tax_sum - $data{$side}->{tax_sum}) < 0.02) {
 
 552 #               'taxkeys' => [ @potential_taxkeys[@taxkey_indices] ],
 
 553 #               'taxes'   => [ @tax_cache ],
 
 557 #           # Weiterzählen der Steuerschlüsselindices zum Interieren über
 
 558 #           # alle möglichen Kombinationen.
 
 561 #             $taxkey_indices[$i]++;
 
 562 #             last if ($taxkey_indices[$i] < scalar @potential_taxkeys);
 
 564 #             $taxkey_indices[$i] = 0;
 
 569 #           if (($now - $start_time) >= 5) {
 
 570 #             $main::lxdebug->message(0, "  " . join("", @taxkey_indices));
 
 571 #             $start_time = $now;
 
 575 #         foreach my $solution (@solutions) {
 
 576 #           $solution->{rows}    = [];
 
 577 #           $solution->{changes} = [];
 
 580 #           foreach my $i (0 .. $num_entries - 1) {
 
 581 #             if ($solution->{taxes}->[$i]) {
 
 582 #               my $tax_rounded          = $form->round_amount($solution->{taxes}->[$i] + $error, 2);
 
 583 #               $error                   = $solution->{taxes}->[$i] + $error - $tax_rounded;
 
 584 #               $solution->{taxes}->[$i] = $tax_rounded;
 
 587 #             my $entry     = $data{$side}->{entries}->[$i];
 
 588 #             my $tax_entry = $all_taxes{taxkeys}->{ $solution->{taxkeys}->[$i] };
 
 590 #             push @{ $solution->{rows} }, {
 
 593 #               'taxamount' => $solution->{taxes}->[$i],
 
 596 #             $solution->{rows}->[$i]->{taxdescription} .= ' ' . $form->format_amount(\%myconfig, $tax_entry->{taxrate} * 100) . ' %';
 
 598 #             push @{ $solution->{changes} }, {
 
 599 #               'acc_trans_id'    => $entry->{acc_trans_id},
 
 600 #               'taxkey' => $solution->{taxkeys}->[$i],
 
 604 #           push @{ $solution->{rows} }, @{ $data{$other_side}->{entries} };
 
 606 #           delete @{ $solution }{ qw(taxes taxkeys) };
 
 609 #         $problem->{type}      = 'wrong_taxkeys';
 
 610 #         $problem->{solutions} = [ @solutions ];
 
 611 #         $problem->{acc_trans} = { %data };
 
 612 #         push @problems, $problem;
 
 618   $main::lxdebug->enter_sub();
 
 623   my $myconfig     = \%main::myconfig;
 
 624   my $form         = $main::form;
 
 626   my $dbh          = $params{dbh} || $form->get_standard_dbh($myconfig);
 
 628   my @transactions = $self->_fetch_transactions(%params, 'dbh' => $dbh);
 
 630   if (!scalar @transactions) {
 
 631     $main::lxdebug->leave_sub();
 
 635   my $callback = $params{callback} ? '&callback=' . $params{callback} : '';
 
 637   $self->{problems}                          = [];
 
 638   $self->{ap_ar_taxkey_problems}             = [];
 
 639   $self->{invoice_inventory_taxkey_problems} = [];
 
 641   foreach my $transaction (@transactions) {
 
 642     my %common_args = $self->_prepare_data('transaction' => $transaction, 'callback' => $callback, 'full_analysis' => $params{full_analysis});
 
 644     next if ($self->_check_trans_ap_ar_wrong_taxkeys(%common_args));
 
 645     next if ($self->_check_trans_invoices_inventory_with_taxkeys(%common_args));
 
 646     next if ($self->_check_trans_split_multiple_credit_and_debit(%common_args));
 
 647     next if ($self->_check_trans_wrong_taxkeys(%common_args));
 
 650   my @problems = @{ $self->{problems} };
 
 652   map { $self->{$_} ||= [] } qw(ap_ar_taxkey_problems invoice_inventory_taxkey_problems missing_taxkeys_in_invoices);
 
 654   if (0 != scalar @{ $self->{ap_ar_taxkey_problems} }) {
 
 656       'type'        => 'ap_ar_wrong_taxkeys',
 
 657       'ap_problems' => [ grep { $_->{data}->{module} eq 'ap' } @{ $self->{ap_ar_taxkey_problems} } ],
 
 658       'ar_problems' => [ grep { $_->{data}->{module} eq 'ar' } @{ $self->{ap_ar_taxkey_problems} } ],
 
 660     unshift @problems, $problem;
 
 663   if (0 != scalar @{ $self->{invoice_inventory_taxkey_problems} }) {
 
 665       'type'        => 'invoice_inventory_with_taxkeys',
 
 666       'ap_problems' => [ grep { $_->{data}->{module} eq 'ap' } @{ $self->{invoice_inventory_taxkey_problems} } ],
 
 667       'ar_problems' => [ grep { $_->{data}->{module} eq 'ar' } @{ $self->{invoice_inventory_taxkey_problems} } ],
 
 669     unshift @problems, $problem;
 
 672   if (0 != scalar @{ $self->{missing_taxkeys_in_invoices} }) {
 
 674       'type'        => 'missing_taxkeys_in_invoices',
 
 675       'ap_problems' => [ grep { $_->{data}->{module} eq 'ap' } @{ $self->{missing_taxkeys_in_invoices} } ],
 
 676       'ar_problems' => [ grep { $_->{data}->{module} eq 'ar' } @{ $self->{missing_taxkeys_in_invoices} } ],
 
 678     unshift @problems, $problem;
 
 681   $main::lxdebug->leave_sub();
 
 683 #  $::lxdebug->dump(0, 'problems:', \@problems);
 
 688 sub fix_ap_ar_wrong_taxkeys {
 
 689   $main::lxdebug->enter_sub();
 
 694   my $myconfig = \%main::myconfig;
 
 695   my $form     = $main::form;
 
 697   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
 
 699   my $query    = qq|SELECT 'ap' AS module,
 
 700                       at.acc_trans_id, at.trans_id, at.chart_id, at.amount, at.taxkey, at.transdate,
 
 703                     LEFT JOIN chart c ON (at.chart_id = c.id)
 
 704                     WHERE (trans_id IN (SELECT id FROM ap WHERE NOT invoice))
 
 705                       AND (taxkey IN (2, 3, 12, 13))
 
 709                     SELECT 'ar' AS module,
 
 710                       at.acc_trans_id, at.trans_id, at.chart_id, at.amount, at.taxkey, at.transdate,
 
 713                     LEFT JOIN chart c ON (at.chart_id = c.id)
 
 714                     WHERE (trans_id IN (SELECT id FROM ar WHERE NOT invoice))
 
 715                       AND (taxkey IN (8, 9, 18, 19))
 
 717                     ORDER BY trans_id, acc_trans_id|;
 
 719   my $sth      = prepare_execute_query($form, $dbh, $query);
 
 722   while (my $ref = $sth->fetchrow_hashref()) {
 
 723     if ((!scalar @transactions) || ($ref->{trans_id} != $transactions[-1]->[0]->{trans_id})) {
 
 724       push @transactions, [];
 
 727     push @{ $transactions[-1] }, $ref;
 
 732   @transactions = grep { (scalar(@transactions) % 2) == 0 } @transactions;
 
 734   my %taxkey_replacements = (
 
 746     'ap' => { map { $_ => 1 } (2, 3, 12, 13) },
 
 747     'ar' => { map { $_ => 1 } (8, 9, 18, 19) },
 
 750   my @corrections = ();
 
 752   foreach my $transaction (@transactions) {
 
 754     for (my $i = 0; $i < scalar @{ $transaction }; $i += 2) {
 
 755       my ($non_tax_idx, $tax_idx) = abs($transaction->[$i]->{amount}) > abs($transaction->[$i + 1]->{amount}) ? ($i, $i + 1) : ($i + 1, $i);
 
 756       my ($non_tax,     $tax)     = @{ $transaction }[$non_tax_idx, $tax_idx];
 
 758       last if ($non_tax->{link} =~ m/(:?AP|AR)_tax(:?$|:)/);
 
 759       last if ($tax->{link}     !~ m/(:?AP|AR)_tax(:?$|:)/);
 
 761       next if (!$bad_taxkeys{ $non_tax->{module} }->{ $non_tax->{taxkey} });
 
 763       my %all_taxes = $self->{taxkeys}->get_full_tax_info('transdate' => $non_tax->{transdate});
 
 765       push @corrections, ({ 'acc_trans_id' => $non_tax->{acc_trans_id},
 
 766                             'taxkey'       => $taxkey_replacements{$non_tax->{taxkey}},
 
 769                             'acc_trans_id' => $tax->{acc_trans_id},
 
 770                             'taxkey'       => $taxkey_replacements{$non_tax->{taxkey}},
 
 771                             'chart_id'     => $all_taxes{taxkeys}->{ $taxkey_replacements{$non_tax->{taxkey}} }->{taxchart_id},
 
 776   if (scalar @corrections) {
 
 777     my $q_taxkey_only     = qq|UPDATE acc_trans SET taxkey = ? WHERE acc_trans_id = ?|;
 
 778     my $h_taxkey_only     = prepare_query($form, $dbh, $q_taxkey_only);
 
 780     my $q_taxkey_chart_id = qq|UPDATE acc_trans SET taxkey = ?, chart_id = ? WHERE acc_trans_id = ?|;
 
 781     my $h_taxkey_chart_id = prepare_query($form, $dbh, $q_taxkey_chart_id);
 
 783     foreach my $entry (@corrections) {
 
 784       if ($entry->{chart_id}) {
 
 785         do_statement($form, $h_taxkey_chart_id, $q_taxkey_chart_id, $entry->{taxkey}, $entry->{chart_id}, $entry->{acc_trans_id});
 
 787         do_statement($form, $h_taxkey_only, $q_taxkey_only, $entry->{taxkey}, $entry->{acc_trans_id});
 
 791     $h_taxkey_only->finish();
 
 792     $h_taxkey_chart_id->finish();
 
 794     $dbh->commit() unless ($params{dbh});
 
 797   $main::lxdebug->leave_sub();
 
 800 sub fix_invoice_inventory_with_taxkeys {
 
 801   $main::lxdebug->enter_sub();
 
 806   my $myconfig = \%main::myconfig;
 
 807   my $form     = $main::form;
 
 809   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
 
 811   my $query    = qq|SELECT at.*, c.link
 
 813                     LEFT JOIN ar      ON (at.trans_id = ar.id)
 
 814                     LEFT JOIN chart c ON (at.chart_id = c.id)
 
 821                     LEFT JOIN ap      ON (at.trans_id = ap.id)
 
 822                     LEFT JOIN chart c ON (at.chart_id = c.id)
 
 825                     ORDER BY trans_id, acc_trans_id|;
 
 827   my $sth      = prepare_execute_query($form, $dbh, $query);
 
 830   while (my $ref = $sth->fetchrow_hashref()) {
 
 831     if ((!scalar @transactions) || ($ref->{trans_id} != $transactions[-1]->[0]->{trans_id})) {
 
 832       push @transactions, [];
 
 835     push @{ $transactions[-1] }, $ref;
 
 840   my @corrections = ();
 
 842   foreach my $transaction (@transactions) {
 
 843     my @sub_transactions = $self->_group_sub_transactions($transaction);
 
 845     foreach my $sub_transaction (@sub_transactions) {
 
 846       my $is_cogs = first { $_->{link} =~ m/IC_cogs/ } @{ $sub_transaction };
 
 847       next unless ($is_cogs);
 
 849       foreach my $entry (@{ $sub_transaction }) {
 
 850         next if ($entry->{taxkey} == 0);
 
 851         push @corrections, $entry->{acc_trans_id};
 
 857     $query = qq|UPDATE acc_trans SET taxkey = 0 WHERE acc_trans_id = ?|;
 
 858     $sth   = prepare_query($form, $dbh, $query);
 
 860     foreach my $acc_trans_id (@corrections) {
 
 861       do_statement($form, $sth, $query, $acc_trans_id);
 
 866     $dbh->commit() unless ($params{dbh});
 
 870   $main::lxdebug->leave_sub();
 
 873 sub fix_wrong_taxkeys {
 
 874   $main::lxdebug->enter_sub();
 
 879   Common::check_params(\%params, qw(fixes));
 
 881   my $myconfig = \%main::myconfig;
 
 882   my $form     = $main::form;
 
 884   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
 
 886   my $q_taxkey_only  = qq|UPDATE acc_trans SET taxkey = ? WHERE acc_trans_id = ?|;
 
 887   my $h_taxkey_only  = prepare_query($form, $dbh, $q_taxkey_only);
 
 889   my $q_taxkey_chart = qq|UPDATE acc_trans SET taxkey = ?, chart_id = ? WHERE acc_trans_id = ?|;
 
 890   my $h_taxkey_chart = prepare_query($form, $dbh, $q_taxkey_chart);
 
 892   my $q_transdate    = qq|SELECT transdate FROM acc_trans WHERE acc_trans_id = ?|;
 
 893   my $h_transdate    = prepare_query($form, $dbh, $q_transdate);
 
 895   foreach my $fix (@{ $params{fixes} }) {
 
 896     next unless ($fix->{acc_trans_id});
 
 898     do_statement($form, $h_taxkey_only, $q_taxkey_only, conv_i($fix->{taxkey}), conv_i($fix->{acc_trans_id}));
 
 900     next unless ($fix->{tax_entry_acc_trans_id});
 
 902     do_statement($form, $h_transdate, $q_transdate, conv_i($fix->{tax_entry_acc_trans_id}));
 
 903     my ($transdate) = $h_transdate->fetchrow_array();
 
 905     my %all_taxes = $self->{taxkeys}->get_full_tax_info('transdate' => $transdate);
 
 906     my $tax_info  = $all_taxes{taxkeys}->{ $fix->{taxkey} };
 
 908     next unless ($tax_info);
 
 910     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}));
 
 913   $h_taxkey_only->finish();
 
 914   $h_taxkey_chart->finish();
 
 915   $h_transdate->finish();
 
 918   $dbh->commit() unless ($params{dbh});
 
 920   $main::lxdebug->leave_sub();
 
 923 sub delete_transaction {
 
 924   $main::lxdebug->enter_sub();
 
 929   Common::check_params(\%params, qw(trans_id));
 
 931   my $myconfig = \%main::myconfig;
 
 932   my $form     = $main::form;
 
 934   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
 
 936   do_query($form, $dbh, qq|UPDATE ar SET storno_id = NULL WHERE storno_id = ?|, conv_i($params{trans_id}));
 
 937   do_query($form, $dbh, qq|UPDATE ap SET storno_id = NULL WHERE storno_id = ?|, conv_i($params{trans_id}));
 
 938   do_query($form, $dbh, qq|UPDATE gl SET storno_id = NULL WHERE storno_id = ?|, conv_i($params{trans_id}));
 
 940   do_query($form, $dbh, qq|DELETE FROM ar        WHERE id       = ?|, conv_i($params{trans_id}));
 
 941   do_query($form, $dbh, qq|DELETE FROM ap        WHERE id       = ?|, conv_i($params{trans_id}));
 
 942   do_query($form, $dbh, qq|DELETE FROM gl        WHERE id       = ?|, conv_i($params{trans_id}));
 
 943   do_query($form, $dbh, qq|DELETE FROM acc_trans WHERE trans_id = ?|, conv_i($params{trans_id}));
 
 946   $dbh->commit() unless ($params{dbh});
 
 948   $main::lxdebug->leave_sub();