1 package AccTransCorrections;
 
   6 use List::Util qw(first);
 
  19   $self->{taxkeys} = Taxkeys->new();
 
  24 sub _fetch_transactions {
 
  25   $main::lxdebug->enter_sub();
 
  30   my $myconfig = \%main::myconfig;
 
  31   my $form     = $main::form;
 
  33   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
 
  35   my (@where, @values) = ((), ());
 
  37   if ($params{transdate_from}) {
 
  38     push @where,  qq|at.transdate >= ?|;
 
  39     push @values, $params{transdate_from};
 
  42   if ($params{transdate_to}) {
 
  43     push @where,  qq|at.transdate <= ?|;
 
  44     push @values, $params{transdate_to};
 
  47   if ($params{trans_id}) {
 
  48     push @where,  qq|at.trans_id = ?|;
 
  49     push @values, $params{trans_id};
 
  54     $where = 'WHERE ' . join(' AND ', map { "($_)" } @where);
 
  59       c.accno, c.description AS chartdescription, c.charttype, c.category AS chartcategory, c.link AS chartlink,
 
  60       COALESCE(gl.reference, COALESCE(ap.invnumber, ar.invnumber)) AS reference,
 
  61       COALESCE(ap.invoice, COALESCE(ar.invoice, FALSE)) AS invoice,
 
  63         WHEN gl.id IS NOT NULL THEN gl.storno AND (gl.storno_id IS NOT NULL)
 
  64         WHEN ap.id IS NOT NULL THEN ap.storno AND (ap.storno_id IS NOT NULL)
 
  65         ELSE                        ar.storno AND (ar.storno_id IS NOT NULL)
 
  68         WHEN gl.id IS NOT NULL THEN 'gl'
 
  69         WHEN ap.id IS NOT NULL THEN 'ap'
 
  74     LEFT JOIN chart c ON (at.chart_id = c.id)
 
  75     LEFT JOIN gl      ON (at.trans_id = gl.id)
 
  76     LEFT JOIN ap      ON (at.trans_id = ap.id)
 
  77     LEFT JOIN ar      ON (at.trans_id = ar.id)
 
  79     ORDER BY at.trans_id, at.acc_trans_id
 
  82   my @transactions = ();
 
  83   my $last_trans   = undef;
 
  85   foreach my $entry (@{ selectall_hashref_query($form, $dbh, $query, @values) }) {
 
  86     if (!$last_trans || ($last_trans->[0]->{trans_id} != $entry->{trans_id})) {
 
  88       push @transactions, $last_trans;
 
  91     push @{ $last_trans }, $entry;
 
  94   $main::lxdebug->leave_sub();
 
 100   $main::lxdebug->enter_sub();
 
 105   my $transaction = $params{transaction};
 
 106   my $callback    = $params{callback};
 
 108   my $myconfig    = \%main::myconfig;
 
 109   my $form        = $main::form;
 
 129   foreach my $entry (@{ $transaction }) {
 
 130     $entry->{chartlinks} = { map { $_ => 1 } split(m/:/, $entry->{chartlink}) };
 
 131     delete $entry->{chartlink};
 
 134   # Verknüpfungen zwischen Steuerschlüsseln und zum Zeitpunkt der Transaktion
 
 135   # gültigen Steuersätze
 
 136   my %all_taxes = $self->{taxkeys}->get_full_tax_info('transdate' => $transaction->[0]->{transdate});
 
 138   my ($trans_type, $previous_non_tax_entry);
 
 140   my $first_sub_trans = 1;
 
 142   my $storno_mult     = $transaction->[0]->{is_storno} ? -1 : 1;
 
 144   # Aufteilung der Buchungspositionen in Soll-, Habenseite sowie
 
 145   # getrennte Auflistung der Positionen, die auf Steuerkonten gebucht werden.
 
 146   foreach my $entry (@{ $transaction }) {
 
 147     if (!$first_sub_trans && ($entry->{chartlinks}->{AP_paid} || $entry->{chartlinks}->{AR_paid})) {
 
 148       push @{ $data->{payments} }, $entry;
 
 152     my $tax_info = $all_taxes{taxkeys}->{ $entry->{taxkey} };
 
 154       $entry->{taxdescription} = $tax_info->{taxdescription} . ' ' . $form->format_amount($myconfig, $tax_info->{taxrate} * 100) . ' %';
 
 157     if ($entry->{chartlinks}->{AP}) {
 
 159     } elsif ($entry->{chartlinks}->{AR}) {
 
 163     my $idx = 0 < ($entry->{amount} * $storno_mult) ? 'credit' : 'debit';
 
 165     if ($entry->{chartlinks}->{AP_tax} || $entry->{chartlinks}->{AR_tax}) {
 
 166       $data->{$idx}->{tax_sum} += $entry->{amount};
 
 167       push @{ $data->{$idx}->{tax_entries} }, $entry;
 
 169       if ($previous_non_tax_entry) {
 
 170         $previous_non_tax_entry->{tax_entry} = $entry;
 
 171         undef $previous_non_tax_entry;
 
 175       $data->{$idx}->{sum} += $entry->{amount};
 
 176       push @{ $data->{$idx}->{entries} }, $entry;
 
 178       $previous_non_tax_entry = $entry;
 
 181     $sum += $entry->{amount};
 
 183     if (abs($sum) < 0.02) {
 
 185       $first_sub_trans = 0;
 
 189   # Alle Einträge entfernen, die die Gegenkonten zu Zahlungsein- und
 
 190   # -ausgängen darstellen.
 
 191   foreach my $payment (@{ $data->{payments} }) {
 
 192     my $idx = 0 < $payment->{amount} ? 'debit' : 'credit';
 
 194     foreach my $i (0 .. scalar(@{ $data->{$idx}->{entries} }) - 1) {
 
 195       my $entry = $data->{$idx}->{entries}->[$i];
 
 197       next if ((($payment->{amount} * -1) != $entry->{amount}) || ($payment->{transdate} ne $entry->{transdate}));
 
 199       splice @{ $data->{$idx}->{entries} }, $i, 1;
 
 205   delete $data->{payments};
 
 207   map { $data->{$_}->{num} = scalar @{ $data->{$_}->{entries} } } qw(credit debit);
 
 209   my $info   = $transaction->[0];
 
 210   my $script = ($info->{module} eq 'ar') && $info->{invoice} ? 'is'
 
 211              : ($info->{module} eq 'ap') && $info->{invoice} ? 'ir'
 
 216     'trans_type'    => $trans_type,
 
 217     'all_taxes'     => { %all_taxes },
 
 218     'transaction'   => $transaction,
 
 219     'full_analysis' => $params{full_analysis},
 
 222       'link'        => $script . ".pl?action=edit${callback}&id=" . $info->{trans_id},
 
 226   $main::lxdebug->leave_sub();
 
 231 sub _group_sub_transactions {
 
 232   $main::lxdebug->enter_sub();
 
 235   my $transaction      = shift;
 
 237   my @sub_transactions = ();
 
 240   foreach my $i (0 .. scalar(@{ $transaction }) - 1) {
 
 241     my $entry = $transaction->[$i];
 
 243     if (abs($sum) <= 0.01) {
 
 244       push @sub_transactions, [];
 
 247     $sum += $entry->{amount};
 
 249     push @{ $sub_transactions[-1] }, $entry;
 
 252   $main::lxdebug->leave_sub();
 
 254   return @sub_transactions;
 
 257 # Problemfall: Verkaufsrechnungen, bei denen Buchungen auf Warenbestandskonten
 
 258 # mit Steuerschlüssel != 0 durchgeführt wurden. Richtig wäre, dass alle
 
 259 # Steuerschlüssel für solche Warenbestandsbuchungen 0 sind.
 
 260 sub _check_trans_invoices_inventory_with_taxkeys {
 
 261   $main::lxdebug->enter_sub();
 
 266   # ist nur für bestandsmethode notwendig. bei der Aufwandsmethode
 
 267   # können Warenkonten mit Steuerschlüssel sein (5400 in SKR04)
 
 268   return 0 if $::instance_conf->get_inventory_system eq 'periodic';
 
 270   if (!$params{transaction}->[0]->{invoice}) {
 
 271     $main::lxdebug->leave_sub();
 
 275   my @sub_transactions = $self->_group_sub_transactions($params{transaction});
 
 277   foreach my $sub_transaction (@sub_transactions) {
 
 278     my $is_cogs = first { $_->{chartlinks}->{IC_cogs} } @{ $sub_transaction };
 
 279     next unless ($is_cogs);
 
 281     my $needs_fixing = first { $_->{taxkey} != 0 } @{ $sub_transaction };
 
 282     next unless ($needs_fixing);
 
 284     $params{problem}->{type} = 'invoice_inventory_with_taxkeys';
 
 285     push @{ $self->{invoice_inventory_taxkey_problems} }, $params{problem};
 
 287     $main::lxdebug->leave_sub();
 
 292   $main::lxdebug->leave_sub();
 
 297 # Problemfall: Verkaufsrechnungen, bei denen Steuern verbucht wurden, obwohl
 
 298 # kein Steuerschlüssel eingetragen ist.
 
 299 sub _check_missing_taxkeys_in_invoices {
 
 300   $::lxdebug->enter_sub;
 
 304   my $transaction = $params{transaction};
 
 305   my $found_broken = 0;
 
 307   $::lxdebug->leave_sub and return 0
 
 308     if    !$transaction->[0]->{invoice};
 
 310   my @sub_transactions = $self->_group_sub_transactions($transaction);
 
 312   for my $sub_transaction (@sub_transactions) {
 
 313     $::lxdebug->leave_sub and return 0
 
 314       if    _is_split_transaction($sub_transaction)
 
 315          || _is_simple_transaction($sub_transaction);
 
 317     my $split_side_entries = _get_splitted_side($sub_transaction);
 
 320     for my $entry (@{ $split_side_entries }) {
 
 321       my $is_tax = grep { m/(?:AP_tax|AR_tax)/ } keys %{ $entry->{chartlinks} };
 
 323       $num_tax_rows++   if  $is_tax;
 
 324       $num_taxed_rows++ if !$is_tax && $entry->{tax_key} != 0;
 
 327     # now if this has tax rows but NO taxed rows, something is wrong.
 
 328     if ($num_tax_rows > 0 && $num_taxed_rows == 0) {
 
 329       $params{problem}->{type} = 'missing_taxkeys_in_invoices';
 
 330       push @{ $self->{missing_taxkeys_in_invoices} ||= [] }, $params{problem};
 
 335   $::lxdebug->leave_sub;
 
 337   return $found_broken;
 
 340 # Problemfall: Kreditorenbuchungen, bei denen mit Umsatzsteuerschlüsseln
 
 341 # gebucht wurde und Debitorenbuchungen, bei denen mit Vorsteuerschlüsseln
 
 343 sub _check_trans_ap_ar_wrong_taxkeys {
 
 344   $main::lxdebug->enter_sub();
 
 351   if (!$params{transaction}->[0]->{invoice}
 
 352       && ((   ($params{transaction}->[0]->{module} eq 'ap')
 
 353           && (first { my $taxkey = $_->{taxkey}; first { $taxkey == $_ } (2, 3, 12, 13) } @{ $params{transaction} }))
 
 355          (   ($params{transaction}->[0]->{module} eq 'ar')
 
 356           && (first { my $taxkey = $_->{taxkey}; first { $taxkey == $_ } (8, 9, 18, 19) } @{ $params{transaction} })))) {
 
 357     $params{problem}->{type} = 'ap_ar_wrong_taxkeys';
 
 358     push @{ $self->{ap_ar_taxkey_problems} }, $params{problem};
 
 363   $main::lxdebug->leave_sub();
 
 368 # Problemfall: Splitbuchungen, die mehrere Haben- und Sollkonten ansprechen.
 
 369 # Aber nur für Debitoren- und Kreditorenbuchungen, weil das bei Einkaufs- und
 
 370 # Verkaufsrechnungen hingegen völlig normal ist.
 
 371 sub _check_trans_split_multiple_credit_and_debit {
 
 372   $main::lxdebug->enter_sub();
 
 379   if (   !$params{transaction}->[0]->{invoice}
 
 380       && (1 < $params{data}->{credit}->{num})
 
 381       && (1 < $params{data}->{debit}->{num})) {
 
 382     $params{problem}->{type} = 'split_multiple_credit_and_debit';
 
 383     push @{ $self->{problems} }, $params{problem};
 
 388   $main::lxdebug->leave_sub();
 
 393 # Problemfall: Buchungen, bei denen Steuersummen nicht mit den Summen
 
 394 # übereinstimmen, die nach ausgewähltem Steuerschlüssel hätten auftreten müssen.
 
 395 sub _check_trans_wrong_taxkeys {
 
 396   $main::lxdebug->enter_sub();
 
 401   my $form        = $main::form;
 
 403   my %data        = %{ $params{data} };
 
 404   my $transaction = $params{transaction};
 
 406   if (   $transaction->[0]->{invoice}
 
 407       || $transaction->[0]->{ob_transaction}
 
 408       || $transaction->[0]->{cb_transaction}
 
 409       || (!scalar @{ $data{credit}->{entries} } && !scalar @{ $data{debit}->{entries} })
 
 410       || (   ($transaction->[0]->{module} eq 'gl')
 
 411           && (!scalar @{ $data{credit}->{entries} } || !scalar @{ $data{debit}->{entries} }))) {
 
 412     $main::lxdebug->leave_sub();
 
 418   my ($side, $other_side);
 
 419   if (   (grep { $_->{taxkey} * 1 } @{ $data{credit}->{entries} })
 
 420       || (scalar @{ $data{credit}->{tax_entries} })) {
 
 422     $other_side = 'debit';
 
 424   } elsif (   (grep { $_->{taxkey} * 1 } @{ $data{debit}->{entries} })
 
 425            || (scalar @{ $data{debit}->{tax_entries} })) {
 
 427     $other_side = 'credit';
 
 431     $main::lxdebug->leave_sub();
 
 435   my $expected_tax          = 0;
 
 436   my %num_entries_per_chart = ();
 
 437   my $num_taxed_entries     = 0;
 
 439   foreach my $entry (@{ $data{$side}->{entries} }) {
 
 440     my $taxinfo             = $params{all_taxes}->{taxkeys}->{$entry->{taxkey}} || { };
 
 441     $entry->{expected_tax}  = $entry->{amount} * $taxinfo->{taxrate};
 
 442     $expected_tax          += $entry->{expected_tax};
 
 444     $num_taxed_entries++ if ($taxinfo->{taxrate} * 1);
 
 446     my $chart_key = $entry->{chart_id} . "-" . $entry->{taxkey};
 
 447     $num_entries_per_chart{$chart_key} ||= 0;
 
 448     $num_entries_per_chart{$chart_key}++;
 
 451 #   $main::lxdebug->message(0, "side $side trans_id $transaction->[0]->{trans_id} expected tax $expected_tax actual tax $data{$side}->{tax_sum}");
 
 453   if (abs($expected_tax - $data{$side}->{tax_sum}) >= (0.01 * ($num_taxed_entries + 1))) {
 
 454     if ($params{full_analysis}) {
 
 455       my $storno_mult = $data{$side}->{entries}->[0]->{is_storno} ? -1 : 1;
 
 457       foreach my $entry (@{ $data{$other_side}->{entries} }) {
 
 458         $entry->{display_amount} = $form->round_amount(abs($entry->{amount}) * $storno_mult, 2);
 
 461       foreach my $entry (@{ $data{$side}->{entries} }) {
 
 462         $entry->{actual_tax}              = $form->round_amount(abs($entry->{tax_entry} ? $entry->{tax_entry}->{amount} : 0), 2);
 
 463         $entry->{expected_tax}            = $form->round_amount(abs($entry->{expected_tax}), 2);
 
 464         $entry->{taxkey_error}            =    ( $entry->{taxkey} && !$entry->{tax_entry})
 
 465                                             || (!$entry->{taxkey} &&  $entry->{tax_entry})
 
 466                                             || (abs($entry->{expected_tax} - $entry->{actual_tax}) >= 0.02);
 
 467         $entry->{tax_entry_acc_trans_id}  = $entry->{tax_entry}->{acc_trans_id};
 
 468         delete $entry->{tax_entry};
 
 470         $entry->{display_amount}       = $form->round_amount(abs($entry->{amount}) * $storno_mult, 2);
 
 471         $entry->{display_actual_tax}   = $entry->{actual_tax}   * $storno_mult;
 
 472         $entry->{display_expected_tax} = $entry->{expected_tax} * $storno_mult;
 
 474         if ($entry->{taxkey_error}) {
 
 475           $self->{negative_taxkey_filter} ||= {
 
 476             'ar' => { map { $_ => 1 } (   8, 9, 18, 19) },
 
 477             'ap' => { map { $_ => 1 } (1, 2, 3, 12, 13) },
 
 481           $entry->{correct_taxkeys} = [];
 
 483           my %all_taxes = $self->{taxkeys}->get_full_tax_info('transdate' => $entry->{transdate});
 
 485           foreach my $taxkey (sort { $a <=> $b } keys %{ $all_taxes{taxkeys} }) {
 
 486             next if ($self->{negative_taxkey_filter}->{ $entry->{module} }->{$taxkey});
 
 488             my $tax_info = $all_taxes{taxkeys}->{$taxkey};
 
 490             next if ((!$tax_info || (0 == $tax_info->{taxrate} * 1)) && $entry->{tax_entry_acc_trans_id});
 
 492             push @{ $entry->{correct_taxkeys} }, {
 
 494               'tax'         => $form->round_amount(abs($entry->{amount}) * $tax_info->{taxrate}, 2),
 
 495               'description' => sprintf("%s %d%%", $tax_info->{taxdescription}, int($tax_info->{taxrate} * 100)),
 
 502     if (first { $_ > 1 } values %num_entries_per_chart) {
 
 503       $params{problem}->{type} = 'wrong_taxkeys';
 
 505       $params{problem}->{type} = 'wrong_taxes';
 
 508     $params{problem}->{acc_trans} = { %data };
 
 509     push @{ $self->{problems} }, $params{problem};
 
 514   $main::lxdebug->leave_sub();
 
 519 # Inaktiver Code für das Erraten möglicher Verteilungen von
 
 520 # Steuerschlüsseln. Deaktiviert, weil er exponentiell Zeit
 
 523 #       if (abs($expected_tax - $data{$side}->{tax_sum}) >= 0.02) {
 
 524 #         my @potential_taxkeys = $trans_type eq 'AP' ? (0, 8, 9) : (0, 1, 2, 3);
 
 526 #         $main::lxdebug->dump(0, "pota", \@potential_taxkeys);
 
 528 #         # Über alle Kombinationen aus Buchungssätzen und potenziellen Steuerschlüsseln
 
 529 #         # iterieren und jeweils die Summe ermitteln.
 
 530 #         my $num_entries    = scalar @{ $data{$side}->{entries} };
 
 531 #         my @taxkey_indices = (0) x $num_entries;
 
 533 #         my @solutions      = ();
 
 535 #         my $start_time     = time();
 
 537 #         $main::lxdebug->message(0, "num_entries $num_entries");
 
 539 #         while ($num_entries == scalar @taxkey_indices) {
 
 540 #           my @tax_cache = ();
 
 542 #           # Berechnen der Steuersumme für die aktuell angenommenen Steuerschlüssel.
 
 544 #           foreach my $i (0 .. $num_entries - 1) {
 
 545 #             my $taxkey      = $potential_taxkeys[$taxkey_indices[$i]];
 
 546 #             my $entry       = $data{$side}->{entries}->[$i];
 
 547 #             my $taxinfo     = $all_taxes{taxkeys}->{ $taxkey } || { };
 
 548 #             $tax_cache[$i]  = $entry->{amount} * $taxinfo->{taxrate};
 
 549 #             $tax_sum       += $tax_cache[$i];
 
 552 #           # Entspricht die Steuersumme mit den aktuell angenommenen Steuerschlüsseln
 
 553 #           # der verbuchten Steuersumme? Wenn ja, dann ist das eine potenzielle
 
 555 #           if (abs($tax_sum - $data{$side}->{tax_sum}) < 0.02) {
 
 557 #               'taxkeys' => [ @potential_taxkeys[@taxkey_indices] ],
 
 558 #               'taxes'   => [ @tax_cache ],
 
 562 #           # Weiterzählen der Steuerschlüsselindices zum Interieren über
 
 563 #           # alle möglichen Kombinationen.
 
 566 #             $taxkey_indices[$i]++;
 
 567 #             last if ($taxkey_indices[$i] < scalar @potential_taxkeys);
 
 569 #             $taxkey_indices[$i] = 0;
 
 574 #           if (($now - $start_time) >= 5) {
 
 575 #             $main::lxdebug->message(0, "  " . join("", @taxkey_indices));
 
 576 #             $start_time = $now;
 
 580 #         foreach my $solution (@solutions) {
 
 581 #           $solution->{rows}    = [];
 
 582 #           $solution->{changes} = [];
 
 585 #           foreach my $i (0 .. $num_entries - 1) {
 
 586 #             if ($solution->{taxes}->[$i]) {
 
 587 #               my $tax_rounded          = $form->round_amount($solution->{taxes}->[$i] + $error, 2);
 
 588 #               $error                   = $solution->{taxes}->[$i] + $error - $tax_rounded;
 
 589 #               $solution->{taxes}->[$i] = $tax_rounded;
 
 592 #             my $entry     = $data{$side}->{entries}->[$i];
 
 593 #             my $tax_entry = $all_taxes{taxkeys}->{ $solution->{taxkeys}->[$i] };
 
 595 #             push @{ $solution->{rows} }, {
 
 598 #               'taxamount' => $solution->{taxes}->[$i],
 
 601 #             $solution->{rows}->[$i]->{taxdescription} .= ' ' . $form->format_amount(\%myconfig, $tax_entry->{taxrate} * 100) . ' %';
 
 603 #             push @{ $solution->{changes} }, {
 
 604 #               'acc_trans_id'    => $entry->{acc_trans_id},
 
 605 #               'taxkey' => $solution->{taxkeys}->[$i],
 
 609 #           push @{ $solution->{rows} }, @{ $data{$other_side}->{entries} };
 
 611 #           delete @{ $solution }{ qw(taxes taxkeys) };
 
 614 #         $problem->{type}      = 'wrong_taxkeys';
 
 615 #         $problem->{solutions} = [ @solutions ];
 
 616 #         $problem->{acc_trans} = { %data };
 
 617 #         push @problems, $problem;
 
 623   $main::lxdebug->enter_sub();
 
 628   my $myconfig     = \%main::myconfig;
 
 629   my $form         = $main::form;
 
 631   my $dbh          = $params{dbh} || $form->get_standard_dbh($myconfig);
 
 633   my @transactions = $self->_fetch_transactions(%params, 'dbh' => $dbh);
 
 635   if (!scalar @transactions) {
 
 636     $main::lxdebug->leave_sub();
 
 640   my $callback = $params{callback} ? '&callback=' . $params{callback} : '';
 
 642   $self->{problems}                          = [];
 
 643   $self->{ap_ar_taxkey_problems}             = [];
 
 644   $self->{invoice_inventory_taxkey_problems} = [];
 
 646   foreach my $transaction (@transactions) {
 
 647     my %common_args = $self->_prepare_data('transaction' => $transaction, 'callback' => $callback, 'full_analysis' => $params{full_analysis});
 
 649     next if ($self->_check_trans_ap_ar_wrong_taxkeys(%common_args));
 
 650     next if ($self->_check_trans_invoices_inventory_with_taxkeys(%common_args));
 
 651     next if ($self->_check_trans_split_multiple_credit_and_debit(%common_args));
 
 652     next if ($self->_check_trans_wrong_taxkeys(%common_args));
 
 655   my @problems = @{ $self->{problems} };
 
 657   map { $self->{$_} ||= [] } qw(ap_ar_taxkey_problems invoice_inventory_taxkey_problems missing_taxkeys_in_invoices);
 
 659   if (0 != scalar @{ $self->{ap_ar_taxkey_problems} }) {
 
 661       'type'        => 'ap_ar_wrong_taxkeys',
 
 662       'ap_problems' => [ grep { $_->{data}->{module} eq 'ap' } @{ $self->{ap_ar_taxkey_problems} } ],
 
 663       'ar_problems' => [ grep { $_->{data}->{module} eq 'ar' } @{ $self->{ap_ar_taxkey_problems} } ],
 
 665     unshift @problems, $problem;
 
 668   if (0 != scalar @{ $self->{invoice_inventory_taxkey_problems} }) {
 
 670       'type'        => 'invoice_inventory_with_taxkeys',
 
 671       'ap_problems' => [ grep { $_->{data}->{module} eq 'ap' } @{ $self->{invoice_inventory_taxkey_problems} } ],
 
 672       'ar_problems' => [ grep { $_->{data}->{module} eq 'ar' } @{ $self->{invoice_inventory_taxkey_problems} } ],
 
 674     unshift @problems, $problem;
 
 677   if (0 != scalar @{ $self->{missing_taxkeys_in_invoices} }) {
 
 679       'type'        => 'missing_taxkeys_in_invoices',
 
 680       'ap_problems' => [ grep { $_->{data}->{module} eq 'ap' } @{ $self->{missing_taxkeys_in_invoices} } ],
 
 681       'ar_problems' => [ grep { $_->{data}->{module} eq 'ar' } @{ $self->{missing_taxkeys_in_invoices} } ],
 
 683     unshift @problems, $problem;
 
 686   $main::lxdebug->leave_sub();
 
 688 #  $::lxdebug->dump(0, 'problems:', \@problems);
 
 693 sub fix_ap_ar_wrong_taxkeys {
 
 694   $main::lxdebug->enter_sub();
 
 699   my $myconfig = \%main::myconfig;
 
 700   my $form     = $main::form;
 
 702   my $dbh      = $params{dbh} || SL::DB->client->dbh;
 
 704   my $query    = qq|SELECT 'ap' AS module,
 
 705                       at.acc_trans_id, at.trans_id, at.chart_id, at.amount, at.taxkey, at.transdate,
 
 708                     LEFT JOIN chart c ON (at.chart_id = c.id)
 
 709                     WHERE (trans_id IN (SELECT id FROM ap WHERE NOT invoice))
 
 710                       AND (taxkey IN (2, 3, 12, 13))
 
 714                     SELECT 'ar' AS module,
 
 715                       at.acc_trans_id, at.trans_id, at.chart_id, at.amount, at.taxkey, at.transdate,
 
 718                     LEFT JOIN chart c ON (at.chart_id = c.id)
 
 719                     WHERE (trans_id IN (SELECT id FROM ar WHERE NOT invoice))
 
 720                       AND (taxkey IN (8, 9, 18, 19))
 
 722                     ORDER BY trans_id, acc_trans_id|;
 
 724   my $sth      = prepare_execute_query($form, $dbh, $query);
 
 727   while (my $ref = $sth->fetchrow_hashref()) {
 
 728     if ((!scalar @transactions) || ($ref->{trans_id} != $transactions[-1]->[0]->{trans_id})) {
 
 729       push @transactions, [];
 
 732     push @{ $transactions[-1] }, $ref;
 
 737   @transactions = grep { (scalar(@transactions) % 2) == 0 } @transactions;
 
 739   my %taxkey_replacements = (
 
 751     'ap' => { map { $_ => 1 } (2, 3, 12, 13) },
 
 752     'ar' => { map { $_ => 1 } (8, 9, 18, 19) },
 
 755   my @corrections = ();
 
 757   foreach my $transaction (@transactions) {
 
 759     for (my $i = 0; $i < scalar @{ $transaction }; $i += 2) {
 
 760       my ($non_tax_idx, $tax_idx) = abs($transaction->[$i]->{amount}) > abs($transaction->[$i + 1]->{amount}) ? ($i, $i + 1) : ($i + 1, $i);
 
 761       my ($non_tax,     $tax)     = @{ $transaction }[$non_tax_idx, $tax_idx];
 
 763       last if ($non_tax->{link} =~ m/(:?AP|AR)_tax(:?$|:)/);
 
 764       last if ($tax->{link}     !~ m/(:?AP|AR)_tax(:?$|:)/);
 
 766       next if (!$bad_taxkeys{ $non_tax->{module} }->{ $non_tax->{taxkey} });
 
 768       my %all_taxes = $self->{taxkeys}->get_full_tax_info('transdate' => $non_tax->{transdate});
 
 770       push @corrections, ({ 'acc_trans_id' => $non_tax->{acc_trans_id},
 
 771                             'taxkey'       => $taxkey_replacements{$non_tax->{taxkey}},
 
 774                             'acc_trans_id' => $tax->{acc_trans_id},
 
 775                             'taxkey'       => $taxkey_replacements{$non_tax->{taxkey}},
 
 776                             'chart_id'     => $all_taxes{taxkeys}->{ $taxkey_replacements{$non_tax->{taxkey}} }->{taxchart_id},
 
 781   if (scalar @corrections) {
 
 782     SL::DB->client->with_transaction(sub {
 
 783       my $q_taxkey_only     = qq|UPDATE acc_trans SET taxkey = ? WHERE acc_trans_id = ?|;
 
 784       my $h_taxkey_only     = prepare_query($form, $dbh, $q_taxkey_only);
 
 786       my $q_taxkey_chart_id = qq|UPDATE acc_trans SET taxkey = ?, chart_id = ? WHERE acc_trans_id = ?|;
 
 787       my $h_taxkey_chart_id = prepare_query($form, $dbh, $q_taxkey_chart_id);
 
 789       foreach my $entry (@corrections) {
 
 790         if ($entry->{chart_id}) {
 
 791           do_statement($form, $h_taxkey_chart_id, $q_taxkey_chart_id, $entry->{taxkey}, $entry->{chart_id}, $entry->{acc_trans_id});
 
 793           do_statement($form, $h_taxkey_only, $q_taxkey_only, $entry->{taxkey}, $entry->{acc_trans_id});
 
 797       $h_taxkey_only->finish();
 
 798       $h_taxkey_chart_id->finish();
 
 800     }) or do { die SL::DB->client->error };
 
 803   $main::lxdebug->leave_sub();
 
 806 sub fix_invoice_inventory_with_taxkeys {
 
 807   $main::lxdebug->enter_sub();
 
 812   # ist nur für bestandsmethode notwendig. bei der Aufwandsmethode
 
 813   # können Warenkonten mit Steuerschlüssel sein (5400 in SKR04)
 
 814   return 0 if $::instance_conf->get_inventory_system eq 'periodic';
 
 816   my $myconfig = \%main::myconfig;
 
 817   my $form     = $main::form;
 
 819   my $dbh      = $params{dbh} || SL::DB->client->dbh;
 
 821   my $query    = qq|SELECT at.*, c.link
 
 823                     LEFT JOIN ar      ON (at.trans_id = ar.id)
 
 824                     LEFT JOIN chart c ON (at.chart_id = c.id)
 
 831                     LEFT JOIN ap      ON (at.trans_id = ap.id)
 
 832                     LEFT JOIN chart c ON (at.chart_id = c.id)
 
 835                     ORDER BY trans_id, acc_trans_id|;
 
 837   my $sth      = prepare_execute_query($form, $dbh, $query);
 
 840   while (my $ref = $sth->fetchrow_hashref()) {
 
 841     if ((!scalar @transactions) || ($ref->{trans_id} != $transactions[-1]->[0]->{trans_id})) {
 
 842       push @transactions, [];
 
 845     push @{ $transactions[-1] }, $ref;
 
 850   my @corrections = ();
 
 852   foreach my $transaction (@transactions) {
 
 853     my @sub_transactions = $self->_group_sub_transactions($transaction);
 
 855     foreach my $sub_transaction (@sub_transactions) {
 
 856       my $is_cogs = first { $_->{link} =~ m/IC_cogs/ } @{ $sub_transaction };
 
 857       next unless ($is_cogs);
 
 859       foreach my $entry (@{ $sub_transaction }) {
 
 860         next if ($entry->{taxkey} == 0);
 
 861         push @corrections, $entry->{acc_trans_id};
 
 867     SL::DB->client->with_transaction(sub {
 
 868       $query = qq|UPDATE acc_trans SET taxkey = 0 WHERE acc_trans_id = ?|;
 
 869       $sth   = prepare_query($form, $dbh, $query);
 
 871       foreach my $acc_trans_id (@corrections) {
 
 872         do_statement($form, $sth, $query, $acc_trans_id);
 
 877     }) or do { die SL::DB->client->error };
 
 880   $main::lxdebug->leave_sub();
 
 883 sub fix_wrong_taxkeys {
 
 884   $main::lxdebug->enter_sub();
 
 889   Common::check_params(\%params, qw(fixes));
 
 891   my $myconfig = \%main::myconfig;
 
 892   my $form     = $main::form;
 
 894   my $dbh      = $params{dbh} || SL::DB->client->dbh;
 
 896   SL::DB->client->with_transaction(sub {
 
 897     my $q_taxkey_only  = qq|UPDATE acc_trans SET taxkey = ? WHERE acc_trans_id = ?|;
 
 898     my $h_taxkey_only  = prepare_query($form, $dbh, $q_taxkey_only);
 
 900     my $q_taxkey_chart = qq|UPDATE acc_trans SET taxkey = ?, chart_id = ? WHERE acc_trans_id = ?|;
 
 901     my $h_taxkey_chart = prepare_query($form, $dbh, $q_taxkey_chart);
 
 903     my $q_transdate    = qq|SELECT transdate FROM acc_trans WHERE acc_trans_id = ?|;
 
 904     my $h_transdate    = prepare_query($form, $dbh, $q_transdate);
 
 906     foreach my $fix (@{ $params{fixes} }) {
 
 907       next unless ($fix->{acc_trans_id});
 
 909       do_statement($form, $h_taxkey_only, $q_taxkey_only, conv_i($fix->{taxkey}), conv_i($fix->{acc_trans_id}));
 
 911       next unless ($fix->{tax_entry_acc_trans_id});
 
 913       do_statement($form, $h_transdate, $q_transdate, conv_i($fix->{tax_entry_acc_trans_id}));
 
 914       my ($transdate) = $h_transdate->fetchrow_array();
 
 916       my %all_taxes = $self->{taxkeys}->get_full_tax_info('transdate' => $transdate);
 
 917       my $tax_info  = $all_taxes{taxkeys}->{ $fix->{taxkey} };
 
 919       next unless ($tax_info);
 
 921       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}));
 
 924     $h_taxkey_only->finish();
 
 925     $h_taxkey_chart->finish();
 
 926     $h_transdate->finish();
 
 928   }) or do { die SL::DB->client->error };
 
 930   $main::lxdebug->leave_sub();
 
 933 sub delete_transaction {
 
 934   $main::lxdebug->enter_sub();
 
 939   Common::check_params(\%params, qw(trans_id));
 
 941   my $myconfig = \%main::myconfig;
 
 942   my $form     = $main::form;
 
 944   my $dbh      = $params{dbh} || SL::DB->client->dbh;
 
 946   SL::DB->client->with_transaction(sub {
 
 947     do_query($form, $dbh, qq|UPDATE ar SET storno_id = NULL WHERE storno_id = ?|, conv_i($params{trans_id}));
 
 948     do_query($form, $dbh, qq|UPDATE ap SET storno_id = NULL WHERE storno_id = ?|, conv_i($params{trans_id}));
 
 949     do_query($form, $dbh, qq|UPDATE gl SET storno_id = NULL WHERE storno_id = ?|, conv_i($params{trans_id}));
 
 951     do_query($form, $dbh, qq|DELETE FROM ar        WHERE id       = ?|, conv_i($params{trans_id}));
 
 952     do_query($form, $dbh, qq|DELETE FROM ap        WHERE id       = ?|, conv_i($params{trans_id}));
 
 953     do_query($form, $dbh, qq|DELETE FROM gl        WHERE id       = ?|, conv_i($params{trans_id}));
 
 954     do_query($form, $dbh, qq|DELETE FROM acc_trans WHERE trans_id = ?|, conv_i($params{trans_id}));
 
 956   }) or do { die SL::DB->client->error };
 
 958   $main::lxdebug->leave_sub();