|  | package SL::Controller::Order;
 | 
  
    |  | 
 | 
  
    |  | use strict;
 | 
  
    |  | use parent qw(SL::Controller::Base);
 | 
  
    |  | 
 | 
  
    |  | use SL::Helper::Flash qw(flash_later);
 | 
  
    |  | use SL::HTML::Util;
 | 
  
    |  | use SL::Presenter::Tag qw(select_tag hidden_tag div_tag);
 | 
  
    |  | use SL::Locale::String qw(t8);
 | 
  
    |  | 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::AdditionalBillingAddress;
 | 
  
    |  | use SL::DB::AuthUser;
 | 
  
    |  | use SL::DB::History;
 | 
  
    |  | use SL::DB::Order;
 | 
  
    |  | use SL::DB::Default;
 | 
  
    |  | use SL::DB::Unit;
 | 
  
    |  | use SL::DB::Part;
 | 
  
    |  | use SL::DB::PartClassification;
 | 
  
    |  | use SL::DB::PartsGroup;
 | 
  
    |  | use SL::DB::Printer;
 | 
  
    |  | use SL::DB::Note;
 | 
  
    |  | use SL::DB::Language;
 | 
  
    |  | use SL::DB::RecordLink;
 | 
  
    |  | use SL::DB::RequirementSpec;
 | 
  
    |  | use SL::DB::Shipto;
 | 
  
    |  | use SL::DB::Translation;
 | 
  
    |  | 
 | 
  
    |  | use SL::Helper::CreatePDF qw(:all);
 | 
  
    |  | use SL::Helper::PrintOptions;
 | 
  
    |  | use SL::Helper::ShippedQty;
 | 
  
    |  | use SL::Helper::UserPreferences::DisplayPreferences;
 | 
  
    |  | use SL::Helper::UserPreferences::PositionsScrollbar;
 | 
  
    |  | use SL::Helper::UserPreferences::UpdatePositions;
 | 
  
    |  | 
 | 
  
    |  | use SL::Controller::Helper::GetModels;
 | 
  
    |  | 
 | 
  
    |  | use List::Util qw(first sum0);
 | 
  
    |  | 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 Cwd;
 | 
  
    |  | use Sort::Naturally;
 | 
  
    |  | 
 | 
  
    |  | use Rose::Object::MakeMethods::Generic
 | 
  
    |  | (
 | 
  
    |  |  scalar => [ qw(item_ids_to_delete is_custom_shipto_to_delete) ],
 | 
  
    |  |  'scalar --get_set_init' => [ qw(order valid_types type cv p all_price_factors
 | 
  
    |  |                               search_cvpartnumber show_update_button
 | 
  
    |  |                               part_picker_classification_ids
 | 
  
    |  |                               is_final_version) ],
 | 
  
    |  | );
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  | # safety
 | 
  
    |  | __PACKAGE__->run_before('check_auth',
 | 
  
    |  |                         except => [ qw(close_quotations) ]);
 | 
  
    |  | 
 | 
  
    |  | __PACKAGE__->run_before('check_auth_for_edit',
 | 
  
    |  |                         except => [ qw(edit show_customer_vendor_details_dialog price_popup load_second_rows close_quotations) ]);
 | 
  
    |  | 
 | 
  
    |  | #
 | 
  
    |  | # actions
 | 
  
    |  | #
 | 
  
    |  | 
 | 
  
    |  | # add a new order
 | 
  
    |  | 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       :
 | 
  
    |  |                    $self->type eq sales_order_type()     ? $::instance_conf->get_delivery_date_interval : 1;
 | 
  
    |  | 
 | 
  
    |  |   if (   ($self->type eq sales_order_type()     &&  $::instance_conf->get_deliverydate_on)
 | 
  
    |  |       || ($self->type eq sales_quotation_type() &&  $::instance_conf->get_reqdate_on)
 | 
  
    |  |       && (!$self->order->reqdate)) {
 | 
  
    |  |     $self->order->reqdate(DateTime->today_local->next_workday(extra_days => $extra_days));
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  |   $self->pre_render();
 | 
  
    |  |   $self->render(
 | 
  
    |  |     'order/form',
 | 
  
    |  |     title => $self->get_title_for('add'),
 | 
  
    |  |     %{$self->{template_args}}
 | 
  
    |  |   );
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub action_add_from_reclamation {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   require SL::DB::Reclamation;
 | 
  
    |  |   my $reclamation = SL::DB::Reclamation->new(id => $::form->{from_id})->load;
 | 
  
    |  |   my $order = $reclamation->convert_to_order();
 | 
  
    |  | 
 | 
  
    |  |   $self->order($order);
 | 
  
    |  | 
 | 
  
    |  |   $self->recalc();
 | 
  
    |  |   $self->pre_render();
 | 
  
    |  |   $self->render(
 | 
  
    |  |     'order/form',
 | 
  
    |  |     title => $self->get_title_for('edit'),
 | 
  
    |  |     %{$self->{template_args}}
 | 
  
    |  |   );
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # edit an existing order
 | 
  
    |  | sub action_edit {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   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 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 };
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   $self->recalc();
 | 
  
    |  |   $self->pre_render();
 | 
  
    |  |   $self->render(
 | 
  
    |  |     'order/form',
 | 
  
    |  |     title => $self->get_title_for('edit'),
 | 
  
    |  |     %{$self->{template_args}}
 | 
  
    |  |   );
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # 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) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $errors = $self->delete();
 | 
  
    |  | 
 | 
  
    |  |   if (scalar @{ $errors }) {
 | 
  
    |  |     $self->js->flash('error', $_) foreach @{ $errors };
 | 
  
    |  |     return $self->js->render();
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   my $text = $self->type eq sales_order_type()       ? $::locale->text('The order has been deleted')
 | 
  
    |  |            : $self->type eq purchase_order_type()    ? $::locale->text('The order has been deleted')
 | 
  
    |  |            : $self->type eq sales_quotation_type()   ? $::locale->text('The quotation has been deleted')
 | 
  
    |  |            : $self->type eq request_quotation_type() ? $::locale->text('The rfq has been deleted')
 | 
  
    |  |            : '';
 | 
  
    |  |   flash_later('info', $text);
 | 
  
    |  | 
 | 
  
    |  |   my @redirect_params = (
 | 
  
    |  |     action => 'add',
 | 
  
    |  |     type   => $self->type,
 | 
  
    |  |   );
 | 
  
    |  | 
 | 
  
    |  |   $self->redirect_to(@redirect_params);
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # save the order
 | 
  
    |  | sub action_save {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $errors = $self->save();
 | 
  
    |  | 
 | 
  
    |  |   if (scalar @{ $errors }) {
 | 
  
    |  |     $self->js->flash('error', $_) foreach @{ $errors };
 | 
  
    |  |     return $self->js->render();
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   my $text = $self->type eq sales_order_type()       ? $::locale->text('The order has been saved')
 | 
  
    |  |            : $self->type eq purchase_order_type()    ? $::locale->text('The order has been saved')
 | 
  
    |  |            : $self->type eq sales_quotation_type()   ? $::locale->text('The quotation has been saved')
 | 
  
    |  |            : $self->type eq request_quotation_type() ? $::locale->text('The rfq has been saved')
 | 
  
    |  |            : '';
 | 
  
    |  |   flash_later('info', $text);
 | 
  
    |  | 
 | 
  
    |  |   my @redirect_params;
 | 
  
    |  |   if ($::form->{back_to_caller}) {
 | 
  
    |  |     @redirect_params = $::form->{callback} ? ($::form->{callback})
 | 
  
    |  |                                            : (controller => 'LoginScreen', action => 'user_login');
 | 
  
    |  | 
 | 
  
    |  |   } else {
 | 
  
    |  |     @redirect_params = (
 | 
  
    |  |       action   => 'edit',
 | 
  
    |  |       type     => $self->type,
 | 
  
    |  |       id       => $self->order->id,
 | 
  
    |  |       callback => $::form->{callback},
 | 
  
    |  |     );
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   $self->redirect_to(@redirect_params);
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # create new version and set version number
 | 
  
    |  | sub action_add_subversion {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $current_version_number = $self->order->current_version_number;
 | 
  
    |  |   my $new_version_number     = $current_version_number + 1;
 | 
  
    |  | 
 | 
  
    |  |   my $new_number = $self->order->number;
 | 
  
    |  |   $new_number    =~ s/-$current_version_number$//;
 | 
  
    |  |   $self->order->number($new_number . '-' . $new_version_number);
 | 
  
    |  |   $self->order->add_order_version(SL::DB::OrderVersion->new(oe_id   => $self->order->id,
 | 
  
    |  |                                                             version => $new_version_number));
 | 
  
    |  | 
 | 
  
    |  |   # call the save action
 | 
  
    |  |   $self->action_save();
 | 
  
    |  | 
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # save the order as new document and open it for edit
 | 
  
    |  | sub action_save_as_new {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $order = $self->order;
 | 
  
    |  | 
 | 
  
    |  |   if (!$order->id) {
 | 
  
    |  |     $self->js->flash('error', t8('This object has not been saved yet.')); |