]> wagnertech.de Git - mfinanz.git/commitdiff
Merge pull request #41 from kivitendo/f-rights-to-view
authorJan Büren <jan@kivitendo-premium.de>
Wed, 9 Mar 2022 08:12:50 +0000 (09:12 +0100)
committerGitHub <noreply@github.com>
Wed, 9 Mar 2022 08:12:50 +0000 (09:12 +0100)
F rights to view

27 files changed:
SL/AP.pm
SL/Common.pm
SL/Controller/DeliveryOrder.pm
SL/Controller/Order.pm
SL/Controller/PayPostingImport.pm
SL/DATEV.pm
SL/DB/ApGl.pm [new file with mode: 0644]
SL/DB/Helper/ALL.pm
SL/DB/Helper/Mappings.pm
SL/DB/Manager/ApGl.pm [new file with mode: 0644]
SL/DB/MetaSetup/ApGl.pm [new file with mode: 0644]
SL/DB/MetaSetup/InvoiceItem.pm
SL/DB/MetaSetup/OrderItem.pm
SL/DB/MetaSetup/Tax.pm
SL/Helper/UNECERecommendation20.pm
bin/mozilla/ap.pl
bin/mozilla/gl.pl
doc/changelog
locale/de/all
locale/en/all
sql/Pg-upgrade2/ap_gl.sql [new file with mode: 0644]
sql/Pg-upgrade2/cape_remove_oids.sql [new file with mode: 0644]
sql/Pg-upgrade2/convert_real_qty.sql [new file with mode: 0644]
sql/Pg-upgrade2/remove_oids.sql [new file with mode: 0644]
sql/Pg-upgrade2/tax_reverse_charge.sql [new file with mode: 0644]
t/pay_posting_import/datev_import.t
templates/webpages/ap/form_header.html

index c2b5e64401ec58add2041e0c0537c472280715c3..8ca5634cf5d60466dc2050378fbd7b971b9b5025 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 36635fdbc4f1914e8af9bcb52cf3bbab713118a5..126a30ed846d45cabca592b5b2306c762c09c9db 100644 (file)
@@ -388,6 +388,8 @@ sub save_email_status {
 
   my ($self, $myconfig, $form) = @_;
 
+  return unless ($::instance_conf->get_email_journal);
+
   my ($table, $query, $dbh);
 
   if ($form->{script} eq 'oe.pl') {
@@ -646,6 +648,11 @@ C<full> (replace consecutive line feed/carriage return characters in
 the middle by a single space and remove tailing line feed/carriage
 return characters).
 
+=item C<save_email_status>
+
+Adds sending information to internal notes.
+Does nothing if the client config email_journal is enabled.
+
 =back
 
 =head1 BUGS
index c8075a688482abf8f2bd38cfffc673742acc00f8..db3184435c4d2c2d868d0696edfec8d2ebd9a425 100644 (file)
@@ -483,18 +483,21 @@ sub action_send_email {
   $::form->{id} = $self->order->id; # this is used in SL::Mailer to create a linked record to the mail
   $::form->send_email(\%::myconfig, 'pdf');
 
-  # internal notes
-  my $intnotes = $self->order->intnotes;
-  $intnotes   .= "\n\n" if $self->order->intnotes;
-  $intnotes   .= t8('[email]')                                                                                        . "\n";
-  $intnotes   .= t8('Date')       . ": " . $::locale->format_date_object(DateTime->now_local, precision => 'seconds') . "\n";
-  $intnotes   .= t8('To (email)') . ": " . $::form->{email}                                                           . "\n";
-  $intnotes   .= t8('Cc')         . ": " . $::form->{cc}                                                              . "\n"    if $::form->{cc};
-  $intnotes   .= t8('Bcc')        . ": " . $::form->{bcc}                                                             . "\n"    if $::form->{bcc};
-  $intnotes   .= t8('Subject')    . ": " . $::form->{subject}                                                         . "\n\n";
-  $intnotes   .= t8('Message')    . ": " . $::form->{message};
-
-  $self->order->update_attributes(intnotes => $intnotes);
+  # internal notes unless no email journal
+  unless ($::instance_conf->get_email_journal) {
+
+    my $intnotes = $self->order->intnotes;
+    $intnotes   .= "\n\n" if $self->order->intnotes;
+    $intnotes   .= t8('[email]')                                                                                        . "\n";
+    $intnotes   .= t8('Date')       . ": " . $::locale->format_date_object(DateTime->now_local, precision => 'seconds') . "\n";
+    $intnotes   .= t8('To (email)') . ": " . $::form->{email}                                                           . "\n";
+    $intnotes   .= t8('Cc')         . ": " . $::form->{cc}                                                              . "\n"    if $::form->{cc};
+    $intnotes   .= t8('Bcc')        . ": " . $::form->{bcc}                                                             . "\n"    if $::form->{bcc};
+    $intnotes   .= t8('Subject')    . ": " . $::form->{subject}                                                         . "\n\n";
+    $intnotes   .= t8('Message')    . ": " . $::form->{message};
+
+    $self->order->update_attributes(intnotes => $intnotes);
+  }
 
   $self->save_history('MAILED');
 
index 67402c0598209dd5f13189547731a7397c79d05c..b7d38cd7837667f03dcc80e9b9fa74c6dde9fa6d 100644 (file)
@@ -519,18 +519,20 @@ sub action_send_email {
   $::form->{id} = $self->order->id; # this is used in SL::Mailer to create a linked record to the mail
   $::form->send_email(\%::myconfig, $::form->{print_options}->{format});
 
-  # internal notes
-  my $intnotes = $self->order->intnotes;
-  $intnotes   .= "\n\n" if $self->order->intnotes;
-  $intnotes   .= t8('[email]')                                                                                        . "\n";
-  $intnotes   .= t8('Date')       . ": " . $::locale->format_date_object(DateTime->now_local, precision => 'seconds') . "\n";
-  $intnotes   .= t8('To (email)') . ": " . $::form->{email}                                                           . "\n";
-  $intnotes   .= t8('Cc')         . ": " . $::form->{cc}                                                              . "\n"    if $::form->{cc};
-  $intnotes   .= t8('Bcc')        . ": " . $::form->{bcc}                                                             . "\n"    if $::form->{bcc};
-  $intnotes   .= t8('Subject')    . ": " . $::form->{subject}                                                         . "\n\n";
-  $intnotes   .= t8('Message')    . ": " . SL::HTML::Util->strip($::form->{message});
-
-  $self->order->update_attributes(intnotes => $intnotes);
+  # internal notes unless no email journal
+  unless ($::instance_conf->get_email_journal) {
+    my $intnotes = $self->order->intnotes;
+    $intnotes   .= "\n\n" if $self->order->intnotes;
+    $intnotes   .= t8('[email]')                                                                                        . "\n";
+    $intnotes   .= t8('Date')       . ": " . $::locale->format_date_object(DateTime->now_local, precision => 'seconds') . "\n";
+    $intnotes   .= t8('To (email)') . ": " . $::form->{email}                                                           . "\n";
+    $intnotes   .= t8('Cc')         . ": " . $::form->{cc}                                                              . "\n"    if $::form->{cc};
+    $intnotes   .= t8('Bcc')        . ": " . $::form->{bcc}                                                             . "\n"    if $::form->{bcc};
+    $intnotes   .= t8('Subject')    . ": " . $::form->{subject}                                                         . "\n\n";
+    $intnotes   .= t8('Message')    . ": " . SL::HTML::Util->strip($::form->{message});
+
+    $self->order->update_attributes(intnotes => $intnotes);
+  }
 
   $self->save_history('MAILED');
 
index a2d103f2f6933f82cce5b82fc7de43dffa7fc580..c8d39091d7d604af37b5e4aad8f42990ee2856c8 100644 (file)
@@ -82,7 +82,7 @@ sub parse_and_import {
       # optional KOST1 - KOST2 ?
       $department_name = $row->[36];
       if ($department_name) {
-        $department    = SL::DB::Manager::Department->get_first(description => { like =>  $department_name . '%' });
+        $department    = SL::DB::Manager::Department->get_first(where => [ description => { ilike =>  $department_name . '%' } ]);
       }
 
       my $amount = $::form->parse_amount({ numberformat => '1000,00' }, $row->[0]);
index 1db417c9a42c03437695fbfeb85e745d57dcd1bd..86cb729bc6abf72719f92414ff4680b6a5223bfb 100644 (file)
@@ -543,6 +543,7 @@ sub generate_datev_data {
          $gl_itime_filter
          $gl_department_id_filter
          $gl_imported
+         AND NOT EXISTS (SELECT gl_id from ap_gl where gl_id = gl.id)
          $filter
 
        ORDER BY trans_id, acc_trans_id|;
@@ -835,6 +836,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'};
diff --git a/SL/DB/ApGl.pm b/SL/DB/ApGl.pm
new file mode 100644 (file)
index 0000000..77368a3
--- /dev/null
@@ -0,0 +1,13 @@
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::ApGl;
+
+use strict;
+
+use SL::DB::MetaSetup::ApGl;
+use SL::DB::Manager::ApGl;
+
+__PACKAGE__->meta->initialize;
+
+1;
index bb0c3b42ea5b82a4a7eafd07769c4a6bd0b5ec31..633373b73d14c12316787df547e34dad59db9934 100644 (file)
@@ -4,6 +4,7 @@ use strict;
 
 use SL::DB::AccTransaction;
 use SL::DB::AdditionalBillingAddress;
+use SL::DB::ApGl;
 use SL::DB::Assembly;
 use SL::DB::AssortmentItem;
 use SL::DB::AuthClient;
index 0e9f230758a4ff3ff2a6047b07b7499f90224805..8d0ed7e0c74e015c3ff382659a972ed762accde0 100644 (file)
@@ -99,6 +99,7 @@ my %kivitendo_package_names = (
   'auth.user_group'              => 'auth_user_group',
   ar                             => 'invoice',
   ap                             => 'purchase_invoice',
+  ap_gl                          => 'ap_gl',
   assembly                       => 'assembly',
   assortment_items               => 'assortment_item',
   background_jobs                => 'background_job',
diff --git a/SL/DB/Manager/ApGl.pm b/SL/DB/Manager/ApGl.pm
new file mode 100644 (file)
index 0000000..3cc0396
--- /dev/null
@@ -0,0 +1,14 @@
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::Manager::ApGl;
+
+use strict;
+
+use parent qw(SL::DB::Helper::Manager);
+
+sub object_class { 'SL::DB::ApGl' }
+
+__PACKAGE__->make_manager_methods;
+
+1;
diff --git a/SL/DB/MetaSetup/ApGl.pm b/SL/DB/MetaSetup/ApGl.pm
new file mode 100644 (file)
index 0000000..9f3f80a
--- /dev/null
@@ -0,0 +1,35 @@
+# This file has been auto-generated. Do not modify it; it will be overwritten
+# by rose_auto_create_model.pl automatically.
+package SL::DB::ApGl;
+
+use strict;
+
+use parent qw(SL::DB::Object);
+
+__PACKAGE__->meta->table('ap_gl');
+
+__PACKAGE__->meta->columns(
+  ap_id => { type => 'integer', not_null => 1 },
+  gl_id => { type => 'integer', not_null => 1 },
+  itime => { type => 'timestamp', default => 'now()' },
+  mtime => { type => 'timestamp' },
+);
+
+__PACKAGE__->meta->primary_key_columns([ 'ap_id', 'gl_id' ]);
+
+__PACKAGE__->meta->allow_inline_column_values(1);
+
+__PACKAGE__->meta->foreign_keys(
+  ap => {
+    class       => 'SL::DB::PurchaseInvoice',
+    key_columns => { ap_id => 'id' },
+  },
+
+  gl => {
+    class       => 'SL::DB::GLTransaction',
+    key_columns => { gl_id => 'id' },
+  },
+);
+
+1;
+;
index 85b8876be8712bc0488583dea19c2ddce3612bbd..8a67022fcbaeca360c9028becec081739a2dfbe9 100644 (file)
@@ -35,7 +35,7 @@ __PACKAGE__->meta->columns(
   price_factor_id        => { type => 'integer' },
   pricegroup_id          => { type => 'integer' },
   project_id             => { type => 'integer' },
-  qty                    => { type => 'float', precision => 4, scale => 4 },
+  qty                    => { type => 'numeric', precision => 25, scale => 5 },
   sellprice              => { type => 'numeric', precision => 15, scale => 5 },
   serialnumber           => { type => 'text' },
   subtotal               => { type => 'boolean', default => 'false' },
index afa64d815e61b5b0167c4cf7bc00efaf3ff47294..e83b65bda7d61aba4e91a4d506e8d75c27a6337c 100644 (file)
@@ -31,7 +31,7 @@ __PACKAGE__->meta->columns(
   price_factor_id        => { type => 'integer' },
   pricegroup_id          => { type => 'integer' },
   project_id             => { type => 'integer' },
-  qty                    => { type => 'float', precision => 4, scale => 4 },
+  qty                    => { type => 'numeric', precision => 25, scale => 5 },
   reqdate                => { type => 'date' },
   sellprice              => { type => 'numeric', precision => 15, scale => 5 },
   serialnumber           => { type => 'text' },
index 55aba86bc4157018f190922966fec6fcbb51b19a..c29a455816aa67979447f1e9a578db317edbc0f1 100644 (file)
@@ -15,6 +15,7 @@ __PACKAGE__->meta->columns(
   itime                    => { type => 'timestamp', default => 'now()' },
   mtime                    => { type => 'timestamp' },
   rate                     => { type => 'numeric', default => '0', not_null => 1, precision => 15, scale => 5 },
+  reverse_charge_chart_id  => { type => 'integer' },
   skonto_purchase_chart_id => { type => 'integer' },
   skonto_sales_chart_id    => { type => 'integer' },
   taxdescription           => { type => 'text', not_null => 1 },
index 56ea12e768d909489938827807f7231ad03c4d80..e79991cb6930fed712b54cd32932f555f75b47ff 100644 (file)
@@ -39,7 +39,7 @@ my @mappings = (
   [ 'LTR', qr{^(?:l|liter|litre)$}i ],
 
   # miscellaneous
-  [ 'C62', qr{^(?:stck|stück|pieces?|pc|psch|pauschal)$}i ],
+  [ 'C62', qr{^(?:stck|stück|pieces?|pc|psch|pauschal|licenses?|lizenz(?:en)?)$}i ],
 );
 
 sub map_name_to_code {
index e7918d15e3a69c449888b1685376ab17d0e23654..7fcfef17d850e4ed8aee62a108a8684dfc480d51 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 0d39d47ea65e403c3b1818cec19839d95659c82e..551937bb0fd811011ea6f5523ffede6892454010 100644 (file)
@@ -38,6 +38,7 @@ use strict;
 use POSIX qw(strftime);
 use List::Util qw(first sum);
 
+use SL::DB::ApGl;
 use SL::DB::RecordTemplate;
 use SL::DB::BankTransactionAccTrans;
 use SL::DB::Tax;
@@ -969,11 +970,15 @@ sub setup_gl_action_bar {
   my $form   = $::form;
   my $change_never            = $::instance_conf->get_gl_changeable == 0;
   my $change_on_same_day_only = $::instance_conf->get_gl_changeable == 2 && ($form->current_date(\%::myconfig) ne $form->{gldate});
-  my $is_linked_bank_transaction;
+  my ($is_linked_bank_transaction, $is_linked_ap_transaction);
 
   if ($form->{id} && SL::DB::Manager::BankTransactionAccTrans->find_by(gl_id => $form->{id})) {
     $is_linked_bank_transaction = 1;
   }
+  if ($form->{id} && SL::DB::Manager::ApGl->find_by(gl_id => $form->{id})) {
+    $is_linked_ap_transaction = 1;
+  }
+
 
   my $create_post_action = sub {
     # $_[0]: description
@@ -986,7 +991,8 @@ sub setup_gl_action_bar {
                 : ($form->{id} && $change_never)            ? t8('Changing general ledger transaction has been disabled in the configuration.')
                 : ($form->{id} && $change_on_same_day_only) ? t8('General ledger transactions can only be changed on the day they are posted.')
                 : $is_linked_bank_transaction               ? t8('This transaction is linked with a bank transaction. Please undo and redo the bank transaction booking if needed.')
-                :                                             undef,
+                : $is_linked_ap_transaction                 ? t8('This transaction is linked with a AP transaction. Please undo and redo the AP transaction booking if needed.')
+                : undef,
     ],
   };
 
@@ -1017,6 +1023,7 @@ sub setup_gl_action_bar {
           disabled => !$form->{id}                ? t8('This general ledger transaction has not been posted yet.')
                     : $form->{storno}             ? t8('A canceled general ledger transaction cannot be canceled again.')
                     : $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_ap_transaction   ? t8('This transaction is linked with a AP transaction. Please undo and redo the AP transaction booking if needed.')
                     : undef,
         ],
         action => [ t8('Delete'),
@@ -1027,8 +1034,9 @@ sub setup_gl_action_bar {
                     : $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.')
                     : $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_ap_transaction   ? t8('This transaction is linked with a AP transaction. Please undo and redo the AP transaction booking if needed.')
                     : $form->{storno}             ? t8('A canceled general ledger transaction cannot be deleted.')
-                    :                            undef,
+                    : undef,
         ],
       ], # end of combobox "Storno"
 
@@ -1053,7 +1061,7 @@ sub setup_gl_action_bar {
           call     => [ 'kivi.Draft.popup', 'gl', 'unknown', $form->{draft_id}, $form->{draft_description} ],
           disabled => $form->{id}     ? t8('This invoice has already been posted.')
                     : $form->{locked} ? t8('The billing period has already been locked.')
-                    :                   undef,
+                    : undef,
         ],
       ], # end of combobox "more"
     );
index 9f87cec1e05aac44fc154d4ba30048093f0d93f4..3edc09b59cca623c197e811076a380aa4a2bd252 100644 (file)
@@ -2,6 +2,20 @@
 # Veränderungen von kivitendo #
 ###############################
 
+2022-0x-xx - Release 3.6.1
+
+Größere neue Features:
+
+Mittelgroße neue Features:
+
+Kleinere neue Features und Detailverbesserungen:
+
+ - Die Protokollierung von E-Mails in interne Bemerkungen ist deaktiviert,
+   falls der Mandant sowieso das E-Mail-Journal aktiviert hat.
+ - Steuerschlüssel 94, Reverse Charge gleichzeitige Vor- und Mehrwertsteuer
+   kann in einem netto verbuchten Kreditorenbeleg verbucht werden, die Steuer
+   wird dann mit einer verknüpften Dialogbuchung verbucht.
+
 2022-03-02 - Release 3.6.0
 
 Größere neue Features:
index 0c734bb53bdfcb1cb4eb2b115dee61236b4e3887..bc215181c7920dfe3bf8ad52a1d91cf5eb3d4446 100755 (executable)
@@ -568,6 +568,7 @@ $self->{texts} = {
   'Cancel Accounts Payables Transaction' => 'Kreditorenbuchung stornieren',
   'Cancel Accounts Receivables Transaction' => 'Debitorenbuchung stornieren',
   'Cancelling is disallowed. Either undo or balance the current payments until the open amount matches the invoice amount' => 'Storno verboten, da Zahlungen zum Beleg vorhanden sind. Entweder die Zahlungen löschen oder mit umgekehrten Vorzeichen ausbuchen, sodass der offene Betrag dem Rechnungsbetrag entspricht.',
+  'Cannot Post AP transaction with tax included!' => 'Kann diesen kreditorischen Beleg nicht mit "Steuer im Preis inbegriffen" verbuchen!',
   'Cannot add Booking, reason: #1 DB: #2 ' => 'Kann die Buchung nicht hinzufügen, Grund: #1 DB: #2',
   'Cannot allocate parts.'      => 'Es sind nicht genügend Artikel vorhanden',
   'Cannot change transaction in a closed period!' => 'In einem bereits abgeschlossenen Zeitraum kann keine Buchung verändert werden!',
@@ -3922,7 +3923,9 @@ $self->{texts} = {
   'This sales order has an active configuration for periodic invoices. If you save then all subsequently created invoices will contain those changes as well, but not those that have already been created. Do you want to continue?' => 'Dieser Auftrag besitzt eine aktive Konfiguration für wiederkehrende Rechnungen. Wenn Sie jetzt speichern, so werden alle zukünftig hieraus erzeugten Rechnungen die Änderungen enthalten, nicht aber die bereits erzeugten Rechnungen. Möchten Sie speichern?',
   'This status output will be refreshed every five seconds.' => 'Diese Statusausgabe wird alle fünf Sekunden aktualisiert.',
   'This transaction has to be split into several transactions manually.' => 'Diese Buchung muss manuell in mehrere Buchungen aufgeteilt werden.',
+  'This transaction is linked with a AP transaction. Please undo and redo the AP transaction booking if needed.' => 'Diese Buchung ist mit einer Kreditorenbuchung verknüpft. Bitte Löschen oder Ändern Sie die Kreditorenbuchung nötigenfalls.',
   'This transaction is linked with a bank transaction. Please undo and redo the bank transaction booking if needed.' => 'Ein oder mehrere Zahlungen des Belegs sind über das Verbuchen von Kontoauszüge erstellt worden, falls notwendig kann eine Neuverbuchung über Zahlungsverkehr -> Bericht Bankbewegung möglich gemacht werden.',
+  'This transaction is linked with a gl transaction. Please delete the ap transaction booking if needed.' => 'Diese Buchung ist mit einer Dialogbuchung verknüpft. Bitte Löschen oder Ändern Sie diese Kreditorenbuchung nötigenfalls.',
   'This update will change the nature the onhand of goods is tracked.' => 'Dieses update ändert die Art und Weise wie Lagermengen gezält werden.',
   'This user is a member in the following groups' => 'Dieser Benutzer ist Mitglied in den folgenden Gruppen',
   'This user will have access to the following clients' => 'Dieser Benutzer wird Zugriff auf die folgenden Mandanten haben',
index 2699e2e678514c2aff1c58fe735f128f0543f554..b33cba2fdaa725314bce6acb9e6d08abd622da80 100644 (file)
@@ -568,6 +568,7 @@ $self->{texts} = {
   'Cancel Accounts Payables Transaction' => '',
   'Cancel Accounts Receivables Transaction' => '',
   'Cancelling is disallowed. Either undo or balance the current payments until the open amount matches the invoice amount' => '',
+  'Cannot Post AP transaction with tax included!' => '',
   'Cannot add Booking, reason: #1 DB: #2 ' => '',
   'Cannot allocate parts.'      => '',
   'Cannot change transaction in a closed period!' => '',
@@ -3921,7 +3922,9 @@ $self->{texts} = {
   'This sales order has an active configuration for periodic invoices. If you save then all subsequently created invoices will contain those changes as well, but not those that have already been created. Do you want to continue?' => '',
   'This status output will be refreshed every five seconds.' => '',
   'This transaction has to be split into several transactions manually.' => '',
+  'This transaction is linked with a AP transaction. Please undo and redo the AP transaction booking if needed.' => '',
   'This transaction is linked with a bank transaction. Please undo and redo the bank transaction booking if needed.' => '',
+  'This transaction is linked with a gl transaction. Please delete the ap transaction booking if needed.' => '',
   'This update will change the nature the onhand of goods is tracked.' => '',
   'This user is a member in the following groups' => '',
   'This user will have access to the following clients' => '',
diff --git a/sql/Pg-upgrade2/ap_gl.sql b/sql/Pg-upgrade2/ap_gl.sql
new file mode 100644 (file)
index 0000000..cb9e01b
--- /dev/null
@@ -0,0 +1,16 @@
+-- @tag: ap_gl
+-- @description: Hilfstabelle für automatische GL-Buchung nach Kreditorenbuchung
+-- @depends: release_3_5_0
+-- @ignore: 0
+      CREATE TABLE ap_gl (
+        ap_id                   integer,
+        gl_id                   integer,
+        itime                   TIMESTAMP      DEFAULT now(),
+        mtime                   TIMESTAMP,
+        PRIMARY KEY (ap_id, gl_id),
+        FOREIGN KEY (ap_id)                    REFERENCES ap (id),
+        FOREIGN KEY (gl_id)                    REFERENCES gl (id) ON DELETE CASCADE);
+
+
+
+
diff --git a/sql/Pg-upgrade2/cape_remove_oids.sql b/sql/Pg-upgrade2/cape_remove_oids.sql
new file mode 100644 (file)
index 0000000..b30d286
--- /dev/null
@@ -0,0 +1,9 @@
+-- @tag: cape_remove_oids
+-- @description: OIDs von Tabellen entfernen
+-- @depends: release_3_5_6
+ALTER TABLE assembly             SET WITHOUT OIDS;
+ALTER TABLE delivery_order_items SET WITHOUT OIDS;
+ALTER TABLE invoice              SET WITHOUT OIDS;
+ALTER TABLE orderitems           SET WITHOUT OIDS;
+ALTER TABLE parts                SET WITHOUT OIDS;
+ALTER TABLE partsgroup           SET WITHOUT OIDS;
diff --git a/sql/Pg-upgrade2/convert_real_qty.sql b/sql/Pg-upgrade2/convert_real_qty.sql
new file mode 100644 (file)
index 0000000..5e0ea65
--- /dev/null
@@ -0,0 +1,7 @@
+-- @tag: convert_real_qty
+-- @description: Spaltentyp auf Numeric anstelle von Real für qty
+-- @depends: release_3_6_0
+ALTER TABLE orderitems ALTER column qty type numeric(25,5);
+ALTER TABLE invoice    ALTER column qty type numeric(25,5);
+
+
diff --git a/sql/Pg-upgrade2/remove_oids.sql b/sql/Pg-upgrade2/remove_oids.sql
new file mode 100644 (file)
index 0000000..9e9f439
--- /dev/null
@@ -0,0 +1,9 @@
+-- @tag: remove_oids
+-- @description: OIDs von Tabellen entfernen
+-- @depends: release_3_6_0
+ALTER TABLE assembly             SET WITHOUT OIDS;
+ALTER TABLE delivery_order_items SET WITHOUT OIDS;
+ALTER TABLE invoice              SET WITHOUT OIDS;
+ALTER TABLE orderitems           SET WITHOUT OIDS;
+ALTER TABLE parts                SET WITHOUT OIDS;
+ALTER TABLE partsgroup           SET WITHOUT OIDS;
diff --git a/sql/Pg-upgrade2/tax_reverse_charge.sql b/sql/Pg-upgrade2/tax_reverse_charge.sql
new file mode 100644 (file)
index 0000000..fd1e755
--- /dev/null
@@ -0,0 +1,108 @@
+-- @tag: tax_reverse_charge
+-- @description: Reverse Charge für Kreditorenbelege
+-- @depends: release_3_6_0
+-- @ignore: 0
+
+ALTER TABLE tax add column reverse_charge_chart_id integer;
+
+INSERT INTO chart (
+  accno, description,
+  charttype,   category,  link,
+  taxkey_id
+  )
+SELECT
+  '1577','Abziehbare Vorst. nach §13b UstG 19%',
+  'A',         'E',       'AP_tax:IC_taxpart:IC_taxservice',
+  0
+WHERE EXISTS ( -- update only for SKR03
+    SELECT coa FROM defaults
+    WHERE defaults.coa='Germany-DATEV-SKR03EU' AND NOT EXISTS (SELECT id from chart where accno='1577')
+);
+
+INSERT INTO chart (
+  accno, description,
+  charttype,   category,  link,
+  taxkey_id
+  )
+SELECT
+  '1787','Umsatzsteuer nach §13b UStG 19%',
+  'A',         'I',       'AR_tax:IC_taxpart:IC_taxservice',
+  0
+WHERE EXISTS ( -- update only for SKR03
+    SELECT coa FROM defaults
+    WHERE defaults.coa='Germany-DATEV-SKR03EU' AND NOT EXISTS (SELECT id from chart where accno='1787')
+);
+
+
+INSERT INTO chart (
+  accno, description,
+  charttype,   category,  link,
+  taxkey_id
+  )
+SELECT
+  '1407','Abziehbare Vorst. nach §13b UstG 19%',
+  'A',         'E',       'AP_tax:IC_taxpart:IC_taxservice',
+  0
+WHERE EXISTS ( -- update only for SKR04
+    SELECT coa FROM defaults
+    WHERE defaults.coa='Germany-DATEV-SKR04EU' AND NOT EXISTS (SELECT id from chart where accno='1407')
+);
+
+INSERT INTO chart (
+  accno, description,
+  charttype,   category,  link,
+  taxkey_id
+  )
+SELECT
+  '3837','Umsatzsteuer nach §13b UStG 19%',
+  'A',         'I',       'AR_tax:IC_taxpart:IC_taxservice',
+  0
+WHERE EXISTS ( -- update only for SKR04
+    SELECT coa FROM defaults
+    WHERE defaults.coa='Germany-DATEV-SKR04EU' AND NOT EXISTS (SELECT id from chart where accno='3837')
+);
+
+
+
+INSERT INTO tax (
+  chart_id,
+  reverse_charge_chart_id,
+  rate,
+  taxkey,
+  taxdescription,
+  chart_categories
+  )
+  SELECT
+  (SELECT id FROM chart WHERE accno = '1577'),
+  (SELECT id FROM chart WHERE accno = '1787'), 0,
+  '94', '19% Vorsteuer und 19% Umsatzsteuer', 'EI'
+WHERE EXISTS ( -- update only for SKR03
+    SELECT coa FROM defaults
+    WHERE defaults.coa='Germany-DATEV-SKR03EU'
+);
+
+
+INSERT INTO tax (
+  chart_id,
+  reverse_charge_chart_id,
+  rate,
+  taxkey,
+  taxdescription,
+  chart_categories
+  )
+  SELECT
+  (SELECT id FROM chart WHERE accno = '1407'),
+  (SELECT id FROM chart WHERE accno = '3837'), 0,
+  '94', '19% Vorsteuer und 19% Umsatzsteuer', 'EI'
+WHERE EXISTS ( -- update only for SKR03
+    SELECT coa FROM defaults
+    WHERE defaults.coa='Germany-DATEV-SKR04EU'
+);
+
+-- if not defined
+insert into taxkeys(chart_id,tax_id,taxkey_id,startdate) SELECT (SELECT chart_id FROM tax WHERE taxkey = '94'),0,0,'1970-01-01' WHERE NOT EXISTS
+  (SELECT chart_id from taxkeys where chart_id = ( SELECT chart_id FROM tax WHERE taxkey = '94'));
+
+insert into taxkeys(chart_id,tax_id,taxkey_id,startdate) SELECT (SELECT reverse_charge_chart_id FROM tax WHERE taxkey = '94'),0,0,'1970-01-01' WHERE NOT EXISTS
+  (SELECT chart_id from taxkeys where chart_id = ( SELECT reverse_charge_chart_id FROM tax WHERE taxkey = '94'));
+
index e11415c8506ea137ef462ee551c17fc9967e08cf..c164046cfd1683a7665388f50b8b0d296a4b7467 100644 (file)
@@ -50,6 +50,23 @@ foreach my $accno (@charts) {
 }
 
 # and add department (KOST1 description)
+  SL::DB::Department->new(
+    description => 'Total falsche Abteilung, niemals zuordnen!'
+  )->save;
+
+  SL::DB::Department->new(
+    description => '2. Total falsche Abteilung, niemals zuordnen!'
+  )->save;
+
+  SL::DB::Department->new(
+    description => '3. Total falsche Abteilung, niemals zuordnen!'
+  )->save;
+
+  SL::DB::Department->new(
+    description => 'annahme stelle. Total falsche Abteilung, niemals zuordnen!'
+  )->save;
+
+
   SL::DB::Department->new(
     description => 'Wisavis'
   )->save;
@@ -73,7 +90,8 @@ foreach my $booking (@{ $gl_bookings }) {
 
   # gl
   is ($current_row->[13], $booking->reference, "Buchungstext correct");
-  if (ref $booking->department eq 'SL::DB::Department') {
+  if ($current_row->[36] eq 'wisavis') {
+    is(ref $booking->department eq 'SL::DB::Department', 1, "Department assigned");
     is ($current_row->[36], 'wisavis', "Department correctly assigned");                # lowercase
     is ('Wisavis', $booking->department->description, "Department correctly assigned"); # upper case
   } else {
index 05b7a7a670bf211d427fdd7b4fe90317e6de0cb6..c4902bbe304ef9e9f1e5d50cda6012f9d8ab931a 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 %]