1 package SL::ShopConnector::Shopware;
 
   5 use parent qw(SL::ShopConnector::Base);
 
  10 use LWP::Authen::Digest;
 
  11 use SL::DB::ShopOrder;
 
  12 use SL::DB::ShopOrderItem;
 
  14 use SL::DB::PaymentTerm;
 
  15 use DateTime::Format::Strptime;
 
  18 use Sort::Naturally ();
 
  19 use SL::Helper::Flash;
 
  20 use Encode qw(encode_utf8);
 
  24 use Rose::Object::MakeMethods::Generic (
 
  25   'scalar --get_set_init' => [ qw(connector url) ],
 
  29   my ($self, $ordnumber) = @_;
 
  31   my $dbh       = SL::DB::client;
 
  34   my $data      = $self->connector->get($url . "api/orders/$ordnumber?useNumberAsId=true");
 
  38   if ($data->is_success && $data->content_type eq 'application/json'){
 
  39     my $data_json = $data->content;
 
  40     my $import    = SL::JSON::decode_json($data_json);
 
  41     my $shoporder = $import->{data};
 
  42     $dbh->with_transaction( sub{
 
  43       $self->import_data_to_shop_order($import);
 
  46       push @errors,($::locale->text('Saving failed. Error message from the database: #1', $dbh->error));
 
  50       $self->set_orderstatus($import->{data}->{id}, "fetched");
 
  53       flash_later('error', $::locale->text('Database errors: #1', @errors));
 
  55     %fetched_orders = (shop_description => $self->config->description, number_of_orders => $of);
 
  58       shop_id          => $self->config->id,
 
  59       shop_description => $self->config->description,
 
  60       message          => "Error: $data->status_line",
 
  63     %fetched_orders = %error_msg;
 
  66   return \%fetched_orders;
 
  73   my $last_order_number = $self->config->last_order_number;
 
  74   my $otf              = $self->config->orders_to_fetch;
 
  76   my $last_data      = $self->connector->get($url . "api/orders/$last_order_number?useNumberAsId=true");
 
  77   my $last_data_json = $last_data->content;
 
  78   my $last_import    = SL::JSON::decode_json($last_data_json);
 
  80   my $orders_data      = $self->connector->get($url . "api/orders?limit=$otf&filter[1][property]=status&filter[1][value]=0&filter[0][property]=id&filter[0][expression]=>&filter[0][value]=" . $last_import->{data}->{id});
 
  82   my $dbh = SL::DB->client;
 
  85   if ($orders_data->is_success && $orders_data->content_type eq 'application/json'){
 
  86     my $orders_data_json = $orders_data->content;
 
  87     my $orders_import    = SL::JSON::decode_json($orders_data_json);
 
  88     foreach my $shoporder(@{ $orders_import->{data} }){
 
  90       my $data      = $self->connector->get($url . "api/orders/" . $shoporder->{id});
 
  91       my $data_json = $data->content;
 
  92       my $import    = SL::JSON::decode_json($data_json);
 
  94       $dbh->with_transaction( sub{
 
  95           $self->import_data_to_shop_order($import);
 
  97           $self->config->assign_attributes( last_order_number => $shoporder->{number});
 
 101         push @errors,($::locale->text('Saving failed. Error message from the database: #1', $dbh->error));
 
 105         $self->set_orderstatus($shoporder->{id}, "fetched");
 
 108         flash_later('error', $::locale->text('Database errors: #1', @errors));
 
 111     %fetched_orders = (shop_description => $self->config->description, number_of_orders => $of);
 
 114       shop_id          => $self->config->id,
 
 115       shop_description => $self->config->description,
 
 116       message          => "Error: $orders_data->status_line",
 
 119     %fetched_orders = %error_msg;
 
 122   return \%fetched_orders;
 
 125 sub import_data_to_shop_order {
 
 126   my ( $self, $import ) = @_;
 
 127   my $shop_order = $self->map_data_to_shoporder($import);
 
 130   my $id = $shop_order->id;
 
 132   my @positions = sort { Sort::Naturally::ncmp($a->{"articleNumber"}, $b->{"articleNumber"}) } @{ $import->{data}->{details} };
 
 133   #my @positions = sort { Sort::Naturally::ncmp($a->{"partnumber"}, $b->{"partnumber"}) } @{ $import->{data}->{details} };
 
 135   my $active_price_source = $self->config->price_source;
 
 137   foreach my $pos(@positions) {
 
 138     my $price = $::form->round_amount($pos->{price},2);
 
 139     my %pos_columns = ( description          => $pos->{articleName},
 
 140                         partnumber           => $pos->{articleNumber},
 
 142                         quantity             => $pos->{quantity},
 
 143                         position             => $position,
 
 144                         tax_rate             => $pos->{taxRate},
 
 145                         shop_trans_id        => $pos->{articleId},
 
 146                         shop_order_id        => $id,
 
 147                         active_price_source  => $active_price_source,
 
 149     my $pos_insert = SL::DB::ShopOrderItem->new(%pos_columns);
 
 153   $shop_order->positions($position-1);
 
 155   if ( $self->config->shipping_costs_parts_id ) {
 
 156     my $shipping_part = SL::DB::Part->find_by( id => $self->config->shipping_costs_parts_id);
 
 157     my %shipping_pos = ( description    => $import->{data}->{dispatch}->{name},
 
 158                          partnumber     => $shipping_part->partnumber,
 
 159                          price          => $import->{data}->{invoiceShipping},
 
 161                          position       => $position,
 
 163                          shop_order_id  => $id,
 
 165     my $shipping_pos_insert = SL::DB::ShopOrderItem->new(%shipping_pos);
 
 166     $shipping_pos_insert->save;
 
 169   my $customer = $shop_order->get_customer;
 
 172     $shop_order->kivi_customer_id($customer->id);
 
 177 sub map_data_to_shoporder {
 
 178   my ($self, $import) = @_;
 
 180   my $parser = DateTime::Format::Strptime->new( pattern   => '%Y-%m-%dT%H:%M:%S',
 
 184   my $orderdate = $parser->parse_datetime($import->{data}->{orderTime});
 
 186   my $shop_id      = $self->config->id;
 
 187   my $tax_included = $self->config->pricetype;
 
 189   # Mapping Zahlungsmethoden muss an Firmenkonfiguration angepasst werden
 
 190   my %payment_ids_methods = (
 
 191     # shopware_paymentId => kivitendo_payment_id
 
 193   my $default_payment    = SL::DB::Manager::PaymentTerm->get_first();
 
 194   my $default_payment_id = $default_payment ? $default_payment->id : undef;
 
 195   # Mapping to table shoporders. See http://community.shopware.com/_detail_1690.html#GET_.28Liste.29
 
 197     amount                  => $import->{data}->{invoiceAmount},
 
 198     billing_city            => $import->{data}->{billing}->{city},
 
 199     billing_company         => $import->{data}->{billing}->{company},
 
 200     billing_country         => $import->{data}->{billing}->{country}->{name},
 
 201     billing_department      => $import->{data}->{billing}->{department},
 
 202     billing_email           => $import->{data}->{customer}->{email},
 
 203     billing_fax             => $import->{data}->{billing}->{fax},
 
 204     billing_firstname       => $import->{data}->{billing}->{firstName},
 
 205     #billing_greeting        => ($import->{data}->{billing}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
 
 206     billing_lastname        => $import->{data}->{billing}->{lastName},
 
 207     billing_phone           => $import->{data}->{billing}->{phone},
 
 208     billing_street          => $import->{data}->{billing}->{street},
 
 209     billing_vat             => $import->{data}->{billing}->{vatId},
 
 210     billing_zipcode         => $import->{data}->{billing}->{zipCode},
 
 211     customer_city           => $import->{data}->{billing}->{city},
 
 212     customer_company        => $import->{data}->{billing}->{company},
 
 213     customer_country        => $import->{data}->{billing}->{country}->{name},
 
 214     customer_department     => $import->{data}->{billing}->{department},
 
 215     customer_email          => $import->{data}->{customer}->{email},
 
 216     customer_fax            => $import->{data}->{billing}->{fax},
 
 217     customer_firstname      => $import->{data}->{billing}->{firstName},
 
 218     #customer_greeting       => ($import->{data}->{billing}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
 
 219     customer_lastname       => $import->{data}->{billing}->{lastName},
 
 220     customer_phone          => $import->{data}->{billing}->{phone},
 
 221     customer_street         => $import->{data}->{billing}->{street},
 
 222     customer_vat            => $import->{data}->{billing}->{vatId},
 
 223     customer_zipcode        => $import->{data}->{billing}->{zipCode},
 
 224     customer_newsletter     => $import->{data}->{customer}->{newsletter},
 
 225     delivery_city           => $import->{data}->{shipping}->{city},
 
 226     delivery_company        => $import->{data}->{shipping}->{company},
 
 227     delivery_country        => $import->{data}->{shipping}->{country}->{name},
 
 228     delivery_department     => $import->{data}->{shipping}->{department},
 
 229     delivery_email          => "",
 
 230     delivery_fax            => $import->{data}->{shipping}->{fax},
 
 231     delivery_firstname      => $import->{data}->{shipping}->{firstName},
 
 232     #delivery_greeting       => ($import->{data}->{shipping}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
 
 233     delivery_lastname       => $import->{data}->{shipping}->{lastName},
 
 234     delivery_phone          => $import->{data}->{shipping}->{phone},
 
 235     delivery_street         => $import->{data}->{shipping}->{street},
 
 236     delivery_vat            => $import->{data}->{shipping}->{vatId},
 
 237     delivery_zipcode        => $import->{data}->{shipping}->{zipCode},
 
 238     host                    => $import->{data}->{shop}->{hosts},
 
 239     netamount               => $import->{data}->{invoiceAmountNet},
 
 240     order_date              => $orderdate,
 
 241     payment_description     => $import->{data}->{payment}->{description},
 
 242     payment_id              => $payment_ids_methods{$import->{data}->{paymentId}} || $default_payment_id,
 
 243     remote_ip               => $import->{data}->{remoteAddress},
 
 244     sepa_account_holder     => $import->{data}->{paymentIntances}->{accountHolder},
 
 245     sepa_bic                => $import->{data}->{paymentIntances}->{bic},
 
 246     sepa_iban               => $import->{data}->{paymentIntances}->{iban},
 
 247     shipping_costs          => $import->{data}->{invoiceShipping},
 
 248     shipping_costs_net      => $import->{data}->{invoiceShippingNet},
 
 249     shop_c_billing_id       => $import->{data}->{billing}->{customerId},
 
 250     shop_c_billing_number   => $import->{data}->{billing}->{number},
 
 251     shop_c_delivery_id      => $import->{data}->{shipping}->{id},
 
 252     shop_customer_id        => $import->{data}->{customerId},
 
 253     shop_customer_number    => $import->{data}->{billing}->{number},
 
 254     shop_customer_comment   => $import->{data}->{customerComment},
 
 256     shop_ordernumber        => $import->{data}->{number},
 
 257     shop_trans_id           => $import->{data}->{id},
 
 258     tax_included            => $tax_included eq "brutto" ? 1 : 0,
 
 261   my $shop_order = SL::DB::ShopOrder->new(%columns);
 
 268   my $url        = $self->url;
 
 269   my $data       = $self->connector->get($url . "api/categories");
 
 270   my $data_json  = $data->content;
 
 271   my $import     = SL::JSON::decode_json($data_json);
 
 272   my @daten      = @{$import->{data}};
 
 273   my %categories = map { ($_->{id} => $_) } @daten;
 
 277     # ignore root with id=1
 
 281     my $parent = $categories{$_->{parentId}};
 
 282     if($parent && $parent->{id} != 1) {
 
 283       $parent->{children} ||= [];
 
 284       push @{$parent->{children}},$_;
 
 286       push @categories_tree, $_;
 
 290   return \@categories_tree;
 
 296   my $url       = $self->url;
 
 297   my $data      = $self->connector->get($url . "api/version");
 
 298   my $type = $data->content_type;
 
 299   my $status_line = $data->status_line;
 
 301   if($data->is_success && $type eq 'application/json'){
 
 302     my $data_json = $data->content;
 
 303     return SL::JSON::decode_json($data_json);
 
 305     my %return = ( success => 0,
 
 306                    data    => { version => $url . ": " . $status_line, revision => $type },
 
 307                    message => "Server not found or wrong data type",
 
 314   my ($self, $shop_part, $todo) = @_;
 
 316   #shop_part is passed as a param
 
 317   die unless ref($shop_part) eq 'SL::DB::ShopPart';
 
 319   my $url = $self->url;
 
 320   my $part = SL::DB::Part->new(id => $shop_part->part_id)->load;
 
 323   my $cvars = { map { ($_->config->name => { value => $_->value_as_text, is_valid => $_->is_valid }) } @{ $part->cvars_by_config } };
 
 326   foreach my $row_cat ( @{ $shop_part->shop_category } ) {
 
 327     my $temp = { ( id => @{$row_cat}[0], ) };
 
 328     push ( @cat, $temp );
 
 331   my @upload_img = $shop_part->get_images;
 
 332   my $tax_n_price = $shop_part->get_tax_and_price;
 
 333   my $price = $tax_n_price->{price};
 
 334   my $taxrate = $tax_n_price->{tax};
 
 335   # mapping to shopware still missing attributes,metatags
 
 338   if($todo eq "price"){
 
 339     %shop_data = ( mainDetail => { number   => $part->partnumber,
 
 340                                    prices   =>  [ { from             => 1,
 
 342                                                     customerGroupKey => 'EK',
 
 347   }elsif($todo eq "stock"){
 
 348     %shop_data = ( mainDetail => { number   => $part->partnumber,
 
 349                                    inStock  => $part->onhand,
 
 352   }elsif($todo eq "price_stock"){
 
 353     %shop_data =  ( mainDetail => { number   => $part->partnumber,
 
 354                                     inStock  => $part->onhand,
 
 355                                     prices   =>  [ { from             => 1,
 
 357                                                      customerGroupKey => 'EK',
 
 362   }elsif($todo eq "active"){
 
 363     %shop_data =  ( mainDetail => { number   => $part->partnumber,
 
 365                     active => ($part->partnumber == 1 ? 0 : 1),
 
 367   }elsif($todo eq "all"){
 
 368   # mapping to shopware still missing attributes,metatags
 
 369     %shop_data =  (   name              => $part->description,
 
 370                       mainDetail        => { number   => $part->partnumber,
 
 371                                              inStock  => $part->onhand,
 
 372                                              prices   =>  [ {          from   => 1,
 
 374                                                             customerGroupKey  => 'EK',
 
 377                                              active   => $shop_part->active,
 
 378                                              #attribute => { attr1  => $cvars->{CVARNAME}->{value}, } , #HowTo handle attributes
 
 380                       supplier          => 'AR', # Is needed by shopware,
 
 381                       descriptionLong   => $shop_part->shop_description,
 
 382                       active            => $shop_part->active,
 
 383                       images            => [ @upload_img ],
 
 384                       __options_images  => { replace => 1, },
 
 385                       categories        => [ @cat ],
 
 386                       description       => $shop_part->shop_description,
 
 387                       categories        => [ @cat ],
 
 393   my $dataString = SL::JSON::to_json(\%shop_data);
 
 394   $dataString    = encode_utf8($dataString);
 
 398   my ($import,$data,$data_json);
 
 399   my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
 
 400   # Shopware RestApi sends an erroremail if configured and part not found. But it needs this info to decide if update or create a new article
 
 401   # LWP->post = create LWP->put = update
 
 402     $data       = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
 
 403     $data_json  = $data->content;
 
 404     $import     = SL::JSON::decode_json($data_json);
 
 405   if($import->{success}){
 
 407     my $partnumber  = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
 
 408     $upload         = $self->connector->put($url . "api/articles/$partnumber?useNumberAsId=true", Content => $dataString);
 
 409     my $data_json   = $upload->content;
 
 410     $upload_content = SL::JSON::decode_json($data_json);
 
 413     $upload         = $self->connector->post($url . "api/articles/", Content => $dataString);
 
 414     my $data_json   = $upload->content;
 
 415     $upload_content = SL::JSON::decode_json($data_json);
 
 417   # don't know if this is needed
 
 419     my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
 
 420     my $imgup      = $self->connector->put($url . "api/generatearticleimages/$partnumber?useNumberAsId=true");
 
 423   return $upload_content->{success};
 
 427   my ($self,$partnumber) = @_;
 
 429   my $url       = $self->url;
 
 430   $partnumber   = $::form->escape($partnumber);#shopware don't accept / in articlenumber
 
 431   my $data      = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
 
 432   my $data_json = $data->content;
 
 433   return SL::JSON::decode_json($data_json);
 
 436 sub set_orderstatus {
 
 437   my ($self,$order_id, $status) = @_;
 
 438   if ($status eq "fetched") { $status = 1; }
 
 439   if ($status eq "completed") { $status = 2; }
 
 440   my %new_status = (orderStatusId => $status);
 
 441   my $new_status_json = SL::JSON::to_json(\%new_status);
 
 442   $self->connector->put($self->url . "api/orders/$order_id", Content => $new_status_json);
 
 447   $self->url($self->config->protocol . "://" . $self->config->server . ":" . $self->config->port . $self->config->path);
 
 452   my $ua = LWP::UserAgent->new;
 
 454       $self->config->server . ":" . $self->config->port,
 
 455       $self->config->realm,
 
 456       $self->config->login => $self->config->password
 
 471 SL::Shopconnector::Shopware - connector for shopware 5
 
 478 This is the connector to shopware.
 
 479 In this file you can do the mapping to your needs.
 
 480 see https://developers.shopware.com/developers-guide/rest-api/
 
 481 for more information.
 
 487 =item C<get_one_order>
 
 489 Fetches one order specified by ordnumber
 
 491 =item C<get_new_orders>
 
 493 Fetches new order by parameters from shop configuration
 
 495 =item C<import_data_to_shop_order>
 
 497 Creates on shoporder object from json
 
 498 Here is the mapping for the positions.
 
 499 see https://developers.shopware.com/developers-guide/rest-api/
 
 500 for detailed information
 
 502 =item C<map_data_to_shoporder>
 
 504 Here is the mapping for the order data.
 
 505 see https://developers.shopware.com/developers-guide/rest-api/
 
 506 for detailed information
 
 508 =item C<get_categories>
 
 512 Use this for test Connection
 
 517 Here is the mapping for the article data.
 
 518 see https://developers.shopware.com/developers-guide/rest-api/
 
 519 for detailed information
 
 539 Pricesrules, pricessources aren't fully implemented yet.
 
 540 Payments aren't implemented( need to map payments from Shopware like invoice, paypal etc. to payments in kivitendo)
 
 548 W. Hahn E<lt>wh@futureworldsearch.netE<gt>