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;
270 # ignore root with id=1
274 my $parent = $categories{$_->{parentId}};
275 if($parent && $parent->{id} != 1) {
276 $parent->{children} ||= [];
277 push @{$parent->{children}},$_;
279 push @categories_tree, $_;
283 return \@categories_tree;
289 my $url = $self->url;
290 my $data = $self->connector->get($url . "api/version");
291 my $type = $data->content_type;
292 my $status_line = $data->status_line;
294 if($data->is_success && $type eq 'application/json'){
295 my $data_json = $data->content;
296 return SL::JSON::decode_json($data_json);
298 my %return = ( success => 0,
299 data => { version => $url . ": " . $status_line, revision => $type },
300 message => "Server not found or wrong data type",
307 my ($self, $shop_part, $todo) = @_;
309 #shop_part is passed as a param
310 die unless ref($shop_part) eq 'SL::DB::ShopPart';
312 my $url = $self->url;
313 my $part = SL::DB::Part->new(id => $shop_part->part_id)->load;
316 my $cvars = { map { ($_->config->name => { value => $_->value_as_text, is_valid => $_->is_valid }) } @{ $part->cvars_by_config } };
319 foreach my $row_cat ( @{ $shop_part->shop_category } ) {
320 my $temp = { ( id => @{$row_cat}[0], ) };
321 push ( @cat, $temp );
324 my @upload_img = $shop_part->get_images;
325 my $tax_n_price = $shop_part->get_tax_and_price;
326 my $price = $tax_n_price->{price};
327 my $taxrate = $tax_n_price->{tax};
328 # mapping to shopware still missing attributes,metatags
331 if($todo eq "price"){
332 %shop_data = ( mainDetail => { number => $part->partnumber,
333 prices => [ { from => 1,
335 customerGroupKey => 'EK',
340 }elsif($todo eq "stock"){
341 %shop_data = ( mainDetail => { number => $part->partnumber,
342 inStock => $part->onhand,
345 }elsif($todo eq "price_stock"){
346 %shop_data = ( mainDetail => { number => $part->partnumber,
347 inStock => $part->onhand,
348 prices => [ { from => 1,
350 customerGroupKey => 'EK',
355 }elsif($todo eq "active"){
356 %shop_data = ( mainDetail => { number => $part->partnumber,
358 active => ($part->partnumber == 1 ? 0 : 1),
360 }elsif($todo eq "all"){
361 # mapping to shopware still missing attributes,metatags
362 %shop_data = ( name => $part->description,
363 mainDetail => { number => $part->partnumber,
364 inStock => $part->onhand,
365 prices => [ { from => 1,
367 customerGroupKey => 'EK',
370 active => $shop_part->active,
371 #attribute => { attr1 => $cvars->{CVARNAME}->{value}, } , #HowTo handle attributes
373 supplier => 'AR', # Is needed by shopware,
374 descriptionLong => $shop_part->shop_description,
375 active => $shop_part->active,
376 images => [ @upload_img ],
377 __options_images => { replace => 1, },
378 categories => [ @cat ],
379 description => $shop_part->shop_description,
380 categories => [ @cat ],
386 my $dataString = SL::JSON::to_json(\%shop_data);
387 $dataString = encode_utf8($dataString);
391 my ($import,$data,$data_json);
392 my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
393 # 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
394 # LWP->post = create LWP->put = update
395 $data = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
396 $data_json = $data->content;
397 $import = SL::JSON::decode_json($data_json);
398 if($import->{success}){
400 my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
401 $upload = $self->connector->put($url . "api/articles/$partnumber?useNumberAsId=true", Content => $dataString);
402 my $data_json = $upload->content;
403 $upload_content = SL::JSON::decode_json($data_json);
406 $upload = $self->connector->post($url . "api/articles/", Content => $dataString);
407 my $data_json = $upload->content;
408 $upload_content = SL::JSON::decode_json($data_json);
410 # don't know if this is needed
412 my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
413 my $imgup = $self->connector->put($url . "api/generatearticleimages/$partnumber?useNumberAsId=true");
416 return $upload_content->{success};
420 my ($self,$partnumber) = @_;
422 my $url = $self->url;
423 $partnumber = $::form->escape($partnumber);#shopware don't accept / in articlenumber
424 my $data = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
425 my $data_json = $data->content;
426 return SL::JSON::decode_json($data_json);
429 sub set_orderstatus {
430 my ($self,$order_id, $status) = @_;
431 if ($status eq "fetched") { $status = 1; }
432 if ($status eq "completed") { $status = 2; }
433 my %new_status = (orderStatusId => $status);
434 my $new_status_json = SL::JSON::to_json(\%new_status);
435 $self->connector->put($self->url . "api/orders/$order_id", Content => $new_status_json);
440 $self->url($self->config->protocol . "://" . $self->config->server . ":" . $self->config->port . $self->config->path);
445 my $ua = LWP::UserAgent->new;
447 $self->config->server . ":" . $self->config->port,
448 $self->config->realm,
449 $self->config->login => $self->config->password
464 SL::Shopconnecter::Shopware - connector for shopware 5
471 This is the connector to shopware.
472 In this file you can do the mapping to your needs.
473 see https://developers.shopware.com/developers-guide/rest-api/
474 for more information.
480 =item C<get_one_order>
482 Fetches one order specified by ordnumber
484 =item C<get_new_orders>
486 Fetches new order by parameters from shop configuration
488 =item C<import_data_to_shop_order>
490 Creates on shoporder object from json
491 Here is the mapping for the positions.
492 see https://developers.shopware.com/developers-guide/rest-api/
493 for detailed information
495 =item C<map_data_to_shoporder>
497 Here is the mapping for the order data.
498 see https://developers.shopware.com/developers-guide/rest-api/
499 for detailed information
501 =item C<get_categories>
505 Use this for test Connection
510 Here is the mapping for the article data.
511 see https://developers.shopware.com/developers-guide/rest-api/
512 for detailed information
532 Pricesrules, pricessources aren't fully implemented yet.
533 Payments aren't implemented( need to map payments from Shopware like invoice, paypal etc. to payments in kivitendo)
541 W. Hahn E<lt>wh@futureworldsearch.netE<gt>