1 package SL::Controller::ShopPart;
 
   5 use parent qw(SL::Controller::Base);
 
   7 use SL::BackgroundJob::ShopPartMassUpload;
 
   8 use SL::System::TaskServer;
 
  10 use SL::Locale::String qw(t8);
 
  14 use SL::DB::ShopImage;
 
  16 use SL::Helper::Flash;
 
  17 use SL::Controller::Helper::ParseFilter;
 
  20 use Rose::Object::MakeMethods::Generic
 
  22    scalar                 => [ qw(price_sources) ],
 
  23   'scalar --get_set_init' => [ qw(shop_part file shops) ],
 
  26 __PACKAGE__->run_before('check_auth');
 
  27 __PACKAGE__->run_before('add_javascripts', only => [ qw(edit_popup list_articles) ]);
 
  28 __PACKAGE__->run_before('load_pricesources',    only => [ qw(create_or_edit_popup) ]);
 
  34 sub action_create_or_edit_popup {
 
  37   $self->render_shop_part_edit_dialog();
 
  40 sub action_update_shop {
 
  41   my ($self, %params) = @_;
 
  43   my $shop_part = SL::DB::Manager::ShopPart->find_by(id => $::form->{shop_part_id});
 
  44   die unless $shop_part;
 
  47   my $shop = SL::Shop->new( config => $shop_part->shop );
 
  49   my $connect = $shop->check_connectivity;
 
  50   if($connect->{success}){
 
  51     my $return    = $shop->connector->update_part($self->shop_part, 'all');
 
  53     # the connector deals with parsing/result verification, just needs to return success or failure
 
  55       my $now = DateTime->now;
 
  56       my $attributes->{last_update} = $now;
 
  57       $self->shop_part->assign_attributes(%{ $attributes });
 
  58       $self->shop_part->save;
 
  59       $self->js->html('#shop_part_last_update_' . $shop_part->id, $now->to_kivitendo('precision' => 'minute'))
 
  60              ->flash('info', t8("Updated part [#1] in shop [#2] at #3", $shop_part->part->displayable_name, $shop_part->shop->description, $now->to_kivitendo('precision' => 'minute') ) )
 
  63       $self->js->flash('error', t8('The shop part wasn\'t updated.'))->render;
 
  66     $self->js->flash('error', t8('The shop part wasn\'t updated. #1', $connect->{data}->{version}))->render;
 
  72 sub action_show_files {
 
  75   my $images = SL::DB::Manager::ShopImage->get_all( where => [ 'files.object_id' => $::form->{id}, ], with_objects => 'file', sort_by => 'position' );
 
  77   $self->render('shop_part/_list_images', { header => 0 }, IMAGES => $images);
 
  80 sub action_ajax_delete_file {
 
  85     ->run('kivi.ShopPart.show_images',$self->file->object_id)
 
  89 sub action_get_categories {
 
  93   my $shop = SL::Shop->new( config => $self->shop_part->shop );
 
  95   my $connect = $shop->check_connectivity;
 
  96   if($connect->{success}){
 
  97     my $categories = $shop->connector->get_categories;
 
 101         'kivi.ShopPart.shop_part_dialog',
 
 102         t8('Shopcategories'),
 
 103         $self->render('shop_part/categories', { output => 0 }, CATEGORIES => $categories )
 
 108     $self->js->flash('error', t8('Can\'t connect to shop. #1', $connect->{data}->{version}))->render;
 
 113 sub action_show_price_n_pricesource {
 
 116   my ( $price, $price_src_str ) = $self->get_price_n_pricesource($::form->{pricesource});
 
 118   if( $price_src_str eq 'sellprice'){
 
 119     $price_src_str = t8('Sellprice');
 
 120   }elsif( $price_src_str eq 'listprice'){
 
 121     $price_src_str = t8('Listprice');
 
 122   }elsif( $price_src_str eq 'lastcost'){
 
 123     $price_src_str = t8('Lastcost');
 
 125   $self->js->html('#price_' . $self->shop_part->id, $::form->format_amount(\%::myconfig,$price,2))
 
 126            ->html('#active_price_source_' . $self->shop_part->id, $price_src_str)
 
 130 sub action_show_stock {
 
 132   my ( $stock_local, $stock_onlineshop, $active_online );
 
 135   my $shop = SL::Shop->new( config => $self->shop_part->shop );
 
 137   if($self->shop_part->last_update) {
 
 138     my $shop_article = $shop->connector->get_article($self->shop_part->part->partnumber);
 
 139     $stock_onlineshop = $shop_article->{data}->{mainDetail}->{inStock};
 
 140     $active_online = $shop_article->{data}->{active};
 
 143   $stock_local = $self->shop_part->part->onhand;
 
 145   $self->js->html('#stock_' . $self->shop_part->id, $::form->format_amount(\%::myconfig,$stock_local,0)."/".$::form->format_amount(\%::myconfig,$stock_onlineshop,0))
 
 146            ->html('#toogle_' . $self->shop_part->id,$active_online)
 
 150 sub action_get_n_write_categories {
 
 153   my @shop_parts =  @{ $::form->{shop_parts_ids} || [] };
 
 154   foreach my $part(@shop_parts){
 
 156     my $shop_part = SL::DB::Manager::ShopPart->get_all( where => [id => $part], with_objects => ['part', 'shop'])->[0];
 
 157     require SL::DB::Shop;
 
 158     my $shop = SL::Shop->new( config => $shop_part->shop );
 
 159     my $online_article = $shop->connector->get_article($shop_part->part->partnumber);
 
 160     my $online_cat = $online_article->{data}->{categories};
 
 162     for(keys %$online_cat){
 
 164       push @cattmp,$online_cat->{$_}->{id};
 
 165       push @cattmp,$online_cat->{$_}->{name};
 
 168     my $attributes->{shop_category} = \@cat;
 
 169     my $active->{active} = $online_article->{data}->{active};
 
 170     $shop_part->assign_attributes(%{$attributes}, %{$active});
 
 173   $self->redirect_to( action => 'list_articles' );
 
 176 sub action_save_categories {
 
 179   my @categories =  @{ $::form->{categories} || [] };
 
 182     foreach my $cat ( @categories) {
 
 184       push( @cattmp,$cat );
 
 185       push( @cattmp,$::form->{"cat_id_${cat}"} );
 
 186       push( @cat,\@cattmp );
 
 189   my $categories->{shop_category} = \@cat;
 
 191   my $params = delete($::form->{shop_part}) || { };
 
 193   $self->shop_part->assign_attributes(%{ $params });
 
 194   $self->shop_part->assign_attributes(%{ $categories });
 
 196   $self->shop_part->save;
 
 198   flash('info', t8('The categories has been saved.'));
 
 200   $self->js->run('kivi.ShopPart.close_dialog')
 
 201            ->flash('info', t8("Updated categories"))
 
 207   require SL::DB::ShopImage;
 
 208   SL::DB::ShopImage->reorder_list(@{ $::form->{image_id} || [] });
 
 210   $self->render(\'', { type => 'json' });
 
 213 sub action_list_articles {
 
 216   my %filter      = ($::form->{filter} ? parse_filter($::form->{filter}) : query => [ 'shop.obsolete' => 0 ]);
 
 217   my $sort_by     = $::form->{sort_by} ? $::form->{sort_by} : 'part.partnumber';
 
 218   $sort_by .=$::form->{sort_dir} ? ' DESC' : ' ASC';
 
 220   my $articles = SL::DB::Manager::ShopPart->get_all( %filter ,with_objects => [ 'part','shop' ], sort_by => $sort_by );
 
 222   foreach my $article (@{ $articles}) {
 
 223     my $images = SL::DB::Manager::ShopImage->get_all_count( where => [ 'files.object_id' => $article->part->id, ], with_objects => 'file', sort_by => 'position' );
 
 224     $article->{images} = $images;
 
 227   $self->render('shop_part/_list_articles', title => t8('Webshops articles'), SHOP_PARTS => $articles);
 
 230 sub action_upload_status {
 
 232   my $job     = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
 
 233   my $html    = $self->render('shop_part/_upload_status', { output => 0 }, job => $job);
 
 235   $self->js->html('#status_mass_upload', $html);
 
 236   $self->js->run('kivi.ShopPart.massUploadFinished') if $job->data_as_hash->{status} == SL::BackgroundJob::ShopPartMassUpload->DONE();
 
 240 sub action_mass_upload {
 
 243   my @shop_parts =  @{ $::form->{shop_parts_ids} || [] };
 
 245   my $job = SL::DB::BackgroundJob->new(
 
 248         package_name         => 'ShopPartMassUpload',
 
 250         shop_part_record_ids => [ @shop_parts ],
 
 251         todo                 => $::form->{upload_todo},
 
 252         status               => SL::BackgroundJob::ShopPartMassUpload->WAITING_FOR_EXECUTION(),
 
 255    )->update_next_run_at;
 
 257    SL::System::TaskServer->new->wake_up;
 
 259    my $html = $self->render('shop_part/_upload_status', { output => 0 }, job => $job);
 
 262       ->html('#status_mass_upload', $html)
 
 263       ->run('kivi.ShopPart.massUploadStarted')
 
 270   $self->create_or_update;
 
 273 sub render_shop_part_edit_dialog {
 
 278       'kivi.ShopPart.shop_part_dialog',
 
 280       $self->render('shop_part/edit', { output => 0 })
 
 287 sub create_or_update {
 
 290   my $is_new = !$self->shop_part->id;
 
 292   my $params = delete($::form->{shop_part}) || { };
 
 294   $self->shop_part->assign_attributes(%{ $params });
 
 296   $self->shop_part->save;
 
 298   my ( $price, $price_src_str ) = $self->get_price_n_pricesource($self->shop_part->active_price_source);
 
 300   flash('info', $is_new ? t8('The shop part has been created.') : t8('The shop part has been saved.'));
 
 301   $self->js->html('#shop_part_description_' . $self->shop_part->id, $self->shop_part->shop_description)
 
 302            ->html('#shop_part_active_' . $self->shop_part->id, $self->shop_part->active)
 
 303            ->html('#price_' . $self->shop_part->id, $::form->format_amount(\%::myconfig,$price,2))
 
 304            ->html('#active_price_source_' . $self->shop_part->id, $price_src_str)
 
 305            ->run('kivi.ShopPart.close_dialog')
 
 306            ->flash('info', t8("Updated shop part"))
 
 309     $self->redirect_to(controller => 'Part', action => 'edit', 'part.id' => $self->shop_part->part_id);
 
 316 sub add_javascripts  {
 
 317   $::request->{layout}->add_javascripts(qw(kivi.ShopPart.js));
 
 320 sub load_pricesources {
 
 324   push( @{ $pricesources } , { id => "master_data/sellprice", name => t8("Master Data")." - ".t8("Sellprice") },
 
 325                              { id => "master_data/listprice", name => t8("Master Data")." - ".t8("Listprice") },
 
 326                              { id => "master_data/lastcost",  name => t8("Master Data")." - ".t8("Lastcost") }
 
 328   my $pricegroups = SL::DB::Manager::Pricegroup->get_all;
 
 329   foreach my $pg ( @$pricegroups ) {
 
 330     push( @{ $pricesources } , { id => "pricegroup/".$pg->id, name => t8("Pricegroup") . " - " . $pg->pricegroup} );
 
 333   $self->price_sources( $pricesources );
 
 336 sub get_price_n_pricesource {
 
 337   my ($self,$pricesource) = @_;
 
 339   my ( $price_src_str, $price_src_id ) = split(/\//,$pricesource);
 
 341   require SL::DB::Pricegroup;
 
 342   require SL::DB::Part;
 
 344   if ($price_src_str eq "master_data") {
 
 345     my $part       = SL::DB::Manager::Part->find_by( id => $self->shop_part->part_id );
 
 346     $price         = $part->$price_src_id;
 
 347     $price_src_str = $price_src_id;
 
 349     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];
 
 350     #my $part       = SL::DB::Manager::Part->find_by( id => $self->shop_part->part_id, 'prices.'.pricegroup_id => $price_src_id );
 
 351     my $pricegrp   = SL::DB::Manager::Pricegroup->find_by( id => $price_src_id )->pricegroup;
 
 352     $price         = $part->prices->[0]->price;
 
 353     $price_src_str = $pricegrp;
 
 355   return($price,$price_src_str);
 
 359   $::auth->assert('shop_part_edit');
 
 363   if ($::form->{shop_part_id}) {
 
 364     SL::DB::Manager::ShopPart->find_by(id => $::form->{shop_part_id});
 
 366     SL::DB::ShopPart->new(shop_id => $::form->{shop_id}, part_id => $::form->{part_id});
 
 371   my $file = $::form->{id} ? SL::DB::File->new(id => $::form->{id})->load : SL::DB::File->new;
 
 376   SL::DB::Shop->shops_dd;
 
 388 SL::Controller::ShopPart - Controller for managing ShopParts
 
 392 ShopParts are configured in a tab of the corresponding part.
 
 398 =item C<action_update_shop>
 
 400 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.
 
 402 =item C<action_show_files>
 
 406 =item C<action_ajax_delete_file>
 
 410 =item C<action_get_categories>
 
 414 =item C<action_show_price_n_pricesource>
 
 418 =item C<action_show_stock>
 
 422 =item C<action_get_n_write_categories>
 
 424 Can be used to sync the categories of a shoppart with the categories from online.
 
 426 =item C<action_save_categories>
 
 428 The ShopwareConnector works with the CategoryID @categories[x][0] in others/new Connectors it must be tested
 
 429 Each assigned categorie is saved with id,categorie_name an multidimensional array and could be expanded with categoriepath or what is needed
 
 431 =item C<action_reorder>
 
 435 =item C<action_upload_status>
 
 439 =item C<action_mass_upload>
 
 443 =item C<action_update>
 
 447 =item C<create_or_update>
 
 451 =item C<render_shop_part_edit_dialog>
 
 453 when self->shop_part is called in template, it will be an existing shop_part with id,
 
 454 or a new shop_part with only part_id and shop_id set
 
 456 =item C<add_javascripts>
 
 459 =item C<load_pricesources>
 
 461 the price sources to use for the article: sellprice, lastcost,
 
 462 listprice, or one of the pricegroups. It overwrites the default pricesource from the shopconfig.
 
 463 TODO: implement valid pricerules for the article
 
 465 =item C<get_price_n_pricesource>
 
 471 =item C<init_shop_part>
 
 479 data for drop down filter options
 
 486 Pricesrules, pricessources aren't fully implemented yet.
 
 490 G. Richardson E<lt>information@kivitendo-premium.deE<gt>
 
 491 W. Hahn E<lt>wh@futureworldsearch.netE<gt>