Shopware6: Status completed innerhalb des Konnektors mappen
[kivitendo-erp.git] / SL / ShopConnector / Shopware6.pm
index b306014..6d04a1b 100644 (file)
@@ -113,15 +113,6 @@ sub update_part {
   my $part = SL::DB::Part->new(id => $shop_part->part_id)->load;
   die "Shop Part but no kivi Part?" unless ref $part eq 'SL::DB::Part';
 
   my $part = SL::DB::Part->new(id => $shop_part->part_id)->load;
   die "Shop Part but no kivi Part?" unless ref $part eq 'SL::DB::Part';
 
-  my @cat = ();
-  # if the part is connected to a category at all
-  if ($shop_part->shop_category) {
-    foreach my $row_cat ( @{ $shop_part->shop_category } ) {
-      my $temp = { ( id => @{$row_cat}[0] ) };
-      push ( @cat, $temp );
-    }
-  }
-
   my $tax_n_price = $shop_part->get_tax_and_price;
   my $price       = $tax_n_price->{price};
   my $taxrate     = $tax_n_price->{tax};
   my $tax_n_price = $shop_part->get_tax_and_price;
   my $price       = $tax_n_price->{price};
   my $taxrate     = $tax_n_price->{tax};
@@ -138,10 +129,10 @@ sub update_part {
 
   my $update_p;
   $update_p->{productNumber} = $part->partnumber;
 
   my $update_p;
   $update_p->{productNumber} = $part->partnumber;
-  $update_p->{name}          = $part->description;
+  $update_p->{name}          = _u8($part->description);
   $update_p->{description}   =   $shop_part->shop->use_part_longdescription
   $update_p->{description}   =   $shop_part->shop->use_part_longdescription
-                               ? $part->notes
-                               : $shop_part->shop_description;
+                               ? _u8($part->notes)
+                               : _u8($shop_part->shop_description);
 
   # locales simple check for english
   my $english = SL::DB::Manager::Language->get_first(query => [ description   => { ilike => 'Englisch' },
 
   # locales simple check for english
   my $english = SL::DB::Manager::Language->get_first(query => [ description   => { ilike => 'Englisch' },
@@ -151,8 +142,8 @@ sub update_part {
     # add english translation for product
     # TODO (or not): No Translations for shop_part->shop_description available
     my $translation = first { $english->id == $_->language_id } @{ $part->translations };
     # add english translation for product
     # TODO (or not): No Translations for shop_part->shop_description available
     my $translation = first { $english->id == $_->language_id } @{ $part->translations };
-    $update_p->{translations}->{'en-GB'}->{name}        = $translation->{translation};
-    $update_p->{translations}->{'en-GB'}->{description} = $translation->{longdescription};
+    $update_p->{translations}->{'en-GB'}->{name}        = _u8($translation->{translation});
+    $update_p->{translations}->{'en-GB'}->{description} = _u8($translation->{longdescription});
   }
 
   $update_p->{stock}  = $::form->round_amount($part->onhand, 0) if ($todo =~ m/(stock|all)/);
   }
 
   $update_p->{stock}  = $::form->round_amount($part->onhand, 0) if ($todo =~ m/(stock|all)/);
@@ -189,7 +180,11 @@ sub update_part {
     }
     undef $update_p->{partNumber}; # we dont need this one
     $ret = $self->connector->PATCH('api/product/' . $one_d->{id}, to_json($update_p));
     }
     undef $update_p->{partNumber}; # we dont need this one
     $ret = $self->connector->PATCH('api/product/' . $one_d->{id}, to_json($update_p));
-    die "Updating part with " .  $part->partnumber . " failed: " . $ret->responseContent() unless (204 == $ret->responseCode());
+    unless (204 == $ret->responseCode()) {
+      die t8('Part Description is too long for this Shopware version. It should have lower than 255 characters.')
+         if $ret->responseContent() =~ m/Diese Zeichenkette ist zu lang. Sie sollte.*255 Zeichen/;
+      die "Updating part with " .  $part->partnumber . " failed: " . $ret->responseContent() unless (204 == $ret->responseCode());
+    }
   } else {
     # create part
     # 1. get the correct tax for this product
   } else {
     # create part
     # 1. get the correct tax for this product
@@ -240,8 +235,72 @@ sub update_part {
     $self->sync_all_images(shop_part => $shop_part, set_cover => 1, delete_orphaned => 1);
   } catch { die "Could not sync images for Part " . $part->partnumber . " Reason: $_" };
 
     $self->sync_all_images(shop_part => $shop_part, set_cover => 1, delete_orphaned => 1);
   } catch { die "Could not sync images for Part " . $part->partnumber . " Reason: $_" };
 
+  # if there are categories try to sync this with the shop_part
+  try {
+    $self->sync_all_categories(shop_part => $shop_part);
+  } catch { die "Could not sync Categories for Part " . $part->partnumber . " Reason: $_" };
+
   return 1; # no invalid response code -> success
 }
   return 1; # no invalid response code -> success
 }
+sub sync_all_categories {
+  my ($self, %params) = @_;
+
+  my $shop_part = delete $params{shop_part};
+  croak "Need a valid Shop Part for updating Images" unless ref($shop_part) eq 'SL::DB::ShopPart';
+
+  my $partnumber = $shop_part->part->partnumber;
+  die "Shop Part but no kivi Partnumber" unless $partnumber;
+
+  my ($ret, $response_code);
+  # 1 get  uuid for product
+  my $product_filter = {
+          'filter' => [
+                        {
+                          'value' => $partnumber,
+                          'type'  => 'equals',
+                          'field' => 'productNumber'
+                        }
+                      ]
+    };
+
+  $ret = $self->connector->POST('api/search/product', to_json($product_filter));
+  $response_code = $ret->responseCode();
+  die "Request failed, response code was: $response_code\n" . $ret->responseContent() unless $response_code == 200;
+  my ($product_id, $category_tree);
+  try {
+    $product_id    = from_json($ret->responseContent())->{data}->[0]->{id};
+    $category_tree = from_json($ret->responseContent())->{data}->[0]->{categoryIds};
+  } catch { die "Malformed JSON Data: $_ " . $ret->responseContent();  };
+  my $cat;
+  # if the part is connected to a category at all
+  if ($shop_part->shop_category) {
+    foreach my $row_cat (@{ $shop_part->shop_category }) {
+      $cat->{@{ $row_cat }[0]} = @{ $row_cat }[1];
+    }
+  }
+  # delete
+  foreach my $shopware_cat (@{ $category_tree }) {
+    if ($cat->{$shopware_cat}) {
+      # cat exists and no delete
+      delete $cat->{$shopware_cat};
+      next;
+    }
+    # cat exists and delete
+    $ret = $self->connector->DELETE("api/product/$product_id/categories/$shopware_cat");
+    $response_code = $ret->responseCode();
+    die "Request failed, response code was: $response_code\n" . $ret->responseContent() unless $response_code == 204;
+  }
+  # now add only new categories
+  my $p;
+  $p->{id}  = $product_id;
+  $p->{categories} = ();
+  foreach my $new_cat (keys %{ $cat }) {
+    push @{ $p->{categories} }, {id => $new_cat};
+  }
+    $ret = $self->connector->PATCH("api/product/$product_id", to_json($p));
+    $response_code = $ret->responseCode();
+    die "Request failed, response code was: $response_code\n" . $ret->responseContent() unless $response_code == 204;
+}
 
 sub sync_all_images {
   my ($self, %params) = @_;
 
 sub sync_all_images {
   my ($self, %params) = @_;
@@ -642,8 +701,11 @@ sub get_version {
 sub set_orderstatus {
   my ($self, $order_id, $transition) = @_;
 
 sub set_orderstatus {
   my ($self, $order_id, $transition) = @_;
 
-  croak "No order ID, should be in format [0-9a-f]{32}" unless $order_id   =~ m/^[0-9a-f]{32}$/;
-  croak "NO valid transition value"                     unless $transition =~ m/(open|process|cancel|complete)/;
+  # one state differs
+  $transition = 'complete' if $transition eq 'completed';
+
+  croak "No shop order ID, should be in format [0-9a-f]{32}" unless $order_id   =~ m/^[0-9a-f]{32}$/;
+  croak "NO valid transition value"                          unless $transition =~ m/(open|process|cancel|complete)/;
   my $ret;
   $ret = $self->connector->POST("/api/_action/order/$order_id/state/$transition");
   my $response_code = $ret->responseCode();
   my $ret;
   $ret = $self->connector->POST("/api/_action/order/$order_id/state/$transition");
   my $response_code = $ret->responseCode();
@@ -654,7 +716,10 @@ sub set_orderstatus {
 sub init_connector {
   my ($self) = @_;
 
 sub init_connector {
   my ($self) = @_;
 
-  my $client = REST::Client->new(host => $self->config->server);
+  my $protocol = $self->config->server =~ /(^https:\/\/|^http:\/\/)/ ? '' : $self->config->protocol . '://';
+  my $client   = REST::Client->new(host => $protocol . $self->config->server);
+
+  $client->getUseragent()->proxy([$self->config->protocol], $self->config->proxy) if $self->config->proxy;
   $client->addHeader('Content-Type', 'application/json');
   $client->addHeader('charset',      'UTF-8');
   $client->addHeader('Accept',       'application/json');
   $client->addHeader('Content-Type', 'application/json');
   $client->addHeader('charset',      'UTF-8');
   $client->addHeader('Accept',       'application/json');
@@ -705,8 +770,8 @@ sub import_data_to_shop_order {
     foreach my $pos (@positions) {
       $position++;
       my $price       = $::form->round_amount($pos->{unitPrice}, 2); # unit
     foreach my $pos (@positions) {
       $position++;
       my $price       = $::form->round_amount($pos->{unitPrice}, 2); # unit
-      my %pos_columns = ( description          => $pos->{product}->{description},
-                          partnumber           => $pos->{label},
+      my %pos_columns = ( description          => $pos->{product}->{name},
+                          partnumber           => $pos->{product}->{productNumber},
                           price                => $price,
                           quantity             => $pos->{quantity},
                           position             => $position,
                           price                => $price,
                           quantity             => $pos->{quantity},
                           position             => $position,
@@ -909,15 +974,13 @@ Updates all metadata for a shop part. See base class for a general description.
 Specific Implementation notes:
 =over 4
 
 Specific Implementation notes:
 =over 4
 
-=item Calls sync_all_images with set_cover = 1 and delete_orphaned = 1
+=item Calls sync_all_images with set_cover = 1 and delete_orphaned = 1
 
 
-=item Checks if longdescription should be taken from part or shop_part
+=item Checks if longdescription should be taken from part or shop_part
 
 
-=item Checks if a language with the name 'Englisch' or template_code 'en'
+=item Checks if a language with the name 'Englisch' or template_code 'en'
       is available and sets the shopware6 'en-GB' locales for the product
 
       is available and sets the shopware6 'en-GB' locales for the product
 
-=back
-
 =item C<sync_all_images (set_cover: 0|1, delete_orphaned: 0|1)>
 
 The connecting key for shopware to kivi images is the image name.
 =item C<sync_all_images (set_cover: 0|1, delete_orphaned: 0|1)>
 
 The connecting key for shopware to kivi images is the image name.
@@ -935,6 +998,9 @@ entry for the image is deleted.
 More on media and Shopware6 can be found here:
 https://shopware.stoplight.io/docs/admin-api/ZG9jOjEyNjI1Mzkw-media-handling
 
 More on media and Shopware6 can be found here:
 https://shopware.stoplight.io/docs/admin-api/ZG9jOjEyNjI1Mzkw-media-handling
 
+=back
+
+=over 4
 
 =item C<get_article>
 
 
 =item C<get_article>
 
@@ -1014,6 +1080,12 @@ Right now the returning structure and the common parts of the filter are in two
 
 Many error messages are thrown, but at least the more common cases should be localized.
 
 
 Many error messages are thrown, but at least the more common cases should be localized.
 
+=item * Multi language support
+
+By guessing the correct german name for the english language some translation for parts can
+also be synced. This should be more clear (language configuration for shops) and the order
+synchronisation should also handle this (longdescription is simply copied from part.notes)
+
 =back
 
 =head1 AUTHOR
 =back
 
 =head1 AUTHOR