Auftrags-Controller: Preis nach Preisgruppe setzen, wenn keiner angegeben
[kivitendo-erp.git] / SL / Controller / Order.pm
index 8393850..f275f87 100644 (file)
@@ -4,8 +4,11 @@ use strict;
 use parent qw(SL::Controller::Base);
 
 use SL::Helper::Flash;
-use SL::ClientJS;
 use SL::Presenter;
+use SL::Locale::String;
+use SL::SessionFile::Random;
+use SL::PriceSource;
+use SL::Form;
 
 use SL::DB::Order;
 use SL::DB::Customer;
@@ -15,15 +18,19 @@ use SL::DB::Employee;
 use SL::DB::Project;
 use SL::DB::Default;
 use SL::DB::Unit;
+use SL::DB::Price;
 
 use SL::Helper::DateTime;
+use SL::Helper::CreatePDF qw(:all);
 
 use List::Util qw(max first);
 use List::MoreUtils qw(none pairwise);
+use English qw(-no_match_vars);
+use File::Spec;
 
 use Rose::Object::MakeMethods::Generic
 (
- 'scalar --get_set_init' => [ qw(order valid_types type cv js p) ],
+ 'scalar --get_set_init' => [ qw(order valid_types type cv p) ],
 );
 
 
@@ -31,8 +38,10 @@ use Rose::Object::MakeMethods::Generic
 __PACKAGE__->run_before('_check_auth');
 
 __PACKAGE__->run_before('_recalc',
-                        only => [ qw(edit update save) ]);
+                        only => [ qw(edit update save save_and_delivery_order create_pdf send_email) ]);
 
+__PACKAGE__->run_before('_get_unalterable_data',
+                        only => [ qw(save save_and_delivery_order create_pdf send_email) ]);
 
 #
 # actions
@@ -86,7 +95,7 @@ sub action_save {
 
   if (scalar @{ $errors }) {
     $self->js->flash('error', $_) foreach @{ $errors };
-    return $self->js->render($self);
+    return $self->js->render();
   }
 
   flash_later('info', $::locale->text('The order has been saved'));
@@ -99,6 +108,138 @@ sub action_save {
   $self->redirect_to(@redirect_params);
 }
 
+sub action_create_pdf {
+  my ($self) = @_;
+
+  my $pdf;
+  my @errors = _create_pdf($self->order, \$pdf);
+  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;
+
+  my $key = join('_', Time::HiRes::gettimeofday(), int rand 1000000000000);
+  $::auth->set_session_value("Order::create_pdf-${key}" => $sfile->file_name);
+
+  my $pdf_filename =  t8('Sales Order') . '_' . $self->order->ordnumber . '.pdf';
+
+  $self->js
+    ->run('download_pdf', $pdf_filename, $key)
+    ->flash('info', t8('The PDF has been created'))->render($self);
+}
+
+sub action_download_pdf {
+  my ($self) = @_;
+
+  my $key = $::form->{key};
+  my $tmp_filename = $::auth->get_session_value("Order::create_pdf-${key}");
+  return $self->send_file(
+    $tmp_filename,
+    type => 'application/pdf',
+    name => $::form->{pdf_filename},
+  );
+}
+
+sub action_show_email_dialog {
+  my ($self) = @_;
+
+  my $cv_method = $self->cv;
+
+  if (!$self->order->$cv_method) {
+    return $self->js->flash('error', t8('Cannot send E-mail without ' . $self->cv))
+                    ->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;
+  # Todo: get addresses from shipto, if any
+
+  my $form = Form->new;
+  $form->{ordnumber} = $self->order->ordnumber;
+  $form->{formname}  = $self->type;
+  $form->{type}      = $self->type;
+  $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();
+
+  my $dialog_html = $self->render('order/tabs/_email_dialog', { output => 0 });
+  $self->js
+      ->run('show_email_dialog', $dialog_html)
+      ->reinit_widgets
+      ->render($self);
+}
+
+# Todo: handling error messages: flash is not displayed in dialog, but in the main form
+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 $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);
+  }
+
+  $mail->{attachments} = [{ "content" => $pdf,
+                            "name"    => $::form->{email}->{attachment_filename} }];
+
+  if (my $err = $mail->send) {
+    return $self->js->flash('error', t8('Sending E-mail: ') . $err)
+                    ->render($self);
+  }
+
+  # 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};
+
+  $self->js
+      ->val('#order_intnotes', $intnotes)
+      ->run('close_email_dialog')
+      ->render($self);
+}
+
+sub action_save_and_delivery_order {
+  my ($self) = @_;
+
+  my $errors = $self->_save();
+
+  if (scalar @{ $errors }) {
+    $self->js->flash('error', $_) foreach @{ $errors };
+    return $self->js->render();
+  }
+
+  my $delivery_order = $self->order->convert_to_delivery_order($self->order);
+
+  flash_later('info', $::locale->text('The order has been saved'));
+  my @redirect_params = (
+    controller => 'do.pl',
+    action     => 'edit',
+    type       => $delivery_order->type,
+    id         => $delivery_order->id,
+    vc         => $delivery_order->is_sales ? 'customer' : 'vendor',
+  );
+
+  $self->redirect_to(@redirect_params);
+}
+
 sub action_customer_vendor_changed {
   my ($self) = @_;
 
@@ -143,12 +284,17 @@ sub action_add_item {
   my $cv_method   = $self->cv;
   my $cv_discount = $self->order->$cv_method? $self->order->$cv_method->discount : 0.0;
 
+  my $price = $item->sellprice;
+  $price  ||= ($self->order->$cv_method && $self->order->$cv_method->klass)
+            ? (SL::DB::Manager::Price->find_by(parts_id => $part->id, pricegroup_id => $self->order->$cv_method->klass)->price || $part->sellprice)
+            : $part->sellprice;
+
   my %new_attr;
   $new_attr{part}        = $part;
   $new_attr{description} = $part->description if ! $item->description;
   $new_attr{qty}         = 1.0                if ! $item->qty;
   $new_attr{unit}        = $part->unit;
-  $new_attr{sellprice}   = $part->sellprice   if ! $item->sellprice;
+  $new_attr{sellprice}   = $price;
   $new_attr{discount}    = $cv_discount       if ! $item->discount;
 
   # add_custom_variables adds cvars to an orderitem with no cvars for saving, but
@@ -175,10 +321,11 @@ sub action_add_item {
     ->val('#add_item_discount_as_percent', '')
     ->run('row_table_scroll_down')
     ->run('row_set_keyboard_events_by_id', $item_id)
+    ->on('.recalc', 'change', 'recalc_amounts_and_taxes')
     ->focus('#add_item_parts_id_name');
 
   $self->_js_redisplay_amounts_and_taxes;
-  $self->js->render($self);
+  $self->js->render();
 }
 
 sub action_recalc_amounts_and_taxes {
@@ -186,13 +333,34 @@ sub action_recalc_amounts_and_taxes {
 
   $self->_recalc();
 
+  $self->_js_redisplay_linetotals;
   $self->_js_redisplay_amounts_and_taxes;
-  $self->js->render($self);
+  $self->js->render();
+}
+
+sub _js_redisplay_linetotals {
+  my ($self) = @_;
+
+  my @data = map {$::form->format_amount(\%::myconfig, $_->{linetotal}, 2, 0)} @{ $self->order->items };
+  $self->js
+    ->run('redisplay_linetotals', \@data);
 }
 
 sub _js_redisplay_amounts_and_taxes {
   my ($self) = @_;
 
+  if (scalar @{ $self->{taxes} }) {
+    $self->js->show('#taxincluded_row_id');
+  } else {
+    $self->js->hide('#taxincluded_row_id');
+  }
+
+  if ($self->order->taxincluded) {
+    $self->js->hide('#subtotal_row_id');
+  } else {
+    $self->js->show('#subtotal_row_id');
+  }
+
   $self->js
     ->html('#netamount_id', $::form->format_amount(\%::myconfig, $self->order->netamount, -2))
     ->html('#amount_id',    $::form->format_amount(\%::myconfig, $self->order->amount,    -2))
@@ -228,10 +396,6 @@ sub init_cv {
   return $cv;
 }
 
-sub init_js {
-  SL::ClientJS->new;
-}
-
 sub init_p {
   SL::Presenter->get;
 }
@@ -280,7 +444,7 @@ sub build_tax_rows {
 
   my $rows_as_html;
   foreach my $tax (@{ $self->{taxes} }) {
-    $rows_as_html .= $self->p->render('order/tabs/_tax_row', TAX => $tax);
+    $rows_as_html .= $self->p->render('order/tabs/_tax_row', TAX => $tax, TAXINCLUDED => $self->order->taxincluded);
   }
   return $rows_as_html;
 }
@@ -309,16 +473,45 @@ sub _recalc {
   $self->order->currency_id($::instance_conf->get_currency_id());
 
   my %pat = $self->order->calculate_prices_and_taxes();
-
+  $self->{taxes} = [];
   foreach my $tax_chart_id (keys %{ $pat{taxes} }) {
     my $tax = SL::DB::Manager::Tax->find_by(chart_id => $tax_chart_id);
-    push(@{ $self->{taxes} }, { amount => $pat{taxes}->{$tax_chart_id},
-                                tax    => $tax });
+
+    my @amount_keys = grep { $pat{amounts}->{$_}->{tax_id} == $tax->id } keys %{ $pat{amounts} };
+    push(@{ $self->{taxes} }, { amount    => $pat{taxes}->{$tax_chart_id},
+                                netamount => $pat{amounts}->{$amount_keys[0]}->{amount},
+                                tax       => $tax });
   }
 
   pairwise { $a->{linetotal} = $b->{linetotal} } @{$self->order->items}, @{$pat{items}};
 }
 
+
+sub _get_unalterable_data {
+  my ($self) = @_;
+
+  foreach my $item (@{ $self->order->items }) {
+    if ($item->id) {
+      # load data from orderitems (db)
+      my $db_item = SL::DB::OrderItem->new(id => $item->id)->load;
+      $item->$_($db_item->$_) for qw(active_discount_source active_price_source longdescription);
+    } else {
+      # set data from part (or other sources)
+      $item->longdescription($item->part->notes);
+      #$item->active_price_source('');
+      #$item->active_discount_source('');
+    }
+
+    # autovivify all cvars that are not in the form (cvars_by_config can do it).
+    # workaround to pre-parse number-cvars (parse_custom_variable_values does not parse number values).
+    foreach my $var (@{ $item->cvars_by_config }) {
+      $var->unparsed_value($::form->parse_amount(\%::myconfig, $var->{__unparsed_value})) if ($var->config->type eq 'number' && exists($var->{__unparsed_value}));
+    }
+    $item->parse_custom_variable_values;
+  }
+}
+
+
 sub _save {
   my ($self) = @_;
 
@@ -337,15 +530,56 @@ sub _save {
 sub _pre_render {
   my ($self) = @_;
 
-  $self->{all_taxzones}  = SL::DB::Manager::TaxZone->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_projects}  = SL::DB::Manager::Project->get_all(where => [ or => [ id => $self->order->globalproject_id,
-                                                                                active => 1 ] ],
-                                                             sort_by => 'projectnumber');
+  $self->{all_taxzones}        = SL::DB::Manager::TaxZone->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();
+  $self->{all_delivery_terms}  = SL::DB::Manager::DeliveryTerm->get_all_sorted();
 
   $self->{current_employee_id} = SL::DB::Manager::Employee->current->id;
+
+  $::request->{layout}->use_javascript("${_}.js")  for qw(ckeditor/ckeditor ckeditor/adapters/jquery);
+}
+
+sub _create_pdf {
+  my ($order, $pdf_ref, $params) = @_;
+
+  my $print_form = Form->new('');
+  $print_form->{type}     = 'sales_order';
+  $print_form->{formname} = 'sales_order',
+  $print_form->{format}   = $params->{format} || 'pdf',
+  $print_form->{media}    = $params->{media}  || 'file';
+
+  $order->flatten_to_form($print_form, format_amounts => 1);
+  # flatten_to_form sets payment_terms from customer/vendor - we do not want that here
+  delete $print_form->{payment_terms} if !$print_form->{payment_id};
+
+  my @errors = ();
+  $print_form->throw_on_error(sub {
+    eval {
+      $print_form->prepare_for_printing;
+
+      $$pdf_ref = SL::Helper::CreatePDF->create_pdf(
+        template  => SL::Helper::CreatePDF->find_template(name => $print_form->{formname}),
+        variables => $print_form,
+        variable_content_types => {
+          longdescription => 'html',
+          partnotes       => 'html',
+          notes           => 'html',
+        },
+      );
+      1;
+    } || push @errors, ref($EVAL_ERROR) eq 'SL::X::FormError' ? $EVAL_ERROR->getMessage : $EVAL_ERROR;
+  });
+
+  return @errors;
 }
 
 sub _sales_order_type {
@@ -357,3 +591,22 @@ sub _purchase_order_type {
 }
 
 1;
+
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::Controller::Order - controller for orders
+
+=head1 TODO
+
+Testing, PriceSources, pricefactor, units, currency, delivered, delivery order created, ...
+
+=head1 AUTHOR
+
+Bernd Bleßmann E<lt>bernd@kivitendo-premium.deE<gt>
+
+=cut
+