Amazon-MWS
view release on metacpan or search on metacpan
lib/Amazon/MWS/Uploader.pm view on Meta::CPAN
has dbh => (is => 'lazy');
=item skus_warnings_modes
Determines how to treat warnings. This is a hash reference with the
code of the warning as key and one of the following modes as value:
=over 4
=item warn
Prints warning from Amazon with C<warn> function (default mode).
=item print
Prints warning from Amazon with C<print> function (default mode).
=item skip
Ignores warning from Amazon.
=back
=cut
has skus_warnings_modes => (is => 'rw',
isa => HashRef,
default => sub {{}},
);
=item order_days_range
When calling get_orders, check the orders for the last X days. It
accepts an integer which should be in the range 1-30. Defaults to 7.
Keep in mind that if you change the default and you have a lot of
orders, you will get throttled because for each order we retrieve the
orderline as well.
DEVEL NOTE: a possible smart fix would be to store this object in the
order (or into a closure) and make the orderline a lazy attribute
which will call C<ListOrderItems>.
=cut
has order_days_range => (is => 'rw',
default => sub { 7 },
isa => sub {
my $days = $_[0];
die "Not an integer"
unless is_Int($days);
die "$days is out of range 1-30"
unless $days > 0 && $days < 31;
});
=item shop_id
You can pass an arbitrary identifier to the constructor which will be
used to keep the database records separated if you have multiple
amazon accounts. If not provided, the merchant id will be used, which
will work, but it's harder (for the humans) to spot and debug.
=cut
has shop_id => (is => 'ro');
has _unique_shop_id => (is => 'lazy');
sub _build__unique_shop_id {
my $self = shift;
if (my $id = $self->shop_id) {
return $id;
}
else {
return $self->merchant_id;
}
}
=item debug
Print out additional information.
=item logfile
Passed to L<Amazon::MWS::Client> constructor.
=cut
has debug => (is => 'ro');
has logfile => (is => 'ro');
=item quiet
Boolean. Do not warn on timeouts and aborts (just print) if set to
true.
=cut
has quiet => (is => 'ro');
sub _build_dbh {
my $self = shift;
my $dsn = $self->db_dsn;
die "Missing dns" unless $dsn;
my $options = $self->db_options || {};
# forse raise error and auto-commit
$options->{RaiseError} = 1;
$options->{AutoCommit} = 1;
my $dbh = DBI->connect($dsn, $self->db_username, $self->db_password,
$options) or die "Couldn't connect to $dsn!";
return $dbh;
}
=item purge_missing_products
If true, the first time C<products_to_upload> is called, products not
passed to the C<products> constructor will be purged from the
C<amazon_mws_products> table. Default to false.
This setting is DEPRECATED because can have some unwanted
side-effects. You are recommended to delete the obsoleted products
yourself.
=cut
has purge_missing_products => (is => 'rw');
=item reset_all_errors
If set to a true value, don't skip previously failed items and
effectively reset all of them.
Also, when the accessor is set for send_shipping_confirmation, try to
upload again previously failed orders.
=cut
has reset_all_errors => (is => 'ro');
=item reset_errors
A string containing a comma separated list of error codes, optionally
prefixed with a "!" (to reverse its meaning).
Example:
"!6024,6023"
lib/Amazon/MWS/Uploader.pm view on Meta::CPAN
As C<products>, but no check is performed. This takes precedence.
=item sqla
Lazy attribute to hold the C<SQL::Abstract> object.
=cut
has products => (is => 'rw',
isa => ArrayRef);
has sqla => (
is => 'ro',
default => sub {
return SQL::Abstract->new;
}
);
has existing_products => (is => 'lazy');
sub _build_existing_products {
my $self = shift;
my $sth = $self->_exe_query($self->sqla->select(amazon_mws_products => [qw/sku
timestamp_string
status
listed
error_code
/],
{
status => { -not_in => [qw/deleted/] },
shop_id => $self->_unique_shop_id,
}));
my %uploaded;
while (my $row = $sth->fetchrow_hashref) {
$row->{timestamp_string} ||= 0;
$uploaded{$row->{sku}} = $row;
}
return \%uploaded;
}
has products_to_upload => (is => 'lazy');
has checked_products => (is => 'rw', isa => ArrayRef);
sub _build_products_to_upload {
my $self = shift;
if (my $checked = $self->checked_products) {
return $checked;
}
my $product_arrayref = $self->products;
return [] unless $product_arrayref && @$product_arrayref;
my @products = @$product_arrayref;
my $existing = $self->existing_products;
my @todo;
foreach my $product (@products) {
my $sku = $product->sku;
if (my $exists = $existing->{$sku}) {
# mark the item as visited
$exists->{_examined} = 1;
}
print "Checking $sku\n" if $self->debug;
next unless $self->product_needs_upload($product->sku, $product->timestamp_string);
print "Scheduling product " . $product->sku . " for upload\n";
if (my $limit = $self->limit_inventory) {
my $real = $product->inventory;
if ($real > $limit) {
print "Limiting the $sku inventory from $real to $limit\n" if $self->debug;
$product->inventory($limit);
}
}
if (my $children = $product->children) {
my @good_children;
foreach my $child (@$children) {
# skip failed children, but if the current status of
# parent is failed, and we reached this point, retry.
if (! exists $self->_force_hashref->{$child} and
$existing->{$child} and
$existing->{$sku} and
$existing->{$sku}->{status} ne 'failed' and
$existing->{$child}->{status} eq 'failed') {
print "Ignoring failed variant $child\n";
}
else {
push @good_children, $child;
}
}
$product->children(\@good_children);
}
push @todo, $product;
}
if ($self->purge_missing_products) {
# nuke the products not passed
# print Dumper($existing);
my @deletions = map { $_->{sku} }
grep { !$_->{_examined} }
values %$existing;
if (@deletions) {
$self->delete_skus(@deletions);
}
}
return \@todo;
}
=item client
An L<Amazon::MWS::Client> object, built lazily, so you don't have to
pass it.
=back
=cut
has client => (is => 'lazy');
sub _build_client {
my $self = shift;
my %mws_args = map { $_ => $self->$_ } (qw/merchant_id
marketplace_id
access_key_id
secret_key
debug
logfile
endpoint/);
return Amazon::MWS::Client->new(%mws_args);
}
has _mismatch_patterns => (is => 'lazy', isa => HashRef);
sub _build__mismatch_patterns {
my $self = shift;
my $merchant_re = qr{\s+\((?:Merchant|Verkäufer):\s+'(.*?)'\s+/};
my $amazon_re = qr{\s+.*?/\s*Amazon:\s+'(.*?)'\)};
my %patterns = (
# informative only
asin => qr{ASIN(?:\s+überein)?\s+([0-9A-Za-z]+)},
shop_part_number => qr{part_number$merchant_re},
amazon_part_number => qr{part_number$amazon_re},
shop_title => qr{item_name$merchant_re},
amazon_title => qr{item_name$amazon_re},
shop_manufacturer => qr{manufacturer$merchant_re},
amazon_manufacturer => qr{manufacturer$amazon_re},
shop_brand => qr{brand$merchant_re},
amazon_brand => qr{brand$amazon_re},
shop_color => qr{color$merchant_re},
amazon_color => qr{color$amazon_re},
shop_size => qr{size$merchant_re},
amazon_size => qr{size$amazon_re},
);
return \%patterns;
}
=head1 MAIN METHODS
=head2 upload
If the products is set, begin the routine to upload them. Because of
the asynchronous way AMWS works, at some point it will bail out,
saving the state in the database. You should reinstantiate the object
and call C<resume> on it every 10 minutes or so.
The workflow is described here:
L<http://docs.developer.amazonservices.com/en_US/feeds/Feeds_Overview.html>
This has to be done for each feed: Product, Inventory, Price, Image,
Relationship (for variants).
This method first generate the feeds in the feed directory, and then
calls C<resume>, which is in charge for the actual uploading.
=head2 resume
Restore the state and resume where it was left.
lib/Amazon/MWS/Uploader.pm view on Meta::CPAN
}
sub _condition_for_shipped_orders {
my ($self, $order) = @_;
die "Missing order" unless $order;
my %condition = (shop_id => $self->_unique_shop_id);
if (my $amazon_order_id = $order->amazon_order_id) {
$condition{amazon_order_id} = $amazon_order_id;
}
elsif (my $order_id = $order->merchant_order_id) {
$condition{shop_order_id} = $order_id;
}
else {
die "Missing amazon_order_id or merchant_order_id";
}
return \%condition;
}
=head2 orders_waiting_for_shipping
Return a list of hashref with two keys, C<amazon_order_id> and
C<shop_order_id> for each order which is waiting confirmation.
This is implemented looking into amazon_mws_orders where there is no
shipping confirmation job id.
The confirmed flag (which means we acknowledged the order) is ignored
to avoid stuck order_ack jobs to prevent the shipping confirmation.
=cut
sub orders_waiting_for_shipping {
my $self = shift;
my $sth = $self->_exe_query($self->sqla->select('amazon_mws_orders',
[qw/amazon_order_id
shop_order_id/],
{
shop_id => $self->_unique_shop_id,
shipping_confirmation_job_id => undef,
# do not stop the unconfirmed to be considered
# confirmed => 1,
}));
my @out;
while (my $row = $sth->fetchrow_hashref) {
push @out, $row;
}
return @out;
}
=head2 product_needs_upload($sku, $timestamp)
Lookup the product $sku with timestamp $timestamp and return the sku
if the product needs to be uploaded or can be safely skipped. This
method is stateless and doesn't alter anything.
=cut
sub product_needs_upload {
my ($self, $sku, $timestamp) = @_;
my $debug = $self->debug;
return unless $sku;
my $forced = $self->_force_hashref;
# if it's forced, we have nothing to check, just pass it.
if ($forced->{$sku}) {
print "Forcing $sku as requested\n" if $debug;
return $sku;
}
$timestamp ||= 0;
my $existing = $self->existing_products;
if (exists $existing->{$sku}) {
if (my $exists = $existing->{$sku}) {
my $status = $exists->{status} || '';
if ($status eq 'ok') {
if ($exists->{timestamp_string} eq $timestamp) {
return;
}
else {
return $sku;
}
}
elsif ($status eq 'redo') {
return $sku;
}
elsif ($status eq 'failed') {
if ($self->reset_all_errors) {
return $sku;
}
elsif (my $reset = $self->_reset_error_structure) {
# option for this error was passed.
my $error = $exists->{error_code};
my $match = $reset->{codes}->{$error};
if (($match && $reset->{negate}) or
(!$match && !$reset->{negate})) {
# was passed !this error or !random , so do not reset
print "Skipping failed item $sku with error code $error\n" if $debug;
return;
}
else {
# otherwise reset
print "Resetting error for $sku with error code $error\n" if $debug;
return $sku;
}
}
else {
print "Skipping failed item $sku\n" if $debug;
return;
}
}
elsif ($status eq 'pending') {
print "Skipping pending item $sku\n" if $debug;
return;
}
die "I shouldn't have reached this point with status <$status>";
}
}
print "$sku wasn't uploaded so far, scheduling it\n" if $debug;
return $sku;
}
=head2 orders_in_shipping_job($job_id)
Lookup the C<amazon_mws_orders> table and return a list of
C<amazon_order_id> for the given shipping confirmation job. INTERNAL.
=cut
sub orders_in_shipping_job {
my ($self, $job_id) = @_;
die unless $job_id;
my $sth = $self->_exe_query($self->sqla->select(amazon_mws_orders => [qw/amazon_order_id/],
{
shipping_confirmation_job_id => $job_id,
shop_id => $self->_unique_shop_id,
}));
my @orders;
while (my $row = $sth->fetchrow_hashref) {
push @orders, $row->{amazon_order_id};
}
return @orders;
}
=head2 put_product_on_error(sku => $sku, timestamp_string => $timestamp, error_code => $error_code, error_msg => $error)
Register a custom error for the product $sku with error $error and
$timestamp as the timestamp string. The error is optional, and will be
"shop error" if not provided. The error code will be 1 if not provided.
=cut
sub put_product_on_error {
my ($self, %product) = @_;
die "Missing sku" unless $product{sku};
die "Missing timestamp" unless defined $product{timestamp_string};
my %identifier = (
shop_id => $self->_unique_shop_id,
sku => $product{sku},
);
my %errors = (
status => 'failed',
error_msg => $product{error_msg} || 'shop error',
error_code => $product{error_code} || 1,
timestamp_string => $product{timestamp_string},
);
# check if we have it
my $sth = $self->_exe_query($self->sqla
->select('amazon_mws_products',
[qw/sku/], { %identifier }));
if ($sth->fetchrow_hashref) {
$sth->finish;
print "Updating $product{sku} with error $product{error_msg}\n";
$self->_exe_query($self->sqla->update('amazon_mws_products',
\%errors, \%identifier));
}
( run in 0.847 second using v1.01-cache-2.11-cpan-39bf76dae61 )