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>