]> wagnertech.de Git - mfinanz.git/blob - SL/Controller/CsvImport/Base.pm
kivitendo 3.9.2-0.2
[mfinanz.git] / SL / Controller / CsvImport / Base.pm
1 package SL::Controller::CsvImport::Base;
2
3 use strict;
4
5 use English qw(-no_match_vars);
6 use List::Util qw(min);
7 use List::MoreUtils qw(pairwise any);
8
9 use SL::Helper::Csv;
10
11 use SL::DB;
12 use SL::DB::BankAccount;
13 use SL::DB::Customer;
14 use SL::DB::Language;
15 use SL::DB::PaymentTerm;
16 use SL::DB::DeliveryTerm;
17 use SL::DB::Vendor;
18 use SL::DB::Contact;
19 use SL::DB::History;
20
21 use parent qw(Rose::Object);
22
23 use Rose::Object::MakeMethods::Generic
24 (
25  scalar                  => [ qw(controller file csv test_run save_with_cascade) ],
26  '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) ],
27 );
28
29 sub run {
30   my ($self, %params) = @_;
31
32   $self->test_run($params{test});
33
34   $self->controller->track_progress(phase => 'parsing csv', progress => 0);
35
36   my $profile = $self->profile;
37   $self->csv(SL::Helper::Csv->new(file                   => ('SCALAR' eq ref $self->file)? $self->file: $self->file->file_name,
38                                   encoding               => $self->controller->profile->get('charset'),
39                                   profile                => [{ profile => $profile, class => $self->class, mapping => $self->controller->mappings_for_profile }],
40                                   ignore_unknown_columns => 1,
41                                   strict_profile         => 1,
42                                   case_insensitive_header => 1,
43                                   map { ( $_ => $self->controller->profile->get($_) ) } qw(sep_char escape_char quote_char),
44                                  ));
45
46   $self->controller->track_progress(progress => 10);
47
48   local $::myconfig{numberformat} = $self->controller->profile->get('numberformat');
49   local $::myconfig{dateformat}   = $self->controller->profile->get('dateformat');
50
51   $self->csv->parse;
52
53   $self->controller->track_progress(progress => 50);
54
55   $self->controller->errors([ $self->csv->errors ]) if $self->csv->errors;
56
57   return if ( !$self->csv->header || $self->csv->errors );
58
59   my $headers         = { headers => [ grep { $self->csv->dispatcher->is_known($_, 0) } @{ $self->csv->header } ] };
60   $headers->{methods} = [ map { $_->{path} } @{ $self->csv->specs->[0] } ];
61   $headers->{used}    = { map { ($_ => 1) }  @{ $headers->{headers} } };
62   $self->controller->headers($headers);
63   $self->controller->raw_data_headers({ used => { }, headers => [ ] });
64   $self->controller->info_headers({ used => { }, headers => [ ] });
65
66   my $objects  = $self->csv->get_objects;
67   if ($self->csv->errors) {
68     $self->controller->errors([ $self->csv->errors ]) ;
69     return;
70   }
71
72   $self->controller->track_progress(progress => 70);
73
74   my @raw_data = @{ $self->csv->get_data };
75
76   $self->controller->track_progress(progress => 80);
77
78   $self->controller->data([ pairwise { { object => $a, raw_data => $b, errors => [], information => [], info_data => {} } } @$objects, @raw_data ]);
79
80   $self->controller->track_progress(progress => 90);
81
82   $self->check_objects;
83   if ( $self->controller->profile->get('duplicates', 'no_check') ne 'no_check' ) {
84     $self->check_std_duplicates();
85     $self->check_duplicates();
86   }
87   $self->fix_field_lengths;
88
89   $self->controller->track_progress(progress => 100);
90 }
91
92 sub add_columns {
93   my ($self, @columns) = @_;
94
95   my $h = $self->controller->headers;
96
97   foreach my $column (grep { !$h->{used}->{$_} } @columns) {
98     $h->{used}->{$column} = 1;
99     push @{ $h->{methods} }, $column;
100     push @{ $h->{headers} }, $column;
101   }
102 }
103
104 sub add_info_columns {
105   my ($self, @columns) = @_;
106
107   my $h = $self->controller->info_headers;
108
109   foreach my $column (grep { !$h->{used}->{ $_->{method} } } map { ref $_ eq 'HASH' ? $_ : { method => $_, header => $_ } } @columns) {
110     $h->{used}->{ $column->{method} } = 1;
111     push @{ $h->{methods} }, $column->{method};
112     push @{ $h->{headers} }, $column->{header};
113   }
114 }
115
116 sub add_raw_data_columns {
117   my ($self, @columns) = @_;
118
119   my $h = $self->controller->raw_data_headers;
120
121   foreach my $column (grep { !$h->{used}->{$_} } @columns) {
122     $h->{used}->{$column} = 1;
123     push @{ $h->{headers} }, $column;
124   }
125 }
126
127 sub add_cvar_raw_data_columns {
128   my ($self) = @_;
129
130   map { $self->add_raw_data_columns($_) if exists $self->controller->data->[0]->{raw_data}->{$_} } @{ $self->cvar_columns };
131 }
132
133 sub init_all_cvar_configs {
134   # Must be overridden by derived specialized importer classes.
135   return [];
136 }
137
138 sub init_cvar_columns {
139   my ($self) = @_;
140
141   return [ map { "cvar_" . $_->name } (@{ $self->all_cvar_configs }) ];
142 }
143
144 sub init_all_languages {
145   my ($self) = @_;
146
147   return SL::DB::Manager::Language->get_all;
148 }
149
150 sub init_all_bank_accounts {
151   my ($self) = @_;
152
153   return SL::DB::Manager::BankAccount->get_all_sorted( query => [ obsolete => 0 ] );
154 }
155
156 sub init_payment_terms_by {
157   my ($self) = @_;
158
159   my $all_payment_terms = SL::DB::Manager::PaymentTerm->get_all;
160   return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_payment_terms } } ) } qw(id description) };
161 }
162
163 sub init_delivery_terms_by {
164   my ($self) = @_;
165
166   my $all_delivery_terms = SL::DB::Manager::DeliveryTerm->get_all;
167   return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_delivery_terms } } ) } qw(id description) };
168 }
169
170 sub init_all_vc {
171   my ($self) = @_;
172
173   return { customers => SL::DB::Manager::Customer->get_all,
174            vendors   => SL::DB::Manager::Vendor->get_all };
175 }
176
177 sub init_clone_methods {
178   {}
179 }
180
181 sub force_allow_columns {
182   return ();
183 }
184
185 sub init_vc_by {
186   my ($self)    = @_;
187
188   my %by_id     = map { ( $_->id => $_ ) } @{ $self->all_vc->{customers} }, @{ $self->all_vc->{vendors} };
189   my %by_number = ( customers => { map { ( $_->customernumber => $_ ) } @{ $self->all_vc->{customers} } },
190                     vendors   => { map { ( $_->vendornumber   => $_ ) } @{ $self->all_vc->{vendors}   } } );
191   my %by_name   = ( customers => { map { ( $_->name           => $_ ) } @{ $self->all_vc->{customers} } },
192                     vendors   => { map { ( $_->name           => $_ ) } @{ $self->all_vc->{vendors}   } } );
193   my %by_gln    = ( customers => { map { ( $_->gln            => $_ ) } grep $_->gln, @{ $self->all_vc->{customers} } },
194                     vendors   => { map { ( $_->gln            => $_ ) } grep $_->gln, @{ $self->all_vc->{vendors}   } } );
195
196   return { id     => \%by_id,
197            number => \%by_number,
198            name   => \%by_name,
199            gln    => \%by_gln };
200 }
201
202 sub init_vc_counts_by {
203   my ($self) = @_;
204
205   my $vc_counts_by = {};
206
207   $vc_counts_by->{number}->{customers}->{$_->customernumber}++ for @{ $self->all_vc->{customers} };
208   $vc_counts_by->{number}->{vendors}->  {$_->vendornumber}++   for @{ $self->all_vc->{vendors} };
209   $vc_counts_by->{name}->  {customers}->{$_->name}++           for @{ $self->all_vc->{customers} };
210   $vc_counts_by->{name}->  {vendors}->  {$_->name}++           for @{ $self->all_vc->{vendors} };
211   $vc_counts_by->{gln}->   {customers}->{$_->gln}++            for grep $_->gln, @{ $self->all_vc->{customers} };
212   $vc_counts_by->{gln}->   {vendors}->  {$_->gln}++            for grep $_->gln, @{ $self->all_vc->{vendors} };
213
214   return $vc_counts_by;
215 }
216
217 sub check_vc {
218   my ($self, $entry, $id_column) = @_;
219
220   if ($entry->{object}->$id_column) {
221     $entry->{object}->$id_column(undef) if !$self->vc_by->{id}->{ $entry->{object}->$id_column };
222   }
223
224   my $is_ambiguous;
225   if (!$entry->{object}->$id_column) {
226     my $vc;
227     if ($entry->{raw_data}->{customernumber}) {
228       $vc = $self->vc_by->{number}->{customers}->{ $entry->{raw_data}->{customernumber} };
229       if ($vc && $self->vc_counts_by->{number}->{customers}->{ $entry->{raw_data}->{customernumber} } > 1) {
230         $vc = undef;
231         $is_ambiguous = 1;
232       }
233     } elsif ($entry->{raw_data}->{vendornumber}) {
234       $vc = $self->vc_by->{number}->{vendors}->{ $entry->{raw_data}->{vendornumber} };
235       if ($vc && $self->vc_counts_by->{number}->{vendors}->{ $entry->{raw_data}->{vendornumber} } > 1) {
236         $vc = undef;
237         $is_ambiguous = 1;
238       }
239     }
240
241     $entry->{object}->$id_column($vc->id) if $vc;
242   }
243
244   if (!$entry->{object}->$id_column) {
245     my $vc;
246     if ($entry->{raw_data}->{customer}) {
247       $vc = $self->vc_by->{name}->{customers}->{ $entry->{raw_data}->{customer} };
248       if ($vc && $self->vc_counts_by->{name}->{customers}->{ $entry->{raw_data}->{customer} } > 1) {
249         $vc = undef;
250         $is_ambiguous = 1;
251       }
252     } elsif ($entry->{raw_data}->{vendor}) {
253       $vc = $self->vc_by->{name}->{vendors}->{ $entry->{raw_data}->{vendor} };
254       if ($vc && $self->vc_counts_by->{name}->{vendors}->{ $entry->{raw_data}->{vendor} } > 1) {
255         $vc = undef;
256         $is_ambiguous = 1;
257       }
258     }
259
260     $entry->{object}->$id_column($vc->id) if $vc;
261   }
262
263   if (!$entry->{object}->$id_column) {
264     my $vc;
265     if ($entry->{raw_data}->{customer_gln}) {
266       $vc = $self->vc_by->{gln}->{customers}->{ $entry->{raw_data}->{customer_gln} };
267       if ($vc && $self->vc_counts_by->{gln}->{customers}->{ $entry->{raw_data}->{customer_gln} } > 1) {
268         $vc = undef;
269         $is_ambiguous = 1;
270       }
271     } elsif ($entry->{raw_data}->{vendor_gln}) {
272       $vc = $self->vc_by->{gln}->{vendors}->{ $entry->{raw_data}->{vendor_gln} };
273       if ($vc && $self->vc_counts_by->{gln}->{vendors}->{ $entry->{raw_data}->{vendor_gln} } > 1) {
274         $vc = undef;
275         $is_ambiguous = 1;
276       }
277     }
278     $entry->{object}->$id_column($vc->id) if $vc;
279   }
280
281   if ($entry->{object}->$id_column) {
282     $entry->{info_data}->{vc_name} = $self->vc_by->{id}->{ $entry->{object}->$id_column }->name;
283   } else {
284     if ($is_ambiguous) {
285       push @{ $entry->{errors} }, $::locale->text('Error: Customer/vendor is ambiguous');
286     } else {
287       push @{ $entry->{errors} }, $::locale->text('Error: Customer/vendor not found');
288     }
289   }
290 }
291
292 sub handle_cvars {
293   my ($self, $entry) = @_;
294
295   my $object = $entry->{object_to_save} || $entry->{object};
296   return unless $object->can('cvars_by_config');
297
298   my %type_to_column = ( text      => 'text_value',
299                          textfield => 'text_value',
300                          htmlfield => 'text_value',
301                          select    => 'text_value',
302                          date      => 'timestamp_value_as_date',
303                          timestamp => 'timestamp_value_as_date',
304                          number    => 'number_value_as_number',
305                          bool      => 'bool_value' );
306
307   # autovivify all cvars (cvars_by_config will do that for us)
308   my @cvars;
309   my %changed_cvars;
310   foreach my $config (@{ $self->all_cvar_configs }) {
311     next unless exists $entry->{raw_data}->{ "cvar_" . $config->name };
312     my $value  = $entry->{raw_data}->{ "cvar_" . $config->name };
313     my $column = $type_to_column{ $config->type } || die "Program logic error: unknown custom variable storage type";
314
315     my $new_cvar = SL::DB::CustomVariable->new(config_id => $config->id, $column => $value, sub_module => '');
316
317     push @cvars, $new_cvar;
318     $changed_cvars{$config->name} = $new_cvar;
319   }
320
321   # merge existing with new cvars. swap every existing with the imported one, push the rest
322   my @orig_cvars = @{ $object->cvars_by_config };
323   for (@orig_cvars) {
324     $_ = $changed_cvars{ $_->config->name } if $changed_cvars{ $_->config->name };
325     delete $changed_cvars{ $_->config->name };
326   }
327   push @orig_cvars, values %changed_cvars;
328   $object->custom_variables(\@orig_cvars);
329 }
330
331 sub init_profile {
332   my ($self) = @_;
333
334   eval "require " . $self->class;
335
336   my %unwanted = map { ( $_ => 1 ) } (qw(itime mtime), map { $_->name } @{ $self->class->meta->primary_key_columns });
337   delete $unwanted{$_} for ($self->force_allow_columns);
338
339   my %profile;
340   for my $col ($self->class->meta->columns) {
341     next if $unwanted{$col};
342
343     my $name = $col->isa('Rose::DB::Object::Metadata::Column::Numeric')   ? "$col\_as_number"
344       :        $col->isa('Rose::DB::Object::Metadata::Column::Date')      ? "$col\_as_date"
345       :        $col->isa('Rose::DB::Object::Metadata::Column::Timestamp') ? "$col\_as_date"
346       :                                                                     $col->name;
347
348     $profile{$col} = $name;
349   }
350
351   $profile{ 'cvar_' . $_->name } = '' for @{ $self->all_cvar_configs };
352
353   \%profile;
354 }
355
356 sub add_displayable_columns {
357   my ($self, @columns) = @_;
358
359   my @cols       = @{ $self->controller->displayable_columns || [] };
360   my %ex_col_map = map { $_->{name} => $_ } @cols;
361
362   foreach my $column (@columns) {
363     if ($ex_col_map{ $column->{name} }) {
364       @{ $ex_col_map{ $column->{name} } }{ keys %{ $column } } = @{ $column }{ keys %{ $column } };
365     } else {
366       push @cols, $column;
367     }
368   }
369
370   $self->controller->displayable_columns([ sort { $a->{name} cmp $b->{name} } @cols ]);
371 }
372
373 sub setup_displayable_columns {
374   my ($self) = @_;
375
376   $self->add_displayable_columns(map { { name => $_ } } keys %{ $self->profile });
377 }
378
379 sub add_cvar_columns_to_displayable_columns {
380   my ($self) = @_;
381
382   $self->add_displayable_columns(map { { name        => 'cvar_' . $_->name,
383                                          description => $::locale->text('#1 (custom variable)', $_->description) } }
384                                      @{ $self->all_cvar_configs });
385 }
386
387 sub init_existing_objects {
388   my ($self) = @_;
389
390   eval "require " . $self->class;
391   $self->existing_objects($self->manager_class->get_all);
392 }
393
394 sub init_class {
395   die "class not set";
396 }
397
398 sub init_manager_class {
399   my ($self) = @_;
400
401   $self->class =~ m/^SL::DB::(.+)/;
402   $self->manager_class("SL::DB::Manager::" . $1);
403 }
404
405 sub is_multiplexed { 0 }
406
407 sub check_objects {
408 }
409
410 sub check_duplicates {
411 }
412
413 sub check_auth {
414   $::auth->assert('config');
415 }
416
417 sub check_std_duplicates {
418   my $self = shift;
419
420   my $duplicates = {};
421
422   my $all_fields = $self->get_duplicate_check_fields();
423
424   foreach my $key (keys(%{ $all_fields })) {
425     if ( $self->controller->profile->get('duplicates_'. $key) && (!exists($all_fields->{$key}->{std_check}) || $all_fields->{$key}->{std_check} )  ) {
426       $duplicates->{$key} = {};
427     }
428   }
429
430   my @duplicates_keys = keys(%{ $duplicates });
431
432   if ( !scalar(@duplicates_keys) ) {
433     return;
434   }
435
436   if ( $self->controller->profile->get('duplicates') eq 'check_db' ) {
437     foreach my $object (@{ $self->existing_objects }) {
438       foreach my $key (@duplicates_keys) {
439         my $value = exists($all_fields->{$key}->{maker}) ? $all_fields->{$key}->{maker}->($object, $self) : $object->$key;
440         $duplicates->{$key}->{$value} = 'db';
441       }
442     }
443   }
444
445   foreach my $entry (@{ $self->controller->data }) {
446     if ( @{ $entry->{errors} } ) {
447       next;
448     }
449
450     my $object = $entry->{object};
451
452     foreach my $key (@duplicates_keys) {
453       my $value = exists($all_fields->{$key}->{maker}) ? $all_fields->{$key}->{maker}->($object, $self) : $object->$key;
454
455       if ( exists($duplicates->{$key}->{$value}) ) {
456         push(@{ $entry->{errors} }, $duplicates->{$key}->{$value} eq 'db' ? $::locale->text('Duplicate in database') : $::locale->text('Duplicate in CSV file'));
457         last;
458       } else {
459         $duplicates->{$key}->{$value} = 'csv';
460       }
461
462     }
463   }
464
465 }
466
467 sub get_duplicate_check_fields {
468   return {};
469 }
470
471 sub check_payment {
472   my ($self, $entry) = @_;
473
474   my $object = $entry->{object};
475
476   # Check whether or not payment ID is valid.
477   if ($object->payment_id && !$self->payment_terms_by->{id}->{ $object->payment_id }) {
478     push @{ $entry->{errors} }, $::locale->text('Error: Invalid payment terms');
479     return 0;
480   }
481
482   # Map name to ID if given.
483   if (!$object->payment_id && $entry->{raw_data}->{payment}) {
484     my $terms = $self->payment_terms_by->{description}->{ $entry->{raw_data}->{payment} };
485
486     if (!$terms) {
487       push @{ $entry->{errors} }, $::locale->text('Error: Invalid payment terms');
488       return 0;
489     }
490
491     $object->payment_id($terms->id);
492
493     # register payment_id for method copying later
494     $self->clone_methods->{payment_id} = 1;
495   }
496
497   return 1;
498 }
499
500 sub check_delivery_term {
501   my ($self, $entry) = @_;
502
503   my $object = $entry->{object};
504
505   # Check whether or not delivery term ID is valid.
506   if ($object->delivery_term_id && !$self->delivery_terms_by->{id}->{ $object->delivery_term_id }) {
507     push @{ $entry->{errors} }, $::locale->text('Error: Invalid delivery terms');
508     return 0;
509   }
510
511   # Map name to ID if given.
512   if (!$object->delivery_term_id && $entry->{raw_data}->{delivery_term}) {
513     my $terms = $self->delivery_terms_by->{description}->{ $entry->{raw_data}->{delivery_term} };
514
515     if (!$terms) {
516       push @{ $entry->{errors} }, $::locale->text('Error: Invalid delivery terms');
517       return 0;
518     }
519
520     $object->delivery_term_id($terms->id);
521
522     # register delivery_term_id for method copying later
523     $self->clone_methods->{delivery_term_id} = 1;
524   }
525
526   return 1;
527 }
528
529 sub save_objects {
530   my ($self, %params) = @_;
531
532   my $data = $params{data} || $self->controller->data;
533
534   return unless $data->[0];
535   return unless $data->[0]{object};
536
537   # If we store into tables which get numbers from the TransNumberGenerator
538   # we have to lock all tables referenced by the storage table (or by
539   # tables stored alongside with the storage table) that are handled by
540   # the TransNumberGenerator, too.
541   # Otherwise we can run into a deadlock if someone saves a document via
542   # the user interface. The exact behavoir depends on timing.
543   # E.g. we are importing orders and a user want to
544   # book an invoice:
545   # web: locks ar (via before-save hook and TNG (or SL::TransNumber))
546   # importer: locks oe (via before-save hook and TNG) (*)
547   # importer: locks defaults (via before-save hook and TNG)
548   # web: wants to lock defaults (via before-save hook and TNG (or SL::TransNumber)) -> is waiting
549   # importer: wants to save oe and wants to lock referenced tables (here ar) -> is waiting
550   # --> deadlock
551   #
552   # (*) if the importer locks ar here, too, everything is fine, because it will wait here
553   # before locking the defaults table.
554   #
555   # List of referenced tables:
556   # (Locking is done in the transaction below)
557   my %referenced_tables_by_type = (
558     orders          => [qw(ar customer vendor)],
559     delivery_orders => [qw(customer vendor)   ],
560     ar_transactions => [qw(customer)          ],
561     ap_transactions => [qw(vendor)            ],
562   );
563
564   $self->controller->track_progress(phase => 'saving data', progress => 0); # scale from 45..95%;
565
566   my $last_index = $#$data;
567   my $chunk_size = 100;      # one transaction and progress update every 100 objects
568
569   for my $chunk (0 .. $last_index / $chunk_size) {
570     $self->controller->track_progress(progress => ($chunk_size * $chunk)/scalar(@$data) * 100); # scale from 45..95%;
571     SL::DB->client->with_transaction(sub {
572
573       foreach my $refs (@{ $referenced_tables_by_type{$self->controller->{type}} || [] }) {
574         SL::DB->client->dbh->do("LOCK " . $refs) || die SL::DB->client->dbh->errstr;
575       }
576
577       foreach my $entry_index ($chunk_size * $chunk .. min( $last_index, $chunk_size * ($chunk + 1) - 1 )) {
578         my $entry = $data->[$entry_index];
579
580         my $object = $entry->{object_to_save} || $entry->{object};
581         $self->save_additions_always($object);
582
583         next if @{ $entry->{errors} };
584
585         my $ret;
586         if (!eval { $ret = $object->save(cascade => !!$self->save_with_cascade()); 1 }) {
587           push @{ $entry->{errors} }, $::locale->text('Error when saving: #1', $EVAL_ERROR);
588         } elsif ( !$ret ) {
589           push @{ $entry->{errors} }, $::locale->text('Error when saving: #1', $object->db->error);
590         } else {
591           $self->_save_history($object);
592           $self->save_additions($object);
593           $self->controller->num_imported($self->controller->num_imported + 1);
594         }
595       }
596       1;
597     }) or do { die SL::DB->client->error };
598   }
599   $self->controller->track_progress(progress => 100);
600 }
601
602 sub field_lengths {
603   my ($self) = @_;
604
605   return map { $_->name => $_->length } grep { $_->type eq 'varchar' } @{$self->class->meta->columns};
606 }
607
608 sub fix_field_lengths {
609   my ($self) = @_;
610
611   my %field_lengths = $self->field_lengths;
612   foreach my $entry (@{ $self->controller->data }) {
613     next unless @{ $entry->{errors} };
614     map { $entry->{object}->$_(substr($entry->{object}->$_, 0, $field_lengths{$_})) if $entry->{object}->$_ } keys %field_lengths;
615   }
616 }
617
618 sub clean_fields {
619   my ($self, $illegal_chars, $object, @fields) = @_;
620
621   my @cleaned_fields;
622   foreach my $field (grep { $object->can($_) } @fields) {
623     my $value = $object->$field;
624
625     next unless defined($value) && ($value =~ s/$illegal_chars/ /g);
626
627     $object->$field($value);
628     push @cleaned_fields, $field;
629   }
630
631   return @cleaned_fields;
632 }
633
634 sub save_additions {
635   my ($self, $object) = @_;
636
637   # Can be overridden by derived specialized importer classes to save
638   # additional tables (e.g. record links).
639   # This sub is called after the object is saved successfully in an transaction.
640
641   return;
642 }
643
644 sub save_additions_always {
645   my ($self, $object) = @_;
646
647   # Can be overridden by derived specialized importer classes to save
648   # additional tables always.
649   # This sub is called before the object is saved. Therefore this
650   # hook will always be executed whether or not the import entry can be saved successfully.
651
652   return;
653 }
654
655
656 sub _save_history {
657   my ($self, $object) = @_;
658
659   if (any { $self->controller->{type} && $_ eq $self->controller->{type} } qw(parts customers_vendors orders delivery_orders ar_transactions)) {
660     my $snumbers = $self->controller->{type} eq 'parts'             ? 'partnumber_' . $object->partnumber
661                  : $self->controller->{type} eq 'customers_vendors' ?
662                      ($self->table eq 'customer' ? 'customernumber_' . $object->customernumber : 'vendornumber_' . $object->vendornumber)
663                  : $self->controller->{type} eq 'orders'            ? 'ordnumber_' . $object->ordnumber
664                  : $self->controller->{type} eq 'delivery_orders'   ? 'donumber_'  . $object->donumber
665                  : $self->controller->{type} eq 'ar_transactions'   ? 'invnumber_' . $object->invnumber
666                  : '';
667
668     my $what_done = '';
669     if ($self->controller->{type} eq 'orders') {
670       $what_done = $object->customer_id ? 'sales_order' : 'purchase_order';
671     }
672     if ($self->controller->{type} eq 'delivery_orders') {
673       $what_done = $object->customer_id ? 'sales_delivery_order' : 'purchase_delivery_order';
674     }
675
676     SL::DB::History->new(
677       trans_id    => $object->id,
678       snumbers    => $snumbers,
679       employee_id => $self->controller->{employee_id},
680       addition    => 'SAVED',
681       what_done   => $what_done,
682     )->save();
683   }
684 }
685
686 1;