package webshop::Keeper; use strict; use warnings 'all'; use base qw(Contenido::Keeper); use Data::Dumper; use Contenido::Globals; use webshop::Basket; use webshop::SQL::Basket; sub add_item { my $self = shift; my $object = shift; my (%options) = @_; return unless ref $object; return unless $object->item_id && $object->number; return unless $object->uid || $object->session; my %opts; if ( $object->uid ) { $opts{uid} = $object->uid; } elsif ( $object->session ) { $opts{session} = $object->session; } my @items = $keeper->get_documents ( class => 'webshop::Basket', status => 1, order_id=> 0, %opts, ); my $total = 0; my $sum = 0; my $found = 0; if ( @items ) { foreach my $item ( @items ) { if ( $object->item_id == $item->item_id && $object->color_id == $item->color_id && ((!$object->size_id && !$object->size) || ($object->size_id && $object->size_id == $item->size_id) || ($object->size && $object->size eq $item->size)) ) { $item->number( $item->number + $object->number ); $item->price( $object->price ); $item->store; $found = 1; } $total += $item->number; $sum += $item->number * $item->price; } } unless ( $found ) { $total += $object->number; $sum += $object->number * $object->price; $object->order_id(0); $object->store; } my @plugins = split (/[\ |\t]+/, $state->{plugins}); if ( grep { $_ eq 'session' } @plugins && ref $session ) { $session->set ( basket_total => $total, basket_sum => $sum ); } return ($total, $sum); } sub add_wishlist { my $self = shift; my $object = shift; my (%options) = @_; return unless ref $object; return unless $object->item_id && $object->number; return unless $object->uid || $object->session; my %opts; if ( $object->uid ) { $opts{uid} = $object->uid; } elsif ( $object->session ) { $opts{session} = $object->session; } my @items = $keeper->get_documents ( class => 'webshop::Basket', status => 0, order_id=> 0, %opts, ); my $total = 0; my $sum = 0; my $found = 0; if ( @items ) { foreach my $item ( @items ) { if ( $object->item_id == $item->item_id && $object->color_id == $item->color_id && ((!$object->size_id && !$object->size) || ($object->size_id && $object->size_id == $item->size_id) || ($object->size && $object->size eq $item->size)) ) { $item->number($item->number + $object->number); $item->store; $found = 1; } $total += $item->number; $sum += $item->number * $item->price; } } unless ( $found ) { $total += $object->number; $sum += $object->number * $object->price; $object->order_id(0); $object->store; } return ($total, $sum); } ### Метод приведения корзины для пользователя в момент логина ############################################################# sub merge_basket { my $self = shift; my (%opts) = @_; warn "Merge begin: ".Dumper(\%opts)."\n" if $DEBUG; return unless $opts{uid} && $opts{session}; my @items_session = $keeper->get_documents ( class => 'webshop::Basket', status => [1,0], session => $opts{session}, order_id => 0, ); my @items_user = $keeper->get_documents ( class => 'webshop::Basket', status => [1,0], order_id=> 0, uid => $opts{uid}, ); my ($basket_total, $basket_sum, $wishlist_total, $wishlist_sum) = (0,0,0,0); my %p_ids = map { $_->item_id => 1 } (@items_user, @items_session); my @p_ids = keys %p_ids; my $products = @p_ids ? $keeper->get_documents( id => \@p_ids, class => $state->{webshop}->{item_document_class}, return_mode => 'hash_ref', ) : {}; foreach my $item ( @items_user ) { my $product = exists $products->{$item->item_id} ? $products->{$item->item_id} : undef; my ($item_from_session) = grep { $_->item_id == $item->item_id && $_->status == $item->status && ($_->color_id || 0) == ($item->color_id || 0) && ($_->size_id || 0) == ($item->size_id || 0) && ($_->colour || '') eq ($item->colour || '') && ($_->size || '') eq ($item->size || '') } @items_session; if ( ref $item_from_session ) { warn "Merge basket: Found identical item id=".$item_from_session->id."\n" if $DEBUG; $item->number( $item->number + $item_from_session->number ); $item->price( ref $product ? $product->price : $item_from_session->price ); $item->store; $item_from_session->status(-1); $item_from_session->delete; } if ( $item->status == 1 ) { $basket_total += $item->number; $basket_sum += $item->number * $item->price; } else { $wishlist_total += $item->number; $wishlist_sum += $item->number * $item->price; } } foreach my $item ( @items_session ) { my $product = exists $products->{$item->item_id} ? $products->{$item->item_id} : undef; if ( $item->status > 0 ) { $item->session(undef); $item->uid( $opts{uid} ); if ( ref $product ) { $item->price( $product->price ); } $item->store; if ( $item->status == 1 ) { $basket_total += $item->number; $basket_sum += $item->number * $item->price; } else { $wishlist_total += $item->number; $wishlist_sum += $item->number * $item->price; } } } warn "Merge end\n" if $DEBUG; return ($basket_total, $basket_sum, $wishlist_total, $wishlist_sum); } sub get_basket { my $self = shift; my (%opts) = @_; return unless $opts{uid} || $opts{session} || $opts{order_id}; my $with_products = delete $opts{with_products} || 0; my $product_status = exists $opts{product_status} ? delete $opts{product_status} : 'positive'; my $uid = delete $opts{uid}; my $session_id = delete $opts{session}; unless ( exists $opts{order_id} && $opts{order_id} ) { $opts{order_id} = 0; if ( $uid ) { $opts{uid} = $uid; } elsif ( $session_id ) { $opts{session} = $session_id; } } $opts{status} = 1 unless exists $opts{status} && defined $opts{status}; my @basket = $keeper->get_documents ( class => 'webshop::Basket', %opts, ); my $total = 0; my $sum = 0; my $items; if ( $with_products ) { my %ids = map { $_->item_id => 1 } @basket; my @ids = keys %ids; $items = @ids ? $keeper->get_documents ( id => \@ids, class => $state->{webshop}->{item_document_class}, status => $product_status, return_mode => 'hash_ref', ) : {}; } foreach my $bi ( @basket ) { if ( $bi->status == 1 ) { $total += $bi->number; $sum += $bi->number * ($bi->price || 0); } if ( ref $items eq 'HASH' && exists $items->{$bi->item_id} ) { $bi->{item} = $items->{$bi->item_id}; } } return \@basket; } sub basket_count { my $self = shift; my $basket = shift; return (0,0) unless ref $basket eq 'ARRAY' && @$basket; my (%opts) = @_; my $with_products = delete $opts{with_products}; my $total = 0; my $sum = 0; foreach my $item ( @$basket ) { if ( $item->status == 1 ) { $total += $item->number; if ( exists $item->{item} && $item->{item}->status && $item->{item}->price && $item->{item}->storage ) { $sum += $item->number * $item->{item}->price; } elsif ( !$with_products ) { $sum += $item->number * $item->price; } } } return ($total, $sum); } sub wishlist_count { my $self = shift; my $basket = shift; return (0,0) unless ref $basket eq 'ARRAY' && @$basket; my $total = 0; my $sum = 0; foreach my $item ( @$basket ) { if ( $item->status == 0 ) { $total += $item->number; $sum += $item->number * $item->price; } } return ($total, $sum); } sub clear_basket { my $self = shift; my (%opts) = @_; return unless exists $opts{uid} || exists $opts{session}; my $table_name = webshop::SQL::Basket->db_table; my $request = "delete from $table_name where order_id = 0 AND status = 1 AND"; my $dbh = $keeper->SQL; my @vals; if ( exists $opts{uid} && $opts{uid} ) { $request .= " uid in (?)"; push @vals, $opts{uid}; } elsif ( exists $opts{session} ) { $request .= " session in (?)"; push @vals, $opts{session}; } warn "CLEAR: [$request]. VALS: [".join(',',@vals)."]\n" if $DEBUG; my $statement = $dbh->prepare ($request); $statement->execute( @vals ) || $log->error("DBI execute error on $request\n"."\ncalled with opts:\n".Data::Dumper::Dumper(\%opts));; $statement->finish; my @plugins = split (/[\ |\t]+/, $state->{plugins}); if ( grep { $_ eq 'session' } @plugins && ref $session ) { $session->set ( basket_total => 0, basket_sum => 0 ); } } sub clear_wishlist { my $self = shift; my (%opts) = @_; return unless exists $opts{uid} || exists $opts{session}; my $table_name = webshop::SQL::Basket->db_table; my $request = "delete from $table_name where order_id = 0 AND status = 0 AND"; my $dbh = $keeper->SQL; my @vals; if ( exists $opts{uid} ) { $request .= " uid in (?)"; push @vals, $opts{uid}; } elsif ( exists $opts{session} ) { $request .= " session in (?)"; push @vals, $opts{session}; } my $statement = $dbh->prepare ($request); $statement->execute( @vals ) || $log->error("DBI execute error on $request\n"."\ncalled with opts:\n".Data::Dumper::Dumper(\%opts));; $statement->finish; } ### Метод пересчета корзины # Принимает на вход параметры: # session => session_id пользователя # uid => UID пользователя # delete => массив или отдельный item_id # renumber => ссылка на хеш вида item => number ############################################################# sub recount { my $self = shift; my (%opts) = @_; warn "Recount Started!!!\n" if $DEBUG; return unless exists $opts{uid} || exists $opts{session} || exists $opts{order_id}; my $basket = $self->get_basket ( %opts ); return unless ref $basket eq 'ARRAY' && @$basket; warn Dumper(\%opts) if $DEBUG; my $total = 0; my $sum = 0; my @new_basket; my $session_no_store = delete $opts{session_no_store}; foreach my $item ( @$basket ) { my $delete = 0; if ( exists $opts{renumber} && ref $opts{renumber} eq 'HASH' && exists $opts{renumber}{$item->id} && int($opts{renumber}{$item->id}) == 0 ) { $delete = 1; } elsif ( exists $opts{delete} && ref $opts{delete} eq 'ARRAY' ) { $delete = 1 if grep { $_ == $item->id } @{ $opts{delete} }; } elsif ( exists $opts{delete} && $opts{delete} ) { $delete = 1 if $item->id == $opts{delete}; } if ( $delete ) { warn "Item ID=".$item->id." DELETE\n" if $DEBUG; $item->delete(); next; } else { my $store = 0; if ( exists $opts{renumber} && ref $opts{renumber} eq 'HASH' && exists $opts{renumber}{$item->id} && $opts{renumber}{$item->id} != $item->number ) { $item->number( $opts{renumber}{$item->id} ); $store = 1; } if ( exists $opts{price} && ref $opts{price} eq 'HASH' && exists $opts{price}{$item->id} && $opts{price}{$item->id} != $item->price ) { $item->price( $opts{price}{$item->id} ); $store = 1; } $item->store if $store; $total += $item->number; $sum += $item->number * $item->price; push @new_basket, $item; } } my @plugins = split (/[\ |\t]+/, $state->{plugins}); if ( !$session_no_store && grep { $_ eq 'session' } @plugins && ref $session ) { $session->set ( basket_total => $total, basket_sum => $sum ); } return ($total, $sum, \@new_basket); } sub get_orders { my $self = shift; my (%opts) = @_; my $list = delete $opts{list}; my $count = delete $opts{count}; if ( $count ) { delete $opts{order_by}; my $item_count = $keeper->get_documents ( class => 'webshop::Order', count => 1, %opts, ); return $item_count; } else { $opts{order_by} ||= 'status'; my @items = $keeper->get_documents ( class => 'webshop::Order', %opts, ); if ( exists $opts{id} && defined $opts{id} && !ref $opts{id} ) { if ( $list ) { $items[0]->{list} = $self->get_order_list( order_id => $opts{id} ); } return $items[0]; } else { if ( $list ) { map { $_->{list} = $self->get_order_list( order_id => $_->id ) } @items; } return \@items; } } } sub get_order_list { my $self = shift; my (%opts) = @_; return unless $opts{order_id}; $opts{status} = 1; my @items = $keeper->get_documents ( class => 'webshop::Basket', %opts, ); my $total = 0; my $sum = 0; foreach my $item ( @items ) { my $Item = $item->class->new( $keeper, $item->id ); $item->{item} = $Item; if ( $item->status == 1 ) { $total += $item->number; $sum += $item->number * $item->price; } } return \@items; } ### Метод приведения купонов для пользователя в момент логина ############################################################# sub merge_coupons { my $self = shift; my (%opts) = @_; warn "Merge (coupons) begin: ".Dumper(\%opts)."\n" if $DEBUG; return unless $opts{uid} && $opts{session}; my @items = $keeper->get_links ( class => 'webshop::OrderCouponLink', session => $opts{session}, source_id => 0, ); my $merge_to = $keeper->get_links ( class => 'webshop::OrderCouponLink', uid => $opts{uid}, source_id => 0, return_mode => 'array_ref', ); my %merge_to; foreach my $link ( @$merge_to ) { if ( exists $merge_to{$link->dest_id} ) { $link->delete; } else { $merge_to{$link->dest_id} = $link; } } foreach my $item ( @items ) { if ( exists $merge_to{$item->dest_id} ) { $item->delete; } else { $item->session( undef ); $item->uid( $opts{uid} ); $item->store; } } } sub check_discount { my $self = shift; my (%opts) = @_; warn "Check discount begin:\n" if $DEBUG; my %dopts; if ( exists $opts{uid} && $opts{uid} ) { $dopts{uid} = [0,1]; } else { $dopts{uid} = 0; } my $basket = exists $opts{basket} ? $opts{basket} : $self->get_basket( %opts, with_products => 1 ); return 0 unless ref $basket eq 'ARRAY' && @$basket; my @basket = grep { exists $_->{item} && $_->{item} } @$basket; return 0 unless @basket; my $payment = delete $opts{payment}; if ( $payment && !ref $payment ) { if ( $payment =~ /^\d+$/ ) { $payment = $keeper->get_document_by_id($payment, class => 'webshop::Payment'); } } my $now = Contenido::DateTime->new; my @discounts = $keeper->get_documents( class => 'webshop::Discount', status => 1, interval=> [$now, $now], %dopts, ); my @summoned = sort { $b->min_sum <=> $a->min_sum } grep { $_->min_sum && $_->discount && $_->min_sum =~ /^\d+$/ } @discounts; my ($total, $sum) = $self->basket_count( \@basket ); return 0 unless $sum; my $result = 0; foreach my $discount ( @summoned ) { if ( $sum > $discount->min_sum ) { my $res = $discount->get_discount( basket => \@basket ); $result = $res if $res > $result; } } my $summarize = 0; if ( ref $payment && $payment->discount && $payment->discount > 0 ) { my $payment_discount = $payment->discount / 100; my $payment_discount_sum = 0; if ( $payment->exclude_specials ) { foreach my $item ( @basket ) { next if $item->special_price; $payment_discount_sum += $item->price; } $payment_discount_sum = int($payment_discount_sum * $payment_discount); } else { $payment_discount_sum = int($sum * $payment_discount); } if ( $payment->summarize ) { $summarize = 1; $result += $payment_discount_sum if $payment_discount_sum + $result < $sum; } else { $result = $payment_discount_sum if $payment_discount_sum > $result; } } $result = 0 if $result >= $sum; return ($result, $summarize); } sub check_coupons { my $self = shift; my (%opts) = @_; warn "Check coupons begin:\n" if $DEBUG; my %dopts; if ( exists $opts{uid} && $opts{uid} ) { $dopts{luid} = $opts{uid}; } else { $dopts{lsession} = $opts{session}; } my $basket = exists $opts{basket} ? $opts{basket} : $self->get_basket( %opts, with_products => 1 ); return (0, []) unless ref $basket eq 'ARRAY' && @$basket; my @basket = grep { exists $_->{item} && $_->{item} } @$basket; return (0, []) unless @basket; my $now = Contenido::DateTime->new; my @coupons = exists $opts{coupons} && ref $opts{coupons} eq 'ARRAY' ? @{$opts{coupons}} : $keeper->get_documents( class => 'webshop::Coupon', lclass => 'webshop::OrderCouponLink', lsource => 0, status => 1, interval=> [$now->ymd, $now->ymd], %dopts, ); return (0, []) unless @coupons; my @usable = grep { $_->discount } @coupons; warn "We count on ".scalar(@usable)." coupons: [".join(', ', map { $_->id.'. '.$_->name } @usable)."]\n" if $DEBUG; my ($total, $sum) = $self->basket_count( \@basket ); return (0, \@usable) unless $sum; my %summoned = ( coupons => [], discount => 0 ); my @groups; foreach my $coupon ( @usable ) { if ( $coupon->summon ) { push @{$summoned{coupons}}, $coupon; } else { push @groups, { coupons => [$coupon], discount => 0 }; } } my $result = 0; foreach my $coupon ( @usable ) { my $res = $coupon->get_discount( basket => \@basket ); $coupon->{result} = $res; } foreach my $group ( @groups ) { $group->{discount} = $group->{coupons}->[0]->{result}; $group->{coupons}->[0]->{afflict_order} = 1; } if ( @{$summoned{coupons}} ) { @{$summoned{coupons}} = sort { $b->{result} <=> $a->{result} } @{$summoned{coupons}}; my $check = $sum; foreach my $coupon ( @{$summoned{coupons}} ) { if ( ($coupon->{result} > 0) && ($check - $coupon->{result} > 0) ) { $summoned{discount} += $coupon->{result}; $check -= $coupon->{result}; $coupon->{afflict_order} = 1; } else { $coupon->{afflict_order} = 0; } } unshift @groups, \%summoned; } @groups = sort { $b->{discount} <=> $a->{discount} } @groups; my %chosen = map { $_->id => $_ } @{$groups[0]->{coupons}}; foreach my $coupon ( @usable ) { if ( !exists $chosen{$coupon->id} ) { $coupon->{afflict_order} = 0; } } warn "We choose ".scalar(@{$groups[0]->{coupons}})." coupons: [".join(', ', map { $_->id.'. '.$_->name } @{$groups[0]->{coupons}})."]\n" if $DEBUG; return ($groups[0]->{discount}, \@usable); } sub register_coupon { my $self = shift; my ($code, $session) = @_; my %result; my %opts; if ( ref $session ) { if ( $code ) { $code =~ s/([%_\\])/\\$1/g; if ( $session->id ) { $opts{uid} = $session->{id}; } else { $opts{session} = $session->_session_id; } my $basket = $self->get_basket ( %opts, with_products => 1 ); my $now = Contenido::DateTime->new; my @reglinks = $keeper->get_links ( class => 'webshop::OrderCouponLink', source_id => 0, %opts, ); my %cids = map { $_->dest_id => 1 } @reglinks; my @cids = keys %cids; my @registered = @cids ? $keeper->get_documents( id => \@cids, class => 'webshop::Coupon', ) : (); my ($coupon) = grep { uc(Encode::decode('utf-8', $_->code)) eq uc(Encode::decode('utf-8', $code)) } @registered; @registered = grep { my $bt = Contenido::DateTime->new( postgres => $_->dtime ); my $et = Contenido::DateTime->new( postgres => $_->etime ); $now >= $bt && $now <= $et && $_->status == 1 } @registered; $result{coupons} = \@registered; if ( ref $coupon ) { $result{error} = 'Такой купон уже зарегистрирован'; $result{found} = $coupon; } else { ($coupon) = $keeper->get_documents( class => 'webshop::Coupon', code => $code, uid => 0, status => [1,3], interval => [$now, $now], ilike => 1, ); if ( $session->id && !ref $coupon ) { ($coupon) = $keeper->get_documents( class => 'webshop::Coupon', code => $code, uid => $session->id, status => [1,3], interval => [$now, $now], ilike => 1, ); } if ( ref $coupon ) { if ( $coupon->uid && $coupon->status == 3 ) { $result{error} = 'Купон уже использован'; } unless ( exists $result{error} && $result{error} ) { my $coupon_link = webshop::OrderCouponLink->new( $keeper ); $coupon_link->status( 0 ); if ( $session->id ) { $coupon_link->uid( $session->id ); } else { $coupon_link->uid( 0 ); $coupon_link->session( $session->_session_id ); } $coupon_link->dest_id( $coupon->id ); $coupon_link->dest_class( $coupon->class ); $coupon_link->source_id( 0 ); $coupon_link->source_class( 'webshop::Order' ); $coupon_link->store; $result{created} = $coupon; push @registered, $coupon; } } else { $result{error} = 'Купон не найден'; } } } else { $result{error} = 'Вы не указали код купона'; } } else { $result{error} = 'Фатальная ошибка. Не работают сессии! Обратитесь в службу поддержки магазина'; } return \%result; } sub price_format { my $self = shift; my $price = shift; if ( defined $price ) { $price = reverse $price; $price =~ s/(\d{3})/$1\ /g; $price = reverse $price; } return $price; } 1;