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 DateTime::Format::Strptime;
 
  17 use Sort::Naturally ();
 
  18 use SL::Helper::Flash;
 
  19 use Encode qw(encode_utf8);
 
  23 use Rose::Object::MakeMethods::Generic (
 
  24   'scalar --get_set_init' => [ qw(connector url) ],
 
  28   my ($self, $ordnumber) = @_;
 
  30   my $dbh       = SL::DB::client;
 
  33   my $data      = $self->connector->get($url . "api/orders/$ordnumber?useNumberAsId=true");
 
  37   if ($data->is_success && $data->content_type eq 'application/json'){
 
  38     my $data_json = $data->content;
 
  39     my $import    = SL::JSON::decode_json($data_json);
 
  40     my $shoporder = $import->{data};
 
  41     $dbh->with_transaction( sub{
 
  42       $self->import_data_to_shop_order($import);
 
  45       push @errors,($::locale->text('Saving failed. Error message from the database: #1', $dbh->error));
 
  49       $self->set_orderstatus($import->{data}->{id}, "fetched");
 
  52       flash_later('error', $::locale->text('Database errors: #1', @errors));
 
  54     %fetched_orders = (shop_description => $self->config->description, number_of_orders => $of);
 
  57       shop_id          => $self->config->id,
 
  58       shop_description => $self->config->description,
 
  59       message          => "Error: $data->status_line",
 
  62     %fetched_orders = %error_msg;
 
  65   return \%fetched_orders;
 
  72   my $last_order_number = $self->config->last_order_number;
 
  73   my $otf              = $self->config->orders_to_fetch;
 
  75   my $last_data      = $self->connector->get($url . "api/orders/$last_order_number?useNumberAsId=true");
 
  76   my $last_data_json = $last_data->content;
 
  77   my $last_import    = SL::JSON::decode_json($last_data_json);
 
  79   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});
 
  81   my $dbh = SL::DB->client;
 
  84   if ($orders_data->is_success && $orders_data->content_type eq 'application/json'){
 
  85     my $orders_data_json = $orders_data->content;
 
  86     my $orders_import    = SL::JSON::decode_json($orders_data_json);
 
  87     foreach my $shoporder(@{ $orders_import->{data} }){
 
  89       my $data      = $self->connector->get($url . "api/orders/" . $shoporder->{id});
 
  90       my $data_json = $data->content;
 
  91       my $import    = SL::JSON::decode_json($data_json);
 
  93       $dbh->with_transaction( sub{
 
  94           $self->import_data_to_shop_order($import);
 
  96           $self->config->assign_attributes( last_order_number => $shoporder->{number});
 
 100         push @errors,($::locale->text('Saving failed. Error message from the database: #1', $dbh->error));
 
 104         $self->set_orderstatus($shoporder->{id}, "fetched");
 
 107         flash_later('error', $::locale->text('Database errors: #1', @errors));
 
 110     %fetched_orders = (shop_description => $self->config->description, number_of_orders => $of);
 
 113       shop_id          => $self->config->id,
 
 114       shop_description => $self->config->description,
 
 115       message          => "Error: $orders_data->status_line",
 
 118     %fetched_orders = %error_msg;
 
 121   return \%fetched_orders;
 
 124 sub import_data_to_shop_order {
 
 125   my ( $self, $import ) = @_;
 
 126   my $shop_order = $self->map_data_to_shoporder($import);
 
 129   my $id = $shop_order->id;
 
 131   my @positions = sort { Sort::Naturally::ncmp($a->{"articleNumber"}, $b->{"articleNumber"}) } @{ $import->{data}->{details} };
 
 132   #my @positions = sort { Sort::Naturally::ncmp($a->{"partnumber"}, $b->{"partnumber"}) } @{ $import->{data}->{details} };
 
 134   my $active_price_source = $self->config->price_source;
 
 136   foreach my $pos(@positions) {
 
 137     my $price = $::form->round_amount($pos->{price},2);
 
 138     my %pos_columns = ( description          => $pos->{articleName},
 
 139                         partnumber           => $pos->{articleNumber},
 
 141                         quantity             => $pos->{quantity},
 
 142                         position             => $position,
 
 143                         tax_rate             => $pos->{taxRate},
 
 144                         shop_trans_id        => $pos->{articleId},
 
 145                         shop_order_id        => $id,
 
 146                         active_price_source  => $active_price_source,
 
 148     my $pos_insert = SL::DB::ShopOrderItem->new(%pos_columns);
 
 152   $shop_order->positions($position-1);
 
 154   my $customer = $shop_order->get_customer;
 
 157     $shop_order->kivi_customer_id($customer->id);
 
 162 sub map_data_to_shoporder {
 
 163   my ($self, $import) = @_;
 
 165   my $parser = DateTime::Format::Strptime->new( pattern   => '%Y-%m-%dT%H:%M:%S',
 
 169   my $orderdate = $parser->parse_datetime($import->{data}->{orderTime});
 
 171   my $shop_id      = $self->config->id;
 
 172   my $tax_included = $self->config->pricetype;
 
 174   # Mapping to table shoporders. See http://community.shopware.com/_detail_1690.html#GET_.28Liste.29
 
 176     amount                  => $import->{data}->{invoiceAmount},
 
 177     billing_city            => $import->{data}->{billing}->{city},
 
 178     billing_company         => $import->{data}->{billing}->{company},
 
 179     billing_country         => $import->{data}->{billing}->{country}->{name},
 
 180     billing_department      => $import->{data}->{billing}->{department},
 
 181     billing_email           => $import->{data}->{customer}->{email},
 
 182     billing_fax             => $import->{data}->{billing}->{fax},
 
 183     billing_firstname       => $import->{data}->{billing}->{firstName},
 
 184     #billing_greeting        => ($import->{data}->{billing}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
 
 185     billing_lastname        => $import->{data}->{billing}->{lastName},
 
 186     billing_phone           => $import->{data}->{billing}->{phone},
 
 187     billing_street          => $import->{data}->{billing}->{street},
 
 188     billing_vat             => $import->{data}->{billing}->{vatId},
 
 189     billing_zipcode         => $import->{data}->{billing}->{zipCode},
 
 190     customer_city           => $import->{data}->{billing}->{city},
 
 191     customer_company        => $import->{data}->{billing}->{company},
 
 192     customer_country        => $import->{data}->{billing}->{country}->{name},
 
 193     customer_department     => $import->{data}->{billing}->{department},
 
 194     customer_email          => $import->{data}->{customer}->{email},
 
 195     customer_fax            => $import->{data}->{billing}->{fax},
 
 196     customer_firstname      => $import->{data}->{billing}->{firstName},
 
 197     #customer_greeting       => ($import->{data}->{billing}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
 
 198     customer_lastname       => $import->{data}->{billing}->{lastName},
 
 199     customer_phone          => $import->{data}->{billing}->{phone},
 
 200     customer_street         => $import->{data}->{billing}->{street},
 
 201     customer_vat            => $import->{data}->{billing}->{vatId},
 
 202     customer_zipcode        => $import->{data}->{billing}->{zipCode},
 
 203     customer_newsletter     => $import->{data}->{customer}->{newsletter},
 
 204     delivery_city           => $import->{data}->{shipping}->{city},
 
 205     delivery_company        => $import->{data}->{shipping}->{company},
 
 206     delivery_country        => $import->{data}->{shipping}->{country}->{name},
 
 207     delivery_department     => $import->{data}->{shipping}->{department},
 
 208     delivery_email          => "",
 
 209     delivery_fax            => $import->{data}->{shipping}->{fax},
 
 210     delivery_firstname      => $import->{data}->{shipping}->{firstName},
 
 211     #delivery_greeting       => ($import->{data}->{shipping}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
 
 212     delivery_lastname       => $import->{data}->{shipping}->{lastName},
 
 213     delivery_phone          => $import->{data}->{shipping}->{phone},
 
 214     delivery_street         => $import->{data}->{shipping}->{street},
 
 215     delivery_vat            => $import->{data}->{shipping}->{vatId},
 
 216     delivery_zipcode        => $import->{data}->{shipping}->{zipCode},
 
 217     host                    => $import->{data}->{shop}->{hosts},
 
 218     netamount               => $import->{data}->{invoiceAmountNet},
 
 219     order_date              => $orderdate,
 
 220     payment_description     => $import->{data}->{payment}->{description},
 
 221     payment_id              => $import->{data}->{paymentId},
 
 222     remote_ip               => $import->{data}->{remoteAddress},
 
 223     sepa_account_holder     => $import->{data}->{paymentIntances}->{accountHolder},
 
 224     sepa_bic                => $import->{data}->{paymentIntances}->{bic},
 
 225     sepa_iban               => $import->{data}->{paymentIntances}->{iban},
 
 226     shipping_costs          => $import->{data}->{invoiceShipping},
 
 227     shipping_costs_net      => $import->{data}->{invoiceShippingNet},
 
 228     shop_c_billing_id       => $import->{data}->{billing}->{customerId},
 
 229     shop_c_billing_number   => $import->{data}->{billing}->{number},
 
 230     shop_c_delivery_id      => $import->{data}->{shipping}->{id},
 
 231     shop_customer_id        => $import->{data}->{customerId},
 
 232     shop_customer_number    => $import->{data}->{billing}->{number},
 
 233     shop_customer_comment   => $import->{data}->{customerComment},
 
 235     shop_ordernumber        => $import->{data}->{number},
 
 236     shop_trans_id           => $import->{data}->{id},
 
 237     tax_included            => $tax_included eq "brutto" ? 1 : 0,
 
 240   my $shop_order = SL::DB::ShopOrder->new(%columns);
 
 247   my $url        = $self->url;
 
 248   my $data       = $self->connector->get($url . "api/categories");
 
 249   my $data_json  = $data->content;
 
 250   my $import     = SL::JSON::decode_json($data_json);
 
 251   my @daten      = @{$import->{data}};
 
 252   my %categories = map { ($_->{id} => $_) } @daten;
 
 255     my $parent = $categories{$_->{parentId}};
 
 256     $parent->{children} ||= [];
 
 257     push @{$parent->{children}},$_;
 
 266   my $url       = $self->url;
 
 267   my $data      = $self->connector->get($url . "api/version");
 
 268   my $type = $data->content_type;
 
 269   my $status_line = $data->status_line;
 
 271   if($data->is_success && $type eq 'application/json'){
 
 272     my $data_json = $data->content;
 
 273     return SL::JSON::decode_json($data_json);
 
 275     my %return = ( success => 0,
 
 276                    data    => { version => $url . ": " . $status_line, revision => $type },
 
 277                    message => "Server not found or wrong data type",
 
 284   my ($self, $shop_part, $todo) = @_;
 
 286   #shop_part is passed as a param
 
 287   die unless ref($shop_part) eq 'SL::DB::ShopPart';
 
 289   my $url = $self->url;
 
 290   my $part = SL::DB::Part->new(id => $shop_part->part_id)->load;
 
 293   my $cvars = { map { ($_->config->name => { value => $_->value_as_text, is_valid => $_->is_valid }) } @{ $part->cvars_by_config } };
 
 296   foreach my $row_cat ( @{ $shop_part->shop_category } ) {
 
 297     my $temp = { ( id => @{$row_cat}[0], ) };
 
 298     push ( @cat, $temp );
 
 301   my @upload_img = $shop_part->get_images;
 
 302   my $tax_n_price = $shop_part->get_tax_and_price;
 
 303   my $price = $tax_n_price->{price};
 
 304   my $taxrate = $tax_n_price->{tax};
 
 305   # mapping to shopware still missing attributes,metatags
 
 308   if($todo eq "price"){
 
 309     %shop_data = ( mainDetail => { number   => $part->partnumber,
 
 310                                    prices   =>  [ { from             => 1,
 
 312                                                     customerGroupKey => 'EK',
 
 317   }elsif($todo eq "stock"){
 
 318     %shop_data = ( mainDetail => { number   => $part->partnumber,
 
 319                                    inStock  => $part->onhand,
 
 322   }elsif($todo eq "price_stock"){
 
 323     %shop_data =  ( mainDetail => { number   => $part->partnumber,
 
 324                                     inStock  => $part->onhand,
 
 325                                     prices   =>  [ { from             => 1,
 
 327                                                      customerGroupKey => 'EK',
 
 332   }elsif($todo eq "active"){
 
 333     %shop_data =  ( mainDetail => { number   => $part->partnumber,
 
 335                     active => ($part->partnumber == 1 ? 0 : 1),
 
 337   }elsif($todo eq "all"){
 
 338   # mapping to shopware still missing attributes,metatags
 
 339     %shop_data =  (   name              => $part->description,
 
 340                       mainDetail        => { number   => $part->partnumber,
 
 341                                              inStock  => $part->onhand,
 
 342                                              prices   =>  [ {          from   => 1,
 
 344                                                             customerGroupKey  => 'EK',
 
 347                                              active   => $shop_part->active,
 
 348                                              #attribute => { attr1  => $cvars->{CVARNAME}->{value}, } , #HowTo handle attributes
 
 350                       supplier          => 'AR', # Is needed by shopware,
 
 351                       descriptionLong   => $shop_part->shop_description,
 
 352                       active            => $shop_part->active,
 
 353                       images            => [ @upload_img ],
 
 354                       __options_images  => { replace => 1, },
 
 355                       categories        => [ @cat ],
 
 356                       description       => $shop_part->shop_description,
 
 357                       categories        => [ @cat ],
 
 363   my $dataString = SL::JSON::to_json(\%shop_data);
 
 364   $dataString    = encode_utf8($dataString);
 
 368   my ($import,$data,$data_json);
 
 369   my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
 
 370   # 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
 
 371   # LWP->post = create LWP->put = update
 
 372     $data       = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
 
 373     $data_json  = $data->content;
 
 374     $import     = SL::JSON::decode_json($data_json);
 
 375   if($import->{success}){
 
 377     my $partnumber  = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
 
 378     $upload         = $self->connector->put($url . "api/articles/$partnumber?useNumberAsId=true", Content => $dataString);
 
 379     my $data_json   = $upload->content;
 
 380     $upload_content = SL::JSON::decode_json($data_json);
 
 383     $upload         = $self->connector->post($url . "api/articles/", Content => $dataString);
 
 384     my $data_json   = $upload->content;
 
 385     $upload_content = SL::JSON::decode_json($data_json);
 
 387   # don't know if this is needed
 
 389     my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
 
 390     my $imgup      = $self->connector->put($url . "api/generatearticleimages/$partnumber?useNumberAsId=true");
 
 393   return $upload_content->{success};
 
 397   my ($self,$partnumber) = @_;
 
 399   my $url       = $self->url;
 
 400   $partnumber   = $::form->escape($partnumber);#shopware don't accept / in articlenumber
 
 401   my $data      = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
 
 402   my $data_json = $data->content;
 
 403   return SL::JSON::decode_json($data_json);
 
 406 sub set_orderstatus {
 
 407   my ($self,$order_id, $status) = @_;
 
 408   if ($status eq "fetched") { $status = 1; }
 
 409   if ($status eq "completed") { $status = 2; }
 
 410   my %new_status = (orderStatusId => $status);
 
 411   my $new_status_json = SL::JSON::to_json(\%new_status);
 
 412   $self->connector->put($self->url . "api/orders/$order_id", Content => $new_status_json);
 
 417   $self->url($self->config->protocol . "://" . $self->config->server . ":" . $self->config->port . $self->config->path);
 
 422   my $ua = LWP::UserAgent->new;
 
 424       $self->config->server . ":" . $self->config->port,
 
 425       $self->config->realm,
 
 426       $self->config->login => $self->config->password
 
 441 SL::Shopconnecter::Shopware - connector for shopware 5
 
 448 This is the connector to shopware.
 
 449 In this file you can do the mapping to your needs.
 
 450 see https://developers.shopware.com/developers-guide/rest-api/
 
 451 for more information.
 
 457 =item C<get_one_order>
 
 459 Fetches one order specified by ordnumber
 
 461 =item C<get_new_orders>
 
 463 Fetches new order by parameters from shop configuration
 
 465 =item C<import_data_to_shop_order>
 
 467 Creates on shoporder object from json
 
 468 Here is the mapping for the positions.
 
 469 see https://developers.shopware.com/developers-guide/rest-api/
 
 470 for detailed information
 
 472 =item C<map_data_to_shoporder>
 
 474 Here is the mapping for the order data.
 
 475 see https://developers.shopware.com/developers-guide/rest-api/
 
 476 for detailed information
 
 478 =item C<get_categories>
 
 482 Use this for test Connection
 
 487 Here is the mapping for the article data.
 
 488 see https://developers.shopware.com/developers-guide/rest-api/
 
 489 for detailed information
 
 509 Pricesrules, pricessources aren't fully implemented yet.
 
 510 Payments aren't implemented( need to map payments from Shopware like invoice, paypal etc. to payments in kivitendo)
 
 518 W. Hahn E<lt>wh@futureworldsearch.netE<gt>