Inventory.pm - Whitespace entfernt
[kivitendo-erp.git] / SL / Controller / Order.pm
index 508fdfe..78e9e54 100644 (file)
@@ -93,7 +93,7 @@ sub action_delete {
 
   flash_later('info', $::locale->text('The order has been deleted'));
   my @redirect_params = (
 
   flash_later('info', $::locale->text('The order has been deleted'));
   my @redirect_params = (
-    action => 'edit',
+    action => 'add',
     type   => $self->type,
   );
 
     type   => $self->type,
   );
 
@@ -326,7 +326,7 @@ sub action_save_and_delivery_order {
   $self->redirect_to(@redirect_params);
 }
 
   $self->redirect_to(@redirect_params);
 }
 
-# set form elements in respect of a changed customer or vendor
+# 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 {
 #
 # This action is called on an change of the customer/vendor picker.
 sub action_customer_vendor_changed {
@@ -401,6 +401,7 @@ sub action_add_item {
   return unless $form_attr->{parts_id};
 
   my $item = _new_item($self->order, $form_attr);
   return unless $form_attr->{parts_id};
 
   my $item = _new_item($self->order, $form_attr);
+
   $self->order->add_items($item);
 
   $self->_recalc();
   $self->order->add_items($item);
 
   $self->_recalc();
@@ -413,7 +414,35 @@ sub action_add_item {
   );
 
   $self->js
   );
 
   $self->js
-    ->append('#row_table_id', $row_as_html)
+    ->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->_recalc();
+      my $item_id = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000);
+      my $row_as_html = $self->p->render('order/tabs/_row',
+                                         ITEM              => $item,
+                                         ID                => $item_id,
+                                         ALL_PRICE_FACTORS => $self->all_price_factors
+      );
+      $self->js
+        ->append('#row_table_id', $row_as_html);
+    };
+  };
+
+  $self->js
     ->val('.add_item_input', '')
     ->run('kivi.Order.init_row_handlers')
     ->run('kivi.Order.row_table_scroll_down')
     ->val('.add_item_input', '')
     ->run('kivi.Order.init_row_handlers')
     ->run('kivi.Order.row_table_scroll_down')
@@ -452,7 +481,7 @@ sub action_multi_items_update_result {
   }
 }
 
   }
 }
 
-# add item rows for multiple items add once
+# add item rows for multiple items at once
 sub action_add_multi_items {
   my ($self) = @_;
 
 sub action_add_multi_items {
   my ($self) = @_;
 
@@ -461,7 +490,22 @@ sub action_add_multi_items {
 
   my @items;
   foreach my $attr (@form_attr) {
 
   my @items;
   foreach my $attr (@form_attr) {
-    push @items, _new_item($self->order, $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, $assortment_item;
+      }
+    }
   }
   $self->order->add_items(@items);
 
   }
   $self->order->add_items(@items);
 
@@ -500,7 +544,7 @@ sub action_recalc_amounts_and_taxes {
   $self->js->render();
 }
 
   $self->js->render();
 }
 
-# redisplay item rows if the are sorted by an attribute
+# redisplay item rows if they are sorted by an attribute
 sub action_reorder_items {
   my ($self) = @_;
 
 sub action_reorder_items {
   my ($self) = @_;
 
@@ -538,7 +582,7 @@ sub action_price_popup {
 # longdescription was opened and the longdescription is empty
 #
 # If this item is new, get the longdescription from Part.
 # longdescription was opened and the longdescription is empty
 #
 # If this item is new, get the longdescription from Part.
-# Get it from OrderItem else.
+# Otherwise get it from OrderItem.
 sub action_get_item_longdescription {
   my $longdescription;
 
 sub action_get_item_longdescription {
   my $longdescription;
 
@@ -783,6 +827,8 @@ sub _make_item {
 
   $item->assign_attributes(%$attr);
   $item->longdescription($item->part->notes) if $is_new && !defined $attr->{longdescription};
 
   $item->assign_attributes(%$attr);
   $item->longdescription($item->part->notes) if $is_new && !defined $attr->{longdescription};
+  # item fields that currently can't be set in in row but are needed:
+  $item->lastcost($item->part->lastcost) if $is_new;
 
   return $item;
 }
 
   return $item;
 }
@@ -802,7 +848,11 @@ sub _new_item {
   $item->unit($part->unit) if !$item->unit;
 
   my $price_src;
   $item->unit($part->unit) if !$item->unit;
 
   my $price_src;
-  if ($item->sellprice) {
+  if ( $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);
+  } elsif ($item->sellprice) {
     $price_src = $price_source->price_from_source("");
     $price_src->price($item->sellprice);
   } else {
     $price_src = $price_source->price_from_source("");
     $price_src->price($item->sellprice);
   } else {
@@ -847,7 +897,7 @@ sub _new_item {
 
 # recalculate prices and taxes
 #
 
 # recalculate prices and taxes
 #
-# Using the PriceTaxCalclulator. Store linetotals in the item objects.
+# Using the PriceTaxCalculator. Store linetotals in the item objects.
 sub _recalc {
   my ($self) = @_;
 
 sub _recalc {
   my ($self) = @_;
 
@@ -891,9 +941,9 @@ sub _delete {
   my ($self) = @_;
 
   my $errors = [];
   my ($self) = @_;
 
   my $errors = [];
-  my $db = $self->order->db;
+  my $db     = $self->order->db;
 
 
-  $db->do_transaction(
+  $db->with_transaction(
     sub {
       my @spoolfiles = grep { $_ } map { $_->spoolfile } @{ SL::DB::Manager::Status->get_all(where => [ trans_id => $self->order->id ]) };
       $self->order->delete;
     sub {
       my @spoolfiles = grep { $_ } map { $_->spoolfile } @{ SL::DB::Manager::Status->get_all(where => [ trans_id => $self->order->id ]) };
       $self->order->delete;
@@ -913,12 +963,11 @@ sub _save {
   my ($self) = @_;
 
   my $errors = [];
   my ($self) = @_;
 
   my $errors = [];
-  my $db = $self->order->db;
+  my $db     = $self->order->db;
 
 
-  $db->do_transaction(
-    sub {
-      SL::DB::OrderItem->new(id => $_)->delete for @{$self->item_ids_to_delete};
-      $self->order->save(cascade => 1);
+  $db->with_transaction(sub {
+    SL::DB::OrderItem->new(id => $_)->delete for @{$self->item_ids_to_delete};
+    $self->order->save(cascade => 1);
   }) || push(@{$errors}, $db->error);
 
   return $errors;
   }) || push(@{$errors}, $db->error);
 
   return $errors;
@@ -1058,36 +1107,44 @@ The aim is to provide the user a better expirience and a faster flow
 of work. Also the code should be more readable, more reliable and
 better to maintain.
 
 of work. Also the code should be more readable, more reliable and
 better to maintain.
 
-=head2 key features
+=head2 Key Features
 
 
-=over 2
+=over 4
 
 =item *
 
 =item *
+
 One input row, so that input happens every time at the same place.
 
 =item *
 One input row, so that input happens every time at the same place.
 
 =item *
+
 Use of pickers where possible.
 
 =item *
 Use of pickers where possible.
 
 =item *
+
 Possibility to enter more than one item at once.
 
 =item *
 Possibility to enter more than one item at once.
 
 =item *
+
 Save order only on "save" (and "save and delivery order"-workflow). No
 Save order only on "save" (and "save and delivery order"-workflow). No
-hidden save on "print" or "email". 
+hidden save on "print" or "email".
 
 =item *
 
 =item *
+
 Item list in a scrollable area, so that the workflow buttons stay at
 the bottom.
 
 =item *
 Item list in a scrollable area, so that the workflow buttons stay at
 the bottom.
 
 =item *
+
 Reordering item rows with drag and drop is possible. Sorting item rows is
 possible (by partnumber, description, qty, sellprice and discount for now).
 
 =item *
 Reordering item rows with drag and drop is possible. Sorting item rows is
 possible (by partnumber, description, qty, sellprice and discount for now).
 
 =item *
-No "update" is necessary. All entries and calculations are managed
-with ajax-calls and the page does only reload on "save".
+
+No C<update> is necessary. All entries and calculations are managed
+with ajax-calls and the page does only reload on C<save>.
 
 =item *
 
 =item *
+
 User can see changes immediately, because of the use of java script
 and ajax.
 
 User can see changes immediately, because of the use of java script
 and ajax.
 
@@ -1095,128 +1152,151 @@ and ajax.
 
 =head1 CODE
 
 
 =head1 CODE
 
-=head2 layout
+=head2 Layout
 
 
-=over 2
+=over 4
 
 
-=item *
-SL/Controller/Order.pm: the controller
+=item * C<SL/Controller/Order.pm>
 
 
-=item *
-template/webpages/order/form.html: main form
+the controller
 
 
-=item *
-template/webpages/order/tabs/basic_data.html: main tab for basic_data
+=item * C<template/webpages/order/form.html>
 
 
-This is the only tab here for now. "linked records" and "webdav" tabs are reused
-from generic code.
+main form
 
 
-=over 3
+=item * C<template/webpages/order/tabs/basic_data.html>
 
 
-=item *
-template/webpages/order/tabs/_item_input.html: the input line for items
+Main tab for basic_data.
 
 
-=item *
-template/webpages/order/tabs/_row.html: one row for already entered items
+This is the only tab here for now. "linked records" and "webdav" tabs are
+reused from generic code.
 
 
-=item *
-template/webpages/order/tabs/_tax_row.html: displaying tax information
+=over 4
 
 
-=item *
-template/webpages/order/tabs/_multi_items_dialog.html: dialog for entering more
-than one item at once
+=item * C<template/webpages/order/tabs/_item_input.html>
 
 
-=item *
-template/webpages/order/tabs/_multi_items_result.html: results for the filter in
-the multi items dialog
+The input line for items
 
 
-=item *
-template/webpages/order/tabs/_price_sources_dialog.html: dialog for selecting
-price and discount sources
+=item * C<template/webpages/order/tabs/_row.html>
 
 
-=item *
-template/webpages/order/tabs/_email_dialog.html: email dialog
+One row for already entered items
+
+=item * C<template/webpages/order/tabs/_tax_row.html>
+
+Displaying tax information
+
+=item * C<template/webpages/order/tabs/_multi_items_dialog.html>
+
+Dialog for entering more than one item at once
+
+=item * C<template/webpages/order/tabs/_multi_items_result.html>
+
+Results for the filter in the multi items dialog
+
+=item * C<template/webpages/order/tabs/_price_sources_dialog.html>
+
+Dialog for selecting price and discount sources
+
+=item * C<template/webpages/order/tabs/_email_dialog.html>
+
+Email dialog
 
 =back
 
 
 =back
 
-=item *
-js/kivi.Order.js: java script functions
+=item * C<js/kivi.Order.js>
+
+java script functions
 
 =back
 
 =head1 TODO
 
 
 =back
 
 =head1 TODO
 
-=over 2
+=over 4
 
 
-=item *
+=item * testing
 
 
-testing
+=item * currency
 
 
+=item * customer/vendor details ('D'-button)
 
 
-=item *
+=item * credit limit
 
 
-currency
+=item * more workflows (save as new / invoice)
 
 
+=item * price sources: little symbols showing better price / better discount
 
 
-=item *
+=item * custom shipto address
 
 
-customer/vendor details ('D'-button)
+=item * periodic invoices
 
 
+=item * more details on second row (marge, ...)
 
 
-=item *
+=item * language / part translations
 
 
-credit limit
+=item * access rights
 
 
+=item * preset salesman from customer
 
 
-=item *
+=item * display weights
 
 
-more workflows (save as new / invoice)
+=item * force project if enabled in client config
 
 
+=item * history
 
 
-=item *
+=item * mtime check
 
 
-price sources: little symbols showing better price / better discount
+=back
 
 
+=head1 KNOWN BUGS AND CAVEATS
 
 
-=item *
+=over 4
 
 
-custom shipto address
+=item *
 
 
+Customer discount is not displayed as a valid discount in price source popup
+(this might be a bug in price sources)
 
 =item *
 
 
 =item *
 
-periodic invoices
-
+No indication that double click expands second row, no exand all button
 
 =item *
 
 
 =item *
 
-more details on second row (marge, ...)
-
+Implementation of second row with a tbody for every item is not supported by
+our css.
 
 =item *
 
 
 =item *
 
-language / part translations
-
+As a consequence row striping does not currently work
 
 =item *
 
 
 =item *
 
-access rights
+Inline creation of parts is not currently supported
 
 
+=item *
+
+Table header is not sticky in the scrolling area.
 
 =item *
 
 
 =item *
 
-preset salesman from customer
+Sorting does not include C<position>, neither does reordering.
 
 
+This behavior was implemented intentionally. But we can discuss, which behavior
+should be implemented.
 
 =item *
 
 
 =item *
 
-display weights
+C<show_multi_items_dialog> does not use the currently inserted string for
+filtering.
 
 
+=item * Performance
 
 
-=item *
+Rendering a 50 items order takes twice as long as the old code.
 
 
-force project if enabled in client config
+90% of that is rendering the (hidden) second rows, and 50% of those again are
+checks for is_valid and C<INCLUDE> on the cvar input template.
 
 
+Suggestion: fetch second rows when asked for.
 
 =back
 
 
 =back