commit 72e545d62bd1eb0d65e9afc0c05b97306864a15a
Author: Bernd Bleßmann <bernd@kivitendo-premium.de>
Date:   Mon Dec 20 15:40:51 2021 +0100

    Anzahlungs-Rg.: Workflow vom Auftrag: alle Anzahlugns-Rg. und Schluss-Rg.
    
    Wird der Workflow vom Auftrag aus begonnen, so werden alle Anzahlungs- und
    die Schlussrechnung vom Auftrag aus gemacht.
    Der Einstieg über eine Anzahlungs-Rg. und dann der Workflow
    "weitere Anzahlungs-Rg." (...) und hieraus Schluss-Rg. bleibt bestehen.

diff --git a/SL/Controller/Order.pm b/SL/Controller/Order.pm
index 7d421ef01..b4328a1e2 100644
--- a/SL/Controller/Order.pm
+++ b/SL/Controller/Order.pm
@@ -57,11 +57,11 @@ use Rose::Object::MakeMethods::Generic
 __PACKAGE__->run_before('check_auth');
 
 __PACKAGE__->run_before('recalc',
-                        only => [ qw(save save_as_new save_and_delivery_order save_and_invoice save_and_invoice_for_advance_payment save_and_ap_transaction
+                        only => [ qw(save save_as_new save_and_delivery_order save_and_invoice save_and_invoice_for_advance_payment save_and_final_invoice save_and_ap_transaction
                                      print send_email) ]);
 
 __PACKAGE__->run_before('get_unalterable_data',
-                        only => [ qw(save save_as_new save_and_delivery_order save_and_invoice save_and_invoice_for_advance_payment save_and_ap_transaction
+                        only => [ qw(save save_as_new save_and_delivery_order save_and_invoice save_and_invoice_for_advance_payment save_and_final_invoice save_and_ap_transaction
                                      print send_email) ]);
 
 #
@@ -698,6 +698,16 @@ sub action_save_and_invoice_for_advance_payment {
   );
 }
 
+sub action_save_and_final_invoice {
+  my ($self) = @_;
+
+  $self->save_and_redirect_to(
+    controller       => 'oe.pl',
+    action           => 'oe_invoice_from_order',
+    new_invoice_type => 'final_invoice',
+  );
+}
+
 # workflow from sales order to sales quotation
 sub action_sales_quotation {
   $_[0]->workflow_sales_or_request_for_quotation();
@@ -1990,6 +2000,12 @@ sub setup_edit_action_bar {
     $has_invoice_for_advance_payment = any {'SL::DB::Invoice' eq ref $_ && "invoice_for_advance_payment" eq $_->type} @$lr;
   }
 
+  my $has_final_invoice;
+  if ($self->order->id && $self->type eq sales_order_type()) {
+    my $lr = $self->order->linked_records(direction => 'to', to => ['Invoice']);
+    $has_final_invoice               = any {'SL::DB::Invoice' eq ref $_ && "final_invoice" eq $_->type} @$lr;
+  }
+
   for my $bar ($::request->layout->get('actionbar')) {
     $bar->add(
       combobox => [
@@ -2057,15 +2073,25 @@ sub setup_edit_action_bar {
           ],
         ],
         action => [
-          t8('Save and Invoice for Advance Payment'),
+          ($has_invoice_for_advance_payment ? t8('Save and Further Invoice for Advance Payment') : t8('Save and Invoice for Advance Payment')),
           call      => [ 'kivi.Order.save', 'save_and_invoice_for_advance_payment', $::instance_conf->get_order_warn_duplicate_parts ],
           checks    => [ 'kivi.Order.check_save_active_periodic_invoices',
                          @req_trans_cost_art, @req_cusordnumber,
           ],
-          disabled  => $has_invoice_for_advance_payment ? t8('This order has already an invoice for advanced payment.')
-                                                        : undef,
+          disabled  => $has_final_invoice ? t8('This order has already a final invoice.')
+                                          : undef,
           only_if   => (any { $self->type eq $_ } (sales_order_type())),
         ],
+        action => [
+          t8('Save and Final Invoice'),
+          call      => [ 'kivi.Order.save', 'save_and_final_invoice', $::instance_conf->get_order_warn_duplicate_parts ],
+          checks    => [ 'kivi.Order.check_save_active_periodic_invoices',
+                         @req_trans_cost_art, @req_cusordnumber,
+          ],
+          disabled  => $has_final_invoice ? t8('This order has already a final invoice.')
+                                          : undef,
+          only_if   => (any { $self->type eq $_ } (sales_order_type())) && $has_invoice_for_advance_payment,
+        ],
         action => [
           t8('Save and AP Transaction'),
           call      => [ 'kivi.Order.save', 'save_and_ap_transaction', $::instance_conf->get_order_warn_duplicate_parts ],
diff --git a/SL/IS.pm b/SL/IS.pm
index 8b79376d2..ba4c2a183 100644
--- a/SL/IS.pm
+++ b/SL/IS.pm
@@ -578,7 +578,9 @@ sub invoice_details {
   $form->{username} = $myconfig->{name};
   $form->{$_} = $form->format_amount($myconfig, $form->{$_}, 2) for @separate_totals;
 
-  foreach my $invoice_for_advance_payment (@{$self->_get_invoices_for_advance_payment($form->{convert_from_ar_ids} || $form->{id})}) {
+  my $id_for_iap = $form->{convert_from_oe_ids} || $form->{convert_from_ar_ids} || $form->{id};
+  my $from_order = !!$form->{convert_from_oe_ids};
+  foreach my $invoice_for_advance_payment (@{$self->_get_invoices_for_advance_payment($id_for_iap, $from_order)}) {
     # Collect VAT of invoices for advance payment.
     # Set sellprices to fxsellprices for items, because
     # the PriceTaxCalculator sets fxsellprice from sellprice before calculating.
@@ -1090,7 +1092,9 @@ SQL
 
   my $iap_amounts;
   if ($form->{type} eq 'final_invoice') {
-    my $invoices_for_advance_payment = $self->_get_invoices_for_advance_payment($form->{convert_from_ar_ids} || $form->{id});
+    my $id_for_iap = $form->{convert_from_oe_ids} || $form->{convert_from_ar_ids} || $form->{id};
+    my $from_order = !!$form->{convert_from_oe_ids};
+    my $invoices_for_advance_payment = $self->_get_invoices_for_advance_payment($id_for_iap, $from_order);
     if (scalar @$invoices_for_advance_payment > 0) {
       # reverse booking for invoices for advance payment
       foreach my $invoice_for_advance_payment (@$invoices_for_advance_payment) {
@@ -1587,15 +1591,44 @@ SQL
 }
 
 sub _get_invoices_for_advance_payment {
-  my ($self, $id) = @_;
+  my ($self, $id, $id_is_from_order) = @_;
 
   return [] if !$id;
 
-  my $invoice_obj      = SL::DB::Invoice->new(id => $id*1)->load;
-  my $links            = $invoice_obj->linked_records(direction => 'from', from => ['Invoice'], recursive => 1);
+  # Search all related invoices for advance payment.
+  # Case 1:
+  # (order) -> invoice for adv. payment 1 -> invoice for adv. payment 2 -> invoice for adv. payment 3 -> final invoice
+  #
+  # Case 2:
+  # order -> invoice for adv. payment 1
+  #   | |`-> invoice for adv. payment 2
+  #   | `--> invoice for adv. payment 3
+  #   `----> final invoice
+  #
+  # The id is currently that from the last invoice for adv. payment (3 in this example),
+  # that from the final invoice or that from the order.
+
+  my $invoice_obj;
+  my $order_obj;
+  my $links;
+
+  if (!$id_is_from_order) {
+    $invoice_obj = SL::DB::Invoice->load_cached($id*1);
+    $links       = $invoice_obj->linked_records(direction => 'from', from => ['Order']);
+    $order_obj   = $links->[0];
+  } else {
+    $order_obj   = SL::DB::Order->load_cached($id*1);
+  }
+
+  if ($order_obj) {
+    $links        = $order_obj  ->linked_records(direction => 'to',   to => ['Invoice']);
+  } else {
+    $links        = $invoice_obj->linked_records(direction => 'from', from => ['Invoice'], recursive => 1);
+  }
+
   my @related_invoices = grep {'SL::DB::Invoice' eq ref $_ && "invoice_for_advance_payment" eq $_->type} @$links;
 
-  push @related_invoices, $invoice_obj if "invoice_for_advance_payment" eq $invoice_obj->type;
+  push @related_invoices, $invoice_obj if !$order_obj && "invoice_for_advance_payment" eq $invoice_obj->type;
 
   return \@related_invoices;
 }
@@ -2109,7 +2142,7 @@ sub _delete_invoice {
 
   # if we delete a final invoice, the reverse bookings for the clearing account in the invoice for advance payment
   # must be deleted as well
-  my $invoices_for_advance_payment = $self->_get_invoices_for_advance_payment($form->{convert_from_ar_ids} || $form->{id});
+  my $invoices_for_advance_payment = $self->_get_invoices_for_advance_payment($form->{id});
 
   # Todo: allow only if invoice for advance payment is not paid.
   # die if any { $_->paid } for @$invoices_for_advance_payment;
diff --git a/bin/mozilla/is.pl b/bin/mozilla/is.pl
index b4e1a7bcd..93d5dbe6c 100644
--- a/bin/mozilla/is.pl
+++ b/bin/mozilla/is.pl
@@ -336,6 +336,13 @@ sub setup_is_action_bar {
     $has_final_invoice = any {'SL::DB::Invoice' eq ref $_ && "final_invoice" eq $_->invoice_type} @$lr;
   }
 
+  my $is_invoice_for_advance_payment_from_order;
+  if ($form->{id} && $form->{type} eq "invoice_for_advance_payment") {
+    my $invoice_obj = SL::DB::Invoice->load_cached($form->{id});
+    my $lr          = $invoice_obj->linked_records(direction => 'from', from => ['Order']);
+    $is_invoice_for_advance_payment_from_order = scalar @$lr >= 1;
+  }
+
   for my $bar ($::request->layout->get('actionbar')) {
     $bar->add(
       action => [
@@ -428,6 +435,7 @@ sub setup_is_action_bar {
                     : !$form->{id}      ? t8('This invoice has not been posted yet.')
                     : $has_further_invoice_for_advance_payment ? t8('This invoice has already a further invoice for advanced payment.')
                     : $has_final_invoice                       ? t8('This invoice has already a final invoice.')
+                    : $is_invoice_for_advance_payment_from_order ? t8('This invoice was added from an order. See there.')
                     :                     undef,
           only_if  => $form->{type} eq "invoice_for_advance_payment",
         ],
@@ -439,6 +447,7 @@ sub setup_is_action_bar {
                     : !$form->{id}      ? t8('This invoice has not been posted yet.')
                     : $has_further_invoice_for_advance_payment ? t8('This invoice has a further invoice for advanced payment.')
                     : $has_final_invoice                       ? t8('This invoice has already a final invoice.')
+                    : $is_invoice_for_advance_payment_from_order ? t8('This invoice was added from an order. See there.')
                     :                     undef,
           only_if  => $form->{type} eq "invoice_for_advance_payment",
         ],
@@ -1213,11 +1222,6 @@ sub final_invoice {
 
   $main::auth->assert('invoice_edit');
 
-  # search all related invoices for advance payment
-  #
-  # (order) -> invoice for adv. payment 1 -> invoice for adv. payment 2 -> invoice for adv. payment 3 -> final invoice
-  #
-  # we are currently in the last invoice for adv. payment (3 in this example)
   my $related_invoices = IS->_get_invoices_for_advance_payment($form->{id});
 
   delete @{ $form }{qw(printed emailed queued invnumber invdate exchangerate forex deliverydate datepaid_1 gldate_1 acc_trans_id_1 source_1 memo_1 paid_1 exchangerate_1 AP_paid_1 storno locked)};
diff --git a/bin/mozilla/oe.pl b/bin/mozilla/oe.pl
index b9edf4c96..96764ebdf 100644
--- a/bin/mozilla/oe.pl
+++ b/bin/mozilla/oe.pl
@@ -1654,7 +1654,7 @@ sub invoice {
     $::dispatcher->end_request;
   }
 
-  _oe_remove_delivered_or_billed_rows(id => $form->{id}, type => 'billed');
+  _oe_remove_delivered_or_billed_rows(id => $form->{id}, type => 'billed') if $form->{new_invoice_type} ne 'final_invoice';
 
   $form->{cp_id} *= 1;
 
@@ -1699,8 +1699,8 @@ sub invoice {
 
   if (   $form->{type} eq 'sales_order'
       || $form->{type} eq 'sales_quotation') {
-    $form->{title}  = ($form->{new_invoice_type} eq 'invoice_for_advance_payment')
-                    ? $locale->text('Add Invoice for Advance Payment')
+    $form->{title}  = ($form->{new_invoice_type} eq 'invoice_for_advance_payment') ? $locale->text('Add Invoice for Advance Payment')
+                    : ($form->{new_invoice_type} eq 'final_invoice')               ? $locale->text('Add Final Invoice')
                     : $locale->text('Add Sales Invoice');
     $form->{script} = 'is.pl';
     $script         = "is";
