doc aktualisiert
[kivitendo-erp.git] / SL / Controller / Order.pm
index 9ba587e..b57d772 100644 (file)
@@ -15,12 +15,14 @@ use SL::DB::Order;
 use SL::DB::Default;
 use SL::DB::Unit;
 use SL::DB::Part;
+use SL::DB::PartsGroup;
 use SL::DB::Printer;
 use SL::DB::Language;
 use SL::DB::RecordLink;
 
 use SL::Helper::CreatePDF qw(:all);
 use SL::Helper::PrintOptions;
+use SL::Helper::ShippedQty;
 
 use SL::Controller::Helper::GetModels;
 
@@ -42,10 +44,10 @@ 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 print create_pdf send_email) ]);
+                        only => [ qw(save save_as_new save_and_delivery_order save_and_invoice print send_email) ]);
 
 __PACKAGE__->run_before('get_unalterable_data',
-                        only => [ qw(save save_as_new save_and_delivery_order save_and_invoice print create_pdf send_email) ]);
+                        only => [ qw(save save_as_new save_and_delivery_order save_and_invoice print send_email) ]);
 
 #
 # actions
@@ -71,7 +73,23 @@ sub action_add {
 sub action_edit {
   my ($self) = @_;
 
-  $self->load_order;
+  if ($::form->{id}) {
+    $self->load_order;
+
+  } else {
+    # this is to edit an order from an unsaved order object
+
+    # set item ids to new fake id, to identify them as new items
+    foreach my $item (@{$self->order->items_sorted}) {
+      $item->{new_fake_id} = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000);
+    }
+    # trigger rendering values for second row/longdescription as hidden,
+    # because they are loaded only on demand. So we need to keep the values
+    # from the source.
+    $_->{render_second_row}      = 1 for @{ $self->order->items_sorted };
+    $_->{render_longdescription} = 1 for @{ $self->order->items_sorted };
+  }
+
   $self->recalc();
   $self->pre_render();
   $self->render(
@@ -81,6 +99,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) = @_;
@@ -192,14 +240,24 @@ 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->val('#id', $self->order->id)
+           ->val('#order_' . $self->nr_key(), $self->order->number);
+
   my $format      = $::form->{print_options}->{format};
   my $media       = $::form->{print_options}->{media};
   my $formname    = $::form->{print_options}->{formname};
   my $copies      = $::form->{print_options}->{copies};
   my $groupitems  = $::form->{print_options}->{groupitems};
 
-  # only pdf by now
-  if (none { $format eq $_ } qw(pdf)) {
+  # only pdf and opendocument by now
+  if (none { $format eq $_ } qw(pdf opendocument opendocument_pdf)) {
     return $self->js->flash('error', t8('Format \'#1\' is not supported yet/anymore.', $format))->render;
   }
 
@@ -236,7 +294,7 @@ sub action_print {
     $sfile->fh->close;
 
     my $key = join('_', Time::HiRes::gettimeofday(), int rand 1000000000000);
-    $::auth->set_session_value("Order::create_pdf-${key}" => $sfile->file_name);
+    $::auth->set_session_value("Order::print-${key}" => $sfile->file_name);
 
     $self->js
     ->run('kivi.Order.download_pdf', $pdf_filename, $key)
@@ -294,7 +352,7 @@ sub action_download_pdf {
   my ($self) = @_;
 
   my $key = $::form->{key};
-  my $tmp_filename = $::auth->get_session_value("Order::create_pdf-${key}");
+  my $tmp_filename = $::auth->get_session_value("Order::print-${key}");
   return $self->send_file(
     $tmp_filename,
     type => 'application/pdf',
@@ -352,6 +410,17 @@ 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->val('#id', $self->order->id)
+           ->val('#order_' . $self->nr_key(), $self->order->number);
+
   my $email_form  = delete $::form->{email_form};
   my %field_names = (to => 'email');
 
@@ -369,11 +438,11 @@ sub action_send_email {
     $language = SL::DB::Language->new(id => $::form->{print_options}->{language_id})->load if $::form->{print_options}->{language_id};
 
     my $pdf;
-    my @errors = genereate_pdf($self->order, \$pdf, {media      => $::form->{media},
-                                                     format     => $::form->{print_options}->{format},
-                                                     formname   => $::form->{print_options}->{formname},
-                                                     language   => $language,
-                                                     groupitems => $::form->{print_options}->{groupitems}});
+    my @errors = generate_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);
     }
@@ -399,6 +468,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')
@@ -417,7 +488,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(
@@ -605,6 +676,7 @@ 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;
@@ -729,7 +801,6 @@ sub action_add_item {
 
 # open the dialog for entering multiple items at once
 sub action_show_multi_items_dialog {
-  require SL::DB::PartsGroup;
   $_[0]->render('order/tabs/_multi_items_dialog', { layout => 0 },
                 all_partsgroups => SL::DB::Manager::PartsGroup->get_all);
 }
@@ -950,6 +1021,19 @@ sub js_redisplay_amounts_and_taxes {
     $self->js->show('#subtotal_row_id');
   }
 
+  if ($self->order->is_sales) {
+    my $is_neg = $self->order->marge_total < 0;
+    $self->js
+      ->html('#marge_total_id',   $::form->format_amount(\%::myconfig, $self->order->marge_total,   2))
+      ->html('#marge_percent_id', $::form->format_amount(\%::myconfig, $self->order->marge_percent, 2))
+      ->action_if( $is_neg, 'addClass',    '#marge_total_id',        'plus0')
+      ->action_if( $is_neg, 'addClass',    '#marge_percent_id',      'plus0')
+      ->action_if( $is_neg, 'addClass',    '#marge_percent_sign_id', 'plus0')
+      ->action_if(!$is_neg, 'removeClass', '#marge_total_id',        'plus0')
+      ->action_if(!$is_neg, 'removeClass', '#marge_percent_id',      'plus0')
+      ->action_if(!$is_neg, 'removeClass', '#marge_percent_sign_id', 'plus0');
+  }
+
   $self->js
     ->html('#netamount_id', $::form->format_amount(\%::myconfig, $self->order->netamount, -2))
     ->html('#amount_id',    $::form->format_amount(\%::myconfig, $self->order->amount,    -2))
@@ -1135,8 +1219,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 ? 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([]);
@@ -1265,7 +1351,7 @@ sub setup_order_from_cv {
   $order->intnotes($order->customervendor->notes);
 
   if ($order->is_sales) {
-    $order->salesman_id($order->customer->salesman_id);
+    $order->salesman_id($order->customer->salesman_id || SL::DB::Manager::Employee->current->id);
     $order->taxincluded(defined($order->customer->taxincluded_checked)
                         ? $order->customer->taxincluded_checked
                         : $::myconfig{taxincluded_checked});
@@ -1344,13 +1430,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 }) {
@@ -1440,7 +1530,7 @@ sub pre_render {
                 show_headers       => 1,
                 no_queue           => 1,
                 no_postscript      => 1,
-                no_opendocument    => 1,
+                no_opendocument    => 0,
                 no_html            => 1},
   );
 
@@ -1450,6 +1540,11 @@ sub pre_render {
     $item->active_discount_source($price_source->discount_from_source($item->active_discount_source));
   }
 
+  if (any { $self->type eq $_ } (sales_order_type(), purchase_order_type())) {
+    # calculate shipped qtys here to prevent calling calculate for every item via the items method
+    SL::Helper::ShippedQty->new->calculate($self->order)->write_to_objects;
+  }
+
   if ($self->order->number && $::instance_conf->get_webdav) {
     my $webdav = SL::Webdav->new(
       type     => $self->type,
@@ -1489,19 +1584,6 @@ sub setup_edit_action_bar {
           checks    => [ 'kivi.Order.check_save_active_periodic_invoices' ],
           disabled  => !$self->order->id ? t8('This object has not been saved yet.') : undef,
         ],
-        action => [
-          t8('Save and Delivery Order'),
-          call      => [ 'kivi.Order.save', 'save_and_delivery_order', $::instance_conf->get_order_warn_duplicate_parts,
-                                                                       $::instance_conf->get_order_warn_no_deliverydate,
-                                                                                                                        ],
-          checks    => [ 'kivi.Order.check_save_active_periodic_invoices' ],
-          only_if   => (any { $self->type eq $_ } (sales_order_type(), purchase_order_type()))
-        ],
-        action => [
-          t8('Save and Invoice'),
-          call      => [ 'kivi.Order.save', 'save_and_invoice', $::instance_conf->get_order_warn_duplicate_parts ],
-          checks    => [ 'kivi.Order.check_save_active_periodic_invoices' ],
-        ],
       ], # end of combobox "Save"
 
       combobox => [
@@ -1520,6 +1602,19 @@ sub setup_edit_action_bar {
           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,
         ],
+        action => [
+          t8('Save and Delivery Order'),
+          call      => [ 'kivi.Order.save', 'save_and_delivery_order', $::instance_conf->get_order_warn_duplicate_parts,
+                                                                       $::instance_conf->get_order_warn_no_deliverydate,
+                                                                                                                        ],
+          checks    => [ 'kivi.Order.check_save_active_periodic_invoices' ],
+          only_if   => (any { $self->type eq $_ } (sales_order_type(), purchase_order_type()))
+        ],
+        action => [
+          t8('Save and Invoice'),
+          call      => [ 'kivi.Order.save', 'save_and_invoice', $::instance_conf->get_order_warn_duplicate_parts ],
+          checks    => [ 'kivi.Order.check_save_active_periodic_invoices' ],
+        ],
       ], # end of combobox "Workflow"
 
       combobox => [
@@ -1527,12 +1622,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'),
@@ -1569,9 +1664,17 @@ sub generate_pdf {
   $order->language($params->{language});
   $order->flatten_to_form($print_form, format_amounts => 1);
 
+  my $template_ext;
+  my $template_type;
+  if ($print_form->{format} =~ /(opendocument|oasis)/i) {
+    $template_ext  = 'odt';
+    $template_type = 'OpenDocument';
+  }
+
   # search for the template
   my ($template_file, @template_files) = SL::Helper::CreatePDF->find_template(
     name        => $print_form->{formname},
+    extension   => $template_ext,
     email       => $print_form->{media} eq 'email',
     language    => $params->{language},
     printer_id  => $print_form->{printer_id},  # todo
@@ -1588,8 +1691,10 @@ sub generate_pdf {
       $print_form->prepare_for_printing;
 
       $$pdf_ref = SL::Helper::CreatePDF->create_pdf(
-        template  => $template_file,
-        variables => $print_form,
+        format        => $print_form->{format},
+        template_type => $template_type,
+        template      => $template_file,
+        variables     => $print_form,
         variable_content_types => {
           longdescription => 'html',
           partnotes       => 'html',
@@ -1742,11 +1847,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.
 
@@ -1836,7 +1936,7 @@ java script functions
 
 =item * credit limit
 
-=item * more workflows (save as new, quotation, purchase order)
+=item * more workflows (quotation, rfq)
 
 =item * price sources: little symbols showing better price / better discount
 
@@ -1941,12 +2041,6 @@ editor or on text processing application).
 
 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