|  | package SL::Controller::DeliveryOrder;
 | 
  
    |  | 
 | 
  
    |  | use strict;
 | 
  
    |  | use parent qw(SL::Controller::Base);
 | 
  
    |  | 
 | 
  
    |  | use SL::Helper::Flash qw(flash_later);
 | 
  
    |  | use SL::Helper::Number qw(_format_number _parse_number);
 | 
  
    |  | use SL::Presenter::Tag qw(select_tag hidden_tag div_tag);
 | 
  
    |  | use SL::Presenter::DeliveryOrder qw(delivery_order_status_line);
 | 
  
    |  | 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::History;
 | 
  
    |  | use SL::DB::Order;
 | 
  
    |  | use SL::DB::Default;
 | 
  
    |  | use SL::DB::Unit;
 | 
  
    |  | use SL::DB::Order;
 | 
  
    |  | use SL::DB::Part;
 | 
  
    |  | use SL::DB::PartClassification;
 | 
  
    |  | use SL::DB::PartsGroup;
 | 
  
    |  | use SL::DB::Printer;
 | 
  
    |  | use SL::DB::Language;
 | 
  
    |  | use SL::DB::RecordLink;
 | 
  
    |  | use SL::DB::Shipto;
 | 
  
    |  | use SL::DB::Translation;
 | 
  
    |  | use SL::DB::TransferType;
 | 
  
    |  | use SL::DB::Warehouse;
 | 
  
    |  | 
 | 
  
    |  | 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 SL::Controller::DeliveryOrder::TypeData qw(:types);
 | 
  
    |  | 
 | 
  
    |  | 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 type_data) ],
 | 
  
    |  | );
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  | # safety
 | 
  
    |  | __PACKAGE__->run_before('check_auth',
 | 
  
    |  |                         except => [ qw(update_stock_information) ]);
 | 
  
    |  | 
 | 
  
    |  | __PACKAGE__->run_before('check_auth_for_edit',
 | 
  
    |  |                         except => [ qw(update_stock_information edit show_customer_vendor_details_dialog price_popup stock_in_out_dialog load_second_rows) ]);
 | 
  
    |  | 
 | 
  
    |  | __PACKAGE__->run_before('get_unalterable_data',
 | 
  
    |  |                         only => [ qw(save save_as_new save_and_delivery_order save_and_invoice save_and_ap_transaction
 | 
  
    |  |                                      print send_email) ]);
 | 
  
    |  | 
 | 
  
    |  | #
 | 
  
    |  | # actions
 | 
  
    |  | #
 | 
  
    |  | 
 | 
  
    |  | # add a new order
 | 
  
    |  | sub action_add {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   $self->order->transdate(DateTime->now_local());
 | 
  
    |  |   $self->type_data->set_reqdate_by_type;
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  |   $self->pre_render();
 | 
  
    |  |   $self->render(
 | 
  
    |  |     'delivery_order/form',
 | 
  
    |  |     title => $self->get_title_for('add'),
 | 
  
    |  |     %{$self->{template_args}}
 | 
  
    |  |   );
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub action_add_from_order {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  |   # this interfers with init_order
 | 
  
    |  |   $self->{converted_from_oe_id} = delete $::form->{id};
 | 
  
    |  | 
 | 
  
    |  |   $self->type_data->validate($::form->{type});
 | 
  
    |  | 
 | 
  
    |  |   my $order = SL::DB::Order->new(id => $self->{converted_from_oe_id})->load;
 | 
  
    |  | 
 | 
  
    |  |   $self->order(SL::DB::DeliveryOrder->new_from($order, type => $::form->{type}));
 | 
  
    |  | 
 | 
  
    |  |   $self->action_add;
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub action_add_from_reclamation {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   require SL::DB::Reclamation;
 | 
  
    |  |   my $reclamation = SL::DB::Reclamation->new(id => $::form->{from_id})->load;
 | 
  
    |  |   my $delivery_order = SL::DB::DeliveryOrder->new_from($reclamation);
 | 
  
    |  |   $self->{converted_from_reclamation_id} = $::form->{from_id};
 | 
  
    |  |   $self->order($delivery_order);
 | 
  
    |  | 
 | 
  
    |  |   $self->action_add;
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # 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->pre_render();
 | 
  
    |  |   $self->render(
 | 
  
    |  |     'delivery_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::DeliveryOrder->new(id => $multi_ids[0])->load);
 | 
  
    |  |     $self->action_save_as_new();
 | 
  
    |  |     return;
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   # make new order from given orders
 | 
  
    |  |   my @multi_orders = map { SL::DB::DeliveryOrder->new(id => $_)->load } @multi_ids;
 | 
  
    |  |   $self->{converted_from_oe_id} = join ' ', map { $_->id } @multi_orders;
 | 
  
    |  |   $self->order(SL::DB::DeliveryOrder->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();
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   flash_later('info', $self->type_data->text("delete"));
 | 
  
    |  | 
 | 
  
    |  |   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();
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   flash_later('info', $self->type_data->text("saved"));
 | 
  
    |  | 
 | 
  
    |  |   my @redirect_params = (
 | 
  
    |  |     action => 'edit',
 | 
  
    |  |     type   => $self->type,
 | 
  
    |  |     id     => $self->order->id,
 | 
  
    |  |   );
 | 
  
    |  | 
 | 
  
    |  |   $self->redirect_to(@redirect_params);
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # save the order as new document an 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.'));
 | 
  
    |  |     return $self->js->render();
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   # load order from db to check if values changed
 | 
  
    |  |   my $saved_order = SL::DB::DeliveryOrder->new(id => $order->id)->load;
 | 
  
    |  | 
 | 
  
    |  |   my %new_attrs;
 | 
  
    |  |   # Lets assign a new number if the user hasn't changed the previous one.
 | 
  
    |  |   # If it has been changed manually then use it as-is.
 | 
  
    |  |   $new_attrs{number}    = (trim($order->number) eq $saved_order->number)
 | 
  
    |  |                         ? ''
 | 
  
    |  |                         : trim($order->number);
 | 
  
    |  | 
 | 
  
    |  |   # Clear transdate unless changed
 | 
  
    |  |   $new_attrs{transdate} = ($order->transdate == $saved_order->transdate)
 | 
  
    |  |                         ? DateTime->today_local
 | 
  
    |  |                         : $order->transdate;
 | 
  
    |  | 
 | 
  
    |  |   # Set new reqdate unless changed if it is enabled in client config
 | 
  
    |  |   $new_attrs{reqdate} = $self->type_data->get_reqdate_by_type($order->reqdate, $saved_order->reqdate);
 | 
  
    |  | 
 | 
  
    |  |   # Update employee
 | 
  
    |  |   $new_attrs{employee}  = SL::DB::Manager::Employee->current;
 | 
  
    |  | 
 | 
  
    |  |   # Create new record from current one
 | 
  
    |  |   $self->order(SL::DB::DeliveryOrder->new_from($order, destination_type => $order->type, attributes => \%new_attrs));
 | 
  
    |  | 
 | 
  
    |  |   # no linked records on save as new
 | 
  
    |  |   delete $::form->{$_} for qw(converted_from_oe_id converted_from_orderitems_ids);
 | 
  
    |  | 
 | 
  
    |  |   # save
 | 
  
    |  |   $self->action_save();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # print the order
 | 
  
    |  | #
 | 
  
    |  | # This is called if "print" is pressed in the print dialog.
 | 
  
    |  | # If PDF creation was requested and succeeded, the pdf is offered for download
 | 
  
    |  | # via send_file (which uses ajax in this case).
 | 
  
    |  | sub action_print {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   if ( !$self->order->delivered ) {
 | 
  
    |  |     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};
 | 
  
    |  |   my $copies      = $::form->{print_options}->{copies};
 | 
  
    |  |   my $groupitems  = $::form->{print_options}->{groupitems};
 | 
  
    |  |   my $printer_id  = $::form->{print_options}->{printer_id};
 | 
  
    |  | 
 | 
  
    |  |   # 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;
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   # only screen or printer by now
 | 
  
    |  |   if (none { $media eq $_ } qw(screen printer)) {
 | 
  
    |  |     return $self->js->flash('error', t8('Media \'#1\' is not supported yet/anymore.', $media))->render;
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   # create a form for generate_attachment_filename
 | 
  
    |  |   my $form   = Form->new;
 | 
  
    |  |   $form->{$self->nr_key()}  = $self->order->number;
 | 
  
    |  |   $form->{type}             = $self->type;
 | 
  
    |  |   $form->{format}           = $format;
 | 
  
    |  |   $form->{formname}         = $formname;
 | 
  
    |  |   $form->{language}         = '_' . $self->order->language->template_code if $self->order->language;
 | 
  
    |  |   my $pdf_filename          = $form->generate_attachment_filename();
 | 
  
    |  |   $main::lxdebug->dump(0, 'WH: FoRM ', $form);
 | 
  
    |  |   $main::lxdebug->dump(0, 'WH: PDF ', $pdf_filename);
 | 
  
    |  |   my $pdf;
 | 
  
    |  |   my @errors = generate_pdf($self->order, \$pdf, { format     => $format,
 | 
  
    |  |                                                    formname   => $formname,
 | 
  
    |  |                                                    language   => $self->order->language,
 | 
  
    |  |                                                    printer_id => $printer_id,
 | 
  
    |  |                                                    groupitems => $groupitems });
 | 
  
    |  |   if (scalar @errors) {
 | 
  
    |  |     return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render;
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   if ($media eq 'screen') {
 | 
  
    |  |     # screen/download
 | 
  
    |  |     $self->js->flash('info', t8('The PDF has been created'));
 | 
  
    |  |     $self->send_file(
 | 
  
    |  |       \$pdf,
 | 
  
    |  |       type         => SL::MIME->mime_type_from_ext($pdf_filename),
 | 
  
    |  |       name         => $pdf_filename,
 | 
  
    |  |       js_no_render => 1,
 | 
  
    |  |     );
 | 
  
    |  | 
 | 
  
    |  |   } elsif ($media eq 'printer') {
 | 
  
    |  |     # printer
 | 
  
    |  |     my $printer_id = $::form->{print_options}->{printer_id};
 | 
  
    |  |     SL::DB::Printer->new(id => $printer_id)->load->print_document(
 | 
  
    |  |       copies  => $copies,
 | 
  
    |  |       content => $pdf,
 | 
  
    |  |     );
 | 
  
    |  | 
 | 
  
    |  |     $self->js->flash('info', t8('The PDF has been printed'));
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   my @warnings = store_pdf_to_webdav_and_filemanagement($self->order, $pdf, $pdf_filename);
 | 
  
    |  |   if (scalar @warnings) {
 | 
  
    |  |     $self->js->flash('warning', $_) for @warnings;
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   $self->save_history('PRINTED');
 | 
  
    |  | 
 | 
  
    |  |   $self->js
 | 
  
    |  |     ->run('kivi.ActionBar.setEnabled', '#save_and_email_action')
 | 
  
    |  |     ->render;
 | 
  
    |  | }
 | 
  
    |  | sub action_preview_pdf {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   if ( !$self->order->delivered ) {
 | 
  
    |  |     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      = 'pdf';
 | 
  
    |  |   my $media       = 'screen';
 | 
  
    |  |   my $formname    = $self->type;
 | 
  
    |  | 
 | 
  
    |  |   # only pdf
 | 
  
    |  |   # create a form for generate_attachment_filename
 | 
  
    |  |   my $form   = Form->new;
 | 
  
    |  |   $form->{$self->nr_key()}  = $self->order->number;
 | 
  
    |  |   $form->{type}             = $self->type;
 | 
  
    |  |   $form->{format}           = $format;
 | 
  
    |  |   $form->{formname}         = $formname;
 | 
  
    |  |   $form->{language}         = '_' . $self->order->language->template_code if $self->order->language;
 | 
  
    |  |   my $pdf_filename          = $form->generate_attachment_filename();
 | 
  
    |  | 
 | 
  
    |  |   my $pdf;
 | 
  
    |  |   my @errors = generate_pdf($self->order, \$pdf, { format     => $format,
 | 
  
    |  |                                                    formname   => $formname,
 | 
  
    |  |                                                    language   => $self->order->language,
 | 
  
    |  |                                                  });
 | 
  
    |  |   if (scalar @errors) {
 | 
  
    |  |     return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render;
 | 
  
    |  |   }
 | 
  
    |  |   $self->save_history('PREVIEWED');
 | 
  
    |  |   $self->js->flash('info', t8('The PDF has been previewed'));
 | 
  
    |  |   # screen/download
 | 
  
    |  |   $self->send_file(
 | 
  
    |  |     \$pdf,
 | 
  
    |  |     type         => SL::MIME->mime_type_from_ext($pdf_filename),
 | 
  
    |  |     name         => $pdf_filename,
 | 
  
    |  |     js_no_render => 0,
 | 
  
    |  |   );
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # open the email dialog
 | 
  
    |  | sub action_save_and_show_email_dialog {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   if ( !$self->order->delivered ) {
 | 
  
    |  |     my $errors = $self->save();
 | 
  
    |  | 
 | 
  
    |  |     if (scalar @{ $errors }) {
 | 
  
    |  |       $self->js->flash('error', $_) foreach @{ $errors };
 | 
  
    |  |       return $self->js->render();
 | 
  
    |  |     }
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   my $cv_method = $self->cv;
 | 
  
    |  | 
 | 
  
    |  |   if (!$self->order->$cv_method) {
 | 
  
    |  |     return $self->js->flash('error', $self->cv eq 'customer' ? t8('Cannot send E-mail without customer given') : t8('Cannot send E-mail without vendor given'))
 | 
  
    |  |                     ->render($self);
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   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;
 | 
  
    |  |   $form->{$self->nr_key()}  = $self->order->number;
 | 
  
    |  |   $form->{cusordnumber}     = $self->order->cusordnumber;
 | 
  
    |  |   $form->{formname}         = $self->type;
 | 
  
    |  |   $form->{type}             = $self->type;
 | 
  
    |  |   $form->{language}         = '_' . $self->order->language->template_code if $self->order->language;
 | 
  
    |  |   $form->{language_id}      = $self->order->language->id                  if $self->order->language;
 | 
  
    |  |   $form->{format}           = 'pdf';
 | 
  
    |  |   $form->{cp_id}            = $self->order->contact->cp_id if $self->order->contact;
 | 
  
    |  | 
 | 
  
    |  |   $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.DeliveryOrder.send_email()';
 | 
  
    |  | 
 | 
  
    |  |   my %files = $self->get_files_for_email_dialog();
 | 
  
    |  |   $self->{all_employees} = SL::DB::Manager::Employee->get_all(query => [ deleted => 0 ]);
 | 
  
    |  |   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->type_data->is_customer,
 | 
  
    |  |                                   ALL_EMPLOYEES => $self->{all_employees},
 | 
  
    |  |   );
 | 
  
    |  | 
 | 
  
    |  |   $self->js
 | 
  
    |  |       ->run('kivi.DeliveryOrder.show_email_dialog', $dialog_html)
 | 
  
    |  |       ->reinit_widgets
 | 
  
    |  |       ->render($self);
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # send email
 | 
  
    |  | #
 | 
  
    |  | # Todo: handling error messages: flash is not displayed in dialog, but in the main form
 | 
  
    |  | sub action_send_email {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   if ( !$self->order->delivered ) {
 | 
  
    |  |     my $errors = $self->save();
 | 
  
    |  | 
 | 
  
    |  |     if (scalar @{ $errors }) {
 | 
  
    |  |       $self->js->run('kivi.DeliveryOrder.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');
 | 
  
    |  | 
 | 
  
    |  |   $::form->{ $field_names{$_} // $_ } = $email_form->{$_} for keys %{ $email_form };
 | 
  
    |  | 
 | 
  
    |  |   # for Form::cleanup which may be called in Form::send_email
 | 
  
    |  |   $::form->{cwd}    = getcwd();
 | 
  
    |  |   $::form->{tmpdir} = $::lx_office_conf{paths}->{userspath};
 | 
  
    |  | 
 | 
  
    |  |   $::form->{$_}     = $::form->{print_options}->{$_} for keys %{ $::form->{print_options} };
 | 
  
    |  |   $::form->{media}  = 'email';
 | 
  
    |  | 
 | 
  
    |  |   if (($::form->{attachment_policy} // '') !~ m{^(?:old_file|no_file)$}) {
 | 
  
    |  |     my $pdf;
 | 
  
    |  |     my @errors = generate_pdf($self->order, \$pdf, {media      => $::form->{media},
 | 
  
    |  |                                                     format     => $::form->{print_options}->{format},
 | 
  
    |  |                                                     formname   => $::form->{print_options}->{formname},
 | 
  
    |  |                                                     language   => $self->order->language,
 | 
  
    |  |                                                     printer_id => $::form->{print_options}->{printer_id},
 | 
  
    |  |                                                     groupitems => $::form->{print_options}->{groupitems}});
 | 
  
    |  |     if (scalar @errors) {
 | 
  
    |  |       return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render($self);
 | 
  
    |  |     }
 | 
  
    |  | 
 | 
  
    |  |     my @warnings = store_pdf_to_webdav_and_filemanagement($self->order, $pdf, $::form->{attachment_filename});
 | 
  
    |  |     if (scalar @warnings) {
 | 
  
    |  |       flash_later('warning', $_) for @warnings;
 | 
  
    |  |     }
 | 
  
    |  | 
 | 
  
    |  |     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->{id} = $self->order->id; # this is used in SL::Mailer to create a linked record to the mail
 | 
  
    |  |   $::form->send_email(\%::myconfig, 'pdf');
 | 
  
    |  | 
 | 
  
    |  |   # internal notes unless no email journal
 | 
  
    |  |   unless ($::instance_conf->get_email_journal) {
 | 
  
    |  | 
 | 
  
    |  |     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)') . ": " . $::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->order->update_attributes(intnotes => $intnotes);
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   $self->save_history('MAILED');
 | 
  
    |  | 
 | 
  
    |  |   flash_later('info', t8('The email has been sent.'));
 | 
  
    |  | 
 | 
  
    |  |   my @redirect_params = (
 | 
  
    |  |     action => 'edit',
 | 
  
    |  |     type   => $self->type,
 | 
  
    |  |     id     => $self->order->id,
 | 
  
    |  |   );
 | 
  
    |  | 
 | 
  
    |  |   $self->redirect_to(@redirect_params);
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # save the order and redirect to the frontend subroutine for a new
 | 
  
    |  | # delivery order
 | 
  
    |  | sub action_save_and_delivery_order {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   $self->save_and_redirect_to(
 | 
  
    |  |     controller => 'oe.pl',
 | 
  
    |  |     action     => 'oe_delivery_order_from_order',
 | 
  
    |  |   );
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # save the order and redirect to the frontend subroutine for a new
 | 
  
    |  | # invoice
 | 
  
    |  | sub action_save_and_invoice {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   $self->save_and_redirect_to(
 | 
  
    |  |     controller => 'oe.pl',
 | 
  
    |  |     action     => 'oe_invoice_from_order',
 | 
  
    |  |   );
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # workflow from sales order to sales quotation
 | 
  
    |  | sub action_sales_quotation {
 | 
  
    |  |   $_[0]->workflow_sales_or_request_for_quotation();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # workflow from sales order to sales quotation
 | 
  
    |  | sub action_request_for_quotation {
 | 
  
    |  |   $_[0]->workflow_sales_or_request_for_quotation();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # workflow from sales quotation to sales order
 | 
  
    |  | sub action_sales_order {
 | 
  
    |  |   $_[0]->workflow_sales_or_purchase_order();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # workflow from rfq to purchase order
 | 
  
    |  | sub action_purchase_order {
 | 
  
    |  |   $_[0]->workflow_sales_or_purchase_order();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # workflow from purchase order to ap transaction
 | 
  
    |  | sub action_save_and_ap_transaction {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   $self->save_and_redirect_to(
 | 
  
    |  |     controller => 'ap.pl',
 | 
  
    |  |     action     => 'add_from_purchase_order',
 | 
  
    |  |   );
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # set form elements in respect to a changed customer or vendor
 | 
  
    |  | #
 | 
  
    |  | # This action is called on an change of the customer/vendor picker.
 | 
  
    |  | sub action_customer_vendor_changed {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   setup_order_from_cv($self->order);
 | 
  
    |  | 
 | 
  
    |  |   my $cv_method = $self->cv;
 | 
  
    |  | 
 | 
  
    |  |   if ($self->order->$cv_method->contacts && scalar @{ $self->order->$cv_method->contacts } > 0) {
 | 
  
    |  |     $self->js->show('#cp_row');
 | 
  
    |  |   } else {
 | 
  
    |  |     $self->js->hide('#cp_row');
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   if ($self->order->$cv_method->shipto && scalar @{ $self->order->$cv_method->shipto } > 0) {
 | 
  
    |  |     $self->js->show('#shipto_selection');
 | 
  
    |  |   } else {
 | 
  
    |  |     $self->js->hide('#shipto_selection');
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   $self->js->val( '#order_salesman_id',      $self->order->salesman_id)        if $self->order->is_sales;
 | 
  
    |  | 
 | 
  
    |  |   $self->js
 | 
  
    |  |     ->replaceWith('#order_cp_id',            $self->build_contact_select)
 | 
  
    |  |     ->replaceWith('#order_shipto_id',        $self->build_shipto_select)
 | 
  
    |  |     ->replaceWith('#shipto_inputs  ',        $self->build_shipto_inputs)
 | 
  
    |  |     ->replaceWith('#business_info_row',      $self->build_business_info_row)
 | 
  
    |  |     ->val(        '#order_taxzone_id',       $self->order->taxzone_id)
 | 
  
    |  |     ->val(        '#order_taxincluded',      $self->order->taxincluded)
 | 
  
    |  |     ->val(        '#order_currency_id',      $self->order->currency_id)
 | 
  
    |  |     ->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(        '#order_language_id',      $self->order->$cv_method->language_id)
 | 
  
    |  |     ->focus(      '#order_' . $self->cv . '_id')
 | 
  
    |  |     ->run('kivi.DeliveryOrder.update_exchangerate');
 | 
  
    |  | 
 | 
  
    |  |   $self->js_redisplay_cvpartnumbers;
 | 
  
    |  |   $self->js->render();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # open the dialog for customer/vendor details
 | 
  
    |  | sub action_show_customer_vendor_details_dialog {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $is_customer = 'customer' eq $::form->{vc};
 | 
  
    |  |   my $cv;
 | 
  
    |  |   if ($is_customer) {
 | 
  
    |  |     $cv = SL::DB::Customer->new(id => $::form->{vc_id})->load;
 | 
  
    |  |   } else {
 | 
  
    |  |     $cv = SL::DB::Vendor->new(id => $::form->{vc_id})->load;
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   my %details = map { $_ => $cv->$_ } @{$cv->meta->columns};
 | 
  
    |  |   $details{discount_as_percent} = $cv->discount_as_percent;
 | 
  
    |  |   $details{creditlimt}          = $cv->creditlimit_as_number;
 | 
  
    |  |   $details{business}            = $cv->business->description      if $cv->business;
 | 
  
    |  |   $details{language}            = $cv->language_obj->description  if $cv->language_obj;
 | 
  
    |  |   $details{delivery_terms}      = $cv->delivery_term->description if $cv->delivery_term;
 | 
  
    |  |   $details{payment_terms}       = $cv->payment->description       if $cv->payment;
 | 
  
    |  |   $details{pricegroup}          = $cv->pricegroup->pricegroup     if $is_customer && $cv->pricegroup;
 | 
  
    |  | 
 | 
  
    |  |   foreach my $entry (@{ $cv->shipto }) {
 | 
  
    |  |     push @{ $details{SHIPTO} },   { map { $_ => $entry->$_ } @{$entry->meta->columns} };
 | 
  
    |  |   }
 | 
  
    |  |   foreach my $entry (@{ $cv->contacts }) {
 | 
  
    |  |     push @{ $details{CONTACTS} }, { map { $_ => $entry->$_ } @{$entry->meta->columns} };
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   $_[0]->render('common/show_vc_details', { layout => 0 },
 | 
  
    |  |                 is_customer => $is_customer,
 | 
  
    |  |                 %details);
 | 
  
    |  | 
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # called if a unit in an existing item row is changed
 | 
  
    |  | sub action_unit_changed {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $idx  = first_index { $_ eq $::form->{item_id} } @{ $::form->{orderitem_ids} };
 | 
  
    |  |   my $item = $self->order->items_sorted->[$idx];
 | 
  
    |  | 
 | 
  
    |  |   my $old_unit_obj = SL::DB::Unit->new(name => $::form->{old_unit})->load;
 | 
  
    |  |   $item->sellprice($item->unit_obj->convert_to($item->sellprice, $old_unit_obj));
 | 
  
    |  | 
 | 
  
    |  |   $self->js
 | 
  
    |  |     ->run('kivi.DeliveryOrder.update_sellprice', $::form->{item_id}, $item->sellprice_as_number);
 | 
  
    |  |   $self->js_redisplay_line_values;
 | 
  
    |  |   $self->js->render();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # add an item row for a new item entered in the input row
 | 
  
    |  | sub action_add_item {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   delete $::form->{add_item}->{create_part_type};
 | 
  
    |  | 
 | 
  
    |  |   my $form_attr = $::form->{add_item};
 | 
  
    |  | 
 | 
  
    |  |   return unless $form_attr->{parts_id};
 | 
  
    |  | 
 | 
  
    |  |   my $item = new_item($self->order, $form_attr);
 | 
  
    |  | 
 | 
  
    |  |   $self->order->add_items($item);
 | 
  
    |  | 
 | 
  
    |  |   $self->get_item_cvpartnumber($item);
 | 
  
    |  | 
 | 
  
    |  |   my $item_id = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000);
 | 
  
    |  |   my $row_as_html = $self->p->render('delivery_order/tabs/_row',
 | 
  
    |  |                                      ITEM => $item,
 | 
  
    |  |                                      ID   => $item_id,
 | 
  
    |  |                                      SELF => $self,
 | 
  
    |  |                                      in_out => $self->type_data->transfer,
 | 
  
    |  |   );
 | 
  
    |  | 
 | 
  
    |  |   if ($::form->{insert_before_item_id}) {
 | 
  
    |  |     $self->js
 | 
  
    |  |       ->before ('.row_entry:has(#item_' . $::form->{insert_before_item_id} . ')', $row_as_html);
 | 
  
    |  |   } else {
 | 
  
    |  |     $self->js
 | 
  
    |  |       ->append('#row_table_id', $row_as_html);
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   if ( $item->part->is_assortment ) {
 | 
  
    |  |     $form_attr->{qty_as_number} = 1 unless $form_attr->{qty_as_number};
 | 
  
    |  |     foreach my $assortment_item ( @{$item->part->assortment_items} ) {
 | 
  
    |  |       my $attr = { parts_id => $assortment_item->parts_id,
 | 
  
    |  |                    qty      => $assortment_item->qty * $::form->parse_amount(\%::myconfig, $form_attr->{qty_as_number}), # TODO $form_attr->{unit}
 | 
  
    |  |                    unit     => $assortment_item->unit,
 | 
  
    |  |                    description => $assortment_item->part->description,
 | 
  
    |  |                  };
 | 
  
    |  |       my $item = new_item($self->order, $attr);
 | 
  
    |  | 
 | 
  
    |  |       # set discount to 100% if item isn't supposed to be charged, overwriting any customer discount
 | 
  
    |  |       $item->discount(1) unless $assortment_item->charge;
 | 
  
    |  | 
 | 
  
    |  |       $self->order->add_items( $item );
 | 
  
    |  |       $self->get_item_cvpartnumber($item);
 | 
  
    |  |       my $item_id = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000);
 | 
  
    |  |       my $row_as_html = $self->p->render('delivery_order/tabs/_row',
 | 
  
    |  |                                          ITEM => $item,
 | 
  
    |  |                                          ID   => $item_id,
 | 
  
    |  |                                          SELF => $self,
 | 
  
    |  |       );
 | 
  
    |  |       if ($::form->{insert_before_item_id}) {
 | 
  
    |  |         $self->js
 | 
  
    |  |           ->before ('.row_entry:has(#item_' . $::form->{insert_before_item_id} . ')', $row_as_html);
 | 
  
    |  |       } else {
 | 
  
    |  |         $self->js
 | 
  
    |  |           ->append('#row_table_id', $row_as_html);
 | 
  
    |  |       }
 | 
  
    |  |     };
 | 
  
    |  |   };
 | 
  
    |  | 
 | 
  
    |  |   $self->js
 | 
  
    |  |     ->val('.add_item_input', '')
 | 
  
    |  |     ->run('kivi.DeliveryOrder.init_row_handlers')
 | 
  
    |  |     ->run('kivi.DeliveryOrder.renumber_positions')
 | 
  
    |  |     ->focus('#add_item_parts_id_name');
 | 
  
    |  | 
 | 
  
    |  |   $self->js->run('kivi.DeliveryOrder.row_table_scroll_down') if !$::form->{insert_before_item_id};
 | 
  
    |  | 
 | 
  
    |  |   $self->js->render();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # add item rows for multiple items at once
 | 
  
    |  | sub action_add_multi_items {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my @form_attr = grep { $_->{qty_as_number} } @{ $::form->{add_items} };
 | 
  
    |  |   return $self->js->render() unless scalar @form_attr;
 | 
  
    |  | 
 | 
  
    |  |   my @items;
 | 
  
    |  |   foreach my $attr (@form_attr) {
 | 
  
    |  |     my $item = new_item($self->order, $attr);
 | 
  
    |  |     push @items, $item;
 | 
  
    |  |     if ( $item->part->is_assortment ) {
 | 
  
    |  |       foreach my $assortment_item ( @{$item->part->assortment_items} ) {
 | 
  
    |  |         my $attr = { parts_id => $assortment_item->parts_id,
 | 
  
    |  |                      qty      => $assortment_item->qty * $item->qty, # TODO $form_attr->{unit}
 | 
  
    |  |                      unit     => $assortment_item->unit,
 | 
  
    |  |                      description => $assortment_item->part->description,
 | 
  
    |  |                    };
 | 
  
    |  |         my $item = new_item($self->order, $attr);
 | 
  
    |  | 
 | 
  
    |  |         # set discount to 100% if item isn't supposed to be charged, overwriting any customer discount
 | 
  
    |  |         $item->discount(1) unless $assortment_item->charge;
 | 
  
    |  |         push @items, $item;
 | 
  
    |  |       }
 | 
  
    |  |     }
 | 
  
    |  |   }
 | 
  
    |  |   $self->order->add_items(@items);
 | 
  
    |  | 
 | 
  
    |  |   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('delivery_order/tabs/_row',
 | 
  
    |  |                                        ITEM => $item,
 | 
  
    |  |                                        ID   => $item_id,
 | 
  
    |  |                                        SELF => $self,
 | 
  
    |  |                                        in_out => $self->type_data->transfer,
 | 
  
    |  |     );
 | 
  
    |  | 
 | 
  
    |  |     if ($::form->{insert_before_item_id}) {
 | 
  
    |  |       $self->js
 | 
  
    |  |         ->before ('.row_entry:has(#item_' . $::form->{insert_before_item_id} . ')', $row_as_html);
 | 
  
    |  |     } else {
 | 
  
    |  |       $self->js
 | 
  
    |  |         ->append('#row_table_id', $row_as_html);
 | 
  
    |  |     }
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   $self->js
 | 
  
    |  |     ->run('kivi.Part.close_picker_dialogs')
 | 
  
    |  |     ->run('kivi.DeliveryOrder.init_row_handlers')
 | 
  
    |  |     ->run('kivi.DeliveryOrder.renumber_positions')
 | 
  
    |  |     ->focus('#add_item_parts_id_name');
 | 
  
    |  | 
 | 
  
    |  |   $self->js->run('kivi.DeliveryOrder.row_table_scroll_down') if !$::form->{insert_before_item_id};
 | 
  
    |  | 
 | 
  
    |  |   $self->js->render();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub action_update_exchangerate {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $data = {
 | 
  
    |  |     is_standard   => $self->order->currency_id == $::instance_conf->get_currency_id,
 | 
  
    |  |     currency_name => $self->order->currency->name,
 | 
  
    |  |   };
 | 
  
    |  | 
 | 
  
    |  |   $self->render(\SL::JSON::to_json($data), { type => 'json', process => 0 });
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # redisplay item rows if they are sorted by an attribute
 | 
  
    |  | 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 },
 | 
  
    |  |     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}) {
 | 
  
    |  |     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 {
 | 
  
    |  |     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.DeliveryOrder.redisplay_items', \@to_sort)
 | 
  
    |  |     ->render;
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # show the popup to choose a price/discount source
 | 
  
    |  | sub action_price_popup {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $idx  = first_index { $_ eq $::form->{item_id} } @{ $::form->{orderitem_ids} };
 | 
  
    |  |   my $item = $self->order->items_sorted->[$idx];
 | 
  
    |  | 
 | 
  
    |  |   $self->render_price_dialog($item);
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # save the order in a session variable and redirect to the part controller
 | 
  
    |  | sub action_create_part {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $previousform = $::auth->save_form_in_session(non_scalars => 1);
 | 
  
    |  | 
 | 
  
    |  |   my $callback     = $self->url_for(
 | 
  
    |  |     action       => 'return_from_create_part',
 | 
  
    |  |     type         => $self->type, # type is needed for check_auth on return
 | 
  
    |  |     previousform => $previousform,
 | 
  
    |  |   );
 | 
  
    |  | 
 | 
  
    |  |   flash_later('info', t8('You are adding a new part while you are editing another document. You will be redirected to your document when saving the new part or aborting this form.'));
 | 
  
    |  | 
 | 
  
    |  |   my @redirect_params = (
 | 
  
    |  |     controller    => 'Part',
 | 
  
    |  |     action        => 'add',
 | 
  
    |  |     part_type     => $::form->{add_item}->{create_part_type},
 | 
  
    |  |     callback      => $callback,
 | 
  
    |  |     inline_create => 1,
 | 
  
    |  |   );
 | 
  
    |  | 
 | 
  
    |  |   $self->redirect_to(@redirect_params);
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub action_return_from_create_part {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   $self->{created_part} = SL::DB::Part->new(id => delete $::form->{new_parts_id})->load if $::form->{new_parts_id};
 | 
  
    |  | 
 | 
  
    |  |   $::auth->restore_form_from_session(delete $::form->{previousform});
 | 
  
    |  | 
 | 
  
    |  |   # 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);
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   $self->get_unalterable_data();
 | 
  
    |  |   $self->pre_render();
 | 
  
    |  | 
 | 
  
    |  |   # 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->render(
 | 
  
    |  |     'delivery_order/form',
 | 
  
    |  |     title => $self->get_title_for('edit'),
 | 
  
    |  |     %{$self->{template_args}}
 | 
  
    |  |   );
 | 
  
    |  | 
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub action_stock_in_out_dialog {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $part    = SL::DB::Part->load_cached($::form->{parts_id}) or die "need parts_id";
 | 
  
    |  |   my $unit    = SL::DB::Unit->load_cached($::form->{unit}) or die "need unit";
 | 
  
    |  |   my $stock   = $::form->{stock};
 | 
  
    |  |   my $row     = $::form->{row};
 | 
  
    |  |   my $item_id = $::form->{item_id};
 | 
  
    |  |   my $qty     = _parse_number($::form->{qty_as_number});
 | 
  
    |  | 
 | 
  
    |  |   my $inout = $self->type_data->transfer;
 | 
  
    |  | 
 | 
  
    |  |   my @contents   = DO->get_item_availability(parts_id => $part->id);
 | 
  
    |  |   my $stock_info = DO->unpack_stock_information(packed => $stock);
 | 
  
    |  | 
 | 
  
    |  |   $self->merge_stock_data($stock_info, \@contents, $part, $unit);
 | 
  
    |  | 
 | 
  
    |  |   $self->render("delivery_order/stock_dialog", { layout => 0 },
 | 
  
    |  |     WHCONTENTS => \@contents,
 | 
  
    |  |     STOCK_INFO => $stock_info,
 | 
  
    |  |     WAREHOUSES => SL::DB::Manager::Warehouse->get_all(with_objects=> ["bins",]),
 | 
  
    |  |     part       => $part,
 | 
  
    |  |     do_qty     => $qty,
 | 
  
    |  |     do_unit    => $unit->unit,
 | 
  
    |  |     delivered  => $self->order->delivered,
 | 
  
    |  |     row        => $row,
 | 
  
    |  |     item_id    => $item_id,
 | 
  
    |  |     in_out     => $inout,
 | 
  
    |  |   );
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub action_add_stock_in_line_to_dialog {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $do_qty       = _parse_number($::form->{do_qty});
 | 
  
    |  |   my $qty_sum   = $::form->{qty_sum};
 | 
  
    |  |   my $row_count = $::form->{row_count};
 | 
  
    |  |   my $part      = SL::DB::Part->load_cached($::form->{parts_id}) or die "need parts_id";
 | 
  
    |  | 
 | 
  
    |  |   my $row_as_html = $self->p->render('delivery_order/stock_dialog/_stock_in_new_row',
 | 
  
    |  |     WAREHOUSES => SL::DB::Manager::Warehouse->get_all(with_objects=> ["bins",]),
 | 
  
    |  |     PART => $part,
 | 
  
    |  |     pos  => $row_count + 1,
 | 
  
    |  |     remaining_qty => $do_qty - $qty_sum,
 | 
  
    |  |   );
 | 
  
    |  | 
 | 
  
    |  |   $self->js->append('#stock-in-out-table', $row_as_html)->render();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub action_update_stock_information {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $stock_info = $::form->{stock_info};
 | 
  
    |  |   my $unit = $::form->{unit};
 | 
  
    |  |   my $yaml = SL::YAML::Dump($stock_info);
 | 
  
    |  |   my $stock_qty = $self->calculate_stock_in_out_from_stock_info($unit, $stock_info);
 | 
  
    |  | 
 | 
  
    |  |   my $response = {
 | 
  
    |  |     stock_info => $yaml,
 | 
  
    |  |     stock_qty => $stock_qty,
 | 
  
    |  |   };
 | 
  
    |  |   $self->render(\ SL::JSON::to_json($response), { layout => 0, type => 'json', process => 0 });
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub merge_stock_data {
 | 
  
    |  |   my ($self, $stock_info, $contents, $part, $unit) = @_;
 | 
  
    |  |   # TODO rewrite to mapping
 | 
  
    |  | 
 | 
  
    |  |   if (!$self->order->delivered) {
 | 
  
    |  |     for my $row (@$contents) {
 | 
  
    |  |       # row here is in parts units. stock is in item units
 | 
  
    |  |       $row->{available_qty} = _format_number($part->unit_obj->convert_to($row->{qty}, $unit));
 | 
  
    |  | 
 | 
  
    |  |       for my $sinfo (@{ $stock_info }) {
 | 
  
    |  |         next if $row->{bin_id}       != $sinfo->{bin_id} ||
 | 
  
    |  |                 $row->{warehouse_id} != $sinfo->{warehouse_id} ||
 | 
  
    |  |                 $row->{chargenumber} ne $sinfo->{chargenumber} ||
 | 
  
    |  |                 $row->{bestbefore}   ne $sinfo->{bestbefore};
 | 
  
    |  | 
 | 
  
    |  |         $row->{"stock_$_"} = $sinfo->{$_}
 | 
  
    |  |           for qw(qty unit error delivery_order_items_stock_id);
 | 
  
    |  |       }
 | 
  
    |  |     }
 | 
  
    |  | 
 | 
  
    |  |   } else {
 | 
  
    |  |     for my $sinfo (@{ $stock_info }) {
 | 
  
    |  |       my $bin = SL::DB::Bin->load_cached($sinfo->{bin_id});
 | 
  
    |  |       $sinfo->{warehousedescription} = $bin->warehouse->description;
 | 
  
    |  |       $sinfo->{bindescription}       = $bin->description;
 | 
  
    |  |       map { $sinfo->{"stock_$_"}      = $sinfo->{$_} } qw(qty unit);
 | 
  
    |  |     }
 | 
  
    |  |   }
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # load the second row for one or more items
 | 
  
    |  | #
 | 
  
    |  | # This action gets the html code for all items second rows by rendering a template for
 | 
  
    |  | # the second row and sets the html code via client js.
 | 
  
    |  | sub action_load_second_rows {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   foreach my $item_id (@{ $::form->{item_ids} }) {
 | 
  
    |  |     my $idx  = first_index { $_ eq $item_id } @{ $::form->{orderitem_ids} };
 | 
  
    |  |     my $item = $self->order->items_sorted->[$idx];
 | 
  
    |  | 
 | 
  
    |  |     $self->js_load_second_row($item, $item_id, 0);
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   $self->js->run('kivi.DeliveryOrder.init_row_handlers') if $self->order->is_sales; # for lastcosts change-callback
 | 
  
    |  | 
 | 
  
    |  |   $self->js->render();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # update description, notes and sellprice from master data
 | 
  
    |  | sub action_update_row_from_master_data {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   foreach my $item_id (@{ $::form->{item_ids} }) {
 | 
  
    |  |     my $idx   = first_index { $_ eq $item_id } @{ $::form->{orderitem_ids} };
 | 
  
    |  |     my $item  = $self->order->items_sorted->[$idx];
 | 
  
    |  |     my $texts = get_part_texts($item->part, $self->order->language_id);
 | 
  
    |  | 
 | 
  
    |  |     $item->description($texts->{description});
 | 
  
    |  |     $item->longdescription($texts->{longdescription});
 | 
  
    |  | 
 | 
  
    |  |     my $price_source = SL::PriceSource->new(record_item => $item, record => $self->order);
 | 
  
    |  | 
 | 
  
    |  |     my $price_src;
 | 
  
    |  |     if ($item->part->is_assortment) {
 | 
  
    |  |     # add assortment items with price 0, as the components carry the price
 | 
  
    |  |       $price_src = $price_source->price_from_source("");
 | 
  
    |  |       $price_src->price(0);
 | 
  
    |  |     } else {
 | 
  
    |  |       $price_src = $price_source->best_price
 | 
  
    |  |                  ? $price_source->best_price
 | 
  
    |  |                  : $price_source->price_from_source("");
 | 
  
    |  |       $price_src->price($::form->round_amount($price_src->price / $self->order->exchangerate, 5)) if $self->order->exchangerate;
 | 
  
    |  |       $price_src->price(0) if !$price_source->best_price;
 | 
  
    |  |     }
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  |     $item->sellprice($price_src->price);
 | 
  
    |  |     $item->active_price_source($price_src);
 | 
  
    |  | 
 | 
  
    |  |     $self->js
 | 
  
    |  |       ->run('kivi.DeliveryOrder.update_sellprice', $item_id, $item->sellprice_as_number)
 | 
  
    |  |       ->html('.row_entry:has(#item_' . $item_id . ') [name = "partnumber"] a', $item->part->partnumber)
 | 
  
    |  |       ->val ('.row_entry:has(#item_' . $item_id . ') [name = "order.orderitems[].description"]', $item->description)
 | 
  
    |  |       ->val ('.row_entry:has(#item_' . $item_id . ') [name = "order.orderitems[].longdescription"]', $item->longdescription);
 | 
  
    |  | 
 | 
  
    |  |     if ($self->search_cvpartnumber) {
 | 
  
    |  |       $self->get_item_cvpartnumber($item);
 | 
  
    |  |       $self->js->html('.row_entry:has(#item_' . $item_id . ') [name = "cvpartnumber"]', $item->{cvpartnumber});
 | 
  
    |  |     }
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   $self->js_redisplay_line_values;
 | 
  
    |  | 
 | 
  
    |  |   $self->js->render();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub action_transfer_stock {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   if ($self->order->delivered) {
 | 
  
    |  |     return $self->js->flash("error", t8('The parts for this order have already been transferred'))->render;
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   my $inout = $self->type_data->properties('transfer');
 | 
  
    |  | 
 | 
  
    |  |   my $errors = $self->save;
 | 
  
    |  | 
 | 
  
    |  |   if (@$errors) {
 | 
  
    |  |     $self->js->flash('error', $_) for @$errors;
 | 
  
    |  |     return $self->js->render;
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   my $order = $self->order;
 | 
  
    |  | 
 | 
  
    |  |   # TODO move to type data
 | 
  
    |  |   my $trans_type = $inout eq 'in'
 | 
  
    |  |     ? SL::DB::Manager::TransferType->find_by(direction => "in", description => "stock")
 | 
  
    |  |     : SL::DB::Manager::TransferType->find_by(direction => "out", description => "shipped");
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  |   my @transfer_requests;
 | 
  
    |  | 
 | 
  
    |  |   for my $item (@{ $order->items_sorted }) {
 | 
  
    |  |     for my $stock (@{ $item->delivery_order_stock_entries }) {
 | 
  
    |  |       my $transfer = SL::DB::Inventory->new_from($stock);
 | 
  
    |  |       $transfer->trans_type($trans_type);
 | 
  
    |  |       $transfer->oe_id($order->id);
 | 
  
    |  |       $transfer->qty($transfer->qty * -1) if $inout eq 'out';
 | 
  
    |  |       $transfer->qty($transfer->qty * 1) if $inout eq 'in';
 | 
  
    |  | 
 | 
  
    |  |       push @transfer_requests, $transfer if defined $transfer->qty && $transfer->qty != 0;
 | 
  
    |  |     };
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   if (!@transfer_requests) {
 | 
  
    |  |     return $self->js->flash("error", t8("No stock to transfer"))->render;
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   SL::DB->client->with_transaction(sub {
 | 
  
    |  |     $_->save for @transfer_requests;
 | 
  
    |  |     $self->order->update_attributes(delivered => 1, closed => 1);
 | 
  
    |  |   });
 | 
  
    |  | 
 | 
  
    |  |   $self->js
 | 
  
    |  |     ->flash("info", t8("Stock transfered"))
 | 
  
    |  |     ->run('kivi.ActionBar.setDisabled', '#transfer_out_action', t8('The parts for this order have already been transferred'))
 | 
  
    |  |     ->run('kivi.ActionBar.setDisabled', '#transfer_in_action', t8('The parts for this order have already been transferred'))
 | 
  
    |  |     ->run('kivi.ActionBar.setDisabled', '#delete_action', t8('The parts for this order have already been transferred'))
 | 
  
    |  |     ->run('kivi.ActionBar.setEnabled', '#undo_transfer_action', t8('The parts for this order have already been transferred'))
 | 
  
    |  |     ->replaceWith('#data-status-line', delivery_order_status_line($self->order))
 | 
  
    |  |     ->render;
 | 
  
    |  | 
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub action_undo_transfers {
 | 
  
    |  |   my ( $self ) = @_;
 | 
  
    |  | 
 | 
  
    |  |   SL::DB->client->with_transaction(sub {
 | 
  
    |  |     foreach my $item (@{$self->order->orderitems}) {
 | 
  
    |  |       foreach my $inv_item (@{ $item->delivery_order_stock_entries}) {
 | 
  
    |  |         $inv_item->inventory->delete;
 | 
  
    |  |         $inv_item->delete;
 | 
  
    |  |       }
 | 
  
    |  |     }
 | 
  
    |  |     $self->order->update_attributes(delivered => 0);
 | 
  
    |  |     $self->order->update_attributes(closed => 0);
 | 
  
    |  |   });
 | 
  
    |  | 
 | 
  
    |  |   flash_later('info', t8("Transfer undone"));
 | 
  
    |  |   my @redirect_params = (
 | 
  
    |  |     action => 'edit',
 | 
  
    |  |     type   => $self->type,
 | 
  
    |  |     id     => $self->order->id,
 | 
  
    |  |   );
 | 
  
    |  | 
 | 
  
    |  |   $self->redirect_to(@redirect_params);
 | 
  
    |  | 
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub js_load_second_row {
 | 
  
    |  |   my ($self, $item, $item_id, $do_parse) = @_;
 | 
  
    |  | 
 | 
  
    |  |   if ($do_parse) {
 | 
  
    |  |     # Parse values from form (they are formated while rendering (template)).
 | 
  
    |  |     # Workaround to pre-parse number-cvars (parse_custom_variable_values does not parse number values).
 | 
  
    |  |     # This parsing is not necessary at all, if we assure that the second row/cvars are only loaded once.
 | 
  
    |  |     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;
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   my $row_as_html = $self->p->render('delivery_order/tabs/_second_row', ITEM => $item, TYPE => $self->type);
 | 
  
    |  | 
 | 
  
    |  |   $self->js
 | 
  
    |  |     ->html('#second_row_' . $item_id, $row_as_html)
 | 
  
    |  |     ->data('#second_row_' . $item_id, 'loaded', 1);
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub js_redisplay_line_values {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $is_sales = $self->order->is_sales;
 | 
  
    |  | 
 | 
  
    |  |   # sales orders with margins
 | 
  
    |  |   my @data;
 | 
  
    |  |   if ($is_sales) {
 | 
  
    |  |     @data = map {
 | 
  
    |  |       [
 | 
  
    |  |        $::form->format_amount(\%::myconfig, $_->{linetotal},     2, 0),
 | 
  
    |  |        $::form->format_amount(\%::myconfig, $_->{marge_total},   2, 0),
 | 
  
    |  |        $::form->format_amount(\%::myconfig, $_->{marge_percent}, 2, 0),
 | 
  
    |  |       ]} @{ $self->order->items_sorted };
 | 
  
    |  |   } else {
 | 
  
    |  |     @data = map {
 | 
  
    |  |       [
 | 
  
    |  |        $::form->format_amount(\%::myconfig, $_->{linetotal},     2, 0),
 | 
  
    |  |       ]} @{ $self->order->items_sorted };
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   $self->js
 | 
  
    |  |     ->run('kivi.DeliveryOrder.redisplay_line_values', $is_sales, \@data);
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | 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.DeliveryOrder.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('#converted_from_reclamation_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[+]"]', '');
 | 
  
    |  |   $self->js->val('[name="converted_from_reclamation_items_ids[+]"]', '');
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | #
 | 
  
    |  | # helpers
 | 
  
    |  | #
 | 
  
    |  | 
 | 
  
    |  | sub init_type {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   if (none { $::form->{type} eq $_ } @{$self->valid_types}) {
 | 
  
    |  |     die "Not a valid type for delivery order";
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   $self->type($::form->{type});
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub init_cv {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   return $self->type_data->customervendor;
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | 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_show_update_button {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   !!SL::Helper::UserPreferences::UpdatePositions->new()->get_show_update_button();
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub init_p {
 | 
  
    |  |   SL::Presenter->get;
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub init_order {
 | 
  
    |  |   $_[0]->make_order;
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub init_all_price_factors {
 | 
  
    |  |   SL::DB::Manager::PriceFactor->get_all;
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub init_part_picker_classification_ids {
 | 
  
    |  |   my ($self)    = @_;
 | 
  
    |  | 
 | 
  
    |  |   return [ map { $_->id } @{ SL::DB::Manager::PartClassification->get_all(where => $self->type_data->part_classification_query) } ];
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub check_auth {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   $::auth->assert($self->type_data->access('view') || 'DOES_NOT_EXIST');
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub check_auth_for_edit {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   $::auth->assert($self->type_data->access('edit') || 'DOES_NOT_EXIST');
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # build the selection box for contacts
 | 
  
    |  | #
 | 
  
    |  | # Needed, if customer/vendor changed.
 | 
  
    |  | sub build_contact_select {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   select_tag('order.cp_id', [ $self->order->{$self->cv}->contacts ],
 | 
  
    |  |     value_key  => 'cp_id',
 | 
  
    |  |     title_key  => 'full_name_dep',
 | 
  
    |  |     default    => $self->order->cp_id,
 | 
  
    |  |     with_empty => 1,
 | 
  
    |  |     style      => 'width: 300px',
 | 
  
    |  |   );
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # build the selection box for shiptos
 | 
  
    |  | #
 | 
  
    |  | # Needed, if customer/vendor changed.
 | 
  
    |  | sub build_shipto_select {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   select_tag('order.shipto_id',
 | 
  
    |  |              [ {displayable_id => t8("No/individual shipping address"), shipto_id => ''}, $self->order->{$self->cv}->shipto ],
 | 
  
    |  |              value_key  => 'shipto_id',
 | 
  
    |  |              title_key  => 'displayable_id',
 | 
  
    |  |              default    => $self->order->shipto_id,
 | 
  
    |  |              with_empty => 0,
 | 
  
    |  |              style      => 'width: 300px',
 | 
  
    |  |   );
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # build the inputs for the cusom shipto dialog
 | 
  
    |  | #
 | 
  
    |  | # Needed, if customer/vendor changed.
 | 
  
    |  | sub build_shipto_inputs {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   my $content = $self->p->render('common/_ship_to_dialog',
 | 
  
    |  |                                  vc_obj      => $self->order->customervendor,
 | 
  
    |  |                                  cs_obj      => $self->order->custom_shipto,
 | 
  
    |  |                                  cvars       => $self->order->custom_shipto->cvars_by_config,
 | 
  
    |  |                                  id_selector => '#order_shipto_id');
 | 
  
    |  | 
 | 
  
    |  |   div_tag($content, id => 'shipto_inputs');
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # render the info line for business
 | 
  
    |  | #
 | 
  
    |  | # Needed, if customer/vendor changed.
 | 
  
    |  | sub build_business_info_row
 | 
  
    |  | {
 | 
  
    |  |   $_[0]->p->render('delivery_order/tabs/_business_info_row', SELF => $_[0]);
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  | sub load_order {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   return if !$::form->{id};
 | 
  
    |  | 
 | 
  
    |  |   $self->order(SL::DB::DeliveryOrder->new(id => $::form->{id})->load);
 | 
  
    |  | 
 | 
  
    |  |   # Add an empty custom shipto to the order, so that the dialog can render the cvar inputs.
 | 
  
    |  |   # You need a custom shipto object to call cvars_by_config to get the cvars.
 | 
  
    |  |   $self->order->custom_shipto(SL::DB::Shipto->new(module => 'OE', custom_variables => [])) if !$self->order->custom_shipto;
 | 
  
    |  | 
 | 
  
    |  |   $self->order->prepare_stock_info($_) for $self->order->items;
 | 
  
    |  | 
 | 
  
    |  |   return $self->order;
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # load or create a new order 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.
 | 
  
    |  | sub make_order {
 | 
  
    |  |   my ($self) = @_;
 | 
  
    |  | 
 | 
  
    |  |   # add_items adds items to an order with no items for saving, but they cannot
 | 
  
    |  |   # be retrieved via items until the order is saved. Adding empty items to new
 | 
  
    |  |   # order here solves this problem.
 | 
  
    |  |   my $order;
 | 
  
    |  |   $order   = SL::DB::DeliveryOrder->new(id => $::form->{id})->load(with => [ 'orderitems', 'orderitems.part' ]) if $::form->{id};
 | 
  
    |  |   $order ||= SL::DB::DeliveryOrder->new(orderitems  => [], currency_id => $::instance_conf->get_currency_id(), order_type => $self->type_data->validate($::form->{type}));
 | 
  
    |  | 
 | 
  
    |  |   my $cv_id_method = $self->cv . '_id';
 | 
  
    |  |   if (!$::form->{id} && $::form->{$cv_id_method}) {
 | 
  
    |  |     $order->$cv_id_method($::form->{$cv_id_method});
 | 
  
    |  |     setup_order_from_cv($order);
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   my $form_orderitems                  = delete $::form->{order}->{orderitems};
 | 
  
    |  | 
 | 
  
    |  |   $order->assign_attributes(%{$::form->{order}});
 | 
  
    |  | 
 | 
  
    |  |   $self->setup_custom_shipto_from_form($order, $::form);
 | 
  
    |  | 
 | 
  
    |  |   # remove deleted items
 | 
  
    |  |   $self->item_ids_to_delete([]);
 | 
  
    |  |   foreach my $idx (reverse 0..$#{$order->orderitems}) {
 | 
  
    |  |     my $item = $order->orderitems->[$idx];
 | 
  
    |  |     if (none { $item->id == $_->{id} } @{$form_orderitems}) {
 | 
  
    |  |       splice @{$order->orderitems}, $idx, 1;
 | 
  
    |  |       push @{$self->item_ids_to_delete}, $item->id;
 | 
  
    |  |     }
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   my @items;
 | 
  
    |  |   my $pos = 1;
 | 
  
    |  |   foreach my $form_attr (@{$form_orderitems}) {
 | 
  
    |  |     my $item = make_item($order, $form_attr);
 | 
  
    |  |     $item->position($pos);
 | 
  
    |  |     push @items, $item;
 | 
  
    |  |     $pos++;
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   $order->add_items(grep {!$_->id} @items);
 | 
  
    |  | 
 | 
  
    |  |   return $order;
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | # create or update items from form
 | 
  
    |  | #
 | 
  
    |  | # Make item objects from form values. For items already existing read from db.
 | 
  
    |  | # Create a new item else. And assign attributes |