Kreditorenbuchung um Steuerschlüssel 94 (reverse charge) erweitert
authorJan Büren <jan@kivitendo.de>
Tue, 1 Mar 2022 12:48:46 +0000 (13:48 +0100)
committerJan Büren <jan@kivitendo.de>
Thu, 3 Mar 2022 12:39:43 +0000 (13:39 +0100)
Bucht die gegensätzliche Steuer auf eine verknüpfte Dialogbuchung
und setzt den Steuerschlüssel beim DATEV-Export auf 0. Ferner sind
Steuer inklusive Buchungen unterbunden und die Dialogbuchung ist
nicht veränderbar, wird aber entsprechend modifiziert wenn die
Quell-Buchung geändert (gelöscht) wird.

SL/AP.pm
SL/DATEV.pm
bin/mozilla/ap.pl
templates/webpages/ap/form_header.html

index a77906c..8a3394b 100644 (file)
--- a/SL/AP.pm
+++ b/SL/AP.pm
@@ -39,6 +39,7 @@ use SL::DATEV qw(:CONSTANTS);
 use SL::DBUtils;
 use SL::IO;
 use SL::MoreCommon;
+use SL::DB::ApGl;
 use SL::DB::Default;
 use SL::DB::Draft;
 use SL::DB::Order;
@@ -406,6 +407,8 @@ sub _post_transaction {
     SL::DB::Manager::Draft->delete_all(where => [ id => delete($form->{draft_id}) ]);
   }
 
+  # hook for taxkey 94
+  $self->_reverse_charge($myconfig, $form);
   # safety check datev export
   if ($::instance_conf->get_datev_check_on_ap_transaction) {
     my $datev = SL::DATEV->new(
@@ -422,12 +425,86 @@ sub _post_transaction {
   return 1;
 }
 
+sub _reverse_charge {
+  my ($self, $myconfig, $form) = @_;
+
+  # check taxkey settings or return
+  my $tax = SL::DB::Manager::Tax->get_first( where => [taxkey => 94 ]);
+  return unless ref $tax eq 'SL::DB::Tax';
+
+  # delete previous bookings, if they exists (repost)
+  my $ap_gl = SL::DB::Manager::ApGl->get_first(where => [ ap_id => $form->{id} ]);
+  my $gl_id = ref $ap_gl eq 'SL::DB::ApGl' ? $ap_gl->gl_id : undef;
+
+  SL::DB::Manager::GLTransaction->delete_all(where => [ id    => $gl_id ])       if $gl_id;
+  SL::DB::Manager::ApGl->         delete_all(where => [ ap_id => $form->{id} ])  if $gl_id;
+  SL::DB::Manager::RecordLink->   delete_all(where => [ from_table => 'ap', to_table => 'gl', from_id => $form->{id} ]);
+
+  # gl booking
+  my ($credit, $debit);
+  $credit   = SL::DB::Manager::Chart->find_by(id => $tax->chart_id);
+  $debit    = SL::DB::Manager::Chart->find_by(id => $tax->reverse_charge_chart_id);
+
+  croak("No such Chart ID" . $tax->chart_id)          unless ref $credit eq 'SL::DB::Chart';
+  croak("No such Chart ID" . $tax->reverse_chart_id)  unless ref $debit  eq 'SL::DB::Chart';
+
+  my ($i, $current_transaction);
+
+  for $i (1 .. $form->{rowcount}) {
+    next unless $form->{"taxkey_$i"} == 94;
+
+    my ($tmpnetamount, $tmptaxamount) = $form->calculate_tax($form->{"amount_$i"}, 0.19, $form->{taxincluded}, 2);
+    $current_transaction = SL::DB::GLTransaction->new(
+          employee_id    => $form->{employee_id},
+          transdate      => $form->{transdate},
+          description    => $form->{notes} || $form->{invnumber},
+          reference      => $form->{invnumber},
+          department_id  => $form->{department_id} ? $form->{department_id} : undef,
+          imported       => 0, # not imported
+          taxincluded    => 0,
+        )->add_chart_booking(
+          chart  => $tmptaxamount < 0 ? $credit : $debit,
+          credit => abs($tmptaxamount),
+          source => "Reverse Charge for " . $form->{invnumber},
+        )->add_chart_booking(
+          chart  => $tmptaxamount < 0 ? $debit : $credit,
+          debit  => abs($tmptaxamount),
+          source => "Reverse Charge for " . $form->{invnumber},
+      )->post;
+    # add a stable link from ap to gl
+    my %props_gl = (
+        ap_id => $form->{id},
+        gl_id => $current_transaction->id,
+      );
+    SL::DB::ApGl->new(%props_gl)->save;
+    # Record a record link from ap to gl
+    my %props_rl = (
+        from_table => 'ap',
+        from_id    => $form->{id},
+        to_table   => 'gl',
+        to_id      => $current_transaction->id,
+      );
+    SL::DB::RecordLink->new(%props_rl)->save;
+  }
+}
+
 sub delete_transaction {
   $main::lxdebug->enter_sub();
 
   my ($self, $myconfig, $form) = @_;
 
   SL::DB->client->with_transaction(sub {
+
+    # if tax 94 reverse charge, clear all GL bookings and links
+    my $ap_gl = SL::DB::Manager::ApGl->get_first(where => [ ap_id => $form->{id} ]);
+    my $gl_id = ref $ap_gl eq 'SL::DB::ApGl' ? $ap_gl->gl_id : undef;
+
+    SL::DB::Manager::GLTransaction->delete_all(where => [ id    => $gl_id ])       if $gl_id;
+    SL::DB::Manager::ApGl->         delete_all(where => [ ap_id => $form->{id} ])  if $gl_id;
+    SL::DB::Manager::RecordLink->   delete_all(where => [ from_table => 'ap', to_table => 'gl', from_id => $form->{id} ]);
+    # done gl delete for tax 94 case
+
+    # begin ap delete
     my $query = qq|DELETE FROM ap WHERE id = ?|;
     do_query($form, SL::DB->client->dbh, $query, $form->{id});
     1;
index 1db417c..9dcfe0b 100644 (file)
@@ -835,6 +835,7 @@ sub generate_datev_lines {
       }
       if ($transaction->[$i]->{'taxkey'}) {
         $taxkey = $transaction->[$i]->{'taxkey'};
+        $taxkey = 0 if $taxkey == 94; # taxbookings are in gl
       }
       if ($transaction->[$i]->{'charttax'}) {
         $charttax = $transaction->[$i]->{'charttax'};
index e7918d1..7fcfef1 100644 (file)
@@ -493,6 +493,15 @@ sub form_header {
     $form->{"selected_taxchart_$i"}  = $selected_taxchart;
     $form->{"AP_amount_chart_id_$i"} = $amount_chart_id;
     $form->{"taxcharts_$i"}          = \@taxcharts;
+
+    # reverse charge hack for template, display two taxes
+    if ($taxchart_to_use->taxkey == 94) {
+      my $tmpnetamount;
+      ($tmpnetamount, $form->{"tax_reverse_$i"}) = $form->calculate_tax($form->parse_amount(\%myconfig, $form->{"amount_$i"}), 0.19, $form->{taxincluded},2);
+      $form->{"tax_charge_$i"}  = $form->{"tax_reverse_$i"} * -1;
+      $form->{"tax_reverse_$i"} = $form->format_amount(\%myconfig, $form->{"tax_reverse_$i"}, 2);
+      $form->{"tax_charge_$i"}  = $form->format_amount(\%myconfig, $form->{"tax_charge_$i"}, 2);
+    }
   }
 
   $form->{taxchart_value_title_sub} = sub {
@@ -797,10 +806,16 @@ sub post {
   $form->error($locale->text('Cannot post transaction for a closed period!')) if ($form->date_closed($form->{"transdate"}, \%myconfig));
 
   my $zero_amount_posting = 1;
+  # no taxincluded for 94
+  my $tax = SL::DB::Manager::Tax->get_first( where => [taxkey => 94 ]);
+  my $tax_id = ref $tax eq 'SL::DB::Tax' ? $tax->id : undef;
   for my $i (1 .. $form->{rowcount}) {
+    # no taxincluded for 94
+    if ($tax_id && $form->{"taxchart_$i"} =~ m/^$tax_id--/ && $form->{taxincluded}) {
+      $form->error($locale->text('Cannot Post AP transaction with tax included!'));
+    }
     if ($form->parse_amount(\%myconfig, $form->{"amount_$i"})) {
       $zero_amount_posting = 0;
-      last;
     }
   }
 
@@ -1348,6 +1363,10 @@ sub setup_ap_display_form_action_bar {
 
     $is_linked_bank_transaction = 1;
   }
+  my $is_linked_gl_transaction;
+  if ($::form->{id} && SL::DB::Manager::ApGl->find_by(ap_id => $::form->{id})) {
+    $is_linked_gl_transaction = 1;
+  }
 
   my $create_post_action = sub {
     # $_[0]: description
@@ -1419,6 +1438,7 @@ sub setup_ap_display_form_action_bar {
                     : $is_storno           ? t8('Reversal invoices cannot be canceled.')
                     : $::form->{totalpaid} ? t8('Invoices with payments cannot be canceled.')
                     : $has_sepa_exports    ? t8('This invoice has been linked with a sepa export, undo this first.')
+                    : $is_linked_gl_transaction ? t8('This transaction is linked with a gl transaction. Please delete the ap transaction booking if needed.')
                     :                        undef,
         ],
         action => [ t8('Delete'),
@@ -1426,12 +1446,13 @@ sub setup_ap_display_form_action_bar {
           confirm  => t8('Do you really want to delete this object?'),
           disabled => !$may_edit_create           ? t8('You must not change this AP transaction.')
                     : !$::form->{id}              ? t8('This invoice has not been posted yet.')
-                    : $change_never               ? t8('Changing invoices has been disabled in the configuration.')
-                    : $change_on_same_day_only    ? t8('Invoices can only be changed on the day they are posted.')
-                    : $has_storno                 ? t8('This invoice has been canceled already.')
                     : $is_closed                  ? t8('The billing period has already been locked.')
                     : $has_sepa_exports           ? t8('This invoice has been linked with a sepa export, undo this first.')
                     : $is_linked_bank_transaction ? t8('This transaction is linked with a bank transaction. Please undo and redo the bank transaction booking if needed.')
+                    : $is_linked_gl_transaction   ? undef # linked transactions can be deleted, if period is not closed
+                    : $change_never               ? t8('Changing invoices has been disabled in the configuration.')
+                    : $change_on_same_day_only    ? t8('Invoices can only be changed on the day they are posted.')
+                    : $has_storno                 ? t8('This invoice has been canceled already.')
                     :                               undef,
         ],
       ], # end of combobox "Storno"
index 05b7a7a..c4902bb 100644 (file)
               <input name="amount_[% i %]" size="10" value="[% temp = "amount_"_ i %][% $temp | html %]">
             </td>
             <td>
-              [% temp = "tax_"_ i %][% $temp | html %]
+              [% IF "tax_reverse_"_ i  %]
+                [% temp_r = "tax_reverse_"_ i %][% $temp_r | html %]
+                &nbsp;&nbsp;&nbsp;
+                [% temp_c = "tax_charge_"_ i %][% $temp_c | html %]
+              [% ELSE %]
+                [% temp = "tax_"_ i %][% $temp | html %]
+              [% END %]
             </td>
             <td>
               [% temp = 'selected_taxchart_'_ i %]