X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FController%2FPart.pm;h=2b5ac56c5dbb2348835379e5fd64a9e979fbc640;hb=1f6dae289732896411da0c63ad173a17cb968e22;hp=66f873136d838e6b90103e852f22a7d2beb50e97;hpb=c2efdba2dfa10e76937e4bdf5a18e5483d0cddb0;p=kivitendo-erp.git diff --git a/SL/Controller/Part.pm b/SL/Controller/Part.pm index 66f873136..2b5ac56c5 100644 --- a/SL/Controller/Part.pm +++ b/SL/Controller/Part.pm @@ -6,6 +6,8 @@ use parent qw(SL::Controller::Base); use Clone qw(clone); use SL::DB::Part; use SL::DB::PartsGroup; +use SL::DB::PriceRuleItem; +use SL::DB::Shop; use SL::Controller::Helper::GetModels; use SL::Locale::String qw(t8); use SL::JSON; @@ -18,10 +20,13 @@ use SL::DB::Helper::ValidateAssembly qw(validate_assembly); use SL::CVar; use SL::MoreCommon qw(save_form); use Carp; +use SL::Presenter::EscapedText qw(escape is_escaped); +use SL::Presenter::Tag qw(select_tag); use Rose::Object::MakeMethods::Generic ( 'scalar --get_set_init' => [ qw(parts models part p warehouses multi_items_models - makemodels + makemodels shops_not_assigned + customerprices orphaned assortment assortment_items assembly assembly_items all_pricegroups all_translations all_partsgroups all_units @@ -135,11 +140,11 @@ sub action_save { )->save(); CVar->save_custom_variables( - dbh => $self->part->db->dbh, - module => 'IC', - trans_id => $self->part->id, - variables => $::form, # $::form->{cvar} would be nicer - always_valid => 1, + dbh => $self->part->db->dbh, + module => 'IC', + trans_id => $self->part->id, + variables => $::form, # $::form->{cvar} would be nicer + save_validity => 1, ); 1; @@ -402,15 +407,13 @@ sub action_show_multi_items_dialog { sub action_multi_items_update_result { my $max_count = 100; - $::form->{multi_items}->{filter}->{obsolete} = 0; - my $count = $_[0]->multi_items_models->count; if ($count == 0) { - my $text = SL::Presenter::EscapedText->new(text => $::locale->text('No results.')); + my $text = escape($::locale->text('No results.')); $_[0]->render($text, { layout => 0 }); } elsif ($count > $max_count) { - my $text = SL::Presenter::EscapedText->new(text => $::locale->text('Too many results (#1 from #2).', $count, $max_count)); + my $text = escpae($::locale->text('Too many results (#1 from #2).', $count, $max_count)); $_[0]->render($text, { layout => 0 }); } else { my $multi_items = $_[0]->multi_items_models->get; @@ -453,6 +456,39 @@ sub action_add_makemodel_row { ->render; } +sub action_add_customerprice_row { + my ($self) = @_; + + my $customer_id = $::form->{add_customerprice}; + + my $customer = SL::DB::Manager::Customer->find_by(id => $customer_id) + or return $self->js->error(t8("No customer selected or found!"))->render; + + if (grep { $customer_id == $_->customer_id } @{ $self->customerprices }) { + $self->js->flash('info', t8("This customer has already been added.")); + } + + my $position = scalar @{ $self->customerprices } + 1; + + my $cu = SL::DB::PartCustomerPrice->new( + customer_id => $customer->id, + customer_partnumber => '', + price => 0, + sortorder => $position, + ) or die "Can't create Customerprice object"; + + my $row_as_html = $self->p->render( + 'part/_customerprice_row', + customerprice => $cu, + listrow => $position % 2 ? 0 + : 1, + ); + + $self->js->append('#customerprice_rows', $row_as_html) # append in tbody + ->val('.add_customerprice_input', '') + ->run('kivi.Part.focus_last_customerprice_input')->render; +} + sub action_reorder_items { my ($self) = @_; @@ -547,6 +583,7 @@ sub action_ajax_autocomplete { id => $_->id, partnumber => $_->partnumber, description => $_->description, + ean => $_->ean, part_type => $_->part_type, unit => $_->unit, cvars => { map { ($_->config->name => { value => $_->value_as_text, is_valid => $_->is_valid }) } @{ $_->cvars_by_config } }, @@ -604,6 +641,8 @@ sub prepare_assortment_render_vars { sub prepare_assembly_render_vars { my ($self) = @_; + croak("Need assembly item(s) to create a 'save as new' assembly.") unless $self->part->items; + my %vars = ( items_sellprice_sum => $self->part->items_sellprice_sum, items_lastcost_sum => $self->part->items_lastcost_sum, assembly_html => $self->render_assembly_items_to_html( \@{ $self->part->items } ), @@ -636,7 +675,7 @@ sub add { sub _set_javascript { my ($self) = @_; - $::request->layout->use_javascript("${_}.js") for qw(kivi.Part kivi.File kivi.PriceRule ckeditor/ckeditor ckeditor/adapters/jquery); + $::request->layout->use_javascript("${_}.js") for qw(kivi.Part kivi.File kivi.PriceRule ckeditor/ckeditor ckeditor/adapters/jquery kivi.ShopPart); $::request->layout->add_javascripts_inline("\$(function(){kivi.PriceRule.load_price_rules_for_part(@{[ $self->part->id ]})});") if $self->part->id; } @@ -691,6 +730,8 @@ sub parse_form { $self->part->assign_attributes(%{ $params}); $self->part->bin_id(undef) unless $self->part->warehouse_id; + $self->normalize_text_blocks; + # Only reset items ([]) and rewrite from form if $::form->{assortment_items} isn't empty. This # will be the case for used assortments when saving, or when a used assortment # is "used as new" @@ -710,6 +751,7 @@ sub parse_form { $self->part->prices([]); $self->parse_form_prices; + $self->parse_form_customerprices; $self->parse_form_makemodels; } @@ -756,8 +798,9 @@ sub parse_form_makemodels { $position++; my $vendor = SL::DB::Manager::Vendor->find_by(id => $makemodel->{make}) || die "Can't find vendor from make"; + my $id = $makemodels_map->{$makemodel->{id}} ? $makemodels_map->{$makemodel->{id}}->id : undef; my $mm = SL::DB::MakeModel->new( # parts_id => $self->part->id, # will be assigned by row add_makemodels - id => $makemodel->{id}, + id => $id, make => $makemodel->{make}, model => $makemodel->{model} || '', lastcost => $::form->parse_amount(\%::myconfig, $makemodel->{lastcost_as_number}), @@ -779,13 +822,54 @@ sub parse_form_makemodels { }; } +sub parse_form_customerprices { + my ($self) = @_; + + my $customerprices_map; + if ( $self->part->customerprices ) { # check for new parts or parts without customerprices + $customerprices_map = { map { $_->id => Rose::DB::Object::Helpers::clone($_) } @{$self->part->customerprices} }; + }; + + $self->part->customerprices([]); + + my $position = 0; + my $customerprices = delete($::form->{customerprices}) || []; + foreach my $customerprice ( @{$customerprices} ) { + next unless $customerprice->{customer_id}; + $position++; + my $customer = SL::DB::Manager::Customer->find_by(id => $customerprice->{customer_id}) || die "Can't find customer from id"; + + my $id = $customerprices_map->{$customerprice->{id}} ? $customerprices_map->{$customerprice->{id}}->id : undef; + my $cu = SL::DB::PartCustomerPrice->new( # parts_id => $self->part->id, # will be assigned by row add_customerprices + id => $id, + customer_id => $customerprice->{customer_id}, + customer_partnumber => $customerprice->{customer_partnumber} || '', + price => $::form->parse_amount(\%::myconfig, $customerprice->{price_as_number}), + sortorder => $position, + ); + if ($customerprices_map->{$cu->id} && !$customerprices_map->{$cu->id}->lastupdate && $customerprices_map->{$cu->id}->price == 0 && $cu->price == 0) { + # lastupdate isn't set, original price is 0 and new lastcost is 0 + # don't change lastupdate + } elsif ( !$customerprices_map->{$cu->id} && $cu->price == 0 ) { + # new customerprice, no lastcost entered, leave lastupdate empty + } elsif ($customerprices_map->{$cu->id} && $customerprices_map->{$cu->id}->price == $cu->price) { + # price hasn't changed, use original lastupdate + $cu->lastupdate($customerprices_map->{$cu->id}->lastupdate); + } else { + $cu->lastupdate(DateTime->now); + }; + $self->part->add_customerprices($cu); + }; +} + sub build_bin_select { - $_[0]->p->select_tag('part.bin_id', [ $_[0]->warehouse->bins ], + select_tag('part.bin_id', [ $_[0]->warehouse->bins ], title_key => 'description', default => $_[0]->bin->id, ); } + # get_set_inits for partpicker sub init_parts { @@ -803,7 +887,7 @@ sub init_part { # used by edit, save, delete and add if ( $::form->{part}{id} ) { - return SL::DB::Part->new(id => $::form->{part}{id})->load(with => [ qw(makemodels prices translations partsgroup) ]); + return SL::DB::Part->new(id => $::form->{part}{id})->load(with => [ qw(makemodels customerprices prices translations partsgroup shop_parts shop_parts.shop) ]); } else { die "part_type missing" unless $::form->{part}{part_type}; return SL::DB::Part->new(part_type => $::form->{part}{part_type}); @@ -882,6 +966,29 @@ sub init_makemodels { return \@makemodel_array; } +sub init_customerprices { + my ($self) = @_; + + my $position = 0; + my @customerprice_array = (); + my $customerprices = delete($::form->{customerprices}) || []; + + foreach my $customerprice ( @{$customerprices} ) { + next unless $customerprice->{customer_id}; + $position++; + my $cu = SL::DB::PartCustomerPrice->new( # parts_id => $self->part->id, # will be assigned by row add_customerprices + id => $customerprice->{id}, + customer_partnumber => $customerprice->{customer_partnumber}, + customer_id => $customerprice->{customer_id} || '', + price => $::form->parse_amount(\%::myconfig, $customerprice->{price_as_number} || 0), + sortorder => $position, + ) or die "Can't create cu"; + # $cu->id($customerprice->{id}) if $customerprice->{id}; + push(@customerprice_array, $cu); + }; + return \@customerprice_array; +} + sub init_assembly_items { my ($self) = @_; my $position = 0; @@ -920,7 +1027,19 @@ sub init_all_buchungsgruppen { if ( $self->part->orphaned ) { return SL::DB::Manager::Buchungsgruppe->get_all_sorted; } else { - return SL::DB::Manager::Buchungsgruppe->get_all(where => [ id => $self->part->buchungsgruppen_id ]); + return SL::DB::Manager::Buchungsgruppe->get_all_sorted(where => [ id => $self->part->buchungsgruppen_id ]); + } +} + +sub init_shops_not_assigned { + my ($self) = @_; + + my @used_shop_ids = map { $_->shop->id } @{ $self->part->shop_parts }; + if ( @used_shop_ids ) { + return SL::DB::Manager::Shop->get_all( query => [ obsolete => 0, '!id' => \@used_shop_ids ], sort_by => 'sortkey' ); + } + else { + return SL::DB::Manager::Shop->get_all( query => [ obsolete => 0 ], sort_by => 'sortkey' ); } } @@ -1075,6 +1194,25 @@ sub check_has_valid_part_type { die "invalid part_type" unless $_[0] =~ /^(part|service|assembly|assortment)$/; } + +sub normalize_text_blocks { + my ($self) = @_; + + # check if feature is enabled (select normalize_part_descriptions from defaults) + return unless ($::instance_conf->get_normalize_part_descriptions); + + # text block + foreach (qw(description)) { + $self->part->{$_} =~ s/\s+$//s; + $self->part->{$_} =~ s/^\s+//s; + $self->part->{$_} =~ s/ {2,}/ /g; + } + # html block (caveat: can be circumvented by using bold or italics) + $self->part->{notes} =~ s/^

( )+\s+/

/s; + $self->part->{notes} =~ s/( )+<\/p>$/<\/p>/s; + +} + sub render_assortment_items_to_html { my ($self, $assortment_items, $number_of_items) = @_; @@ -1149,7 +1287,8 @@ sub parse_add_items_to_objects { sub _setup_form_action_bar { my ($self) = @_; - my $may_edit = $::auth->assert('part_service_assembly_edit', 'may fail'); + my $may_edit = $::auth->assert('part_service_assembly_edit', 'may fail'); + my $used_in_pricerules = !!SL::DB::Manager::PriceRuleItem->get_all_count(where => [type => 'part', value_int => $self->part->id]); for my $bar ($::request->layout->get('actionbar')) { $bar->add( @@ -1158,7 +1297,6 @@ sub _setup_form_action_bar { t8('Save'), call => [ 'kivi.Part.save' ], disabled => !$may_edit ? t8('You do not have the permissions to access this function.') : undef, - accesskey => 'enter', ], action => [ t8('Use as new'), @@ -1176,6 +1314,7 @@ sub _setup_form_action_bar { disabled => !$self->part->id ? t8('This object has not been saved yet.') : !$may_edit ? t8('You do not have the permissions to access this function.') : !$self->part->orphaned ? t8('This object has already been used.') + : $used_in_pricerules ? t8('This object is used in price rules.') : undef, ],