Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision d8275f6e

Von Jan Büren vor mehr als 2 Jahren hinzugefügt

  • ID d8275f6e998ac5f51c6edd786f6e4ae9db6f577f
  • Vorgänger ee2a0ae9
  • Nachfolger 91fef42d

Payment-Helper Skonto verbuchen mit Steuerkorrektur

tax_and_amount_by_tax_id ausgelagert für ar und ap in SalesPurchaseInvoice.
pay_invoice mit skonto erwartet die banktransaction.id
Invoice und PurchaseInvoice bindet den SPI Helper ein
Alte Methode skonto_charts noch im Payment-Helper drin.
Ferner auskommentierte Debug-Statements und auskommtiert ArGl, ApGl
stabile Anbindung an ARAP (nicht notwendig, da mit BankTransationAccTrans
verknüpft).

Unterschiede anzeigen:

SL/Controller/BankTransaction.pm
680 680
                          source        => $source,
681 681
                          memo          => $memo,
682 682
                          skonto_amount => $free_skonto_amount,
683
                          bt_id         => $bt_id,
683 684
                          transdate     => $bank_transaction->valutadate->to_kivitendo);
684 685
    # ... and record the origin via BankTransactionAccTrans
685 686
    if (scalar(@acc_ids) < 2) {
SL/DB/Helper/Payment.pm
4 4

  
5 5
use parent qw(Exporter);
6 6
our @EXPORT = qw(pay_invoice);
7
our @EXPORT_OK = qw(skonto_date skonto_charts amount_less_skonto within_skonto_period percent_skonto reference_account reference_amount open_amount open_percent remaining_skonto_days skonto_amount check_skonto_configuration valid_skonto_amount get_payment_suggestions validate_payment_type open_sepa_transfer_amount get_payment_select_options_for_bank_transaction exchangerate forex);
7
our @EXPORT_OK = qw(skonto_date skonto_charts amount_less_skonto within_skonto_period percent_skonto reference_account reference_amount open_amount open_percent remaining_skonto_days skonto_amount check_skonto_configuration valid_skonto_amount get_payment_suggestions validate_payment_type open_sepa_transfer_amount get_payment_select_options_for_bank_transaction exchangerate forex _skonto_charts_and_tax_correction);
8 8
our %EXPORT_TAGS = (
9 9
  "ALL" => [@EXPORT, @EXPORT_OK],
10 10
);
......
39 39

  
40 40
  # check for required parameters and optional params depending on payment_type
41 41
  Common::check_params(\%params, qw(chart_id transdate));
42
  Common::check_params(\%params, qw(bt_id)) unless $params{payment_type} eq 'without_skonto';
42 43
  if ( $params{'payment_type'} eq 'without_skonto' && abs($params{'amount'}) < 0) {
43 44
    croak "invalid amount for payment_type 'without_skonto': $params{'amount'}\n";
44 45
  }
......
220 221
      if ( $params{payment_type} eq 'with_skonto_pt' ) {
221 222
        $total_skonto_amount = $self->skonto_amount;
222 223
      } elsif ( $params{payment_type} eq 'difference_as_skonto' ) {
224
        # only used for tests. no real code calls this payment_type!
223 225
        $total_skonto_amount = $self->open_amount;
224 226
      } elsif ( $params{payment_type} eq 'free_skonto') {
225 227
        $total_skonto_amount = $params{skonto_amount};
226 228
      }
227
      my @skonto_bookings = $self->skonto_charts($total_skonto_amount);
228

  
229
      my @skonto_bookings = $self->_skonto_charts_and_tax_correction(amount => $total_skonto_amount, bt_id => $params{bt_id},
230
                                                                     transdate_obj => $transdate_obj, memo => $params{memo},
231
                                                                     source => $params{source});
229 232
      # error checking:
230 233
      if ( $params{payment_type} eq 'difference_as_skonto' ) {
231 234
        my $calculated_skonto_sum  = sum map { $_->{skonto_amount} } @skonto_bookings;
......
235 238
      my $reference_amount = $total_skonto_amount;
236 239

  
237 240
      # create an acc_trans entry for each result of $self->skonto_charts
241
      # TODO create internal sub _skonto_bookings
238 242
      foreach my $skonto_booking ( @skonto_bookings ) {
239 243
        next unless $skonto_booking->{'chart_id'};
240 244
        next unless $skonto_booking->{'skonto_amount'} != 0;
......
566 570

  
567 571
  return $open_sepa_amount || 0;
568 572

  
569
};
573
}
574

  
575
sub _skonto_charts_and_tax_correction {
576
  my ($self, %params)   = @_;
577
  my $amount = $params{amount} || $self->skonto_amount;
578

  
579
  croak "no amount passed to skonto_charts"                    unless abs(_round($amount)) >= 0.01;
580
  croak "no banktransaction.id passed to skonto_charts"        unless $params{bt_id};
581
  croak "no banktransaction.transdate passed to skonto_charts" unless ref $params{transdate_obj} eq 'DateTime';
582
  #$main::lxdebug->message(0, 'id der transaktion' . $params{bt_id});
583
  #$main::lxdebug->message(0, 'wert des skontos:' . $amount);
584
  my $is_sales = $self->is_sales;
585
  my (@skonto_charts, $inv_calc, $total_skonto_rounded);
586
  $inv_calc = $self->get_tax_and_amount_by_tax_chart_id();
587
  #$main::lxdebug->message(0, 'lulu' . Dumper($inv_calc));
588
  while (my ($tax_chart_id, $entry) = each %{ $inv_calc } ) {  # foreach tax key = tax.id
589
    #$main::lxdebug->message(0, 'was hier:' . $tax_chart_id);
590
    my $tax = SL::DB::Manager::Tax->find_by(id => $entry->{tax_id}) || die "Can't find tax with id " . $tax_chart_id;
591
    die t8('no skonto_chart configured for taxkey #1 : #2 : #3', $tax->taxkey, $tax->taxdescription , $tax->rate * 100)
592
      unless $is_sales ? ref $tax->skonto_sales_chart : ref $tax->skonto_purchase_chart;
593
    #$main::lxdebug->message(0, 'was dort:' . $tax->id);
594
    my $transaction_net_skonto_percent = abs($entry->{netamount} / $self->amount);
595
    my $skonto_netamount_unrounded     = abs($amount * $transaction_net_skonto_percent);
596
    #$main::lxdebug->message(0, 'ungerundet netto:' . $skonto_netamount_unrounded);
597
    # divide for tax
598
    my $transaction_tax_skonto_percent = abs($entry->{tax} / $self->amount);
599
    my $skonto_taxamount_unrounded     = abs($amount * $transaction_tax_skonto_percent);
600
    #$main::lxdebug->message(0, 'ungerundet steuer:' . $skonto_taxamount_unrounded);
601
    my $skonto_taxamount_rounded   = _round($skonto_taxamount_unrounded);
602
    my $skonto_netamount_rounded   = _round($skonto_netamount_unrounded);
603
    my $chart_id                   = $is_sales ? $tax->skonto_sales_chart->id : $tax->skonto_purchase_chart->id;
604

  
605
    my $rec_net = {
606
      chart_id               => $chart_id,
607
      skonto_amount          => _round($skonto_netamount_unrounded + $skonto_taxamount_unrounded),
608
      # skonto_amount          => _round($skonto_netamount_unrounded) + _round($skonto_taxamount_unrounded),
609
    };
610
    push @skonto_charts, $rec_net;
611
    $total_skonto_rounded += $rec_net->{skonto_amount};
612

  
613
    # add-on: correct tax with one linked gl booking
614

  
615
    # no skonto tax correction for dual tax (reverse charge) or rate = 0
616
    next if ($tax->rate == 0 || $tax->reverse_charge_chart_id);
617

  
618
    my ($credit, $debit);
619
    $credit = SL::DB::Manager::Chart->find_by(id => $chart_id);
620
    $debit  = SL::DB::Manager::Chart->find_by(id => $tax_chart_id);
621
    croak("No such Chart ID")  unless ref $credit eq 'SL::DB::Chart' && ref $debit eq 'SL::DB::Chart';
622

  
623
    my $current_transaction = SL::DB::GLTransaction->new(
624
         employee_id    => $self->employee_id,
625
         transdate      => $params{transdate_obj},
626
         notes          => $params{source} . ' ' . $params{memo},
627
         description    => $self->notes || $self->invnumber,
628
         reference      => t8('Skonto Tax Correction for') . " " . $tax->rate * 100 . '% ' . $self->invnumber,
629
         department_id  => $self->department_id ? $self->department_id : undef,
630
         imported       => 0, # not imported
631
         taxincluded    => 0,
632
      )->add_chart_booking(
633
         chart  => $is_sales ? $debit : $credit,
634
         debit  => abs($skonto_taxamount_rounded),
635
         source => t8('Skonto Tax Correction for') . " " . $self->invnumber,
636
         memo   => $params{memo},
637
         tax_id => 0,
638
      )->add_chart_booking(
639
         chart  => $is_sales ? $credit : $debit,
640
         credit => abs($skonto_taxamount_rounded),
641
         source => t8('Skonto Tax Correction for') . " " . $self->invnumber,
642
         memo   => $params{memo},
643
         tax_id => 0,
644
      )->post;
645

  
646
    ## add a stable link from ap to gl
647
    # not needed, BankTransactionAccTrans is already stable
648
    # furthermore the origin of the booking is the bank_transaction
649
    #my $arap = $self->is_sales ? 'ar' : 'ap';
650
    #my %props_gl = (
651
    #  $arap . _id => $self->id,
652
    #  gl_id => $current_transaction->id,
653
    #  datev_export => 1,
654
    #);
655
    #if ($arap eq 'ap') {
656
    #  require SL::DB::ApGl;
657
    #  SL::DB::ApGl->new(%props_gl)->save;
658
    #} elsif ($arap eq 'ar') {
659
    #  require SL::DB::ArGl;
660
    #  SL::DB::ArGl->new(%props_gl)->save;
661
    #} else { die "Invalid state"; }
662
    #push @new_acc_ids, map { $_->acc_trans_id } @{ $current_transaction->transactions };
663

  
664
    foreach my $transaction (@{ $current_transaction->transactions }) {
665
      my %props_acc = (
666
           acc_trans_id        => $transaction->acc_trans_id,
667
           bank_transaction_id => $params{bt_id},
668
           gl                  => $current_transaction->id,
669
      );
670
      SL::DB::BankTransactionAccTrans->new(%props_acc)->save;
671
    }
672
    # Record a record link from banktransactions to gl
673
    # caller has to assign param bt_id
674
    my %props_rl = (
675
         from_table => 'bank_transactions',
676
         from_id    => $params{bt_id},
677
         to_table   => 'gl',
678
         to_id      => $current_transaction->id,
679
    );
680
    SL::DB::RecordLink->new(%props_rl)->save;
681
    # Record a record link from arap to gl
682
    # linked gl booking will appear in tab linked records
683
    # this is just a link for convenience
684
    %props_rl = (
685
         from_table => $is_sales ? 'ar' : 'ap',
686
         from_id    => $self->id,
687
         to_table   => 'gl',
688
         to_id      => $current_transaction->id,
689
    );
690
    SL::DB::RecordLink->new(%props_rl)->save;
570 691

  
692
  }
693
  # check for rounding errors, at least for the payment chart
694
  # we ignore tax rounding errors as long as the user or calculated
695
  # amount of skonto is fully assigned
696
  # we simply alter one cent for the first skonto booking entry
697
  # should be correct for most of the cases (no invoices with mixed taxes)
698
  if ($total_skonto_rounded - $amount > 0.01) {
699
    # add one cent
700
    $main::lxdebug->message(0, 'Una mas!' . $total_skonto_rounded);
701
    $skonto_charts[0]->{skonto_amount} -= 0.01;
702
  } elsif ($amount - $total_skonto_rounded > 0.01) {
703
    # subtract one cent
704
    $main::lxdebug->message(0, 'Una menos!' . $total_skonto_rounded);
705
    $skonto_charts[0]->{skonto_amount} += 0.01;
706
  } else { $main::lxdebug->message(0, 'No rounding error');  }
707

  
708
  # return same array of skonto charts as sub skonto_charts
709
  return @skonto_charts;
710
}
571 711

  
572 712
sub skonto_charts {
573 713
  my $self = shift;
SL/DB/Helper/SalesPurchaseInvoice.pm
1
package SL::DB::Helper::SalesPurchaseInvoice;
2

  
3
use strict;
4
use utf8;
5

  
6
use parent qw(Exporter);
7
our @EXPORT = qw(get_tax_and_amount_by_tax_chart_id);
8

  
9
sub get_tax_and_amount_by_tax_chart_id {
10
  my ($self) = @_;
11

  
12
  my $ARAP = $self->is_sales ? 'AR' : 'AP';
13
  my ($tax_and_amount_by_tax_id, $total);
14

  
15
  foreach my $transaction (@{ $self->transactions }) {
16
    next if $transaction->chart_link =~ m/(^${ARAP}$|paid)/;
17

  
18
    my $tax_or_netamount = $transaction->chart_link =~ m/tax/            ? 'tax'
19
                         : $transaction->chart_link =~ m/(${ARAP}_amount|IC_cogs)/ ? 'netamount'
20
                         : undef;
21
    if ($tax_or_netamount eq 'netamount') {
22
      $tax_and_amount_by_tax_id->{ $transaction->tax->chart_id }->{$tax_or_netamount} ||= 0;
23
      $tax_and_amount_by_tax_id->{ $transaction->tax->chart_id }->{$tax_or_netamount}  += $transaction->amount;
24
      # die "Invalid state" unless $tax_and_amount_by_tax_id->{ $transaction->tax->chart_id }->{tax_id} == 0
25
      $tax_and_amount_by_tax_id->{ $transaction->tax->chart_id }->{tax_id}              = $transaction->tax_id;
26
    } elsif ($tax_or_netamount eq 'tax') {
27
      $tax_and_amount_by_tax_id->{ $transaction->chart_id }->{$tax_or_netamount} ||= 0;
28
      $tax_and_amount_by_tax_id->{ $transaction->chart_id }->{$tax_or_netamount}  += $transaction->amount;
29
    } else {
30
      die "Invalid chart link at: " . $transaction->chart_link unless $tax_or_netamount;
31
    }
32
    $total ||= 0;
33
    $total  += $transaction->amount;
34
  }
35
  die "Invalid calculated amount" if abs($total) - abs($self->amount) > 0.001;
36
  return $tax_and_amount_by_tax_id;
37
}
38

  
39

  
40

  
41
1;
42

  
43
__END__
44

  
45
=pod
46

  
47
=encoding utf8
48

  
49
=head1 NAME
50

  
51
SL::DB::Helper::SalesPurchaseInvoice - Helper functions for Sales or Purchase bookings (mirrored)
52

  
53
Delivers the booked amounts split by net amount and tax amount for one ar or ap transaction
54
as persisted in the table acc_trans.
55
Should be rounding or calculation error prone because all values are already computed before
56
the values are written in the acc_trans table.
57

  
58
That is the main purpose for this helper class.
59
=head1 FUNCTIONS
60

  
61
=over 4
62

  
63
=item C<get_tax_and_amount_by_tax_chart_id>
64

  
65
Iterates over all transactions for one distinct ar or ap transaction (trans_id in acc_trans) and
66
groups the amounts in relation to distinct tax (tax.id) and net amounts (sums all bookings with
67
_cogs or _amount chart links).
68
Returns a hashref with the chart_id of the tax entry as key like this:
69

  
70
 '775' => {
71
    'tax_id'    => 777
72
    'tax'       => '332.18',
73
    'netamount' => '1748.32',
74
  },
75

  
76
 '194' => {
77
    'tax_id'    => 378,
78
    'netamount' => '20',
79
    'tax'       => '1.4'
80
  }
81

  
82
C<tax_id> is the id of the used tax. C<tax> ist the amount of tax booked for the whole transaction.
83
C<netamount> is the netamount booked with this tax.
84
TODO: Please note the hash key chart_id may not be unique but the entry tax_id is always unique.
85

  
86
As additional safety method the functions dies if the calculated sums do not match the
87
the whole amount of the transaction with an accuracy of two decimal places.
88

  
89
=back
90

  
91
=head1 BUGS
92

  
93
Nothing here yet.
94

  
95
=head1 AUTHOR
96

  
97
Jan Büren E<lt>jan@kivitendo.deE<gt>
98

  
99
=cut
SL/DB/Invoice.pm
16 16
use SL::DB::Helper::PDF_A;
17 17
use SL::DB::Helper::PriceTaxCalculator;
18 18
use SL::DB::Helper::PriceUpdater;
19
use SL::DB::Helper::SalesPurchaseInvoice;
19 20
use SL::DB::Helper::TransNumberGenerator;
20 21
use SL::DB::Helper::ZUGFeRD;
21 22
use SL::Locale::String qw(t8);
SL/DB/PurchaseInvoice.pm
11 11
use SL::DB::Helper::AttrSorted;
12 12
use SL::DB::Helper::LinkedRecords;
13 13
use SL::DB::Helper::Payment qw(:ALL);
14
use SL::DB::Helper::SalesPurchaseInvoice;
14 15
use SL::Locale::String qw(t8);
15 16
use Rose::DB::Object::Helpers qw(has_loaded_related forget_related);
16 17

  

Auch abrufbar als: Unified diff