X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FController%2FCsvImport%2FBase.pm;h=2fa788753dd8ed52f12c87a9ddd67256384f17e1;hb=e902839eeaf164ad540bbc3072213831d9572c63;hp=f8e9b591c6f2e3f08773db4da467215688646070;hpb=0f0cb3b708f9078b774a07cecd996e5bdc5fac63;p=kivitendo-erp.git diff --git a/SL/Controller/CsvImport/Base.pm b/SL/Controller/CsvImport/Base.pm index f8e9b591c..2fa788753 100644 --- a/SL/Controller/CsvImport/Base.pm +++ b/SL/Controller/CsvImport/Base.pm @@ -2,9 +2,14 @@ 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; use SL::DB::PaymentTerm; @@ -18,20 +23,20 @@ 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_vc vc_by) ], + '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) ], ); sub run { my ($self, %params) = @_; - $self->test_run($params{test_run}); + $self->test_run($params{test}); $self->controller->track_progress(phase => 'parsing csv', progress => 0); my $profile = $self->profile; $self->csv(SL::Helper::Csv->new(file => $self->file->file_name, encoding => $self->controller->profile->get('charset'), - profile => [{ profile => $profile, class => $self->class }], + profile => [{ profile => $profile, class => $self->class, mapping => $self->controller->mappings_for_profile }], ignore_unknown_columns => 1, strict_profile => 1, case_insensitive_header => 1, @@ -51,14 +56,14 @@ sub run { return if ( !$self->csv->header || $self->csv->errors ); - my $headers = { headers => [ grep { $profile->{$_} } @{ $self->csv->header } ] }; - $headers->{methods} = [ map { $profile->{$_} } @{ $headers->{headers} } ]; - $headers->{used} = { map { ($_ => 1) } @{ $headers->{headers} } }; + 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); $self->controller->raw_data_headers({ used => { }, headers => [ ] }); $self->controller->info_headers({ used => { }, headers => [ ] }); - my @objects = $self->csv->get_objects; + my $objects = $self->csv->get_objects; $self->controller->track_progress(progress => 70); @@ -66,7 +71,7 @@ sub run { $self->controller->track_progress(progress => 80); - $self->controller->data([ pairwise { { object => $a, raw_data => $b, errors => [], information => [], info_data => {} } } @objects, @raw_data ]); + $self->controller->data([ pairwise { { object => $a, raw_data => $b, errors => [], information => [], info_data => {} } } @$objects, @raw_data ]); $self->controller->track_progress(progress => 90); @@ -140,6 +145,12 @@ sub init_all_languages { return SL::DB::Manager::Language->get_all; } +sub init_all_bank_accounts { + my ($self) = @_; + + return SL::DB::Manager::BankAccount->get_all_sorted( query => [ obsolete => 0 ] ); +} + sub init_payment_terms_by { my ($self) = @_; @@ -161,6 +172,10 @@ sub init_all_vc { vendors => SL::DB::Manager::Vendor->get_all }; } +sub init_clone_methods { + {} +} + sub force_allow_columns { return (); } @@ -187,8 +202,8 @@ sub check_vc { } if (!$entry->{object}->$id_column) { - my $vc = $self->vc_by->{number}->{customers}->{ $entry->{raw_data}->{customernumber} } - || $self->vc_by->{number}->{vendors}->{ $entry->{raw_data}->{vendornumber} }; + 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} }); $entry->{object}->$id_column($vc->id) if $vc; } @@ -217,15 +232,29 @@ sub handle_cvars { bool => 'bool_value' ); my @cvars; + my %changed_cvars; foreach my $config (@{ $self->all_cvar_configs }) { next unless exists $entry->{raw_data}->{ "cvar_" . $config->name }; my $value = $entry->{raw_data}->{ "cvar_" . $config->name }; my $column = $type_to_column{ $config->type } || die "Program logic error: unknown custom variable storage type"; - push @cvars, SL::DB::CustomVariable->new(config_id => $config->id, $column => $value, sub_module => ''); + my $new_cvar = SL::DB::CustomVariable->new(config_id => $config->id, $column => $value, sub_module => ''); + + push @cvars, $new_cvar; + $changed_cvars{$config->name} = $new_cvar; } - $entry->{object}->custom_variables(\@cvars); + # merge existing with new cvars. swap every existing with the imported one, push the rest + if (@cvars) { + my @orig_cvars = ($entry->{object_to_save} || $entry->{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; + + $entry->{object}->custom_variables(\@orig_cvars); + } } sub init_profile { @@ -385,6 +414,9 @@ sub check_payment { } $object->payment_id($terms->id); + + # register payment_id for method copying later + $self->clone_methods->{payment_id} = 1; } return 1; @@ -411,6 +443,9 @@ sub check_delivery_term { } $object->delivery_term_id($terms->id); + + # register delivery_term_id for method copying later + $self->clone_methods->{delivery_term_id} = 1; } return 1; @@ -428,34 +463,38 @@ sub save_objects { 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', $@); - } elsif ( !$ret ) { - push @{ $entry->{errors} }, $::locale->text('Error when saving: #1', $entry->{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 { - return (); + my ($self) = @_; + + return map { $_->name => $_->length } grep { $_->type eq 'varchar' } @{$self->class->meta->columns}; } sub fix_field_lengths { @@ -487,11 +526,12 @@ sub clean_fields { sub _save_history { my ($self, $object) = @_; - if (any { $_ eq $self->controller->{type} } qw(parts customers_vendors orders)) { + if (any { $self->controller->{type} && $_ eq $self->controller->{type} } qw(parts customers_vendors orders ar_transactions)) { my $snumbers = $self->controller->{type} eq 'parts' ? 'partnumber_' . $object->partnumber : $self->controller->{type} eq 'customers_vendors' ? ($self->table eq 'customer' ? 'customernumber_' . $object->customernumber : 'vendornumber_' . $object->vendornumber) : $self->controller->{type} eq 'orders' ? 'ordnumber_' . $object->ordnumber + : $self->controller->{type} eq 'ar_transactions' ? 'invnumber_' . $object->invnumber : ''; my $what_done = $self->controller->{type} eq 'orders' ? 'sales_order'