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));
51 flash_later('error', $::locale->text('Database errors: #1', @errors));
53 %fetched_orders = (shop_description => $self->config->description, number_of_orders => $of);
56 shop_id => $self->config->id,
57 shop_description => $self->config->description,
58 message => "Error: $data->status_line",
61 %fetched_orders = %error_msg;
64 return \%fetched_orders;
71 my $last_order_number = $self->config->last_order_number;
72 my $otf = $self->config->orders_to_fetch;
74 my $last_data = $self->connector->get($url . "api orders/$last_order_number?useNumberAsId=true");
75 my $last_data_json = $last_data->content;
76 my $last_import = SL::JSON::decode_json($last_data_json);
78 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});
80 my $dbh = SL::DB->client;
83 if ($orders_data->is_success && $orders_data->content_type eq 'application/json'){
84 my $orders_data_json = $orders_data->content;
85 my $orders_import = SL::JSON::decode_json($orders_data_json);
86 foreach my $shoporder(@{ $orders_import->{data} }){
88 my $data = $self->connector->get($url . "api/orders/" . $shoporder->{id});
89 my $data_json = $data->content;
90 my $import = SL::JSON::decode_json($data_json);
92 $dbh->with_transaction( sub{
93 $self->import_data_to_shop_order($import);
95 $self->config->assign_attributes( last_order_number => $shoporder->{number});
99 push @errors,($::locale->text('Saving failed. Error message from the database: #1', $dbh->error));
105 flash_later('error', $::locale->text('Database errors: #1', @errors));
108 %fetched_orders = (shop_description => $self->config->description, number_of_orders => $of);
111 shop_id => $self->config->id,
112 shop_description => $self->config->description,
113 message => "Error: $orders_data->status_line",
116 %fetched_orders = %error_msg;
119 return \%fetched_orders;
122 sub import_data_to_shop_order {
123 my ( $self, $import ) = @_;
124 my $shop_order = $self->map_data_to_shoporder($import);
127 my $id = $shop_order->id;
129 my @positions = sort { Sort::Naturally::ncmp($a->{"articleNumber"}, $b->{"articleNumber"}) } @{ $import->{data}->{details} };
130 #my @positions = sort { Sort::Naturally::ncmp($a->{"partnumber"}, $b->{"partnumber"}) } @{ $import->{data}->{details} };
132 my $active_price_source = $self->config->price_source;
134 foreach my $pos(@positions) {
135 my $price = $::form->round_amount($pos->{price},2);
136 my %pos_columns = ( description => $pos->{articleName},
137 partnumber => $pos->{articleNumber},
139 quantity => $pos->{quantity},
140 position => $position,
141 tax_rate => $pos->{taxRate},
142 shop_trans_id => $pos->{articleId},
143 shop_order_id => $id,
144 active_price_source => $active_price_source,
146 my $pos_insert = SL::DB::ShopOrderItem->new(%pos_columns);
150 $shop_order->positions($position-1);
152 my $customer = $shop_order->get_customer;
155 $shop_order->kivi_customer_id($customer->id);
160 sub map_data_to_shoporder {
161 my ($self, $import) = @_;
163 my $parser = DateTime::Format::Strptime->new( pattern => '%Y-%m-%dT%H:%M:%S',
167 my $orderdate = $parser->parse_datetime($import->{data}->{orderTime});
169 my $shop_id = $self->config->id;
170 my $tax_included = $self->config->pricetype;
172 # Mapping to table shoporders. See http://community.shopware.com/_detail_1690.html#GET_.28Liste.29
174 amount => $import->{data}->{invoiceAmount},
175 billing_city => $import->{data}->{billing}->{city},
176 billing_company => $import->{data}->{billing}->{company},
177 billing_country => $import->{data}->{billing}->{country}->{name},
178 billing_department => $import->{data}->{billing}->{department},
179 billing_email => $import->{data}->{customer}->{email},
180 billing_fax => $import->{data}->{billing}->{fax},
181 billing_firstname => $import->{data}->{billing}->{firstName},
182 #billing_greeting => ($import->{data}->{billing}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
183 billing_lastname => $import->{data}->{billing}->{lastName},
184 billing_phone => $import->{data}->{billing}->{phone},
185 billing_street => $import->{data}->{billing}->{street},
186 billing_vat => $import->{data}->{billing}->{vatId},
187 billing_zipcode => $import->{data}->{billing}->{zipCode},
188 customer_city => $import->{data}->{billing}->{city},
189 customer_company => $import->{data}->{billing}->{company},
190 customer_country => $import->{data}->{billing}->{country}->{name},
191 customer_department => $import->{data}->{billing}->{department},
192 customer_email => $import->{data}->{customer}->{email},
193 customer_fax => $import->{data}->{billing}->{fax},
194 customer_firstname => $import->{data}->{billing}->{firstName},
195 #customer_greeting => ($import->{data}->{billing}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
196 customer_lastname => $import->{data}->{billing}->{lastName},
197 customer_phone => $import->{data}->{billing}->{phone},
198 customer_street => $import->{data}->{billing}->{street},
199 customer_vat => $import->{data}->{billing}->{vatId},
200 customer_zipcode => $import->{data}->{billing}->{zipCode},
201 customer_newsletter => $import->{data}->{customer}->{newsletter},
202 delivery_city => $import->{data}->{shipping}->{city},
203 delivery_company => $import->{data}->{shipping}->{company},
204 delivery_country => $import->{data}->{shipping}->{country}->{name},
205 delivery_department => $import->{data}->{shipping}->{department},
206 delivery_email => "",
207 delivery_fax => $import->{data}->{shipping}->{fax},
208 delivery_firstname => $import->{data}->{shipping}->{firstName},
209 #delivery_greeting => ($import->{data}->{shipping}->{salutation} eq 'mr' ? 'Herr' : 'Frau'),
210 delivery_lastname => $import->{data}->{shipping}->{lastName},
211 delivery_phone => $import->{data}->{shipping}->{phone},
212 delivery_street => $import->{data}->{shipping}->{street},
213 delivery_vat => $import->{data}->{shipping}->{vatId},
214 delivery_zipcode => $import->{data}->{shipping}->{zipCode},
215 host => $import->{data}->{shop}->{hosts},
216 netamount => $import->{data}->{invoiceAmountNet},
217 order_date => $orderdate,
218 payment_description => $import->{data}->{payment}->{description},
219 payment_id => $import->{data}->{paymentId},
220 remote_ip => $import->{data}->{remoteAddress},
221 sepa_account_holder => $import->{data}->{paymentIntances}->{accountHolder},
222 sepa_bic => $import->{data}->{paymentIntances}->{bic},
223 sepa_iban => $import->{data}->{paymentIntances}->{iban},
224 shipping_costs => $import->{data}->{invoiceShipping},
225 shipping_costs_net => $import->{data}->{invoiceShippingNet},
226 shop_c_billing_id => $import->{data}->{billing}->{customerId},
227 shop_c_billing_number => $import->{data}->{billing}->{number},
228 shop_c_delivery_id => $import->{data}->{shipping}->{id},
229 shop_customer_id => $import->{data}->{customerId},
230 shop_customer_number => $import->{data}->{billing}->{number},
231 shop_customer_comment => $import->{data}->{customerComment},
233 shop_ordernumber => $import->{data}->{number},
234 shop_trans_id => $import->{data}->{id},
235 tax_included => $tax_included eq "brutto" ? 1 : 0,
238 my $shop_order = SL::DB::ShopOrder->new(%columns);
245 my $url = $self->url;
246 my $data = $self->connector->get($url . "api/categories");
247 my $data_json = $data->content;
248 my $import = SL::JSON::decode_json($data_json);
249 my @daten = @{$import->{data}};
250 my %categories = map { ($_->{id} => $_) } @daten;
253 my $parent = $categories{$_->{parentId}};
254 $parent->{children} ||= [];
255 push @{$parent->{children}},$_;
264 my $url = $self->url;
265 my $data = $self->connector->get($url . "api/version");
266 my $type = $data->content_type;
267 my $status_line = $data->status_line;
269 if($data->is_success && $type eq 'application/json'){
270 my $data_json = $data->content;
271 return SL::JSON::decode_json($data_json);
273 my %return = ( success => 0,
274 data => { version => $url . ": " . $status_line, revision => $type },
275 message => "Server not found or wrong data type",
282 my ($self, $shop_part, $todo) = @_;
284 #shop_part is passed as a param
285 die unless ref($shop_part) eq 'SL::DB::ShopPart';
287 my $url = $self->url;
288 my $part = SL::DB::Part->new(id => $shop_part->part_id)->load;
291 my $cvars = { map { ($_->config->name => { value => $_->value_as_text, is_valid => $_->is_valid }) } @{ $part->cvars_by_config } };
294 foreach my $row_cat ( @{ $shop_part->shop_category } ) {
295 my $temp = { ( id => @{$row_cat}[0], ) };
296 push ( @cat, $temp );
299 my @upload_img = $shop_part->get_images;
300 my $tax_n_price = $shop_part->get_tax_and_price;
301 my $price = $tax_n_price->{price};
302 my $taxrate = $tax_n_price->{tax};
303 # mapping to shopware still missing attributes,metatags
306 if($todo eq "price"){
307 %shop_data = ( mainDetail => { number => $part->partnumber,
308 prices => [ { from => 1,
310 customerGroupKey => 'EK',
315 }elsif($todo eq "stock"){
316 %shop_data = ( mainDetail => { number => $part->partnumber,
317 inStock => $part->onhand,
320 }elsif($todo eq "price_stock"){
321 %shop_data = ( mainDetail => { number => $part->partnumber,
322 inStock => $part->onhand,
323 prices => [ { from => 1,
325 customerGroupKey => 'EK',
330 }elsif($todo eq "active"){
331 %shop_data = ( mainDetail => { number => $part->partnumber,
333 active => ($part->partnumber == 1 ? 0 : 1),
335 }elsif($todo eq "all"){
336 # mapping to shopware still missing attributes,metatags
337 %shop_data = ( name => $part->description,
338 mainDetail => { number => $part->partnumber,
339 inStock => $part->onhand,
340 prices => [ { from => 1,
342 customerGroupKey => 'EK',
345 active => $shop_part->active,
346 #attribute => { attr1 => $cvars->{CVARNAME}->{value}, } , #HowTo handle attributes
348 supplier => 'AR', # Is needed by shopware,
349 descriptionLong => $shop_part->shop_description,
350 active => $shop_part->active,
351 images => [ @upload_img ],
352 __options_images => { replace => 1, },
353 categories => [ @cat ],
354 description => $shop_part->shop_description,
355 categories => [ @cat ],
361 my $dataString = SL::JSON::to_json(\%shop_data);
362 $dataString = encode_utf8($dataString);
366 my ($import,$data,$data_json);
367 my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
368 # 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
369 # LWP->post = create LWP->put = update
370 $data = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
371 $data_json = $data->content;
372 $import = SL::JSON::decode_json($data_json);
373 if($import->{success}){
375 my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
376 $upload = $self->connector->put($url . "api/articles/$partnumber?useNumberAsId=true", Content => $dataString);
377 my $data_json = $upload->content;
378 $upload_content = SL::JSON::decode_json($data_json);
381 $upload = $self->connector->post($url . "api/articles/", Content => $dataString);
382 my $data_json = $upload->content;
383 $upload_content = SL::JSON::decode_json($data_json);
385 # don't know if this is needed
387 my $partnumber = $::form->escape($part->partnumber);#shopware don't accept / in articlenumber
388 my $imgup = $self->connector->put($url . "api/generatearticleimages/$partnumber?useNumberAsId=true");
391 return $upload_content->{success};
395 my ($self,$partnumber) = @_;
397 my $url = $self->url;
398 $partnumber = $::form->escape($partnumber);#shopware don't accept / in articlenumber
399 my $data = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
400 my $data_json = $data->content;
401 return SL::JSON::decode_json($data_json);
406 $self->url($self->config->protocol . "://" . $self->config->server . ":" . $self->config->port . $self->config->path);
411 my $ua = LWP::UserAgent->new;
413 $self->config->server . ":" . $self->config->port,
414 $self->config->realm,
415 $self->config->login => $self->config->password
430 SL::Shopconnecter::Shopware - connector for shopware 5
437 This is the connector to shopware.
438 In this file you can do the mapping to your needs.
439 see https://developers.shopware.com/developers-guide/rest-api/
440 for more information.
446 =item C<get_one_order>
448 Fetches one order specified by ordnumber
450 =item C<get_new_orders>
452 Fetches new order by parameters from shop configuration
454 =item C<import_data_to_shop_order>
456 Creates on shoporder object from json
457 Here is the mapping for the positions.
458 see https://developers.shopware.com/developers-guide/rest-api/
459 for detailed information
461 =item C<map_data_to_shoporder>
463 Here is the mapping for the order data.
464 see https://developers.shopware.com/developers-guide/rest-api/
465 for detailed information
467 =item C<get_categories>
471 Use this for test Connection
476 Here is the mapping for the article data.
477 see https://developers.shopware.com/developers-guide/rest-api/
478 for detailed information
498 Pricesrules, pricessources aren't fully implemented yet.
499 Payments aren't implemented( need to map payments from Shopware like invoice, paypal etc. to payments in kivitendo)
507 W. Hahn E<lt>wh@futureworldsearch.netE<gt>