Merge branch 'b-3.6.1' of ../kivitendo-erp_20220811
[kivitendo-erp.git] / SL / Helper / Csv / Dispatcher.pm
index ce57809..801e822 100644 (file)
@@ -5,8 +5,9 @@ use strict;
 use Data::Dumper;
 use Carp;
 use Scalar::Util qw(weaken);
+use List::MoreUtils qw(all pairwise);
 use Rose::Object::MakeMethods::Generic scalar => [ qw(
-  _specs _errors
+  _specs _row_class _row_spec _errors
 ) ];
 
 use SL::Helper::Csv::Error;
@@ -38,48 +39,34 @@ sub dispatch {
   return $obj;
 }
 
-# return class for given line
-# if only one profile is given, return this profiles class
-# if more than one profile is given, identify class by first
-# column???
 sub _class_by_line {
   my ($self, $line) = @_;
 
-  my $class;
+  # initialize lookup hash if not already done
+  if ($self->_csv->is_multiplexed && ! defined $self->_row_class ) {
+    $self->_row_class({ map { $_->{row_ident} => $_->{class} } @{ $self->_csv->profile } });
+  }
+
   if ($self->_csv->is_multiplexed) {
-    foreach my $p (@{ $self->_csv->profile }) {
-      my $row_ident = $p->{row_ident};
-      if ($line->{datatype} eq $row_ident) {
-        $class = $p->{class};
-        last;
-      }
-    }
+    return $self->_row_class->{$line->{datatype}};
   } else {
-    $class = $self->_csv->profile->[0]->{class};
+    return $self->_csv->profile->[0]->{class};
   }
-
-  return $class;
 }
 
 sub _specs_by_line {
   my ($self, $line) = @_;
 
-  my $spec;
-  my $i = 0;
+  # initialize lookup hash if not already done
+  if ($self->_csv->is_multiplexed && ! defined $self->_row_spec ) {
+    $self->_row_spec({ pairwise { no warnings 'once'; $a->{row_ident} => $b } @{ $self->_csv->profile }, @{ $self->_specs } });
+  }
+
   if ($self->_csv->is_multiplexed) {
-    foreach my $p (@{ $self->_csv->profile }) {
-      my $row_ident = $p->{row_ident};
-      if ($line->{datatype} eq $row_ident) {
-        $spec = $self->_specs->[$i];
-        last;
-      }
-      $i++;
-    }
+    return $self->_row_spec->{$line->{datatype}};
   } else {
-    $spec = $self->_specs->[0];
+    return $self->_specs->[0];
   }
-
-  return $spec;
 }
 
 
@@ -112,28 +99,22 @@ sub apply {
 }
 
 sub is_known {
-  my ($self, $col) = @_;
-  return grep { $col eq $_->{key} } $self->_specs;
+  my ($self, $col, $row) = @_;
+  return grep { $col eq $_->{key} } @{ $self->_specs->[$row // 0] };
 }
 
 sub parse_profile {
   my ($self, %params) = @_;
 
-  my $profile;
-  my $class;
-  my $header;
   my @specs;
 
+  my $csv_profile = $self->_csv->profile;
+  my $h_aref = ($self->_csv->is_multiplexed)? $self->_csv->header : [ $self->_csv->header ];
   my $i = 0;
-  foreach my $h (@{ $self->_csv->header }) {
-    $header = $h;
-    if ($self->_csv->profile) {
-      $profile = $self->_csv->profile->[$i]->{profile};
-      $class   = $self->_csv->profile->[$i]->{class};
-    }
-
-    my $spec = $self->_parse_profile(profile => $profile,
-                                     class   => $class,
+  foreach my $header (@{ $h_aref }) {
+    my $spec = $self->_parse_profile(profile => $csv_profile->[$i]->{profile},
+                                     mapping => $csv_profile->[$i]->{mapping},
+                                     class   => $csv_profile->[$i]->{class},
                                      header  => $header);
     push @specs, $spec;
     $i++;
@@ -141,44 +122,45 @@ sub parse_profile {
 
   $self->_specs(\@specs);
 
+  $self->_csv->_push_error($self->errors);
+
   return ! $self->errors;
 }
 
 sub _parse_profile {
   my ($self, %params) = @_;
 
-  my $profile = $params{profile};
+  my $profile = $params{profile} // {};
   my $class   = $params{class};
   my $header  = $params{header};
+  my $mapping = $params{mapping};
 
   my @specs;
 
   for my $col (@$header) {
     next unless $col;
-    if ($self->_csv->strict_profile) {
-      if (exists $profile->{$col}) {
-        push @specs, $self->make_spec($col, $profile->{$col}, $class);
-      } else {
-        $self->unknown_column($col, undef);
-      }
+    if (exists $mapping->{$col} && $profile->{$mapping->{$col}}) {
+      push @specs, $self->make_spec($col, $profile->{$mapping->{$col}}, $class);
+    } elsif (exists $mapping->{$col} && !%{ $profile }) {
+      push @specs, $self->make_spec($col, $mapping->{$col}, $class);
+    } elsif (exists $profile->{$col}) {
+      push @specs, $self->make_spec($col, $profile->{$col}, $class);
     } else {
-      if (exists $profile->{$col}) {
-        push @specs, $self->make_spec($col, $profile->{$col}, $class);
+      if ($self->_csv->strict_profile) {
+        $self->unknown_column($col, undef);
       } else {
         push @specs, $self->make_spec($col, $col, $class);
       }
     }
   }
 
-  $self->_csv->_push_error($self->errors);
-
   return \@specs;
 }
 
 sub make_spec {
   my ($self, $col, $path, $cur_class) = @_;
 
-  my $spec = { key => $col, steps => [] };
+  my $spec = { key => $col, path => $path, steps => [] };
 
   return unless $path;