Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 60d3b3b8

Von Tamino vor etwa 4 Jahren hinzugefügt

  • ID 60d3b3b8c3edd40b6cb887012aa76c0ba2fa6906
  • Vorgänger 1ef2ad98
  • Nachfolger 61f133f1

WebshopApi: ShopConnector WooCommerce erstellt

Funktion get_version implementiert.

Unterschiede anzeigen:

SL/ShopConnector/ALL.pm
3 3
use strict;
4 4

  
5 5
use SL::ShopConnector::Shopware;
6
use SL::ShopConnector::WooCommerce;
6 7

  
7 8
my %shop_connector_by_name = (
8 9
  shopware    => 'SL::ShopConnector::Shopware',
10
  woocommerce    => 'SL::ShopConnector::WooCommerce',
9 11
);
10 12

  
11 13
my %shop_connector_by_connector = (
12 14
  shopware   => 'SL::ShopConnector::Shopware',
15
  woocommerce => 'SL::ShopConnector::WooCommerce',
13 16
);
14 17

  
15 18
my @shop_connector_order = qw(
19
  woocommerce
16 20
  shopware
17 21
);
18 22

  
19 23
my @shop_connectors = (
20 24
  { id => "shopware",   description => "Shopware" },
25
  { id => "woocommerce",   description => "WooCommerce" },
21 26
);
22 27

  
23 28

  
SL/ShopConnector/Shopware.pm
249 249
  my @daten      = @{$import->{data}};
250 250
  my %categories = map { ($_->{id} => $_) } @daten;
251 251

  
252
  my @categories_tree;
252 253
  for(@daten) {
254
    # ignore root with id=1
255
    if( $_->{id} == 1) {
256
      next;
257
    }
253 258
    my $parent = $categories{$_->{parentId}};
254
    $parent->{children} ||= [];
255
    push @{$parent->{children}},$_;
259
    if($parent && $parent->{id} != 1) {
260
      $parent->{children} ||= [];
261
      push @{$parent->{children}},$_;
262
    } else {
263
      push @categories_tree, $_;
264
    }
256 265
  }
257 266

  
258
  return \@daten;
267
  return \@categories_tree;
259 268
}
260 269

  
261 270
sub get_version {
SL/ShopConnector/WooCommerce.pm
1
package SL::ShopConnector::WooCommerce;
2

  
3
use strict;
4

  
5
use parent qw(SL::ShopConnector::Base);
6

  
7
use SL::JSON;
8
use LWP::UserAgent;
9
use LWP::Authen::Digest;
10
use SL::DB::ShopOrder;
11
use SL::DB::ShopOrderItem;
12
use SL::DB::History;
13
use SL::DB::File;
14
use Data::Dumper;
15
use SL::Helper::Flash;
16
use Encode qw(encode_utf8);
17

  
18
sub get_one_order {
19
  my ($self, $order_id) = @_;
20

  
21
  my $dbh       = SL::DB::client;
22
  my $number_of_orders = 0;
23
  my @errors;
24

  
25
  my $answer = $self->send_request(
26
    "orders/" . $order_id,
27
    undef,
28
    "get"
29
  );
30
  my %fetched_orders;
31
  if($answer->{success}) {
32
    my $shoporder = $answer->{data};
33

  
34
    $dbh->with_transaction( sub{
35
        unless ($self->import_data_to_shop_order($shoporder)) { return 0;}
36

  
37
        #update status on server
38
        $shoporder->{status} = "processing";
39
        my %new_status = ( status => "processing" );
40
        my $status_json = SL::JSON::to_json( \%new_status);
41
        $answer = $self->send_request("orders/$shoporder->{id}", $status_json, "put");
42
        unless($answer->{success}){
43
          push @errors,($::locale->text('Saving failed. Error message from the server: #1', $answer->message));
44
          return 0
45
        }
46

  
47
        1;
48
      })or do {
49
      push @errors,($::locale->text('Saving failed. Error message from the database: #1', $dbh->error));
50
    };
51

  
52
    if(@errors){
53
      flash_later('error', $::locale->text('Errors: #1', @errors));
54
    } else {
55
      $number_of_orders++;
56
    }
57
    %fetched_orders = (shop_description => $self->config->description, number_of_orders => $number_of_orders);
58
  } else {
59
    my %error_msg  = (
60
      shop_id          => $self->config->id,
61
      shop_description => $self->config->description,
62
      message          => $answer->{message},
63
      error            => 1,
64
    );
65
    %fetched_orders = %error_msg;
66
  }
67
  return \%fetched_orders;
68
}
69

  
70
sub get_new_orders {
71
  my ($self) = @_;
72

  
73
  my $dbh       = SL::DB::client;
74
  my $otf              = $self->config->orders_to_fetch || 10;
75
  my $number_of_orders = 0;
76
  my @errors;
77

  
78
  my $answer = $self->send_request(
79
    "orders",
80
    undef,
81
    "get",
82
    "&per_page=$otf&status=pending"
83
  );
84
  my %fetched_orders;
85
  if($answer->{success}) {
86
    my $orders = $answer->{data};
87
    foreach my $shoporder(@{$orders}){
88

  
89
      $dbh->with_transaction( sub{
90
          unless ($self->import_data_to_shop_order($shoporder)) { return 0;}
91

  
92
          #update status on server
93
          $shoporder->{status} = "processing";
94
          my %new_status = ( status => "processing" );
95
          my $status_json = SL::JSON::to_json( \%new_status);
96
          $answer = $self->send_request("orders/$shoporder->{id}", $status_json, "put");
97
          unless($answer->{success}){
98
            push @errors,($::locale->text('Saving failed. Error message from the server: #1', $answer->message));
99
            return 0;
100
          }
101

  
102
          1;
103
      })or do {
104
        push @errors,($::locale->text('Saving failed. Error message from the database: #1', $dbh->error));
105
      };
106

  
107
      if(@errors){
108
        flash_later('error', $::locale->text('Errors: #1', @errors));
109
      } else {
110
        $number_of_orders++;
111
      }
112
    }
113
    %fetched_orders = (shop_description => $self->config->description, number_of_orders => $number_of_orders);
114

  
115
  } else {
116
    my %error_msg  = (
117
      shop_id          => $self->config->id,
118
      shop_description => $self->config->description,
119
      message          => $answer->{message},
120
      error            => 1,
121
    );
122
    %fetched_orders = %error_msg;
123
  }
124

  
125
  return \%fetched_orders;
126
}
127

  
128
sub import_data_to_shop_order {
129
  my ( $self, $import ) = @_;
130
  my $shop_order = $self->map_data_to_shoporder($import);
131

  
132
  $shop_order->save;
133
  my $id = $shop_order->id;
134

  
135
  my @positions = sort { Sort::Naturally::ncmp($a->{"sku"}, $b->{"sku"}) } @{ $import->{line_items} };
136
  my $position = 1;
137

  
138
  my $answer= $self->send_request("taxes");
139
  unless ($answer->{success}){ return 0;}
140
  my %taxes = map { ($_->{id} => $_) } @{ $answer->{data} };
141

  
142
  my $active_price_source = $self->config->price_source;
143
  #Mapping Positions
144
  foreach my $pos(@positions) {
145
    my $price = $::form->round_amount($pos->{total},2);
146
    my $tax_id = $pos->{taxes}[0]->{id};
147
    my $tax_rate = $taxes{ $tax_id }{rate};
148
    my %pos_columns = ( description          => $pos->{name},
149
                        partnumber           => $pos->{sku}, # sku has to be a valid value in WooCommerce
150
                        price                => $price,
151
                        quantity             => $pos->{quantity},
152
                        position             => $position,
153
                        tax_rate             => $tax_rate,
154
                        shop_trans_id        => $pos->{product_id},
155
                        shop_order_id        => $id,
156
                        active_price_source  => $active_price_source,
157
                      );
158
    my $pos_insert = SL::DB::ShopOrderItem->new(%pos_columns);
159
    $pos_insert->save;
160
    $position++;
161
  }
162
  $shop_order->positions($position-1);
163

  
164
  my $customer = $shop_order->get_customer;
165

  
166
  if(ref($customer)){
167
    $shop_order->kivi_customer_id($customer->id);
168
  }
169
  $shop_order->save;
170
}
171

  
172

  
173
sub map_data_to_shoporder {
174
  my ($self, $import) = @_;
175

  
176
  my $parser = DateTime::Format::Strptime->new( pattern   => '%Y-%m-%dT%H:%M:%S',
177
                                                  locale    => 'de_DE',
178
                                                  time_zone => 'local'
179
                                                );
180

  
181
  my $shop_id      = $self->config->id;
182

  
183
  # Mapping to table shoporders. See https://woocommerce.github.io/woocommerce-rest-api-docs/?shell#order-properties
184
  my %columns = (
185
#billing
186
    billing_firstname       => $import->{billing}->{first_name},
187
    billing_lastname        => $import->{billing}->{last_name},
188
    #address_1 address_2
189
    billing_street         => $import->{billing}->{address_1} . ($import->{billing}->{address_2} ? " " . $import->{billing}->{address_2} : ""),
190
    # ???
191
    billing_city            => $import->{billing}->{city},
192
    #state
193
    # ???
194
    billing_zipcode         => $import->{billing}->{postcode},
195
    billing_country         => $import->{billing}->{country},
196
    billing_email           => $import->{billing}->{email},
197
    billing_phone           => $import->{billing}->{phone},
198

  
199
    #billing_greeting        => "",
200
    #billing_fax             => "",
201
    #billing_vat             => "",
202
    #billing_company         => "",
203
    #billing_department      => "",
204

  
205
#customer
206
    #customer_id
207
    shop_customer_id        => $import->{customer_id},
208
    shop_customer_number    => $import->{customer_id},
209
    #customer_ip_address
210
    remote_ip               => $import->{customer_ip_address},
211
    #customer_user_agent
212
    #customer_note
213
    shop_customer_comment   => $import->{customer_note},
214

  
215
    #customer_city           => "",
216
    #customer_company        => "",
217
    #customer_country        => "",
218
    #customer_department     => "",
219
    #customer_email          => "",
220
    #customer_fax            => "",
221
    #customer_firstname      => "",
222
    #customer_greeting       => "",
223
    #customer_lastname       => "",
224
    #customer_phone          => "",
225
    #customer_street         => "",
226
    #customer_vat            => "",
227

  
228
#shipping
229
    delivery_firstname      => $import->{shipping}->{first_name},
230
    delivery_lastname       => $import->{shipping}->{last_name},
231
    delivery_company        => $import->{shipping}->{company},
232
    #address_1 address_2
233
    delivery_street         => $import->{shipping}->{address_1} . ($import->{shipping}->{address_2} ? " " . $import->{shipping}->{address_2} : ""),
234
    delivery_city           => $import->{shipping}->{city},
235
    #state ???
236
    delivery_zipcode        => $import->{shipping}->{postcode},
237
    delivery_country        => $import->{shipping}->{country},
238
    #delivery_department     => "",
239
    #delivery_email          => "",
240
    #delivery_fax            => "",
241
    #delivery_phone          => "",
242
    #delivery_vat            => "",
243

  
244
#other
245
    #id
246
    #parent_id
247
    #number
248
    shop_ordernumber        => $import->{number},
249
    #order_key
250
    #created_via
251
    #version
252
    #status
253
    #currency
254
    #date_created
255
    order_date              => $parser->parse_datetime($import->{date_created}),
256
    #date_created_gmt
257
    #date_modified
258
    #date_modified_gmt
259
    #discount_total
260
    #discount_tax
261
    #shipping_total
262
    shipping_costs          => $import->{shipping_costs},
263
    #shipping_tax
264
    shipping_costs_net      => $import->{shipping_costs} - $import->{shipping_tax},
265
    #cart_tax
266
    #total
267
    amount                  => $import->{total},
268
    #total_tax
269
    netamount               => $import->{total} - $import->{total_tax},
270
    #prices_include_tax
271
    tax_included            => $import->{prices_include_tax} eq "true" ? 1 : 0,
272
    #payment_method
273
    # ??? payment_id              => $import->{payment_method},
274
    #payment_method_title
275
    payment_description     => $import->{payment}->{payment_method_title},
276
    #transaction_id
277
    shop_trans_id           => $import->{id},
278
    #date_paid
279
    #date_paid_gmt
280
    #date_completed
281
    #date_completed_gmt
282

  
283
    host                    => $import->{_links}->{self}[0]->{href},
284

  
285
    #sepa_account_holder     => "",
286
    #sepa_bic                => "",
287
    #sepa_iban               => "",
288

  
289
    #shop_c_billing_id       => "",
290
    #shop_c_billing_number   => "",
291
    shop_c_delivery_id      => $import->{shipping_lines}[0]->{id}, # ???
292

  
293
# not in Shop
294
    shop_id                 => $shop_id,
295
  );
296

  
297
  my $shop_order = SL::DB::ShopOrder->new(%columns);
298
  return $shop_order;
299
}
300

  
301
#TODO CVARS, tax and images
302
sub update_part {
303
  my ($self, $shop_part, $todo) = @_;
304

  
305
  #shop_part is passed as a param
306
  die unless ref($shop_part) eq 'SL::DB::ShopPart';
307
  my $part = SL::DB::Part->new(id => $shop_part->part_id)->load;
308

  
309
  # CVARS to map
310
  #my $cvars = {
311
  #  map {
312
  #    ($_->config->name => {
313
  #      value => $_->value_as_text,
314
  #      is_valid => $_->is_valid
315
  #    })
316
  #  }
317
  #  @{ $part->cvars_by_config }
318
  #};
319

  
320
  my @categories = ();
321
  foreach my $row_cat ( @{ $shop_part->shop_category } ) {
322
    my $temp = { ( id => @{$row_cat}[0], ) };
323
    push ( @categories, $temp );
324
  }
325

  
326
  #my @upload_img = $shop_part->get_images;
327
  my $partnumber = $::form->escape($part->partnumber);#don't accept / in articlenumber
328
  my $stock_status = ($part->onhand ? "instock" : "outofstock");
329
  my $status = ($shop_part->active ? "publish" : "private");
330
  my $tax_n_price = $shop_part->get_tax_and_price;
331
  my $price = $tax_n_price->{price};
332
  #my $taxrate = $tax_n_price->{tax};
333
  #my $tax_class = ($taxrate >= 16 ? "standard" : "reduzierter-preis");
334

  
335
  my %shop_data;
336

  
337
  if($todo eq "price"){
338
    %shop_data = (
339
      regular_price => $price,
340
    );
341
  }elsif($todo eq "stock"){
342
    %shop_data = (
343
      stock_status => $stock_status,
344
    );
345
  }elsif($todo eq "price_stock"){
346
    %shop_data =  (
347
      stock_status => $stock_status,
348
      regular_price => $price,
349
    );
350
  }elsif($todo eq "active"){
351
    %shop_data =  (
352
      status => $status,
353
    );
354
  }elsif($todo eq "all"){
355
  # mapping  still missing attributes,metatags
356
    %shop_data =  (
357
      sku => $partnumber,
358
      name => $part->description,
359
      stock_status => $stock_status,
360
      regular_price => $price,
361
      status => $status,
362
      description=> $shop_part->shop_description,
363
      short_description=> $shop_part->shop_description,
364
      categories => [ @categories ],
365
      #tax_class => $tax_class,
366
    );
367
  }
368

  
369
  my $dataString = SL::JSON::to_json(\%shop_data);
370
  $dataString    = encode_utf8($dataString);
371

  
372
  # LWP->post = create || LWP->put = update
373
  my $answer = $self->send_request("products/", undef , "get" , "&sku=$partnumber");
374

  
375
  if($answer->{success} && scalar @{$answer->{data}}){
376
    #update
377
    my $woo_shop_part_id = $answer->{data}[0]->{id};
378
    $answer = $self->send_request("products/$woo_shop_part_id", $dataString, "put");
379
  }else{
380
    #upload
381
    $answer = $self->send_request("products", $dataString, "post");
382
  }
383

  
384
  # don't know if this is needed
385
  #if(@upload_img) {
386
  #  my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
387
  #  my $imgup      = $self->connector->put($url . "api/generatearticleimages/$partnumber?useNumberAsId=true");
388
  #}
389

  
390
  return $answer->{success};
391
}
392

  
393
sub get_article {
394
  my ($self) = @_;
395
  my $partnumber = $_[1];
396

  
397
  $partnumber   = $::form->escape($partnumber);#don't accept / in partnumber
398
  my $answer = $self->send_request("products/", undef , "get" , "&sku=$partnumber");
399

  
400
  if($answer->{success} && scalar @{$answer->{data}}){
401
    my $article = $answer->{data}[0];
402
    return $article;
403
  } else {
404
    #What shut be here?
405
    return $answer
406
  }
407
}
408

  
409
sub get_categories {
410
  my ($self) = @_;
411

  
412
  my $answer = $self->send_request("products/categories");
413
  unless($answer->{success}) {
414
    return $answer;
415
  }
416
  my @data = @{$answer->{data}};
417
  my %categories = map { ($_->{id} => $_) } @data;
418

  
419
  my @categories_tree;
420
  for(@data) {
421
    my $parent = $categories{$_->{parent}};
422
    if($parent) {
423
      $parent->{children} ||= [];
424
      push @{$parent->{children}},$_;
425
    } else {
426
      push @categories_tree, $_;
427
    }
428
  }
429

  
430
  return \@categories_tree;
431
}
432

  
433
sub get_version {
434
  my ($self) = @_;
435

  
436
  my $answer = $self->send_request("system_status");
437
  if($answer->{success}) {
438
    my $version = $answer->{data}->{environment}->{version};
439
    my %return = (
440
      success => 1,
441
      data    => { version => $version },
442
    );
443
    return \%return;
444
  } else {
445
    return $answer;
446
  }
447
}
448

  
449
sub create_url {
450
  my ($self) = @_;
451
  my $request = $_[1];
452
  my $parameters = $_[2];
453

  
454
  my $consumer_key = $self->config->login;
455
  my $consumer_secret = $self->config->password;
456
  my $protocol = $self->config->protocol;
457
  my $server = $self->config->server;
458
  my $port = $self->config->port;
459
  my $path = $self->config->path;
460

  
461
  return $protocol . "://". $server . ":" . $port . $path . $request . "?consumer_key=" . $consumer_key . "&consumer_secret=" . $consumer_secret . $parameters;
462
}
463

  
464
sub send_request {
465
  my ($self) = @_;
466
  my $request = $_[1];
467
  my $json_data = $_[2];
468
  my $method_type = $_[3];
469
  my $parameters = $_[4];
470

  
471
  my $ua = LWP::UserAgent->new;
472
  my $url = $self->create_url( $request, $parameters );
473

  
474
  my $answer;
475
  if( $method_type eq "put" ) {
476
    $answer = $ua->put($url, "Content-Type" => "application/json", Content => $json_data);
477
  } elsif ( $method_type eq "post") {
478
    $answer = $ua->post($url, "Content-Type" => "application/json", Content => $json_data);
479
  } else {
480
    $answer = $ua->get($url);
481
  }
482

  
483
  my $type = $answer->content_type;
484
  my $status_line = $answer->status_line;
485

  
486
  my %return;
487
  if($answer->is_success && $type eq 'application/json'){
488
    my $data_json = $answer->content;
489
    my $json = SL::JSON::decode_json($data_json);
490
    %return = (
491
      success => 1,
492
      data    => $json,
493
    );
494
  }else{
495
    %return = (
496
      success => 0,
497
      data    => { version => $url . ": " . $status_line, data_type => $type },
498
      message => "Error: $status_line",
499
    );
500
  }
501
  #$main::lxdebug->dump(0, "TST: WooCommerce send_request return ", \%return);
502
  return \%return;
503

  
504
}
505

  
506
1;
templates/webpages/shop_part/categories.html
3 3
[%- USE L -%]
4 4
[%- USE P -%]
5 5
[%- USE LxERP -%]
6
[%- USE Dumper -%]
6 7

  
7 8
[%  LxERP.t8("Part") %]: [% HTML.escape(SELF.shop_part.part.displayable_name) %]<br>
8 9
[%  LxERP.t8("Shop") %]: [% HTML.escape(SELF.shop_part.shop.description) %]<br>
9 10

  
10 11
<form action="controller.pl" method="post">
11 12
  [% BLOCK recurse %]
12
    [% # path = '' %]<!-- TODO: Pfad wg neuer Kategorie im Shop anlegen -->
13
    [% FOREACH obj = data %]
14
      <ul>
15
        <li>
16
        [% checked = '' %]
17
        [% # path = path _ obj.name %]
18
        [% # test = path.split('\|') %]
19
        [% IF SELF.shop_part.shop_category.1.grep(obj.name).size %]
20
          [% checked = 'checked' %]
21
        [% ELSE %]
13
      [% FOREACH categorie = categories_array %]
14
        <ul>
15
          <li>
16
          [% checked = '' %]
22 17
          [% FOREACH cat_row = SELF.shop_part.shop_category %]
23
            [% IF cat_row.1.grep(obj.name).size %]
18
            [% IF cat_row.0 == categorie.id %]
24 19
              [% checked = 'checked' %]
25 20
            [% END %]
26 21
          [% END %]
27
        [% END %]
28
          [% L.checkbox_tag('categories[]',value=obj.id, checked=checked) %][% HTML.escape(obj.name) %][% L.hidden_tag("cat_id_" _ obj.id, obj.name) %]</li>
29
        [% IF obj.childrenCount >= 1 %]
30
          [% # path = path _ '|' %]
31
          [% INCLUDE recurse data=obj.children %]
32
        [% END %]
33
      </ul>
22
            [% L.checkbox_tag('categories[]',value=categorie.id, checked=checked) %][% HTML.escape(categorie.name) %][% L.hidden_tag("cat_id_" _ categorie.id, categorie.name) %]
23
          </li>
24
          [% IF categorie.children.size %]
25
            [% INCLUDE recurse categories_array=categorie.children %]
26
          [% END %]
27
        </ul>
34 28
    [% END %]
35 29
  [% END %]
36 30
  <div><h2>[% LxERP.t8("Shopcategories") %]</h2>
37
      [% FOREACH row = CATEGORIES %]
38
<!-- TODO: Is still hardcoded 'Root' is shopware specified -->
39
        [% IF row.name == 'Root' %]
40
          [% IF row.childrenCount >= 1 %]
41
            [% path = '' %]
42
            [% INCLUDE recurse data=row.children path=path %]
43
          [% END %]
44
        [% END %]
45
      [% END %]
31
    [% # Dumper.dump_html( CATEGORIES ) %]
32
    [% INCLUDE recurse categories_array=CATEGORIES %]
46 33
  </div>
47 34
    [% L.button_tag("kivi.ShopPart.save_categories(" _ SELF.shop_part.id _", " _ SELF.shop_part.shop.id _")", LxERP.t8("Save"))  %]</td>
48 35
</form>

Auch abrufbar als: Unified diff