]> wagnertech.de Git - mfinanz.git/blobdiff - SL/Controller/CsvImport/Base.pm
restart apache2 in postinst
[mfinanz.git] / SL / Controller / CsvImport / Base.pm
index 4d7df747ada36c18ae8607da3882513d7660b840..3df2332677d505be995c452e717de79eb61eb7a5 100644 (file)
@@ -64,6 +64,10 @@ sub run {
   $self->controller->info_headers({ used => { }, headers => [ ] });
 
   my $objects  = $self->csv->get_objects;
+  if ($self->csv->errors) {
+    $self->controller->errors([ $self->csv->errors ]) ;
+    return;
+  }
 
   $self->controller->track_progress(progress => 70);
 
@@ -288,14 +292,19 @@ sub check_vc {
 sub handle_cvars {
   my ($self, $entry) = @_;
 
+  my $object = $entry->{object_to_save} || $entry->{object};
+  return unless $object->can('cvars_by_config');
+
   my %type_to_column = ( text      => 'text_value',
                          textfield => 'text_value',
+                         htmlfield => 'text_value',
                          select    => 'text_value',
                          date      => 'timestamp_value_as_date',
                          timestamp => 'timestamp_value_as_date',
                          number    => 'number_value_as_number',
                          bool      => 'bool_value' );
 
+  # autovivify all cvars (cvars_by_config will do that for us)
   my @cvars;
   my %changed_cvars;
   foreach my $config (@{ $self->all_cvar_configs }) {
@@ -310,17 +319,13 @@ sub handle_cvars {
   }
 
   # merge existing with new cvars. swap every existing with the imported one, push the rest
-  if (@cvars) {
-    my $object     = $entry->{object_to_save} || $entry->{object};
-    my @orig_cvars = $object->custom_variables;
-    for (@orig_cvars) {
-      $_ = $changed_cvars{ $_->config->name } if $changed_cvars{ $_->config->name };
-      delete $changed_cvars{ $_->config->name };
-    }
-    push @orig_cvars, values %changed_cvars;
-
-    $object->custom_variables(\@orig_cvars);
+  my @orig_cvars = @{ $object->cvars_by_config };
+  for (@orig_cvars) {
+    $_ = $changed_cvars{ $_->config->name } if $changed_cvars{ $_->config->name };
+    delete $changed_cvars{ $_->config->name };
   }
+  push @orig_cvars, values %changed_cvars;
+  $object->custom_variables(\@orig_cvars);
 }
 
 sub init_profile {
@@ -529,6 +534,33 @@ sub save_objects {
   return unless $data->[0];
   return unless $data->[0]{object};
 
+  # If we store into tables which get numbers from the TransNumberGenerator
+  # we have to lock all tables referenced by the storage table (or by
+  # tables stored alongside with the storage table) that are handled by
+  # the TransNumberGenerator, too.
+  # Otherwise we can run into a deadlock if someone saves a document via
+  # the user interface. The exact behavoir depends on timing.
+  # E.g. we are importing orders and a user want to
+  # book an invoice:
+  # web: locks ar (via before-save hook and TNG (or SL::TransNumber))
+  # importer: locks oe (via before-save hook and TNG) (*)
+  # importer: locks defaults (via before-save hook and TNG)
+  # web: wants to lock defaults (via before-save hook and TNG (or SL::TransNumber)) -> is waiting
+  # importer: wants to save oe and wants to lock referenced tables (here ar) -> is waiting
+  # --> deadlock
+  #
+  # (*) if the importer locks ar here, too, everything is fine, because it will wait here
+  # before locking the defaults table.
+  #
+  # List of referenced tables:
+  # (Locking is done in the transaction below)
+  my %referenced_tables_by_type = (
+    orders          => [qw(ar customer vendor)],
+    delivery_orders => [qw(customer vendor)   ],
+    ar_transactions => [qw(customer)          ],
+    ap_transactions => [qw(vendor)            ],
+  );
+
   $self->controller->track_progress(phase => 'saving data', progress => 0); # scale from 45..95%;
 
   my $last_index = $#$data;
@@ -537,11 +569,18 @@ sub save_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 $refs (@{ $referenced_tables_by_type{$self->controller->{type}} || [] }) {
+        SL::DB->client->dbh->do("LOCK " . $refs) || die SL::DB->client->dbh->errstr;
+      }
+
       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};
+        $self->save_additions_always($object);
+
+        next if @{ $entry->{errors} };
 
         my $ret;
         if (!eval { $ret = $object->save(cascade => !!$self->save_with_cascade()); 1 }) {
@@ -602,6 +641,18 @@ sub save_additions {
   return;
 }
 
+sub save_additions_always {
+  my ($self, $object) = @_;
+
+  # Can be overridden by derived specialized importer classes to save
+  # additional tables always.
+  # This sub is called before the object is saved. Therefore this
+  # hook will always be executed whether or not the import entry can be saved successfully.
+
+  return;
+}
+
+
 sub _save_history {
   my ($self, $object) = @_;