Auftrags-Controller: mime-type füer odt
[kivitendo-erp.git] / SL / Controller / Order.pm
index 8cd8b6f..7a7841d 100644 (file)
@@ -10,7 +10,9 @@ use SL::SessionFile::Random;
 use SL::PriceSource;
 use SL::Webdav;
 use SL::File;
+use SL::MIME;
 use SL::Util qw(trim);
+use SL::YAML;
 use SL::DB::Order;
 use SL::DB::Default;
 use SL::DB::Unit;
@@ -23,6 +25,7 @@ use SL::DB::RecordLink;
 use SL::Helper::CreatePDF qw(:all);
 use SL::Helper::PrintOptions;
 use SL::Helper::ShippedQty;
+use SL::Helper::UserPreferences::PositionsScrollbar;
 
 use SL::Controller::Helper::GetModels;
 
@@ -32,11 +35,12 @@ use List::MoreUtils qw(any none pairwise first_index);
 use English qw(-no_match_vars);
 use File::Spec;
 use Cwd;
+use Sort::Naturally;
 
 use Rose::Object::MakeMethods::Generic
 (
  scalar => [ qw(item_ids_to_delete) ],
- 'scalar --get_set_init' => [ qw(order valid_types type cv p multi_items_models all_price_factors) ],
+ 'scalar --get_set_init' => [ qw(order valid_types type cv p multi_items_models all_price_factors search_cvpartnumber) ],
 );
 
 
@@ -58,9 +62,11 @@ sub action_add {
   my ($self) = @_;
 
   $self->order->transdate(DateTime->now_local());
-  my $extra_days = $self->type eq sales_quotation_type() ? $::instance_conf->get_reqdate_interval : 1;
+  my $extra_days = $self->{type} eq 'sales_quotation' ? $::instance_conf->get_reqdate_interval       :
+                   $self->{type} eq 'sales_order'     ? $::instance_conf->get_delivery_date_interval : 1;
   $self->order->reqdate(DateTime->today_local->next_workday(extra_days => $extra_days)) if !$self->order->reqdate;
 
+
   $self->pre_render();
   $self->render(
     'order/form',
@@ -99,6 +105,36 @@ sub action_edit {
   );
 }
 
+# edit a collective order (consisting of one or more existing orders)
+sub action_edit_collective {
+  my ($self) = @_;
+
+  # collect order ids
+  my @multi_ids = map {
+    $_ =~ m{^multi_id_(\d+)$} && $::form->{'multi_id_' . $1} && $::form->{'trans_id_' . $1} && $::form->{'trans_id_' . $1}
+  } grep { $_ =~ m{^multi_id_\d+$} } keys %$::form;
+
+  # fall back to add if no ids are given
+  if (scalar @multi_ids == 0) {
+    $self->action_add();
+    return;
+  }
+
+  # fall back to save as new if only one id is given
+  if (scalar @multi_ids == 1) {
+    $self->order(SL::DB::Order->new(id => $multi_ids[0])->load);
+    $self->action_save_as_new();
+    return;
+  }
+
+  # make new order from given orders
+  my @multi_orders = map { SL::DB::Order->new(id => $_)->load } @multi_ids;
+  $self->{converted_from_oe_id} = join ' ', map { $_->id } @multi_orders;
+  $self->order(SL::DB::Order->new_from_multi(\@multi_orders, sort_sources_by => 'transdate'));
+
+  $self->action_edit();
+}
+
 # delete the order
 sub action_delete {
   my ($self) = @_;
@@ -180,7 +216,8 @@ sub action_save_as_new {
 
   # Set new reqdate unless changed
   if ($order->reqdate == $saved_order->reqdate) {
-    my $extra_days = $self->type eq sales_quotation_type() ? $::instance_conf->get_reqdate_interval : 1;
+    my $extra_days = $self->{type} eq 'sales_quotation' ? $::instance_conf->get_reqdate_interval       :
+                     $self->{type} eq 'sales_order'     ? $::instance_conf->get_delivery_date_interval : 1;
     $new_attrs{reqdate} = DateTime->today_local->next_workday(extra_days => $extra_days);
   } else {
     $new_attrs{reqdate} = $order->reqdate;
@@ -210,6 +247,15 @@ sub action_save_as_new {
 sub action_print {
   my ($self) = @_;
 
+  my $errors = $self->save();
+
+  if (scalar @{ $errors }) {
+    $self->js->flash('error', $_) foreach @{ $errors };
+    return $self->js->render();
+  }
+
+  $self->js_reset_order_and_item_ids_after_save;
+
   my $format      = $::form->{print_options}->{format};
   my $media       = $::form->{print_options}->{media};
   my $formname    = $::form->{print_options}->{formname};
@@ -315,7 +361,7 @@ sub action_download_pdf {
   my $tmp_filename = $::auth->get_session_value("Order::print-${key}");
   return $self->send_file(
     $tmp_filename,
-    type => 'application/pdf',
+    type => SL::MIME->mime_type_from_ext($::form->{pdf_filename}),
     name => $::form->{pdf_filename},
   );
 }
@@ -370,6 +416,16 @@ sub action_show_email_dialog {
 sub action_send_email {
   my ($self) = @_;
 
+  my $errors = $self->save();
+
+  if (scalar @{ $errors }) {
+    $self->js->run('kivi.Order.close_email_dialog');
+    $self->js->flash('error', $_) foreach @{ $errors };
+    return $self->js->render();
+  }
+
+  $self->js_reset_order_and_item_ids_after_save;
+
   my $email_form  = delete $::form->{email_form};
   my %field_names = (to => 'email');
 
@@ -417,6 +473,8 @@ sub action_send_email {
   $intnotes   .= t8('Subject')    . ": " . $::form->{subject}                                                         . "\n\n";
   $intnotes   .= t8('Message')    . ": " . $::form->{message};
 
+  $self->order->update_attributes(intnotes => $intnotes);
+
   $self->js
       ->val('#order_intnotes', $intnotes)
       ->run('kivi.Order.close_email_dialog')
@@ -435,7 +493,7 @@ sub action_show_periodic_invoices_config_dialog {
   $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,
+                                                   start_date_as_date      => $::form->{transdate_as_date} || $::form->current_date,
                                                    extend_automatically_by => 12,
                                                    active                  => 1,
                                                    email_subject           => GenericTranslations->get(
@@ -456,6 +514,7 @@ sub action_show_periodic_invoices_config_dialog {
 
   if ($::form->{customer_id}) {
     $::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(where => [ cp_cv_id => $::form->{customer_id} ]);
+    $::form->{email_recipient_invoice_address} = SL::DB::Manager::Customer->find_by(id => $::form->{customer_id})->invoice_mail;
   }
 
   $self->render('oe/edit_periodic_invoices_config', { layout => 0 },
@@ -494,7 +553,7 @@ sub action_assign_periodic_invoices_config {
                  email_body                 => $::form->{email_body},
                };
 
-  my $periodic_invoices_config = YAML::Dump($config);
+  my $periodic_invoices_config = SL::YAML::Dump($config);
 
   my $status = $self->get_periodic_invoices_status($config);
 
@@ -623,9 +682,11 @@ sub action_customer_vendor_changed {
     ->val(        '#order_payment_id',       $self->order->payment_id)
     ->val(        '#order_delivery_term_id', $self->order->delivery_term_id)
     ->val(        '#order_intnotes',         $self->order->intnotes)
+    ->val(        '#language_id',            $self->order->$cv_method->language_id)
     ->focus(      '#order_' . $self->cv . '_id');
 
   $self->js_redisplay_amounts_and_taxes;
+  $self->js_redisplay_cvpartnumbers;
   $self->js->render();
 }
 
@@ -696,12 +757,15 @@ sub action_add_item {
 
   $self->recalc();
 
+  $self->get_item_cvpartnumber($item);
+
   my $item_id = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000);
   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
+                                     ITEM                => $item,
+                                     ID                  => $item_id,
+                                     TYPE                => $self->type,
+                                     ALL_PRICE_FACTORS   => $self->all_price_factors,
+                                     SEARCH_CVPARTNUMBER => $self->search_cvpartnumber,
   );
 
   $self->js
@@ -722,12 +786,14 @@ sub action_add_item {
 
       $self->order->add_items( $item );
       $self->recalc();
+      $self->get_item_cvpartnumber($item);
       my $item_id = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000);
       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
+                                         ITEM                => $item,
+                                         ID                  => $item_id,
+                                         TYPE                => $self->type,
+                                         ALL_PRICE_FACTORS   => $self->all_price_factors,
+                                         SEARCH_CVPARTNUMBER => $self->search_cvpartnumber,
       );
       $self->js
         ->append('#row_table_id', $row_as_html);
@@ -803,12 +869,14 @@ sub action_add_multi_items {
   $self->recalc();
 
   foreach my $item (@items) {
+    $self->get_item_cvpartnumber($item);
     my $item_id = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000);
     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
+                                       ITEM                => $item,
+                                       ID                  => $item_id,
+                                       TYPE                => $self->type,
+                                       ALL_PRICE_FACTORS   => $self->all_price_factors,
+                                       SEARCH_CVPARTNUMBER => $self->search_cvpartnumber,
     );
 
     $self->js->append('#row_table_id', $row_as_html);
@@ -841,19 +909,30 @@ sub action_reorder_items {
   my ($self) = @_;
 
   my %sort_keys = (
-    partnumber  => sub { $_[0]->part->partnumber },
-    description => sub { $_[0]->description },
-    qty         => sub { $_[0]->qty },
-    sellprice   => sub { $_[0]->sellprice },
-    discount    => sub { $_[0]->discount },
+    partnumber   => sub { $_[0]->part->partnumber },
+    description  => sub { $_[0]->description },
+    qty          => sub { $_[0]->qty },
+    sellprice    => sub { $_[0]->sellprice },
+    discount     => sub { $_[0]->discount },
+    cvpartnumber => sub { $_[0]->{cvpartnumber} },
   );
 
+  $self->get_item_cvpartnumber($_) for @{$self->order->items_sorted};
+
   my $method = $sort_keys{$::form->{order_by}};
   my @to_sort = map { { old_pos => $_->position, order_by => $method->($_) } } @{ $self->order->items_sorted };
   if ($::form->{sort_dir}) {
-    @to_sort = sort { $a->{order_by} cmp $b->{order_by} } @to_sort;
+    if ( $::form->{order_by} =~ m/qty|sellprice|discount/ ){
+      @to_sort = sort { $a->{order_by} <=> $b->{order_by} } @to_sort;
+    } else {
+      @to_sort = sort { $a->{order_by} cmp $b->{order_by} } @to_sort;
+    }
   } else {
-    @to_sort = sort { $b->{order_by} cmp $a->{order_by} } @to_sort;
+    if ( $::form->{order_by} =~ m/qty|sellprice|discount/ ){
+      @to_sort = sort { $b->{order_by} <=> $a->{order_by} } @to_sort;
+    } else {
+      @to_sort = sort { $b->{order_by} cmp $a->{order_by} } @to_sort;
+    }
   }
   $self->js
     ->run('kivi.Order.redisplay_items', \@to_sort)
@@ -923,8 +1002,8 @@ sub js_load_second_row {
   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)
-    ->data('.row_entry:has(#item_' . $item_id . ') [name = "second_row"]', 'loaded', 1);
+    ->html('#second_row_' . $item_id, $row_as_html)
+    ->data('#second_row_' . $item_id, 'loaded', 1);
 }
 
 sub js_redisplay_line_values {
@@ -987,6 +1066,39 @@ sub js_redisplay_amounts_and_taxes {
     ->insertBefore($self->build_tax_rows, '#amount_row_id');
 }
 
+sub js_redisplay_cvpartnumbers {
+  my ($self) = @_;
+
+  $self->get_item_cvpartnumber($_) for @{$self->order->items_sorted};
+
+  my @data = map {[$_->{cvpartnumber}]} @{ $self->order->items_sorted };
+
+  $self->js
+    ->run('kivi.Order.redisplay_cvpartnumbers', \@data);
+}
+
+sub js_reset_order_and_item_ids_after_save {
+  my ($self) = @_;
+
+  $self->js
+    ->val('#id', $self->order->id)
+    ->val('#converted_from_oe_id', '')
+    ->val('#order_' . $self->nr_key(), $self->order->number);
+
+  my $idx = 0;
+  foreach my $form_item_id (@{ $::form->{orderitem_ids} }) {
+    next if !$self->order->items_sorted->[$idx]->id;
+    next if $form_item_id !~ m{^new};
+    $self->js
+      ->val ('[name="orderitem_ids[+]"][value="' . $form_item_id . '"]', $self->order->items_sorted->[$idx]->id)
+      ->val ('#item_' . $form_item_id, $self->order->items_sorted->[$idx]->id)
+      ->attr('#item_' . $form_item_id, "id", 'item_' . $self->order->items_sorted->[$idx]->id);
+  } continue {
+    $idx++;
+  }
+  $self->js->val('[name="converted_from_orderitems_ids[+]"]', '');
+}
+
 #
 # helpers
 #
@@ -1015,6 +1127,17 @@ sub init_cv {
   return $cv;
 }
 
+sub init_search_cvpartnumber {
+  my ($self) = @_;
+
+  my $user_prefs = SL::Helper::UserPreferences::PartPickerSearch->new();
+  my $search_cvpartnumber;
+  $search_cvpartnumber = !!$user_prefs->get_sales_search_customer_partnumber() if $self->cv eq 'customer';
+  $search_cvpartnumber = !!$user_prefs->get_purchase_search_makemodel()        if $self->cv eq 'vendor';
+
+  return $search_cvpartnumber;
+}
+
 sub init_p {
   SL::Presenter->get;
 }
@@ -1150,7 +1273,7 @@ sub make_order {
   # be retrieved via items until the order is saved. Adding empty items to new
   # order here solves this problem.
   my $order;
-  $order   = SL::DB::Manager::Order->find_by(id => $::form->{id}) if $::form->{id};
+  $order   = SL::DB::Order->new(id => $::form->{id})->load(with => [ 'orderitems', 'orderitems.part' ]) if $::form->{id};
   $order ||= SL::DB::Order->new(orderitems => [],
                                 quotation  => (any { $self->type eq $_ } (sales_quotation_type(), request_quotation_type())));
 
@@ -1165,8 +1288,10 @@ sub make_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;
+  if (my $periodic_invoices_config_attrs = $form_periodic_invoices_config ? SL::YAML::Load($form_periodic_invoices_config) : undef) {
+    my $periodic_invoices_config = $order->periodic_invoices_config || $order->periodic_invoices_config(SL::DB::PeriodicInvoicesConfig->new);
+    $periodic_invoices_config->assign_attributes(%$periodic_invoices_config_attrs);
+  }
 
   # remove deleted items
   $self->item_ids_to_delete([]);
@@ -1323,7 +1448,7 @@ sub recalc {
                                 tax       => $tax });
   }
 
-  pairwise { $a->{linetotal} = $b->{linetotal} } @{$self->order->items}, @{$pat{items}};
+  pairwise { $a->{linetotal} = $b->{linetotal} } @{$self->order->items_sorted}, @{$pat{items}};
 }
 
 # get data for saving, printing, ..., that is not changed in the form
@@ -1374,13 +1499,17 @@ sub save {
   my $db     = $self->order->db;
 
   $db->with_transaction(sub {
-    SL::DB::OrderItem->new(id => $_)->delete for @{$self->item_ids_to_delete};
+    SL::DB::OrderItem->new(id => $_)->delete for @{$self->item_ids_to_delete || []};
     $self->order->save(cascade => 1);
 
     # link records
     if ($::form->{converted_from_oe_id}) {
-      SL::DB::Order->new(id => $::form->{converted_from_oe_id})->load->link_to_record($self->order);
-
+      my @converted_from_oe_ids = split ' ', $::form->{converted_from_oe_id};
+      foreach my $converted_from_oe_id (@converted_from_oe_ids) {
+        my $src = SL::DB::Order->new(id => $converted_from_oe_id)->load;
+        $src->update_attributes(closed => 1) if $src->type =~ /_quotation$/;
+        $src->link_to_record($self->order);
+      }
       if (scalar @{ $::form->{converted_from_orderitems_ids} || [] }) {
         my $idx = 0;
         foreach (@{ $self->order->items_sorted }) {
@@ -1404,6 +1533,14 @@ sub save {
 sub workflow_sales_or_purchase_order {
   my ($self) = @_;
 
+  # always save
+  my $errors = $self->save();
+
+  if (scalar @{ $errors }) {
+    $self->js->flash('error', $_) foreach @{ $errors };
+    return $self->js->render();
+  }
+
   my $destination_type = $::form->{type} eq sales_quotation_type()   ? sales_order_type()
                        : $::form->{type} eq request_quotation_type() ? purchase_order_type()
                        : $::form->{type} eq purchase_order_type()    ? sales_order_type()
@@ -1445,20 +1582,21 @@ sub workflow_sales_or_purchase_order {
 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_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);
-  $self->{order_probabilities}      = [ map { { title => ($_ * 10) . '%', id => $_ * 10 } } (0..10) ];
+  $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_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);
+  $self->{order_probabilities}        = [ map { { title => ($_ * 10) . '%', id => $_ * 10 } } (0..10) ];
+  $self->{positions_scrollbar_height} = SL::Helper::UserPreferences::PositionsScrollbar->new()->get_height();
 
   my $print_form = Form->new('');
   $print_form->{type}      = $self->type;
@@ -1497,6 +1635,8 @@ sub pre_render {
                                                 } } @all_objects;
   }
 
+  $self->get_item_cvpartnumber($_) for @{$self->order->items_sorted};
+
   $::request->{layout}->use_javascript("${_}.js") for qw(kivi.SalesPurchase kivi.Order kivi.File ckeditor/ckeditor ckeditor/adapters/jquery edit_periodic_invoices_config calculate_qty);
   $self->setup_edit_action_bar;
 }
@@ -1531,13 +1671,13 @@ sub setup_edit_action_bar {
           t8('Workflow'),
         ],
         action => [
-          t8('Sales Order'),
+          t8('Save and Sales Order'),
           submit   => [ '#order_form', { action => "Order/sales_order" } ],
           only_if  => (any { $self->type eq $_ } (sales_quotation_type(), purchase_order_type())),
           disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
         ],
         action => [
-          t8('Purchase Order'),
+          t8('Save and Purchase Order'),
           submit   => [ '#order_form', { action => "Order/purchase_order" } ],
           only_if  => (any { $self->type eq $_ } (sales_order_type(), request_quotation_type())),
           disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
@@ -1562,12 +1702,12 @@ sub setup_edit_action_bar {
           t8('Export'),
         ],
         action => [
-          t8('Print'),
-          call => [ 'kivi.Order.show_print_options' ],
+          t8('Save and print'),
+          call => [ 'kivi.Order.show_print_options', $::instance_conf->get_order_warn_duplicate_parts ],
         ],
         action => [
-          t8('E-mail'),
-          call => [ 'kivi.Order.email' ],
+          t8('Save and E-mail'),
+          call => [ 'kivi.Order.email', $::instance_conf->get_order_warn_duplicate_parts ],
         ],
         action => [
           t8('Download attachments of all parts'),
@@ -1642,7 +1782,7 @@ sub generate_pdf {
         },
       );
       1;
-    } || push @errors, ref($EVAL_ERROR) eq 'SL::X::FormError' ? $EVAL_ERROR->getMessage : $EVAL_ERROR;
+    } || push @errors, ref($EVAL_ERROR) eq 'SL::X::FormError' ? $EVAL_ERROR->error : $EVAL_ERROR;
   });
 
   return @errors;
@@ -1684,7 +1824,7 @@ sub make_periodic_invoices_config_from_yaml {
   my ($yaml_config) = @_;
 
   return if !$yaml_config;
-  my $attr = YAML::Load($yaml_config);
+  my $attr = SL::YAML::Load($yaml_config);
   return if 'HASH' ne ref $attr;
   return SL::DB::PeriodicInvoicesConfig->new(%$attr);
 }
@@ -1726,6 +1866,18 @@ sub get_title_for {
        : '';
 }
 
+sub get_item_cvpartnumber {
+  my ($self, $item) = @_;
+
+  if ($self->cv eq 'vendor') {
+    my @mms = grep { $_->make eq $self->order->customervendor->id } @{$item->part->makemodels};
+    $item->{cvpartnumber} = $mms[0]->model if scalar @mms;
+  } elsif ($self->cv eq 'customer') {
+    my @cps = grep { $_->customer_id eq $self->order->customervendor->id } @{$item->part->customerprices};
+    $item->{cvpartnumber} = $cps[0]->customer_partnumber if scalar @cps;
+  }
+}
+
 sub sales_order_type {
   'sales_order';
 }
@@ -1765,9 +1917,8 @@ SL::Controller::Order - controller for orders
 This is a new form to enter orders, completely rewritten with the use
 of controller and java script techniques.
 
-The aim is to provide the user a better expirience and a faster flow
-of work. Also the code should be more readable, more reliable and
-better to maintain.
+The aim is to provide the user a better experience and a faster workflow. Also
+the code should be more readable, more reliable and better to maintain.
 
 =head2 Key Features
 
@@ -1787,11 +1938,6 @@ Possibility to enter more than one item at once.
 
 =item *
 
-Save order only on "save" (and "save and delivery order"-workflow). No
-hidden save on "print" or "email".
-
-=item *
-
 Item list in a scrollable area, so that the workflow buttons stay at
 the bottom.
 
@@ -1803,7 +1949,7 @@ possible (by partnumber, description, qty, sellprice and discount for now).
 =item *
 
 No C<update> is necessary. All entries and calculations are managed
-with ajax-calls and the page does only reload on C<save>.
+with ajax-calls and the page only reloads on C<save>.
 
 =item *