# $Id: Order.pm 589 2005-07-15 02:11:40Z claco $ package Handel::Order; use strict; use warnings; BEGIN { use base 'Handel::DBI'; use Handel::Checkout; use Handel::Constants qw(:checkout :returnas :order); use Handel::Constraints qw(:all); use Handel::Currency; use Handel::L10N qw(translate); }; __PACKAGE__->autoupdate(0); __PACKAGE__->table('orders'); __PACKAGE__->iterator_class('Handel::Iterator'); __PACKAGE__->columns(All => qw(id shopper type number created updated comments shipmethod shipping handling tax subtotal total billtofirstname billtolastname billtoaddress1 billtoaddress2 billtoaddress3 billtocity billtostate billtozip billtocountry billtodayphone billtonightphone billtofax billtoemail shiptosameasbillto shiptofirstname shiptolastname shiptoaddress1 shiptoaddress2 shiptoaddress3 shiptocity shiptostate shiptozip shiptocountry shiptodayphone shiptonightphone shiptofax shiptoemail)); __PACKAGE__->has_many(_items => 'Handel::Order::Item', 'orderid'); __PACKAGE__->has_a(subtotal => 'Handel::Currency'); __PACKAGE__->has_a(total => 'Handel::Currency'); __PACKAGE__->has_a(shipping => 'Handel::Currency'); __PACKAGE__->has_a(handling => 'Handel::Currency'); __PACKAGE__->has_a(tax => 'Handel::Currency'); __PACKAGE__->add_constraint('id', id => \&constraint_uuid); __PACKAGE__->add_constraint('shopper', shopper => \&constraint_uuid); __PACKAGE__->add_constraint('type', type => \&constraint_order_type); __PACKAGE__->add_constraint('shipping', shipping => \&constraint_price); __PACKAGE__->add_constraint('handling', handling => \&constraint_price); __PACKAGE__->add_constraint('subtotal', subtotal => \&constraint_price); __PACKAGE__->add_constraint('tax', tax => \&constraint_price); __PACKAGE__->add_constraint('total', total => \&constraint_price); sub new { my ($self, $data, $noprocess) = @_; throw Handel::Exception::Argument( -details => translate('Param 1 is not a HASH reference') . '.') unless ref($data) eq 'HASH'; if (!defined($data->{'id'}) || !constraint_uuid($data->{'id'})) { $data->{'id'} = $self->uuid; }; if (!defined($data->{'type'})) { $data->{'type'} = ORDER_TYPE_TEMP; }; my $cart = $data->{'cart'}; my $is_uuid = constraint_uuid($cart); delete $data->{'cart'}; throw Handel::Exception::Argument( -details => translate( 'Cart reference is not a HASH reference or Handel::Cart') . '.') unless (ref($cart) eq 'HASH' or UNIVERSAL::isa($cart, 'Handel::Cart') or $is_uuid); if (ref $cart eq 'HASH') { $cart = Handel::Cart->load($cart, RETURNAS_ITERATOR)->first; throw Handel::Exception::Order( -details => translate( 'Could not find a cart matching the supplied search criteria') . '.') unless $cart; } elsif ($is_uuid) { $cart = Handel::Cart->load({id => $cart}, RETURNAS_ITERATOR)->first; throw Handel::Exception::Order( -details => translate( 'Could not find a cart matching the supplied search criteria') . '.') unless $cart; }; throw Handel::Exception::Order( -details => translate( 'Could not create a new order because the supplied cart is empty') . '.') unless $cart->count > 0; my $order = $self->create($data); my $subtotal = 0; my $items = $cart->items(undef, RETURNAS_ITERATOR); while (my $item = $items->next) { my %copy; foreach ($item->columns) { next if $_ =~ /^(id|cart)$/i; $copy{$_} = $item->$_; }; $copy{'id'} = $self->uuid unless constraint_uuid($copy{'id'}); $copy{'orderid'} = $order->id; $copy{'total'} = $copy{'quantity'}*$copy{'price'}; $subtotal += $copy{'total'}; $order->add_to__items(\%copy); }; $order->subtotal($subtotal); $order->update; unless ($noprocess) { my $checkout = Handel::Checkout->new; $checkout->order($order); my $status = $checkout->process([CHECKOUT_PHASE_INITIALIZE]); if ($status == CHECKOUT_STATUS_OK) { $checkout->order->update; } else { $order->delete; undef $order; }; undef $checkout; }; return $order; }; sub clear { my $self = shift; $self->_items->delete_all; return undef; }; sub count { my $self = shift; return $self->_items->count || 0; }; sub delete { my ($self, $filter) = @_; throw Handel::Exception::Argument( -details => translate('Param 1 is not a HASH reference') . '.') unless ref($filter) eq 'HASH'; ## I'd much rather use $self->_items->search_like, but it doesn't work that ## way yet. This should be fine as long as :weaken refs works. return Handel::Cart::Item->search_like(%{$filter}, cart => $self->id)->delete_all; }; sub items { my ($self, $filter, $wantiterator) = @_; throw Handel::Exception::Argument( -details => translate('Param 1 is not a HASH reference') . '.') unless( ref($filter) eq 'HASH' or !$filter); $wantiterator ||= RETURNAS_AUTO; $filter ||= {}; my $wildcard = Handel::DBI::has_wildcard($filter); ## If the filter as a wildcard, push it through a fresh search_like since it ## doesn't appear to be available within a loaded object. if ((wantarray && $wantiterator != RETURNAS_ITERATOR) || $wantiterator == RETURNAS_LIST) { my @items = $wildcard ? Handel::Order::Item->search_like(%{$filter}, orderid => $self->id) : $self->_items(%{$filter}); return @items; } elsif ($wantiterator == RETURNAS_ITERATOR) { my $iterator = $wildcard ? Handel::Order::Item->search_like(%{$filter}, orderid => $self->id) : $self->_items(%{$filter}); return $iterator; } else { my $iterator = $wildcard ? Handel::Order::Item->search_like(%{$filter}, orderid => $self->id) : $self->_items(%{$filter}); if ($iterator->count == 1 && $wantiterator != RETURNAS_ITERATOR && $wantiterator != RETURNAS_LIST) { return $iterator->next; } else { return $iterator; }; }; }; sub load { my ($self, $filter, $wantiterator) = @_; throw Handel::Exception::Argument( -details => translate('Param 1 is not a HASH reference') . '.') unless( ref($filter) eq 'HASH' or !$filter); $wantiterator ||= RETURNAS_AUTO; ## only return array if wantarray and not explicitly asking for an iterator ## or we've explicitly asked for a list/array if ((wantarray && $wantiterator != RETURNAS_ITERATOR) || $wantiterator == RETURNAS_LIST) { my @orders = $filter ? $self->search_like(%{$filter}) : $self->retrieve_all; return @orders; ## return an iterator if explicitly asked for } elsif ($wantiterator == RETURNAS_ITERATOR) { my $iterator = $filter ? $self->search_like(%{$filter}) : $self->retrieve_all; return $iterator; ## full out auto } else { my $iterator = $filter ? $self->search_like(%{$filter}) : $self->retrieve_all; if ($iterator->count == 1 && $wantiterator != RETURNAS_ITERATOR && $wantiterator != RETURNAS_LIST) { return $iterator->next; } else { return $iterator; }; }; }; 1; __END__ =head1 NAME Handel::Order - Module for maintaining order contents =head1 SYNOPSIS my $order = Handel::Order->new({ id => '12345678-9098-7654-322-345678909876' }); my $iterator = $order->items; while (my $item = $iterator->next) { print $item->sku; print $item->price; print $item->total; }; =head1 DESCRIPTION C<Handel::Order> is a component for maintaining simple order records. While C<Handel::Order> subclasses L<Class::DBI>, it is strongly recommended that you not use its methods unless it's absolutely necessary. Stick to the documented methods here and you'll be safe should I decide to implement some other data access mechanism. :-) =head1 CONSTRUCTOR There are three ways to create a new order object. You can either pass a hashref into C<new> containing all the required values needed to create a new order record or pass a hashref into C<load> containing the search criteria to use to load an existing order or set of orders. B<NOTE:> The only required hash key is C<cart>. C<new> will copy the specified carts items inthe the order items. C<cart> can be an already existing Handel::Cart object, of a hash reference of search critera, or a cart id (uuid). When creating a new order from a cart, C<new> will automatically create a new Handel::Checkout process and process C<CHECKOUT_PHASE_INITIALIZE> on the new order. This can be disabled by passing any true value in the second option C<noprocess>. =over =item C<Handel::Order-E<gt>new(\%data [, $noprocess])> my $order = Handel::Order->new({ shopper => '10020400-E260-11CF-AE68-00AA004A34D5', id => '111111111-2222-3333-4444-555566667777', cart => $cartobject }); my $order = Handel::Order->new({ shopper => '10020400-E260-11CF-AE68-00AA004A34D5', id => '111111111-2222-3333-4444-555566667777', cart => '11112222-3333-4444-5555-666677778888' }); my $order = Handel::Order->new({ shopper => '10020400-E260-11CF-AE68-00AA004A34D5', id => '111111111-2222-3333-4444-555566667777', cart => { id => '11112222-3333-4444-5555-666677778888', type => CART_TYPE_TEMP } }); =item C<Handel::Order-E<gt>load([\%filter, $wantiterator])> my $order = Handel::Order->load({ id => 'D597DEED-5B9F-11D1-8DD2-00AA004ABD5E' }); You can also omit \%filter to load all available orders. my @orders = Handel::Order->load(); In scalar context C<load> returns a C<Handel::Order> object if there is a single result, or a L<Handel::Iterator> object if there are multiple results. You can force C<load> to always return an iterator even if only one cart exists by setting the C<$wantiterator> parameter to C<RETURNAS_ITERATOR>. my $iterator = Handel::Order->load(undef, RETURNAS_ITERATOR); while (my $item = $iterator->next) { print $item->sku; }; See L<Handel::Contstants> for the available C<RETURNAS> options. A C<Handel::Exception::Argument> exception is thrown if the first parameter is not a hashref. =back =head1 METHODS =head2 clear This method removes all items from the current cart object. $cart->clear; =head2 delete(\%filter) This method deletes the cart item(s) matching the supplied filter values and returns the number of items deleted. if ( $cart->delete({id => '8D4B0BE1-C02E-11D2-A33D-00A0C94B8D0E'}) ) { print 'Item deleted'; }; =head2 items([\%filter, [$wantiterator]) You can retrieve all or some of the items contained in the order via the C<items> method. In a scalar context, items returns an iterator object which can be used to cycle through items one at a time. In list context, it will return an array containing all items. my $iterator = $order->items; while (my $item = $iterator->next) { print $item->sku; }; my @items = $order->items; ... dosomething(\@items); When filtering the items in the order in scalar context, a C<Handel::Order::Item> object will be returned if there is only one result. If there are multiple results, a Handel::Iterator object will be returned instead. You can force C<items> to always return a C<Handel::Iterator> object even if only one item exists by setting the $wantiterator parameter to C<RETURNAS_ITERATOR>. my $item = $order->items({sku => 'SKU1234'}, RETURNAS_ITERATOR); if ($item->isa('Handel::Order::Item)) { print $item->sku; } else { while ($item->next) { print $_->sku; }; }; See the C<RETURNAS> constants in L<Handel::Constants> for other options. In list context, filtered items return an array of items just as when items is called without a filter specified. my @items - $order->items((sku -> 'SKU1%'}); A C<Handel::Exception::Argument> exception is thrown if parameter one isn't a hashref or undef. =head2 billtofirstname Gets/sets the bill to first name =head2 billtolastname Gets/sets the bill to last name =head2 billtoaddress1 Gets/sets the bill to address line 1 =head2 billtoaddress2 Gets/sets the bill to address line 2 =head2 billtoaddress3 Gets/sets the bill to address line 3 =head2 billtocity Gets/sets the bill to city =head2 billtostate Gets/sets the bill to state/province =head2 billtozip Gets/sets the bill to zip/postal code =head2 billtocountry Gets/sets the bill to country =head2 billtodayphone Gets/sets the bill to day phone number =head2 billtonightphone Gets/sets the bill to night phone number =head2 billtofax Gets/sets the bill to fax number =head2 billtoemail Gets/sets the bill to email address =head2 comments Gets/sets the comments for this order =head2 count Gets the number of items in the order =head2 created Gets/sets the created date of the order =head2 handling Gets/sets the handling charge =head2 id Gets/sets the record id =head2 number Gets/sets the order number =head2 shipmethod Gets/sets the shipping method =head2 shipping Gets/sets the shipping cost =head2 shiptosameasbillto Gets/sets the ship to same as bill to flag. When set, the ship to information will be copied from the bill to =head2 shiptofirstname Gets/sets the ship to first name =head2 shiptolastname Gets/sets the ship to last name =head2 shiptoaddress1 Gets/sets the ship to address line 1 =head2 shiptoaddress2 Gets/sets the ship to address line 2 =head2 shiptoaddress3 Gets/sets the ship to address line 3 =head2 shiptocity Gets/sets the ship to city =head2 shiptostate Gets/sets the ship to state =head2 shiptozip Gets/sets the ship to zip/postal code =head2 shiptocountry Gets/sets the ship to country =head2 shiptodayphone Gets/sets the ship to day phone number =head2 shiptonightphone Gets/sets the ship to night phone number =head2 shiptofax Gets/sets the ship to fax number =head2 shiptoemail Gets/sets the ship to email address =head2 shopper Gets/sets the shopper id =head2 subtotal Gets/sets the orders subtotal =head2 tax Gets/sets the orders tax =head2 total Gets/sets the orders total =head2 type Gets/sets the order type =head2 updated Gets/sets the last updated date of the order =head1 AUTHOR Christopher H. Laco CPAN ID: CLACO claco@chrislaco.com http://today.icantfocus.com/blog/