The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Business::Cart::Generic::Database::Order;

use strict;
use warnings;

use Moose;

extends 'Business::Cart::Generic::Database::Base';

use namespace::autoclean;

our $VERSION = '0.85';

# --------------------------------------------------

sub add_to_cart
{
	my($self, $order) = @_;

	$self -> db -> logger -> log(debug => 'add_to_cart()');

	# Note: save_order() sets $$order{id}, and both
	# save_order() and save_order_history() use $$order{status_id}.

	my($order_session) = $self -> db -> session -> param('order');
	my(@status2id)     = $self -> schema -> resultset('OrderStatuse') -> search({}, {columns => [qw/name id/]});
	my(%status2id)     = map{($_ -> name, $_ -> id)} @status2id;
	my($status)        = 'Processing';
	$$order{status_id} = $status2id{$status};

	# Is it a new order?

	if ($$order_session{item_count} == 0)
	{
		$self -> save_order($order);

		$$order_session{id} = $$order{id};
	}
	else
	{
		$$order{id} = $$order_session{id};
	}

	$$order_session{item_id} = $$order{item_id} = $self -> save_order_item($order);

	$self -> save_order_history($order);

	$$order_session{item_count}++;

	$self -> db -> session -> param(order => $order_session);
	$self -> db -> logger -> log(info => "Order item saved. id: $$order{id}. item id: $$order{item_id}. item count: $$order_session{item_count}");

	return $order_session;

} # End of add_to_cart.

# --------------------------------------------------

sub cancel_order
{
	my($self) = @_;

	$self -> db -> logger -> log(debug => 'cancel_order()');

	my($order_session) = $self -> db -> session -> param('order');

	$self -> schema -> resultset('OrderHistory') -> search({order_id => $$order_session{id} }) -> delete;
	$self -> schema -> resultset('OrderItem') -> search({order_id => $$order_session{id} }) -> delete;
	$self -> schema -> resultset('Order') -> search({id => $$order_session{id} }) -> delete;
	$self -> db -> logger -> log(info => "Order cancelled: id: $$order_session{id}");
	$self -> db -> reset_order;

} # End of cancel_order.

# --------------------------------------------------

sub checkout
{
	my($self) = @_;

	$self -> db -> logger -> log(debug => 'checkout()');

	my($order_session)          = $self -> db -> session -> param('order');
	my($order_db)               = $self -> schema -> resultset('Order') -> search({id => $$order_session{id} }) -> single;
	my($order_inflated)         = $self -> inflate_order($order_db);
	my(@status2id)              = $self -> schema -> resultset('OrderStatuse') -> search({}, {columns => [qw/name id/]});
	my(%status2id)              = map{($_ -> name, $_ -> id)} @status2id;
	$$order_inflated{status}    = 'Checked out';
	$$order_inflated{status_id} = $status2id{$$order_inflated{status} };

	my($option);
	my($product);

	for my $item (@{$$order_inflated{item} })
	{
		$self -> db -> logger -> log(debug => "Updating product. id: $$item{product_id}");

		$product = $self -> schema -> resultset('Product') -> search({id => $$item{product_id} }) -> single;
		$option  =
		{
			quantity_on_hand => $product -> quantity_on_hand - $$item{quantity},
			quantity_ordered => $product -> quantity_ordered + $$item{quantity},
		};

		$product -> update($option);
	}

	$self -> db -> logger -> log(debug => "Updating order. id: $$order_session{id}");

	$option =
	{
		date_completed  => \'now()',
		date_modified   => \'now()',
		order_status_id => $$order_inflated{status_id},
	};

	$order_db -> update($option);

	# Note: save_order_history() inserts item_id into the comment field in the order_history table.

	$$order_inflated{item_id} = $$order_session{item_id};

	$self -> save_order_history($order_inflated);

} # End of checkout.

# --------------------------------------------------

sub get_orders
{
	my($self, $limit) = @_;
	$limit ||= {};

	$self -> db -> logger -> log(debug => 'get_orders()');

	return scalar $self -> schema -> resultset('Order') -> search
		(
		 $limit,
		 {
			 join     => [qw/billing_address customer customer_address delivery_address order_status payment_method/],
			 order_by => [qw/me.date_added/],
		 }
		);

} # End of get_orders.

# --------------------------------------------------

sub inflate_order
{
	my($self, $order) = @_;

	$self -> db -> logger -> log(debug => 'inflate_order()');

	my($id)      = $order -> id;
	my $item_set = $self -> schema -> resultset('OrderItem') -> search
		(
		 {
			 order_id => $id
		 },
		 {
			 join     => [qw/product/],
			 order_by => [qw/product.name/],
		 }
		);
	my(%total) =
		(
		 price    => 0,
		 quantity => 0,
		 tax      => 0,
		);

	my(@item);
	my($value);

	while (my $item = $item_set -> next)
	{
		$value           = $item -> quantity * $item -> price;
		$total{price}    += $value;
		$total{quantity} += $item -> quantity;
		$total{tax}      += $value * $item -> tax_rate;

		push @item,
		{
			currency_id => $item -> product -> currency_id,
			description => $item -> product -> description,
			item_id     => $item -> id,
			name        => $item -> name,
			order_id    => $item -> order_id,
			price       => $item -> price,
			product_id  => $item -> product_id,
			quantity    => $item -> quantity,
			tax_rate    => $item -> tax_rate,
		};
	}

	return
	{
		billing_address =>
		{
			country_name => $order -> billing_address -> country -> name,
			locality     => $order -> billing_address -> locality,
			street_1     => $order -> billing_address -> street_1,
			zone_name    => $order -> billing_address -> zone -> name,
		},
		customer =>
		{
			name  => $order -> customer -> name,
			title => $order -> customer -> title -> name,
		},
		customer_address =>
		{
			country_name => $order -> customer_address -> country -> name,
			locality     => $order -> customer_address -> locality,
			street_1     => $order -> customer_address -> street_1,
			zone_name    => $order -> customer_address -> zone -> name,
		},
		delivery_address =>
		{
			country_name => $order -> delivery_address -> country -> name,
			locality     => $order -> delivery_address -> locality,
			street_1     => $order -> delivery_address -> street_1,
			zone_name    => $order -> delivery_address -> zone -> name,
		},
		date_added     => $order -> date_added,
		date_completed => $order -> date_completed,
		id             => $id,
		item           => [@item],
		order_status   => $order -> order_status -> name,
		payment_method => $order -> payment_method -> name,
		total_price    => $total{price},
		total_quantity => $total{quantity},
		total_tax      => $total{tax},
	};

} # End of inflate_order.

# --------------------------------------------------

sub remove_from_cart
{
	my($self, $order_id, $item_id) = @_;

	$self -> db -> logger -> log(debug => "remove_from_cart($order_id, $item_id)");

	$self -> schema -> resultset('OrderItem') -> search({id => $item_id}) -> delete;

	if ($self -> db -> decrement_order_items($order_id) == 0)
	{
		$self -> cancel_order;
	}

} # End of remove_from_cart.

# --------------------------------------------------

sub save_order
{
	my($self, $order) = @_;

	$self -> db -> logger -> log(debug => 'save_order()');

	my($rs)     = $self -> schema -> resultset('Order');
	my($result) = $rs -> create
		({
			billing_address_id  => $$order{billing_address_id},
			customer_address_id => $$order{customer_address_id},
			customer_id         => $$order{customer_id},
			date_added          => \'now()',
			date_completed      => \'now()',
			date_modified       => \'now()',
			delivery_address_id => $$order{delivery_address_id},
			order_status_id     => $$order{status_id},
			payment_method_id   => $$order{payment_method_id},
		});
	my($fix_quote) = \'now()';
	$$order{id}    = $result -> id;

} # End of save_order.

# --------------------------------------------------

sub save_order_history
{
	my($self, $order) = @_;

	$self -> db -> logger -> log(debug => 'save_order_history()');

	my($rs)          = $self -> schema -> resultset('OrderHistory');
	my(@yesno2id)    = $self -> schema -> resultset('YesNo') -> search({}, {columns => [qw/name id/]});
	my(%yesno2id)    = map{($_ -> name, $_ -> id)} @yesno2id;
	my($notified_id) = $yesno2id{'No'};
	my($result)      = $rs -> create
		({
			comment              => "item_id: $$order{item_id}",
			date_added           => \'now()',
			date_modified        => \'now()',
			customer_notified_id => $notified_id,
			order_id             => $$order{id},
			order_status_id      => $$order{status_id},
		 });

} # End of save_order_history.

# --------------------------------------------------

sub save_order_item
{
	my($self, $order) = @_;

	$self -> db -> logger -> log(debug => 'save_order_item()');

	my($product)  = $self -> schema -> resultset('Product') -> search({id => $$order{product_id} },{}) -> single;
	my($tax_rate) = $self -> schema -> resultset('TaxRate') -> search({tax_class_id => $$order{tax_class_id}, zone_id => $$order{zone_id} }, {}) -> single;
	my($rs)       = $self -> schema -> resultset('OrderItem');
	my($result)   = $rs -> create
		({
			model      => $product -> model,
			name       => $product -> name,
			order_id   => $$order{id},
			price      => $product -> price,
			product_id => $$order{product_id},
			quantity   => $$order{quantity},
			tax_rate   => $tax_rate -> rate,
			upper_name => uc $product -> name,
		 });

	return $result -> id;

} # End of save_order_item.

# --------------------------------------------------

__PACKAGE__ -> meta -> make_immutable;

1;

=pod

=head1 NAME

L<Business::Cart::Generic::Database::Order> - Basic shopping cart

=head1 Synopsis

See L<Business::Cart::Generic>.

=head1 Description

L<Business::Cart::Generic> implements parts of osCommerce and PrestaShop in Perl.

=head1 Installation

See L<Business::Cart::Generic>.

=head1 Constructor and Initialization

=head2 Parentage

This class extends L<Business::Cart::Generic::Database::Base>.

=head2 Using new()

C<new()> is called as C<< my($obj) = Business::Cart::Generic::Database::Order -> new(k1 => v1, k2 => v2, ...) >>.

It returns a new object of type C<Business::Cart::Generic::Database::Order>. See L<Business::Cart::Generic::Database>.

Key-value pairs accepted in the parameter list:

=over 4

=item o db => $db

Takes an object of type L<Business::Cart::Generic::Database>.

This key => value pair is mandatory.

=item o schema => $schema

Takes a L<DBIx::Class> schema object.

This value is provided by the parent, L<Business::Cart::Generic::Database::Base>.

=back

These keys are also getter-type methods.

=head1 Methods

=head2 add_to_cart($order)

Add an item to the cart.

$order is a hashref returned by L<Business::Cart::Generic::Util::Validator>.

Returns the order hashref from the session object.

This latter hashref is discussed in the FAQ in L<Business::Cart::Generic>.
The code is in L<Business::Cart::Generic::Database/reset_order()>.

=head2 cancel_order()

Cancel the order whose id is in the order hashref in the session object.

Returns nothing.

=head2 checkout()

Wrap up the order by calculating quantities on hand per item, and updating the order history.

Returns nothing.

=head2 get_orders($limit)

Get order item objects from the orders table.

These items are of type L<DBIx::Class::Row>.

Limit defaults to {}, and can be used to get 1 order by - e.g. - setting it to {'me.id' => $order_id}.

The orders table is joined with these tables:

=over 4

=item o The billing_address

I.e. the street_addresses table.

=item o The customers table

=item o The customer_address

I.e. the street_addresses table.

=item o The delivery_address

I.e. the street_addresses table.

=item o The order_statuses table

=item o The payment_methods table

=back

=head2 inflate_order($order)

$order is of type L<DBIx::Class::Row>.

Returns a hashref with these keys:

=over 4

=item o billing_address

This is a hashref with these keys:

=over 4

=item o country_name

=item o locality

=item o street_1

=item o zone_name

=back

The values are all strings.

=item o customer

This is a hashref with these keys:

=over 4

=item o name

=item o title

=back

The values are all strings.

=item o customer_address

This is a hashref with these keys:

=over 4

=item o country_name

=item o locality

=item o street_1

=item o zone_name

=back

The values are all strings.

=item o delivery_address

This is a hashref with these keys:

=over 4

=item o country_name

=item o locality

=item o street_1

=item o zone_name

=back

The values are all strings.

=item o date_added

This is a string like '2011-05-10 10:48:53'.

=item o date_completed

This is a string like '2011-05-10 10:48:53'.

=item o id

This is the order's primary key in the orders table.

=item o item

This is an array ref of item hashrefs, with these keys:

=over 4

=item o currency_id

This is the currency's primary key in the currencies table.

=item o description

This is a string from the products table.

=item o item_id

This is the item's primary key in the items table.

=item o name

This is a string from the products table.

=item o order_id

This is the order's primary key in the orders table.

=item o price

This is a float from the products table.

=item o product_id

This is the product's primary key in the products table.

=item o quantity

This is an integer from the items table.

=item o tax_rate

This is a float from the products table.

=back

=item o order_status

This is a string from the order_statuses table.

=item o payment_method

This is a string from the payment_methods table.

=item o total_price

This is a sum over all items.

=item o total_quantity

This is a sum over all items.

=item o total_tax

This is a sum over all items.

=back

=head2 remove_from_cart($order_id, $item_id)

Remove an item from the cart.

$order_id is the primary key in the orders table.

$item_id is the primary key in the items table.

=head2 save_order($order)

Called by add_to_cart().

$order is a hashref returned by L<Business::Cart::Generic::Util::Validator>.

Returns nothing.

=head2 save_order_history($order)

Called by add_to_cart().

$order is a hashref returned by L<Business::Cart::Generic::Util::Validator>.

Returns nothing.

=head2 save_order_item($order)

Called by add_to_cart().

$order is a hashref returned by L<Business::Cart::Generic::Util::Validator>.

Returns the primary key in the items table of the newly-inserted item.

=head1 Machine-Readable Change Log

The file CHANGES was converted into Changelog.ini by L<Module::Metadata::Changes>.

=head1 Version Numbers

Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.

=head1 Thanks

Many thanks are due to the people who chose to make osCommerce and PrestaShop, Zen Cart, etc, Open Source.

=head1 Support

Email the author, or log a bug on RT:

L<https://rt.cpan.org/Public/Dist/Display.html?Name=Business::Cart::Generic>.

=head1 Author

L<Business::Cart::Generic> was written by Ron Savage I<E<lt>ron@savage.net.auE<gt>> in 2011.

Home page: L<http://savage.net.au/index.html>.

=head1 Copyright

Australian copyright (c) 2011, Ron Savage.

	All Programs of mine are 'OSI Certified Open Source Software';
	you can redistribute them and/or modify them under the terms of
	The Artistic License, a copy of which is available at:
	http://www.opensource.org/licenses/index.html

=cut