commit 60a0462faa58167aeae73ee50ac1165ce979291b
Author: Werner Hahn <wh@futureworldsearch.net>
Date:   Tue Aug 15 09:48:32 2017 +0200

    Shopmodul: ActionBar, automatische Prüfung der Shopverbindung beim
    speichern
    Zusätzliche Felder in der Shopconfig für Pfad, Realm und Protokoll

diff --git a/SL/Controller/Shop.pm b/SL/Controller/Shop.pm
index 19b131f80..e54fbfdb3 100644
--- a/SL/Controller/Shop.pm
+++ b/SL/Controller/Shop.pm
@@ -12,7 +12,7 @@ use SL::DB::Pricegroup;
 use SL::DB::TaxZone;
 
 use Rose::Object::MakeMethods::Generic (
-  scalar                  => [ qw(connectors price_types price_sources taxzone_id) ],
+  scalar                  => [ qw(connectors price_types price_sources taxzone_id protocols) ],
   'scalar --get_set_init' => [ qw(shop) ]
 );
 
@@ -26,34 +26,25 @@ __PACKAGE__->run_before('load_types',    only => [ qw(new edit) ]);
 sub action_list {
   my ($self) = @_;
 
+  $self->_setup_list_action_bar;
   $self->render('shops/list',
                 title => t8('Shops'),
                 SHOPS => SL::DB::Manager::Shop->get_all_sorted,
                );
 }
 
-sub action_new {
-  my ($self) = @_;
-
-  $self->shop(SL::DB::Shop->new);
-  $self->render('shops/form', title       => t8('Add shop'));
-};
-
 sub action_edit {
   my ($self) = @_;
 
-  $self->render('shops/form', title       => t8('Edit shop'));
+  my $is_new = !$self->shop->id;
+  $self->_setup_form_action_bar;
+  $self->render('shops/form', title       => ($is_new ? t8('Add shop') : t8('Edit shop')));
 }
 
-sub action_create {
+sub action_save {
   my ($self) = @_;
 
-  $self->shop(SL::DB::Shop->new);
-  $self->create_or_update;
-}
-
-sub action_update {
-  my ($self) = @_;
+  my $is_new = !$self->shop->id;
   $self->create_or_update;
 }
 
@@ -75,16 +66,30 @@ sub action_reorder {
   $self->render(\'', { type => 'json' });
 }
 
-#
-# filters
-#
+sub action_check_connectivity {
+  my ($self) = @_;
+
+  my $ok = 0;
+  require SL::Shop;
+  my $shop = SL::Shop->new( config => $self->shop );
+  my $version = $shop->connector->get_version;
+  $ok       = $version->{success};
+
+  if($ok) {
+    flash_later('ok', t8('The connection to the webshop is success. Version: #1 -- Revision: #2', $version->{data}->{version}, $version->{data}->{revision}));
+    return;
+  }else{
+    return $version;
+  }
+}
 
 sub check_auth {
   $::auth->assert('config');
 }
 
 sub init_shop {
-  SL::DB::Shop->new(id => $::form->{id})->load;
+  SL::DB::Manager::Shop->find_by_or_create(id => $::form->{id} || 0)
+                              ->assign_attributes(%{ $::form->{shop} });
 }
 
 #
@@ -95,16 +100,19 @@ sub create_or_update {
   my ($self) = @_;
   my $is_new = !$self->shop->id;
 
-  my $params = delete($::form->{shop}) || { };
-
-  $self->shop->assign_attributes(%{ $params });
-
   my @errors = $self->shop->validate;
-
   if (@errors) {
     flash('error', @errors);
-    $self->render('shops/form',
-                   title => $is_new ? t8('Add shop') : t8('Edit shop'));
+    $self->load_types();
+    $self->action_edit();
+    return;
+  }
+
+  my $version = $self->action_check_connectivity();
+  if ($version) {
+    flash('error', t8('The connection to the webshop is not success. Message: #1 -- URL: #2 -- Datatype: #3', $version->{message}, $version->{data}->{version}, $version->{data}->{revision}));
+    $self->load_types();
+    $self->action_edit();
     return;
   }
 
@@ -118,18 +126,13 @@ sub load_types {
   my ($self) = @_;
   # data for the dropdowns when editing Shop configs
 
-  # hardcoded the possible connectors, which correspond to
-  # SL/ShopConnector/xxxx classes
-  $self->connectors( [ { id => "xtcommerce", description => "XT Commerce"},
-                       { id => "shopware",   description => "Shopware" },
-                       { id => "ideal",      description => "IDeal" }
-                     ]);
+  require SL::ShopConnector::ALL;
+  $self->connectors(SL::ShopConnector::ALL->connectors);
 
-  # whether the shop presents its prices as brutto or netto
   $self->price_types( [ { id => "brutto", name => t8('brutto')}, { id => "netto", name => t8('netto') } ] );
 
-  # the possible default price sources to use for the shops: sellprice, lastcost,
-  # listprice, or one of the pricegroups
+  $self->protocols( [ { id => "http", name => t8('http') }, { id => "https", name => t8('https') } ] );
+
   my $pricesources;
   push( @{ $pricesources } , { id => "master_data/sellprice", name => t8("Master Data")." - ".t8("Sellprice") },
                              { id => "master_data/listprice", name => t8("Master Data")." - ".t8("Listprice") },
@@ -153,6 +156,42 @@ sub load_types {
 
 };
 
+sub _setup_form_action_bar {
+  my ($self) = @_;
+
+  for my $bar ($::request->layout->get('actionbar')) {
+    $bar->add(
+      combobox => [
+        action => [
+          t8('Save'),
+          submit    => [ '#form', { action => "Shop/save" } ],
+          accesskey => 'enter',
+        ],
+         action => [
+          t8('Delete'),
+          submit => [ '#form', { action => "Shop/delete" } ],
+        ],
+        ],
+        action => [
+          t8('Cancel'),
+          submit => [ '#form', { action => "Shop/list" } ],
+        ],
+    );
+  }
+}
+
+sub _setup_list_action_bar {
+  my ($self) = @_;
+
+  for my $bar ($::request->layout->get('actionbar')) {
+    $bar->add(
+        link => [
+          t8('Add'),
+          link => $self->url_for(action => 'edit'),
+        ],
+    );
+  }
+}
 
 1;
 
diff --git a/SL/DB/MetaSetup/Shop.pm b/SL/DB/MetaSetup/Shop.pm
index 463e6cebe..d015f841c 100644
--- a/SL/DB/MetaSetup/Shop.pm
+++ b/SL/DB/MetaSetup/Shop.pm
@@ -17,12 +17,15 @@ __PACKAGE__->meta->columns(
   obsolete          => { type => 'boolean', default => 'false', not_null => 1 },
   orders_to_fetch   => { type => 'integer' },
   password          => { type => 'text' },
+  path              => { type => 'text', default => '/', not_null => 1 },
   port              => { type => 'integer' },
   price_source      => { type => 'text' },
   pricetype         => { type => 'text' },
+  protocol          => { type => 'text', default => 'http', not_null => 1 },
+  realm             => { type => 'text' },
+  server            => { type => 'text' },
   sortkey           => { type => 'integer' },
   taxzone_id        => { type => 'integer' },
-  url               => { type => 'text' },
 );
 
 __PACKAGE__->meta->primary_key_columns([ 'id' ]);
diff --git a/SL/Shop.pm b/SL/Shop.pm
index b3130f3a7..576795605 100644
--- a/SL/Shop.pm
+++ b/SL/Shop.pm
@@ -62,7 +62,7 @@ __END__
 =head1 SYNOPSIS
 
   my $config = SL::DB::Manager::Shop->get_first();
-  my $shop = SL::WebShop->new( config => $config );
+  my $shop = SL::Shop->new( config => $config );
 
   From the config we know which Connector class to load, save in $shop->connector
   and do stuff from there:
diff --git a/SL/ShopConnector/ALL.pm b/SL/ShopConnector/ALL.pm
index f2ce768b1..928e3cb57 100644
--- a/SL/ShopConnector/ALL.pm
+++ b/SL/ShopConnector/ALL.pm
@@ -23,6 +23,13 @@ my @shop_connector_order = qw(
   ideal
 );
 
+my @shop_connectors =
+  (
+    { id => "xtcommerce", description => "XT Commerce"},
+    { id => "shopware",   description => "Shopware" },
+    { id => "ideal",      description => "IDeal" }
+  );
+
 sub all_enabled_shop_connectors {
   my %disabled = map { $_ => 1 } @{ $::instance_conf->get_disabled_shop_connectors || [] };
 
@@ -41,4 +48,7 @@ sub shop_connector_class_by_connector {
   $shop_connector_by_connector{$_[1]};
 }
 
+sub connectors {
+  \@shop_connectors;
+}
 1;
diff --git a/SL/ShopConnector/Base.pm b/SL/ShopConnector/Base.pm
index 61199daf2..83cf117a5 100644
--- a/SL/ShopConnector/Base.pm
+++ b/SL/ShopConnector/Base.pm
@@ -16,6 +16,8 @@ sub update_part { die 'update_part needs to be implemented' }
 sub get_article { die 'get_article needs to be implemented' }
 
 sub get_categories { die 'get_order needs to be implemented' }
+
+sub get_version { die 'get_order needs to be implemented' }
 1;
 
 __END__
@@ -24,7 +26,7 @@ __END__
 
 =head1 NAME
 
-SL::ShopConnectorBase - this is the base class for shop connectors
+  SL::ShopConnectorBase - this is the base class for shop connectors
 
 =head1 SYNOPSIS
 
@@ -41,18 +43,25 @@ SL::ShopConnectorBase - this is the base class for shop connectors
 
 =item C<update_part>
 
+=item C<get_article>
+
+=item C<get_categories>
+
+=item C<get_version>
+
 =back
 
 =head1 SEE ALSO
 
-L<SL::ShopConnector::ALL>
+  L<SL::ShopConnector::ALL>
 
 =head1 BUGS
 
-None yet. :)
+  None yet. :)
 
 =head1 AUTHOR
 
-G. Richardson <lt>information@kivitendo-premium.deE<gt>
+  G. Richardson <lt>information@kivitendo-premium.deE<gt>
+  W. Hahn E<lt>wh@futureworldsearch.netE<gt>
 
 =cut
diff --git a/SL/ShopConnector/Shopware.pm b/SL/ShopConnector/Shopware.pm
index 050ac3241..924d3c11b 100644
--- a/SL/ShopConnector/Shopware.pm
+++ b/SL/ShopConnector/Shopware.pm
@@ -26,17 +26,16 @@ use Rose::Object::MakeMethods::Generic (
 sub get_new_orders {
   my ($self, $id) = @_;
 
-  my $url = $self->url;
-
+  my $url       = $self->url;
   my $ordnumber = $self->config->last_order_number + 1;
-  my $otf = $self->config->orders_to_fetch;
+  my $otf       = $self->config->orders_to_fetch;
 
   my $i;
   for ($i=1;$i<=$otf;$i++) {
 
-    my $data = $self->connector->get("http://$url/api/orders/$ordnumber?useNumberAsId=true");
+    my $data      = $self->connector->get($url . "api/orders/$ordnumber?useNumberAsId=true");
     my $data_json = $data->content;
-    my $import = SL::JSON::decode_json($data_json);
+    my $import    = SL::JSON::decode_json($data_json);
     if ($import->{success}){
 
       # Mapping to table shoporders. See http://community.shopware.com/_detail_1690.html#GET_.28Liste.29
@@ -131,12 +130,12 @@ sub get_new_orders {
       $shop_order->{positions} = $position-1;
 
       # Only Customers which are not found will be applied
-      my $name = $shop_order->billing_lastname ne '' ? "%" . $shop_order->billing_firstname . "%" . $shop_order->billing_lastname . "%" : '';
+      my $name     = $shop_order->billing_lastname ne '' ? "%" . $shop_order->billing_firstname . "%" . $shop_order->billing_lastname . "%" : '';
       my $lastname = $shop_order->billing_lastname ne '' ? "%" . $shop_order->billing_lastname . "%" : '';
-      my $company = $shop_order->billing_company ne '' ? "%" . $shop_order->billing_company . "%" : '';
-      my $street = $shop_order->billing_street ne '' ?  $shop_order->billing_street : '';
+      my $company  = $shop_order->billing_company ne '' ? "%" . $shop_order->billing_company . "%" : '';
+      my $street   = $shop_order->billing_street ne '' ?  $shop_order->billing_street : '';
       # Fuzzysearch for street to find e.g. "Dorfstrasse - Dorfstr. - Dorfstraße"
-      my $dbh = $::form->get_standard_dbh();
+      my $dbh      = $::form->get_standard_dbh();
       my $fs_query = "SELECT id FROM customer WHERE ( ( (    name ILIKE ?
                                                           OR name ILIKE ?
                                                         )
@@ -148,7 +147,7 @@ sub get_new_orders {
                                                       OR email ILIKE ?
                                                     )";
       my @values = ($lastname, $company, $shop_order->billing_zipcode, $street, $shop_order->billing_zipcode, $shop_order->billing_email);
-      my @c_ids = selectall_array_query($::form, $dbh, $fs_query, @values);
+      my @c_ids  = selectall_array_query($::form, $dbh, $fs_query, @values);
 
       if(!scalar(@c_ids)){
 
@@ -200,7 +199,7 @@ sub get_new_orders {
       $ordnumber++;
     }
   }
-  my $shop = $self->config->description;
+  my $shop           = $self->config->description;
   my @fetched_orders = ($shop,$i);
   return \@fetched_orders;
 };
@@ -208,13 +207,11 @@ sub get_new_orders {
 sub get_categories {
   my ($self) = @_;
 
-  my $url = $self->url;
-
-  my $data = $self->connector->get("http://$url/api/categories");
-
-  my $data_json = $data->content;
-  my $import = SL::JSON::decode_json($data_json);
-  my @daten = @{$import->{data}};
+  my $url        = $self->url;
+  my $data       = $self->connector->get($url . "api/categories");
+  my $data_json  = $data->content;
+  my $import     = SL::JSON::decode_json($data_json);
+  my @daten      = @{$import->{data}};
   my %categories = map { ($_->{id} => $_) } @daten;
 
   for(@daten) {
@@ -226,12 +223,31 @@ sub get_categories {
   return \@daten;
 }
 
+sub get_version {
+  my ($self) = @_;
+
+  my $url       = $self->url;
+  my $data      = $self->connector->get($url . "api/version");
+  my $type = $data->content_type;
+  my $status_line = $data->status_line;
+
+  if($data->is_success && $type eq 'application/json'){
+    my $data_json = $data->content;
+    return SL::JSON::decode_json($data_json);
+  }else{
+    my %return = ( success => 0,
+                   data  => { version => $url . ": " . $status_line, revision => $type },
+                   message => "Server not found or wrong data type",
+                );
+    return \%return;
+  }
+}
+
 sub get_articles {
   my ($self, $json_data) = @_;
 
 }
 
-
 sub update_part {
   my ($self, $shop_part, $json, $todo) = @_;
 
@@ -253,10 +269,11 @@ sub update_part {
   my $images = SL::DB::Manager::ShopImage->get_all( where => [ 'files.object_id' => $part->{id}, ], with_objects => 'file', sort_by => 'position' );
   my @upload_img = ();
   foreach my $img (@{ $images }) {
-    my $file = SL::File->get(id => $img->file->id );
-    my $file_path = $file->get_file;
+    my $file               = SL::File->get(id => $img->file->id );
+    my $file_path          = $file->get_file;
     my ($path, $extension) = (split /\./, $file->file_name);
-    my $content = File::Slurp::read_file($file->get_file);
+    my $content            = File::Slurp::read_file($file->get_file);
+
     my $temp ={ ( link        => 'data:' . $file->mime_type . ';base64,' . MIME::Base64::encode($content),
                   description => $img->file->title,
                   position    => $img->position,
@@ -271,9 +288,9 @@ sub update_part {
     my $partnumber = $::form->escape($part->{partnumber});#shopware don't accept / in articlenumber
 # Shopware RestApi schreibt Fehleremail wenn Artikel nicht gefunden. es braucht aber irgendeine Abfrage, ob der Artikel schon im Shop ist.
 # LWP->post = neuanlegen LWP->put = update
-    $data = $self->connector->get("http://$url/api/articles/$partnumber?useNumberAsId=true");
-    $data_json = $data->content;
-    $import = SL::JSON::decode_json($data_json);
+    $data       = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
+    $data_json  = $data->content;
+    $import     = SL::JSON::decode_json($data_json);
 #  }
 
   # get the right price
@@ -291,7 +308,7 @@ sub update_part {
   # get the right taxrate for the article
   # TODO In extra Helper??
   my $taxrate;
-  my $dbh = $::form->get_standard_dbh();
+  my $dbh  = $::form->get_standard_dbh();
   my $b_id = $part->buchungsgruppen_id;
   my $t_id = $shop_part->shop->taxzone_id;
 
@@ -362,25 +379,25 @@ sub update_part {
   }
 
   my $dataString = SL::JSON::to_json(\%shop_data);
-  $dataString = encode_utf8($dataString);
+  $dataString    = encode_utf8($dataString);
   my $upload_content;
   my $upload;
   if($import->{success}){
     #update
-    my $partnumber = $::form->escape($part->{partnumber});#shopware don't accept / in articlenumber
-    $upload = $self->connector->put("http://$url/api/articles/$partnumber?useNumberAsId=true",Content => $dataString);
-    my $data_json = $upload->content;
+    my $partnumber  = $::form->escape($part->{partnumber});#shopware don't accept / in articlenumber
+    $upload         = $self->connector->put($url . "api/articles/$partnumber?useNumberAsId=true", Content => $dataString);
+    my $data_json   = $upload->content;
     $upload_content = SL::JSON::decode_json($data_json);
   }else{
     #upload
-    $upload = $self->connector->post("http://$url/api/articles/",Content => $dataString);
-    my $data_json = $upload->content;
+    $upload         = $self->connector->post($url . "api/articles/", Content => $dataString);
+    my $data_json   = $upload->content;
     $upload_content = SL::JSON::decode_json($data_json);
   }
-  # Don't know if this is needed
+  # don't know if this is needed
   if(@upload_img) {
     my $partnumber = $::form->escape($part->{partnumber});#shopware don't accept / in articlenumber
-    my $imgup = $self->connector->put("http://$url/api/generateArticleImages/$partnumber?useNumberAsId=true");
+    my $imgup      = $self->connector->put($url . "api/generatearticleimages/$partnumber?usenumberasid=true");
   }
 
   return $upload_content->{success};
@@ -389,33 +406,34 @@ sub update_part {
 sub get_article {
   my ($self,$partnumber) = @_;
 
-  my $url = $self->url;
-  $partnumber = $::form->escape($partnumber);#shopware don't accept / in articlenumber
-  my $data = $self->connector->get("http://$url/api/articles/$partnumber?useNumberAsId=true");
+  my $url       = $self->url;
+  $partnumber   = $::form->escape($partnumber);#shopware don't accept / in articlenumber
+  my $data      = $self->connector->get($url . "api/articles/$partnumber?usenumberasid=true");
   my $data_json = $data->content;
   return SL::JSON::decode_json($data_json);
 }
 
+
 sub set_orderstatus {
   my ($self,$ordernumber);
 }
 
 sub init_url {
   my ($self) = @_;
-  # TODO: validate url and port Mabey in shopconfig test connection
-  $self->url($self->config->url . ":" . $self->config->port);
-};
+  $self->url($self->config->protocol . "://" . $self->config->server . ":" . $self->config->port . $self->config->path);
+}
 
 sub init_connector {
   my ($self) = @_;
   my $ua = LWP::UserAgent->new;
   $ua->credentials(
-      $self->url,
-      "Shopware REST-API", # TODO in config
+      $self->config->server . ":" . $self->config->port,
+      $self->config->realm,
       $self->config->login => $self->config->password
   );
+
   return $ua;
-};
+}
 
 1;
 
@@ -425,13 +443,17 @@ __END__
 
 =head1 NAME
 
-  SL::ShopConnecter::Shopware - connector for Shopware 5
+  SL::Shopconnecter::Shopware - connector for shopware 5
 
 =head1 SYNOPSIS
 
 
 =head1 DESCRIPTION
 
+=head1 TODO
+
+  Pricesrules, pricessources aren't fully implemented yet.
+  Payments aren't implemented( need to map payments from Shopware like invoice, paypal etc. to payments in kivitendo)
 
 =head1 BUGS
 
diff --git a/sql/Pg-upgrade2/shops_1.sql b/sql/Pg-upgrade2/shops_1.sql
new file mode 100644
index 000000000..0b2c5a2f3
--- /dev/null
+++ b/sql/Pg-upgrade2/shops_1.sql
@@ -0,0 +1,9 @@
+-- @tag: shop_1
+-- @description: Add tables for part information for shop
+-- @charset: UTF-8
+-- @depends: shops
+-- @ignore: 0
+
+ALTER TABLE shops ADD COLUMN protocol TEXT NOT NULL DEFAULT 'http';
+ALTER TABLE shops ADD COLUMN path TEXT NOT NULL DEFAULT '/';
+ALTER TABLE shops RENAME COLUMN url TO server;
diff --git a/sql/Pg-upgrade2/shops_2.sql b/sql/Pg-upgrade2/shops_2.sql
new file mode 100644
index 000000000..b6828436b
--- /dev/null
+++ b/sql/Pg-upgrade2/shops_2.sql
@@ -0,0 +1,7 @@
+-- @tag: shop_2
+-- @description: Add tables for part information for shop
+-- @charset: UTF-8
+-- @depends: shops
+-- @ignore: 0
+
+ALTER TABLE shops ADD COLUMN realm TEXT;
diff --git a/sql/Pg-upgrade2/trigram_indices.sql b/sql/Pg-upgrade2/trigram_indices.sql
new file mode 100644
index 000000000..9d88f8659
--- /dev/null
+++ b/sql/Pg-upgrade2/trigram_indices.sql
@@ -0,0 +1,46 @@
+-- @tag: trigram_indices
+-- @description: Trigram Indizes für häufig durchsuchte Spalten
+-- @depends: release_3_4_1
+-- @encoding: utf-8
+-- @ignore: 1
+
+-- CREATE EXTENSION IF NOT EXISTS pg_trgm;
+
+CREATE INDEX customer_customernumber_gin_trgm_idx    ON customer        USING gin (customernumber          gin_trgm_ops);
+CREATE INDEX customer_name_gin_trgm_idx              ON customer        USING gin (name                    gin_trgm_ops);
+
+CREATE INDEX vendor_vendornumber_gin_trgm_idx        ON vendor          USING gin (vendornumber            gin_trgm_ops);
+CREATE INDEX vendor_name_gin_trgm_idx                ON vendor          USING gin (name                    gin_trgm_ops);
+
+CREATE INDEX parts_partnumber_gin_trgm_idx           ON parts           USING gin (partnumber              gin_trgm_ops);
+CREATE INDEX parts_description_gin_trgm_idx          ON parts           USING gin (description             gin_trgm_ops);
+
+CREATE INDEX oe_ordnumber_gin_trgm_idx               ON oe              USING gin (ordnumber               gin_trgm_ops);
+CREATE INDEX oe_quonumber_gin_trgm_idx               ON oe              USING gin (quonumber               gin_trgm_ops);
+CREATE INDEX oe_cusordnumber_gin_trgm_idx            ON oe              USING gin (cusordnumber            gin_trgm_ops);
+CREATE INDEX oe_transaction_description_gin_trgm_idx ON oe              USING gin (transaction_description gin_trgm_ops);
+
+CREATE INDEX do_donumber_gin_trgm_idx                ON delivery_orders USING gin (donumber                gin_trgm_ops);
+CREATE INDEX do_ordnumber_gin_trgm_idx               ON delivery_orders USING gin (ordnumber               gin_trgm_ops);
+CREATE INDEX do_cusordnumber_gin_trgm_idx            ON delivery_orders USING gin (cusordnumber            gin_trgm_ops);
+CREATE INDEX do_transaction_description_gin_trgm_idx ON delivery_orders USING gin (transaction_description gin_trgm_ops);
+
+CREATE INDEX ar_invnumber_gin_trgm_idx               ON ar              USING gin (invnumber               gin_trgm_ops);
+CREATE INDEX ar_ordnumber_gin_trgm_idx               ON ar              USING gin (ordnumber               gin_trgm_ops);
+CREATE INDEX ar_quonumber_gin_trgm_idx               ON ar              USING gin (quonumber               gin_trgm_ops);
+CREATE INDEX ar_cusordnumber_gin_trgm_idx            ON ar              USING gin (cusordnumber            gin_trgm_ops);
+CREATE INDEX ar_transaction_description_gin_trgm_idx ON ar              USING gin (transaction_description gin_trgm_ops);
+
+CREATE INDEX ap_invnumber_gin_trgm_idx               ON ap              USING gin (invnumber               gin_trgm_ops);
+CREATE INDEX ap_ordnumber_gin_trgm_idx               ON ap              USING gin (ordnumber               gin_trgm_ops);
+CREATE INDEX ap_quonumber_gin_trgm_idx               ON ap              USING gin (quonumber               gin_trgm_ops);
+CREATE INDEX ap_transaction_description_gin_trgm_idx ON ap              USING gin (transaction_description gin_trgm_ops);
+
+CREATE INDEX gl_description_gin_trgm_idx             ON gl              USING gin (description             gin_trgm_ops);
+CREATE INDEX gl_reference_gin_trgm_idx               ON gl              USING gin (reference               gin_trgm_ops);
+
+CREATE INDEX orderitems_description_gin_trgm_idx     ON orderitems      USING gin (description             gin_trgm_ops);
+
+CREATE INDEX doi_description_gin_trgm_idx       ON delivery_order_items USING gin (description             gin_trgm_ops);
+
+CREATE INDEX invoice_description_gin_trgm_idx        ON invoice         USING gin (description             gin_trgm_ops);
diff --git a/templates/webpages/shops/form.html b/templates/webpages/shops/form.html
index f82812b19..d73f9ae34 100644
--- a/templates/webpages/shops/form.html
+++ b/templates/webpages/shops/form.html
@@ -6,7 +6,7 @@
 
 <h1>[% HTML.escape(title) %]</h1>
 [% #Dumper.dump_html(SELF.shop) %]
-<form action="controller.pl" method="post">
+<form id="form" action="controller.pl" method="post">
 
 [%- INCLUDE 'common/flash.html' %]
 
@@ -34,19 +34,32 @@
     <td>[% L.select_tag('shop.taxzone_id', SELF.taxzone_id, value_key = 'id', title_key = 'name', with_empty = 0, default = SELF.shop.taxzone_id, default_value_key='id' ) %]</td>
   </tr>
   <tr>
-    <th align="right">[% 'URL' | $T8 %]</th>
-    <td>[%- L.input_tag("shop.url", SELF.shop.url, size=size) %]</td>
+    <th align="right">[% 'Protocol' | $T8 %]</th>
+    <td>[% L.select_tag('shop.protocol', SELF.protocols value_key = 'id', title_key = 'name', with_empty = 0, default = SELF.shop.protocol, default_value_key='id' ) %]</td>
+  </tr>
+  <tr>
+    <th align="right">[% 'Server' | $T8 %]</th>
+    <td>[%- L.input_tag("shop.server", SELF.shop.server, size=size) %]</td>
   </tr>
   <tr>
     <th align="right">[% 'Port' | $T8 %]</th>
     <td>[%- L.input_tag("shop.port", SELF.shop.port, size=5) %]</td>
-   <tr>
+  </tr>
+  <tr>
+    <th align="right">[% 'Path' | $T8 %]</th>
+    <td>[%- L.input_tag("shop.path", SELF.shop.path, size=size) %]</td>
+  </tr>
+  <tr>
+    <th align="right">[% 'Realm' | $T8 %]</th>
+    <td>[%- L.input_tag("shop.realm", SELF.shop.realm, size=size) %]</td>
+  </tr>
+  <tr>
     <th align="right">[% 'User' | $T8 %]</th>
-    <td>[%- L.input_tag("shop.login", SELF.shop.login, size=12) %]</td>
+    <td>[%- L.input_tag("shop.login", SELF.shop.login, size=size) %]</td>
   </tr>
   <tr>
     <th align="right">[% 'Password' | $T8 %]</th>
-    <td>[%- L.input_tag("shop.password", SELF.shop.password, size=12) %]</td>
+    <td>[%- L.input_tag("shop.password", SELF.shop.password, size=size) %]</td>
   </tr>
   <tr>
     <th align="right">[% 'Last ordernumber' | $T8 %]</th>
@@ -62,31 +75,7 @@
   </tr>
 </table>
 
- <p>
-  [% L.hidden_tag("action", "Shop/dispatch") %]
-  [% L.submit_tag("action_" _  (SELF.shop.id ? "update" : "create"), LxERP.t8('Save'), onclick="return check_prerequisites();") %]
-  [%- IF SELF.shop.id -%]
-    [% L.submit_tag("action_delete", LxERP.t8('Delete')) %]
-  [%- END %]
-  <a href="[% SELF.url_for(action='list') %]">[%- LxERP.t8("Cancel") %]</a>
- </p>
-
  <hr>
- <table>
-  <tr>
-    <th align="right">[% 'Description' | $T8 %]</th>
-    <th>Last update</th>
-    <th>[% 'Available qty' | $T8 %]</th>
-  </tr>
- [%- FOREACH shop_part = SELF.shop.updatable_parts %]
-  <tr>
-   <td>[% P.part(shop_part.part) %]</td>
-   <td>[% shop_part.last_update.to_kivitendo %]</td>
-   <td>[% shop_part.part.get_stock %]</td>
-  </tr>
- [%- END %]
- </table>
-
 
 <script type="text/javascript">
 <!--
diff --git a/templates/webpages/shops/list.html b/templates/webpages/shops/list.html
index 4d6ecc76c..2dc96dca1 100644
--- a/templates/webpages/shops/list.html
+++ b/templates/webpages/shops/list.html
@@ -27,7 +27,3 @@
 <hr height="3">
 
 [% L.sortable_element('#shop_list tbody', url=SELF.url_for(action='reorder'), with='shop_id') %]
-
-<p>
- <a href="[% SELF.url_for(action='new') %]">[%- 'Add' | $T8 %]</a>
-</p>
