1 package SL::ShopConnector::WooCommerce;
5 use parent qw(SL::ShopConnector::Base);
9 use LWP::Authen::Digest;
10 use SL::DB::ShopOrder;
11 use SL::DB::ShopOrderItem;
15 use SL::Helper::Flash;
16 use Encode qw(encode_utf8);
19 my ($self, $order_id) = @_;
21 my $dbh = SL::DB::client;
22 my $number_of_orders = 0;
25 my $answer = $self->send_request(
26 "orders/" . $order_id,
31 if($answer->{success}) {
32 my $shoporder = $answer->{data};
34 $dbh->with_transaction( sub{
35 unless ($self->import_data_to_shop_order($shoporder)) { return 0;}
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));
49 push @errors,($::locale->text('Saving failed. Error message from the database: #1', $dbh->error));
53 flash_later('error', $::locale->text('Errors: #1', @errors));
57 %fetched_orders = (shop_description => $self->config->description, number_of_orders => $number_of_orders);
60 shop_id => $self->config->id,
61 shop_description => $self->config->description,
62 message => $answer->{message},
65 %fetched_orders = %error_msg;
67 return \%fetched_orders;
73 my $dbh = SL::DB::client;
74 my $otf = $self->config->orders_to_fetch || 10;
75 my $number_of_orders = 0;
78 my $answer = $self->send_request(
82 "&per_page=$otf&status=pending"
85 if($answer->{success}) {
86 my $orders = $answer->{data};
87 foreach my $shoporder(@{$orders}){
89 $dbh->with_transaction( sub{
90 unless ($self->import_data_to_shop_order($shoporder)) { return 0;}
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));
104 push @errors,($::locale->text('Saving failed. Error message from the database: #1', $dbh->error));
108 flash_later('error', $::locale->text('Errors: #1', @errors));
113 %fetched_orders = (shop_description => $self->config->description, number_of_orders => $number_of_orders);
117 shop_id => $self->config->id,
118 shop_description => $self->config->description,
119 message => $answer->{message},
122 %fetched_orders = %error_msg;
125 return \%fetched_orders;
128 sub import_data_to_shop_order {
129 my ( $self, $import ) = @_;
130 my $shop_order = $self->map_data_to_shoporder($import);
133 my $id = $shop_order->id;
135 my @positions = sort { Sort::Naturally::ncmp($a->{"sku"}, $b->{"sku"}) } @{ $import->{line_items} };
138 my $answer= $self->send_request("taxes");
139 unless ($answer->{success}){ return 0;}
140 my %taxes = map { ($_->{id} => $_) } @{ $answer->{data} };
142 my $active_price_source = $self->config->price_source;
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
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,
158 my $pos_insert = SL::DB::ShopOrderItem->new(%pos_columns);
162 $shop_order->positions($position-1);
164 my $customer = $shop_order->get_customer;
167 $shop_order->kivi_customer_id($customer->id);
173 sub map_data_to_shoporder {
174 my ($self, $import) = @_;
176 my $parser = DateTime::Format::Strptime->new( pattern => '%Y-%m-%dT%H:%M:%S',
181 my $shop_id = $self->config->id;
183 # Mapping to table shoporders. See https://woocommerce.github.io/woocommerce-rest-api-docs/?shell#order-properties
186 billing_firstname => $import->{billing}->{first_name},
187 billing_lastname => $import->{billing}->{last_name},
189 billing_street => $import->{billing}->{address_1} . ($import->{billing}->{address_2} ? " " . $import->{billing}->{address_2} : ""),
191 billing_city => $import->{billing}->{city},
194 billing_zipcode => $import->{billing}->{postcode},
195 billing_country => $import->{billing}->{country},
196 billing_email => $import->{billing}->{email},
197 billing_phone => $import->{billing}->{phone},
199 #billing_greeting => "",
202 #billing_company => "",
203 #billing_department => "",
207 shop_customer_id => $import->{customer_id},
208 shop_customer_number => $import->{customer_id},
210 remote_ip => $import->{customer_ip_address},
213 shop_customer_comment => $import->{customer_note},
215 #customer_city => "",
216 #customer_company => "",
217 #customer_country => "",
218 #customer_department => "",
219 #customer_email => "",
221 #customer_firstname => "",
222 #customer_greeting => "",
223 #customer_lastname => "",
224 #customer_phone => "",
225 #customer_street => "",
229 delivery_firstname => $import->{shipping}->{first_name},
230 delivery_lastname => $import->{shipping}->{last_name},
231 delivery_company => $import->{shipping}->{company},
233 delivery_street => $import->{shipping}->{address_1} . ($import->{shipping}->{address_2} ? " " . $import->{shipping}->{address_2} : ""),
234 delivery_city => $import->{shipping}->{city},
236 delivery_zipcode => $import->{shipping}->{postcode},
237 delivery_country => $import->{shipping}->{country},
238 #delivery_department => "",
239 #delivery_email => "",
241 #delivery_phone => "",
248 shop_ordernumber => $import->{number},
255 order_date => $parser->parse_datetime($import->{date_created}),
262 shipping_costs => $import->{shipping_costs},
264 shipping_costs_net => $import->{shipping_costs} - $import->{shipping_tax},
267 amount => $import->{total},
269 netamount => $import->{total} - $import->{total_tax},
271 tax_included => $import->{prices_include_tax} eq "true" ? 1 : 0,
273 # ??? payment_id => $import->{payment_method},
274 #payment_method_title
275 payment_description => $import->{payment}->{payment_method_title},
277 shop_trans_id => $import->{id},
283 host => $import->{_links}->{self}[0]->{href},
285 #sepa_account_holder => "",
289 #shop_c_billing_id => "",
290 #shop_c_billing_number => "",
291 shop_c_delivery_id => $import->{shipping_lines}[0]->{id}, # ???
297 my $shop_order = SL::DB::ShopOrder->new(%columns);
301 #TODO CVARS, tax and images
303 my ($self, $shop_part, $todo) = @_;
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;
312 # ($_->config->name => {
313 # value => $_->value_as_text,
314 # is_valid => $_->is_valid
317 # @{ $part->cvars_by_config }
321 foreach my $row_cat ( @{ $shop_part->shop_category } ) {
322 my $temp = { ( id => @{$row_cat}[0], ) };
323 push ( @categories, $temp );
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");
337 if($todo eq "price"){
339 regular_price => $price,
341 }elsif($todo eq "stock"){
343 stock_status => $stock_status,
345 }elsif($todo eq "price_stock"){
347 stock_status => $stock_status,
348 regular_price => $price,
350 }elsif($todo eq "active"){
354 }elsif($todo eq "all"){
355 # mapping still missing attributes,metatags
358 name => $part->description,
359 stock_status => $stock_status,
360 regular_price => $price,
362 description=> $shop_part->shop_description,
363 short_description=> $shop_part->shop_description,
364 categories => [ @categories ],
365 #tax_class => $tax_class,
369 my $dataString = SL::JSON::to_json(\%shop_data);
370 $dataString = encode_utf8($dataString);
372 # LWP->post = create || LWP->put = update
373 my $answer = $self->send_request("products/", undef , "get" , "&sku=$partnumber");
375 if($answer->{success} && scalar @{$answer->{data}}){
377 my $woo_shop_part_id = $answer->{data}[0]->{id};
378 $answer = $self->send_request("products/$woo_shop_part_id", $dataString, "put");
381 $answer = $self->send_request("products", $dataString, "post");
384 # don't know if this is needed
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");
390 return $answer->{success};
395 my $partnumber = $_[1];
397 $partnumber = $::form->escape($partnumber);#don't accept / in partnumber
398 my $answer = $self->send_request("products/", undef , "get" , "&sku=$partnumber");
400 if($answer->{success} && scalar @{$answer->{data}}){
401 my $article = $answer->{data}[0];
412 my $answer = $self->send_request("products/categories");
413 unless($answer->{success}) {
416 my @data = @{$answer->{data}};
417 my %categories = map { ($_->{id} => $_) } @data;
421 my $parent = $categories{$_->{parent}};
423 $parent->{children} ||= [];
424 push @{$parent->{children}},$_;
426 push @categories_tree, $_;
430 return \@categories_tree;
436 my $answer = $self->send_request("system_status");
437 if($answer->{success}) {
438 my $version = $answer->{data}->{environment}->{version};
441 data => { version => $version },
452 my $parameters = $_[2];
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;
461 return $protocol . "://". $server . ":" . $port . $path . $request . "?consumer_key=" . $consumer_key . "&consumer_secret=" . $consumer_secret . $parameters;
467 my $json_data = $_[2];
468 my $method_type = $_[3];
469 my $parameters = $_[4];
471 my $ua = LWP::UserAgent->new;
472 my $url = $self->create_url( $request, $parameters );
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);
480 $answer = $ua->get($url);
483 my $type = $answer->content_type;
484 my $status_line = $answer->status_line;
487 if($answer->is_success && $type eq 'application/json'){
488 my $data_json = $answer->content;
489 my $json = SL::JSON::decode_json($data_json);
497 data => { version => $url . ": " . $status_line, data_type => $type },
498 message => "Error: $status_line",
501 #$main::lxdebug->dump(0, "TST: WooCommerce send_request return ", \%return);