commit 5c859d6455cd282a69bc354bd270efb87e1b8760
Author: Bernd Bleßmann <bernd@kivitendo-premium.de>
Date:   Mon Jan 29 13:23:57 2018 +0100

    Auftrags-Controller: Konfiguration wiederkehrender Rechnungen.

diff --git a/SL/Controller/Order.pm b/SL/Controller/Order.pm
index b1a90f353..ac619d5bd 100644
--- a/SL/Controller/Order.pm
+++ b/SL/Controller/Order.pm
@@ -4,7 +4,7 @@ use strict;
 use parent qw(SL::Controller::Base);
 
 use SL::Helper::Flash qw(flash_later);
-use SL::Presenter::Tag qw(select_tag);
+use SL::Presenter::Tag qw(select_tag hidden_tag);
 use SL::Locale::String qw(t8);
 use SL::SessionFile::Random;
 use SL::PriceSource;
@@ -25,7 +25,7 @@ use SL::Controller::Helper::GetModels;
 
 use List::Util qw(first);
 use List::UtilsBy qw(sort_by uniq_by);
-use List::MoreUtils qw(none pairwise first_index);
+use List::MoreUtils qw(any none pairwise first_index);
 use English qw(-no_match_vars);
 use File::Spec;
 use Cwd;
@@ -348,6 +348,105 @@ sub action_send_email {
       ->render($self);
 }
 
+# open the periodic invoices config dialog
+#
+# If there are values in the form (i.e. dialog was opened before),
+# then use this values. Create new ones, else.
+sub action_show_periodic_invoices_config_dialog {
+  my ($self) = @_;
+
+  my $config = _make_periodic_invoices_config_from_yaml(delete $::form->{config});
+  $config  ||= SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $::form->{id}) if $::form->{id};
+  $config  ||= SL::DB::PeriodicInvoicesConfig->new(periodicity             => 'm',
+                                                   order_value_periodicity => 'p', # = same as periodicity
+                                                   start_date_as_date      => $::form->{transdate} || $::form->current_date,
+                                                   extend_automatically_by => 12,
+                                                   active                  => 1,
+                                                   email_subject           => GenericTranslations->get(
+                                                                                language_id      => $::form->{language_id},
+                                                                                translation_type =>"preset_text_periodic_invoices_email_subject"),
+                                                   email_body              => GenericTranslations->get(
+                                                                                language_id      => $::form->{language_id},
+                                                                                translation_type =>"preset_text_periodic_invoices_email_body"),
+  );
+  $config->periodicity('m')             if none { $_ eq $config->periodicity             }       @SL::DB::PeriodicInvoicesConfig::PERIODICITIES;
+  $config->order_value_periodicity('p') if none { $_ eq $config->order_value_periodicity } ('p', @SL::DB::PeriodicInvoicesConfig::ORDER_VALUE_PERIODICITIES);
+
+  $::form->get_lists(printers => "ALL_PRINTERS",
+                     charts   => { key       => 'ALL_CHARTS',
+                                   transdate => 'current_date' });
+
+  $::form->{AR} = [ grep { $_->{link} =~ m/(?:^|:)AR(?::|$)/ } @{ $::form->{ALL_CHARTS} } ];
+
+  if ($::form->{customer_id}) {
+    $::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(where => [ cp_cv_id => $::form->{customer_id} ]);
+  }
+
+  $self->render('oe/edit_periodic_invoices_config', { layout => 0 },
+                popup_dialog             => 1,
+                popup_js_close_function  => 'kivi.Order.close_periodic_invoices_config_dialog()',
+                popup_js_assign_function => 'kivi.Order.assign_periodic_invoices_config()',
+                config                   => $config,
+                %$::form);
+}
+
+# assign the values of the periodic invoices config dialog
+# as yaml in the hidden tag and set the status.
+sub action_assign_periodic_invoices_config {
+  my ($self) = @_;
+
+  $::form->isblank('start_date_as_date', $::locale->text('The start date is missing.'));
+
+  my $config = { active                  => $::form->{active}     ? 1 : 0,
+                 terminated              => $::form->{terminated} ? 1 : 0,
+                 direct_debit            => $::form->{direct_debit} ? 1 : 0,
+                 periodicity             => (any { $_ eq $::form->{periodicity}             }       @SL::DB::PeriodicInvoicesConfig::PERIODICITIES)              ? $::form->{periodicity}             : 'm',
+                 order_value_periodicity => (any { $_ eq $::form->{order_value_periodicity} } ('p', @SL::DB::PeriodicInvoicesConfig::ORDER_VALUE_PERIODICITIES)) ? $::form->{order_value_periodicity} : 'p',
+                 start_date_as_date      => $::form->{start_date_as_date},
+                 end_date_as_date        => $::form->{end_date_as_date},
+                 first_billing_date_as_date => $::form->{first_billing_date_as_date},
+                 print                   => $::form->{print} ? 1 : 0,
+                 printer_id              => $::form->{print} ? $::form->{printer_id} * 1 : undef,
+                 copies                  => $::form->{copies} * 1 ? $::form->{copies} : 1,
+                 extend_automatically_by => $::form->{extend_automatically_by} * 1 || undef,
+                 ar_chart_id             => $::form->{ar_chart_id} * 1,
+                 send_email                 => $::form->{send_email} ? 1 : 0,
+                 email_recipient_contact_id => $::form->{email_recipient_contact_id} * 1 || undef,
+                 email_recipient_address    => $::form->{email_recipient_address},
+                 email_sender               => $::form->{email_sender},
+                 email_subject              => $::form->{email_subject},
+                 email_body                 => $::form->{email_body},
+               };
+
+  my $periodic_invoices_config = YAML::Dump($config);
+
+  my $status = $self->_get_periodic_invoices_status($config);
+
+  $self->js
+    ->remove('#order_periodic_invoices_config')
+    ->insertAfter(hidden_tag('order.periodic_invoices_config', $periodic_invoices_config), '#periodic_invoices_status')
+    ->run('kivi.Order.close_periodic_invoices_config_dialog')
+    ->html('#periodic_invoices_status', $status)
+    ->flash('info', t8('The periodic invoices config has been assigned.'))
+    ->render($self);
+}
+
+sub action_get_has_active_periodic_invoices {
+  my ($self) = @_;
+
+  my $config = _make_periodic_invoices_config_from_yaml(delete $::form->{config});
+  $config  ||= SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $::form->{id}) if $::form->{id};
+
+  my $has_active_periodic_invoices =
+       $self->type eq _sales_order_type()
+    && $config
+    && $config->active
+    && (!$config->end_date || ($config->end_date > DateTime->today_local))
+    && $config->get_previous_billed_period_start_date;
+
+  $_[0]->render(\ !!$has_active_periodic_invoices, { type => 'text' });
+}
+
 # save the order and redirect to the frontend subroutine for a new
 # delivery order
 sub action_save_and_delivery_order {
@@ -896,7 +995,7 @@ sub _load_order {
 
 # load or create a new order object
 #
-# And assign changes from the for to this object.
+# And assign changes from the form to this object.
 # If the order is loaded from db, check if items are deleted in the form,
 # remove them form the object and collect them for removing from db on saving.
 # Then create/update items from form (via _make_item) and add them.
@@ -910,9 +1009,14 @@ sub _make_order {
   $order   = SL::DB::Manager::Order->find_by(id => $::form->{id}) if $::form->{id};
   $order ||= SL::DB::Order->new(orderitems => []);
 
-  my $form_orderitems = delete $::form->{order}->{orderitems};
+  my $form_orderitems               = delete $::form->{order}->{orderitems};
+  my $form_periodic_invoices_config = delete $::form->{order}->{periodic_invoices_config};
+
   $order->assign_attributes(%{$::form->{order}});
 
+  my $periodic_invoices_config = _make_periodic_invoices_config_from_yaml($form_periodic_invoices_config);
+  $order->periodic_invoices_config($periodic_invoices_config) if $periodic_invoices_config;
+
   # remove deleted items
   $self->item_ids_to_delete([]);
   foreach my $idx (reverse 0..$#{$order->orderitems}) {
@@ -1106,23 +1210,22 @@ sub _save {
 sub _pre_render {
   my ($self) = @_;
 
-  $self->{all_taxzones}        = SL::DB::Manager::TaxZone->get_all_sorted();
-  $self->{all_departments}     = SL::DB::Manager::Department->get_all_sorted();
-  $self->{all_employees}       = SL::DB::Manager::Employee->get_all(where => [ or => [ id => $self->order->employee_id,
-                                                                                       deleted => 0 ] ],
-                                                                    sort_by => 'name');
-  $self->{all_salesmen}        = SL::DB::Manager::Employee->get_all(where => [ or => [ id => $self->order->salesman_id,
-                                                                                       deleted => 0 ] ],
-                                                                    sort_by => 'name');
-  $self->{all_projects}        = SL::DB::Manager::Project->get_all(where => [ or => [ id => $self->order->globalproject_id,
-                                                                                      active => 1 ] ],
-                                                                   sort_by => 'projectnumber');
-  $self->{all_payment_terms}   = SL::DB::Manager::PaymentTerm->get_all_sorted(where => [ or => [ id => $self->order->payment_id,
-                                                                                                 obsolete => 0 ] ]);
-
-  $self->{all_delivery_terms}  = SL::DB::Manager::DeliveryTerm->get_all_sorted();
-
-  $self->{current_employee_id} = SL::DB::Manager::Employee->current->id;
+  $self->{all_taxzones}             = SL::DB::Manager::TaxZone->get_all_sorted();
+  $self->{all_departments}          = SL::DB::Manager::Department->get_all_sorted();
+  $self->{all_employees}            = SL::DB::Manager::Employee->get_all(where => [ or => [ id => $self->order->employee_id,
+                                                                                            deleted => 0 ] ],
+                                                                         sort_by => 'name');
+  $self->{all_salesmen}             = SL::DB::Manager::Employee->get_all(where => [ or => [ id => $self->order->salesman_id,
+                                                                                            deleted => 0 ] ],
+                                                                         sort_by => 'name');
+  $self->{all_projects}             = SL::DB::Manager::Project->get_all(where => [ or => [ id => $self->order->globalproject_id,
+                                                                                           active => 1 ] ],
+                                                                        sort_by => 'projectnumber');
+  $self->{all_payment_terms}        = SL::DB::Manager::PaymentTerm->get_all_sorted(where => [ or => [ id => $self->order->payment_id,
+                                                                                                      obsolete => 0 ] ]);
+  $self->{all_delivery_terms}       = SL::DB::Manager::DeliveryTerm->get_all_sorted();
+  $self->{current_employee_id}      = SL::DB::Manager::Employee->current->id;
+  $self->{periodic_invoices_status} = $self->_get_periodic_invoices_status($self->order->periodic_invoices_config);
 
   my $print_form = Form->new('');
   $print_form->{type}      = $self->type;
@@ -1156,7 +1259,7 @@ sub _pre_render {
                                                 } } @all_objects;
   }
 
-  $::request->{layout}->use_javascript("${_}.js")  for qw(kivi.SalesPurchase kivi.Order kivi.File ckeditor/ckeditor ckeditor/adapters/jquery);
+  $::request->{layout}->use_javascript("${_}.js") for qw(kivi.SalesPurchase kivi.Order kivi.File ckeditor/ckeditor ckeditor/adapters/jquery edit_periodic_invoices_config);
   $self->_setup_edit_action_bar;
 }
 
@@ -1172,15 +1275,18 @@ sub _setup_edit_action_bar {
         action => [
           t8('Save'),
           call      => [ 'kivi.Order.save', $::instance_conf->get_order_warn_duplicate_parts ],
+          checks    => [ 'kivi.Order.check_save_active_periodic_invoices' ],
           accesskey => 'enter',
         ],
         action => [
           t8('Save and Delivery Order'),
           call      => [ 'kivi.Order.save_and_delivery_order', $::instance_conf->get_order_warn_duplicate_parts ],
+          checks    => [ 'kivi.Order.check_save_active_periodic_invoices' ],
         ],
         action => [
           t8('Save and Invoice'),
           call      => [ 'kivi.Order.save_and_invoice', $::instance_conf->get_order_warn_duplicate_parts ],
+          checks    => [ 'kivi.Order.check_save_active_periodic_invoices' ],
         ],
 
       ], # end of combobox "Save"
@@ -1298,6 +1404,29 @@ sub _get_files_for_email_dialog {
   return %files;
 }
 
+sub _make_periodic_invoices_config_from_yaml {
+  my ($yaml_config) = @_;
+
+  return if !$yaml_config;
+  my $attr = YAML::Load($yaml_config);
+  return if 'HASH' ne ref $attr;
+  return SL::DB::PeriodicInvoicesConfig->new(%$attr);
+}
+
+
+sub _get_periodic_invoices_status {
+  my ($self, $config) = @_;
+
+  return                      if $self->type ne _sales_order_type();
+  return t8('not configured') if !$config;
+
+  my $active = ('HASH' eq ref $config)                           ? $config->{active}
+             : ('SL::DB::PeriodicInvoicesConfig' eq ref $config) ? $config->active
+             :                                                     die "Cannot get status of periodic invoices config";
+
+  return $active ? t8('active') : t8('inactive');
+}
+
 sub _sales_order_type {
   'sales_order';
 }
@@ -1443,8 +1572,6 @@ java script functions
 
 =item * custom shipto address
 
-=item * periodic invoices
-
 =item * language / part translations
 
 =item * access rights
diff --git a/js/kivi.Order.js b/js/kivi.Order.js
index 6e26d7b0f..39e0191a4 100644
--- a/js/kivi.Order.js
+++ b/js/kivi.Order.js
@@ -546,6 +546,67 @@ namespace('kivi.Order', function(ns) {
     kivi.io.close_dialog();
   };
 
+  ns.show_periodic_invoices_config_dialog = function() {
+    if ($('#type').val() !== 'sales_order') return;
+
+    kivi.popup_dialog({
+      url: 'controller.pl?action=Order/show_periodic_invoices_config_dialog',
+      data: { type       : $('#type').val(),
+              id         : $('#id').val(),
+              config     : $('#order_periodic_invoices_config').val(),
+              customer_id: $('#order_customer_id').val(),
+              transdate  : $('#order_transdate').val(),
+              language_id: $('#language_id').val()
+            },
+      id: 'jq_periodic_invoices_config_dialog',
+      load: kivi.reinit_widgets,
+      dialog: {
+        title: kivi.t8('Edit the configuration for periodic invoices'),
+        width:  800,
+        height: 650
+      }
+    });
+    return true;
+  };
+
+  ns.close_periodic_invoices_config_dialog = function() {
+    $('#jq_periodic_invoices_config_dialog').dialog('close');
+  };
+
+  ns.assign_periodic_invoices_config = function() {
+    var data = $('[name="Form"]').serializeArray();
+    data.push({ name: 'type',   value: $('#type').val() });
+    data.push({ name: 'action', value: 'Order/assign_periodic_invoices_config' });
+    $.post("controller.pl", data, kivi.eval_json_result);
+  };
+
+  ns.check_save_active_periodic_invoices = function() {
+    var type = $('#type').val();
+    if (type !== 'sales_order') return true;
+
+    var active = false;
+    $.ajax({
+      url: 'controller.pl',
+      data: { action: 'Order/get_has_active_periodic_invoices',
+              type  : type,
+              id    : $('#id').val(),
+              config: $('#order_periodic_invoices_config').val(),
+      },
+      method: "GET",
+      async: false,
+      dataType: 'text',
+      success: function(val){
+        active = val;
+      }
+    });
+
+    if (active == 1) {
+      return confirm(kivi.t8('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?'));
+    }
+
+    return true;
+  };
+
 });
 
 $(function(){
diff --git a/js/locale/de.js b/js/locale/de.js
index b296b9e80..9e254d70d 100644
--- a/js/locale/de.js
+++ b/js/locale/de.js
@@ -54,6 +54,7 @@ namespace("kivi").setupLocale({
 "Edit picture":"Bild bearbeiten",
 "Edit project link":"Projektverknüpfung bearbeiten",
 "Edit text block":"Textblock bearbeiten",
+"Edit the configuration for periodic invoices":"Konfiguration für wiederkehrende Rechnungen bearbeiten",
 "Enter longdescription":"Langtext eingeben",
 "Error: Name missing":"Fehler: Name fehlt",
 "File upload":"Datei Upload",
diff --git a/locale/de/all b/locale/de/all
index 80eae73f2..e8e05867d 100755
--- a/locale/de/all
+++ b/locale/de/all
@@ -3283,6 +3283,7 @@ $self->{texts} = {
   'The payment term has been saved.' => 'Die Zahlungsbedingungen wurden gespeichert.',
   'The payment term is in use and cannot be deleted.' => 'Die Zahlungsbedingungen werden bereits benutzt und können nicht gelöscht werden.',
   'The payments have been posted.' => 'Die Zahlungen wurden gebucht.',
+  'The periodic invoices config has been assigned.' => 'Die Konfiguration für wiederkehrende Rechnungen wurde übernommen.',
   'The port is missing.'        => 'Port fehlt',
   'The preferred one is to install packages provided by your operating system distribution (e.g. Debian or RPM packages).' => 'Die bevorzugte Art, ein Perl-Modul zu installieren, ist durch Installation eines von Ihrem Betriebssystem zur Verfügung gestellten Paketes (z.B. Debian-Pakete oder RPM).',
   'The price rule for this discount does not exist anymore' => 'Die Preisregel für diesen Rabatt existiert nicht mehr',
diff --git a/templates/webpages/order/tabs/basic_data.html b/templates/webpages/order/tabs/basic_data.html
index 39b953101..11a71fb62 100644
--- a/templates/webpages/order/tabs/basic_data.html
+++ b/templates/webpages/order/tabs/basic_data.html
@@ -233,6 +233,15 @@
                                       title_key = 'description',
                                       style = 'width: 250px') %]</td>
                 </tr>
+                [%- IF SELF.type == "sales_order" %]
+                <tr>
+                  <th align="right">[%- 'Periodic Invoices' | T8 %]</th>
+                  <td>[% L.button_tag('kivi.Order.show_periodic_invoices_config_dialog()', LxERP.t8('Configure')) %]
+                    (<span id='periodic_invoices_status'>[%- SELF.periodic_invoices_status -%]</span>)
+                    <a href="doc/html/ch03.html#features.periodic-invoices.variables" target="_blank">?</a>
+                  </td>
+                </tr>
+                [%- END %]
               </table>
             </td>
 
