package Vending::Machine;
use strict;
use warnings;
use Vending;
class Vending::Machine {
table_name => 'MACHINE',
id_by => [
machine_id => { is => 'Integer' },
],
has => [
coin_box => { via => 'machine_locations', to => '-filter', where => [ name => 'box' ] },
bank => { via => 'machine_locations', to => '-filter', where => [ name => 'bank' ] },
change_dispenser => { via => 'machine_locations', to => '-filter', where => [ name => 'change' ] },
address => { is => 'Text', is_optional => 1 },
],
has_many => [
products => { is => 'Vending::Product', reverse_as => 'machine' },
items => { is => 'Vending::Content', reverse_as => 'machine' },
inventory_items => { is => 'Vending::Merchandise', reverse_as => 'machine' },
item_types => { is => 'Vending::ContentType', reverse_as => 'machine' },
machine_locations => { is => 'Vending::MachineLocation', reverse_as => 'machine' },
],
data_source => 'Vending::DataSource::Machine',
};
sub insert {
my($self, $item_name) = @_;
my $coin_type = Vending::CoinType->get(name => $item_name);
unless ($coin_type) {
$self->error_message("This machine does not accept '$item_name' coins");
return;
}
my $loc = $self->coin_box();
my $coin = $loc->add_coin(type_id => $coin_type->type_id, machine_id => $self);
return defined($coin);
}
sub coin_return {
my $self = shift;
my $loc = $self->coin_box;
my @coins = $loc->items();
my @returned_items = Vending::ReturnedItem->create_from_vend_items(@coins);
return @returned_items;
}
sub empty_bank {
my $self = shift;
my $loc = $self->bank();
my @coins = $loc->items();
my @returned_items = Vending::ReturnedItem->create_from_vend_items(@coins);
return @returned_items;
}
sub empty_machine_location_by_name {
my($self,$name) = @_;
my $loc = $self->machine_locations(name => $name);
return unless $loc;
unless ($loc->is_buyable) {
die "You can only empty out inventory type machine_locations";
}
my @items = $loc->items();
my @returned_items = Vending::ReturnedItem->create_from_vend_items(@items);
return @returned_items;
}
sub buy {
my($self,@machine_location_names) = @_;
my $coin_box = $self->coin_box();
my $transaction = UR::Context::Transaction->begin();
my @returned_items = eval {
my $users_money = $coin_box->content_value();
my @bought_items;
my %iterator_for_machine_location;
foreach my $loc_name ( @machine_location_names ) {
my $machine_location = $self->machine_locations(name => $loc_name);
unless ($machine_location && $machine_location->is_buyable) {
die "$loc_name is not a valid choice\n";
}
my $iter = $iterator_for_machine_location{$loc_name} || $machine_location->item_iterator();
unless ($iter) {
die "Problem creating iterator for $loc_name\n";
return;
}
my $item = $iter->next(); # This is the one they'll buy
unless ($item) {
$self->error_message("Item $loc_name is empty");
next;
}
push @bought_items, $item->dispense;
}
my @change;
if (@bought_items) {
@change = $self->_complete_purchase_and_make_change_for_selections(@bought_items);
}
return (@change,@bought_items);
};
if ($@) {
my($error) = ($@ =~ m/^(.*?)\n/);
$self->error_message("Couldn't process your purchase:\n$error");
$transaction->rollback();
return;
} else {
$transaction->commit();
return @returned_items;
}
}
# Note that this will die if there's a problem making change
sub _complete_purchase_and_make_change_for_selections {
my($self,@bought_items) = @_;
my $coin_box = $self->coin_box();
my $purchased_value = 0;
foreach my $item ( @bought_items ) {
$purchased_value += $item->cost_cents;
}
my $change_value = $coin_box->content_value() - $purchased_value;
if ($change_value < 0) {
die "You did not enter enough money\n";
}
# Put all the user's coins into the bank
my $bank = $self->bank;
$coin_box->transfer_items_to_machine_location($bank);
if ($change_value == 0) {
return;
}
# List of coin types in decreasing value
my @available_coin_types = map { $_->name }
sort { $b->value_cents <=> $a->value_cents }
Vending::CoinType->get();
my $change_dispenser = $self->change_dispenser;
my @change;
# Make change for the user
MAKING_CHANGE:
foreach my $coin_name ( @available_coin_types ) {
my $coin_iter = $change_dispenser->coin_iterator(name => $coin_name);
unless ($coin_iter) {
die "Can't create iterator for Vending::Coin::Change\n";
}
THIS_coin_type:
while ( my $coin = $coin_iter->next() ) {
last if $change_value < $coin->value_cents;
my($change_coin) = $coin->dispense;
$change_value -= $change_coin->value;
push @change, $change_coin;
}
}
if ($change_value) {
#$DB::single=1;
die "Not enough change\n";
}
return @change;
}
sub _initialize_for_tests {
my $self = shift;
$_->delete foreach $self->inventory_items();
$_->delete foreach $self->products();
$_->delete foreach $self->items();
}
1;