Revision 00ff2ae5
Von Werner Hahn vor mehr als 7 Jahren hinzugefügt
SL/BackgroundJob/ShopPartMassUpload.pm | ||
---|---|---|
package SL::BackgroundJob::ShopPartMassUpload;
|
||
#ShopPartMassUpload
|
||
use strict;
|
||
use warnings;
|
||
|
||
use parent qw(SL::BackgroundJob::Base);
|
||
|
||
use SL::DBUtils;
|
||
use SL::DB::ShopPart;
|
||
use SL::Shop;
|
||
|
||
use constant WAITING_FOR_EXECUTION => 0;
|
||
use constant UPLOAD_TO_WEBSHOP => 1;
|
||
use constant DONE => 2;
|
||
|
||
# Data format:
|
||
# my $data = {
|
||
# shop_part_record_ids => [ 603, 604, 605],
|
||
# num_order_created => 0,
|
||
# orders_ids => [1,2,3]
|
||
# conversation_errors => [ { id => 603 , item => 2, message => "Out of stock"}, ],
|
||
# };
|
||
|
||
sub update_webarticles {
|
||
my ( $self ) = @_;
|
||
|
||
my $job_obj = $self->{job_obj};
|
||
my $db = $job_obj->db;
|
||
|
||
$job_obj->set_data(UPLOAD_TO_WEBSHOP())->save;
|
||
|
||
foreach my $shop_part_id (@{ $job_obj->data_as_hash->{shop_part_record_ids} }) {
|
||
my $data = $job_obj->data_as_hash;
|
||
eval {
|
||
my $shop_part = SL::DB::Manager::ShopPart->find_by(id => $shop_part_id);
|
||
unless($shop_part){
|
||
push @{ $data->{conversion_errors} }, { id => $shop_part_id, number => '', message => 'Shoppart not found' };
|
||
}
|
||
|
||
my $shop = SL::Shop->new( config => $shop_part->shop );
|
||
|
||
my $part_hash = $shop_part->part->as_tree;
|
||
require SL::JSON;
|
||
|
||
my $json = SL::JSON::to_json($part_hash);
|
||
my $return = $shop->connector->update_part($shop_part, $json, $data->{todo});
|
||
if ( $return == 1 ) {
|
||
my $now = DateTime->now;
|
||
my $attributes->{last_update} = $now;
|
||
$shop_part->assign_attributes(%{ $attributes });
|
||
$shop_part->save;
|
||
}else{
|
||
push @{ $data->{conversion_errors} }, { id => $shop_part_id, number => '', message => $return };
|
||
}
|
||
1;
|
||
} or do {
|
||
push @{ $data->{conversion_errors} }, { id => $shop_part_id, number => '', message => $@ };
|
||
};
|
||
|
||
$job_obj->update_attributes(data_as_hash => $data);
|
||
}
|
||
}
|
||
|
||
sub run {
|
||
my ($self, $job_obj) = @_;
|
||
|
||
$self->{job_obj} = $job_obj;
|
||
$self->update_webarticles;
|
||
|
||
$job_obj->set_data(status => DONE())->save;
|
||
|
||
return 1;
|
||
}
|
||
1;
|
SL/Controller/ShopPart.pm | ||
---|---|---|
|
||
use parent qw(SL::Controller::Base);
|
||
|
||
use SL::BackgroundJob::ShopPartMassUpload;
|
||
use SL::System::TaskServer;
|
||
use Data::Dumper;
|
||
use SL::Locale::String qw(t8);
|
||
use SL::DB::ShopPart;
|
||
... | ... | |
use SL::Controller::FileUploader;
|
||
use SL::DB::Default;
|
||
use SL::Helper::Flash;
|
||
use MIME::Base64;
|
||
|
||
use Rose::Object::MakeMethods::Generic
|
||
(
|
||
scalar => [ qw(price_sources) ],
|
||
'scalar --get_set_init' => [ qw(shop_part file) ],
|
||
'scalar --get_set_init' => [ qw(shop_part file shops producers) ],
|
||
);
|
||
|
||
__PACKAGE__->run_before('check_auth');
|
||
__PACKAGE__->run_before('add_javascripts', only => [ qw(edit_popup) ]);
|
||
__PACKAGE__->run_before('add_javascripts', only => [ qw(edit_popup list_articles) ]);
|
||
__PACKAGE__->run_before('load_pricesources', only => [ qw(create_or_edit_popup) ]);
|
||
|
||
#
|
||
... | ... | |
my ($self) = @_;
|
||
|
||
$self->render_shop_part_edit_dialog();
|
||
};
|
||
}
|
||
|
||
sub action_update_shop {
|
||
my ($self, %params) = @_;
|
||
|
||
my $shop_part = SL::DB::Manager::ShopPart->find_by(id => $::form->{shop_part_id});
|
||
die unless $shop_part;
|
||
|
||
require SL::Shop;
|
||
my $shop = SL::Shop->new( config => $shop_part->shop );
|
||
|
||
# data to upload to shop. Goes to SL::Connector::XXXConnector.
|
||
my $part_hash = $shop_part->part->as_tree;
|
||
my $json = SL::JSON::to_json($part_hash);
|
||
my $return = $shop->connector->update_part($self->shop_part, $json);
|
||
my $return = $shop->connector->update_part($self->shop_part, $json,'all');
|
||
|
||
# the connector deals with parsing/result verification, just needs to return success or failure
|
||
if ( $return == 1 ) {
|
||
... | ... | |
$self->js->flash('error', t8('The shop part wasn\'t updated.'))->render;
|
||
};
|
||
|
||
};
|
||
}
|
||
|
||
sub action_show_files {
|
||
my ($self) = @_;
|
||
|
||
require SL::DB::File;
|
||
my $images = SL::DB::Manager::File->get_all_sorted( where => [ trans_id => $::form->{id}, modul => $::form->{modul}, file_content_type => { like => 'image/%' } ], sort_by => 'position' );
|
||
|
||
$self->render('shop_part/_list_images', { header => 0 }, IMAGES => $images);
|
||
... | ... | |
my @file_errors = $self->file->validate if $attributes->{file_content};;
|
||
push @errors,@file_errors if @file_errors;
|
||
|
||
|
||
return $self->js->error(@errors)->render($self) if @errors;
|
||
|
||
$self->file->file_update_type_and_dimensions if $attributes->{file_content};
|
||
... | ... | |
$self->js->render;
|
||
}
|
||
|
||
# old:
|
||
# sub action_edit {
|
||
# my ($self) = @_;
|
||
#
|
||
# $self->render('shop_part/edit'); #, { output => 0 }); #, price_source => $price_source)
|
||
# }
|
||
#
|
||
# used when saving existing ShopPart
|
||
|
||
sub action_update {
|
||
my ($self) = @_;
|
||
|
||
... | ... | |
|
||
my ( $price, $price_src_str ) = $self->get_price_n_pricesource($::form->{pricesource});
|
||
|
||
#TODO Price must be formatted. $price_src_str must be translated
|
||
$self->js->html('#price_' . $self->shop_part->id, $price)
|
||
$self->js->html('#price_' . $self->shop_part->id, $::form->format_amount(\%::myconfig,$price,2))
|
||
->html('#active_price_source_' . $self->shop_part->id, $price_src_str)
|
||
->render;
|
||
}
|
||
|
||
sub action_show_stock {
|
||
my ($self) = @_;
|
||
my ( $stock_local, $stock_onlineshop );
|
||
my ( $stock_local, $stock_onlineshop, $active_online );
|
||
|
||
require SL::Shop;
|
||
my $shop = SL::Shop->new( config => $self->shop_part->shop );
|
||
my $shop_article = $shop->connector->get_article($self->shop_part->part->partnumber);
|
||
|
||
if($self->shop_part->last_update) {
|
||
my $shop_article = $shop->connector->get_article($self->shop_part->part->partnumber);
|
||
$stock_onlineshop = $shop_article->{data}->{mainDetail}->{inStock};
|
||
$active_online = $shop_article->{data}->{active};
|
||
#}
|
||
|
||
$stock_local = $self->shop_part->part->onhand;
|
||
$stock_onlineshop = $shop_article->{data}->{mainDetail}->{inStock};
|
||
|
||
$self->js->html('#stock_' . $self->shop_part->id, $stock_local."/".$stock_onlineshop)
|
||
$self->js->html('#stock_' . $self->shop_part->id, $::form->format_amount(\%::myconfig,$stock_local,0)."/".$::form->format_amount(\%::myconfig,$stock_onlineshop,0))
|
||
->html('#toogle_' . $self->shop_part->id,$active_online)
|
||
->render;
|
||
}
|
||
|
||
sub action_get_n_write_categories {
|
||
my ($self) = @_;
|
||
|
||
my @shop_parts = @{ $::form->{shop_parts_ids} || [] };
|
||
foreach my $part(@shop_parts){
|
||
|
||
my $shop_part = SL::DB::Manager::ShopPart->get_all( where => [id => $part], with_objects => ['part', 'shop'])->[0];
|
||
require SL::DB::Shop;
|
||
my $shop = SL::Shop->new( config => $shop_part->shop );
|
||
my $online_article = $shop->connector->get_article($shop_part->part->partnumber);
|
||
my $online_cat = $online_article->{data}->{categories};
|
||
my @cat = ();
|
||
for(keys %$online_cat){
|
||
# The ShopwareConnector works with the CategoryID @categories[x][0] in others/new Connectors it must be tested
|
||
# Each assigned categorie is saved with id,categorie_name an multidimensional array and could be expanded with categoriepath or what is needed
|
||
my @cattmp;
|
||
push( @cattmp,$online_cat->{$_}->{id} );
|
||
push( @cattmp,$online_cat->{$_}->{name} );
|
||
push( @cat,\@cattmp );
|
||
}
|
||
my $attributes->{shop_category} = \@cat;
|
||
my $active->{active} = $online_article->{data}->{active};
|
||
$shop_part->assign_attributes(%{$attributes}, %{$active});
|
||
$shop_part->save;
|
||
}
|
||
$self->redirect_to( action => 'list_articles' );
|
||
}
|
||
|
||
sub create_or_update {
|
||
my ($self) = @_;
|
||
... | ... | |
# $self->js->val('#partnumber', 'ladida');
|
||
$self->js->html('#shop_part_description_' . $self->shop_part->id, $self->shop_part->shop_description)
|
||
->html('#shop_part_active_' . $self->shop_part->id, $self->shop_part->active)
|
||
->html('#price_' . $self->shop_part->id, $price)
|
||
->html('#price_' . $self->shop_part->id, $::form->format_amount(\%::myconfig,$price,2))
|
||
->html('#active_price_source_' . $self->shop_part->id, $price_src_str)
|
||
->run('kivi.shop_part.close_dialog')
|
||
->flash('info', t8("Updated shop part"))
|
||
... | ... | |
my ($self) = @_;
|
||
|
||
my @categories = @{ $::form->{categories} || [] };
|
||
$main::lxdebug->dump(0, 'WH: KATEGORIEN: ', \@categories);
|
||
my @cat = ();
|
||
foreach my $cat ( @categories) {
|
||
# TODO das koma macht Probleme z.B kategorie "Feldsalat, Rapunzel"
|
||
my @temp = [split(/,/,$cat)];
|
||
push( @cat, @temp );
|
||
}
|
||
$main::lxdebug->dump(0, 'WH: KAT2:',\@cat);
|
||
|
||
# The ShopwareConnector works with the CategoryID @categories[x][0] in others/new Connectors it must be tested
|
||
# Each assigned categorie is saved with id,categorie_name an multidimensional array and could be expanded with categoriepath or what is needed
|
||
my @cat = ();
|
||
foreach my $cat ( @categories) {
|
||
my @cattmp;
|
||
push( @cattmp,$cat );
|
||
push( @cattmp,$::form->{"cat_id_${cat}"} );
|
||
push( @cat,\@cattmp );
|
||
}
|
||
|
||
my $categories->{shop_category} = \@cat;
|
||
|
||
... | ... | |
|
||
sub action_reorder {
|
||
my ($self) = @_;
|
||
$main::lxdebug->message(0, "WH:REORDER ");
|
||
|
||
require SL::DB::File;
|
||
SL::DB::File->reorder_list(@{ $::form->{image_id} || [] });
|
||
$main::lxdebug->message(0, "WH:REORDER II ");
|
||
|
||
$self->render(\'', { type => 'json' });
|
||
}
|
||
|
||
sub action_list_articles {
|
||
my ($self) = @_;
|
||
|
||
my %filter = ($::form->{filter} ? parse_filter($::form->{filter}) : query => [ transferred => 0 ]);
|
||
my $transferred = $::form->{filter}->{transferred_eq_ignore_empty} ne '' ? $::form->{filter}->{transferred_eq_ignore_empty} : '';
|
||
my $sort_by = $::form->{sort_by} ? $::form->{sort_by} : 'part.partnumber';
|
||
$sort_by .=$::form->{sort_dir} ? ' DESC' : ' ASC';
|
||
$main::lxdebug->message(0, "WH:LA ");
|
||
|
||
my $articles = SL::DB::Manager::ShopPart->get_all(where => [ 'shop.obsolete' => 0 ],with_objects => [ 'part','shop' ], sort_by => $sort_by );
|
||
|
||
foreach my $article (@{ $articles}) {
|
||
my $images = SL::DB::Manager::File->get_all_count( where => [ trans_id => $article->part->id, modul => 'shop_part', file_content_type => { like => 'image/%' } ], sort_by => 'position' );
|
||
$article->{images} = $images;
|
||
}
|
||
$main::lxdebug->dump(0, 'WH:ARTIKEL ',\$articles);
|
||
|
||
$self->render('shop_part/_list_articles', title => t8('Webshops articles'), SHOP_PARTS => $articles);
|
||
}
|
||
|
||
sub action_upload_status {
|
||
my ($self) = @_;
|
||
my $job = SL::DB::BackgroundJob->new(id => $::form->{job_id})->load;
|
||
my $html = $self->render('shop_part/_upload_status', { output => 0 }, job => $job);
|
||
|
||
$self->js->html('#status_mass_upload', $html);
|
||
$self->js->run('kivi.shop_part.massUploadFinished') if $job->data_as_hash->{status} == SL::BackgroundJob::ShopPartMassUpload->DONE();
|
||
$self->js->render;
|
||
}
|
||
|
||
sub action_mass_upload {
|
||
my ($self) = @_;
|
||
$main::lxdebug->message(0, "WH:MA ");
|
||
|
||
my @shop_parts = @{ $::form->{shop_parts_ids} || [] };
|
||
|
||
my $job = SL::DB::BackgroundJob->new(
|
||
type => 'once',
|
||
active => 1,
|
||
package_name => 'ShopPartMassUpload',
|
||
)->set_data(
|
||
shop_part_record_ids => [ @shop_parts ],
|
||
todo => $::form->{upload_todo},
|
||
status => SL::BackgroundJob::ShopPartMassUpload->WAITING_FOR_EXECUTION(),
|
||
conversation_errors => [ ],
|
||
)->update_next_run_at;
|
||
$main::lxdebug->dump(0, 'WH:MA JOB ',\$job);
|
||
|
||
SL::System::TaskServer->new->wake_up;
|
||
$main::lxdebug->dump(0, 'WH:MA JOB 2',\$job);
|
||
|
||
my $html = $self->render('shop_part/_transfer_status', { output => 0 }, job => $job);
|
||
|
||
$self->js
|
||
->html('#status_mass_upload', $html)
|
||
->run('kivi.shop_part.massUploadStarted')
|
||
->render;
|
||
}
|
||
|
||
#
|
||
# internal stuff
|
||
#
|
||
... | ... | |
};
|
||
}
|
||
|
||
sub init_file {
|
||
$main::lxdebug->message(0, "WH:INIT_FILES ");
|
||
my $file = $::form->{id} ? SL::DB::File->new(id => $::form->{id})->load : SL::DB::File->new;
|
||
$main::lxdebug->dump(0, 'WH: INITFILE: ',\file);
|
||
|
||
return $file;
|
||
}
|
||
|
||
sub init_shops {
|
||
# data for drop down filter options
|
||
$main::lxdebug->message(0, "WH:INIT_SHOPS ");
|
||
|
||
require SL::DB::Shop;
|
||
my @shops_dd = [ { title => t8("all") , value =>'' } ];
|
||
my $shops = SL::DB::Mangager::Shop->get_all( where => [ obsolete => 0 ] );
|
||
my @tmp = map { { title => $_->{description}, value => $_->{id} } } @{ $shops } ;
|
||
$main::lxdebug->dump(0, 'WH:SHOPS ',\@tmp);
|
||
return @shops_dd;
|
||
|
||
}
|
||
|
||
sub init_producers {
|
||
# data for drop down filter options
|
||
$main::lxdebug->message(0, "WH:INIT_PRODUCERS ");
|
||
|
||
my @producers_dd = [ { title => t8("all") , value =>'' } ];
|
||
return @producers_dd;
|
||
|
||
}
|
||
|
||
1;
|
||
|
||
__END__
|
||
... | ... | |
|
||
=item C<action_update_shop>
|
||
|
||
To be called from the "Update" button, for manually syncing a part with its shop. Generates a Calls some ClientJS functions to modifiy original page.
|
||
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.
|
||
|
||
=item C<action_get_n_write_categories>
|
||
|
||
Can be used to sync the categories of a shoppart with the categories from online.
|
||
|
||
=head1 AUTHORS
|
||
|
||
... | ... | |
W. Hahn E<lt>wh@futureworldsearch.netE<gt>
|
||
|
||
=cut
|
||
|
||
=cut
|
||
1;
|
SL/DB/ShopPart.pm | ||
---|---|---|
# Feel free to modify it at will; it will not be overwritten automatically.
|
||
|
||
package SL::DB::ShopPart;
|
||
#package SL::DB::ShopPart;
|
||
|
||
use strict;
|
||
|
SL/ShopConnector/Shopware.pm | ||
---|---|---|
use LWP::Authen::Digest;
|
||
use SL::DB::ShopOrder;
|
||
use SL::DB::ShopOrderItem;
|
||
use SL::DB::History;
|
||
use SL::DB::File;
|
||
use Data::Dumper;
|
||
use Sort::Naturally ();
|
||
use SL::Helper::Flash;
|
||
... | ... | |
$pos_insert->save;
|
||
$position++;
|
||
}
|
||
$shop_order->{positions} = $position;
|
||
$shop_order->{positions} = $position-1;
|
||
|
||
# Only Customers which are not found will be applied
|
||
my $proposals = SL::DB::Manager::Customer->get_all_count(
|
||
... | ... | |
'fax' => $shop_order->billing_fax,
|
||
'phone' => $shop_order->billing_phone,
|
||
'ustid' => $shop_order->billing_vat,
|
||
'taxincluded_checked' => 1, # TODO hardcoded
|
||
'taxincluded' => 1, # TODO hardcoded
|
||
'klass' => 908, # TODO hardcoded
|
||
'taxzone_id' => 4, # TODO hardcoded, use default taxzone instead
|
||
'taxincluded_checked' => $self->config->pricetype eq "brutto" ? 1 : 0,
|
||
'taxincluded' => $self->config->pricetype eq "brutto" ? 1 : 0,
|
||
'klass' => (split '\/',$self->config->price_source)[1],
|
||
'taxzone_id' => $self->config->taxzone_id,
|
||
'currency' => 1, # TODO hardcoded
|
||
'payment_id' => 7345,# TODO hardcoded
|
||
);
|
||
... | ... | |
$shipping_pos_insert->save;
|
||
|
||
}
|
||
my $attributes->{last_order_number} = $ordnumber;
|
||
$self->config->assign_attributes( %{ $attributes } );
|
||
$self->config->save;
|
||
$ordnumber++;
|
||
# EOT Versandkosten DF
|
||
my $attributes->{last_order_number} = $ordnumber;
|
||
$self->config->assign_attributes( %{ $attributes } );
|
||
$self->config->save;
|
||
$ordnumber++;
|
||
}
|
||
}
|
||
my $shop = $self->config->description;
|
||
... | ... | |
}
|
||
|
||
sub update_part {
|
||
my ($self, $shop_part, $json) = @_;
|
||
my ($self, $shop_part, $json, $todo) = @_;
|
||
|
||
#shop_part is passed as a param
|
||
die unless ref($shop_part) eq 'SL::DB::ShopPart';
|
||
... | ... | |
$taxrate = @$rate[0]->{taxrate}*100;
|
||
|
||
# mapping to shopware still missing attributes,metatags
|
||
my %shop_data = ( name => $part->{description},
|
||
tax => $taxrate,
|
||
mainDetail => { number => $part->{partnumber},
|
||
inStock => $part->{onhand},
|
||
prices => [ { from => 1,
|
||
price => $price,
|
||
customerGroupKey => 'EK',
|
||
},
|
||
],
|
||
},
|
||
supplier => $cvars->{freifeld_7}->{value},
|
||
descriptionLong => $shop_part->{shop_description},
|
||
active => $shop_part->active,
|
||
images => [ @upload_img ],
|
||
__options_images => { replace => 1, },
|
||
categories => [ @cat ],
|
||
description => $shop_part->{shop_description},
|
||
active => $shop_part->active,
|
||
images => [ @upload_img ],
|
||
__options_images => { replace => 1, },
|
||
categories => [ @cat ], #{ path => 'Deutsch|test2' }, ], #[ $categories ],
|
||
|
||
)
|
||
;
|
||
my %shop_data;
|
||
$main::lxdebug->dump(0, 'WH:TODO ',\$todo);
|
||
|
||
if($todo eq "price"){
|
||
%shop_data = ( mainDetail => { number => $part->{partnumber},
|
||
prices => [ { from => 1,
|
||
price => $price,
|
||
customerGroupKey => 'EK',
|
||
},
|
||
],
|
||
},
|
||
);
|
||
}elsif($todo eq "stock"){
|
||
%shop_data = ( mainDetail => { number => $part->{partnumber},
|
||
inStock => $part->{onhand},
|
||
},
|
||
);
|
||
}elsif($todo eq "price_stock"){
|
||
%shop_data = ( mainDetail => { number => $part->{partnumber},
|
||
inStock => $part->{onhand},
|
||
prices => [ { from => 1,
|
||
price => $price,
|
||
customerGroupKey => 'EK',
|
||
},
|
||
],
|
||
},
|
||
);
|
||
}elsif($todo eq "active"){
|
||
%shop_data = ( mainDetail => { number => $part->{partnumber},
|
||
},
|
||
active => ($part->{partnumber} == 1 ? 0 : 1),
|
||
);
|
||
}elsif($todo eq "all"){
|
||
# mapping to shopware still missing attributes,metatags
|
||
%shop_data = ( name => $part->{description},
|
||
tax => $taxrate,
|
||
mainDetail => { number => $part->{partnumber},
|
||
inStock => $part->{onhand},
|
||
active => $shop_part->active,
|
||
prices => [ { from => 1,
|
||
price => $price,
|
||
customerGroupKey => 'EK',
|
||
},
|
||
],
|
||
},
|
||
supplier => $cvars->{freifeld_7}->{value},
|
||
descriptionLong => $shop_part->{shop_description},
|
||
active => $shop_part->active,
|
||
images => [ @upload_img ],
|
||
__options_images => { replace => 1, },
|
||
categories => [ @cat ],
|
||
description => $shop_part->{shop_description},
|
||
active => $shop_part->active,
|
||
images => [ @upload_img ],
|
||
__options_images => { replace => 1, },
|
||
categories => [ @cat ],
|
||
);
|
||
}else{
|
||
my %shop_data = ( mainDetail => { number => $part->{partnumber}, });
|
||
}
|
||
$main::lxdebug->dump(0, 'WH:SHOPDATA ',\%shop_data);
|
||
|
||
my $dataString = SL::JSON::to_json(\%shop_data);
|
||
$dataString = encode_utf8($dataString);
|
||
|
||
... | ... | |
#update
|
||
my $partnumber = $::form->escape($part->{partnumber});#shopware dosn't accept / in articlenumber
|
||
my $upload = $self->connector->put("http://$url/api/articles/$partnumber?useNumberAsId=true",Content => $dataString);
|
||
$main::lxdebug->dump(0, 'WH:iUPLOAD ',\$upload);
|
||
|
||
my $data_json = $upload->content;
|
||
$upload_content = SL::JSON::decode_json($data_json);
|
||
}else{
|
||
... | ... | |
my $data_json = $upload->content;
|
||
$upload_content = SL::JSON::decode_json($data_json);
|
||
}
|
||
# Don't know if this is needed
|
||
if(@upload_img) {
|
||
my $partnumber = $::form->escape($part->{partnumber});#shopware don't accept / in articlenumber
|
||
$self->connector->put("http://$url/api/generateArticleImages/$partnumber?useNumberAsId=true");
|
menus/user/10-shopimport.yaml | ||
---|---|---|
action: ShopOrder/list
|
||
db: shop_orders
|
||
sort_by: shop_ordernumber
|
||
- parent: webshop
|
||
id: webshop_articles
|
||
name: Webshop articles
|
||
params:
|
||
action: ShopPart/list_articles
|
||
db: shop_parts
|
||
sort_by: part.onhand
|
||
#ShopPart
|
templates/webpages/ic/tabs/_shop.html | ||
---|---|---|
<th>[% LxERP.t8("Action") %]</th>
|
||
</tr>
|
||
</thead>
|
||
|
||
[%- FOREACH shop_part = SHOP_PARTS %]
|
||
<tr class="listrow">
|
||
<td>[% HTML.escape( shop_part.shop.description ) %]</td>
|
||
... | ... | |
<td></td>
|
||
<td></td>
|
||
<td></td>
|
||
<td></td>
|
||
<td></td>
|
||
<td></td>
|
||
<td>[% L.button_tag("kivi.shop_part.create_shop_part(" _ id _ ", " _ shop.id _ ")", LxERP.t8("Add")) %]</td>
|
||
<td></td>
|
||
</tr>
|
||
... | ... | |
</p>
|
||
</div>
|
||
[%- END %]
|
||
<script type="text/javascript">
|
||
$(function() {
|
||
kivi.shop_part.show_images([% shop_part.part_id %]);
|
||
});
|
||
</script>
|
||
<div id="shop_images" border=1 >
|
||
</div>
|
||
|
||
<div id="shop_variables">
|
||
[% LxERP.t8("Active shops:") %]
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>[% LxERP.t8("Shop") %]</th>
|
||
<th>[% LxERP.t8("Active") %]</th>
|
||
<th>[% LxERP.t8("Shop part") %]</th>
|
||
<th>[% LxERP.t8("Last update") %]</th>
|
||
<th>[% LxERP.t8("Action") %]</th>
|
||
<th>[% LxERP.t8("Action") %]</th>
|
||
</tr>
|
||
</thead>
|
||
[%- FOREACH shop_part = SHOP_PARTS %]
|
||
<tr>
|
||
<td>[% HTML.escape( shop_part.shop.description ) %]</td>
|
||
<td>[% L.html_tag('span', shop_part.active, id => 'shop_part_active_' _ shop_part.id ) %]</td>
|
||
<td>[% L.html_tag('span', shop_part.shop_description, id => 'shop_part_description_' _ shop_part.id ) %]</td>
|
||
<td>[% L.html_tag('span', shop_part.last_update.to_kivitendo('precision' => 'minute'), id => 'shop_part_last_update_' _ shop_part.id ) %]</td>
|
||
<td>[% L.button_tag("kivi.shop_part.edit_shop_part(" _ shop_part.id _ ")", LxERP.t8("Edit")) %]</td>
|
||
<td>[% L.button_tag("kivi.shop_part.update_shop_part(" _ shop_part.id _ ")", LxERP.t8("Update")) %]</td>
|
||
<td>[% L.button_tag("kivi.shop_part.get_all_categories(" _ shop_part.id _ ")", LxERP.t8("Shopcategories")) %]</td>
|
||
</tr>
|
||
[%- END %]
|
||
[%- FOREACH shop = SHOPS_NOT_ASSIGNED %]
|
||
<tr>
|
||
<td>[% HTML.escape( shop.description ) %]</td>
|
||
<td></td>
|
||
<td></td>
|
||
<td></td>
|
||
<td>[% L.button_tag("kivi.shop_part.create_shop_part(" _ id _ ", " _ shop.id _ ")", LxERP.t8("Add")) %]</td>
|
||
<td></td>
|
||
</tr>
|
||
[%- END %]
|
||
</table>
|
||
|
||
|
||
[% # L.dump(shop_part) %]
|
||
<hr>
|
||
[%- IF CUSTOM_VARIABLES.size %]
|
||
<div id="shop_custom_variables">
|
||
<p>
|
||
<table>
|
||
[%- FOREACH var = CUSTOM_VARIABLES %]
|
||
[%- IF var.name.match('^shop_') %]
|
||
<tr>
|
||
[%- IF !var.partsgroup_filtered %]
|
||
<td align="right" valign="top">[% HTML.escape(var.description) %]</td>
|
||
[%- END %]
|
||
<td valign="top">[% var.HTML_CODE %]</td>
|
||
</tr>
|
||
[%- END %]
|
||
[%- END %]
|
||
</table>
|
||
</p>
|
||
</div>
|
||
[%- IF shop_part.part_id %]
|
||
<script type="text/javascript">
|
||
$(function() {
|
||
kivi.shop_part.show_images([% shop_part.part_id %]);
|
||
});
|
||
</script>
|
||
<div id="shop_images" border=1 ></div>
|
||
[%- ELSE %]
|
||
<div id="shop_images" border=1 >[% LxERP.t8('To upload images: Please create shoppart first') %]</div>
|
||
[%- END %]
|
||
<script type="text/javascript">
|
||
function show_images() {
|
||
var url = 'controller.pl?action=ShopPart/show_files&modul=shop_part&id=[% shop_part.part_id %]';
|
||
$('#shop_images').load(url);
|
||
}
|
||
|
||
$(window).load(function() {
|
||
show_images();
|
||
});
|
||
</script>
|
||
<div id="shop_images" border=1 >
|
||
</div>
|
||
</div>
|
||
|
templates/webpages/shop_part/_filter.html | ||
---|---|---|
[%- USE T8 %]
|
||
[%- USE L %]
|
||
[%- USE LxERP %]
|
||
[%- USE HTML %]
|
||
<!-- ShopOrder -->
|
||
<form action='controller.pl' method='post' id="shop_part_filter">
|
||
<table id='filter_table'>
|
||
|
||
<tr>
|
||
<th align="right">[% 'Shop' | $T8 %]</th>
|
||
<td>[% L.select_tag('filter.transferred:eq_ignore_empty', SELF.shop, value_key = 'value', title_key = 'title', default=SHOPS) %]</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<th align="right">[% 'Producer' | $T8 %]</th>
|
||
<td>[% L.select_tag('filter.transferred:eq_ignore_empty', SELF.producer, value_key = 'value', title_key = 'title', default=PRODUCERS) %]</td>
|
||
</tr>
|
||
|
||
<!-- <tr>
|
||
<th align="right">[% 'from' | $T8 %]</th>
|
||
<td>[% # L.date_tag('filter.order_date:date::ge', filter.order_date_date__ge) %]</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<th align="right">[% 'to' | $T8 %]</th>
|
||
<td>[% # L.date_tag('filter.order_date:date::le', filter.order_date_date__le) %]</td>
|
||
</tr> -->
|
||
|
||
</table>
|
||
|
||
|
||
<a href='#' onClick='javascript:$("#filter_table input").val("");$("#filter_table input[type=checkbox]").prop("checked", 0);'>[% 'Reset' | $T8 %]</a>
|
||
<br>
|
||
|
||
[% L.hidden_tag('action', 'ShopPart/dispatch') %]
|
||
[% L.submit_tag('action_list_articles',LxERP.t8('renew')) %]
|
||
|
||
</form>
|
templates/webpages/shop_part/_list_articles.html | ||
---|---|---|
[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%]
|
||
[% USE Dumper %]
|
||
<!-- ShopPart -->
|
||
<!-- ShopPart -->
|
||
<!-- ShopPart -->
|
||
<h1>[% title %]</h1>
|
||
[%- PROCESS 'shop_part/_filter.html' filter=SELF.models.filtered.laundered %]
|
||
<hr>
|
||
<form method="post" action="controller.pl" name="shop_parts" id="shopparts">
|
||
<div class="data_count">[% 'Number of Data: ' | $T8 %][% SHOP_PARTS.size %]</div>
|
||
<table id="shoplist" width="100%" >
|
||
<thead>
|
||
<tr class="listheading">
|
||
<th>[% L.checkbox_tag('check_all') %]</th>
|
||
<th>[% IF FORM.sort_by == 'shop.description' %]
|
||
<a href ="controller.pl?action=ShopPart/list_articles&sort_by=shop.description&sort_dir=[% 1 - FORM.sort_dir %]" class="sort_link">
|
||
[% 'Shop Host/Connector' | $T8 %][% IF FORM.sort_dir == 0 %]<img border="0" src="image/down.png">[% ELSE %]<img border="0" src="image/up.png">[% END %]</a>
|
||
[% ELSE %]
|
||
<a href ="controller.pl?action=ShopPart/list_articles&sort_by=shop.description&sort_dir=0" class="sort_link">
|
||
[% 'Shop Host/Connector' | $T8 %]</a>
|
||
[% END %]
|
||
</th>
|
||
<th>[% IF FORM.sort_by == 'part.partnumber' %]
|
||
<a href ="controller.pl?action=ShopPart/list_articles&sort_by=part.partnumber&sort_dir=[% 1 - FORM.sort_dir %]" class="sort_link">
|
||
[% 'Partnumber' | $T8 %][% IF FORM.sort_dir == 0 %]<img border="0" src="image/down.png">[% ELSE %]<img border="0" src="image/up.png">[% END %]</a>
|
||
[% ELSE %]
|
||
<a href ="controller.pl?action=ShopPart/list_articles&sort_by=part.partnumber&sort_dir=0" class="sort_link">
|
||
[% 'Partnumber' | $T8 %]</a>
|
||
[% END %]
|
||
</th>
|
||
<th>[% IF FORM.sort_by == 'part.description' %]
|
||
<a href ="controller.pl?action=ShopPart/list_articles&sort_by=part.description&sort_dir=[% 1 - FORM.sort_dir %]" class="sort_link">
|
||
[% 'Description' | $T8 %][% IF FORM.sort_dir == 0 %]<img border="0" src="image/down.png">[% ELSE %]<img border="0" src="image/up.png">[% END %]</a>
|
||
[% ELSE %]
|
||
<a href ="controller.pl?action=ShopPart/list_articles&sort_by=part.description&sort_dir=0" class="sort_link">
|
||
[% 'Description' | $T8 %]</a>
|
||
[% END %]
|
||
</th>
|
||
<th>[% 'Info' | $T8 %]</th>
|
||
<th>[% 'Active' | $T8 %]</th>
|
||
<th>[% 'Price source' | $T8 %]</th>
|
||
<th>[% 'Price' | $T8 %]</th>
|
||
<th>[% IF FORM.sort_by == 'part.onhand' %]
|
||
<a href ="controller.pl?action=ShopPart/list_articles&sort_by=part.onhand&sort_dir=[% 1 - FORM.sort_dir %]" class="sort_link">
|
||
[% 'Stock Local/Shop' | $T8 %][% IF FORM.sort_dir == 0 %]<img border="0" src="image/down.png">[% ELSE %]<img border="0" src="image/up.png">[% END %]</a>
|
||
[% ELSE %]
|
||
<a href ="controller.pl?action=ShopPart/list_articles&sort_by=part.onhand&sort_dir=0" class="sort_link">
|
||
[% 'Stock Local/Shop' | $T8 %]</a>
|
||
[% END %]
|
||
</th>
|
||
<th>[% 'Last update' | $T8 %]</th>
|
||
<th>[% 'Images' | $T8 %]</th>
|
||
<th>[% IF FORM.sort_by == 'part.partsgroup_id' %]
|
||
<a href ="controller.pl?action=ShopPart/list_articles&sort_by=part.partsgroup_id&sort_dir=[% 1 - FORM.sort_dir %]" class="sort_link">
|
||
[% 'Category' %][% IF FORM.sort_dir == 0 %]<img border="0" src="image/down.png">[% ELSE %]<img border="0" src="image/up.png">[% END %]</a>
|
||
[% ELSE %]
|
||
<a href ="controller.pl?action=ShopPart/list_articles&sort_by=part.partsgroup_id&sort_dir=0" class="sort_link">
|
||
[% 'Category' | $T8 %]</a>
|
||
[% END %]
|
||
</th>
|
||
<th>[% IF FORM.sort_by == 'part.microfiche' %]
|
||
<a href ="controller.pl?action=ShopPart/list_articles&sort_by=part.microfiche&sort_dir=[% 1 - FORM.sort_dir %]" class="sort_link">
|
||
[% 'Producer' | $T8 %][% IF FORM.sort_dir == 0 %]<img border="0" src="image/down.png">[% ELSE %]<img border="0" src="image/up.png">[% END %]</a>
|
||
[% ELSE %]
|
||
<a href ="controller.pl?action=ShopPart/list_articles&sort_by=part.microfiche&sort_dir=0" class="sort_link">
|
||
[% 'Producer' | $T8 %]</a>
|
||
[% END %]
|
||
</th>
|
||
<th>[% 'Action' | $T8 %]</th>
|
||
</tr>
|
||
</thead>
|
||
[%- FOREACH shop_part = SHOP_PARTS %]
|
||
[%- # IF shop_part.shop.obsolete %]
|
||
<tr class="listrow">
|
||
<td>[% L.checkbox_tag('shop_parts_ids[]', checked=0, value=shop_part.id) %]</td>
|
||
<td>[% HTML.escape( shop_part.shop.description ) %]/[% HTML.escape( shop_part.shop.connector ) %]</td>
|
||
<td>[% HTML.escape( shop_part.part.partnumber ) %]</td>
|
||
<td><a href="ic.pl?id=[% shop_part.part.id %]&action=edit&callback=[% HTML.url('controller.pl?action=ShopPart/list_articles') %]#shop_variables">[% HTML.escape( shop_part.part.description ) %]</a></td>
|
||
<td>
|
||
[% IF shop_part.shop_description %]
|
||
[% 'Info' | $T8 %]
|
||
[% ELSE %]
|
||
[% 'No Shopdescription' | $T8 %]
|
||
[% END %]
|
||
</td>
|
||
<td style="vertical-align:middle;text-align:center;">
|
||
[% IF shop_part.active %]
|
||
<div id="toogle_[% shop_part.id %]" style="background-image:url(image/gruener_punkt.gif);background-repeat:no-repeat;witdh:15px;height:15px;" onclick="kivi.shop_part.part_toggle_active([% shop_part.id %] ,[% shop_part.active %]);" onMouseOver="this.style.cursor='pointer'"> </div>
|
||
[% ELSE %]
|
||
<div id="toogle_[% shop_part.id %]" style="background-image:url(image/roter_punkt.gif);background-repeat:no-repeat;witdh:15px;height:15px;" onclick="kivi.shop_part.part_toggle_active([% shop_part.id %] ,[% shop_part.active %]);" onMouseOver="this.style.cursor='pointer'"> </div>
|
||
[% END %]
|
||
</td>
|
||
<td>[% L.html_tag('span',LxERP.t8(), id => 'active_price_source_' _ shop_part.id) %] </td>
|
||
<td>[% L.html_tag('span','Price', id => 'price_' _ shop_part.id) %]</td>
|
||
<td>[% L.html_tag('span','Stock', id => 'stock_' _ shop_part.id) %]</td>
|
||
<td>[% L.html_tag('span', shop_part.last_update.to_kivitendo('precision' => 'minute'), id => 'shop_part_last_update_' _ shop_part.id ) %]</td>
|
||
<td>
|
||
[% IF shop_part.images %]
|
||
[% shop_part.images %]
|
||
[% ELSE %]
|
||
[% 'No Shopimages' | $T8 %]
|
||
[% END %]
|
||
</td>
|
||
<td>
|
||
[% IF shop_part.shop_category %]
|
||
[% IF shop_part.shop_category.1.size > 1%]
|
||
[% FOREACH cat = shop_part.shop_category %]
|
||
[% HTML.escape(cat.1) %]<br>
|
||
[% END %]
|
||
[% ELSE %]
|
||
[% HTML.escape(shop_part.shop_category.1) %]<br>
|
||
[% END %]
|
||
[% END %]
|
||
</td>
|
||
<td>[% HTML.escape( shop_part.part.microfiche ) %]</td>
|
||
<td></td>
|
||
<script type="text/javascript">
|
||
$(function() {
|
||
kivi.shop_part.update_price_n_price_source([% shop_part.id %],'[% shop_part.active_price_source %]');
|
||
kivi.shop_part.update_stock([% shop_part.id %]);
|
||
});
|
||
</script>
|
||
</tr>
|
||
[%- # END %]
|
||
[%- END %]
|
||
</table>
|
||
|
||
<hr>
|
||
<div>
|
||
[% L.radio_button_tag('upload_todo', value='all', label= LxERP.t8('All Data')) %]
|
||
[% L.radio_button_tag('upload_todo', value='price', label= LxERP.t8('Only Price')) %]
|
||
[% L.radio_button_tag('upload_todo', value='stock', label= LxERP.t8('Only Stock')) %]
|
||
[% L.radio_button_tag('upload_todo', value='price_stock', checked=1, label= LxERP.t8('Price and Stock')) %]
|
||
[% L.button_tag("kivi.shop_part.setup();", LxERP.t8("Upload all marked"), id="mass_transfer") %]
|
||
</div>
|
||
<div id="status_mass_upload" style="display: none;">
|
||
[%- INCLUDE 'shop_part/_upload_status.html' %]
|
||
</div>
|
||
</form>
|
||
<hr>
|
||
<script type="text/javascript">
|
||
<!--
|
||
|
||
$(function() {
|
||
$('#check_all').checkall('INPUT[name^="shop_parts_ids"]');
|
||
});
|
||
-->
|
||
</script>
|
templates/webpages/shop_part/_transfer_status.html | ||
---|---|---|
[%- USE LxERP -%][%- USE L -%][%- USE HTML -%]
|
||
[%- USE Dumper -%]
|
||
[% SET data = job.data_as_hash %]
|
||
|
||
<!-- ShopOrder BGJ -->
|
||
<h2>[% LxERP.t8("Watch status") %]</h2>
|
||
|
||
[% L.hidden_tag('', job.id, id="smu_job_id") %]
|
||
|
||
JOBID: [% job.id %] <p>
|
||
[% LxERP.t8("This status output will be refreshed every five seconds.") %]
|
||
</p>
|
||
<p>
|
||
<table>
|
||
<tr>
|
||
<th valign="top" align="left">[% LxERP.t8("Current status:") %]</th>
|
||
<td valign="top">
|
||
[% IF !data.status %]
|
||
[% LxERP.t8("waiting for job to be started") %]
|
||
[% ELSIF data.status == 1 %]
|
||
[% LxERP.t8("Creating orders") %]
|
||
[% ELSE %]
|
||
[% LxERP.t8("Done.") %]
|
||
[% END %]
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th valign="top" align="left">[% LxERP.t8("Number of orders created:") %]</th>
|
||
<td valign="top">[% IF data.status > 0 %][% HTML.escape(data.num_created) %] / [% HTML.escape(data.record_ids.size) %][% ELSE %]–[% END %]</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<th valign="top" align="left">[% LxERP.t8("Errors during conversion:") %]</th>
|
||
<td valign="top">
|
||
[% IF !data.status %]
|
||
–
|
||
[% ELSIF !data.conversion_errors.size %]
|
||
[% LxERP.t8("No errors have occurred.") %]
|
||
[% ELSE %]
|
||
<table>
|
||
<tr class="listheader">
|
||
<th>[% LxERP.t8("Shop Order") %]</th>
|
||
<th>[% LxERP.t8("Error") %]</th>
|
||
</tr>
|
||
|
||
[% FOREACH error = data.conversion_errors %]
|
||
<tr>
|
||
<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>
|
||
<td valign="top">[% HTML.escape(error.message) %]</td>
|
||
</tr>
|
||
[% END %]
|
||
</table>
|
||
[% END %]
|
||
</table>
|
||
</p>
|
templates/webpages/shop_part/_upload_status.html | ||
---|---|---|
[%- USE LxERP -%][%- USE L -%][%- USE HTML -%]
|
||
[%- USE Dumper -%]
|
||
[% SET data = job.data_as_hash %]
|
||
|
||
|
||
<!-- ShopPart BGJ -->
|
||
<h2>[% LxERP.t8("Watch status") %]</h2>
|
||
|
||
[% L.hidden_tag('', job.id, id="smu_job_id") %]
|
||
|
||
JOBID: [% job.id %] <p>
|
||
[% LxERP.t8("This status output will be refreshed every five seconds.") %]
|
||
</p>
|
||
<p>
|
||
<table>
|
||
<tr>
|
||
<th valign="top" align="left">[% LxERP.t8("Current status:") %]</th>
|
||
<td valign="top">
|
||
[% IF !data.status %]
|
||
[% LxERP.t8("waiting for job to be started") %]
|
||
[% ELSIF data.status == 1 %]
|
||
[% LxERP.t8("Creating orders") %]
|
||
[% ELSE %]
|
||
[% LxERP.t8("Done.") %]
|
||
[% END %]
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th valign="top" align="left">[% LxERP.t8("Number of orders created:") %]</th>
|
||
<td valign="top">[% IF data.status > 0 %][% HTML.escape(data.num_created) %] / [% HTML.escape(data.record_ids.size) %][% ELSE %]–[% END %]</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<th valign="top" align="left">[% LxERP.t8("Errors during conversion:") %]</th>
|
||
<td valign="top">
|
||
[% IF !data.status %]
|
||
–
|
||
[% ELSIF !data.conversion_errors.size %]
|
||
[% LxERP.t8("No errors have occurred.") %]
|
||
[% ELSE %]
|
||
<table>
|
||
<tr class="listheader">
|
||
<th>[% LxERP.t8("Shop Order") %]</th>
|
||
<th>[% LxERP.t8("Error") %]</th>
|
||
</tr>
|
||
|
||
[% FOREACH error = data.conversion_errors %]
|
||
<tr>
|
||
<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>
|
||
<td valign="top">[% HTML.escape(error.message) %]</td>
|
||
</tr>
|
||
[% END %]
|
||
</table>
|
||
[% END %]
|
||
</table>
|
||
</p>
|
templates/webpages/shop_part/categories.html | ||
---|---|---|
[%- USE P -%]
|
||
[%- USE LxERP -%]
|
||
[%- USE Dumper -%]
|
||
|
||
<!-- ShopPart -->
|
||
[% LxERP.t8("Part") %]: [% HTML.escape(SELF.shop_part.part.displayable_name) %]<br>
|
||
[% LxERP.t8("Shop") %]: [% HTML.escape(SELF.shop_part.shop.description) %]
|
||
<br>
|
templates/webpages/shop_part/edit.html | ||
---|---|---|
[%- USE LxERP -%]
|
||
[%- USE Dumper -%]
|
||
|
||
<!-- ShopPart -->
|
||
<!-- ShopPart -->
|
||
[% LxERP.t8("Part") %]: [% HTML.escape(SELF.shop_part.part.displayable_name) %]<br>
|
||
[% LxERP.t8("Shop") %]: [% HTML.escape(SELF.shop_part.shop.description) %]
|
||
[% Dumper.dump_html(SELF) %]
|
Auch abrufbar als: Unified diff
Shopmodul: ShopPart: Artikel hochladen und Anzeigen
Webshop:: ShopPart
Webshop:: ShopPart
WebShop: ShopPart - Formatierungen ShopPart
Meldung bei Bildern wenn der Artikel noch keinem Shop zugeordnet ist
Conflicts:
templates/webpages/ic/tabs/_shop.html
Webshop: ShopPart Backgroundjob Upload
Conflicts:
SL/BackgroundJob/ShopPartMassUpload.pm
SL/ShopConnector/Shopware.pm
menus/user/10-shopimport.yaml
templates/webpages/shop_part/_list_articles.html
templates/webpages/shop_part/_transfer_status.html
templates/webpages/shop_part/_upload_status.html
Webshop: ShopPart - Webpart Filter nach Anbauer und Shop Anfang
Conflicts:
SL/Controller/ShopPart.pm
Webshop: ShopPart Menu
Conflicts:
menus/user/10-shopimport.yaml
Webshop: ShopPart
Webshop: ShopPart - Mergefehler in templates/webpages/ic/tabs/_shop.html
Conflicts:
templates/webpages/ic/tabs/_shop.html
Webshop: ShopPart
Conflicts:
templates/webpages/shop_part/_filter.html
WebShop: ShopPart - Unterschiedliche Uploadmöglichkeiten (Alles, Nur Preis, Nur Bestand, Bestand und Preis)
Conflicts:
menus/user/10-shopimport.yaml
Conflicts:
SL/Controller/ShopPart.pm
SL/ShopConnector/Shopware.pm
templates/webpages/shop_part/_list_articles.html
WebShop: ShopPart - Shopartikelliste Aktiv grafik
Conflicts:
SL/Controller/ShopPart.pm
SL/ShopConnector/Shopware.pm
js/kivi.shop_part.js
t/background_job/known_jobs.t
Conflicts:
SL/Controller/ShopPart.pm
SL/DB/ShopPart.pm
templates/webpages/ic/tabs/_shop.html