]> wagnertech.de Git - mfinanz.git/blobdiff - SL/Controller/Order.pm
jsTree: keine globalen Key-Handler installieren
[mfinanz.git] / SL / Controller / Order.pm
index 8355dda1442ab56cddbc09438a443f28a6d8864e..ac619d5bdc3de6cf93a97c16c297c5c577d0d19a 100644 (file)
@@ -4,7 +4,7 @@ use strict;
 use parent qw(SL::Controller::Base);
 
 use SL::Helper::Flash qw(flash_later);
 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;
 use SL::Locale::String qw(t8);
 use SL::SessionFile::Random;
 use SL::PriceSource;
@@ -24,9 +24,11 @@ use SL::Helper::PrintOptions;
 use SL::Controller::Helper::GetModels;
 
 use List::Util qw(first);
 use SL::Controller::Helper::GetModels;
 
 use List::Util qw(first);
-use List::MoreUtils qw(none pairwise first_index);
+use List::UtilsBy qw(sort_by uniq_by);
+use List::MoreUtils qw(any none pairwise first_index);
 use English qw(-no_match_vars);
 use File::Spec;
 use English qw(-no_match_vars);
 use File::Spec;
+use Cwd;
 
 use Rose::Object::MakeMethods::Generic
 (
 
 use Rose::Object::MakeMethods::Generic
 (
@@ -39,10 +41,10 @@ use Rose::Object::MakeMethods::Generic
 __PACKAGE__->run_before('_check_auth');
 
 __PACKAGE__->run_before('_recalc',
 __PACKAGE__->run_before('_check_auth');
 
 __PACKAGE__->run_before('_recalc',
-                        only => [ qw(save save_and_delivery_order print create_pdf send_email) ]);
+                        only => [ qw(save save_and_delivery_order save_and_invoice print create_pdf send_email) ]);
 
 __PACKAGE__->run_before('_get_unalterable_data',
 
 __PACKAGE__->run_before('_get_unalterable_data',
-                        only => [ qw(save save_and_delivery_order print create_pdf send_email) ]);
+                        only => [ qw(save save_and_delivery_order save_and_invoice print create_pdf send_email) ]);
 
 #
 # actions
 
 #
 # actions
@@ -152,6 +154,7 @@ sub action_print {
   my $language;
   $language = SL::DB::Language->new(id => $::form->{print_options}->{language_id})->load if $::form->{print_options}->{language_id};
 
   my $language;
   $language = SL::DB::Language->new(id => $::form->{print_options}->{language_id})->load if $::form->{print_options}->{language_id};
 
+  # create a form for generate_attachment_filename
   my $form = Form->new;
   $form->{ordnumber} = $self->order->ordnumber;
   $form->{type}      = $self->type;
   my $form = Form->new;
   $form->{ordnumber} = $self->order->ordnumber;
   $form->{type}      = $self->type;
@@ -253,10 +256,11 @@ sub action_show_email_dialog {
                     ->render($self);
   }
 
                     ->render($self);
   }
 
-  $self->{email}->{to}   = $self->order->contact->cp_email if $self->order->contact;
-  $self->{email}->{to} ||= $self->order->$cv_method->email;
-  $self->{email}->{cc}   = $self->order->$cv_method->cc;
-  $self->{email}->{bcc}  = join ', ', grep $_, $self->order->$cv_method->bcc, SL::DB::Default->get->global_bcc;
+  my $email_form;
+  $email_form->{to}   = $self->order->contact->cp_email if $self->order->contact;
+  $email_form->{to} ||= $self->order->$cv_method->email;
+  $email_form->{cc}   = $self->order->$cv_method->cc;
+  $email_form->{bcc}  = join ', ', grep $_, $self->order->$cv_method->bcc, SL::DB::Default->get->global_bcc;
   # Todo: get addresses from shipto, if any
 
   my $form = Form->new;
   # Todo: get addresses from shipto, if any
 
   my $form = Form->new;
@@ -266,11 +270,19 @@ sub action_show_email_dialog {
   $form->{language} = 'de';
   $form->{format}   = 'pdf';
 
   $form->{language} = 'de';
   $form->{format}   = 'pdf';
 
-  $self->{email}->{subject}             = $form->generate_email_subject();
-  $self->{email}->{attachment_filename} = $form->generate_attachment_filename();
-  $self->{email}->{message}             = $form->create_email_signature();
+  $email_form->{subject}             = $form->generate_email_subject();
+  $email_form->{attachment_filename} = $form->generate_attachment_filename();
+  $email_form->{message}             = $form->generate_email_body();
+  $email_form->{js_send_function}    = 'kivi.Order.send_email()';
+
+  my %files = $self->_get_files_for_email_dialog();
+  my $dialog_html = $self->render('common/_send_email_dialog', { output => 0 },
+                                  email_form  => $email_form,
+                                  show_bcc    => $::auth->assert('email_bcc', 'may fail'),
+                                  FILES       => \%files,
+                                  is_customer => $self->cv eq 'customer',
+  );
 
 
-  my $dialog_html = $self->render('order/tabs/_email_dialog', { output => 0 });
   $self->js
       ->run('kivi.Order.show_email_dialog', $dialog_html)
       ->reinit_widgets
   $self->js
       ->run('kivi.Order.show_email_dialog', $dialog_html)
       ->reinit_widgets
@@ -283,41 +295,158 @@ sub action_show_email_dialog {
 sub action_send_email {
   my ($self) = @_;
 
 sub action_send_email {
   my ($self) = @_;
 
-  my $mail      = Mailer->new;
-  $mail->{from} = qq|"$::myconfig{name}" <$::myconfig{email}>|;
-  $mail->{$_}   = $::form->{email}->{$_} for qw(to cc bcc subject message);
+  my $email_form  = delete $::form->{email_form};
+  my %field_names = (to => 'email');
 
 
-  my $pdf;
-  my @errors = _create_pdf($self->order, \$pdf, {media => 'email'});
-  if (scalar @errors) {
-    return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render($self);
-  }
+  $::form->{ $field_names{$_} // $_ } = $email_form->{$_} for keys %{ $email_form };
 
 
-  $mail->{attachments} = [{ "content" => $pdf,
-                            "name"    => $::form->{email}->{attachment_filename} }];
+  # for Form::cleanup which may be called in Form::send_email
+  $::form->{cwd}    = getcwd();
+  $::form->{tmpdir} = $::lx_office_conf{paths}->{userspath};
 
 
-  if (my $err = $mail->send) {
-    return $self->js->flash('error', t8('Sending E-mail: ') . $err)
-                    ->render($self);
+  $::form->{media}  = 'email';
+
+  if (($::form->{attachment_policy} // '') eq 'normal') {
+    my $language;
+    $language = SL::DB::Language->new(id => $::form->{print_options}->{language_id})->load if $::form->{print_options}->{language_id};
+
+    my $pdf;
+    my @errors = _create_pdf($self->order, \$pdf, {media      => $::form->{media},
+                                                   format     => $::form->{print_options}->{format},
+                                                   formname   => $::form->{print_options}->{formname},
+                                                   language   => $language,
+                                                   groupitems => $::form->{print_options}->{groupitems}});
+    if (scalar @errors) {
+      return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render($self);
+    }
+
+    my $sfile = SL::SessionFile::Random->new(mode => "w");
+    $sfile->fh->print($pdf);
+    $sfile->fh->close;
+
+    $::form->{tmpfile} = $sfile->file_name;
+    $::form->{tmpdir}  = $sfile->get_path; # for Form::cleanup which may be called in Form::send_email
   }
 
   }
 
+  $::form->send_email(\%::myconfig, 'pdf');
+
   # internal notes
   my $intnotes = $self->order->intnotes;
   $intnotes   .= "\n\n" if $self->order->intnotes;
   $intnotes   .= t8('[email]')                                                                                        . "\n";
   $intnotes   .= t8('Date')       . ": " . $::locale->format_date_object(DateTime->now_local, precision => 'seconds') . "\n";
   # internal notes
   my $intnotes = $self->order->intnotes;
   $intnotes   .= "\n\n" if $self->order->intnotes;
   $intnotes   .= t8('[email]')                                                                                        . "\n";
   $intnotes   .= t8('Date')       . ": " . $::locale->format_date_object(DateTime->now_local, precision => 'seconds') . "\n";
-  $intnotes   .= t8('To (email)') . ": " . $mail->{to}                                                                . "\n";
-  $intnotes   .= t8('Cc')         . ": " . $mail->{cc}                                                                . "\n"    if $mail->{cc};
-  $intnotes   .= t8('Bcc')        . ": " . $mail->{bcc}                                                               . "\n"    if $mail->{bcc};
-  $intnotes   .= t8('Subject')    . ": " . $mail->{subject}                                                           . "\n\n";
-  $intnotes   .= t8('Message')    . ": " . $mail->{message};
+  $intnotes   .= t8('To (email)') . ": " . $::form->{email}                                                           . "\n";
+  $intnotes   .= t8('Cc')         . ": " . $::form->{cc}                                                              . "\n"    if $::form->{cc};
+  $intnotes   .= t8('Bcc')        . ": " . $::form->{bcc}                                                             . "\n"    if $::form->{bcc};
+  $intnotes   .= t8('Subject')    . ": " . $::form->{subject}                                                         . "\n\n";
+  $intnotes   .= t8('Message')    . ": " . $::form->{message};
 
   $self->js
       ->val('#order_intnotes', $intnotes)
       ->run('kivi.Order.close_email_dialog')
 
   $self->js
       ->val('#order_intnotes', $intnotes)
       ->run('kivi.Order.close_email_dialog')
+      ->flash('info', t8('The email has been sent.'))
       ->render($self);
 }
 
       ->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 {
 # save the order and redirect to the frontend subroutine for a new
 # delivery order
 sub action_save_and_delivery_order {
@@ -340,6 +469,28 @@ sub action_save_and_delivery_order {
   $self->redirect_to(@redirect_params);
 }
 
   $self->redirect_to(@redirect_params);
 }
 
+# save the order and redirect to the frontend subroutine for a new
+# invoice
+sub action_save_and_invoice {
+  my ($self) = @_;
+
+  my $errors = $self->_save();
+
+  if (scalar @{ $errors }) {
+    $self->js->flash('error', $_) foreach @{ $errors };
+    return $self->js->render();
+  }
+  flash_later('info', $::locale->text('The order has been saved'));
+
+  my @redirect_params = (
+    controller => 'oe.pl',
+    action     => 'oe_invoice_from_order',
+    id         => $self->order->id,
+  );
+
+  $self->redirect_to(@redirect_params);
+}
+
 # set form elements in respect to a changed customer or vendor
 #
 # This action is called on an change of the customer/vendor picker.
 # set form elements in respect to a changed customer or vendor
 #
 # This action is called on an change of the customer/vendor picker.
@@ -425,6 +576,7 @@ sub action_add_item {
   my $row_as_html = $self->p->render('order/tabs/_row',
                                      ITEM              => $item,
                                      ID                => $item_id,
   my $row_as_html = $self->p->render('order/tabs/_row',
                                      ITEM              => $item,
                                      ID                => $item_id,
+                                     TYPE              => $self->type,
                                      ALL_PRICE_FACTORS => $self->all_price_factors
   );
 
                                      ALL_PRICE_FACTORS => $self->all_price_factors
   );
 
@@ -450,6 +602,7 @@ sub action_add_item {
       my $row_as_html = $self->p->render('order/tabs/_row',
                                          ITEM              => $item,
                                          ID                => $item_id,
       my $row_as_html = $self->p->render('order/tabs/_row',
                                          ITEM              => $item,
                                          ID                => $item_id,
+                                         TYPE              => $self->type,
                                          ALL_PRICE_FACTORS => $self->all_price_factors
       );
       $self->js
                                          ALL_PRICE_FACTORS => $self->all_price_factors
       );
       $self->js
@@ -531,6 +684,7 @@ sub action_add_multi_items {
     my $row_as_html = $self->p->render('order/tabs/_row',
                                        ITEM              => $item,
                                        ID                => $item_id,
     my $row_as_html = $self->p->render('order/tabs/_row',
                                        ITEM              => $item,
                                        ID                => $item_id,
+                                       TYPE              => $self->type,
                                        ALL_PRICE_FACTORS => $self->all_price_factors
     );
 
                                        ALL_PRICE_FACTORS => $self->all_price_factors
     );
 
@@ -643,7 +797,7 @@ sub _js_load_second_row {
     $item->parse_custom_variable_values;
   }
 
     $item->parse_custom_variable_values;
   }
 
-  my $row_as_html = $self->p->render('order/tabs/_second_row', ITEM => $item);
+  my $row_as_html = $self->p->render('order/tabs/_second_row', ITEM => $item, TYPE => $self->type);
 
   $self->js
     ->html('.row_entry:has(#item_' . $item_id . ') [name = "second_row"]', $row_as_html)
 
   $self->js
     ->html('.row_entry:has(#item_' . $item_id . ') [name = "second_row"]', $row_as_html)
@@ -841,7 +995,7 @@ sub _load_order {
 
 # load or create a new order object
 #
 
 # 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.
 # 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.
@@ -855,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 => []);
 
   $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}});
 
   $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}) {
   # remove deleted items
   $self->item_ids_to_delete([]);
   foreach my $idx (reverse 0..$#{$order->orderitems}) {
@@ -1051,23 +1210,22 @@ sub _save {
 sub _pre_render {
   my ($self) = @_;
 
 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;
 
   my $print_form = Form->new('');
   $print_form->{type}      = $self->type;
@@ -1101,7 +1259,7 @@ sub _pre_render {
                                                 } } @all_objects;
   }
 
                                                 } } @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;
 }
 
   $self->_setup_edit_action_bar;
 }
 
@@ -1117,11 +1275,18 @@ sub _setup_edit_action_bar {
         action => [
           t8('Save'),
           call      => [ 'kivi.Order.save', $::instance_conf->get_order_warn_duplicate_parts ],
         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 ],
           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"
         ],
 
       ], # end of combobox "Save"
@@ -1169,9 +1334,8 @@ sub _create_pdf {
   $print_form->{media}       = $params->{media}    || 'file';
   $print_form->{groupitems}  = $params->{groupitems};
   $print_form->{media}       = 'file'                             if $print_form->{media} eq 'screen';
   $print_form->{media}       = $params->{media}    || 'file';
   $print_form->{groupitems}  = $params->{groupitems};
   $print_form->{media}       = 'file'                             if $print_form->{media} eq 'screen';
-  $print_form->{language}    = $params->{language}->template_code if $print_form->{language};
-  $print_form->{language_id} = $params->{language}->id            if $print_form->{language};
 
 
+  $order->language($params->{language});
   $order->flatten_to_form($print_form, format_amounts => 1);
 
   # search for the template
   $order->flatten_to_form($print_form, format_amounts => 1);
 
   # search for the template
@@ -1208,6 +1372,61 @@ sub _create_pdf {
   return @errors;
 }
 
   return @errors;
 }
 
+sub _get_files_for_email_dialog {
+  my ($self) = @_;
+
+  my %files = map { ($_ => []) } qw(versions files vc_files part_files);
+
+  return %files if !$::instance_conf->get_doc_storage;
+
+  if ($self->order->id) {
+    $files{versions} = [ SL::File->get_all_versions(object_id => $self->order->id,              object_type => $self->order->type, file_type => 'document') ];
+    $files{files}    = [ SL::File->get_all(         object_id => $self->order->id,              object_type => $self->order->type, file_type => 'attachment') ];
+    $files{vc_files} = [ SL::File->get_all(         object_id => $self->order->{$self->cv}->id, object_type => $self->cv,          file_type => 'attachment') ];
+  }
+
+  my @parts =
+    uniq_by { $_->{id} }
+    map {
+      +{ id         => $_->part->id,
+         partnumber => $_->part->partnumber }
+    } @{$self->order->items_sorted};
+
+  foreach my $part (@parts) {
+    my @pfiles = SL::File->get_all(object_id => $part->{id}, object_type => 'part');
+    push @{ $files{part_files} }, map { +{ %{ $_ }, partnumber => $part->{partnumber} } } @pfiles;
+  }
+
+  foreach my $key (keys %files) {
+    $files{$key} = [ sort_by { lc $_->{db_file}->{file_name} } @{ $files{$key} } ];
+  }
+
+  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';
 }
 sub _sales_order_type {
   'sales_order';
 }
@@ -1325,10 +1544,6 @@ Results for the filter in the multi items dialog
 
 Dialog for selecting price and discount sources
 
 
 Dialog for selecting price and discount sources
 
-=item * C<template/webpages/order/tabs/_email_dialog.html>
-
-Email dialog
-
 =back
 
 =item * C<js/kivi.Order.js>
 =back
 
 =item * C<js/kivi.Order.js>
@@ -1349,7 +1564,7 @@ java script functions
 
 =item * credit limit
 
 
 =item * credit limit
 
-=item * more workflows (save as new / invoice)
+=item * more workflows (save as new, quotation, purchase order)
 
 =item * price sources: little symbols showing better price / better discount
 
 
 =item * price sources: little symbols showing better price / better discount
 
@@ -1357,8 +1572,6 @@ java script functions
 
 =item * custom shipto address
 
 
 =item * custom shipto address
 
-=item * periodic invoices
-
 =item * language / part translations
 
 =item * access rights
 =item * language / part translations
 
 =item * access rights
@@ -1411,6 +1624,10 @@ should be implemented.
 C<show_multi_items_dialog> does not use the currently inserted string for
 filtering.
 
 C<show_multi_items_dialog> does not use the currently inserted string for
 filtering.
 
+=item *
+
+The language selected in print or email dialog is not saved when the order is saved.
+
 =back
 
 =head1 To discuss / Nice to have
 =back
 
 =head1 To discuss / Nice to have
@@ -1450,6 +1667,13 @@ editor or on text processing application).
 
 A warning when leaving the page without saveing unchanged inputs.
 
 
 A warning when leaving the page without saveing unchanged inputs.
 
+=item *
+
+Workflows for delivery order and invoice are in the menu "Save", because the
+order is saved before opening the new document form. Nevertheless perhaps these
+workflow buttons should be put under "Workflows".
+
+
 =back
 
 =head1 AUTHOR
 =back
 
 =head1 AUTHOR