X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;ds=sidebyside;f=SL%2FController%2FPart.pm;h=461090bd9d99ce3b5b3f29fcf65b7786ee8f0742;hb=065cf8d539872ce878b73d348a61c41b48579245;hp=fce0b6fb305982304a19769c1c522d9cc925cb58;hpb=905d9eddfafc30dd4c53e892a6070035f28e6ce0;p=kivitendo-erp.git diff --git a/SL/Controller/Part.pm b/SL/Controller/Part.pm index fce0b6fb3..461090bd9 100644 --- a/SL/Controller/Part.pm +++ b/SL/Controller/Part.pm @@ -16,6 +16,7 @@ use DateTime; use SL::DB::History; use SL::DB::Helper::ValidateAssembly qw(validate_assembly); use SL::CVar; +use SL::MoreCommon qw(save_form); use Carp; use Rose::Object::MakeMethods::Generic ( @@ -25,6 +26,7 @@ use Rose::Object::MakeMethods::Generic ( assortment assortment_items assembly assembly_items all_pricegroups all_translations all_partsgroups all_units all_buchungsgruppen all_payment_terms all_warehouses + parts_classification_filter all_languages all_units all_price_factors) ], 'scalar' => [ qw(warehouse bin) ], ); @@ -40,6 +42,7 @@ __PACKAGE__->run_before('check_part_id', only => [ qw(edit delete) ]); sub action_add_part { my ($self, %params) = @_; + $::form->{callback} = $self->url_for(action => 'add_part') unless $::form->{callback}; $self->part( SL::DB::Part->new_part ); $self->add; }; @@ -47,6 +50,7 @@ sub action_add_part { sub action_add_service { my ($self, %params) = @_; + $::form->{callback} = $self->url_for(action => 'add_service') unless $::form->{callback}; $self->part( SL::DB::Part->new_service ); $self->add; }; @@ -54,6 +58,7 @@ sub action_add_service { sub action_add_assembly { my ($self, %params) = @_; + $::form->{callback} = $self->url_for(action => 'add_assembly') unless $::form->{callback}; $self->part( SL::DB::Part->new_assembly ); $self->add; }; @@ -61,10 +66,23 @@ sub action_add_assembly { sub action_add_assortment { my ($self, %params) = @_; + $::form->{callback} = $self->url_for(action => 'add_assortment') unless $::form->{callback}; $self->part( SL::DB::Part->new_assortment ); $self->add; }; +sub action_add_from_record { + my ($self) = @_; + + check_has_valid_part_type($::form->{part}{part_type}); + + die 'parts_classification_type must be "sales" or "purchases"' + unless $::form->{parts_classification_type} =~ m/^(sales|purchases)$/; + + $self->parse_form; + $self->add; +} + sub action_add { my ($self) = @_; @@ -90,8 +108,11 @@ sub action_save { return $self->js->error(t8('The document has been changed by another user. Please reopen it in another window and copy the changes to the new window'))->render; } - if ( $is_new and !$::form->{part}{partnumber} ) { - $self->check_next_transnumber_is_free or return $self->js->error(t8('The next partnumber in the number range already exists!'))->render; + if ( $is_new + && $::form->{part}{partnumber} + && SL::DB::Manager::Part->find_by(partnumber => $::form->{part}{partnumber}) + ) { + return $self->js->error(t8('The partnumber is already being used'))->render; } $self->parse_form; @@ -128,10 +149,16 @@ sub action_save { 1; }) or return $self->js->error(t8('The item couldn\'t be saved!') . " " . $self->part->db->error )->render; - flash_later('info', $is_new ? t8('The item has been created.') : t8('The item has been saved.')); + ; + flash_later('info', $is_new ? t8('The item has been created.') . " " . $self->part->displayable_name : t8('The item has been saved.')); - # reload item, this also resets last_modification! - $self->redirect_to(controller => 'Part', action => 'edit', 'part.id' => $self->part->id); + if ( $::form->{callback} ) { + $self->redirect_to($::form->unescape($::form->{callback}) . '&new_parts_id=' . $self->part->id); + + } else { + # default behaviour after save: reload item, this also resets last_modification! + $self->redirect_to(controller => 'Part', action => 'edit', 'part.id' => $self->part->id); + } } sub action_save_as_new { @@ -164,11 +191,15 @@ sub action_delete { }) or return $self->js->error(t8('The item couldn\'t be deleted!') . " " . $self->part->db->error)->render; flash_later('info', t8('The item has been deleted.')); - my @redirect_params = ( - controller => 'controller.pl', - action => 'LoginScreen/user_login' - ); - $self->redirect_to(@redirect_params); + if ( $::form->{callback} ) { + $self->redirect_to($::form->unescape($::form->{callback})); + } else { + my @redirect_params = ( + controller => 'controller.pl', + action => 'LoginScreen/user_login' + ); + $self->redirect_to(@redirect_params); + } } sub action_use_as_new { @@ -194,6 +225,7 @@ sub render_form { my ($self, %params) = @_; $self->_set_javascript; + $self->_setup_form_action_bar; my (%assortment_vars, %assembly_vars); %assortment_vars = %{ $self->prepare_assortment_render_vars } if $self->part->is_assortment; @@ -216,7 +248,6 @@ sub render_form { $self->render( 'part/form', title => $title_hash{$self->part->part_type}, - show_edit_buttons => $::auth->assert('part_service_assembly_edit'), %assortment_vars, %assembly_vars, translations_map => { map { ($_->language_id => $_) } @{$self->part->translations} }, @@ -500,22 +531,19 @@ sub action_ajax_autocomplete { # if someone types something, and hits enter, assume he entered the full name. # if something matches, treat that as sole match - # unfortunately get_models can't do more than one per package atm, so we d it - # the oldfashioned way. + # since we need a second get models instance with different filters for that, + # we only modify the original filter temporarily in place if ($::form->{prefer_exact}) { + local $::form->{filter}{'all::ilike'} = delete local $::form->{filter}{'all:substr:multi::ilike'}; + + my $exact_models = SL::Controller::Helper::GetModels->new( + controller => $self, + sorted => 0, + paginated => { per_page => 2 }, + with_objects => [ qw(unit_obj classification) ], + ); my $exact_matches; - if (1 == scalar @{ $exact_matches = SL::DB::Manager::Part->get_all( - query => [ - obsolete => 0, - SL::DB::Manager::Part->type_filter($::form->{filter}{part_type}), - SL::DB::Manager::PartClassification->classification_filter($::form->{filter}{classification_id}), - or => [ - description => { ilike => $::form->{filter}{'all:substr:multi::ilike'} }, - partnumber => { ilike => $::form->{filter}{'all:substr:multi::ilike'} }, - ] - ], - limit => 2, - ) }) { + if (1 == scalar @{ $exact_matches = $exact_models->get }) { $self->parts($exact_matches); } } @@ -599,6 +627,7 @@ sub add { check_has_valid_part_type($self->part->part_type); $self->_set_javascript; + $self->_setup_form_action_bar; my %title_hash = ( part => t8('Add Part'), assembly => t8('Add Assembly'), @@ -608,8 +637,7 @@ sub add { $self->render( 'part/form', - title => $title_hash{$self->part->part_type}, - show_edit_buttons => $::auth->assert('part_service_assembly_edit'), + title => $title_hash{$self->part->part_type}, ); } @@ -668,8 +696,6 @@ sub parse_form { my $params = delete($::form->{part}) || { }; delete $params->{id}; - # never overwrite existing partnumber for parts in use, should be a read-only field in that case anyway - delete $params->{partnumber} if $self->part->partnumber and not $self->orphaned; $self->part->assign_attributes(%{ $params}); $self->part->bin_id(undef) unless $self->part->warehouse_id; @@ -946,6 +972,15 @@ sub init_multi_items_models { ); } +sub init_parts_classification_filter { + return [] unless $::form->{parts_classification_type}; + + return [ used_for_sale => 't' ] if $::form->{parts_classification_type} eq 'sales'; + return [ used_for_purchase => 't' ] if $::form->{parts_classification_type} eq 'purchases'; + + die "no query rules for parts_classification_type " . $::form->{parts_classification_type}; +} + # simple checks to run on $::form before saving sub form_check_part_description_exists { @@ -1027,17 +1062,6 @@ sub form_check_partnumber_is_unique { } # general checking functions -sub check_next_transnumber_is_free { - my ($self) = @_; - - my ($next_transnumber, $count); - $self->part->db->with_transaction(sub { - $next_transnumber = $self->part->get_next_trans_number; - $count = SL::DB::Manager::Part->get_all_count(where => [ partnumber => $next_transnumber ]); - return 1; - }) or die $@; - $count ? return 0 : return 1; -} sub check_part_id { die t8("Can't load item without a valid part.id") . "\n" unless $::form->{part}{id}; @@ -1130,6 +1154,52 @@ sub parse_add_items_to_objects { return \@item_objects; } +sub _setup_form_action_bar { + my ($self) = @_; + + my $may_edit = $::auth->assert('part_service_assembly_edit', 'may fail'); + + for my $bar ($::request->layout->get('actionbar')) { + $bar->add( + combobox => [ + action => [ + 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'), + call => [ 'kivi.Part.use_as_new' ], + disabled => !$self->part->id ? t8('The object has not been saved yet.') + : !$may_edit ? t8('You do not have the permissions to access this function.') + : undef, + ], + ], # end of combobox "Save" + + action => [ + t8('Delete'), + call => [ 'kivi.Part.delete' ], + confirm => t8('Do you really want to delete this object?'), + 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.') + : undef, + ], + + 'separator', + + action => [ + t8('History'), + call => [ 'kivi.Part.open_history_popup' ], + 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.') + : undef, + ], + ); + } +} + 1; __END__ @@ -1229,6 +1299,17 @@ parameter part_type as an action. Example: controller.pl?action=Part/add&part_type=service +=item C + +When adding new items to records they can be created on the fly if the entered +partnumber or description doesn't exist yet. After being asked what part type +the new item should have the user is redirected to the correct edit page. + +Depending on whether the item was added from a sales or a purchase record, only +the relevant part classifications should be selectable for new item, so this +parameter is passed on via a hidden parts_classification_type in the new_item +template. + =item C Saves the current part and then reloads the edit page for the part.