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_id = SL::DB::Manager::PaymentTerm->get_first()->id || undef;
194 # Mapping to table shoporders. See http://community.shopware.com/_detail_1690.html#GET_.28Liste.29
196 amount => $import->{data}->{invoiceAmount},
197 billing_city => $import->{data}->{billing}->{city},
198 billing_company => $import->{data}->{billing}->{company},
199 billing_country => $import->{data}->{billing}->{country}->{name},
200 billing_department => $import->{data}->{billing}->{department},
201 billing_email => $import->{data}->{customer}->{email},
202 billing_fax => $import->{data}->{billing}->{fax},
203 billing_firstname => $import->{data}->{billing}->{firstName},
204 #billing_greeting => ($import->{data}->{billing}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
205 billing_lastname => $import->{data}->{billing}->{lastName},
206 billing_phone => $import->{data}->{billing}->{phone},
207 billing_street => $import->{data}->{billing}->{street},
208 billing_vat => $import->{data}->{billing}->{vatId},
209 billing_zipcode => $import->{data}->{billing}->{zipCode},
210 customer_city => $import->{data}->{billing}->{city},
211 customer_company => $import->{data}->{billing}->{company},
212 customer_country => $import->{data}->{billing}->{country}->{name},
213 customer_department => $import->{data}->{billing}->{department},
214 customer_email => $import->{data}->{customer}->{email},
215 customer_fax => $import->{data}->{billing}->{fax},
216 customer_firstname => $import->{data}->{billing}->{firstName},
217 #customer_greeting => ($import->{data}->{billing}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
218 customer_lastname => $import->{data}->{billing}->{lastName},
219 customer_phone => $import->{data}->{billing}->{phone},
220 customer_street => $import->{data}->{billing}->{street},
221 customer_vat => $import->{data}->{billing}->{vatId},
222 customer_zipcode => $import->{data}->{billing}->{zipCode},
223 customer_newsletter => $import->{data}->{customer}->{newsletter},
224 delivery_city => $import->{data}->{shipping}->{city},
225 delivery_company => $import->{data}->{shipping}->{company},
226 delivery_country => $import->{data}->{shipping}->{country}->{name},
227 delivery_department => $import->{data}->{shipping}->{department},
228 delivery_email => "",
229 delivery_fax => $import->{data}->{shipping}->{fax},
230 delivery_firstname => $import->{data}->{shipping}->{firstName},
231 #delivery_greeting => ($import->{data}->{shipping}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
232 delivery_lastname => $import->{data}->{shipping}->{lastName},
233 delivery_phone => $import->{data}->{shipping}->{phone},
234 delivery_street => $import->{data}->{shipping}->{street},
235 delivery_vat => $import->{data}->{shipping}->{vatId},
236 delivery_zipcode => $import->{data}->{shipping}->{zipCode},
237 host => $import->{data}->{shop}->{hosts},
238 netamount => $import->{data}->{invoiceAmountNet},
239 order_date => $orderdate,
240 payment_description => $import->{data}->{payment}->{description},
241 payment_id => $payment_ids_methods{$import->{data}->{paymentId}} || $default_payment_id,
242 remote_ip => $import->{data}->{remoteAddress},
243 sepa_account_holder => $import->{data}->{paymentIntances}->{accountHolder},
244 sepa_bic => $import->{data}->{paymentIntances}->{bic},
245 sepa_iban => $import->{data}->{paymentIntances}->{iban},
246 shipping_costs => $import->{data}->{invoiceShipping},
247 shipping_costs_net => $import->{data}->{invoiceShippingNet},
248 shop_c_billing_id => $import->{data}->{billing}->{customerId},
249 shop_c_billing_number => $import->{data}->{billing}->{number},
250 shop_c_delivery_id => $import->{data}->{shipping}->{id},
251 shop_customer_id => $import->{data}->{customerId},
252 shop_customer_number => $import->{data}->{billing}->{number},
253 shop_customer_comment => $import->{data}->{customerComment},
255 shop_ordernumber => $import->{data}->{number},
256 shop_trans_id => $import->{data}->{id},
257 tax_included => $tax_included eq "brutto" ? 1 : 0,
260 my $shop_order = SL::DB::ShopOrder->new(%columns);
267 my $url = $self->url;
268 my $data = $self->connector->get($url . "api/categories");
269 my $data_json = $data->content;
270 my $import = SL::JSON::decode_json($data_json);
271 my @daten = @{$import->{data}};
272 my %categories = map { ($_->{id} => $_) } @daten;
276 # ignore root with id=1
280 my $parent = $categories{$_->{parentId}};
281 if($parent && $parent->{id} != 1) {
282 $parent->{children} ||= [];
283 push @{$parent->{children}},$_;
285 push @categories_tree, $_;
289 return \@categories_tree;
295 my $url = $self->url;
296 my $data = $self->connector->get($url . "api/version");
297 my $type = $data->content_type;
298 my $status_line = $data->status_line;
300 if($data->is_success && $type eq 'application/json'){
301 my $data_json = $data->content;
302 return SL::JSON::decode_json($data_json);
304 my %return = ( success => 0,
305 data => { version => $url . ": " . $status_line, revision => $type },
306 message => "Server not found or wrong data type",
313 my ($self, $shop_part, $todo) = @_;
315 #shop_part is passed as a param
316 die unless ref($shop_part) eq 'SL::DB::ShopPart';
318 my $url = $self->url;
319 my $part = SL::DB::Part->new(id => $shop_part->part_id)->load;
322 my $cvars = { map { ($_->config->name => { value => $_->value_as_text, is_valid => $_->is_valid }) } @{ $part->cvars_by_config } };
325 foreach my $row_cat ( @{ $shop_part->shop_category } ) {
326 my $temp = { ( id => @{$row_cat}[0], ) };
327 push ( @cat, $temp );
330 my @upload_img = $shop_part->get_images;
331 my $tax_n_price = $shop_part->get_tax_and_price;
332 my $price = $tax_n_price->{price};
333 my $taxrate = $tax_n_price->{tax};
334 # mapping to shopware still missing attributes,metatags
337 if($todo eq "price"){
338 %shop_data = ( mainDetail => { number => $part->partnumber,
339 prices => [ { from => 1,
341 customerGroupKey => 'EK',
346 }elsif($todo eq "stock"){
347 %shop_data = ( mainDetail => { number => $part->partnumber,
348 inStock => $part->onhand,
351 }elsif($todo eq "price_stock"){
352 %shop_data = ( mainDetail => { number => $part->partnumber,
353 inStock => $part->onhand,
354 prices => [ { from => 1,
356 customerGroupKey => 'EK',
361 }elsif($todo eq "active"){
362 %shop_data = ( mainDetail => { number => $part->partnumber,
364 active => ($part->partnumber == 1 ? 0 : 1),
366 }elsif($todo eq "all"){
367 # mapping to shopware still missing attributes,metatags
368 %shop_data = ( name => $part->description,
369 mainDetail => { number => $part->partnumber,
370 inStock => $part->onhand,
371 prices => [ { from => 1,
373 customerGroupKey => 'EK',
376 active => $shop_part->active,
377 #attribute => { attr1 => $cvars->{CVARNAME}->{value}, } , #HowTo handle attributes
379 supplier => 'AR', # Is needed by shopware,
380 descriptionLong => $shop_part->shop_description,
381 active => $shop_part->active,
382 images => [ @upload_img ],
383 __options_images => { replace => 1, },
384 categories => [ @cat ],
385 description => $shop_part->shop_description,
386 categories => [ @cat ],
392 my $dataString = SL::JSON::to_json(\%shop_data);
393 $dataString = encode_utf8($dataString);
397 my ($import,$data,$data_json);
398 my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
399 # 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
400 # LWP->post = create LWP->put = update
401 $data = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
402 $data_json = $data->content;
403 $import = SL::JSON::decode_json($data_json);
404 if($import->{success}){
406 my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
407 $upload = $self->connector->put($url . "api/articles/$partnumber?useNumberAsId=true", Content => $dataString);
408 my $data_json = $upload->content;
409 $upload_content = SL::JSON::decode_json($data_json);
412 $upload = $self->connector->post($url . "api/articles/", Content => $dataString);
413 my $data_json = $upload->content;
414 $upload_content = SL::JSON::decode_json($data_json);
416 # don't know if this is needed
418 my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
419 my $imgup = $self->connector->put($url . "api/generatearticleimages/$partnumber?useNumberAsId=true");
422 return $upload_content->{success};
426 my ($self,$partnumber) = @_;
428 my $url = $self->url;
429 $partnumber = $::form->escape($partnumber);#shopware don't accept / in articlenumber
430 my $data = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
431 my $data_json = $data->content;
432 return SL::JSON::decode_json($data_json);
435 sub set_orderstatus {
436 my ($self,$order_id, $status) = @_;
437 if ($status eq "fetched") { $status = 1; }
438 if ($status eq "completed") { $status = 2; }
439 my %new_status = (orderStatusId => $status);
440 my $new_status_json = SL::JSON::to_json(\%new_status);
441 $self->connector->put($self->url . "api/orders/$order_id", Content => $new_status_json);
446 $self->url($self->config->protocol . "://" . $self->config->server . ":" . $self->config->port . $self->config->path);
451 my $ua = LWP::UserAgent->new;
453 $self->config->server . ":" . $self->config->port,
454 $self->config->realm,
455 $self->config->login => $self->config->password
470 SL::Shopconnecter::Shopware - connector for shopware 5
477 This is the connector to shopware.
478 In this file you can do the mapping to your needs.
479 see https://developers.shopware.com/developers-guide/rest-api/
480 for more information.
486 =item C<get_one_order>
488 Fetches one order specified by ordnumber
490 =item C<get_new_orders>
492 Fetches new order by parameters from shop configuration
494 =item C<import_data_to_shop_order>
496 Creates on shoporder object from json
497 Here is the mapping for the positions.
498 see https://developers.shopware.com/developers-guide/rest-api/
499 for detailed information
501 =item C<map_data_to_shoporder>
503 Here is the mapping for the order data.
504 see https://developers.shopware.com/developers-guide/rest-api/
505 for detailed information
507 =item C<get_categories>
511 Use this for test Connection
516 Here is the mapping for the article data.
517 see https://developers.shopware.com/developers-guide/rest-api/
518 for detailed information
538 Pricesrules, pricessources aren't fully implemented yet.
539 Payments aren't implemented( need to map payments from Shopware like invoice, paypal etc. to payments in kivitendo)
547 W. Hahn E<lt>wh@futureworldsearch.netE<gt>