CSV-Import Kunden: Verkäufer über Login setzen können
[kivitendo-erp.git] / SL / Controller / CsvImport / Base.pm
index 6906026..43906bf 100644 (file)
@@ -3,10 +3,12 @@ package SL::Controller::CsvImport::Base;
 use strict;
 
 use English qw(-no_match_vars);
+use List::Util qw(min);
 use List::MoreUtils qw(pairwise any);
 
 use SL::Helper::Csv;
 
+use SL::DB;
 use SL::DB::BankAccount;
 use SL::DB::Customer;
 use SL::DB::Language;
@@ -21,7 +23,7 @@ use parent qw(Rose::Object);
 use Rose::Object::MakeMethods::Generic
 (
  scalar                  => [ qw(controller file csv test_run save_with_cascade) ],
- 'scalar --get_set_init' => [ qw(profile displayable_columns existing_objects class manager_class cvar_columns all_cvar_configs all_languages payment_terms_by delivery_terms_by all_bank_accounts all_vc vc_by clone_methods) ],
+ 'scalar --get_set_init' => [ qw(profile displayable_columns existing_objects class manager_class cvar_columns all_cvar_configs all_languages payment_terms_by delivery_terms_by all_bank_accounts all_vc vc_by vc_counts_by clone_methods) ],
 );
 
 sub run {
@@ -54,7 +56,7 @@ sub run {
 
   return if ( !$self->csv->header || $self->csv->errors );
 
-  my $headers         = { headers => [ map {; $_->{key} } @{ $self->csv->specs->[0] } ] };
+  my $headers         = { headers => [ grep { $self->csv->dispatcher->is_known($_, 0) } @{ $self->csv->header } ] };
   $headers->{methods} = [ map { $_->{path} } @{ $self->csv->specs->[0] } ];
   $headers->{used}    = { map { ($_ => 1) }  @{ $headers->{headers} } };
   $self->controller->headers($headers);
@@ -186,10 +188,28 @@ sub init_vc_by {
                     vendors   => { map { ( $_->vendornumber   => $_ ) } @{ $self->all_vc->{vendors}   } } );
   my %by_name   = ( customers => { map { ( $_->name           => $_ ) } @{ $self->all_vc->{customers} } },
                     vendors   => { map { ( $_->name           => $_ ) } @{ $self->all_vc->{vendors}   } } );
+  my %by_gln    = ( customers => { map { ( $_->gln            => $_ ) } grep $_->gln, @{ $self->all_vc->{customers} } },
+                    vendors   => { map { ( $_->gln            => $_ ) } grep $_->gln, @{ $self->all_vc->{vendors}   } } );
 
   return { id     => \%by_id,
            number => \%by_number,
-           name   => \%by_name,   };
+           name   => \%by_name,
+           gln    => \%by_gln };
+}
+
+sub init_vc_counts_by {
+  my ($self) = @_;
+
+  my $vc_counts_by = {};
+
+  $vc_counts_by->{number}->{customers}->{$_->customernumber}++ for @{ $self->all_vc->{customers} };
+  $vc_counts_by->{number}->{vendors}->  {$_->vendornumber}++   for @{ $self->all_vc->{vendors} };
+  $vc_counts_by->{name}->  {customers}->{$_->name}++           for @{ $self->all_vc->{customers} };
+  $vc_counts_by->{name}->  {vendors}->  {$_->name}++           for @{ $self->all_vc->{vendors} };
+  $vc_counts_by->{gln}->   {customers}->{$_->gln}++            for grep $_->gln, @{ $self->all_vc->{customers} };
+  $vc_counts_by->{gln}->   {vendors}->  {$_->gln}++            for grep $_->gln, @{ $self->all_vc->{vendors} };
+
+  return $vc_counts_by;
 }
 
 sub check_vc {
@@ -199,22 +219,71 @@ sub check_vc {
     $entry->{object}->$id_column(undef) if !$self->vc_by->{id}->{ $entry->{object}->$id_column };
   }
 
+  my $is_ambiguous;
   if (!$entry->{object}->$id_column) {
-    my $vc = ($entry->{raw_data}->{customernumber} && $self->vc_by->{number}->{customers}->{ $entry->{raw_data}->{customernumber} })
-          || ($entry->{raw_data}->{vendornumber}   && $self->vc_by->{number}->{vendors}->{   $entry->{raw_data}->{vendornumber}   });
+    my $vc;
+    if ($entry->{raw_data}->{customernumber}) {
+      $vc = $self->vc_by->{number}->{customers}->{ $entry->{raw_data}->{customernumber} };
+      if ($vc && $self->vc_counts_by->{number}->{customers}->{ $entry->{raw_data}->{customernumber} } > 1) {
+        $vc = undef;
+        $is_ambiguous = 1;
+      }
+    } elsif ($entry->{raw_data}->{vendornumber}) {
+      $vc = $self->vc_by->{number}->{vendors}->{ $entry->{raw_data}->{vendornumber} };
+      if ($vc && $self->vc_counts_by->{number}->{vendors}->{ $entry->{raw_data}->{vendornumber} } > 1) {
+        $vc = undef;
+        $is_ambiguous = 1;
+      }
+    }
+
     $entry->{object}->$id_column($vc->id) if $vc;
   }
 
   if (!$entry->{object}->$id_column) {
-    my $vc = $self->vc_by->{name}->{customers}->{ $entry->{raw_data}->{customer} }
-          || $self->vc_by->{name}->{vendors}->{   $entry->{raw_data}->{vendor}   };
+    my $vc;
+    if ($entry->{raw_data}->{customer}) {
+      $vc = $self->vc_by->{name}->{customers}->{ $entry->{raw_data}->{customer} };
+      if ($vc && $self->vc_counts_by->{name}->{customers}->{ $entry->{raw_data}->{customer} } > 1) {
+        $vc = undef;
+        $is_ambiguous = 1;
+      }
+    } elsif ($entry->{raw_data}->{vendor}) {
+      $vc = $self->vc_by->{name}->{vendors}->{ $entry->{raw_data}->{vendor} };
+      if ($vc && $self->vc_counts_by->{name}->{vendors}->{ $entry->{raw_data}->{vendor} } > 1) {
+        $vc = undef;
+        $is_ambiguous = 1;
+      }
+    }
+
+    $entry->{object}->$id_column($vc->id) if $vc;
+  }
+
+  if (!$entry->{object}->$id_column) {
+    my $vc;
+    if ($entry->{raw_data}->{customer_gln}) {
+      $vc = $self->vc_by->{gln}->{customers}->{ $entry->{raw_data}->{customer_gln} };
+      if ($vc && $self->vc_counts_by->{gln}->{customers}->{ $entry->{raw_data}->{customer_gln} } > 1) {
+        $vc = undef;
+        $is_ambiguous = 1;
+      }
+    } elsif ($entry->{raw_data}->{vendor_gln}) {
+      $vc = $self->vc_by->{gln}->{vendors}->{ $entry->{raw_data}->{vendor_gln} };
+      if ($vc && $self->vc_counts_by->{gln}->{vendors}->{ $entry->{raw_data}->{vendor_gln} } > 1) {
+        $vc = undef;
+        $is_ambiguous = 1;
+      }
+    }
     $entry->{object}->$id_column($vc->id) if $vc;
   }
 
   if ($entry->{object}->$id_column) {
     $entry->{info_data}->{vc_name} = $self->vc_by->{id}->{ $entry->{object}->$id_column }->name;
   } else {
-    push @{ $entry->{errors} }, $::locale->text('Error: Customer/vendor not found');
+    if ($is_ambiguous) {
+      push @{ $entry->{errors} }, $::locale->text('Error: Customer/vendor is ambiguous');
+    } else {
+      push @{ $entry->{errors} }, $::locale->text('Error: Customer/vendor not found');
+    }
   }
 }
 
@@ -337,6 +406,10 @@ sub check_objects {
 sub check_duplicates {
 }
 
+sub check_auth {
+  $::auth->assert('config');
+}
+
 sub check_std_duplicates {
   my $self = shift;
 
@@ -459,32 +532,32 @@ sub save_objects {
 
   $self->controller->track_progress(phase => 'saving data', progress => 0); # scale from 45..95%;
 
-  my $dbh = $data->[0]{object}->db;
-
-  $dbh->begin_work;
-  foreach my $entry_index (0 .. $#$data) {
-    my $entry = $data->[$entry_index];
-    next if @{ $entry->{errors} };
-
-    my $object = $entry->{object_to_save} || $entry->{object};
-
-    my $ret;
-    if (!eval { $ret = $object->save(cascade => !!$self->save_with_cascade()); 1 }) {
-      push @{ $entry->{errors} }, $::locale->text('Error when saving: #1', $EVAL_ERROR);
-    } elsif ( !$ret ) {
-      push @{ $entry->{errors} }, $::locale->text('Error when saving: #1', $object->db->error);
-    } else {
-      $self->_save_history($object);
-      $self->controller->num_imported($self->controller->num_imported + 1);
-    }
-  } continue {
-    if ($entry_index % 100 == 0) {
-      $dbh->commit;
-      $self->controller->track_progress(progress => $entry_index/scalar(@$data) * 100); # scale from 45..95%;
-      $dbh->begin_work;
-    }
+  my $last_index = $#$data;
+  my $chunk_size = 100;      # one transaction and progress update every 100 objects
+
+  for my $chunk (0 .. $last_index / $chunk_size) {
+    $self->controller->track_progress(progress => ($chunk_size * $chunk)/scalar(@$data) * 100); # scale from 45..95%;
+    SL::DB->client->with_transaction(sub {
+      foreach my $entry_index ($chunk_size * $chunk .. min( $last_index, $chunk_size * ($chunk + 1) - 1 )) {
+        my $entry = $data->[$entry_index];
+        next if @{ $entry->{errors} };
+
+        my $object = $entry->{object_to_save} || $entry->{object};
+
+        my $ret;
+        if (!eval { $ret = $object->save(cascade => !!$self->save_with_cascade()); 1 }) {
+          push @{ $entry->{errors} }, $::locale->text('Error when saving: #1', $EVAL_ERROR);
+        } elsif ( !$ret ) {
+          push @{ $entry->{errors} }, $::locale->text('Error when saving: #1', $object->db->error);
+        } else {
+          $self->_save_history($object);
+          $self->controller->num_imported($self->controller->num_imported + 1);
+        }
+      }
+      1;
+    }) or do { die SL::DB->client->error };
   }
-  $dbh->commit;
+  $self->controller->track_progress(progress => 100);
 }
 
 sub field_lengths {