Revision ce1ca334
Von Werner Hahn vor mehr als 7 Jahren hinzugefügt
SL/BackgroundJob/ShopPartMassUpload.pm | ||
---|---|---|
1 | 1 |
package SL::BackgroundJob::ShopPartMassUpload; |
2 |
#ShopPartMassUpload |
|
2 |
|
|
3 | 3 |
use strict; |
4 | 4 |
use warnings; |
5 | 5 |
|
... | ... | |
7 | 7 |
|
8 | 8 |
use SL::DBUtils; |
9 | 9 |
use SL::DB::ShopPart; |
10 |
use SL::Shop;
|
|
10 |
use SL::Shop; |
|
11 | 11 |
|
12 | 12 |
use constant WAITING_FOR_EXECUTION => 0; |
13 | 13 |
use constant UPLOAD_TO_WEBSHOP => 1; |
... | ... | |
15 | 15 |
|
16 | 16 |
# Data format: |
17 | 17 |
# my $data = { |
18 |
# shop_part_record_ids => [ 603, 604, 605], |
|
19 |
# num_order_created => 0, |
|
20 |
# orders_ids => [1,2,3] |
|
21 |
# conversation_errors => [ { id => 603 , item => 2, message => "Out of stock"}, ], |
|
18 |
# shop_part_record_ids => [ 603, 604, 605 ], |
|
19 |
# todo => $::form->{upload_todo}, |
|
20 |
# status => SL::BackgroundJob::ShopPartMassUpload->WAITING_FOR_EXECUTION(), |
|
21 |
# num_uploaded => 0, |
|
22 |
# conversation => [ { id => 603 , number => 2, message => "Ok" or $@ }, ], |
|
22 | 23 |
# }; |
23 | 24 |
|
24 | 25 |
sub update_webarticles { |
... | ... | |
28 | 29 |
my $db = $job_obj->db; |
29 | 30 |
|
30 | 31 |
$job_obj->set_data(UPLOAD_TO_WEBSHOP())->save; |
31 |
|
|
32 |
my $num_uploaded = 0; |
|
32 | 33 |
foreach my $shop_part_id (@{ $job_obj->data_as_hash->{shop_part_record_ids} }) { |
33 | 34 |
my $data = $job_obj->data_as_hash; |
34 | 35 |
eval { |
35 | 36 |
my $shop_part = SL::DB::Manager::ShopPart->find_by(id => $shop_part_id); |
36 | 37 |
unless($shop_part){ |
37 |
push @{ $data->{conversion_errors} }, { id => $shop_part_id, number => '', message => 'Shoppart not found' };
|
|
38 |
push @{ $data->{conversion} }, { id => $shop_part_id, number => '', message => 'Shoppart not found' }; |
|
38 | 39 |
} |
39 | 40 |
|
40 | 41 |
my $shop = SL::Shop->new( config => $shop_part->shop ); |
41 | 42 |
|
42 |
my $part_hash = $shop_part->part->as_tree; |
|
43 |
require SL::JSON; |
|
44 |
|
|
45 |
my $json = SL::JSON::to_json($part_hash); |
|
46 |
my $return = $shop->connector->update_part($shop_part, $json, $data->{todo}); |
|
43 |
my $return = $shop->connector->update_part($shop_part, $data->{todo}); |
|
47 | 44 |
if ( $return == 1 ) { |
48 | 45 |
my $now = DateTime->now; |
49 | 46 |
my $attributes->{last_update} = $now; |
50 | 47 |
$shop_part->assign_attributes(%{ $attributes }); |
51 | 48 |
$shop_part->save; |
49 |
$data->{num_uploaded} = $num_uploaded++; |
|
50 |
push @{ $data->{conversion} }, { id => $shop_part_id, number => $shop_part->part->partnumber, message => 'uploaded' }; |
|
52 | 51 |
}else{ |
53 |
push @{ $data->{conversion_errors} }, { id => $shop_part_id, number => '', message => $return };
|
|
52 |
push @{ $data->{conversion} }, { id => $shop_part_id, number => $shop_part->part->partnumber, message => $return };
|
|
54 | 53 |
} |
55 | 54 |
1; |
56 | 55 |
} or do { |
57 |
push @{ $data->{conversion_errors} }, { id => $shop_part_id, number => '', message => $@ };
|
|
56 |
push @{ $data->{conversion} }, { id => $shop_part_id, number => '', message => $@ }; |
|
58 | 57 |
}; |
59 | 58 |
|
60 | 59 |
$job_obj->update_attributes(data_as_hash => $data); |
SL/Controller/ShopPart.pm | ||
---|---|---|
44 | 44 |
require SL::Shop; |
45 | 45 |
my $shop = SL::Shop->new( config => $shop_part->shop ); |
46 | 46 |
|
47 |
my $part_hash = $shop_part->part->as_tree; |
|
48 |
my $json = SL::JSON::to_json($part_hash); |
|
49 |
my $return = $shop->connector->update_part($self->shop_part, $json,'all'); |
|
47 |
my $return = $shop->connector->update_part($self->shop_part, 'all'); |
|
50 | 48 |
|
51 | 49 |
# the connector deals with parsing/result verification, just needs to return success or failure |
52 | 50 |
if ( $return == 1 ) { |
... | ... | |
99 | 97 |
|
100 | 98 |
} |
101 | 99 |
|
102 |
sub action_update { |
|
103 |
my ($self) = @_; |
|
104 |
|
|
105 |
$self->create_or_update; |
|
106 |
} |
|
107 |
|
|
108 | 100 |
sub action_show_price_n_pricesource { |
109 | 101 |
my ($self) = @_; |
110 | 102 |
|
... | ... | |
148 | 140 |
my $online_cat = $online_article->{data}->{categories}; |
149 | 141 |
my @cat = (); |
150 | 142 |
for(keys %$online_cat){ |
151 |
# The ShopwareConnector works with the CategoryID @categories[x][0] in others/new Connectors it must be tested |
|
152 |
# Each assigned categorie is saved with id,categorie_name an multidimensional array and could be expanded with categoriepath or what is needed |
|
153 | 143 |
my @cattmp; |
154 |
push( @cattmp,$online_cat->{$_}->{id} );
|
|
155 |
push( @cattmp,$online_cat->{$_}->{name} );
|
|
156 |
push( @cat,\@cattmp );
|
|
144 |
push @cattmp,$online_cat->{$_}->{id};
|
|
145 |
push @cattmp,$online_cat->{$_}->{name};
|
|
146 |
push @cat,\@cattmp;
|
|
157 | 147 |
} |
158 | 148 |
my $attributes->{shop_category} = \@cat; |
159 | 149 |
my $active->{active} = $online_article->{data}->{active}; |
... | ... | |
163 | 153 |
$self->redirect_to( action => 'list_articles' ); |
164 | 154 |
} |
165 | 155 |
|
166 |
sub create_or_update { |
|
167 |
my ($self) = @_; |
|
168 |
|
|
169 |
my $is_new = !$self->shop_part->id; |
|
170 |
|
|
171 |
# in edit.html all variables start with shop_part |
|
172 |
my $params = delete($::form->{shop_part}) || { }; |
|
173 |
|
|
174 |
$self->shop_part->assign_attributes(%{ $params }); |
|
175 |
|
|
176 |
$self->shop_part->save; |
|
177 |
|
|
178 |
my ( $price, $price_src_str ) = $self->get_price_n_pricesource($self->shop_part->active_price_source); |
|
179 |
|
|
180 |
flash('info', $is_new ? t8('The shop part has been created.') : t8('The shop part has been saved.')); |
|
181 |
$self->js->html('#shop_part_description_' . $self->shop_part->id, $self->shop_part->shop_description) |
|
182 |
->html('#shop_part_active_' . $self->shop_part->id, $self->shop_part->active) |
|
183 |
->html('#price_' . $self->shop_part->id, $::form->format_amount(\%::myconfig,$price,2)) |
|
184 |
->html('#active_price_source_' . $self->shop_part->id, $price_src_str) |
|
185 |
->run('kivi.ShopPart.close_dialog') |
|
186 |
->flash('info', t8("Updated shop part")) |
|
187 |
->render; |
|
188 |
} |
|
189 |
|
|
190 |
sub render_shop_part_edit_dialog { |
|
191 |
my ($self) = @_; |
|
192 |
|
|
193 |
# when self->shop_part is called in template, it will be an existing shop_part with id, |
|
194 |
# or a new shop_part with only part_id and shop_id set |
|
195 |
$self->js |
|
196 |
->run( |
|
197 |
'kivi.ShopPart.shop_part_dialog', |
|
198 |
t8('Shop part'), |
|
199 |
$self->render('shop_part/edit', { output => 0 }) |
|
200 |
) |
|
201 |
->reinit_widgets; |
|
202 |
|
|
203 |
$self->js->render; |
|
204 |
} |
|
205 |
|
|
206 | 156 |
sub action_save_categories { |
207 | 157 |
my ($self) = @_; |
208 | 158 |
|
209 | 159 |
my @categories = @{ $::form->{categories} || [] }; |
210 | 160 |
|
211 |
# The ShopwareConnector works with the CategoryID @categories[x][0] in others/new Connectors it must be tested |
|
212 |
# Each assigned categorie is saved with id,categorie_name an multidimensional array and could be expanded with categoriepath or what is needed |
|
213 | 161 |
my @cat = (); |
214 | 162 |
foreach my $cat ( @categories) { |
215 | 163 |
my @cattmp; |
... | ... | |
275 | 223 |
|
276 | 224 |
my @shop_parts = @{ $::form->{shop_parts_ids} || [] }; |
277 | 225 |
|
278 |
my $job = SL::DB::BackgroundJob->new( |
|
279 |
type => 'once', |
|
280 |
active => 1, |
|
281 |
package_name => 'ShopPartMassUpload', |
|
282 |
)->set_data( |
|
283 |
shop_part_record_ids => [ @shop_parts ], |
|
284 |
todo => $::form->{upload_todo}, |
|
285 |
status => SL::BackgroundJob::ShopPartMassUpload->WAITING_FOR_EXECUTION(), |
|
286 |
conversation_errors => [ ], |
|
226 |
my $job = SL::DB::BackgroundJob->new( |
|
227 |
type => 'once', |
|
228 |
active => 1, |
|
229 |
package_name => 'ShopPartMassUpload', |
|
230 |
)->set_data( |
|
231 |
shop_part_record_ids => [ @shop_parts ], |
|
232 |
todo => $::form->{upload_todo}, |
|
233 |
status => SL::BackgroundJob::ShopPartMassUpload->WAITING_FOR_EXECUTION(), |
|
234 |
conversation => [ ], |
|
235 |
num_uploaded => 0, |
|
287 | 236 |
)->update_next_run_at; |
288 | 237 |
|
289 | 238 |
SL::System::TaskServer->new->wake_up; |
290 | 239 |
|
291 |
my $html = $self->render('shop_part/_transfer_status', { output => 0 }, job => $job);
|
|
240 |
my $html = $self->render('shop_part/_upload_status', { output => 0 }, job => $job);
|
|
292 | 241 |
|
293 | 242 |
$self->js |
294 | 243 |
->html('#status_mass_upload', $html) |
... | ... | |
296 | 245 |
->render; |
297 | 246 |
} |
298 | 247 |
|
248 |
sub action_update { |
|
249 |
my ($self) = @_; |
|
250 |
|
|
251 |
$self->create_or_update; |
|
252 |
} |
|
253 |
|
|
254 |
sub render_shop_part_edit_dialog { |
|
255 |
my ($self) = @_; |
|
256 |
|
|
257 |
$self->js |
|
258 |
->run( |
|
259 |
'kivi.ShopPart.shop_part_dialog', |
|
260 |
t8('Shop part'), |
|
261 |
$self->render('shop_part/edit', { output => 0 }) |
|
262 |
) |
|
263 |
->reinit_widgets; |
|
264 |
|
|
265 |
$self->js->render; |
|
266 |
} |
|
267 |
|
|
268 |
sub create_or_update { |
|
269 |
my ($self) = @_; |
|
270 |
|
|
271 |
my $is_new = !$self->shop_part->id; |
|
272 |
|
|
273 |
my $params = delete($::form->{shop_part}) || { }; |
|
274 |
|
|
275 |
$self->shop_part->assign_attributes(%{ $params }); |
|
276 |
|
|
277 |
$self->shop_part->save; |
|
278 |
|
|
279 |
my ( $price, $price_src_str ) = $self->get_price_n_pricesource($self->shop_part->active_price_source); |
|
280 |
|
|
281 |
flash('info', $is_new ? t8('The shop part has been created.') : t8('The shop part has been saved.')); |
|
282 |
$self->js->html('#shop_part_description_' . $self->shop_part->id, $self->shop_part->shop_description) |
|
283 |
->html('#shop_part_active_' . $self->shop_part->id, $self->shop_part->active) |
|
284 |
->html('#price_' . $self->shop_part->id, $::form->format_amount(\%::myconfig,$price,2)) |
|
285 |
->html('#active_price_source_' . $self->shop_part->id, $price_src_str) |
|
286 |
->run('kivi.ShopPart.close_dialog') |
|
287 |
->flash('info', t8("Updated shop part")) |
|
288 |
->render; |
|
289 |
} |
|
290 |
|
|
299 | 291 |
# |
300 | 292 |
# internal stuff |
301 | 293 |
# |
... | ... | |
306 | 298 |
sub load_pricesources { |
307 | 299 |
my ($self) = @_; |
308 | 300 |
|
309 |
# the price sources to use for the article: sellprice, lastcost, |
|
310 |
# listprice, or one of the pricegroups. It overwrites the default pricesource from the shopconfig. |
|
311 |
# TODO: implement valid pricerules for the article |
|
312 | 301 |
my $pricesources; |
313 | 302 |
push( @{ $pricesources } , { id => "master_data/sellprice", name => t8("Master Data")." - ".t8("Sellprice") }, |
314 | 303 |
{ id => "master_data/listprice", name => t8("Master Data")." - ".t8("Listprice") }, |
315 |
{ id => "master_data/lastcost", name => t8("Master Data")." - ".t8("Lastcost") }
|
|
304 |
{ id => "master_data/lastcost", nam => t8("Master Data")." - ".t8("Lastcost") } |
|
316 | 305 |
); |
317 | 306 |
my $pricegroups = SL::DB::Manager::Pricegroup->get_all; |
318 | 307 |
foreach my $pg ( @$pricegroups ) { |
... | ... | |
331 | 320 |
require SL::DB::Part; |
332 | 321 |
my $price; |
333 | 322 |
if ($price_src_str eq "master_data") { |
334 |
my $part = SL::DB::Manager::Part->get_all( where => [id => $self->shop_part->part_id], with_objects => ['prices'],limit => 1)->[0];
|
|
323 |
my $part = SL::DB::Manager::Part->find_by( id => $self->shop_part->part_id );
|
|
335 | 324 |
$price = $part->$price_src_id; |
336 | 325 |
$price_src_str = $price_src_id; |
337 | 326 |
}else{ |
338 | 327 |
my $part = SL::DB::Manager::Part->get_all( where => [id => $self->shop_part->part_id, 'prices.'.pricegroup_id => $price_src_id], with_objects => ['prices'],limit => 1)->[0]; |
328 |
#my $part = SL::DB::Manager::Part->find_by( id => $self->shop_part->part_id, 'prices.'.pricegroup_id => $price_src_id ); |
|
339 | 329 |
my $pricegrp = SL::DB::Manager::Pricegroup->find_by( id => $price_src_id )->pricegroup; |
340 | 330 |
$price = $part->prices->[0]->price; |
341 | 331 |
$price_src_str = $pricegrp; |
... | ... | |
362 | 352 |
} |
363 | 353 |
|
364 | 354 |
sub init_shops { |
365 |
# data for drop down filter options |
|
366 | 355 |
require SL::DB::Shop; |
367 | 356 |
my @shops_dd = [ { title => t8("all") , value =>'' } ]; |
368 | 357 |
my $shops = SL::DB::Mangager::Shop->get_all( where => [ obsolete => 0 ] ); |
369 | 358 |
my @tmp = map { { title => $_->{description}, value => $_->{id} } } @{ $shops } ; |
370 | 359 |
return @shops_dd; |
371 |
|
|
372 |
} |
|
373 |
|
|
374 |
sub init_producers { |
|
375 |
# data for drop down filter options |
|
376 |
my @producers_dd = [ { title => t8("all") , value =>'' } ]; |
|
377 |
return @producers_dd; |
|
378 |
|
|
379 | 360 |
} |
380 | 361 |
|
381 | 362 |
1; |
... | ... | |
387 | 368 |
|
388 | 369 |
=head1 NAME |
389 | 370 |
|
390 |
SL::Controller::ShopPart - Controller for managing ShopParts
|
|
371 |
SL::Controller::ShopPart - Controller for managing ShopParts |
|
391 | 372 |
|
392 | 373 |
=head1 SYNOPSIS |
393 | 374 |
|
394 |
ShopParts are configured in a tab of the corresponding part. |
|
395 |
|
|
396 |
=head1 FUNCTIONS |
|
375 |
ShopParts are configured in a tab of the corresponding part. |
|
397 | 376 |
|
377 |
=head1 ACTIONS |
|
398 | 378 |
|
399 | 379 |
=over 4 |
400 | 380 |
|
401 | 381 |
=item C<action_update_shop> |
402 | 382 |
|
403 |
To be called from the "Update" button of the shoppart, for manually syncing/upload one part with its shop. Generates a Calls some ClientJS functions to modifiy original page. |
|
383 |
To be called from the "Update" button of the shoppart, for manually syncing/upload one part with its shop. Calls some ClientJS functions to modifiy original page. |
|
384 |
|
|
385 |
=item C<action_show_files> |
|
386 |
|
|
387 |
|
|
388 |
|
|
389 |
=item C<action_ajax_delete_file> |
|
390 |
|
|
391 |
|
|
392 |
|
|
393 |
=item C<action_get_categories> |
|
394 |
|
|
395 |
|
|
396 |
|
|
397 |
=item C<action_show_price_n_pricesource> |
|
398 |
|
|
399 |
|
|
400 |
|
|
401 |
=item C<action_show_stock> |
|
402 |
|
|
403 |
|
|
404 | 404 |
|
405 | 405 |
=item C<action_get_n_write_categories> |
406 | 406 |
|
407 |
Can be used to sync the categories of a shoppart with the categories from online. |
|
407 |
Can be used to sync the categories of a shoppart with the categories from online. |
|
408 |
|
|
409 |
=item C<action_save_categories> |
|
410 |
|
|
411 |
The ShopwareConnector works with the CategoryID @categories[x][0] in others/new Connectors it must be tested |
|
412 |
Each assigned categorie is saved with id,categorie_name an multidimensional array and could be expanded with categoriepath or what is needed |
|
413 |
|
|
414 |
=item C<action_reorder> |
|
415 |
|
|
416 |
|
|
417 |
|
|
418 |
=item C<action_upload_status> |
|
419 |
|
|
420 |
|
|
421 |
|
|
422 |
=item C<action_mass_upload> |
|
423 |
|
|
424 |
|
|
425 |
|
|
426 |
=item C<action_update> |
|
427 |
|
|
428 |
|
|
429 |
|
|
430 |
=item C<create_or_update> |
|
431 |
|
|
432 |
|
|
433 |
|
|
434 |
=item C<render_shop_part_edit_dialog> |
|
435 |
|
|
436 |
when self->shop_part is called in template, it will be an existing shop_part with id, |
|
437 |
or a new shop_part with only part_id and shop_id set |
|
438 |
|
|
439 |
=item C<add_javascripts> |
|
440 |
|
|
441 |
|
|
442 |
=item C<load_pricesources> |
|
443 |
|
|
444 |
the price sources to use for the article: sellprice, lastcost, |
|
445 |
listprice, or one of the pricegroups. It overwrites the default pricesource from the shopconfig. |
|
446 |
TODO: implement valid pricerules for the article |
|
447 |
|
|
448 |
=item C<get_price_n_pricesource> |
|
449 |
|
|
450 |
|
|
451 |
=item C<check_auth> |
|
452 |
|
|
453 |
|
|
454 |
=item C<init_shop_part> |
|
455 |
|
|
456 |
|
|
457 |
=item C<init_file> |
|
458 |
|
|
459 |
|
|
460 |
=item C<init_shops> |
|
461 |
|
|
462 |
data for drop down filter options |
|
408 | 463 |
|
409 | 464 |
=back |
410 | 465 |
|
411 | 466 |
=head1 TODO |
412 | 467 |
|
413 |
Pricesrules, pricessources aren't fully implemented yet. |
|
468 |
CheckAuth |
|
469 |
Pricesrules, pricessources aren't fully implemented yet. |
|
414 | 470 |
|
415 | 471 |
=head1 AUTHORS |
416 | 472 |
|
417 |
G. Richardson E<lt>information@kivitendo-premium.deE<gt>
|
|
418 |
W. Hahn E<lt>wh@futureworldsearch.netE<gt>
|
|
473 |
G. Richardson E<lt>information@kivitendo-premium.deE<gt> |
|
474 |
W. Hahn E<lt>wh@futureworldsearch.netE<gt> |
|
419 | 475 |
|
420 | 476 |
=cut |
SL/DB/ShopPart.pm | ||
---|---|---|
5 | 5 |
|
6 | 6 |
use strict; |
7 | 7 |
|
8 |
use SL::DBUtils; |
|
8 | 9 |
use SL::DB::MetaSetup::ShopPart; |
9 | 10 |
use SL::DB::Manager::ShopPart; |
10 | 11 |
use SL::DB::Helper::AttrHTML; |
... | ... | |
13 | 14 |
__PACKAGE__->meta->initialize; |
14 | 15 |
__PACKAGE__->attr_html('shop_description'); |
15 | 16 |
|
17 |
sub get_tax_and_price { |
|
18 |
my ( $self ) = @_; |
|
19 |
|
|
20 |
require SL::DB::Part; |
|
21 |
my $tax_n_price; |
|
22 |
my ( $price_src_str, $price_src_id ) = split(/\//,$self->active_price_source); |
|
23 |
my $price; |
|
24 |
my $part; |
|
25 |
if ($price_src_str eq "master_data") { |
|
26 |
$part = SL::DB::Manager::Part->find_by( id => $self->part_id ); |
|
27 |
$price = $part->$price_src_id; |
|
28 |
}else{ |
|
29 |
$part = SL::DB::Manager::Part->find_by( id => $self->part_id ); |
|
30 |
$price = $part->prices->[0]->price; |
|
31 |
} |
|
32 |
|
|
33 |
my $taxrate; |
|
34 |
my $dbh = $::form->get_standard_dbh(); |
|
35 |
my $b_id = $part->buchungsgruppen_id; |
|
36 |
my $t_id = $self->shop->taxzone_id; |
|
37 |
|
|
38 |
my $sql_str = "SELECT a.rate AS taxrate from tax a |
|
39 |
WHERE a.taxkey = (SELECT b.taxkey_id |
|
40 |
FROM chart b LEFT JOIN taxzone_charts c ON b.id = c.income_accno_id |
|
41 |
WHERE c.taxzone_id = $t_id |
|
42 |
AND c.buchungsgruppen_id = $b_id)"; |
|
43 |
|
|
44 |
my $rate = selectall_hashref_query($::form, $dbh, $sql_str); |
|
45 |
$taxrate = @$rate[0]->{taxrate}*100; |
|
46 |
|
|
47 |
$tax_n_price->{price} = $price; |
|
48 |
$tax_n_price->{tax} = $taxrate; |
|
49 |
return $tax_n_price; |
|
50 |
} |
|
51 |
|
|
52 |
sub get_images { |
|
53 |
my ( $self ) = @_; |
|
54 |
|
|
55 |
require SL::DB::ShopImage; |
|
56 |
my $images = SL::DB::Manager::ShopImage->get_all( where => [ 'files.object_id' => $self->{part_id}, ], with_objects => 'file', sort_by => 'position' ); |
|
57 |
my @upload_img = (); |
|
58 |
foreach my $img (@{ $images }) { |
|
59 |
my $file = SL::File->get(id => $img->file->id ); |
|
60 |
my ($path, $extension) = (split /\./, $file->file_name); |
|
61 |
my $content = File::Slurp::read_file($file->get_file); |
|
62 |
my $temp ={ ( link => 'data:' . $file->mime_type . ';base64,' . MIME::Base64::encode($content, ""), #$content, # MIME::Base64::encode($content), |
|
63 |
description => $img->file->title, |
|
64 |
position => $img->position, |
|
65 |
extension => $extension, |
|
66 |
path => $path, |
|
67 |
)} ; |
|
68 |
push( @upload_img, $temp); |
|
69 |
} |
|
70 |
return @upload_img; |
|
71 |
} |
|
16 | 72 |
|
17 | 73 |
1; |
74 |
|
|
75 |
__END__ |
|
76 |
|
|
77 |
=pod |
|
78 |
|
|
79 |
=encoding utf-8 |
|
80 |
|
|
81 |
=head1 NAME |
|
82 |
|
|
83 |
SL::DB::ShopPart - Model for the 'shop_parts' table |
|
84 |
|
|
85 |
=head1 SYNOPSIS |
|
86 |
|
|
87 |
This is a standard Rose::DB::Object based model and can be used as one. |
|
88 |
|
|
89 |
=head1 METHODS |
|
90 |
|
|
91 |
=over 4 |
|
92 |
|
|
93 |
=item C<get_tax_and_price> |
|
94 |
|
|
95 |
Returns the price and the taxrate for an shop_article |
|
96 |
|
|
97 |
=item C<get_images> |
|
98 |
|
|
99 |
Returns the images for the shop_article |
|
100 |
|
|
101 |
=back |
|
102 |
|
|
103 |
=head1 TODO |
|
104 |
|
|
105 |
Prices, pricesources, pricerules could be implemented |
|
106 |
|
|
107 |
=head1 AUTHORS |
|
108 |
|
|
109 |
Werner Hahn E<lt>wh@futureworldsearch.netE<gt> |
|
110 |
|
|
111 |
=cut |
SL/ShopConnector/Shopware.pm | ||
---|---|---|
206 | 206 |
} |
207 | 207 |
|
208 | 208 |
sub update_part { |
209 |
my ($self, $shop_part, $json, $todo) = @_;
|
|
209 |
my ($self, $shop_part, $todo) = @_; |
|
210 | 210 |
|
211 | 211 |
#shop_part is passed as a param |
212 | 212 |
die unless ref($shop_part) eq 'SL::DB::ShopPart'; |
... | ... | |
214 | 214 |
my $url = $self->url; |
215 | 215 |
my $part = SL::DB::Part->new(id => $shop_part->{part_id})->load; |
216 | 216 |
|
217 |
# TODO: Prices (pricerules, pricegroups, multiple prices)
|
|
217 |
# CVARS to map
|
|
218 | 218 |
my $cvars = { map { ($_->config->name => { value => $_->value_as_text, is_valid => $_->is_valid }) } @{ $part->cvars_by_config } }; |
219 | 219 |
|
220 | 220 |
my @cat = (); |
... | ... | |
223 | 223 |
push ( @cat, $temp ); |
224 | 224 |
} |
225 | 225 |
|
226 |
my $images = SL::DB::Manager::ShopImage->get_all( where => [ 'files.object_id' => $part->{id}, ], with_objects => 'file', sort_by => 'position' ); |
|
227 |
my @upload_img = (); |
|
228 |
foreach my $img (@{ $images }) { |
|
229 |
my $file = SL::File->get(id => $img->file->id ); |
|
230 |
my ($path, $extension) = (split /\./, $file->file_name); |
|
231 |
my $content = File::Slurp::read_file($file->get_file); |
|
232 |
my $temp ={ ( link => 'data:' . $file->mime_type . ';base64,' . MIME::Base64::encode($content, ""), #$content, # MIME::Base64::encode($content), |
|
233 |
description => $img->file->title, |
|
234 |
position => $img->position, |
|
235 |
extension => $extension, |
|
236 |
path => $path, |
|
237 |
)} ; |
|
238 |
push( @upload_img, $temp); |
|
239 |
} |
|
240 |
|
|
241 |
my ($import,$data,$data_json); |
|
242 |
# if( $shop_part->last_update){ |
|
243 |
my $partnumber = $::form->escape($part->{partnumber});#shopware don't accept / in articlenumber |
|
244 |
# Shopware RestApi schreibt Fehleremail wenn Artikel nicht gefunden. es braucht aber irgendeine Abfrage, ob der Artikel schon im Shop ist. |
|
245 |
# LWP->post = neuanlegen LWP->put = update |
|
246 |
$data = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true"); |
|
247 |
$data_json = $data->content; |
|
248 |
$import = SL::JSON::decode_json($data_json); |
|
249 |
# } |
|
250 |
|
|
251 |
# get the right price |
|
252 |
# TODO In extra Helper?? |
|
253 |
my ( $price_src_str, $price_src_id ) = split(/\//,$shop_part->active_price_source); |
|
254 |
require SL::DB::Part; |
|
255 |
my $price; |
|
256 |
if ($price_src_str eq "master_data") { |
|
257 |
my $part = SL::DB::Manager::Part->get_all( where => [id => $shop_part->part_id], with_objects => ['prices'],limit => 1)->[0]; |
|
258 |
$price = $part->$price_src_id; |
|
259 |
}else{ |
|
260 |
my $part = SL::DB::Manager::Part->get_all( where => [id => $shop_part->part_id, 'prices.'.pricegroup_id => $price_src_id], with_objects => ['prices'],limit => 1)->[0]; |
|
261 |
$price = $part->prices->[0]->price; |
|
262 |
} |
|
263 |
|
|
264 |
# get the right taxrate for the article |
|
265 |
# TODO In extra Helper?? |
|
266 |
my $taxrate; |
|
267 |
my $dbh = $::form->get_standard_dbh(); |
|
268 |
my $b_id = $part->buchungsgruppen_id; |
|
269 |
my $t_id = $shop_part->shop->taxzone_id; |
|
270 |
|
|
271 |
my $sql_str = "SELECT a.rate AS taxrate from tax a |
|
272 |
WHERE a.taxkey = (SELECT b.taxkey_id |
|
273 |
FROM chart b LEFT JOIN taxzone_charts c ON b.id = c.income_accno_id |
|
274 |
WHERE c.taxzone_id = $t_id |
|
275 |
AND c.buchungsgruppen_id = $b_id)"; |
|
276 |
|
|
277 |
my $rate = selectall_hashref_query($::form, $dbh, $sql_str); |
|
278 |
$taxrate = @$rate[0]->{taxrate}*100; |
|
279 |
|
|
226 |
my @upload_img = $shop_part->get_images; |
|
227 |
my $tax_n_price = $shop_part->get_tax_and_price; |
|
228 |
my $price = $tax_n_price->{price}; |
|
229 |
my $taxrate = $tax_n_price->{tax}; |
|
280 | 230 |
# mapping to shopware still missing attributes,metatags |
281 | 231 |
my %shop_data; |
282 | 232 |
|
... | ... | |
339 | 289 |
|
340 | 290 |
my $upload_content; |
341 | 291 |
my $upload; |
292 |
my ($import,$data,$data_json); |
|
293 |
my $partnumber = $::form->escape($part->{partnumber});#shopware don't accept / in articlenumber |
|
294 |
# 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 |
|
295 |
# LWP->post = create LWP->put = update |
|
296 |
$data = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true"); |
|
297 |
$data_json = $data->content; |
|
298 |
$import = SL::JSON::decode_json($data_json); |
|
342 | 299 |
if($import->{success}){ |
343 | 300 |
#update |
344 | 301 |
my $partnumber = $::form->escape($part->{partnumber});#shopware don't accept / in articlenumber |
... | ... | |
354 | 311 |
# don't know if this is needed |
355 | 312 |
if(@upload_img) { |
356 | 313 |
my $partnumber = $::form->escape($part->{partnumber});#shopware don't accept / in articlenumber |
357 |
my $imgup = $self->connector->put($url . "api/generatearticleimages/$partnumber?usenumberasid=true");
|
|
314 |
my $imgup = $self->connector->put($url . "api/generatearticleimages/$partnumber?useNumberAsId=true");
|
|
358 | 315 |
} |
359 | 316 |
|
360 | 317 |
return $upload_content->{success}; |
... | ... | |
365 | 322 |
|
366 | 323 |
my $url = $self->url; |
367 | 324 |
$partnumber = $::form->escape($partnumber);#shopware don't accept / in articlenumber |
368 |
my $data = $self->connector->get($url . "api/articles/$partnumber?usenumberasid=true");
|
|
325 |
my $data = $self->connector->get($url . "api/articles/$partnumber?useNumberAsId=true");
|
|
369 | 326 |
my $data_json = $data->content; |
370 | 327 |
return SL::JSON::decode_json($data_json); |
371 | 328 |
} |
js/kivi.ShopPart.js | ||
---|---|---|
1 | 1 |
namespace('kivi.ShopPart', function(ns) { |
2 | 2 |
var $dialog; |
3 | 3 |
|
4 |
// this is called by sub render, with a certain prerendered html (edit.html,categories.html) |
|
5 | 4 |
ns.shop_part_dialog = function(title, html) { |
6 | 5 |
var id = 'jqueryui_popup_dialog'; |
7 | 6 |
var dialog_params = { |
... | ... | |
28 | 27 |
$dialog.dialog("close"); |
29 | 28 |
} |
30 | 29 |
|
31 |
|
|
32 |
// save existing shop_part_id with new params from form, calls create_or_update and saves to db |
|
33 | 30 |
ns.save_shop_part = function(shop_part_id) { |
34 | 31 |
var form = $('form').serializeArray(); |
35 | 32 |
form.push( { name: 'action', value: 'ShopPart/update' } |
... | ... | |
41 | 38 |
}); |
42 | 39 |
} |
43 | 40 |
|
44 |
// add part to a shop |
|
45 | 41 |
ns.add_shop_part = function(part_id,shop_id) { |
46 | 42 |
var form = $('form').serializeArray(); |
47 | 43 |
form.push( { name: 'action', value: 'ShopPart/update' } |
... | ... | |
51 | 47 |
}); |
52 | 48 |
} |
53 | 49 |
|
54 |
// this is called from tabs/_shop.html, opens edit_window (render) |
|
55 | 50 |
ns.edit_shop_part = function(shop_part_id) { |
56 | 51 |
$.post('controller.pl', { action: 'ShopPart/create_or_edit_popup', shop_part_id: shop_part_id }, function(data) { |
57 | 52 |
kivi.eval_json_result(data); |
58 | 53 |
}); |
59 | 54 |
} |
60 | 55 |
|
61 |
// does the same as edit_shop_part (existing), but with part_id and shop_id, opens edit window (render) |
|
62 | 56 |
ns.create_shop_part = function(part_id, shop_id) { |
63 | 57 |
$.post('controller.pl', { action: 'ShopPart/create_or_edit_popup', part_id: part_id, shop_id: shop_id }, function(data) { |
64 | 58 |
kivi.eval_json_result(data); |
65 | 59 |
}); |
66 | 60 |
} |
67 | 61 |
|
68 |
// gets all categories from the webshop |
|
69 | 62 |
ns.get_all_categories = function(shop_part_id) { |
70 |
//var form = new Array; //$('form').serializeArray(); |
|
71 |
//form.push( { name: 'action', value: 'ShopPart/get_categories' } |
|
72 |
// , { name: 'shop_part_id', value: shop_part_id } |
|
73 |
//); |
|
74 | 63 |
$.post('controller.pl', { action: 'ShopPart/get_categories', shop_part_id: shop_part_id }, function(data) { |
75 | 64 |
kivi.eval_json_result(data); |
76 | 65 |
}); |
77 |
//$.post('controller.pl', form, function(data) { |
|
78 |
// kivi.eval_json_result(data); |
|
79 |
//}); |
|
80 | 66 |
} |
81 |
// write categories in kivi DB not in the shops DB TODO: create new categories in the shops db |
|
67 |
|
|
82 | 68 |
ns.save_categories = function(shop_part_id, shop_id) { |
83 | 69 |
var form = $('form').serializeArray(); |
84 | 70 |
form.push( { name: 'action', value: 'ShopPart/save_categories' } |
... | ... | |
108 | 94 |
$('#shop_images').load(url); |
109 | 95 |
} |
110 | 96 |
|
111 |
//shows the Name and price in _shop.html. Pricerules not implemented yet, just master_data and pricegroups |
|
112 | 97 |
ns.update_price_n_price_source = function(shop_part_id,price_source) { |
113 | 98 |
$.post('controller.pl', { action: 'ShopPart/show_price_n_pricesource', shop_part_id: shop_part_id, pricesource: price_source }, function(data) { |
114 | 99 |
kivi.eval_json_result(data); |
115 | 100 |
}); |
116 | 101 |
} |
117 |
//shows the local and the online stock |
|
102 |
|
|
118 | 103 |
ns.update_stock = function(shop_part_id) { |
119 | 104 |
$.post('controller.pl', { action: 'ShopPart/show_stock', shop_part_id: shop_part_id }, function(data) { |
120 | 105 |
kivi.eval_json_result(data); |
templates/webpages/shop_part/_list_articles.html | ||
---|---|---|
75 | 75 |
</td> |
76 | 76 |
<td style="vertical-align:middle;text-align:center;"> |
77 | 77 |
[% IF shop_part.active %] |
78 |
<div id="toogle_[% shop_part.id %]" style="background-image:url(image/gruener_punkt.gif);background-repeat:no-repeat;witdh:15px;height:15px;" onclick="kivi.ShopPart.part_toggle_active([% shop_part.id %] ,[% shop_part.active %]);" onMouseOver="this.style.cursor='pointer'"> </div>
|
|
78 |
<div id="toogle_[% shop_part.id %]" style="background-image:url(image/gruener_punkt.gif);background-repeat:no-repeat;witdh:15px;height:15px;"> </div> |
|
79 | 79 |
[% ELSE %] |
80 |
<div id="toogle_[% shop_part.id %]" style="background-image:url(image/roter_punkt.gif);background-repeat:no-repeat;witdh:15px;height:15px;" onclick="kivi.ShopPart.part_toggle_active([% shop_part.id %] ,[% shop_part.active %]);" onMouseOver="this.style.cursor='pointer'"> </div>
|
|
80 |
<div id="toogle_[% shop_part.id %]" style="background-image:url(image/roter_punkt.gif);background-repeat:no-repeat;witdh:15px;height:15px;"> </div> |
|
81 | 81 |
[% END %] |
82 | 82 |
</td> |
83 | 83 |
<td>[% L.html_tag('span',LxERP.t8(), id => 'active_price_source_' _ shop_part.id) %] </td> |
templates/webpages/shop_part/_transfer_status.html | ||
---|---|---|
17 | 17 |
[% IF !data.status %] |
18 | 18 |
[% LxERP.t8("waiting for job to be started") %] |
19 | 19 |
[% ELSIF data.status == 1 %] |
20 |
[% LxERP.t8("Creating orders") %]
|
|
20 |
[% LxERP.t8("Uploading Data") %]
|
|
21 | 21 |
[% ELSE %] |
22 | 22 |
[% LxERP.t8("Done.") %] |
23 | 23 |
[% END %] |
24 | 24 |
</td> |
25 | 25 |
</tr> |
26 | 26 |
<tr> |
27 |
<th valign="top" align="left">[% LxERP.t8("Number of orders created:") %]</th>
|
|
28 |
<td valign="top">[% IF data.status > 0 %][% HTML.escape(data.num_created) %] / [% HTML.escape(data.record_ids.size) %][% ELSE %]–[% END %]</td>
|
|
27 |
<th valign="top" align="left">[% LxERP.t8("Number of data uploaded:") %]</th>
|
|
28 |
<td valign="top">[% IF data.status > 0 %][% HTML.escape(data.num_uploaded) %] / [% HTML.escape(data.record_ids.size) %][% ELSE %]–[% END %]</td>
|
|
29 | 29 |
</tr> |
30 | 30 |
|
31 | 31 |
<tr> |
32 |
<th valign="top" align="left">[% LxERP.t8("Errors during conversion:") %]</th>
|
|
32 |
<th valign="top" align="left">[% LxERP.t8("Conversion:") %]</th>
|
|
33 | 33 |
<td valign="top"> |
34 | 34 |
[% IF !data.status %] |
35 | 35 |
– |
36 |
[% ELSIF !data.conversion_errors.size %] |
|
37 |
[% LxERP.t8("No errors have occurred.") %] |
|
38 |
[% ELSE %] |
|
39 | 36 |
<table> |
40 | 37 |
<tr class="listheader"> |
41 |
<th>[% LxERP.t8("Shop Order") %]</th> |
|
42 |
<th>[% LxERP.t8("Error") %]</th> |
|
38 |
<th>[% LxERP.t8("Part") %]</th> |
|
39 |
<th>[% LxERP.t8("Partnumber") %]</th> |
|
40 |
<th>[% LxERP.t8("Message") %]</th> |
|
43 | 41 |
</tr> |
44 | 42 |
|
45 |
[% FOREACH error = data.conversion_errors %]
|
|
43 |
[% FOREACH message = data.conversion %]
|
|
46 | 44 |
<tr> |
47 | 45 |
<td valign="top">[% IF error.id %][% L.link(SELF.url_for(controller='ShopOrder', action='show', id=error.id), HTML.escape(error.number), target="_blank") %][% ELSE %]–[% END %]</td> |
48 |
<td valign="top">[% HTML.escape(error.message) %]</td>
|
|
46 |
<td valign="top">[% HTML.escape(message.message) %]</td>
|
|
49 | 47 |
</tr> |
50 | 48 |
[% END %] |
51 | 49 |
</table> |
templates/webpages/shop_part/_upload_status.html | ||
---|---|---|
2 | 2 |
[%- USE Dumper -%] |
3 | 3 |
[% SET data = job.data_as_hash %] |
4 | 4 |
|
5 |
|
|
6 | 5 |
<h2>[% LxERP.t8("Watch status") %]</h2> |
7 | 6 |
|
8 | 7 |
[% L.hidden_tag('', job.id, id="smu_job_id") %] |
... | ... | |
18 | 17 |
[% IF !data.status %] |
19 | 18 |
[% LxERP.t8("waiting for job to be started") %] |
20 | 19 |
[% ELSIF data.status == 1 %] |
21 |
[% LxERP.t8("Creating orders") %]
|
|
20 |
[% LxERP.t8("Uploading Data") %]
|
|
22 | 21 |
[% ELSE %] |
23 | 22 |
[% LxERP.t8("Done.") %] |
24 | 23 |
[% END %] |
25 | 24 |
</td> |
26 | 25 |
</tr> |
27 | 26 |
<tr> |
28 |
<th valign="top" align="left">[% LxERP.t8("Number of orders created:") %]</th>
|
|
29 |
<td valign="top">[% IF data.status > 0 %][% HTML.escape(data.num_created) %] / [% HTML.escape(data.record_ids.size) %][% ELSE %]–[% END %]</td>
|
|
27 |
<th valign="top" align="left">[% LxERP.t8("Number of data uploaded:") %]</th>
|
|
28 |
<td valign="top">[% IF data.status > 0 %][% HTML.escape(data.num_uploaded) %] / [% HTML.escape(data.shop_part_record_ids.size) %][% ELSE %]–[% END %]</td>
|
|
30 | 29 |
</tr> |
31 | 30 |
|
32 | 31 |
<tr> |
33 |
<th valign="top" align="left">[% LxERP.t8("Errors during conversion:") %]</th>
|
|
32 |
<th valign="top" align="left">[% LxERP.t8("Conversion:") %]</th>
|
|
34 | 33 |
<td valign="top"> |
35 |
[% IF !data.status %] |
|
36 | 34 |
– |
37 |
[% ELSIF !data.conversion_errors.size %] |
|
38 |
[% LxERP.t8("No errors have occurred.") %] |
|
39 |
[% ELSE %] |
|
40 | 35 |
<table> |
41 | 36 |
<tr class="listheader"> |
42 |
<th>[% LxERP.t8("Shop Order") %]</th> |
|
43 |
<th>[% LxERP.t8("Error") %]</th> |
|
37 |
<th>[% LxERP.t8("Part") %]</th> |
|
38 |
<th>[% LxERP.t8("Partnumber") %]</th> |
|
39 |
<th>[% LxERP.t8("Message") %]</th> |
|
44 | 40 |
</tr> |
45 | 41 |
|
46 |
[% FOREACH error = data.conversion_errors %]
|
|
42 |
[% FOREACH message = data.conversion %]
|
|
47 | 43 |
<tr> |
48 |
<td valign="top">[% IF error.id %][% L.link(SELF.url_for(controller='ShopOrder', action='show', id=error.id), HTML.escape(error.number), target="_blank") %][% ELSE %]–[% END %]</td> |
|
49 |
<td valign="top">[% HTML.escape(error.message) %]</td> |
|
44 |
<td valign="top">[% HTML.escape(message.id) %]</td> |
|
45 |
<td valign="top">[% HTML.escape(message.number) %]</td> |
|
46 |
<td valign="top">[% HTML.escape(message.message) %]</td> |
|
50 | 47 |
</tr> |
51 | 48 |
[% END %] |
52 | 49 |
</table> |
53 |
[% END %] |
|
54 | 50 |
</table> |
55 | 51 |
</p> |
Auch abrufbar als: Unified diff
Shopmodul: Artikel überarbeitet
Dinge aus Connector ausgelagert, die auch für andere Conectoren
gültigkeit haben
Output des Backgroundjob MassUpload überarbeitet
POD
Typo