Revision 52518527
Von Martin Helmling martin.helmling@octosoft.eu vor mehr als 8 Jahren hinzugefügt
SL/Controller/CsvImport/Part.pm | ||
---|---|---|
24 | 24 |
( |
25 | 25 |
scalar => [ qw(table makemodel_columns) ], |
26 | 26 |
'scalar --get_set_init' => [ qw(bg_by settings parts_by price_factors_by units_by partsgroups_by |
27 |
warehouses_by bins_by |
|
27 | 28 |
translation_columns all_pricegroups) ], |
28 | 29 |
); |
29 | 30 |
|
... | ... | |
78 | 79 |
return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_units } } ) } qw(name) }; |
79 | 80 |
} |
80 | 81 |
|
82 |
sub init_bins_by { |
|
83 |
my ($self) = @_; |
|
84 |
|
|
85 |
my $all_bins = SL::DB::Manager::Bin->get_all; |
|
86 |
return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_bins } } ) } qw(id description) }; |
|
87 |
} |
|
88 |
|
|
89 |
sub init_warehouses_by { |
|
90 |
my ($self) = @_; |
|
91 |
|
|
92 |
my $all_warehouses = SL::DB::Manager::Warehouse->get_all; |
|
93 |
return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_warehouses } } ) } qw(id description) }; |
|
94 |
} |
|
95 |
|
|
96 |
|
|
81 | 97 |
sub init_parts_by { |
82 | 98 |
my ($self) = @_; |
83 | 99 |
|
... | ... | |
145 | 161 |
$self->check_price_factor($entry); |
146 | 162 |
$self->check_payment($entry); |
147 | 163 |
$self->check_partsgroup($entry); |
164 |
$self->check_warehouse_and_bin($entry); |
|
148 | 165 |
$self->handle_pricegroups($entry); |
149 | 166 |
$self->check_existing($entry) unless @{ $entry->{errors} }; |
150 | 167 |
$self->handle_prices($entry) if $self->settings->{sellprice_adjustment}; |
... | ... | |
156 | 173 |
} continue { |
157 | 174 |
$i++; |
158 | 175 |
} |
159 |
|
|
160 | 176 |
$self->add_columns(qw(type)) if $self->settings->{parts_type} eq 'mixed'; |
161 | 177 |
$self->add_columns(qw(buchungsgruppen_id unit)); |
162 |
$self->add_columns(map { "${_}_id" } grep { exists $self->controller->data->[0]->{raw_data}->{$_} } qw (price_factor payment partsgroup)); |
|
178 |
$self->add_columns(map { "${_}_id" } grep { exists $self->controller->data->[0]->{raw_data}->{$_} } qw (price_factor payment partsgroup warehouse bin));
|
|
163 | 179 |
$self->add_columns(qw(shop)) if $self->settings->{shoparticle_if_missing}; |
164 | 180 |
$self->add_cvar_raw_data_columns; |
165 | 181 |
map { $self->add_raw_data_columns("pricegroup_${_}") if exists $self->controller->data->[0]->{raw_data}->{"pricegroup_$_"} } (1..scalar(@{ $self->all_pricegroups })); |
... | ... | |
198 | 214 |
$default_id = undef unless $self->bg_by->{id}->{ $default_id }; |
199 | 215 |
|
200 | 216 |
# 1. Use default ID if enforced. |
201 |
$object->buchungsgruppen_id($default_id) if $default_id && ($self->settings->{apply_buchungsgruppe} eq 'all'); |
|
217 |
if ($default_id && ($self->settings->{apply_buchungsgruppe} eq 'all')) { |
|
218 |
$object->buchungsgruppen_id($default_id); |
|
219 |
push @{ $entry->{information} }, $::locale->text('Use default booking group because setting is \'all\''); |
|
220 |
} |
|
202 | 221 |
|
203 | 222 |
# 2. Use supplied ID if valid |
204 | 223 |
$object->buchungsgruppen_id(undef) if $object->buchungsgruppen_id && !$self->bg_by->{id}->{ $object->buchungsgruppen_id }; |
... | ... | |
210 | 229 |
} |
211 | 230 |
|
212 | 231 |
# 4. Use default ID if not valid. |
213 |
$object->buchungsgruppen_id($default_id) if !$object->buchungsgruppen_id && $default_id && ($self->settings->{apply_buchungsgruppe} eq 'missing'); |
|
214 |
|
|
232 |
if (!$object->buchungsgruppen_id && $default_id && ($self->settings->{apply_buchungsgruppe} eq 'missing')) { |
|
233 |
$object->buchungsgruppen_id($default_id) ; |
|
234 |
$entry->{buch_information} = $::locale->text('Use default booking group because wanted is missing'); |
|
235 |
} |
|
215 | 236 |
return 1 if $object->buchungsgruppen_id; |
237 |
$entry->{buch_error} = $::locale->text('Error: booking group missing or invalid'); |
|
238 |
return 0; |
|
239 |
} |
|
216 | 240 |
|
217 |
push @{ $entry->{errors} }, $::locale->text('Error: booking group missing or invalid'); |
|
241 |
sub _part_is_used { |
|
242 |
my ($self, $part) = @_; |
|
243 |
|
|
244 |
my $query = |
|
245 |
qq|SELECT COUNT(parts_id) FROM invoice where parts_id = ? |
|
246 |
UNION |
|
247 |
SELECT COUNT(parts_id) FROM assembly where parts_id = ? |
|
248 |
UNION |
|
249 |
SELECT COUNT(parts_id) FROM orderitems where parts_id = ? |
|
250 |
|; |
|
251 |
foreach my $ref (selectall_hashref_query($::form, $part->db->dbh, $query, $part->id, $part->id, $part->id)) { |
|
252 |
return 1 if $ref->{count} != 0; |
|
253 |
} |
|
218 | 254 |
return 0; |
219 | 255 |
} |
220 | 256 |
|
... | ... | |
222 | 258 |
my ($self, $entry) = @_; |
223 | 259 |
|
224 | 260 |
my $object = $entry->{object}; |
261 |
my $raw = $entry->{raw_data}; |
|
225 | 262 |
|
226 | 263 |
if ($object->partnumber && $self->parts_by->{partnumber}{$object->partnumber}) { |
227 |
$entry->{part} = SL::DB::Manager::Part->find_by(partnumber => $object->partnumber); |
|
264 |
$entry->{part} = SL::DB::Manager::Part->get_all( query => [ partnumber => $object->partnumber ], limit => 1, |
|
265 |
with_objects => [ 'translations', 'custom_variables' ] |
|
266 |
) -> [0]; |
|
267 |
if ( !$entry->{part} ) { |
|
268 |
$entry->{part} = SL::DB::Manager::Part->get_all( query => [ partnumber => $object->partnumber ], limit => 1, |
|
269 |
with_objects => [ 'translations' ] |
|
270 |
) -> [0]; |
|
271 |
} |
|
228 | 272 |
} |
229 | 273 |
|
230 | 274 |
if ($entry->{part}) { |
231 |
if ($self->settings->{article_number_policy} eq 'update_prices') { |
|
232 |
if ($self->settings->{parts_type} eq 'mixed' && $entry->{part}->type ne $object->type) { |
|
233 |
push(@{$entry->{errors}}, $::locale->text('Skipping due to existing entry in database with different type')); |
|
234 |
} else { |
|
235 |
map { $entry->{part}->$_( $object->$_ ) if defined $object->$_ } qw(sellprice listprice lastcost); |
|
275 |
if ($entry->{part}->type ne $object->type ) { |
|
276 |
push(@{$entry->{errors}}, $::locale->text('Skipping due to existing entry in database with different type')); |
|
277 |
return; |
|
278 |
} |
|
279 |
if ( $entry->{part}->unit != $object->unit || $entry->{part}->inventory_accno_id != $object->inventory_accno_id ) { |
|
280 |
if ( $entry->{part}->onhand != 0 || $self->_part_is_used($entry->{part})) { |
|
281 |
push(@{$entry->{errors}}, $::locale->text('Skipping due to existing entry with different unit or inventory_accno_id')); |
|
282 |
return; |
|
283 |
} |
|
284 |
} |
|
285 |
} |
|
236 | 286 |
|
287 |
if ($self->settings->{article_number_policy} eq 'update_prices_sn' || $self->settings->{article_number_policy} eq 'update_parts_sn') { |
|
288 |
if (!$entry->{part}) { |
|
289 |
push(@{$entry->{errors}}, $::locale->text('Skipping non-existent article')); |
|
290 |
return; |
|
291 |
} |
|
292 |
} |
|
293 |
|
|
294 |
## checking also doubles in csv !! |
|
295 |
foreach my $csventry (@{ $self->controller->data }) { |
|
296 |
if ( $entry != $csventry && $object->partnumber eq $csventry->{object}->partnumber ) { |
|
297 |
if ( $csventry->{doublechecked} ) { |
|
298 |
push(@{$entry->{errors}}, $::locale->text('Skipping due to same partnumber in csv file')); |
|
299 |
return; |
|
300 |
} |
|
301 |
} |
|
302 |
} |
|
303 |
$entry->{doublechecked} = 1; |
|
304 |
|
|
305 |
if ($entry->{part}) { |
|
306 |
if ($self->settings->{article_number_policy} eq 'update_prices' || $self->settings->{article_number_policy} eq 'update_prices_sn') { |
|
307 |
map { $entry->{part}->$_( $object->$_ ) if defined $object->$_ } qw(sellprice listprice lastcost); |
|
308 |
|
|
309 |
# merge prices |
|
310 |
my %prices_by_pricegroup_id = map { $_->pricegroup->id => $_ } $entry->{part}->prices, $object->prices; |
|
311 |
$entry->{part}->prices(grep { $_ } map { $prices_by_pricegroup_id{$_->id} } @{ $self->all_pricegroups }); |
|
312 |
|
|
313 |
push @{ $entry->{information} }, $::locale->text('Updating prices of existing entry in database'); |
|
314 |
$entry->{object_to_save} = $entry->{part}; |
|
315 |
} elsif ( $self->settings->{article_number_policy} eq 'update_parts' || $self->settings->{article_number_policy} eq 'update_parts_sn') { |
|
316 |
|
|
317 |
# Update parts table |
|
318 |
# copy only the data which is not explicit copied by "methods" |
|
319 |
|
|
320 |
map { $entry->{part}->$_( $object->$_ ) if defined $object->$_ } qw(description notes weight ean rop image |
|
321 |
drawing ve gv |
|
322 |
unit |
|
323 |
has_sernumber not_discountable obsolete |
|
324 |
payment_id |
|
325 |
sellprice listprice lastcost); |
|
326 |
|
|
327 |
if (defined $raw->{"sellprice"} || defined $raw->{"listprice"} || defined $raw->{"lastcost"}) { |
|
237 | 328 |
# merge prices |
238 | 329 |
my %prices_by_pricegroup_id = map { $_->pricegroup->id => $_ } $entry->{part}->prices, $object->prices; |
239 | 330 |
$entry->{part}->prices(grep { $_ } map { $prices_by_pricegroup_id{$_->id} } @{ $self->all_pricegroups }); |
331 |
} |
|
240 | 332 |
|
241 |
push @{ $entry->{information} }, $::locale->text('Updating prices of existing entry in database'); |
|
242 |
$entry->{object_to_save} = $entry->{part}; |
|
333 |
# Update translation |
|
334 |
my @translations; |
|
335 |
push @translations, $entry->{part}->translations; |
|
336 |
foreach my $language (@{ $self->all_languages }) { |
|
337 |
my $desc; |
|
338 |
$desc = $raw->{"description_". $language->article_code} if defined $raw->{"description_". $language->article_code}; |
|
339 |
my $notes; |
|
340 |
$notes = $raw->{"notes_". $language->article_code} if defined $raw->{"notes_". $language->article_code}; |
|
341 |
next unless $desc || $notes; |
|
342 |
|
|
343 |
push @translations, SL::DB::Translation->new(language_id => $language->id, |
|
344 |
translation => $desc, |
|
345 |
longdescription => $notes); |
|
243 | 346 |
} |
244 |
} elsif ( $self->settings->{article_number_policy} eq 'skip' ) { |
|
245 |
push(@{$entry->{errors}}, $::locale->text('Skipping due to existing entry in database')); |
|
347 |
$entry->{part}->translations(\@translations) if @translations; |
|
348 |
|
|
349 |
# Update cvars |
|
350 |
my %type_to_column = ( text => 'text_value', |
|
351 |
textfield => 'text_value', |
|
352 |
select => 'text_value', |
|
353 |
date => 'timestamp_value_as_date', |
|
354 |
timestamp => 'timestamp_value_as_date', |
|
355 |
number => 'number_value_as_number', |
|
356 |
bool => 'bool_value' ); |
|
357 |
my @cvars; |
|
358 |
push @cvars, $entry->{part}->custom_variables; |
|
359 |
foreach my $config (@{ $self->all_cvar_configs }) { |
|
360 |
next unless exists $raw->{ "cvar_" . $config->name }; |
|
361 |
my $value = $raw->{ "cvar_" . $config->name }; |
|
362 |
my $column = $type_to_column{ $config->type } || die "Program logic error: unknown custom variable storage type"; |
|
363 |
push @cvars, SL::DB::CustomVariable->new(config_id => $config->id, $column => $value, sub_module => ''); |
|
364 |
} |
|
365 |
$entry->{part}->custom_variables(\@cvars) if @cvars; |
|
366 |
|
|
367 |
# save Part Update |
|
368 |
push @{ $entry->{information} }, $::locale->text('Updating data of existing entry in database'); |
|
369 |
|
|
370 |
$entry->{object_to_save} = $entry->{part}; |
|
371 |
# copy all other data via "methods" |
|
372 |
my $methods = $self->controller->headers->{methods}; |
|
373 |
$entry->{object_to_save}->$_( $entry->{object}->$_ ) for @{ $methods }, keys %{ $self->clone_methods }; |
|
246 | 374 |
|
375 |
} elsif ( $self->settings->{article_number_policy} eq 'skip' ) { |
|
376 |
push(@{$entry->{errors}}, $::locale->text('Skipping due to existing entry in database')) if ( $entry->{part} ); |
|
247 | 377 |
} else { |
248 |
$object->partnumber('####'); |
|
249 |
push(@{$entry->{errors}}, $::locale->text('Skipping, for assemblies are not importable (yet)')) if $object->type eq 'assembly'; |
|
378 |
#$object->partnumber('####'); |
|
250 | 379 |
} |
251 | 380 |
} else { |
252 |
push(@{$entry->{errors}}, $::locale->text('Skipping, for assemblies are not importable (yet)')) if $object->type eq 'assembly'; |
|
381 |
# set error or info from buch if part not exists |
|
382 |
push @{ $entry->{information} }, $entry->{buch_information} if $entry->{buch_information}; |
|
383 |
push @{ $entry->{errors} }, $entry->{buch_error} if $entry->{buch_error}; |
|
253 | 384 |
} |
254 | 385 |
} |
255 | 386 |
|
... | ... | |
275 | 406 |
sub check_type { |
276 | 407 |
my ($self, $entry) = @_; |
277 | 408 |
|
278 |
my $bg = $self->bg_by->{id}->{ $entry->{object}->buchungsgruppen_id }; |
|
279 |
$bg ||= SL::DB::Buchungsgruppe->new(inventory_accno_id => 1); # does this case ever occur? |
|
280 |
|
|
281 | 409 |
my $type = $self->settings->{parts_type}; |
282 |
if ($type eq 'mixed') { |
|
410 |
|
|
411 |
if ($type eq 'mixed' && $entry->{raw_data}->{type}) { |
|
283 | 412 |
$type = $entry->{raw_data}->{type} =~ m/^p/i ? 'part' |
284 | 413 |
: $entry->{raw_data}->{type} =~ m/^s/i ? 'service' |
285 | 414 |
: $entry->{raw_data}->{type} =~ m/^a/i ? 'assembly' |
286 | 415 |
: undef; |
287 | 416 |
} |
288 | 417 |
|
289 |
$entry->{object}->assembly($type eq 'assembly'); |
|
290 |
|
|
291 | 418 |
# when saving income_accno_id or expense_accno_id use ids from the selected |
292 | 419 |
# $bg according to the default tax_zone (the one with the highest sort |
293 | 420 |
# order). Alternatively one could use the ids from defaults, but they might |
294 | 421 |
# not all be set. |
422 |
# Only use existing bg |
|
295 | 423 |
|
296 |
$entry->{object}->income_accno_id( $bg->income_accno_id( SL::DB::Manager::TaxZone->get_default->id ) );
|
|
424 |
my $bg = $self->bg_by->{id}->{ $entry->{object}->buchungsgruppen_id };
|
|
297 | 425 |
|
298 |
if ($type eq 'part' || $type eq 'service') { |
|
299 |
$entry->{object}->expense_accno_id( $bg->expense_accno_id( SL::DB::Manager::TaxZone->get_default->id ) ); |
|
300 |
} |
|
426 |
# if not set there is an error occurred in check_buchungsgruppe() |
|
427 |
# but if the part exists the new values for accno are ignored |
|
301 | 428 |
|
302 |
if ($type eq 'part') {
|
|
303 |
$entry->{object}->inventory_accno_id( $bg->inventory_accno_id );
|
|
304 |
}
|
|
429 |
if ( $bg ) {
|
|
430 |
$entry->{object}->income_accno_id( $bg->income_accno_id( SL::DB::Manager::TaxZone->get_default->id ) );
|
|
431 |
$self->clone_methods->{income_accno_id} = 1;
|
|
305 | 432 |
|
306 |
if (none { $_ eq $type } qw(part service assembly)) { |
|
307 |
push @{ $entry->{errors} }, $::locale->text('Error: Invalid part type'); |
|
308 |
return 0; |
|
433 |
if ($type eq 'part' || $type eq 'service') { |
|
434 |
$entry->{object}->expense_accno_id( $bg->expense_accno_id( SL::DB::Manager::TaxZone->get_default->id ) ); |
|
435 |
$self->clone_methods->{expense_accno_id} = 1; |
|
436 |
} |
|
309 | 437 |
} |
310 | 438 |
|
439 |
if ($type eq 'part') { |
|
440 |
if ( $bg ) { |
|
441 |
$entry->{object}->inventory_accno_id( $bg->inventory_accno_id ); |
|
442 |
} |
|
443 |
else { |
|
444 |
#use an existent bg |
|
445 |
$entry->{object}->inventory_accno_id( SL::DB::Manager::Buchungsgruppe->get_first->id ); |
|
446 |
} |
|
447 |
} elsif ($type eq 'assembly') { |
|
448 |
$entry->{object}->assembly(1); |
|
449 |
} |
|
311 | 450 |
return 1; |
312 | 451 |
} |
313 | 452 |
|
... | ... | |
332 | 471 |
} |
333 | 472 |
|
334 | 473 |
$object->price_factor_id($pf->id); |
474 |
$self->clone_methods->{price_factor_id} = 1; |
|
475 |
} |
|
476 |
|
|
477 |
return 1; |
|
478 |
} |
|
479 |
|
|
480 |
sub check_warehouse_and_bin { |
|
481 |
my ($self, $entry) = @_; |
|
482 |
|
|
483 |
my $object = $entry->{object}; |
|
484 |
|
|
485 |
# Check whether or not warehouse id is valid. |
|
486 |
if ($object->warehouse_id && !$self->warehouses_by->{id}->{ $object->warehouse_id }) { |
|
487 |
push @{ $entry->{errors} }, $::locale->text('Error: Invalid warehouse id'); |
|
488 |
return 0; |
|
489 |
} |
|
490 |
# Map name to ID if given. |
|
491 |
if (!$object->warehouse_id && $entry->{raw_data}->{warehouse}) { |
|
492 |
my $wh = $self->warehouses_by->{description}->{ $entry->{raw_data}->{warehouse} }; |
|
493 |
|
|
494 |
if (!$wh) { |
|
495 |
push @{ $entry->{errors} }, $::locale->text('Error: Invalid warehouse name #1',$entry->{raw_data}->{warehouse}); |
|
496 |
return 0; |
|
497 |
} |
|
498 |
|
|
499 |
$object->warehouse_id($wh->id); |
|
500 |
} |
|
501 |
$self->clone_methods->{warehouse_id} = 1; |
|
502 |
|
|
503 |
# Check whether or not bin id is valid. |
|
504 |
if ($object->bin_id && !$self->bins_by->{id}->{ $object->bin_id }) { |
|
505 |
push @{ $entry->{errors} }, $::locale->text('Error: Invalid bin id'); |
|
506 |
return 0; |
|
335 | 507 |
} |
508 |
# Map name to ID if given. |
|
509 |
if ($object->warehouse_id && !$object->bin_id && $entry->{raw_data}->{bin}) { |
|
510 |
my $bin = $self->bins_by->{description}->{ $entry->{raw_data}->{bin} }; |
|
511 |
|
|
512 |
if (!$bin) { |
|
513 |
push @{ $entry->{errors} }, $::locale->text('Error: Invalid bin name #1',$entry->{raw_data}->{bin}); |
|
514 |
return 0; |
|
515 |
} |
|
336 | 516 |
|
517 |
$object->bin_id($bin->id); |
|
518 |
} |
|
519 |
$self->clone_methods->{bin_id} = 1; |
|
520 |
|
|
521 |
if ($object->warehouse_id && $object->bin_id ) { |
|
522 |
my $bin = $self->bins_by->{id}->{ $object->bin_id }; |
|
523 |
if ( $bin->warehouse_id != $object->warehouse_id ) { |
|
524 |
push @{ $entry->{errors} }, $::locale->text('Error: Bin #1 is not from warehouse #2', |
|
525 |
$self->bins_by->{id}->{$object->bin_id}->description, |
|
526 |
$self->warehouses_by->{id}->{ $object->warehouse_id }->description); |
|
527 |
return 0; |
|
528 |
} |
|
529 |
} |
|
337 | 530 |
return 1; |
338 | 531 |
} |
339 | 532 |
|
... | ... | |
359 | 552 |
|
360 | 553 |
$object->partsgroup_id($pg->id); |
361 | 554 |
} |
555 |
# register payment_id for method copying later |
|
556 |
$self->clone_methods->{partsgroup_id} = 1; |
|
362 | 557 |
|
363 | 558 |
return 1; |
364 | 559 |
} |
... | ... | |
480 | 675 |
my ($self) = @_; |
481 | 676 |
|
482 | 677 |
my $profile = $self->SUPER::init_profile; |
483 |
delete @{$profile}{qw(assembly bom expense_accno_id income_accno_id inventory_accno_id makemodel priceupdate stockable type)}; |
|
678 |
delete @{$profile}{qw(alternate assembly bom expense_accno_id income_accno_id inventory_accno_id makemodel priceupdate stockable type)};
|
|
484 | 679 |
|
485 | 680 |
$profile->{"pricegroup_$_"} = '' for 1 .. scalar @{ $_[0]->all_pricegroups }; |
486 | 681 |
|
... | ... | |
505 | 700 |
$self->SUPER::setup_displayable_columns; |
506 | 701 |
$self->add_cvar_columns_to_displayable_columns; |
507 | 702 |
|
508 |
$self->add_displayable_columns({ name => 'bin', description => $::locale->text('Bin') }, |
|
509 |
{ name => 'buchungsgruppen_id', description => $::locale->text('Booking group (database ID)') }, |
|
510 |
{ name => 'buchungsgruppe', description => $::locale->text('Booking group (name)') }, |
|
703 |
$self->add_displayable_columns({ name => 'assembly', description => $::locale->text('assembly') }, |
|
704 |
{ name => 'bin_id', description => $::locale->text('Bin (database ID)') }, |
|
705 |
{ name => 'bin', description => $::locale->text('Bin (name)') }, |
|
706 |
{ name => 'buchungsgruppen_id', description => $::locale->text('Booking group (database ID)') }, |
|
707 |
{ name => 'buchungsgruppe', description => $::locale->text('Booking group (name)') }, |
|
511 | 708 |
{ name => 'description', description => $::locale->text('Description') }, |
512 | 709 |
{ name => 'drawing', description => $::locale->text('Drawing') }, |
513 | 710 |
{ name => 'ean', description => $::locale->text('EAN') }, |
... | ... | |
515 | 712 |
{ name => 'gv', description => $::locale->text('Business Volume') }, |
516 | 713 |
{ name => 'has_sernumber', description => $::locale->text('Has serial number') }, |
517 | 714 |
{ name => 'image', description => $::locale->text('Image') }, |
715 |
{ name => 'inventory_accno_id', description => $::locale->text('part') }, |
|
518 | 716 |
{ name => 'lastcost', description => $::locale->text('Last Cost') }, |
519 | 717 |
{ name => 'listprice', description => $::locale->text('List Price') }, |
520 | 718 |
{ name => 'make_X', description => $::locale->text('Make (vendor\'s database ID, number or name; with X being a number)') . ' [1]' }, |
... | ... | |
534 | 732 |
{ name => 'price_factor', description => $::locale->text('Price factor (name)') }, |
535 | 733 |
{ name => 'rop', description => $::locale->text('ROP') }, |
536 | 734 |
{ name => 'sellprice', description => $::locale->text('Sellprice') }, |
537 |
{ name => 'shop', description => $::locale->text('Shop article') }, |
|
735 |
{ name => 'shop', description => $::locale->text('Shop article') },
|
|
538 | 736 |
{ name => 'type', description => $::locale->text('Article type') . ' [3]' }, |
539 | 737 |
{ name => 'unit', description => $::locale->text('Unit (if missing or empty default unit will be used)') }, |
540 | 738 |
{ name => 've', description => $::locale->text('Verrechnungseinheit') }, |
739 |
{ name => 'warehouse_id', description => $::locale->text('Warehouse (database ID)') }, |
|
740 |
{ name => 'warehouse', description => $::locale->text('Warehouse (name)') }, |
|
541 | 741 |
{ name => 'weight', description => $::locale->text('Weight') }, |
542 | 742 |
); |
543 | 743 |
|
doc/changelog | ||
---|---|---|
8 | 8 |
|
9 | 9 |
- Für UStVA Voranmeldung über Elster gibt es die Anbindung über Geierlein (Installation/Config siehe Commit) |
10 | 10 |
|
11 |
- CSV-Import von Artikel hat nun für existierende Artikel folgende Optionen: |
|
12 |
1. Eigenschaften von existierenden Einträgen aktualisieren |
|
13 |
2. Eigenschaften von existierenden Artikeln aktualisieren / Nicht vorhandene überspringen |
|
14 |
3. Preise von vorhandenen Artikeln aktualisieren |
|
15 |
4. Preise von vorhandenen Artikel aktualisieren / Nicht vorhandene überspringen |
|
16 |
5. Mit neuer Artikelnummer einfügen |
|
17 |
6. Eintrag überspringen |
|
18 |
Zusätzlich können nun Spalten "Lager","Lagerort" als Name oder ID eingelesen werden, |
|
19 |
sowie Übersetzungen z.B. als 'description_EN' oder 'description_IT'. |
|
20 |
Auch cvars können als 'cvars_<name>' importiert werden. |
|
21 |
Ebenfalls sind zusätzliche Bemerkungen an den einzelnen Importzeilen eingebaut. |
|
22 |
|
|
11 | 23 |
- In der Lager-Mandantenkonfig gibt es das Feature "Zum Fertigen Standardlager des Bestandteils verwenden". |
12 | 24 |
Statt das Ziellager des Erzeugnisses zu Verwenden, wird nun zur Prüfung der Fertigung das |
13 | 25 |
Standardlager der einzelnen Bestandteile verwendet. |
locale/de/all | ||
---|---|---|
408 | 408 |
'Billing/shipping address (zipcode)' => 'Rechnungs-/Lieferadresse (PLZ)', |
409 | 409 |
'Bin' => 'Lagerplatz', |
410 | 410 |
'Bin (database ID)' => 'Lagerplatz (Datenbank-ID)', |
411 |
'Bin (name)' => 'Lagerplatz (Name)', |
|
411 | 412 |
'Bin From' => 'Quelllagerplatz', |
412 | 413 |
'Bin List' => 'Lagerliste', |
413 | 414 |
'Bin To' => 'Ziellagerplatz', |
... | ... | |
427 | 428 |
'Booking group #1 needs a valid expense account' => 'Buchungsgruppe #1 braucht ein gültiges Aufwandskonto', |
428 | 429 |
'Booking group #1 needs a valid income account' => 'Buchungsgruppe #1 braucht ein gültiges Erfolgskonto', |
429 | 430 |
'Booking group #1 needs a valid inventory account' => 'Buchungsgruppe #1 braucht ein gültiges Warenbestandskonto', |
430 |
'Booking group (database ID)' => 'Buchungsgruppe (Datenbank-ID)',
|
|
431 |
'Booking group (name)' => 'Buchungsgruppe (Name)',
|
|
431 |
'Booking group (database ID)' => 'Buchungsgruppe (database ID)',
|
|
432 |
'Booking group (name)' => 'Buchungsgruppe (name)',
|
|
432 | 433 |
'Booking groups' => 'Buchungsgruppen', |
433 | 434 |
'Books are open' => 'Die Bücher sind geöffnet.', |
434 | 435 |
'Books closed up to' => 'Bücher abgeschlossen bis zum', |
... | ... | |
1151 | 1152 |
'Error: A negative target quantity is not allowed.' => 'Fehler: Eine negative Zielmenge ist nicht erlaubt.', |
1152 | 1153 |
'Error: A quantity and a target quantity could not be given both.' => 'Fehler: Menge und Zielmenge können nicht beide angegeben werden.', |
1153 | 1154 |
'Error: A quantity or a target quantity must be given.' => 'Fehler: Menge oder Zielmenge muss angegeben werden.', |
1155 |
'Error: Bin #1 is not from warehouse #2' => 'Lager \'#2\' hat keinen Lagerplatz \'#1\'', |
|
1154 | 1156 |
'Error: Bin not found' => 'Fehler: Lagerplatz nicht gefunden', |
1155 | 1157 |
'Error: Customer/vendor missing' => 'Fehler: Kunde/Lieferant fehlt', |
1156 | 1158 |
'Error: Customer/vendor not found' => 'Fehler: Kunde/Lieferant nicht gefunden', |
1157 | 1159 |
'Error: Found local bank account number but local bank code doesn\'t match' => 'Fehler: Kontonummer wurde gefunden aber gespeicherte Bankleitzahl stimmt nicht überein', |
1158 | 1160 |
'Error: Gender (cp_gender) missing or invalid' => 'Fehler: Geschlecht (cp_gender) fehlt oder ungültig', |
1159 | 1161 |
'Error: Invalid bin' => 'Fehler: Ungültiger Lagerplatz', |
1162 |
'Error: Invalid bin id' => 'Ungültige Lagerplatz-ID', |
|
1163 |
'Error: Invalid bin name #1' => 'Ungültiger Lagerplatz \'#1\'', |
|
1160 | 1164 |
'Error: Invalid business' => 'Fehler: Kunden-/Lieferantentyp ungültig', |
1161 | 1165 |
'Error: Invalid contact' => 'Fehler: Ansprechperson ungültig', |
1162 | 1166 |
'Error: Invalid currency' => 'Fehler: ungültige Währung', |
... | ... | |
1165 | 1169 |
'Error: Invalid language' => 'Fehler: Sprache ungültig', |
1166 | 1170 |
'Error: Invalid order for this order item' => 'Fehler: Auftrag für diese Position ungültig', |
1167 | 1171 |
'Error: Invalid part' => 'Fehler: Artikel ungültig', |
1168 |
'Error: Invalid part type' => 'Fehler: Artikeltyp ungültig', |
|
1169 | 1172 |
'Error: Invalid parts group' => 'Fehler: Warengruppe ungültig', |
1170 | 1173 |
'Error: Invalid payment terms' => 'Fehler: Zahlungsbedingungen ungültig', |
1171 | 1174 |
'Error: Invalid price factor' => 'Fehler: Preisfaktor ungültig', |
... | ... | |
1177 | 1180 |
'Error: Invalid unit' => 'Fehler: Einheit ungültig', |
1178 | 1181 |
'Error: Invalid vendor in column make_#1' => 'Fehler: Lieferant ungültig in Spalte make_#1', |
1179 | 1182 |
'Error: Invalid warehouse' => 'Fehler: Ungültiges Lager', |
1183 |
'Error: Invalid warehouse id' => 'Ungültige Lager-ID', |
|
1184 |
'Error: Invalid warehouse name #1' => 'Ungültiger Lagername \'#1\'', |
|
1180 | 1185 |
'Error: Name missing' => 'Fehler: Name fehlt', |
1181 | 1186 |
'Error: Part not found' => 'Fehler: Artikel nicht gefunden', |
1182 | 1187 |
'Error: Quantity to transfer is zero.' => 'Fehler: Zu bewegende Menge ist Null.', |
... | ... | |
2578 | 2583 |
'Skipping due to existing bank transaction in database' => 'Wegen schon existierender Bankbewegung in Datenbank übersprungen', |
2579 | 2584 |
'Skipping due to existing entry in database' => 'Wegen existierendem Eintrag mit selber Nummer übersprungen', |
2580 | 2585 |
'Skipping due to existing entry in database with different type' => 'Wegen existierendem Eintrag von unterschiedlichem Artikeltyp übersprungen', |
2581 |
'Skipping, for assemblies are not importable (yet)' => 'Übersprungen, da Erzeugnisse (noch) nicht importiert werden können', |
|
2586 |
'Skipping due to existing entry with different unit or inventory_accno_id' => 'Wegen existierendem und verwendetem Eintrag von unterschiedlicher Einheit oder Buchungsgruppe übersprungen', |
|
2587 |
'Skipping due to same partnumber in csv file' => 'Eintrag in Datei mit doppelter Artikelnummer wird übersprungen', |
|
2588 |
'Skipping non-existent article' => 'Überspringe nicht vorhandenen Artikel', |
|
2582 | 2589 |
'Skonto' => 'Skonto', |
2583 | 2590 |
'Skonto Terms' => 'Zahlungsziel Skonto', |
2584 | 2591 |
'Skonto amount' => 'Skontobetrag', |
... | ... | |
3241 | 3248 |
'Update SKR04: new tax account 3804 (19%)' => 'Update SKR04: neues Steuerkonto 3804 (19%) für innergemeinschaftlichen Erwerb', |
3242 | 3249 |
'Update prices' => 'Preise aktualisieren', |
3243 | 3250 |
'Update prices of existing entries' => 'Preise von vorhandenen Artikeln aktualisieren', |
3251 |
'Update prices of existing entries / skip non-existent' => 'Preise von vorhandenen Artikel aktualisieren / Nicht vorhandene überspringen', |
|
3244 | 3252 |
'Update properties of existing entries' => 'Eigenschaften von existierenden Einträgen aktualisieren', |
3253 |
'Update properties of existing entries / skip non-existent' => 'Eigenschaften von existierenden Artikeln aktualisieren / Nicht vorhandene überspringen', |
|
3245 | 3254 |
'Update quotation/order' => 'Auftrag/Angebot aktualisieren', |
3246 | 3255 |
'Update sales order #1' => 'Kundenauftrag #1 aktualisieren', |
3247 | 3256 |
'Update sales quotation #1' => 'Angebot #1 aktualisieren', |
3248 | 3257 |
'Update this draft.' => 'Aktuellen Entwurf speichern', |
3249 | 3258 |
'Update with section' => 'Mit Abschnitt aktualisieren', |
3250 | 3259 |
'Updated' => 'Erneuert am', |
3260 |
'Updating data of existing entry in database' => 'Aktualisierung von vorhandenen Datenbankdaten', |
|
3251 | 3261 |
'Updating existing entry in database' => 'Existierenden Eintrag in Datenbank aktualisieren', |
3252 | 3262 |
'Updating items with additional parts' => 'Positionen für zusätzliche Artikel aktualisieren', |
3253 | 3263 |
'Updating items with sections' => 'Positionen für Abschnitte aktualisieren', |
... | ... | |
3262 | 3272 |
'Use Income' => 'GUV und BWA verwenden', |
3263 | 3273 |
'Use UStVA' => 'UStVA verwenden', |
3264 | 3274 |
'Use WebDAV Repository' => 'WebDAV-Ablage verwenden', |
3275 |
'Use default booking group because setting is \'all\'' => 'Standardbuchungsgruppe wird verwendet', |
|
3276 |
'Use default booking group because wanted is missing' => 'Fehlende Buchungsgruppe, deshalb Standardbuchungsgruppe', |
|
3265 | 3277 |
'Use default warehouse for assembly transfer' => 'Zum Fertigen Standardlager des Bestandteils verwenden', |
3266 | 3278 |
'Use existing templates' => 'Vorhandene Druckvorlagen verwenden', |
3267 | 3279 |
'Use linked items' => 'Verknüpfte Positionen verwenden', |
... | ... | |
3329 | 3341 |
'WHJournal' => 'Lagerbuchungen', |
3330 | 3342 |
'Warehouse' => 'Lager', |
3331 | 3343 |
'Warehouse (database ID)' => 'Lager (Datenbank-ID)', |
3344 |
'Warehouse (name)' => 'Lager (Name)', |
|
3332 | 3345 |
'Warehouse From' => 'Quelllager', |
3333 | 3346 |
'Warehouse Migration' => 'Lagermigration', |
3334 | 3347 |
'Warehouse To' => 'Ziellager', |
t/controllers/csvimport/parts.t | ||
---|---|---|
1 |
use Test::More tests => 33; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use lib 't'; |
|
6 |
|
|
7 |
use Carp; |
|
8 |
use Data::Dumper; |
|
9 |
use Support::TestSetup; |
|
10 |
use Test::Exception; |
|
11 |
|
|
12 |
use List::MoreUtils qw(pairwise); |
|
13 |
use SL::Controller::CsvImport; |
|
14 |
|
|
15 |
my $DEBUG = 0; |
|
16 |
|
|
17 |
use_ok 'SL::Controller::CsvImport::Part'; |
|
18 |
|
|
19 |
use SL::DB::Buchungsgruppe; |
|
20 |
use SL::DB::Currency; |
|
21 |
use SL::DB::Customer; |
|
22 |
use SL::DB::Language; |
|
23 |
use SL::DB::Warehouse; |
|
24 |
use SL::DB::Bin; |
|
25 |
|
|
26 |
my ($translation, $bin1_1, $bin1_2, $bin2_1, $bin2_2, $wh1, $wh2, $bugru, $cvarconfig ); |
|
27 |
|
|
28 |
Support::TestSetup::login(); |
|
29 |
|
|
30 |
sub reset_state { |
|
31 |
# Create test data |
|
32 |
|
|
33 |
clear_up(); |
|
34 |
|
|
35 |
$translation = SL::DB::Language->new( |
|
36 |
description => 'Englisch', |
|
37 |
article_code => 'EN', |
|
38 |
template_code => 'EN', |
|
39 |
)->save; |
|
40 |
$translation = SL::DB::Language->new( |
|
41 |
description => 'Italienisch', |
|
42 |
article_code => 'IT', |
|
43 |
template_code => 'IT', |
|
44 |
)->save; |
|
45 |
$wh1 = SL::DB::Warehouse->new( |
|
46 |
description => 'Lager1', |
|
47 |
sortkey => 1, |
|
48 |
)->save; |
|
49 |
$bin1_1 = SL::DB::Bin->new( |
|
50 |
description => 'Ort1_von_Lager1', |
|
51 |
warehouse_id => $wh1->id, |
|
52 |
)->save; |
|
53 |
$bin1_2 = SL::DB::Bin->new( |
|
54 |
description => 'Ort2_von_Lager1', |
|
55 |
warehouse_id => $wh1->id, |
|
56 |
)->save; |
|
57 |
$wh2 = SL::DB::Warehouse->new( |
|
58 |
description => 'Lager2', |
|
59 |
sortkey => 2, |
|
60 |
)->save; |
|
61 |
$bin2_1 = SL::DB::Bin->new( |
|
62 |
description => 'Ort1_von_Lager2', |
|
63 |
warehouse_id => $wh2->id, |
|
64 |
)->save; |
|
65 |
$bin2_2 = SL::DB::Bin->new( |
|
66 |
description => 'Ort2_von_Lager2', |
|
67 |
warehouse_id => $wh2->id, |
|
68 |
)->save; |
|
69 |
|
|
70 |
$cvarconfig = SL::DB::CustomVariableConfig->new( |
|
71 |
module => 'IC', |
|
72 |
name => 'mycvar', |
|
73 |
type => 'text', |
|
74 |
description => 'mein schattz', |
|
75 |
searchable => 1, |
|
76 |
sortkey => 1, |
|
77 |
includeable => 0, |
|
78 |
included_by_default => 0, |
|
79 |
)->save; |
|
80 |
} |
|
81 |
|
|
82 |
$bugru = SL::DB::Manager::Buchungsgruppe->find_by(description => { like => 'Standard%19%' }); |
|
83 |
|
|
84 |
reset_state(); |
|
85 |
|
|
86 |
##### |
|
87 |
sub test_import { |
|
88 |
my ($file,$settings) = @_; |
|
89 |
my @profiles; |
|
90 |
my $controller = SL::Controller::CsvImport->new(); |
|
91 |
|
|
92 |
my $csv_part_import = SL::Controller::CsvImport::Part->new( |
|
93 |
settings => $settings, |
|
94 |
controller => $controller, |
|
95 |
file => $file, |
|
96 |
); |
|
97 |
|
|
98 |
$csv_part_import->init_bg_by; |
|
99 |
$csv_part_import->init_price_factors_by; |
|
100 |
$csv_part_import->init_partsgroups_by; |
|
101 |
$csv_part_import->init_units_by; |
|
102 |
$csv_part_import->init_bins_by; |
|
103 |
$csv_part_import->init_warehouses_by; |
|
104 |
$csv_part_import->init_parts_by; |
|
105 |
$csv_part_import->test_run(0); |
|
106 |
$csv_part_import->csv(SL::Helper::Csv->new(file => $csv_part_import->file, |
|
107 |
profile => [{ profile => $csv_part_import->profile, |
|
108 |
class => $csv_part_import->class, |
|
109 |
mapping => $csv_part_import->controller->mappings_for_profile }], |
|
110 |
encoding => 'utf-8', |
|
111 |
ignore_unknown_columns => 1, |
|
112 |
strict_profile => 1, |
|
113 |
case_insensitive_header => 1, |
|
114 |
sep_char => ';', |
|
115 |
quote_char => '"', |
|
116 |
ignore_unknown_columns => 1, |
|
117 |
)); |
|
118 |
|
|
119 |
$csv_part_import->csv->parse; |
|
120 |
|
|
121 |
$csv_part_import->controller->errors([ $csv_part_import->csv->errors ]) if $csv_part_import->csv->errors; |
|
122 |
|
|
123 |
return if ( !$csv_part_import->csv->header || $csv_part_import->csv->errors ); |
|
124 |
|
|
125 |
my $headers = { headers => [ grep { $csv_part_import->csv->dispatcher->is_known($_, 0) } @{ $csv_part_import->csv->header } ] }; |
|
126 |
$headers->{methods} = [ map { $_->{path} } @{ $csv_part_import->csv->specs->[0] } ]; |
|
127 |
$headers->{used} = { map { ($_ => 1) } @{ $headers->{headers} } }; |
|
128 |
$csv_part_import->controller->headers($headers); |
|
129 |
$csv_part_import->controller->raw_data_headers({ used => { }, headers => [ ] }); |
|
130 |
$csv_part_import->controller->info_headers({ used => { }, headers => [ ] }); |
|
131 |
|
|
132 |
my $objects = $csv_part_import->csv->get_objects; |
|
133 |
my @raw_data = @{ $csv_part_import->csv->get_data }; |
|
134 |
|
|
135 |
$csv_part_import->controller->data([ pairwise { no warnings 'once'; { object => $a, raw_data => $b, errors => [], information => [], info_data => {} } } @$objects, @raw_data ]); |
|
136 |
|
|
137 |
$csv_part_import->check_objects; |
|
138 |
|
|
139 |
# don't try and save objects that have errors |
|
140 |
$csv_part_import->save_objects unless scalar @{$csv_part_import->controller->data->[0]->{errors}}; |
|
141 |
|
|
142 |
return $csv_part_import->controller->data; |
|
143 |
} |
|
144 |
|
|
145 |
$::myconfig{numberformat} = '1000.00'; |
|
146 |
my $old_locale = $::locale; |
|
147 |
# set locale to en so we can match errors |
|
148 |
$::locale = Locale->new('en'); |
|
149 |
|
|
150 |
|
|
151 |
my ($entries, $entry, $file); |
|
152 |
|
|
153 |
# different settings for tests |
|
154 |
# |
|
155 |
|
|
156 |
my $settings1 = { |
|
157 |
sellprice_places => 2, |
|
158 |
sellprice_adjustment => 0, |
|
159 |
sellprice_adjustment_type => 'percent', |
|
160 |
article_number_policy => 'update_prices', |
|
161 |
shoparticle_if_missing => '0', |
|
162 |
parts_type => 'part', |
|
163 |
default_buchungsgruppe => ($bugru ? $bugru->id : undef), |
|
164 |
apply_buchungsgruppe => 'all', |
|
165 |
}; |
|
166 |
my $settings2 = { |
|
167 |
sellprice_places => 2, |
|
168 |
sellprice_adjustment => 0, |
|
169 |
sellprice_adjustment_type => 'percent', |
|
170 |
article_number_policy => 'update_parts', |
|
171 |
shoparticle_if_missing => '0', |
|
172 |
parts_type => 'part', |
|
173 |
default_buchungsgruppe => ($bugru ? $bugru->id : undef), |
|
174 |
apply_buchungsgruppe => 'missing', |
|
175 |
default_unit => 'Stck', |
|
176 |
}; |
|
177 |
|
|
178 |
# |
|
179 |
# |
|
180 |
# starting test of csv imports |
|
181 |
# to debug errors in certain tests, run after test_import: |
|
182 |
# die Dumper($entry->{errors}); |
|
183 |
|
|
184 |
|
|
185 |
##### create part |
|
186 |
$file = \<<EOL; |
|
187 |
partnumber;sellprice;lastcost;listprice;unit |
|
188 |
P1000;100.10;90.20;95.30;kg |
|
189 |
EOL |
|
190 |
$entries = test_import($file,$settings1); |
|
191 |
$entry = $entries->[0]; |
|
192 |
#foreach my $err ( @{ $entry->{errors} } ) { |
|
193 |
# print $err; |
|
194 |
#} |
|
195 |
is $entry->{object}->partnumber,'P1000', 'partnumber'; |
|
196 |
is $entry->{object}->sellprice, '100.1', 'sellprice'; |
|
197 |
is $entry->{object}->lastcost, '90.2', 'lastcost'; |
|
198 |
is $entry->{object}->listprice, '95.3', 'listprice'; |
|
199 |
|
|
200 |
##### update prices of part |
|
201 |
$file = \<<EOL; |
|
202 |
partnumber;sellprice;lastcost;listprice;unit |
|
203 |
P1000;110.10;95.20;97.30;kg |
|
204 |
EOL |
|
205 |
$entries = test_import($file,$settings1); |
|
206 |
$entry = $entries->[0]; |
|
207 |
is $entry->{object}->sellprice, '110.1', 'updated sellprice'; |
|
208 |
is $entry->{object}->lastcost, '95.2', 'updated lastcost'; |
|
209 |
is $entry->{object}->listprice, '97.3', 'updated listprice'; |
|
210 |
|
|
211 |
##### insert parts with warehouse,bin name |
|
212 |
|
|
213 |
$file = \<<EOL; |
|
214 |
partnumber;description;warehouse;bin |
|
215 |
P1000;Teil 1000;Lager1;Ort1_von_Lager1 |
|
216 |
P1001;Teil 1001;Lager1;Ort2_von_Lager1 |
|
217 |
P1002;Teil 1002;Lager2;Ort1_von_Lager2 |
|
218 |
P1003;Teil 1003;Lager2;Ort2_von_Lager2 |
|
219 |
EOL |
|
220 |
$entries = test_import($file,$settings2); |
|
221 |
$entry = $entries->[0]; |
|
222 |
is $entry->{object}->description, 'Teil 1000', 'Teil 1000 set'; |
|
223 |
is $entry->{object}->warehouse_id, $wh1->id, 'Lager1'; |
|
224 |
is $entry->{object}->bin_id, $bin1_1->id, 'Lagerort1'; |
|
225 |
$entry = $entries->[2]; |
|
226 |
is $entry->{object}->description, 'Teil 1002', 'Teil 1002 set'; |
|
227 |
is $entry->{object}->warehouse_id, $wh2->id, 'Lager2'; |
|
228 |
is $entry->{object}->bin_id, $bin2_1->id, 'Lagerort1'; |
|
229 |
|
|
230 |
##### update warehouse and bin |
|
231 |
$file = \<<EOL; |
|
232 |
partnumber;description;warehouse;bin |
|
233 |
P1000;Teil 1000;Lager2;Ort1_von_Lager2 |
|
234 |
P1001;Teil 1001;Lager1;Ort1_von_Lager1 |
|
235 |
P1002;Teil 1002;Lager2;Ort1_von_Lager1 |
|
236 |
P1003;Teil 1003;Lager2;kein Lagerort |
|
237 |
EOL |
|
238 |
$entries = test_import($file,$settings2); |
|
239 |
$entry = $entries->[0]; |
|
240 |
is $entry->{object}->description, 'Teil 1000', 'Teil 1000 set'; |
|
241 |
is $entry->{object}->warehouse_id, $wh2->id, 'Lager2'; |
|
242 |
is $entry->{object}->bin_id, $bin2_1->id, 'Lagerort1'; |
|
243 |
$entry = $entries->[2]; |
|
244 |
my $err1 = @{ $entry->{errors} }[0]; |
|
245 |
#print "'".$err1."'\n"; |
|
246 |
is $entry->{object}->description, 'Teil 1002', 'Teil 1002 set'; |
|
247 |
is $entry->{object}->warehouse_id, $wh2->id, 'Lager2'; |
|
248 |
is $err1, 'Error: Bin Ort1_von_Lager1 is not from warehouse Lager2','kein Lager von Lager2'; |
|
249 |
$entry = $entries->[3]; |
|
250 |
$err1 = @{ $entry->{errors} }[0]; |
|
251 |
#print "'".$err1."'\n"; |
|
252 |
is $entry->{object}->description, 'Teil 1003', 'Teil 1003 set'; |
|
253 |
is $entry->{object}->warehouse_id, $wh2->id, 'Lager2'; |
|
254 |
is $err1, 'Error: Invalid bin name kein Lagerort','kein Lagerort'; |
|
255 |
|
|
256 |
##### add translations |
|
257 |
$file = \<<EOL; |
|
258 |
partnumber;description;description_EN;notes_EN;description_IT;notes_IT |
|
259 |
P1000;Teil 1000;descr EN 1000;notes EN;descr IT 1000;notes IT |
|
260 |
P1001;Teil 1001;descr EN 1001;notes EN;descr IT 1001;notes IT |
|
261 |
P1002;Teil 1002;descr EN 1002;notes EN;descr IT 1002;notes IT |
|
262 |
P1003;Teil 1003;descr EN 1003;notes EN;descr IT 1003;notes IT |
|
263 |
EOL |
|
264 |
$entries = test_import($file,$settings2); |
|
265 |
$entry = $entries->[0]; |
|
266 |
is $entry->{object}->description, 'Teil 1000', 'Teil 1000 set'; |
|
267 |
is $entry->{raw_data}->{description_EN},'descr EN 1000','EN set'; |
|
268 |
is $entry->{raw_data}->{description_IT},'descr IT 1000','IT set'; |
|
269 |
my $l = @{$entry->{object}->translations}[0]; |
|
270 |
is $l->translation,'descr EN 1000','EN trans set'; |
|
271 |
is $l->longdescription, 'notes EN','EN notes set'; |
|
272 |
$l = @{$entry->{object}->translations}[1]; |
|
273 |
is $l->translation,'descr IT 1000','IT trans set'; |
|
274 |
is $l->longdescription, 'notes IT','IT notes set'; |
|
275 |
|
|
276 |
##### add customvar |
|
277 |
$file = \<<EOL; |
|
278 |
partnumber;cvar_mycvar |
|
279 |
P1000;das ist der ring |
|
280 |
P1001;nicht der nibelungen |
|
281 |
P1002;sondern vom |
|
282 |
P1003;Herr der Ringe |
|
283 |
EOL |
|
284 |
$entries = test_import($file,$settings2); |
|
285 |
$entry = $entries->[0]; |
|
286 |
is $entry->{object}->partnumber, 'P1000', 'P1000 set'; |
|
287 |
is $entry->{raw_data}->{cvar_mycvar},'das ist der ring','CVAR set'; |
|
288 |
is @{$entry->{object}->custom_variables}[0]->text_value,'das ist der ring','Cvar mit richtigem Weert'; |
|
289 |
|
|
290 |
clear_up(); # remove all data at end of tests |
|
291 |
|
|
292 |
# end of tests |
|
293 |
|
|
294 |
|
|
295 |
sub clear_up { |
|
296 |
SL::DB::Manager::Part ->delete_all(all => 1); |
|
297 |
SL::DB::Manager::Translation->delete_all(all => 1); |
|
298 |
SL::DB::Manager::Language ->delete_all(all => 1); |
|
299 |
SL::DB::Manager::Bin ->delete_all(all => 1); |
|
300 |
SL::DB::Manager::Warehouse ->delete_all(all => 1); |
|
301 |
SL::DB::Manager::CustomVariableConfig->delete_all(all => 1); |
|
302 |
} |
|
303 |
|
|
304 |
|
|
305 |
1; |
|
306 |
|
|
307 |
##### |
|
308 |
# vim: ft=perl |
|
309 |
# set emacs to perl mode |
|
310 |
# Local Variables: |
|
311 |
# mode: perl |
|
312 |
# End: |
templates/webpages/csv_import/_form_parts.html | ||
---|---|---|
3 | 3 |
<tr> |
4 | 4 |
<th align="right">[%- LxERP.t8('Parts with existing part numbers') %]:</th> |
5 | 5 |
<td colspan="10"> |
6 |
[% opts = [ [ 'update_prices', LxERP.t8('Update prices of existing entries') ], [ 'insert_new', LxERP.t8('Insert with new part number') ], [ 'skip', LxERP.t8('Skip entry') ] ] %]
|
|
6 |
[% opts = [[ 'update_parts', LxERP.t8('Update properties of existing entries') ], [ 'update_parts_sn', LxERP.t8('Update properties of existing entries / skip non-existent') ], [ 'update_prices', LxERP.t8('Update prices of existing entries') ],[ 'update_prices_sn', LxERP.t8('Update prices of existing entries / skip non-existent') ] ,[ 'insert_new', LxERP.t8('Insert with new part number') ], [ 'skip', LxERP.t8('Skip entry') ] ] %]
|
|
7 | 7 |
[% L.select_tag('settings.article_number_policy', opts, default = SELF.profile.get('article_number_policy'), style = 'width: 300px') %] |
8 | 8 |
</td> |
9 | 9 |
</tr> |
Auch abrufbar als: Unified diff
CSV-Import Artikel: Einige Erweiterungen
CSV-Import von Artikel hat nun für existierende Artikel folgende Optionen: