1 package SL::Controller::CsvImport::Part;
7 use SL::DB::Buchungsgruppe;
8 use SL::DB::CustomVariable;
9 use SL::DB::CustomVariableConfig;
11 use SL::DB::PartsGroup;
12 use SL::DB::PaymentTerm;
13 use SL::DB::PriceFactor;
14 use SL::DB::Translation;
17 use parent qw(SL::Controller::CsvImport::Base);
19 use Rose::Object::MakeMethods::Generic
21 scalar => [ qw(table) ],
22 'scalar --get_set_init' => [ qw(bg_by settings parts_by price_factors_by units_by payment_terms_by packing_types_by partsgroups_by
23 all_languages translation_columns) ],
28 $self->class('SL::DB::Part');
34 my $all_bg = SL::DB::Manager::Buchungsgruppe->get_all;
35 return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_bg } } ) } qw(id description) };
38 sub init_price_factors_by {
41 my $all_price_factors = SL::DB::Manager::PriceFactor->get_all;
42 return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_price_factors } } ) } qw(id description) };
45 sub init_payment_terms_by {
48 my $all_payment_terms = SL::DB::Manager::PaymentTerm->get_all;
49 return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_payment_terms } } ) } qw(id description) };
52 sub init_packing_types_by {
55 my $all_packing_types = SL::DB::Manager::PackingType->get_all;
56 return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_packing_types } } ) } qw(id description) };
59 sub init_partsgroups_by {
62 my $all_partsgroups = SL::DB::Manager::PartsGroup->get_all;
63 return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_partsgroups } } ) } qw(id partsgroup) };
69 my $all_units = SL::DB::Manager::Unit->get_all;
70 return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_units } } ) } qw(name) };
76 my $parts_by = { id => { map { ( $_->id => $_ ) } grep { !$_->assembly } @{ $self->existing_objects } },
77 partnumber => { part => { },
80 foreach my $part (@{ $self->existing_objects }) {
81 next if $part->assembly;
82 $parts_by->{partnumber}->{ $part->type }->{ $part->partnumber } = $part;
91 return { map { ( $_ => $self->controller->profile->get($_) ) } qw(apply_buchungsgruppe default_buchungsgruppe article_number_policy
92 sellprice_places sellprice_adjustment sellprice_adjustment_type
93 shoparticle_if_missing parts_type) };
96 sub init_all_languages {
99 return SL::DB::Manager::Language->get_all;
102 sub init_all_cvar_configs {
105 return SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'IC' ]);
108 sub init_translation_columns {
111 return [ map { ("description_" . $_->article_code, "notes_" . $_->article_code) } (@{ $self->all_languages }) ];
117 return unless @{ $self->controller->data };
119 foreach my $entry (@{ $self->controller->data }) {
120 my $object = $entry->{object};
121 my $raw_data = $entry->{raw_data};
123 next unless $self->check_buchungsgruppe($entry);
124 next unless $self->check_type($entry);
125 next unless $self->check_unit($entry);
126 next unless $self->check_price_factor($entry);
127 next unless $self->check_payment($entry);
128 next unless $self->check_packing_type($entry);
129 next unless $self->check_partsgroup($entry);
130 $self->check_existing($entry);
131 $self->handle_prices($entry) if $self->settings->{sellprice_adjustment};
132 $self->handle_shoparticle($entry);
133 $self->handle_translations($entry);
134 $self->handle_cvars($entry);
135 $self->set_various_fields($entry);
138 $self->add_columns(qw(type)) if $self->settings->{parts_type} eq 'mixed';
139 $self->add_columns(qw(buchungsgruppen_id unit));
140 $self->add_columns(map { "${_}_id" } grep { exists $self->controller->data->[0]->{raw_data}->{$_} } qw (price_factor payment packing_type partsgroup));
141 $self->add_columns(qw(shop)) if $self->settings->{shoparticle_if_missing};
142 $self->add_cvar_raw_data_columns;
143 map { $self->add_raw_data_columns($_) if exists $self->controller->data->[0]->{raw_data}->{$_} } @{ $self->translation_columns };
146 sub check_duplicates {
147 my ($self, %params) = @_;
149 my $normalizer = sub { my $name = $_[0]; $name =~ s/[\s,\.\-]//g; return $name; };
150 my $name_maker = sub { return $normalizer->($_[0]->description) };
153 if ('check_db' eq $self->controller->profile->get('duplicates')) {
154 %by_name = map { ( $name_maker->($_) => 'db' ) } @{ $self->existing_objects };
157 foreach my $entry (@{ $self->controller->data }) {
158 next if @{ $entry->{errors} };
160 my $name = $name_maker->($entry->{object});
162 if (!$by_name{ $name }) {
163 $by_name{ $name } = 'csv';
166 push @{ $entry->{errors} }, $by_name{ $name } eq 'db' ? $::locale->text('Duplicate in database') : $::locale->text('Duplicate in CSV file');
171 sub check_buchungsgruppe {
172 my ($self, $entry) = @_;
174 my $object = $entry->{object};
176 # Check Buchungsgruppe
178 # Store and verify default ID.
179 my $default_id = $self->settings->{default_buchungsgruppe};
180 $default_id = undef unless $self->bg_by->{id}->{ $default_id };
182 # 1. Use default ID if enforced.
183 $object->buchungsgruppen_id($default_id) if $default_id && ($self->settings->{apply_buchungsgruppe} eq 'all');
185 # 2. Use supplied ID if valid
186 $object->buchungsgruppen_id(undef) if $object->buchungsgruppen_id && !$self->bg_by->{id}->{ $object->buchungsgruppen_id };
188 # 3. Look up name if supplied.
189 if (!$object->buchungsgruppen_id) {
190 my $bg = $self->bg_by->{description}->{ $entry->{raw_data}->{buchungsgruppe} };
191 $object->buchungsgruppen_id($bg->id) if $bg;
194 # 4. Use default ID if not valid.
195 $object->buchungsgruppen_id($default_id) if !$object->buchungsgruppen_id && $default_id && ($self->settings->{apply_buchungsgruppe} eq 'missing');
197 return 1 if $object->buchungsgruppen_id;
199 push @{ $entry->{errors} }, $::locale->text('Error: Buchungsgruppe missing or invalid');
204 my ($self, $entry) = @_;
206 my $object = $entry->{object};
208 my $entry->{part} = $self->parts_by->{partnumber}->{ $object->type }->{ $object->partnumber };
210 if ($self->settings->{article_number_policy} eq 'update_prices') {
211 if ($entry->{part}) {
212 map { $object->$_( $entry->{part}->$_ ) } qw(sellprice listprice lastcost);
213 $entry->{priceupdate} = 1;
217 $object->partnumber('####') if $entry->{part};
222 my ($self, $entry) = @_;
224 foreach my $column (qw(sellprice listprice lastcost)) {
225 next unless $self->controller->headers->{used}->{ $column };
227 my $adjustment = $self->settings->{sellprice_adjustment};
228 my $value = $entry->{object}->$column;
230 $value = $self->settings->{sellprice_adjustment_type} eq 'percent' ? $value * (100 + $adjustment) / 100 : $value + $adjustment;
231 $entry->{object}->$column($::form->round_amount($value, $self->settings->{sellprice_places}));
235 sub handle_shoparticle {
236 my ($self, $entry) = @_;
238 $entry->{object}->shop(1) if $self->settings->{shoparticle_if_missing} && !$self->controller->headers->{used}->{shop};
242 my ($self, $entry) = @_;
244 my $bg = $self->bg_by->{id}->{ $entry->{object}->buchungsgruppen_id };
245 die "Program logic error" if !$bg;
247 my $type = $self->settings->{parts_type};
248 if ($type eq 'mixed') {
249 $type = $entry->{raw_data}->{type} =~ m/^p/i ? 'part'
250 : $entry->{raw_data}->{type} =~ m/^s/i ? 'service'
254 $entry->{object}->income_accno_id( $bg->income_accno_id_0 );
255 $entry->{object}->expense_accno_id( $bg->expense_accno_id_0 );
257 if ($type eq 'part') {
258 $entry->{object}->inventory_accno_id( $bg->inventory_accno_id );
260 } elsif ($type ne 'service') {
261 push @{ $entry->{errors} }, $::locale->text('Error: Invalid part type');
268 sub check_price_factor {
269 my ($self, $entry) = @_;
271 my $object = $entry->{object};
273 # Check whether or not price factor ID is valid.
274 if ($object->price_factor_id && !$self->price_factors_by->{id}->{ $object->price_factor_id }) {
275 push @{ $entry->{errors} }, $::locale->text('Error: Invalid price factor');
279 # Map name to ID if given.
280 if (!$object->price_factor_id && $entry->{raw_data}->{price_factor}) {
281 my $pf = $self->price_factors_by->{description}->{ $entry->{raw_data}->{price_factor} };
284 push @{ $entry->{errors} }, $::locale->text('Error: Invalid price factor');
288 $object->price_factor_id($pf->id);
295 my ($self, $entry) = @_;
297 my $object = $entry->{object};
299 # Check whether or not payment ID is valid.
300 if ($object->payment_id && !$self->payment_terms_by->{id}->{ $object->payment_id }) {
301 push @{ $entry->{errors} }, $::locale->text('Error: Invalid payment terms');
305 # Map name to ID if given.
306 if (!$object->payment_id && $entry->{raw_data}->{payment}) {
307 my $terms = $self->payment_terms_by->{description}->{ $entry->{raw_data}->{payment} };
310 push @{ $entry->{errors} }, $::locale->text('Error: Invalid payment terms');
314 $object->payment_id($terms->id);
320 sub check_packing_type {
321 my ($self, $entry) = @_;
323 my $object = $entry->{object};
325 # Check whether or not packing type ID is valid.
326 if ($object->packing_type_id && !$self->packing_types_by->{id}->{ $object->packing_type_id }) {
327 push @{ $entry->{errors} }, $::locale->text('Error: Invalid packing type');
331 # Map name to ID if given.
332 if (!$object->packing_type_id && $entry->{raw_data}->{packing_type}) {
333 my $type = $self->packing_types_by->{description}->{ $entry->{raw_data}->{packing_type} };
336 push @{ $entry->{errors} }, $::locale->text('Error: Invalid packing type');
340 $object->packing_type_id($type->id);
346 sub check_partsgroup {
347 my ($self, $entry) = @_;
349 my $object = $entry->{object};
351 # Check whether or not part group ID is valid.
352 if ($object->partsgroup_id && !$self->partsgroups_by->{id}->{ $object->partsgroup_id }) {
353 push @{ $entry->{errors} }, $::locale->text('Error: Invalid parts group');
357 # Map name to ID if given.
358 if (!$object->partsgroup_id && $entry->{raw_data}->{partsgroup}) {
359 my $pg = $self->partsgroups_by->{partsgroup}->{ $entry->{raw_data}->{partsgroup} };
362 push @{ $entry->{errors} }, $::locale->text('Error: Invalid parts group');
366 $object->partsgroup_id($pg->id);
373 my ($self, $entry) = @_;
375 my $object = $entry->{object};
377 # Check whether or unit is valid.
378 if (!$self->units_by->{name}->{ $object->unit }) {
379 push @{ $entry->{errors} }, $::locale->text('Error: Unit missing or invalid');
386 sub handle_translations {
387 my ($self, $entry) = @_;
390 foreach my $language (@{ $self->all_languages }) {
391 my ($desc, $notes) = @{ $entry->{raw_data} }{ "description_" . $language->article_code, "notes_" . $language->article_code };
392 next unless $desc || $notes;
394 push @translations, SL::DB::Translation->new(language_id => $language->id,
395 translation => $desc,
396 longdescription => $notes);
399 $entry->{object}->translations(\@translations);
402 sub set_various_fields {
403 my ($self, $entry) = @_;
405 $entry->{object}->priceupdate(DateTime->now_local);
411 my $profile = $self->SUPER::init_profile;
412 delete @{$profile}{qw(type priceupdate)};
418 my ($self, %params) = @_;
420 my $with_number = [ grep { $_->{object}->partnumber ne '####' } @{ $self->controller->data } ];
421 my $without_number = [ grep { $_->{object}->partnumber eq '####' } @{ $self->controller->data } ];
423 map { $_->{object}->partnumber('') } @{ $without_number };
425 $self->SUPER::save_objects(data => $with_number);
426 $self->SUPER::save_objects(data => $without_number);