$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);
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 }) {
}
# 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 {
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;
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 }) {
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) = @_;