Merge remote-tracking branch 'origin/f-leistungsdatum'
authorJan Büren <jan@kivitendo.de>
Sat, 4 Jul 2020 10:10:20 +0000 (12:10 +0200)
committerJan Büren <jan@kivitendo.de>
Sat, 4 Jul 2020 10:10:20 +0000 (12:10 +0200)
23 files changed:
SL/AM.pm
SL/DB/Manager/Chart.pm
SL/IC.pm
SL/IR.pm
SL/IS.pm
SL/OE.pm
bin/mozilla/ap.pl
bin/mozilla/ar.pl
bin/mozilla/gl.pl
bin/mozilla/io.pl
bin/mozilla/ir.pl
bin/mozilla/is.pl
bin/mozilla/login.pl
bin/mozilla/oe.pl
image/kivitendo_corona.png [new file with mode: 0644]
sql/Pg-upgrade2/konjunkturpaket_2020.sql [new file with mode: 0644]
sql/Pg-upgrade2/konjunkturpaket_2020_SKR03.sql [new file with mode: 0644]
sql/Pg-upgrade2/konjunkturpaket_2020_SKR04.sql [new file with mode: 0644]
sql/Pg-upgrade2/remove_double_tax_entries_skr04.pl [new file with mode: 0644]
sql/Pg-upgrade2/remove_taxkey_15_17_skr04.sql [new file with mode: 0644]
t/tax/tax.t [new file with mode: 0644]
t/year_end/year_end.t
templates/webpages/gl/update_tax_accounts.html

index abf3c92..b7c6003 100644 (file)
--- a/SL/AM.pm
+++ b/SL/AM.pm
@@ -106,7 +106,7 @@ sub get_account {
 
     # get the taxkeys of the account
     $form->{ACCOUNT_TAXKEYS} = [];
-    foreach my $taxkey ( @{ $chart_obj->taxkeys } ) {
+    foreach my $taxkey ( sort { $b->startdate <=> $a->startdate } @{ $chart_obj->taxkeys } ) {
       push @{ $form->{ACCOUNT_TAXKEYS} }, { id             => $taxkey->id,
                                             chart_id       => $taxkey->chart_id,
                                             tax_id         => $taxkey->tax_id,
index 4d11960..d70e7f9 100644 (file)
@@ -135,7 +135,7 @@ sub cache_taxkeys {
   my $rows = selectall_hashref_query($::form, $::form->get_standard_dbh, <<"", $date);
     SELECT DISTINCT ON (chart_id) chart_id, startdate, id
     FROM taxkeys
-    WHERE startdate < ?
+    WHERE startdate <= ?
     ORDER BY chart_id, startdate DESC;
 
   for (@$rows) {
index 0983e2a..868fb74 100644 (file)
--- a/SL/IC.pm
+++ b/SL/IC.pm
@@ -806,7 +806,7 @@ sub retrieve_accounts {
 SQL
 
   my $query_tax = <<SQL;
-    SELECT c.accno, t.taxdescription AS description, t.rate,
+    SELECT c.accno, t.taxdescription AS description, t.id as tax_id, t.rate,
            c.accno as taxnumber
     FROM tax t
     LEFT JOIN chart c ON c.id = t.chart_id
@@ -838,7 +838,7 @@ SQL
     $form->{"taxaccounts_$index"} = $ref->{"accno"};
     $form->{"taxaccounts"} .= "$ref->{accno} "if $form->{"taxaccounts"} !~ /$ref->{accno}/;
 
-    $form->{"$ref->{accno}_${_}"} = $ref->{$_} for qw(rate description taxnumber);
+    $form->{"$ref->{accno}_${_}"} = $ref->{$_} for qw(rate description taxnumber tax_id);
   }
 
   $sth_tax->finish;
index 402454d..1ed136c 100644 (file)
--- a/SL/IR.pm
+++ b/SL/IR.pm
@@ -1071,7 +1071,7 @@ sub retrieve_invoice {
     # get tax rates and description
     my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
     $query =
-      qq|SELECT c.accno, t.taxdescription, t.rate,
+      qq|SELECT c.accno, t.taxdescription, t.rate, t.id as tax_id,
                 c.accno as taxnumber   -- taxnumber is same as accno, but still accessed as taxnumber in code
          FROM tax t
          LEFT JOIN chart c ON (c.id = t.chart_id)
@@ -1098,6 +1098,7 @@ sub retrieve_invoice {
         $form->{"$ptr->{accno}_rate"}         = $ptr->{rate};
         $form->{"$ptr->{accno}_description"}  = $ptr->{taxdescription};
         $form->{"$ptr->{accno}_taxnumber"}    = $ptr->{taxnumber};
+        $form->{"$ptr->{accno}_tax_id"}       = $ptr->{tax_id};
         $form->{taxaccounts}                 .= "$ptr->{accno} ";
       }
 
@@ -1341,7 +1342,7 @@ sub retrieve_item {
     # get tax rates and description
     my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
     $query =
-      qq|SELECT c.accno, t.taxdescription, t.rate, c.accno as taxnumber
+      qq|SELECT c.accno, t.taxdescription, t.rate, c.accno as taxnumber, t.id as tax_id
          FROM tax t
          LEFT JOIN chart c on (c.id = t.chart_id)
          WHERE t.id IN
@@ -1372,6 +1373,7 @@ sub retrieve_item {
         $form->{"$ptr->{accno}_rate"}         = $ptr->{rate};
         $form->{"$ptr->{accno}_description"}  = $ptr->{taxdescription};
         $form->{"$ptr->{accno}_taxnumber"}    = $ptr->{taxnumber};
+        $form->{"$ptr->{accno}_tax_id"}       = $ptr->{tax_id};
         $form->{taxaccounts}                 .= "$ptr->{accno} ";
       }
 
index f79952e..83ccbde 100644 (file)
--- a/SL/IS.pm
+++ b/SL/IS.pm
@@ -171,7 +171,7 @@ sub invoice_details {
   push @arrays, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
   push @arrays, map { "project_cvar_$_->{name}" } @{ $project_cvar_configs };
 
-  my @tax_arrays = qw(taxbase tax taxdescription taxrate taxnumber);
+  my @tax_arrays = qw(taxbase tax taxdescription taxrate taxnumber tax_id);
 
   my @payment_arrays = qw(payment paymentaccount paymentdate paymentsource paymentmemo);
 
@@ -505,28 +505,25 @@ sub invoice_details {
     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} },        $form->format_amount($myconfig, $form->{"${item}_rate"} * 100));
     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} },  $form->{"${item}_rate"} * 100);
     push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} },      $form->{"${item}_taxnumber"});
+    push(@{ $form->{TEMPLATE_ARRAYS}->{tax_id} },         $form->{"${item}_tax_id"});
 
-    # taxnumber is used for grouping the amount of the various taxes
+    # taxnumber (= accno) is used for grouping the amounts of the various taxes and as a prefix in form
 
-    # this code assumes that at most one tax entry can point to the same
+    # This code used to assume that at most one tax entry can point to the same
     # chart_id, even though chart_id does not have a unique constraint!
 
-    # this chart_id is then looked up via its accno, which is the key that is
+    # This chart_id was then looked up via its accno, which is the key that is
     # used to group the different taxes by for a record
 
-    # not every tax has a taxnumber (e.g. tax-free), but that is ok, because
-    # then there would be no tax amount to assign it to
+    # As we now also store the tax_id we can use that to look up the tax
+    # instead, this is only done here to get the (translated) taxdescription.
 
-    my $tax_objs = SL::DB::Manager::Tax->get_objects_from_sql(
-      sql  => 'SELECT * FROM tax WHERE chart_id = (SELECT id FROM chart WHERE accno = ?)',
-      args => [ $form->{"${item}_taxnumber"} ]
-    );
-    my $tax_obj;
-    if ( $tax_objs ) {
-      $tax_obj     = $tax_objs->[0];
+    if ( $form->{"${item}_tax_id"} ) {
+      my $tax_obj = SL::DB::Manager::Tax->find_by(id => $form->{"${item}_tax_id"}) or die "Can't find tax with id " . $form->{"${item}_tax_id"};
+      my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription',  $form->{language_id}, 0) : '';
+      push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
     }
-    my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription',  $form->{language_id}, 0) : '';
-    push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
+
   }
 
   for my $i (1 .. $form->{paidaccounts}) {
@@ -2087,7 +2084,7 @@ sub _retrieve_invoice {
       # get tax rates and description
       my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
       $query =
-        qq|SELECT c.accno, t.taxdescription, t.rate, c.accno as taxnumber
+        qq|SELECT c.accno, t.taxdescription, t.rate, t.id as tax_id, c.accno as taxnumber
            FROM tax t
            LEFT JOIN chart c ON (c.id = t.chart_id)
            WHERE t.id IN
@@ -2110,7 +2107,8 @@ sub _retrieve_invoice {
         if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
           $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
           $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
-          $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
+          $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber}; # don't use this anymore
+          $form->{"$ptr->{accno}_tax_id"}      = $ptr->{tax_id};
           $form->{taxaccounts} .= "$ptr->{accno} ";
         }
 
@@ -2412,7 +2410,7 @@ sub retrieve_item {
     # get tax rates and description
     my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
     $query =
-      qq|SELECT c.accno, t.taxdescription, t.rate, c.accno as taxnumber
+      qq|SELECT c.accno, t.taxdescription, t.id as tax_id, t.rate, c.accno as taxnumber
          FROM tax t
          LEFT JOIN chart c ON (c.id = t.chart_id)
          WHERE t.id in
@@ -2441,6 +2439,7 @@ sub retrieve_item {
         $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
         $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
         $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
+        $form->{"$ptr->{accno}_tax_id"}      = $ptr->{tax_id};
         $form->{taxaccounts} .= "$ptr->{accno} ";
       }
 
index 1169aab..144cb1c 100644 (file)
--- a/SL/OE.pm
+++ b/SL/OE.pm
@@ -1195,7 +1195,7 @@ sub _retrieve {
       # get tax rates and description
       my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
       $query =
-        qq|SELECT c.accno, t.taxdescription, t.rate, c.accno as taxnumber | .
+        qq|SELECT c.accno, t.taxdescription, t.rate, t.id as tax_id, c.accno as taxnumber | .
         qq|FROM tax t | .
         qq|LEFT JOIN chart c on (c.id = t.chart_id) | .
         qq|WHERE t.id IN (SELECT tk.tax_id FROM taxkeys tk | .
@@ -1215,6 +1215,7 @@ sub _retrieve {
           $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
           $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
           $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
+          $form->{"$ptr->{accno}_tax_id"}      = $ptr->{tax_id};
           $form->{taxaccounts} .= "$ptr->{accno} ";
         }
 
@@ -1592,17 +1593,13 @@ sub order_details {
     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} },        $form->format_amount($myconfig, $form->{"${item}_rate"} * 100));
     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} },  $form->{"${item}_rate"} * 100);
     push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} },      $form->{"${item}_taxnumber"});
+    push(@{ $form->{TEMPLATE_ARRAYS}->{tax_id} },         $form->{"${item}_tax_id"});
 
-    my $tax_objs     = SL::DB::Manager::Tax->get_objects_from_sql(
-      sql  => 'SELECT * from tax where chart_id = (SELECT id FROM chart WHERE accno = ?)',
-      args => [ $form->{"${item}_taxnumber"} ]
-    );
-    my $tax_obj;
-    if ( $tax_objs ) {
-      $tax_obj     = $tax_objs->[0];
+    if ( $form->{"${item}_tax_id"} ) {
+      my $tax_obj = SL::DB::Manager::Tax->find_by(id => $form->{"${item}_tax_id"}) or die "Can't find tax with id " . $form->{"${item}_tax_id"};
+      my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription',  $form->{language_id}, 0) : '';
+      push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
     }
-    my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription',  $form->{language_id}, 0) : '';
-    push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
   }
 
   $form->{nodiscount_subtotal} = $form->format_amount($myconfig, $form->{nodiscount_total}, 2);
index 704ae4c..e3a9267 100644 (file)
@@ -501,7 +501,7 @@ sub form_header {
     my $item = shift;
     return [
       $item->{id} .'--'. $item->{rate},
-      $item->{taxdescription} .' '. ($item->{rate} * 100) .' %',
+      $item->{taxkey} . ' - ' . $item->{taxdescription} .' '. ($item->{rate} * 100) .' %',
     ];
   };
 
index faabe50..a6e041f 100644 (file)
@@ -438,7 +438,7 @@ sub form_header {
       $taxchart_to_use    = $item if $key eq $form->{"taxchart_$i"};
 
       push(@taxchart_values, $key);
-      $taxchart_labels{$key} = $item->taxdescription . " " . $item->rate * 100 . ' %';
+      $taxchart_labels{$key} = $item->taxkey . " - " . $item->taxdescription . " " . $item->rate * 100 . ' %';
     }
 
     $taxchart_to_use    //= $default_taxchart // $first_taxchart;
index 4d7ab19..1c29b83 100644 (file)
@@ -835,7 +835,7 @@ sub display_rows {
       $taxchart_to_use    = $item if $key eq $form->{"taxchart_$i"};
 
       push(@taxchart_values, $key);
-      $taxchart_labels{$key} = $item->taxdescription . " " . $item->rate * 100 . ' %';
+      $taxchart_labels{$key} = $item->taxkey . " - " . $item->taxdescription . " " . $item->rate * 100 . ' %';
     }
 
     $taxchart_to_use    //= $default_taxchart // $first_taxchart;
index 31f51fc..a89a311 100644 (file)
@@ -1660,7 +1660,7 @@ sub relink_accounts {
   $form->{"taxaccounts"} =~ s/\s*$//;
   $form->{"taxaccounts"} =~ s/^\s*//;
   foreach my $accno (split(/\s*/, $form->{"taxaccounts"})) {
-    map({ delete($form->{"${accno}_${_}"}); } qw(rate description taxnumber));
+    map({ delete($form->{"${accno}_${_}"}); } qw(rate description taxnumber tax_id)); # add tax_id ?
   }
   $form->{"taxaccounts"} = "";
 
index dd4f113..b84516b 100644 (file)
@@ -451,7 +451,7 @@ sub form_header {
     shiptoemail shiptodepartment_1 shiptodepartment_2 message email subject cc bcc taxaccounts cursor_fokus
     convert_from_do_ids convert_from_oe_ids convert_from_ap_ids show_details gldate useasnew
   ), @custom_hiddens,
-  map { $_.'_rate', $_.'_description', $_.'_taxnumber' } split / /, $form->{taxaccounts}];
+  map { $_.'_rate', $_.'_description', $_.'_taxnumber', $_.'_tax_id' } split / /, $form->{taxaccounts}];
 
   $TMPL_VAR{payment_terms_obj} = get_payment_terms_for_invoice();
   $form->{duedate}             = $TMPL_VAR{payment_terms_obj}->calc_date(reference_date => $form->{invdate}, due_date => $form->{duedate})->to_kivitendo if $TMPL_VAR{payment_terms_obj};
index 9baad4d..2c90dbe 100644 (file)
@@ -536,7 +536,7 @@ sub form_header {
     invoice_id
     show_details
   ), @custom_hiddens,
-  map { $_.'_rate', $_.'_description', $_.'_taxnumber' } split / /, $form->{taxaccounts}];
+  map { $_.'_rate', $_.'_description', $_.'_taxnumber', $_.'_tax_id' } split / /, $form->{taxaccounts}];
 
   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.Draft kivi.File kivi.SalesPurchase kivi.Part kivi.CustomerVendor kivi.Validator ckeditor/ckeditor ckeditor/adapters/jquery kivi.io client_js));
 
index 0121b93..8fd9c38 100644 (file)
@@ -56,6 +56,8 @@ sub company_logo {
   my $git             = SL::Git->new;
   ($form->{git_head}) = $git->get_log(since => 'HEAD~1', until => 'HEAD') if $git->is_git_installation;
   $form->{xmas}       = '_xmas' if (DateTime->today->month == 12 && DateTime->today->day < 27);
+  $form->{xmas}       = '_corona' if (DateTime->today->month >= 7 && DateTime->today->year == 2020
+                                      && DateTime->today->month <= 11);
 
   # create the logo screen
   $form->header() unless $form->{noheader};
index 65277ed..321bec3 100644 (file)
@@ -652,7 +652,7 @@ sub form_header {
         taxpart taxservice taxaccounts cursor_fokus
         show_details useasnew),
         @custom_hiddens,
-        map { $_.'_rate', $_.'_description', $_.'_taxnumber' } split / /, $form->{taxaccounts} ];  # deleted: discount
+        map { $_.'_rate', $_.'_description', $_.'_taxnumber', $_.'_tax_id' } split / /, $form->{taxaccounts} ];  # deleted: discount
 
   $TMPL_VAR->{$_} = $type_check_vars{$_} for keys %type_check_vars;
 
diff --git a/image/kivitendo_corona.png b/image/kivitendo_corona.png
new file mode 100644 (file)
index 0000000..d6f3d03
Binary files /dev/null and b/image/kivitendo_corona.png differ
diff --git a/sql/Pg-upgrade2/konjunkturpaket_2020.sql b/sql/Pg-upgrade2/konjunkturpaket_2020.sql
new file mode 100644 (file)
index 0000000..71a7421
--- /dev/null
@@ -0,0 +1,209 @@
+-- @tag: konjunkturpaket_2020
+-- @description: Anpassung der Steuersätze für 16%/5% für Deutsche DATEV-Kontenrahmen SKR03 und SKR04
+-- @depends: release_3_5_5 konjunkturpaket_2020_SKR03 konjunkturpaket_2020_SKR04
+-- @ignore: 0
+
+-- begin;
+
+DO $$
+
+DECLARE
+  -- variables for main taxkey creation loop, not all are needed
+  _chart_id int;
+  _accno text;
+  _description text;
+  _startdates date[];
+  _tax_ids int[];
+  _taxkeyentry_id int[];
+  _taxkey_ids int[];
+  _rates numeric[];
+  _taxcharts text[];
+
+  current_taxkey record;
+  new_taxkey     record;
+  _rate          numeric;
+  _tax           record; -- store the new tax we need to assign to a chart, e.g. 5%, 16%
+
+  _taxkey    int;
+  _old_rate  numeric;
+  _old_chart text;
+  _new_chart numeric;
+  _new_rate  text;
+
+  _tax_conversion record;
+
+
+BEGIN
+
+IF ( select coa from defaults ) ~ 'DATEV' THEN
+
+--begin;
+--delete from taxkeys where startdate >= '2020-01-01';
+
+--  create temp table temp_taxkey_conversions (taxkey int, old_rate numeric, new_rate numeric, tax_chart_skr03 text, tax_chart_skr04 text);
+--  insert into temp_taxkey_conversions (taxkey, old_rate, new_rate, tax_chart_skr03, tax_chart_skr04) values
+----    (2, 0.07, 0.05, '1773', '3803'),  -- 5% case is handled by skr03 case -> needs different automatic chart: 1773 Umsatzsteuer 5% (SKR03, instead of 1771 Umsatzsteuer 7%) or 3803 Umsatzsteuer 5%
+--    -- (8, 0.07, 0.05, null, null),
+--    -- (3, 0.19, 0.16, null, null),
+--    -- (9, 0.19, 0.16, null, null),
+--   (13, 0.19, 0.16, null, null);
+
+
+create temp table temp_taxkey_conversions (taxkey int, old_rate numeric, old_chart text, new_rate numeric, new_chart text);
+
+IF ( select coa from defaults ) = 'Germany-DATEV-SKR03EU' THEN
+  insert into temp_taxkey_conversions (taxkey, old_rate, old_chart, new_rate, new_chart)
+  values (9, 0.19, '1576', 0.16, '1575'),
+         (8, 0.07, '1571', 0.05, '1568'),
+         (3, 0.19, '1776', 0.16, '1575'),
+         (2, 0.07, '1771', 0.05, '1775');
+         --1776 => 19%
+         --1775 => 16%
+         --1775 =>  5%
+         --1771 =>  7%
+         --
+         --VSt:
+         --1576 => 19%
+         --1575 => 16%
+         --1568 =>  5%
+         --1571 =>  7%
+
+  ELSE  -- Germany-DATEV-SKR04EU
+    insert into temp_taxkey_conversions (taxkey, old_rate, old_chart, new_rate, new_chart)
+    values (9, 0.19, '1406', 0.16, '1405'),
+           (8, 0.07, '1401', 0.05, '1403'),
+           (3, 0.19, '3806', 0.16, '3805'),
+           (2, 0.07, '3801', 0.05, '3803');
+  END IF;
+
+  FOR _chart_id, _accno, _description, _startdates, _tax_ids, _taxkeyentry_id, _taxkey_ids, _rates, _taxcharts IN
+
+      select c.id as chart_id,
+             c.accno,
+             c.description,
+             array_agg(t.startdate order by t.startdate desc) as startdates,
+             array_agg(t.tax_id    order by t.startdate desc) as tax_ids,
+             array_agg(t.id        order by t.startdate desc) as taxkeyentry_id,
+             array_agg(t.taxkey_id order by t.startdate desc) as taxkey_ids,
+             array_agg(tax.rate    order by t.startdate desc) as rates,
+             array_agg(tc.accno    order by t.startdate desc) as taxcharts
+        from taxkeys t
+             left join chart c  on (c.id         = t.chart_id)
+             left join tax      on (tax.id       = t.tax_id)
+             left join chart tc on (tax.chart_id = tc.id)
+       where t.taxkey_id in (select taxkey from temp_taxkey_conversions)  -- 2, 3, 8, 9
+             -- and (c.accno = '8400') -- debug
+             -- you can't filter for valid taxrates 19% or 7% here, as that would still leave the 16% rates as the current one
+    group by c.id,
+             c.accno,
+             c.description
+    order by c.accno
+
+    -- example output for human debugging:
+    --  chart_id | accno |     description     |       startdates        |  tax_ids  | taxkeyentry_id | taxkey_ids |       rates       |  taxcharts
+    -- ----------+-------+---------------------+-------------------------+-----------+----------------+------------+-------------------+-------------
+    --       184 | 8400  | Erlöse 16%/19% USt. | {2007-01-01,1970-01-01} | {777,379} | {793,676}      | {3,3}      | {0.19000,0.16000} | {1776,1775}
+
+  -- each chart with one of the applicable taxkeys should receive two new entries, one starting on 01.07.2020, the other on 01.01.2021
+  LOOP
+    -- 1. create new taxkey entry on 2020-07-01, using the active taxkey on 2020-06-30 as a template, but linking to a tax with a different tax rate
+    -- 2. create new taxkey entry on 2021-01-01, using the active taxkey on 2020-06-30 as a template, but with the new date
+
+
+    -- fetch tax information for 2020-06-30, one day before the change, this should also be the first entry in the ordered array aggregates
+    -- this can be used as the template for the reset on 2021-01-01
+
+    -- raise notice 'looking up current taxkey for chart % and taxkey %', (select accno from chart where id = _chart_id), _taxkey_ids[1];
+    select into current_taxkey tk.*, t.rate, t.taxkey
+           from taxkeys tk
+                left join tax t on (t.id = tk.tax_id)
+          where     tk.taxkey_id = _taxkey_ids[1] -- assume taxkey never changed, use the first one
+                and tk.chart_id = _chart_id
+                and tk.startdate <= '2020-06-30'
+       order by tk.startdate desc
+          limit 1;
+    -- RAISE NOTICE 'found current_taxkey = %', current_taxkey;
+    IF current_taxkey is null then continue; end if;
+    -- RAISE NOTICE 'found chart % with current startdate % and taxkey % (current: %), rate = %', _accno, current_taxkey.startdate, _taxkey_ids[1], current_taxkey.taxkey, current_taxkey.rate;
+
+    -- RAISE NOTICE 'current_taxkey = %', current_taxkey;
+    -- RAISE NOTICE 'looking up tkc for chart_id % and taxkey  %', _chart_id, current_taxkey.taxkey;
+
+    select into _taxkey, _old_rate, _old_chart, _new_chart, _new_rate
+                 taxkey,  old_rate,  old_chart,  new_chart,  new_rate
+    from temp_taxkey_conversions tkc
+    where     tkc.taxkey    = current_taxkey.taxkey
+          and tkc.old_rate = current_taxkey.rate;
+          -- and tkc.new_chart = current_taxkey.new_chart;
+
+    -- raise notice '_old_rate = %, _new_rate = %', _old_rate, _new_rate;
+
+    -- don't do anything if current taxrate is 0, which might be the case for taxkey 13, if they were configured in that way
+    IF current_taxkey.rate != 0 THEN  -- debug
+
+      -- _rate := null;
+
+      -- IF current_taxkey.rate = 0.19 THEN _rate := 0.16; END IF;
+      -- IF current_taxkey.rate = 0.07 THEN _rate := 0.05; END IF;
+      IF _old_rate is NULL THEN
+
+        -- option A: ignore rates which don't make sense, useful for upgrade mode
+        -- option B: throw exception, useful for manually testing script
+
+        -- A:
+        -- if the rate on 2020-06-30 is neither 19 or 7, simply ignore it, it is obviously not configured correctly
+        -- This is the case for SKR03 and chart 8315 (taxkey 13)
+        -- It might be better to throw an exception, however then the test cases don't run. Or just fix the chart via an upgrade script!
+        CONTINUE;
+
+        -- B:
+        -- RAISE EXCEPTION 'illegal current taxrate % on 2020-06-30 (startdate = %) for chart % with taxkey %, should be either 0.19 or 0.07',
+        --                 current_taxkey.rate, current_taxkey.startdate,
+        --                 (select accno from chart where id = current_taxkey.chart_id),
+        --                 current_taxkey.taxkey_id;
+      END IF;
+      -- RAISE NOTICE 'current_taxkey.rate = %, desired rate = %, looking for taxkey_id %', current_taxkey.rate, _rate, _taxkey_ids[1];
+
+                       -- if a chart was created way after 2007 and only ever configured for
+      -- 19%, never 16%, which is the case for SKR04 and taxkey 13, there will only be 3
+      -- taxkeys per chart after adding the two new ones
+
+      -- RAISE NOTICE 'searching for tax with taxkey % and rate %', _taxkey_ids[1], _rate;
+      select into _tax
+                  *
+             from tax
+            where tax.rate = _old_rate
+                  and tax.taxkey = _taxkey_ids[1]
+         order by itime desc
+            limit 1; -- look up tax with same taxkey but corresponding rate. As there will now be two entries for e.g. taxkey 9 with rate of 0.16, the old pre-2007 entry and the new 2020-entry. They can only be differentiated by their (automatic tax) chart_id, or during this upgrade script, via itime, use the later one
+                     -- this also assumes taxkeys never change
+      -- RAISE NOTICE 'tax = %', _tax;
+
+      -- insert into taxkeys (chart_id,                 tax_id,   taxkey_id,                pos_ustva,    startdate)
+      --              values ( (select id from chart where accno = 'kkkkgtkttttkk current_taxkey.chart_id, _tax.id, _tax.taxkey, current_taxkey.pos_ustva, '2020-07-01');
+    END IF;
+
+    -- raise notice 'inserting taxkey';
+    insert into taxkeys (chart_id,                                tax_id,                taxkey_id,                pos_ustva, startdate   )
+                 values (_chart_id,
+                         (select id from tax where taxkey = current_taxkey.taxkey and rate = _new_rate::numeric),
+                         current_taxkey.taxkey, -- 2, 3, 8, 9
+                         current_taxkey.pos_ustva, '2020-07-01');
+
+    -- finally insert a copy of the taxkey on 2020-06-30 with the new startdate 2021-01-01, thereby resetting the tax rates again
+    insert into taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+                 values (_chart_id,
+                         current_taxkey.tax_id,
+                         current_taxkey.taxkey,
+                         current_taxkey.pos_ustva, '2021-01-01');
+
+    -- RAISE NOTICE 'inserted 2 taxkeys for chart % with taxkey %', (select accno from chart where id = current_taxkey.chart_id), current_taxkey.taxkey_id;
+  END LOOP;  --
+END IF; -- DATEV coa
+
+END $$;
+
+drop table temp_taxkey_conversions;
+
+-- select * from taxkeys where startdate >= '2020-01-01';
+-- rollback;
diff --git a/sql/Pg-upgrade2/konjunkturpaket_2020_SKR03.sql b/sql/Pg-upgrade2/konjunkturpaket_2020_SKR03.sql
new file mode 100644 (file)
index 0000000..90ae22f
--- /dev/null
@@ -0,0 +1,100 @@
+-- @tag: konjunkturpaket_2020_SKR03
+-- @description: Anpassung des Deutschen DATEV-Kontenrahmen für SKR03 Konjunkturpaket
+-- @depends: release_3_5_5
+-- @ignore: 0
+
+DO $$
+BEGIN
+
+IF ( select coa from defaults ) = 'Germany-DATEV-SKR03EU' THEN
+
+  -- DEBUG
+  -- UPDATE tax SET taxdescription = 'OLD ' || taxdescription WHERE (taxkey = 3 or taxkey = 9) and rate = 0.16;
+
+  -- rename some of the charts, 1773 already exists in kivitendo as Umsatzsteuer 16% innergem.Erwerb
+  -- this is being used by taxkey 13, which is called "Steuerpflichtige EG-Lieferung zum vollen Steuersatz" in kivitendo
+  -- in DATEV taxkey 13 is: innergem. Lieferung ohne USt-IdNr. and should use a different chart
+  UPDATE chart SET description = 'Umsatzsteuer 5 %' where accno = '1773';
+
+  -- rename charts if they weren't already changed
+  UPDATE chart SET description = 'Erlöse 19 % / 16 % USt' where accno = '8400' and description = 'Erlöse 16%/19% USt.';
+  UPDATE chart SET description = 'Erlöse 7 % / 5 % USt'   where accno = '8300' and description = 'Erlöse 7%USt';
+
+  -- there are two strategies for updating the taxkeys.
+
+  -- 1) in any case we need to add the 2 new cases for 5%: 2/0.05/1773 and 8/0.05/1568
+
+  -- 2) default kivi SKR03 already has the correct configuration for 16%, with two entries 3/0.16/1775 and 9/0.16/1575
+  --   a) we could move those to 5 and 7, and then create new 3/0.16/1775 and 9/0.16/1575 entries
+  --   b) simply keep those entries and don't use 5 and 7 (in which case ar/ap/gl must use deliverydate), or create 5 and 7 manually if needed
+
+  -- strategy a:
+  -- datev reactivated the previously reserved chart 1775 in 2020, but it still exists in kivitendo (at least for SKR03)
+  -- with a taxkey starting from 2007 and pointing to the existing automatic tax chart 1775
+
+  -- strategy b:
+  -- UPDATE tax SET taxkey = 5 WHERE taxkey = 3 and rate = 0.16;
+  -- UPDATE tax SET taxkey = 7 WHERE taxkey = 9 and rate = 0.16;
+
+  -- rename old 8735 to 8736
+  UPDATE chart SET accno = '8736', description = 'Gewährte Skonti 19 % USt' where accno = '8735' and description = 'Gewährte Skonti 16%/19% USt.';
+
+  -- new charts, each of these will need a manual taxkey entry for 2020-07-01 after their tax entries are added
+  -- 8732, 3732, 8735, 3737
+  INSERT INTO chart (accno, description, charttype, category, link, taxkey_id, pos_bwa, pos_bilanz, pos_eur, datevautomatik)
+         VALUES ('8732','Gewährte Skonti 5% USt','A', 'I', 'AR_paid', 2, 1, null,1, 't');
+
+  INSERT INTO chart (accno, description, charttype, category, link, taxkey_id, pos_bwa, pos_bilanz, pos_eur, datevautomatik)
+         VALUES ('3732','Erhaltene Skonti 5 % Vorsteuer','A', 'E', 'AP_paid', 8, 4, null, null, 't');
+
+  -- create new 16% charts Skonto
+  INSERT INTO chart(accno,                description, charttype, category,      link, taxkey_id, pos_bwa, pos_bilanz, pos_eur, datevautomatik, pos_er)
+            VALUES ('8735','Gewährte Skonti 16 % USt',       'A',      'I', 'AR_paid',         3,       1,       null,       1,            't',      1);
+
+  INSERT INTO chart(accno,                description, charttype, category,       link, taxkey_id, pos_bwa, pos_bilanz, pos_eur, datevautomatik, pos_er)
+            VALUES ('3737','Erhaltene Skonti 16 % USt',       'A',      'E', 'AP_paid',         9,       4,       null,    null,            't',   null);
+
+  -- create new chart for Abziehbare Vorsteuer 5 % with taxkey 8 for 3732
+  INSERT INTO chart (accno, description, charttype, category, link, taxkey_id, pos_bwa, pos_bilanz, pos_eur, datevautomatik, pos_er)
+         VALUES ('1568','Abziehbare Vorsteuer 5 %','A', 'E', 'AP_tax:IC_taxpart:IC_taxservice', 8, null, null, 27, 't', 27);
+  INSERT INTO taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+               VALUES ( (select id from chart where accno = '1568'), 0, 0, 66, '1970-01-01');
+
+  -- taxkeys can't be inserted until the new taxes exist
+
+  -- new taxes:
+  -- 5% cases for 2 Umsatzsteuer and 8 Vorsteuer
+  INSERT INTO tax (chart_id, rate, taxkey, taxdescription, chart_categories, skonto_sales_chart_id, skonto_purchase_chart_id)
+  VALUES ( (select id from chart where accno = '1773'), 0.05, 2, 'Umsatzsteuer', 'I', (select id from chart where accno = '8732'), null),
+         -- don't add these two entries if we keep the original two 16% accounts, instead better to add new tax entries with taxkey 5 and 7
+         -- ( (select id from chart where accno = '1775'), 0.16, 3, 'Umsatzsteuer', 'I', (select id from chart where accno = '8735'), null),
+         -- ( (select id from chart where accno = '1575'), 0.16, 9, 'Vorsteuer',    'E', null, (select id from chart where accno = '3735')),
+         ( (select id from chart where accno = '1568'), 0.05, 8, 'Vorsteuer',    'E', null, (select id from chart where accno = '3732'));
+
+  UPDATE tax SET skonto_sales_chart_id    = (select id from chart where accno = '8735') where taxkey = 3 and rate = 0.16 and skonto_sales_chart_id    is null;
+  UPDATE tax SET skonto_purchase_chart_id = (select id from chart where accno = '3737') where taxkey = 9 and rate = 0.16 and skonto_purchase_chart_id is null;
+
+  -- new taxkeys for 5% charts only need one startdate, not valid before and won't change back to anything later
+  -- these taxkeys won't be valid on 2020-06-30, so won't be affected later by big taxkeys update
+  -- However, this will also cause opening the charts before 2020-07-01 via the
+  -- interface to break, as AM.pm always calls get_active_taxkey and there won't
+  -- be an active taxkey before 2020-07-01.
+  -- Alternatively you could set those active from 2020-06-01 and in the taxkey upgrade script check for taxkey entries before that date
+  INSERT INTO taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+               VALUES ( (select id from chart where accno = '8732'), (select id from tax where rate = 0.05 and taxkey = 2 and chart_id = (select id from chart where accno = '1773')), 2, 861, '2020-07-01');
+
+  INSERT INTO taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+               VALUES ( (select id from chart where accno = '3732'), (select id from tax where rate = 0.05 and taxkey = 8 and chart_id = (select id from chart where accno = '1568')), 8, 861, '2020-07-01');
+
+  -- 8735 / 3737 - these were never created in the original SKR03, so also start using them from 2020-07-01
+  -- taxkey for Gewährte Skonti 16 % USt pointing to tax 1775 Umsatzsteuer 16%
+  INSERT INTO taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+               VALUES ( (select id from chart where accno = '8735'), (select id from tax where rate = 0.16 and taxkey = 3 and chart_id = (select id from chart where accno = '1775')), 3, 81, '2020-07-01');
+
+  -- taxkey for Erhaltene Skonti 16 % USt pointing to tax 1575 Vorsteuer 16%
+  INSERT INTO taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+               VALUES ( (select id from chart where accno = '3737'), (select id from tax where rate = 0.16 and taxkey = 9 and chart_id = (select id from chart where accno = '1575')), 9, 66, '2020-07-01');
+
+END IF;
+
+END $$;
diff --git a/sql/Pg-upgrade2/konjunkturpaket_2020_SKR04.sql b/sql/Pg-upgrade2/konjunkturpaket_2020_SKR04.sql
new file mode 100644 (file)
index 0000000..552e285
--- /dev/null
@@ -0,0 +1,54 @@
+-- @tag: konjunkturpaket_2020_SKR04
+-- @description: Anpassung des Deutschen DATEV-Kontenrahmen für SKR04 Konjunkturpaket
+-- @depends: release_3_5_5 remove_double_tax_entries_skr04
+-- @ignore: 0
+
+DO $$
+BEGIN
+
+IF ( select coa from defaults ) = 'Germany-DATEV-SKR04EU' THEN
+
+  -- charts 1403 und 3803 for 5% taxes already existed, reconfigure them
+  UPDATE chart set description = 'Abziehbare Vorsteuer 5 %', taxkey_id = 8 where accno = '1403' and description = 'Abziehbare Vorsteuer aus innergemeinschftl. Erwerb 16%';
+  UPDATE chart set description = 'Umsatzsteuer 5 %', taxkey_id = 2 where accno = '3803' and description = 'Umsatzsteuer aus innergemeinschftl. Erwerb 16%';
+
+  -- DEBUG
+  -- UPDATE tax SET taxdescription = 'OLD ' || taxdescription WHERE (taxkey = 5 or taxkey = 7); -- and rate = 0.16;
+
+  UPDATE taxkeys SET tax_id = (SELECT id FROM tax WHERE taxkey = 5 and rate = 0.16)
+   WHERE chart_id = (SELECT id FROM chart where accno = '4400')
+     AND startdate = '1970-01-01';
+
+  -- new charts for 5%
+  -- 4732 and 5732
+  INSERT INTO chart (accno, description, charttype, category, link, taxkey_id, pos_bwa, pos_bilanz, pos_eur, datevautomatik)
+         VALUES ('4732','Gewährte Skonti 5 % USt','A', 'I', 'AR_paid', 2, 1, null, 1, 't');
+  INSERT INTO chart (accno, description, charttype, category, link, taxkey_id, pos_bwa, pos_bilanz, pos_eur, datevautomatik)
+         VALUES ('5732','Erhaltene Skonti 5 % Vorsteuer','A', 'E', 'AP_paid', 8, 4, null, null, 't');
+
+  -- Gewährte and Erhaltene Skonti 16% already exist, but rename them
+  UPDATE chart SET description = 'Gewährte Skonti 16%'  where accno = '4735' and description = 'Gewährte Skonti 16%/19% USt';
+  UPDATE chart SET description = 'Erhaltene Skonti 16%' where accno = '4735' and description = 'Erhaltene Skonti 16%/19% USt';
+
+  -- taxkeys can't be inserted until the new taxes exist
+  INSERT INTO tax (chart_id, rate, taxkey, taxdescription, chart_categories, skonto_sales_chart_id, skonto_purchase_chart_id)
+  VALUES ( (select id from chart where accno = '3803'), 0.05, 2, 'Umsatzsteuer', 'I', (select id from chart where accno = '4732'), null), -- ok
+         ( (select id from chart where accno = '3805'), 0.16, 3, 'Umsatzsteuer', 'I', (select id from chart where accno = '4735'), null),
+         ( (select id from chart where accno = '1405'), 0.16, 9, 'Vorsteuer',    'E', null, (select id from chart where accno = '5735')),
+         ( (select id from chart where accno = '1403'), 0.05, 8, 'Vorsteuer',    'E', null, (select id from chart where accno = '5732'));
+
+  -- new taxkeys for 5% and 16% only need one startdate, not valid before and won't change back to anything later
+  -- these taxkeys won't be valid on 2020-06-30, so won't be affected later by big taxkeys update
+  -- 4732 and 5732
+  INSERT INTO taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+               VALUES ( (select id from chart where accno = '4732'),
+                      ( select id from tax where rate = 0.05 and taxkey = 2 and chart_id = (select id from chart where accno = '3803')), 2, 861, '2020-07-01'); -- ustva_id like 3801, is this correct?
+
+  INSERT INTO taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+               VALUES ( (select id from chart where accno = '5732'),
+                      (select id from tax where rate = 0.05 and taxkey = 8 and chart_id = (select id from chart where accno = '1403')), 8, 66, '2020-07-01'); -- ustva_id like 1401, is this correct?
+
+  -- the taxkeys for the existing charts will be updated in a later update
+END IF;
+
+END $$;
diff --git a/sql/Pg-upgrade2/remove_double_tax_entries_skr04.pl b/sql/Pg-upgrade2/remove_double_tax_entries_skr04.pl
new file mode 100644 (file)
index 0000000..37d35d6
--- /dev/null
@@ -0,0 +1,59 @@
+# @tag: remove_double_tax_entries_skr04
+# @description: doppelte Steuer-Einträge und alte 16% Konten für SKR04 entfernen, wenn unbebucht
+# @depends: release_3_5_5
+package SL::DBUpgrade2::remove_double_tax_entries_skr04;
+
+use strict;
+use utf8;
+
+use parent qw(SL::DBUpgrade2::Base);
+
+use SL::DBUtils;
+
+sub run {
+  my ($self) = @_;
+
+  if (!$self->check_coa('Germany-DATEV-SKR04EU')) {
+    return 1;
+  }
+
+  my $query = <<SQL;
+    SELECT id FROM tax WHERE chart_id = (SELECT id FROM chart WHERE accno LIKE ?) AND taxkey = ? AND rate = ? ORDER BY id;
+SQL
+
+  my $query2 = <<SQL;
+    DELETE FROM taxkeys WHERE tax_id = ?;
+SQL
+
+  my $query3 = <<SQL;
+    DELETE FROM tax WHERE id = ?;
+SQL
+
+  my @taxes_to_test = (
+    {accno => '3806', taxkey => 3, rate => 0.19},
+    {accno => '1406', taxkey => 9, rate => 0.19},
+    {accno => '3805', taxkey => 5, rate => 0.16},
+    {accno => '1405', taxkey => 7, rate => 0.16},
+
+  );
+
+  foreach my $tax_to_test (@taxes_to_test) {
+    my @entries = selectall_hashref_query($::form, $self->dbh, $query, ($tax_to_test->{accno}, $tax_to_test->{taxkey}, $tax_to_test->{rate}));
+
+    if (scalar @entries > 1) {
+      foreach my $tax (@entries) {
+        my ($num_acc_trans_entries) = $self->dbh->selectrow_array("SELECT COUNT(*) FROM acc_trans WHERE tax_id = ?", undef, $tax->{id});
+        next if $num_acc_trans_entries > 0;
+
+        $self->db_query($query2, bind => [ $tax->{id} ]);
+        $self->db_query($query3, bind => [ $tax->{id} ]);
+
+        last; # delete only one tax
+      }
+    }
+  }
+
+  return 1;
+}
+
+1;
diff --git a/sql/Pg-upgrade2/remove_taxkey_15_17_skr04.sql b/sql/Pg-upgrade2/remove_taxkey_15_17_skr04.sql
new file mode 100644 (file)
index 0000000..e431c51
--- /dev/null
@@ -0,0 +1,19 @@
+-- @tag: remove_taxkey_15_17_skr04
+-- @description: Steuer mit Schlüssel 15 und 17 (16%) für SKR04 entfernen, wenn nicht verknüpft
+-- @depends: release_3_5_5
+
+DELETE FROM tax
+  WHERE (SELECT coa FROM defaults) LIKE 'Germany-DATEV-SKR04EU'
+    AND taxkey = 17
+    AND chart_id = (SELECT chart_id FROM chart WHERE accno LIKE '1403')
+    AND rate = .16
+    AND NOT EXISTS (SELECT id FROM taxkeys WHERE tax_id = tax.id)
+    AND NOT EXISTS (SELECT id FROM acc_trans WHERE tax_id = tax.id);
+
+DELETE FROM tax
+  WHERE (SELECT coa FROM defaults) LIKE 'Germany-DATEV-SKR04EU'
+    AND taxkey = 15
+    AND chart_id = (SELECT chart_id FROM chart WHERE accno LIKE '3803')
+    AND rate = .16
+    AND NOT EXISTS (SELECT id FROM taxkeys WHERE tax_id = tax.id)
+    AND NOT EXISTS (SELECT id FROM acc_trans WHERE tax_id = tax.id);
diff --git a/t/tax/tax.t b/t/tax/tax.t
new file mode 100644 (file)
index 0000000..e2861da
--- /dev/null
@@ -0,0 +1,600 @@
+use Test::More tests => 48;
+use Test::Deep qw(cmp_deeply);
+
+use strict;
+
+use lib 't';
+use utf8;
+
+use Support::TestSetup;
+use Test::Exception;
+
+use SL::DB::Customer;
+use SL::DB::Vendor;
+use SL::DB::Invoice;
+use SL::DB::GLTransaction;
+use SL::DB::AccTransaction;
+use SL::DB::Part;
+use SL::DB::PaymentTerm;
+use SL::DBUtils qw(selectall_hashref_query);
+use SL::Dev::Record qw(:ALL);
+use SL::Dev::CustomerVendor qw(new_customer new_vendor);
+use SL::Dev::Part qw(new_part);
+use SL::Dev::Payment qw(create_payment_terms);
+use Data::Dumper;
+
+Support::TestSetup::login();
+my $dbh = SL::DB->client->dbh;
+
+clear_up();
+
+# TODOs: Storno muß noch korrekt funktionieren
+#  neue Konten für 5% anlegen
+#  Leistungszeitraum vs. Datum Zuord. Steuerperiodest
+
+note('checking if all tax entries exist for Konjunkturprogramm');
+
+# create dates to test on
+my $date_2006   = DateTime->new(year => 2006, month => 6, day => 15);
+my $date_2020_1 = DateTime->new(year => 2020, month => 6, day => 15);
+my $date_2020_2 = DateTime->new(year => 2020, month => 7, day => 15);
+my $date_2021   = DateTime->new(year => 2021, month => 1, day => 15);
+
+# The only way to discern the pre-2007 16% tax from the 2020 16% tax is by
+# their configured automatic tax charts, so look them up here:
+
+my ($chart_vst_19, $chart_vst_16, $chart_vst_5, $chart_vst_7);
+my ($chart_ust_19, $chart_ust_16, $chart_ust_5, $chart_ust_7);
+my ($income_19_accno, $income_7_accno);
+my ($ar_accno, $ap_accno);
+my ($chart_reisekosten_accno, $chart_cash_accno, $chart_bank_accno);
+
+my ($skonto_5, $skonto_16, $skonto_7, $skonto_19); # store acc_trans entries during tests
+
+my $test_kontenrahmen = $::instance_conf->get_coa eq 'Germany-DATEV-SKR04EU' ? 'skr04' : 'skr03';
+
+if ( $test_kontenrahmen eq 'skr03' ) {
+
+  is(SL::DB::Default->get->coa, 'Germany-DATEV-SKR03EU', "coa SKR03 ok");
+
+  $chart_ust_19 = '1776';
+  $chart_ust_16 = '1775';
+  $chart_ust_5  = '1773';
+  $chart_ust_7  = '1771';
+
+  $chart_vst_19 = '1576';
+  $chart_vst_16 = '1575';
+  $chart_vst_5  = '1568';
+  $chart_vst_7  = '1571';
+
+  $income_19_accno = '8400';
+  $income_7_accno  = '8300';
+
+  $chart_reisekosten_accno = 4660;
+  $chart_cash_accno        = 1000;
+  $chart_bank_accno        = 1200;
+
+  $ar_accno = 1400;
+  $ap_accno = 1600;
+
+} elsif ( $test_kontenrahmen eq 'skr04') { # skr04 - test can be ran manually by running t/000setup_database.t with coa for SKR04
+  is(SL::DB::Default->get->coa, 'Germany-DATEV-SKR04EU', "coa SKR04 ok");
+  $chart_vst_19 = '1406';
+  $chart_vst_16 = '1405';
+  $chart_vst_5  = '1403';
+  $chart_vst_7  = '1401';
+
+  $chart_ust_19 = '3806';
+  $chart_ust_16 = '3805';
+  $chart_ust_5  = '3803';
+  $chart_ust_7  = '3801';
+
+  $income_19_accno = '4400';
+  $income_7_accno  = '4300';
+
+  $chart_reisekosten_accno = 6650;
+  $chart_cash_accno        = 1600;
+  $chart_bank_accno        = 1800;
+
+  $ar_accno = 1200;
+  $ap_accno = 3300;
+}
+
+my $tax_vst_19 = SL::DB::Manager::Chart->find_by(accno => $chart_vst_19) or die; # 19%
+my $tax_vst_16 = SL::DB::Manager::Chart->find_by(accno => $chart_vst_16) or die; # 16%
+my $tax_vst_5  = SL::DB::Manager::Chart->find_by(accno => $chart_vst_5 ) or die; #  5%
+my $tax_vst_7  = SL::DB::Manager::Chart->find_by(accno => $chart_vst_7 ) or die; #  7%
+
+my $tax_ust_19 = SL::DB::Manager::Chart->find_by(accno => $chart_ust_19) or die; # 19%
+my $tax_ust_16 = SL::DB::Manager::Chart->find_by(accno => $chart_ust_16) or die; # 16%
+my $tax_ust_5  = SL::DB::Manager::Chart->find_by(accno => $chart_ust_5)  or die; #  5%
+my $tax_ust_7  = SL::DB::Manager::Chart->find_by(accno => $chart_ust_7)  or die; #  7%
+
+my $chart_income_19  = SL::DB::Manager::Chart->find_by(accno => $income_19_accno) or die;
+my $chart_income_7   = SL::DB::Manager::Chart->find_by(accno => $income_7_accno) or die;
+
+my $chart_reisekosten = SL::DB::Manager::Chart->find_by(accno => $chart_reisekosten_accno) or die;
+my $chart_cash        = SL::DB::Manager::Chart->find_by(accno => $chart_cash_accno) or die;
+my $chart_bank        = SL::DB::Manager::Chart->find_by(accno => $chart_bank_accno) or die;
+
+my $payment_terms = create_payment_terms();
+
+is(defined SL::DB::Manager::Tax->find_by(taxkey => 2, rate => 0.05), 1, "tax for taxkey 2 with 5% was created ok");
+is(defined SL::DB::Manager::Tax->find_by(taxkey => 3, rate => 0.16, chart_id => $tax_ust_16->id), 1, "new sales tax for taxkey 3 with 16% exists ok");
+is(defined SL::DB::Manager::Tax->find_by(taxkey => 3, rate => 0.19, chart_id => $tax_ust_19->id), 1, "old sales tax for taxkey 3 with 19% exists ok");
+# is(defined SL::DB::Manager::Tax->find_by(taxkey => 5, rate => 0.16, chart_id => $tax_ust_16->id), 1, "new sales tax for taxkey 5 with 16% exists ok");
+
+# is(defined SL::DB::Manager::Tax->find_by(taxkey => 7, rate => 0.16, chart_id => $tax_ust_16->id), 1, "old purchase tax for taxkey 7 with 16% exists ok");
+is(defined SL::DB::Manager::Tax->find_by(taxkey => 8, rate => 0.07, chart_id => $tax_vst_7->id ), 1, "purchase tax for taxkey 8 with 7% exists ok");
+is(defined SL::DB::Manager::Tax->find_by(taxkey => 9, rate => 0.19, chart_id => $tax_vst_19->id), 1, "old purchase tax for taxkey 9 with 19% exists ok");
+is(defined SL::DB::Manager::Tax->find_by(taxkey => 9, rate => 0.16, chart_id => $tax_vst_16->id), 1, "new purchase tax for taxkey 9 with 16% exists ok");
+
+my $vendor   = new_vendor(  name => 'Testvendor',   payment_id => $payment_terms->id)->save;
+my $customer = new_customer(name => 'Testcustomer', payment_id => $payment_terms->id)->save;
+
+cmp_ok($chart_income_7->get_active_taxkey($date_2020_1)->tax->rate, '==', 0.07, "get_active_taxkey rate for 8300 in 2020_1 ok");
+cmp_ok($chart_income_7->get_active_taxkey($date_2020_2)->tax->rate, '==', 0.05, "get_active_taxkey rate for 8300 in 2020_2 ok");
+cmp_ok($chart_income_7->get_active_taxkey($date_2021  )->tax->rate, '==', 0.07, "get_active_taxkey rate for 8300 in 2021   ok");
+cmp_ok($chart_income_7->get_active_taxkey($date_2020_1)->tax->rate, '==', 0.07, "get_active_taxkey rate for $income_7_accno in 2020_1 ok");
+cmp_ok($chart_income_7->get_active_taxkey($date_2020_2)->tax->rate, '==', 0.05, "get_active_taxkey rate for $income_7_accno in 2020_2 ok");
+cmp_ok($chart_income_7->get_active_taxkey($date_2021  )->tax->rate, '==', 0.07, "get_active_taxkey rate for $income_7_accno in 2021   ok");
+cmp_ok($chart_income_7->get_active_taxkey($date_2006  )->tax->rate, '==', 0.07, "get_active_taxkey rate for $income_7_accno in 2016   ok");
+
+cmp_ok($chart_income_19->get_active_taxkey($date_2020_1)->tax->rate, '==', 0.19, "get_active_taxkey rate for $income_19_accno in 2020_1 ok");
+cmp_ok($chart_income_19->get_active_taxkey($date_2020_2)->tax->rate, '==', 0.16, "get_active_taxkey rate for $income_19_accno in 2020_2 ok");
+cmp_ok($chart_income_19->get_active_taxkey($date_2021  )->tax->rate, '==', 0.19, "get_active_taxkey rate for $income_19_accno in 2021   ok");
+cmp_ok($chart_income_19->get_active_taxkey($date_2006  )->tax->rate, '==', 0.16, "get_active_taxkey rate for $income_19_accno in 2016   ok");
+
+my $bugru19 = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 19%') or die "Can't find bugru19";
+my $bugru7  = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 7%' ) or die "Can't find bugru7";
+
+my $part1 = new_part(partnumber => '1', description => 'part19', buchungsgruppen_id => $bugru19->id)->save;
+my $part2 = new_part(partnumber => '2', description => 'part7',  buchungsgruppen_id => $bugru7->id )->save;
+
+note('sales invoices');
+my $sales_invoice_2006   = create_invoice_for_date('2006',   $date_2006);
+my $sales_invoice_2020_1 = create_invoice_for_date('2020_1', $date_2020_1);
+my $sales_invoice_2020_2 = create_invoice_for_date('2020_2', $date_2020_2);
+my $sales_invoice_2021   = create_invoice_for_date('2021',   $date_2021);
+
+is($sales_invoice_2006->amount,   223, '2006 sales invoice has 16% and 7% tax ok'   ); # 116 + 7
+is($sales_invoice_2020_1->amount, 226, '2020_01 sales invoice has 19% and 7% tax ok'); # 119 + 7
+is($sales_invoice_2020_2->amount, 221, '2020_02 sales invoice has 16% and 5% tax ok'); # 116 + 5
+is($sales_invoice_2021->amount,   226, '2021 sales invoice has 19% and 7% tax ok'   ); # 119 + 7
+
+&datev_test($sales_invoice_2020_2,
+           [
+             {
+               'belegfeld1' => 'test is 2020_2',
+               'buchungstext' => 'Testcustomer',
+               'datum' => '15.07.2020',
+               'leistungsdatum' => '15.07.2020', # should leistungsdatum be empty if it doesn't exist?
+               'gegenkonto' => $income_7_accno,
+               'konto' => $ar_accno,
+               'kost1' => undef,
+               'kost2' => undef,
+               'locked' => undef,
+               'umsatz' => 105,
+               'waehrung' => 'EUR'
+             },
+             {
+               'belegfeld1' => 'test is 2020_2',
+               'buchungstext' => 'Testcustomer',
+               'datum' => '15.07.2020',
+               'leistungsdatum' => '15.07.2020',
+               'gegenkonto' => $income_19_accno,
+               'konto' => $ar_accno,
+               'kost1' => undef,
+               'kost2' => undef,
+               'locked' => undef,
+               'umsatz' => 116,
+               'waehrung' => 'EUR'
+             }
+          ],
+          "datev check for 16/5 ok, no taxkey"
+);
+
+note('sales invoice with differing delivery dates');
+my $sales_invoice_2020_1_with_delivery_date_2020_2 = create_invoice_for_date('deliverydate 2020_1', $date_2020_1, $date_2020_2);
+is($sales_invoice_2020_1_with_delivery_date_2020_2->amount, 221, "sales_invoice from 2020_1 with future delivery_date 2020_2 tax ok");
+
+my $sales_invoice_2020_2_with_delivery_date_2020_1 = create_invoice_for_date('deliverydate 2020_2', $date_2020_2, $date_2020_1);
+is($sales_invoice_2020_2_with_delivery_date_2020_1->amount, 226, "sales_invoice from 2020_2 with   past delivery_date 2020_1 tax ok");
+
+&datev_test($sales_invoice_2020_2_with_delivery_date_2020_1,
+            [
+              {
+                'belegfeld1' => 'test is deliverydate 2020_2',
+                'buchungstext' => 'Testcustomer',
+                'datum' => '15.07.2020',
+                'gegenkonto' => $income_7_accno,
+                'konto' => $ar_accno,
+                'kost1' => undef,
+                'kost2' => undef,
+                'leistungsdatum' => '15.06.2020',
+                'locked' => undef,
+                'umsatz' => 107,
+                'waehrung' => 'EUR'
+              },
+              {
+                'belegfeld1' => 'test is deliverydate 2020_2',
+                'buchungstext' => 'Testcustomer',
+                'datum' => '15.07.2020',
+                'gegenkonto' => $income_19_accno,
+                'konto' => $ar_accno,
+                'kost1' => undef,
+                'kost2' => undef,
+                'leistungsdatum' => '15.06.2020',
+                'locked' => undef,
+                'umsatz' => 119,
+                'waehrung' => 'EUR'
+              }
+            ],
+            "datev check for datev export with delivery_date 19/7 ok, no taxkey"
+);
+
+my $sales_invoice_2021_with_delivery_date_2020_2   = create_invoice_for_date('deliverydate 2020_2', $date_2021, $date_2020_2);
+is($sales_invoice_2021_with_delivery_date_2020_2->amount,   221, "sales_invoice from 2021   with   past delivery_date 2020_2 tax ok");
+
+my $sales_invoice_2020_2_with_delivery_date_2021   = create_invoice_for_date('deliverydate 2021', $date_2020_2, $date_2021);
+is($sales_invoice_2020_2_with_delivery_date_2021->amount,   226, "sales_invoice from 2020_2 with future delivery_date 2021   tax ok");
+
+
+note('ap transactions');
+# in the test we want to test for Reisekosten with 19% and 7%. Normally the user
+# would select the entries from the dropdown, as they may differ from the
+# default, so we have to pass the tax we want to create_ap_transaction
+
+# my $tax_9_16_old = SL::DB::Manager::Tax->find_by(taxkey => 7, rate => 0.16, chart_id => $tax_vst_16->id);
+my $tax_9_19     = SL::DB::Manager::Tax->find_by(taxkey => 9, rate => 0.19, chart_id => $tax_vst_19->id) or die "missing 9_19";
+my $tax_9_16     = SL::DB::Manager::Tax->find_by(taxkey => 9, rate => 0.16, chart_id => $tax_vst_16->id) or die "missing 9_16";
+my $tax_8_7      = SL::DB::Manager::Tax->find_by(taxkey => 8, rate => 0.07, chart_id => $tax_vst_7->id)  or die "missing 8_7";
+my $tax_8_5      = SL::DB::Manager::Tax->find_by(taxkey => 8, rate => 0.05, chart_id => $tax_vst_5->id)  or die "missing 8_5";
+
+# simulate user selecting the "correct" taxes in dropdown:
+my $ap_transaction_2006   = create_ap_transaction_for_date('2006',   $date_2006,   undef, $tax_9_16, $tax_8_7);
+my $ap_transaction_2020_1 = create_ap_transaction_for_date('2020_1', $date_2020_1, undef, $tax_9_19, $tax_8_7);
+my $ap_transaction_2020_2 = create_ap_transaction_for_date('2020_2', $date_2020_2, undef, $tax_9_16, $tax_8_5);
+my $ap_transaction_2021   = create_ap_transaction_for_date('2021',   $date_2021,   undef, $tax_9_19, $tax_8_7);
+
+
+is($ap_transaction_2006->amount,   223, '2006    ap transaction has 16% and 7% tax ok'); # 116 + 7
+is($ap_transaction_2020_1->amount, 226, '2020_01 ap transaction has 19% and 7% tax ok'); # 119 + 7
+is($ap_transaction_2020_2->amount, 221, '2020_02 ap transaction has 16% and 5% tax ok'); # 116 + 5
+is($ap_transaction_2021->amount,   226, '2021    ap transaction has 19% and 7% tax ok'); # 119 + 7
+
+# ap transaction in july, but use old tax
+my $ap_transaction_2020_2_with_delivery_date_2020_1 = create_ap_transaction_for_date('2020_2 with delivery date 2020_1', $date_2020_2, $date_2020_1, $tax_9_19, $tax_8_7);
+is($ap_transaction_2020_2_with_delivery_date_2020_1->amount,   226, 'ap transaction 2020_2 with delivery date 2020_1, 19% and 7% tax ok'); # 119 + 7
+&datev_test($ap_transaction_2020_2_with_delivery_date_2020_1,
+            [
+              {
+                'belegfeld1' => 'test ap_transaction 2020_2 with delivery date 2020_1',
+                'buchungsschluessel' => 8,
+                'buchungstext' => 'Testvendor',
+                'datum' => '15.07.2020',
+                'gegenkonto' => $ap_accno,
+                'konto' => $chart_reisekosten_accno,
+                'kost1' => undef,
+                'kost2' => undef,
+                'leistungsdatum' => '15.06.2020',
+                'locked' => undef,
+                'umsatz' => 107,
+                'waehrung' => 'EUR'
+              },
+              {
+                'belegfeld1' => 'test ap_transaction 2020_2 with delivery date 2020_1',
+                'buchungsschluessel' => 9,
+                'buchungstext' => 'Testvendor',
+                'datum' => '15.07.2020',
+                'gegenkonto' => $ap_accno,
+                'konto' => $chart_reisekosten_accno,
+                'kost1' => undef,
+                'kost2' => undef,
+                'leistungsdatum' => '15.06.2020',
+                'locked' => undef,
+                'umsatz' => 119,
+                'waehrung' => 'EUR'
+              }
+            ],
+            "datev check for ap transaction 2020_2 with delivery date 2020_1, 19% and 7% tax ok"
+);
+
+note('ar transactions');
+
+my $ar_transaction_2006   = create_ar_transaction_for_date('2006',   $date_2006);
+my $ar_transaction_2020_1 = create_ar_transaction_for_date('2020_1', $date_2020_1);
+my $ar_transaction_2020_2 = create_ar_transaction_for_date('2020_2', $date_2020_2);
+my $ar_transaction_2021   = create_ar_transaction_for_date('2021',   $date_2021);
+
+is($ar_transaction_2006->amount,   223, '2006    ar transaction has 16% and 7% tax ok'); # 116 + 7
+is($ar_transaction_2020_1->amount, 226, '2020_01 ar transaction has 19% and 7% tax ok'); # 119 + 7
+is($ar_transaction_2020_2->amount, 221, '2020_02 ar transaction has 16% and 5% tax ok'); # 116 + 5
+is($ar_transaction_2021->amount,   226, '2021    ar transaction has 19% and 7% tax ok'); # 119 + 7
+
+note('gl transactions');
+
+my $gl_2006   = create_gl_transaction_for_date('glincome 2006',   $date_2006,   223);
+my $gl_2020_1 = create_gl_transaction_for_date('glincome 2020_1', $date_2020_1, 226);
+my $gl_2020_2 = create_gl_transaction_for_date('glincome 2020_2', $date_2020_2, 221);
+my $gl_2021   = create_gl_transaction_for_date('glincome 2021',   $date_2021,   226);
+
+is(SL::DB::Manager::GLTransaction->get_all_count(), 4, "4 gltransactions created correctly");
+
+my $result = &get_account_balances;
+# print Dumper($result);
+is_deeply( &get_account_balances,
+        [
+          # {
+          #   'accno' => '1000',
+          #   # 'description' => 'Kasse',
+          #   'sum' => '-896.00000'
+          # },
+          # {
+          #   'accno' => '1400',
+          #   # 'description' => 'Ford. a.Lieferungen und Leistungen',
+          #   'sum' => '-2686.00000'
+          # },
+          {
+            'accno' => '1568',
+            # 'description' => 'Abziehbare Vorsteuer 7%',
+            'sum' => '-5.00000'
+          },
+          {
+            'accno' => '1571',
+            # 'description' => 'Abziehbare Vorsteuer 7%',
+            'sum' => '-28.00000'
+          },
+          {
+            'accno' => '1575',
+            # 'description' => 'Abziehbare Vorsteuer 16%',
+            'sum' => '-32.00000'
+          },
+          {
+            'accno' => '1576',
+            # 'description' => 'Abziehbare Vorsteuer 19 %',
+            'sum' => '-57.00000'
+          },
+          # {
+          #   'accno' => '1600',
+          #   # 'description' => 'Verbindlichkeiten aus Lief.u.Leist.',
+          #   'sum' => '896.00000'
+          # },
+          {
+            'accno' => '1771',
+            # 'description' => 'Umsatzsteuer 7%',
+            'sum' => '77.00000'
+          },
+          {
+            'accno' => '1773',
+            # 'description' => 'Umsatzsteuer 5 %',
+            'sum' => '25.00000'
+          },
+          {
+            'accno' => '1775',
+            # 'description' => 'Umsatzsteuer 16%',
+            'sum' => '128.00000'
+          },
+          {
+            'accno' => '1776',
+            # 'description' => 'Umsatzsteuer 19 %',
+            'sum' => '152.00000'
+          },
+          # {
+          #   'accno' => '4660',
+          #   # 'description' => 'Reisekosten Arbeitnehmer',
+          #   'sum' => '-800.00000'
+          # },
+          # {
+          #   'accno' => $income_7_accno,
+          #   # 'description' => "Erl\x{f6}se 7%USt",
+          #   'sum' => '1600.00000'
+          # },
+          # {
+          #   'accno' => $income_19_accno,
+          #   # 'description' => "Erl\x{f6}se 16%/19% USt.",
+          #   'sum' => '1600.00000'
+          # }
+        ],
+        'account balances after invoices'
+);
+
+note('testing payments with skonto');
+
+my %params = ( chart_id     => $chart_bank->id,
+               payment_type => 'with_skonto_pt',
+             );
+
+$sales_invoice_2020_2->pay_invoice( %params,
+                                    amount    => $sales_invoice_2020_2->amount_less_skonto,
+                                    transdate => $date_2020_2->to_kivitendo,
+                                  );
+
+
+$skonto_5 = SL::DB::Manager::AccTransaction->find_by(trans_id => $sales_invoice_2020_2->id, amount => -5.25);
+like($skonto_5->chart->description, qr/Skonti.*5/, "sales_invoice 2020_2 paid in 2020_2 - skonto 5% ok");
+$skonto_16 = SL::DB::Manager::AccTransaction->find_by(trans_id => $sales_invoice_2020_2->id, amount => -5.80);
+like($skonto_16->chart->description, qr/Skonti.*16/, "sales_invoice 2020_2 paid in 2020_2 - skonto 16% ok");
+
+$sales_invoice_2020_1->pay_invoice( %params,
+                                    amount    => $sales_invoice_2020_1->amount_less_skonto,
+                                    transdate => $date_2020_2->to_kivitendo,
+                                  );
+$skonto_7 = SL::DB::Manager::AccTransaction->find_by(trans_id => $sales_invoice_2020_1->id, amount => -5.35);
+like($skonto_7->chart->description, qr/Skonti.*7/, "sales_invoice 2020_1 paid with skonto in 2020_2 - skonto 7% ok");
+$skonto_19 = SL::DB::Manager::AccTransaction->find_by(trans_id => $sales_invoice_2020_1->id, amount => -5.95);
+like($skonto_19->chart->description, qr/Skonti.*19/, "sales_invoice 2020_1 paid with skonto in 2020_2 - skonto 19% ok");
+
+$ap_transaction_2020_1->pay_invoice( %params,
+                                     amount    => $ap_transaction_2020_1->amount_less_skonto,
+                                     transdate => $date_2020_2->to_kivitendo,
+                                   );
+$skonto_7 = SL::DB::Manager::AccTransaction->find_by(trans_id => $ap_transaction_2020_1->id, amount => 5.35);
+like($skonto_7->chart->description, qr/Skonti.*7/, "ap transaction 2020_1 paid with skonto in 2020_2 - skonto 7% ok");
+$skonto_19 = SL::DB::Manager::AccTransaction->find_by(trans_id => $ap_transaction_2020_1->id, amount => 5.95);
+like($skonto_19->chart->description, qr/Skonti.*19/, "ap transaction 2020_1 paid with skonto in 2020_2 - skonto 19% ok");
+
+
+$ap_transaction_2020_2->pay_invoice( %params,
+                                     amount    => $ap_transaction_2020_2->amount_less_skonto,
+                                     transdate => $date_2021->to_kivitendo,
+                                   );
+$skonto_5 = SL::DB::Manager::AccTransaction->find_by(trans_id => $ap_transaction_2020_2->id, amount => 5.25);
+like($skonto_5->chart->description, qr/Skonti.*5/, "ap transaction 2020_2 paid in 2021 - skonto 5% ok");
+
+$skonto_16 = SL::DB::Manager::AccTransaction->find_by(trans_id => $ap_transaction_2020_2->id, amount => 5.80);
+like($skonto_16->chart->description, qr/Skonti.*16/, "sales_invoice 2020_2 paid in 2021 - skonto 16% ok");
+
+clear_up();
+
+done_testing();
+
+###### functions for setting up data
+
+sub create_invoice_for_date {
+  my ($invnumber, $transdate, $deliverydate) = @_;
+
+  $deliverydate = $transdate unless defined $deliverydate;
+
+  my $sales_invoice = create_sales_invoice(
+    invnumber    => 'test is ' . $invnumber,
+    transdate    => $transdate,
+    customer     => $customer,
+    deliverydate => $deliverydate,
+    payment_terms => $payment_terms,
+    taxincluded  => 0,
+    invoiceitems => [ create_invoice_item(part => $part1, qty => 10, sellprice => 10),
+                      create_invoice_item(part => $part2, qty => 10, sellprice => 10),
+                    ]
+  );
+  return $sales_invoice;
+}
+
+sub create_ar_transaction_for_date {
+  my ($invnumber, $transdate) = @_;
+
+  my $ar_transaction = create_ar_transaction(
+    customer      => $customer,
+    invnumber   => 'test ar' . $invnumber,
+    taxincluded => 0,
+    transdate   => $transdate,
+    ar_chart     => SL::DB::Manager::Chart->find_by(accno => $ar_accno), # pass ar_chart, as it is hardcoded for SKR03 in SL::Dev::Record
+    bookings    => [
+                     {
+                       chart  => $chart_income_19,
+                       amount => 100,
+                     },
+                     {
+                       chart  => $chart_income_7,
+                       amount => 100,
+                     },
+                   ]
+  );
+  return $ar_transaction;
+}
+
+sub create_ap_transaction_for_date {
+  my ($invnumber, $transdate, $deliverydate, $tax_high, $tax_low) = @_;
+
+  # printf("invnumber = %s  tax_high = %s   tax_low = %s\n", $invnumber, $tax_high->accno , $tax_low->accno);
+  my $taxkey_ = $chart_reisekosten->get_active_taxkey($transdate);
+
+  my $ap_transaction = create_ap_transaction(
+    vendor       => $vendor,
+    invnumber    => 'test ap_transaction ' . $invnumber,
+    taxincluded  => 0,
+    transdate    => $transdate,
+    deliverydate => $deliverydate,
+    payment_id   => $payment_terms->id,
+    ap_chart     => SL::DB::Manager::Chart->find_by(accno => $ap_accno), # pass ap_chart, as it is hardcoded for SKR03 in SL::Dev::Record
+    bookings     => [
+                     {
+                       chart  => $chart_reisekosten,
+                       amount => 100,
+                       tax_id => $tax_high->id,
+                     },
+                     {
+                       chart  => $chart_reisekosten,
+                       amount => 100,
+                       tax_id => $tax_low->id,
+                     },
+                   ]
+  );
+  return $ap_transaction;
+}
+
+sub create_gl_transaction_for_date {
+  my ($reference, $transdate, $debitamount) = @_;
+
+  my $gl_transaction = create_gl_transaction(
+    reference   => $reference,
+    taxincluded => 0,
+    transdate   => $transdate,
+    bookings    => [
+                     {
+                       chart  => $chart_income_19,
+                       memo   => 'gl 19',
+                       source => 'gl 19',
+                       credit => 100,
+                     },
+                     {
+                       chart  => $chart_income_7,
+                       memo   => 'gl 7',
+                       source => 'gl 7',
+                       credit => 100,
+                     },
+                     {
+                       chart  => $chart_cash,
+                       debit  => $debitamount,
+                       memo   => 'gl 19+7',
+                       source => 'gl 19+7',
+                     },
+                   ],
+  );
+  return $gl_transaction;
+}
+
+sub get_account_balances {
+  my $query = <<SQL;
+  select c.accno, sum(a.amount)
+    from acc_trans a
+         left join chart c on (c.id = a.chart_id)
+   where c.accno ~ '^17' or c.accno ~ '^15'
+group by c.accno, c.description
+order by c.accno
+SQL
+
+  my $result = selectall_hashref_query($::form, $dbh, $query);
+  return $result;
+};
+
+sub datev_test {
+  my ($invoice, $expected_data, $msg) = @_;
+
+  my $datev = SL::DATEV->new(
+    dbh        => $invoice->db->dbh,
+    trans_id   => $invoice->id,
+  );
+
+  $datev->generate_datev_data;
+  my @data_datev   = sort { $a->{umsatz} <=> $b->{umsatz} } @{ $datev->generate_datev_lines() };
+
+  # print Dumper(\@data_datev);
+
+  cmp_deeply(\@data_datev, $expected_data, $msg);
+}
+
+sub clear_up {
+  SL::DB::Manager::OrderItem->delete_all(all => 1);
+  SL::DB::Manager::Order->delete_all(all => 1);
+  SL::DB::Manager::InvoiceItem->delete_all(all => 1);
+  SL::DB::Manager::Invoice->delete_all(all => 1);
+  SL::DB::Manager::PurchaseInvoice->delete_all(all => 1);
+  SL::DB::Manager::GLTransaction->delete_all(all => 1);
+  SL::DB::Manager::Part->delete_all(all => 1);
+  SL::DB::Manager::Customer->delete_all(all => 1);
+  SL::DB::Manager::Vendor->delete_all(all => 1);
+  SL::DB::Manager::PaymentTerm->delete_all(all => 1);
+};
+
+1;
index 1246237..39a7f1c 100644 (file)
@@ -30,7 +30,11 @@ clear_up();
 # * also the default test client has the accounting method "cash" rather than "accrual"
 #   (Ist-versteuerung, rather than Soll-versteuerung)
 
-my $year = DateTime->today->year;
+# hardcode for 2019, as this will break in 2020 due to change in tax (19/16 and 7/5) because we check for account sums
+# can be changed back to current year in 2021
+my $year = 2019; # DateTime->today->year;
+my $start_of_year = DateTime->new(year => $year, month => 01, day => 01);
+my $booking_date  = DateTime->new(year => $year, month => 12, day => 22);
 
 note('configuring accounts');
 my $bank_account = SL::DB::BankAccount->new(
@@ -81,6 +85,7 @@ $dbh->do('UPDATE defaults SET loss_carried_forward_chart_id   = ' . $loss_accoun
 note('creating transactions');
 my $ar_transaction = create_ar_transaction(
   taxincluded => 0,
+  transdate   => $booking_date,
   bookings    => [
                    {
                      chart  => $income_chart, # income 19%, taxkey 3
@@ -91,13 +96,14 @@ my $ar_transaction = create_ar_transaction(
   
 $ar_transaction->pay_invoice(
                               chart_id     => $bank_account->chart_id,
-                              transdate    => DateTime->today_local->to_kivitendo,
                               amount       => $ar_transaction->amount,
+                              transdate    => $booking_date->to_kivitendo,
                               payment_type => 'without_skonto',
                             );
 
 my $ar_transaction2 = create_ar_transaction(
   taxincluded => 1,
+  transdate   => $booking_date,
   bookings    => [
                    {
                      chart  => $income_chart, # income 19%, taxkey 3
@@ -108,6 +114,7 @@ my $ar_transaction2 = create_ar_transaction(
 
 my $ap_transaction = create_ap_transaction(
   taxincluded => 0,
+  transdate   => $booking_date,
   bookings    => [
                    {
                      chart  => SL::DB::Manager::Chart->find_by( accno => '3400' ), # Wareneingang 19%, taxkey 9
@@ -116,8 +123,7 @@ my $ap_transaction = create_ap_transaction(
                  ],
 );
 
-
-gl_booking(40, "01.01.$year", 'foo', 'bar', $bank, $privateinlagen, 1, 0);
+gl_booking(40, $start_of_year, 'foo', 'bar', $bank, $privateinlagen, 1, 0);
 
 is(SL::DB::Manager::AccTransaction->get_all_count(                                ), 13, 'acc_trans transactions created ok');
 is(SL::DB::Manager::AccTransaction->get_all_count(where => [ ob_transaction => 1 ]),  2, 'acc_trans ob_transactions created ok');
@@ -392,8 +398,8 @@ is_deeply( &get_final_balances,
 # adjust that profit-loss-carry-over # chart, rather than creating a new entry
 # for the loss.
 
-gl_booking(10, "22.12.$year", 'foo', 'bar', $cash, $bank, 0, 0);
-gl_booking(5,  "22.12.$year", 'foo', 'bar', $betriebsbedarf, $cash, 0, 0);
+gl_booking(10, $booking_date, 'foo', 'bar', $cash, $bank, 0, 0);
+gl_booking(5,  $booking_date, 'foo', 'bar', $betriebsbedarf, $cash, 0, 0);
 
 SL::Controller::YearEndTransactions::_year_end_bookings( start_date => $start_date,
                                                          cb_date    => $cb_date,
@@ -620,12 +626,12 @@ sub gl_booking {
   # wrapper around SL::Dev::Record::create_gl_transaction for quickly creating transactions
   my ($amount, $date, $reference, $description, $gegenkonto, $konto, $ob, $cb) = @_;
 
-  my $transdate = $::locale->parse_date_to_object($date);
+  my $transdate = $::locale->parse_date_to_object($date);
 
   return create_gl_transaction(
     ob_transaction => $ob,
     cb_transaction => $cb,
-    transdate      => $transdate,
+    transdate      => $date,
     reference      => $reference,
     description    => $description,
     bookings       => [
index 88d8668..72ce6ef 100644 (file)
@@ -1,4 +1,4 @@
 [% USE L %][%- USE LxERP -%]
 [% FOR row = TAX_ACCOUNTS %]
-<option value='[% row.id %]--[% row.rate %]'[% IF row.is_default %] selected[% END %]>[% row.taxdescription %] [% LxERP.round_amount(row.rate * 100) %] %</option>
+<option value='[% row.id %]--[% row.rate %]'[% IF row.is_default %] selected[% END %]>[% row.taxkey _ " - " _ row.taxdescription %] [% LxERP.round_amount(row.rate * 100) %] %</option>
 [% END %]