+ $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.Order.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);
+}
+
+# get the longdescription for an item if the dialog to enter/change the
+# longdescription was opened and the longdescription is empty
+#
+# If this item is new, get the longdescription from Part.
+# Otherwise get it from OrderItem.
+sub action_get_item_longdescription {
+ my $longdescription;
+
+ if ($::form->{item_id}) {
+ $longdescription = SL::DB::OrderItem->new(id => $::form->{item_id})->load->longdescription;
+ } elsif ($::form->{parts_id}) {
+ $longdescription = SL::DB::Part->new(id => $::form->{parts_id})->load->notes;
+ }
+ $_[0]->render(\ $longdescription, { type => 'text' });
+}
+
+# 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) = @_;
+
+ $self->recalc() if $self->order->is_sales; # for margin calculation
+
+ 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.Order.init_row_handlers') if $self->order->is_sales; # for lastcosts change-callback
+
+ $self->js->render();
+}
+
+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('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.Order.redisplay_line_values', $is_sales, \@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');
+ }
+
+ if ($self->order->is_sales) {
+ my $is_neg = $self->order->marge_total < 0;
+ $self->js
+ ->html('#marge_total_id', $::form->format_amount(\%::myconfig, $self->order->marge_total, 2))
+ ->html('#marge_percent_id', $::form->format_amount(\%::myconfig, $self->order->marge_percent, 2))
+ ->action_if( $is_neg, 'addClass', '#marge_total_id', 'plus0')
+ ->action_if( $is_neg, 'addClass', '#marge_percent_id', 'plus0')
+ ->action_if( $is_neg, 'addClass', '#marge_percent_sign_id', 'plus0')
+ ->action_if(!$is_neg, 'removeClass', '#marge_total_id', 'plus0')
+ ->action_if(!$is_neg, 'removeClass', '#marge_percent_id', 'plus0')
+ ->action_if(!$is_neg, 'removeClass', '#marge_percent_sign_id', 'plus0');
+ }
+
+ $self->js
+ ->html('#netamount_id', $::form->format_amount(\%::myconfig, $self->order->netamount, -2))
+ ->html('#amount_id', $::form->format_amount(\%::myconfig, $self->order->amount, -2))
+ ->remove('.tax_row')
+ ->insertBefore($self->build_tax_rows, '#amount_row_id');
+}
+
+sub js_redisplay_cvpartnumbers {
+ my ($self) = @_;
+
+ $self->get_item_cvpartnumber($_) for @{$self->order->items_sorted};
+
+ my @data = map {[$_->{cvpartnumber}]} @{ $self->order->items_sorted };
+
+ $self->js
+ ->run('kivi.Order.redisplay_cvpartnumbers', \@data);
+}
+
+sub js_reset_order_and_item_ids_after_save {
+ my ($self) = @_;
+
+ $self->js
+ ->val('#id', $self->order->id)
+ ->val('#converted_from_oe_id', '')
+ ->val('#order_' . $self->nr_key(), $self->order->number);
+
+ my $idx = 0;
+ foreach my $form_item_id (@{ $::form->{orderitem_ids} }) {
+ next if !$self->order->items_sorted->[$idx]->id;
+ next if $form_item_id !~ m{^new};
+ $self->js
+ ->val ('[name="orderitem_ids[+]"][value="' . $form_item_id . '"]', $self->order->items_sorted->[$idx]->id)
+ ->val ('#item_' . $form_item_id, $self->order->items_sorted->[$idx]->id)
+ ->attr('#item_' . $form_item_id, "id", 'item_' . $self->order->items_sorted->[$idx]->id);
+ } continue {
+ $idx++;
+ }
+ $self->js->val('[name="converted_from_orderitems_ids[+]"]', '');
+}
+
+#
+# helpers
+#
+
+sub init_valid_types {
+ [ sales_order_type(), purchase_order_type(), sales_quotation_type(), request_quotation_type() ];
+}
+
+sub init_type {
+ my ($self) = @_;
+
+ if (none { $::form->{type} eq $_ } @{$self->valid_types}) {
+ die "Not a valid type for order";
+ }
+
+ $self->type($::form->{type});
+}
+
+sub init_cv {
+ my ($self) = @_;
+
+ my $cv = (any { $self->type eq $_ } (sales_order_type(), sales_quotation_type())) ? 'customer'
+ : (any { $self->type eq $_ } (purchase_order_type(), request_quotation_type())) ? 'vendor'
+ : die "Not a valid type for order";
+
+ return $cv;
+}
+
+sub init_search_cvpartnumber {
+ my ($self) = @_;
+
+ my $user_prefs = SL::Helper::UserPreferences::PartPickerSearch->new();
+ my $search_cvpartnumber;
+ $search_cvpartnumber = !!$user_prefs->get_sales_search_customer_partnumber() if $self->cv eq 'customer';
+ $search_cvpartnumber = !!$user_prefs->get_purchase_search_makemodel() if $self->cv eq 'vendor';
+
+ return $search_cvpartnumber;
+}