marei: new koma-names + fallback for outdated versions
[kivitendo-erp.git] / SL / ShopConnector / Shopware.pm
1 package SL::ShopConnector::Shopware;
2
3 use strict;
4
5 use parent qw(SL::ShopConnector::Base);
6
7
8 use SL::JSON;
9 use LWP::UserAgent;
10 use LWP::Authen::Digest;
11 use SL::DB::ShopOrder;
12 use SL::DB::ShopOrderItem;
13 use SL::DB::History;
14 use DateTime::Format::Strptime;
15 use SL::DB::File;
16 use Data::Dumper;
17 use Sort::Naturally ();
18 use SL::Helper::Flash;
19 use Encode qw(encode_utf8);
20 use SL::File;
21 use File::Slurp;
22
23 use Rose::Object::MakeMethods::Generic (
24   'scalar --get_set_init' => [ qw(connector url) ],
25 );
26
27 sub get_new_orders {
28   my ($self, $id) = @_;
29
30   my $url              = $self->url;
31   my $ordnumber        = $self->config->last_order_number + 1;
32   my $otf              = $self->config->orders_to_fetch;
33   my $of               = 0;
34   my $orders_data      = $self->connector->get($url . "api/orders?limit=$otf&filter[0][property]=number&filter[0][expression]=>&filter[0][value]=" . $self->config->last_order_number);
35   my $orders_data_json = $orders_data->content;
36   my $orders_import    = SL::JSON::decode_json($orders_data_json);
37
38   if ($orders_import->{success}){
39     foreach my $shoporder(@{ $orders_import->{data} }){
40
41       my $data      = $self->connector->get($url . "api/orders/" . $shoporder->{id});
42       my $data_json = $data->content;
43       my $import    = SL::JSON::decode_json($data_json);
44
45       $self->import_data_to_shop_order($import);
46
47       $self->config->assign_attributes( last_order_number => $ordnumber);
48       $self->config->save;
49       $ordnumber++;
50       $of++;
51     }
52   }
53   my $shop           = $self->config->description;
54   my %fetched_orders = (shop_id => $self->config->description, number_of_orders => $of);
55   return \%fetched_orders;
56 }
57
58 sub import_data_to_shop_order {
59   my ( $self, $import ) = @_;
60   my $shop_order = $self->map_data_to_shoporder($import);
61
62   $shop_order->save;
63   my $id = $shop_order->id;
64
65   my @positions = sort { Sort::Naturally::ncmp($a->{"partnumber"}, $b->{"partnumber"}) } @{ $import->{data}->{details} };
66   my $position = 1;
67   my $active_price_source = $self->config->price_source;
68   #Mapping Positions
69   foreach my $pos(@positions) {
70     my $price = $::form->round_amount($pos->{price},2);
71     my %pos_columns = ( description          => $pos->{articleName},
72                         partnumber           => $pos->{articleNumber},
73                         price                => $price,
74                         quantity             => $pos->{quantity},
75                         position             => $position,
76                         tax_rate             => $pos->{taxRate},
77                         shop_trans_id        => $pos->{articleId},
78                         shop_order_id        => $id,
79                         active_price_source  => $active_price_source,
80                       );
81     my $pos_insert = SL::DB::ShopOrderItem->new(%pos_columns);
82     $pos_insert->save;
83     $position++;
84   }
85   $shop_order->{positions} = $position-1;
86
87   my $customer = $shop_order->get_customer;
88
89   if(ref($customer)){
90     $shop_order->kivi_customer_id($customer->id);
91     $shop_order->save;
92   }
93 }
94
95 sub map_data_to_shoporder {
96   my ($self, $import) = @_;
97
98   my $parser = DateTime::Format::Strptime->new( pattern   => '%Y-%m-%dT%H:%M:%S',
99                                                   locale    => 'de_DE',
100                                                   time_zone => 'local'
101                                                 );
102   my $orderdate = $parser->parse_datetime($import->{data}->{orderTime});
103
104   my $shop_id      = $self->config->id;
105   my $tax_included = $self->config->pricetype;
106   # Mapping to table shoporders. See http://community.shopware.com/_detail_1690.html#GET_.28Liste.29
107   my %columns = (
108     amount                  => $import->{data}->{invoiceAmount},
109     billing_city            => $import->{data}->{billing}->{city},
110     billing_company         => $import->{data}->{billing}->{company},
111     billing_country         => $import->{data}->{billing}->{country}->{name},
112     billing_department      => $import->{data}->{billing}->{department},
113     billing_email           => $import->{data}->{customer}->{email},
114     billing_fax             => $import->{data}->{billing}->{fax},
115     billing_firstname       => $import->{data}->{billing}->{firstName},
116     #billing_greeting        => ($import->{data}->{billing}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
117     billing_lastname        => $import->{data}->{billing}->{lastName},
118     billing_phone           => $import->{data}->{billing}->{phone},
119     billing_street          => $import->{data}->{billing}->{street},
120     billing_vat             => $import->{data}->{billing}->{vatId},
121     billing_zipcode         => $import->{data}->{billing}->{zipCode},
122     customer_city           => $import->{data}->{billing}->{city},
123     customer_company        => $import->{data}->{billing}->{company},
124     customer_country        => $import->{data}->{billing}->{country}->{name},
125     customer_department     => $import->{data}->{billing}->{department},
126     customer_email          => $import->{data}->{customer}->{email},
127     customer_fax            => $import->{data}->{billing}->{fax},
128     customer_firstname      => $import->{data}->{billing}->{firstName},
129     #customer_greeting       => ($import->{data}->{billing}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
130     customer_lastname       => $import->{data}->{billing}->{lastName},
131     customer_phone          => $import->{data}->{billing}->{phone},
132     customer_street         => $import->{data}->{billing}->{street},
133     customer_vat            => $import->{data}->{billing}->{vatId},
134     customer_zipcode        => $import->{data}->{billing}->{zipCode},
135     customer_newsletter     => $import->{data}->{customer}->{newsletter},
136     delivery_city           => $import->{data}->{shipping}->{city},
137     delivery_company        => $import->{data}->{shipping}->{company},
138     delivery_country        => $import->{data}->{shipping}->{country}->{name},
139     delivery_department     => $import->{data}->{shipping}->{department},
140     delivery_email          => "",
141     delivery_fax            => $import->{data}->{shipping}->{fax},
142     delivery_firstname      => $import->{data}->{shipping}->{firstName},
143     #delivery_greeting       => ($import->{data}->{shipping}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
144     delivery_lastname       => $import->{data}->{shipping}->{lastName},
145     delivery_phone          => $import->{data}->{shipping}->{phone},
146     delivery_street         => $import->{data}->{shipping}->{street},
147     delivery_vat            => $import->{data}->{shipping}->{vatId},
148     delivery_zipcode        => $import->{data}->{shipping}->{zipCode},
149     host                    => $import->{data}->{shop}->{hosts},
150     netamount               => $import->{data}->{invoiceAmountNet},
151     order_date              => $orderdate,
152     payment_description     => $import->{data}->{payment}->{description},
153     payment_id              => $import->{data}->{paymentId},
154     remote_ip               => $import->{data}->{remoteAddress},
155     sepa_account_holder     => $import->{data}->{paymentIntances}->{accountHolder},
156     sepa_bic                => $import->{data}->{paymentIntances}->{bic},
157     sepa_iban               => $import->{data}->{paymentIntances}->{iban},
158     shipping_costs          => $import->{data}->{invoiceShipping},
159     shipping_costs_net      => $import->{data}->{invoiceShippingNet},
160     shop_c_billing_id       => $import->{data}->{billing}->{customerId},
161     shop_c_billing_number   => $import->{data}->{billing}->{number},
162     shop_c_delivery_id      => $import->{data}->{shipping}->{id},
163     shop_customer_id        => $import->{data}->{customerId},
164     shop_customer_number    => $import->{data}->{billing}->{number},
165     shop_customer_comment   => $import->{data}->{customerComment},
166     shop_id                 => $shop_id,
167     shop_ordernumber        => $import->{data}->{number},
168     shop_trans_id           => $import->{data}->{id},
169     tax_included            => $tax_included eq "brutto" ? 1 : 0,
170   );
171
172   my $shop_order = SL::DB::ShopOrder->new(%columns);
173   return $shop_order;
174 }
175
176 sub get_categories {
177   my ($self) = @_;
178
179   my $url        = $self->url;
180   my $data       = $self->connector->get($url . "api/categories");
181   my $data_json  = $data->content;
182   my $import     = SL::JSON::decode_json($data_json);
183   my @daten      = @{$import->{data}};
184   my %categories = map { ($_->{id} => $_) } @daten;
185
186   for(@daten) {
187     my $parent = $categories{$_->{parentId}};
188     $parent->{children} ||= [];
189     push @{$parent->{children}},$_;
190   }
191
192   return \@daten;
193 }
194
195 sub get_version {
196   my ($self) = @_;
197
198   my $url       = $self->url;
199   my $data      = $self->connector->get($url . "api/version");
200   my $type = $data->content_type;
201   my $status_line = $data->status_line;
202
203   if($data->is_success && $type eq 'application/json'){
204     my $data_json = $data->content;
205     return SL::JSON::decode_json($data_json);
206   }else{
207     my %return = ( success => 0,
208                    data    => { version => $url . ": " . $status_line, revision => $type },
209                    message => "Server not found or wrong data type",
210                 );
211     return \%return;
212   }
213 }
214
215 sub update_part {
216   my ($self, $shop_part, $todo) = @_;
217
218   #shop_part is passed as a param
219   die unless ref($shop_part) eq 'SL::DB::ShopPart';
220
221   my $url = $self->url;
222   my $part = SL::DB::Part->new(id => $shop_part->{part_id})->load;
223
224   # CVARS to map
225   my $cvars = { map { ($_->config->name => { value => $_->value_as_text, is_valid => $_->is_valid }) } @{ $part->cvars_by_config } };
226
227   my @cat = ();
228   foreach my $row_cat ( @{ $shop_part->shop_category } ) {
229     my $temp = { ( id => @{$row_cat}[0], ) };
230     push ( @cat, $temp );
231   }
232
233   my @upload_img = $shop_part->get_images;
234   my $tax_n_price = $shop_part->get_tax_and_price;
235   my $price = $tax_n_price->{price};
236   my $taxrate = $tax_n_price->{tax};
237   # mapping to shopware still missing attributes,metatags
238   my %shop_data;
239
240   if($todo eq "price"){
241     %shop_data = ( mainDetail => { number   => $part->{partnumber},
242                                    prices   =>  [ { from             => 1,
243                                                     price            => $price,
244                                                     customerGroupKey => 'EK',
245                                                   },
246                                                 ],
247                                   },
248                  );
249   }elsif($todo eq "stock"){
250     %shop_data = ( mainDetail => { number   => $part->{partnumber},
251                                    inStock  => $part->{onhand},
252                                  },
253                  );
254   }elsif($todo eq "price_stock"){
255     %shop_data =  ( mainDetail => { number   => $part->{partnumber},
256                                     inStock  => $part->{onhand},
257                                     prices   =>  [ { from             => 1,
258                                                      price            => $price,
259                                                      customerGroupKey => 'EK',
260                                                    },
261                                                  ],
262                                    },
263                    );
264   }elsif($todo eq "active"){
265     %shop_data =  ( mainDetail => { number   => $part->{partnumber},
266                                    },
267                     active => ($part->{partnumber} == 1 ? 0 : 1),
268                    );
269   }elsif($todo eq "all"){
270   # mapping to shopware still missing attributes,metatags
271     %shop_data =  (   name              => $part->{description},
272                       mainDetail        => { number   => $part->{partnumber},
273                                              inStock  => $part->{onhand},
274                                              prices   =>  [ {          from   => 1,
275                                                                        price  => $price,
276                                                             customerGroupKey  => 'EK',
277                                                             },
278                                                           ],
279                                              active   => $shop_part->active,
280                                              #attribute => { attr1  => $cvars->{CVARNAME}->{value}, } , #HowTo handle attributes
281                                        },
282                       supplier          => 'AR', # Is needed by shopware,
283                       descriptionLong   => $shop_part->{shop_description},
284                       active            => $shop_part->active,
285                       images            => [ @upload_img ],
286                       __options_images  => { replace => 1, },
287                       categories        => [ @cat ],
288                       description       => $shop_part->{shop_description},
289                       categories        => [ @cat ],
290                       tax               => $taxrate,
291                     )
292                   ;
293   }
294
295   my $dataString = SL::JSON::to_json(\%shop_data);
296   $dataString    = encode_utf8($dataString);
297
298   my $upload_content;
299   my $upload;
300   my ($import,$data,$data_json);
301   my $partnumber = $::form->escape($part->{partnumber});#shopware don't accept / in articlenumber
302   # 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
303   # LWP->post = create LWP->put = update
304     $data       = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
305     $data_json  = $data->content;
306     $import     = SL::JSON::decode_json($data_json);
307   if($import->{success}){
308     #update
309     my $partnumber  = $::form->escape($part->{partnumber});#shopware don't accept / in articlenumber
310     $upload         = $self->connector->put($url . "api/articles/$partnumber?useNumberAsId=true", Content => $dataString);
311     my $data_json   = $upload->content;
312     $upload_content = SL::JSON::decode_json($data_json);
313   }else{
314     #upload
315     $upload         = $self->connector->post($url . "api/articles/", Content => $dataString);
316     my $data_json   = $upload->content;
317     $upload_content = SL::JSON::decode_json($data_json);
318   }
319   # don't know if this is needed
320   if(@upload_img) {
321     my $partnumber = $::form->escape($part->{partnumber});#shopware don't accept / in articlenumber
322     my $imgup      = $self->connector->put($url . "api/generatearticleimages/$partnumber?useNumberAsId=true");
323   }
324
325   return $upload_content->{success};
326 }
327
328 sub get_article {
329   my ($self,$partnumber) = @_;
330
331   my $url       = $self->url;
332   $partnumber   = $::form->escape($partnumber);#shopware don't accept / in articlenumber
333   my $data      = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
334   my $data_json = $data->content;
335   return SL::JSON::decode_json($data_json);
336 }
337
338 sub init_url {
339   my ($self) = @_;
340   $self->url($self->config->protocol . "://" . $self->config->server . ":" . $self->config->port . $self->config->path);
341 }
342
343 sub init_connector {
344   my ($self) = @_;
345   my $ua = LWP::UserAgent->new;
346   $ua->credentials(
347       $self->config->server . ":" . $self->config->port,
348       $self->config->realm,
349       $self->config->login => $self->config->password
350   );
351
352   return $ua;
353
354 }
355
356 1;
357
358 __END__
359
360 =encoding utf-8
361
362 =head1 NAME
363
364 SL::Shopconnecter::Shopware - connector for shopware 5
365
366 =head1 SYNOPSIS
367
368
369 =head1 DESCRIPTION
370
371 This is the connector to shopware.
372 In this file you can do the mapping to your needs.
373 see https://developers.shopware.com/developers-guide/rest-api/
374 for more information.
375
376 =head1 METHODS
377
378 =over 4
379
380 =item C<get_new_orders>
381
382 =item C<import_data_to_shop_order>
383
384 Creates on shoporder object from json
385 Here is the mapping for the positions.
386 see https://developers.shopware.com/developers-guide/rest-api/
387 for detailed information
388
389 =item C<map_data_to_shoporder>
390
391 Here is the mapping for the order data.
392 see https://developers.shopware.com/developers-guide/rest-api/
393 for detailed information
394
395 =item C<get_categories>
396
397 =item C<get_version>
398
399 Use this for test Connection
400 see SL::Shop
401
402 =item C<update_part>
403
404 Here is the mapping for the article data.
405 see https://developers.shopware.com/developers-guide/rest-api/
406 for detailed information
407
408 =item C<get_article>
409
410 =back
411
412 =head1 INITS
413
414 =over 4
415
416 =item init_url
417
418 build an url for LWP
419
420 =item init_connector
421
422 =back
423
424 =head1 TODO
425
426 Pricesrules, pricessources aren't fully implemented yet.
427 Payments aren't implemented( need to map payments from Shopware like invoice, paypal etc. to payments in kivitendo)
428
429 =head1 BUGS
430
431 None yet. :)
432
433 =head1 AUTHOR
434
435 W. Hahn E<lt>wh@futureworldsearch.netE<gt>
436
437 =cut