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 if ( $self->config->shipping_costs_parts_id ) {
155 my $shipping_part = SL::DB::Part->find_by( id => $self->config->shipping_costs_parts_id);
156 my %shipping_pos = ( description => $import->{data}->{dispatch}->{name},
157 partnumber => $shipping_part->partnumber,
158 price => $import->{data}->{invoiceShipping},
160 position => $position,
162 shop_order_id => $id,
164 my $shipping_pos_insert = SL::DB::ShopOrderItem->new(%shipping_pos);
165 $shipping_pos_insert->save;
168 my $customer = $shop_order->get_customer;
171 $shop_order->kivi_customer_id($customer->id);
176 sub map_data_to_shoporder {
177 my ($self, $import) = @_;
179 my $parser = DateTime::Format::Strptime->new( pattern => '%Y-%m-%dT%H:%M:%S',
183 my $orderdate = $parser->parse_datetime($import->{data}->{orderTime});
185 my $shop_id = $self->config->id;
186 my $tax_included = $self->config->pricetype;
188 # Mapping to table shoporders. See http://community.shopware.com/_detail_1690.html#GET_.28Liste.29
190 amount => $import->{data}->{invoiceAmount},
191 billing_city => $import->{data}->{billing}->{city},
192 billing_company => $import->{data}->{billing}->{company},
193 billing_country => $import->{data}->{billing}->{country}->{name},
194 billing_department => $import->{data}->{billing}->{department},
195 billing_email => $import->{data}->{customer}->{email},
196 billing_fax => $import->{data}->{billing}->{fax},
197 billing_firstname => $import->{data}->{billing}->{firstName},
198 #billing_greeting => ($import->{data}->{billing}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
199 billing_lastname => $import->{data}->{billing}->{lastName},
200 billing_phone => $import->{data}->{billing}->{phone},
201 billing_street => $import->{data}->{billing}->{street},
202 billing_vat => $import->{data}->{billing}->{vatId},
203 billing_zipcode => $import->{data}->{billing}->{zipCode},
204 customer_city => $import->{data}->{billing}->{city},
205 customer_company => $import->{data}->{billing}->{company},
206 customer_country => $import->{data}->{billing}->{country}->{name},
207 customer_department => $import->{data}->{billing}->{department},
208 customer_email => $import->{data}->{customer}->{email},
209 customer_fax => $import->{data}->{billing}->{fax},
210 customer_firstname => $import->{data}->{billing}->{firstName},
211 #customer_greeting => ($import->{data}->{billing}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
212 customer_lastname => $import->{data}->{billing}->{lastName},
213 customer_phone => $import->{data}->{billing}->{phone},
214 customer_street => $import->{data}->{billing}->{street},
215 customer_vat => $import->{data}->{billing}->{vatId},
216 customer_zipcode => $import->{data}->{billing}->{zipCode},
217 customer_newsletter => $import->{data}->{customer}->{newsletter},
218 delivery_city => $import->{data}->{shipping}->{city},
219 delivery_company => $import->{data}->{shipping}->{company},
220 delivery_country => $import->{data}->{shipping}->{country}->{name},
221 delivery_department => $import->{data}->{shipping}->{department},
222 delivery_email => "",
223 delivery_fax => $import->{data}->{shipping}->{fax},
224 delivery_firstname => $import->{data}->{shipping}->{firstName},
225 #delivery_greeting => ($import->{data}->{shipping}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
226 delivery_lastname => $import->{data}->{shipping}->{lastName},
227 delivery_phone => $import->{data}->{shipping}->{phone},
228 delivery_street => $import->{data}->{shipping}->{street},
229 delivery_vat => $import->{data}->{shipping}->{vatId},
230 delivery_zipcode => $import->{data}->{shipping}->{zipCode},
231 host => $import->{data}->{shop}->{hosts},
232 netamount => $import->{data}->{invoiceAmountNet},
233 order_date => $orderdate,
234 payment_description => $import->{data}->{payment}->{description},
235 payment_id => $import->{data}->{paymentId},
236 remote_ip => $import->{data}->{remoteAddress},
237 sepa_account_holder => $import->{data}->{paymentIntances}->{accountHolder},
238 sepa_bic => $import->{data}->{paymentIntances}->{bic},
239 sepa_iban => $import->{data}->{paymentIntances}->{iban},
240 shipping_costs => $import->{data}->{invoiceShipping},
241 shipping_costs_net => $import->{data}->{invoiceShippingNet},
242 shop_c_billing_id => $import->{data}->{billing}->{customerId},
243 shop_c_billing_number => $import->{data}->{billing}->{number},
244 shop_c_delivery_id => $import->{data}->{shipping}->{id},
245 shop_customer_id => $import->{data}->{customerId},
246 shop_customer_number => $import->{data}->{billing}->{number},
247 shop_customer_comment => $import->{data}->{customerComment},
249 shop_ordernumber => $import->{data}->{number},
250 shop_trans_id => $import->{data}->{id},
251 tax_included => $tax_included eq "brutto" ? 1 : 0,
254 my $shop_order = SL::DB::ShopOrder->new(%columns);
261 my $url = $self->url;
262 my $data = $self->connector->get($url . "api/categories");
263 my $data_json = $data->content;
264 my $import = SL::JSON::decode_json($data_json);
265 my @daten = @{$import->{data}};
266 my %categories = map { ($_->{id} => $_) } @daten;
269 my $parent = $categories{$_->{parentId}};
270 $parent->{children} ||= [];
271 push @{$parent->{children}},$_;
280 my $url = $self->url;
281 my $data = $self->connector->get($url . "api/version");
282 my $type = $data->content_type;
283 my $status_line = $data->status_line;
285 if($data->is_success && $type eq 'application/json'){
286 my $data_json = $data->content;
287 return SL::JSON::decode_json($data_json);
289 my %return = ( success => 0,
290 data => { version => $url . ": " . $status_line, revision => $type },
291 message => "Server not found or wrong data type",
298 my ($self, $shop_part, $todo) = @_;
300 #shop_part is passed as a param
301 die unless ref($shop_part) eq 'SL::DB::ShopPart';
303 my $url = $self->url;
304 my $part = SL::DB::Part->new(id => $shop_part->part_id)->load;
307 my $cvars = { map { ($_->config->name => { value => $_->value_as_text, is_valid => $_->is_valid }) } @{ $part->cvars_by_config } };
310 foreach my $row_cat ( @{ $shop_part->shop_category } ) {
311 my $temp = { ( id => @{$row_cat}[0], ) };
312 push ( @cat, $temp );
315 my @upload_img = $shop_part->get_images;
316 my $tax_n_price = $shop_part->get_tax_and_price;
317 my $price = $tax_n_price->{price};
318 my $taxrate = $tax_n_price->{tax};
319 # mapping to shopware still missing attributes,metatags
322 if($todo eq "price"){
323 %shop_data = ( mainDetail => { number => $part->partnumber,
324 prices => [ { from => 1,
326 customerGroupKey => 'EK',
331 }elsif($todo eq "stock"){
332 %shop_data = ( mainDetail => { number => $part->partnumber,
333 inStock => $part->onhand,
336 }elsif($todo eq "price_stock"){
337 %shop_data = ( mainDetail => { number => $part->partnumber,
338 inStock => $part->onhand,
339 prices => [ { from => 1,
341 customerGroupKey => 'EK',
346 }elsif($todo eq "active"){
347 %shop_data = ( mainDetail => { number => $part->partnumber,
349 active => ($part->partnumber == 1 ? 0 : 1),
351 }elsif($todo eq "all"){
352 # mapping to shopware still missing attributes,metatags
353 %shop_data = ( name => $part->description,
354 mainDetail => { number => $part->partnumber,
355 inStock => $part->onhand,
356 prices => [ { from => 1,
358 customerGroupKey => 'EK',
361 active => $shop_part->active,
362 #attribute => { attr1 => $cvars->{CVARNAME}->{value}, } , #HowTo handle attributes
364 supplier => 'AR', # Is needed by shopware,
365 descriptionLong => $shop_part->shop_description,
366 active => $shop_part->active,
367 images => [ @upload_img ],
368 __options_images => { replace => 1, },
369 categories => [ @cat ],
370 description => $shop_part->shop_description,
371 categories => [ @cat ],
377 my $dataString = SL::JSON::to_json(\%shop_data);
378 $dataString = encode_utf8($dataString);
382 my ($import,$data,$data_json);
383 my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
384 # 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
385 # LWP->post = create LWP->put = update
386 $data = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
387 $data_json = $data->content;
388 $import = SL::JSON::decode_json($data_json);
389 if($import->{success}){
391 my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
392 $upload = $self->connector->put($url . "api/articles/$partnumber?useNumberAsId=true", Content => $dataString);
393 my $data_json = $upload->content;
394 $upload_content = SL::JSON::decode_json($data_json);
397 $upload = $self->connector->post($url . "api/articles/", Content => $dataString);
398 my $data_json = $upload->content;
399 $upload_content = SL::JSON::decode_json($data_json);
401 # don't know if this is needed
403 my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
404 my $imgup = $self->connector->put($url . "api/generatearticleimages/$partnumber?useNumberAsId=true");
407 return $upload_content->{success};
411 my ($self,$partnumber) = @_;
413 my $url = $self->url;
414 $partnumber = $::form->escape($partnumber);#shopware don't accept / in articlenumber
415 my $data = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
416 my $data_json = $data->content;
417 return SL::JSON::decode_json($data_json);
420 sub set_orderstatus {
421 my ($self,$order_id, $status) = @_;
422 if ($status eq "fetched") { $status = 1; }
423 if ($status eq "completed") { $status = 2; }
424 my %new_status = (orderStatusId => $status);
425 my $new_status_json = SL::JSON::to_json(\%new_status);
426 $self->connector->put($self->url . "api/orders/$order_id", Content => $new_status_json);
431 $self->url($self->config->protocol . "://" . $self->config->server . ":" . $self->config->port . $self->config->path);
436 my $ua = LWP::UserAgent->new;
438 $self->config->server . ":" . $self->config->port,
439 $self->config->realm,
440 $self->config->login => $self->config->password
455 SL::Shopconnecter::Shopware - connector for shopware 5
462 This is the connector to shopware.
463 In this file you can do the mapping to your needs.
464 see https://developers.shopware.com/developers-guide/rest-api/
465 for more information.
471 =item C<get_one_order>
473 Fetches one order specified by ordnumber
475 =item C<get_new_orders>
477 Fetches new order by parameters from shop configuration
479 =item C<import_data_to_shop_order>
481 Creates on shoporder object from json
482 Here is the mapping for the positions.
483 see https://developers.shopware.com/developers-guide/rest-api/
484 for detailed information
486 =item C<map_data_to_shoporder>
488 Here is the mapping for the order data.
489 see https://developers.shopware.com/developers-guide/rest-api/
490 for detailed information
492 =item C<get_categories>
496 Use this for test Connection
501 Here is the mapping for the article data.
502 see https://developers.shopware.com/developers-guide/rest-api/
503 for detailed information
523 Pricesrules, pricessources aren't fully implemented yet.
524 Payments aren't implemented( need to map payments from Shopware like invoice, paypal etc. to payments in kivitendo)
532 W. Hahn E<lt>wh@futureworldsearch.netE<gt>