commit dfdaa70d0c1ef959216e0c220658b4389c8a3dc7
Author: Tamino Steinert <tamino.steinert@tamino.st>
Date:   Thu Feb 1 15:09:23 2024 +0100

    Varianten: Schnellbearbeitung von Varianten (EAN, Auf Lager)

diff --git a/SL/Controller/Part.pm b/SL/Controller/Part.pm
index 40d5b156c..8f26cb015 100644
--- a/SL/Controller/Part.pm
+++ b/SL/Controller/Part.pm
@@ -273,30 +273,28 @@ sub action_update_variants {
   }
 
   SL::DB->client->with_transaction(sub {
-    foreach my $variant (@{$self->part->variants}) {
-      $variant->update_attributes(
-        description         => $variant_id_to_values{$variant->id}->{description},
-        listprice_as_number => $variant_id_to_values{$variant->id}->{listprice_as_number},
-        sellprice_as_number => $variant_id_to_values{$variant->id}->{sellprice_as_number},
-        lastcost_as_number  => $variant_id_to_values{$variant->id}->{lastcost_as_number},
-        rop_as_number       => $variant_id_to_values{$variant->id}->{rop_as_number},
-      );
-    }
+    my $new_variant_property;
     if ($variant_property_id) {
-      SL::DB::VariantPropertyPart->new(
+      $new_variant_property = SL::DB::VariantPropertyPart->new(
         part_id             => $self->part->id,
         variant_property_id => $variant_property_id,
       )->save;
-      foreach my $variant (@{$self->part->variants}) {
+    }
+    foreach my $variant (@{$self->part->variants}) {
+      my $variant_attributes = $variant_id_to_values{$variant->id};
+      my $variant_property_value_id = delete $variant_attributes->{add_variant_property_value};
+      delete $variant_attributes->{$_} for qw(id position);
+      $variant->update_attributes(%$variant_attributes);
+      if ($new_variant_property) {
         SL::DB::VariantPropertyValuePart->new(
           part_id                   => $variant->id,
-          variant_property_value_id => $variant_id_to_values{$variant->id}->{"add_variant_property_value"},
+          variant_property_value_id => $variant_property_value_id,
         )->save;
       }
     }
     1;
   }) or do {
-    return $self->js->error(t8('Error while adding variant property: ' . @_))->render();
+    return $self->js->error(t8('Error while adding variant property: #1', SL::DB->client->error))->render();
   };
 
   $self->redirect_to(
@@ -882,8 +880,12 @@ sub action_reorder_variants {
   my %sort_keys = (
     partnumber       => sub { $_[0]->partnumber },
     description      => sub { $_[0]->description },
+    ean              => sub { $_[0]->ean },
+    listprice        => sub { $_[0]->listprice },
     sellprice        => sub { $_[0]->sellprice },
     lastcost         => sub { $_[0]->lastcost },
+    onhand           => sub { $_[0]->onhand },
+    rop              => sub { $_[0]->rop },
     variant_values   => sub { $_[0]->variant_values },
   );
   foreach my $variant_property (@{$part->variant_properties}) {
@@ -902,7 +904,7 @@ sub action_reorder_variants {
     @{$::form->{variants}};
 
   my @to_sort = map { { old_pos => $variant_id_to_position{$_->id}, order_by => $method->($_) } } @items;
-  if ($::form->{order_by} =~ /^(sellprice|lastcost)$/ ||
+  if ($::form->{order_by} =~ /^(listpirce|sellprice|lastcost|onhand|rop)$/ ||
       $::form->{order_by} =~ /^variant_property_/) {
     if ($::form->{sort_dir}) {
       @to_sort = sort { $a->{order_by} <=> $b->{order_by} } @to_sort;
diff --git a/SL/DB/Part.pm b/SL/DB/Part.pm
index d331b1519..9812074b2 100644
--- a/SL/DB/Part.pm
+++ b/SL/DB/Part.pm
@@ -4,7 +4,7 @@ use strict;
 
 use Carp;
 use List::MoreUtils qw(any uniq pairwise);
-use List::Util qw(sum max);
+use List::Util qw(sum max first);
 use Rose::DB::Object::Helpers qw(as_tree);
 
 use SL::Locale::String qw(t8);
@@ -100,6 +100,7 @@ __PACKAGE__->meta->add_relationships(
     map_from  => 'part',
     map_to    => 'variant_property',
     type      => 'many to many',
+    manager_args => { sort_by => 'sortkey ASC' }
   },
   variant_property_values => {
     map_class => 'SL::DB::VariantPropertyValuePart',
@@ -107,7 +108,7 @@ __PACKAGE__->meta->add_relationships(
     map_to    => 'variant_property_value',
     type      => 'many to many',
   },
-  parent_variant => {
+  parent_variants => { # access through parent_variant
     map_class => 'SL::DB::PartParentVariantPartVariant',
     map_from  => 'variant',
     map_to    => 'parent_variant',
@@ -156,7 +157,7 @@ sub _before_check_variant_property_values {
   my ($self) = @_;
   if ($self->is_variant) {
     my @property_value_ids = sort map {$_->id} $self->variant_property_values;
-    my ($parent_variant) = $self->parent_variant;
+    my $parent_variant = $self->parent_variant;
     my $other_variants = $parent_variant->variants;
     foreach my $variant (@$other_variants) {
       next if $variant->id == $self->id;
@@ -338,6 +339,11 @@ sub buchungsgruppe {
   shift->buchungsgruppen(@_);
 }
 
+sub parent_variant {
+  my ($parent_variant) = shift->parent_variants(@_);
+  return $parent_variant;
+}
+
 sub get_variant_property_value_by_unique_name {
   my ($self, $variant_property_unique_name) = @_;
 
@@ -687,7 +693,9 @@ sub variant_values {
   return unless $self->is_variant;
   return "[" .join("|",
     map {$_->variant_property->abbreviation . ":" . $_->abbreviation}
-    $self->variant_property_values)  . "]";
+    sort {$a->variant_property->sortkey <=> $b->variant_property->sortkey}
+    $self->variant_property_values
+  )  . "]";
 }
 
 sub variant_value {
@@ -695,13 +703,11 @@ sub variant_value {
   die "only callable on parts of type parent_variant"     unless $self->is_variant;
   die "only callable with SL::DB::VariantProperty object" unless ref $variant_property eq 'SL::DB::VariantProperty';
 
-  my %property_id_to_values =
-    map {$_->variant_property_id => $_}
+  my $property_value =
+    first {$variant_property->id == $_->variant_property_id}
     @{$self->variant_property_values};
 
-  my $property_value = $property_id_to_values{$variant_property->id};
-
-  return $property_value && $property_value->displayable_name();
+  return $property_value;
 }
 
 sub init_onhandqty {
diff --git a/templates/design40_webpages/part/_parent_variant.html b/templates/design40_webpages/part/_parent_variant.html
index f739befc3..4c1045006 100644
--- a/templates/design40_webpages/part/_parent_variant.html
+++ b/templates/design40_webpages/part/_parent_variant.html
@@ -24,6 +24,11 @@
             [% 'Partnumber' | $T8 %]
           </a>
         </th>
+        <th id="variant_ean_header_id">
+          <a href='#' onClick='javascript:kivi.Part.reorder_variants("ean")'>
+            [% "EAN" | $T8 %]
+          </a>
+        </th>
         <th id="variant_description_header_id">
           <a href='#' onClick='javascript:kivi.Part.reorder_variants("description")'>
             [% "Description" | $T8 %]
@@ -41,10 +46,31 @@
           </a>
         </th>
         [% END %]
-        <th>[% 'List Price' | $T8 %]</th>
-        <th>[% 'Sell Price' | $T8 %]</th>
-        <th>[% 'Last Cost' | $T8 %]</th>
-        <th>[% 'ROP' | $T8 %]</th>
+        <th id="variant_listprice_header_id">
+          <a href='#' onClick='javascript:kivi.Part.reorder_variants("listprice")'>
+            [% "List Price" | $T8 %]
+          </a>
+        </th>
+        <th id="variant_sellprice_header_id">
+          <a href='#' onClick='javascript:kivi.Part.reorder_variants("sellprice")'>
+            [% "Sell Price" | $T8 %]
+          </a>
+        </th>
+        <th id="variant_lastcost_header_id">
+          <a href='#' onClick='javascript:kivi.Part.reorder_variants("lastcost")'>
+            [% "Last Cost" | $T8 %]
+          </a>
+        </th>
+        <th id="variant_onhand_header_id">
+          <a href='#' onClick='javascript:kivi.Part.reorder_variants("onhand")'>
+            [% "On Hand" | $T8 %]
+          </a>
+        </th>
+        <th id="variant_rop_header_id">
+          <a href='#' onClick='javascript:kivi.Part.reorder_variants("rop")'>
+            [% "ROP" | $T8 %]
+          </a>
+        </th>
         <th>
           [% L.select_tag("add_variant_property", AVAILABLE_VARIANT_PROPERIES
             title_key='displayable_name', value_key='id',
@@ -54,13 +80,14 @@
         </th>
       </tr>
       <tr>
-        <th class="center">
+        <th>
           [%- L.button_tag('kivi.Part.variant_rows_toggle_selected();', "🔄",
                            title=LxERP.t8("Toggle selection"),
                            alt=LxERP.t8("Toggle selection"),
                            ) %]
         </th>
-        <th></th>
+        <th><!-- partnumber --></th>
+        <th><!-- ean --></th>
         <th>
           [% L.input_tag("description_for_selected_variants", SELF.part.description,
                           class="wi-medium",
@@ -72,7 +99,10 @@
                 title=LxERP.t8('Apply to selected rows'),
           ) %]
         </th>
-        <th colspan="[% SELF.part.variant_properties.size + 1 %]"></th>
+        <th><!-- variant_values --></th>
+        [% FOREACH property = SELF.part.variant_properties %]
+        <th></th>
+        [% END %]
         <th>
           [% L.input_tag(
                 "listprice_as_number_for_selected_variants",
@@ -112,6 +142,7 @@
                 title=LxERP.t8('Apply to selected rows'),
           ) %]
         </th>
+        <th><!-- onhand --></th>
         <th>
           [% L.input_tag(
                 "rop_as_number_for_selected_variants",
@@ -148,12 +179,15 @@
               [% L.checkbox_tag('variant_multi_id_' _ loop.count, value=variant.id, checked=0) %]
             </td>
             <td>[% variant.presenter.part %]</td>
+            <td>
+              [% L.input_tag("variants[].ean", variant.ean, class="wi-medium") %]
+            </td>
             <td>
               [% L.input_tag("variants[].description", variant.description, class="wi-medium") %]
             </td>
             <td>[% variant.variant_values | html %]</td>
             [% FOREACH variant_property = SELF.part.variant_properties %]
-            <td> [% variant.variant_value(variant_property) %] </td>
+            <td> [% variant.variant_value(variant_property).displayable_name %] </td>
             [% END %]
             <td>
               [% L.input_tag(
@@ -176,6 +210,11 @@
                     class='reformat_number numeric wi-small',
               ) %]
             </td>
+            <td>
+              <span class="data wi-small numeric">
+                [% LxERP.format_amount(variant.onhand) %] [% variant.unit | html %]
+              </span>
+            </td>
             <td>
               [% L.input_tag(
                     "variants[].rop_as_number",
