Revision 517 (by ahitrov, 2015/10/08 22:41:38) Merge similar items

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);
  foreach my $item ( @items_user ) {
	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( $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 ) {
	if ( $item->status > 0 ) {
		$item->session(undef);
		$item->uid( $opts{uid} );
		$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 $total = 0;
    my $sum = 0;
    foreach my $item ( @$basket ) {
	if ( $item->status == 1 ) {
		$total += $item->number;
		if ( exists $item->{item} ) {
			$sum += $item->number * $item->{item}->price;
		} else {
			$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	=> 'hash',
		hash_by		=> 'dest_id',
	);
    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 $now = Contenido::DateTime->new;
    my @discounts = $keeper->get_documents(
			class	=> 'webshop::Discount',
			status	=> 1,
			interval=> [$now, $now],
			%dopts,
		);
    return 0	unless @discounts;
    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;
	}
    }
    $result = 0		if $result >= $sum;
    return $result;
}


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 @summoned = grep { $_->discount } @coupons;
    warn "We count on ".scalar(@summoned)." coupons\n"			if $DEBUG;
    my ($total, $sum) = $self->basket_count( \@basket );
    return (0, \@summoned)	unless $sum;
    my $result = 0;
    foreach my $coupon ( @summoned ) {
	my $res = $coupon->get_discount( basket => \@basket );
	$coupon->{result} = $res;
    }
    @summoned = sort { $b->{result} <=> $a->{result} } @summoned;
    foreach my $coupon ( @summoned ) {
	if ( ($coupon->{result} > 0) && ($sum - $coupon->{result} > 0) ) {
		$result += $coupon->{result};
		$sum -= $coupon->{result};
		$coupon->{afflict_order} = 1;
	} else {
		$coupon->{afflict_order} = 0;
	}
    }
    return ($result, \@summoned);
}


sub price_format {
	my $self = shift;
	my $price = shift;

	$price = reverse $price;
	$price =~ s/(\d{3})/$1\ /g;
	$price = reverse $price;

	return $price;
}


1;