package WebGUI::Shop::Tax; =head1 LEGAL ------------------------------------------------------------------- WebGUI is Copyright 2001-2009 Plain Black Corporation. ------------------------------------------------------------------- Please read the legal notices (docs/legal.txt) and the license (docs/license.txt) that came with this distribution before using this software. ------------------------------------------------------------------- http://www.plainblack.com info@plainblack.com ------------------------------------------------------------------- =cut use strict; use Class::InsideOut qw{ :std }; use WebGUI::Exception::Shop; use WebGUI::Shop::Admin; use WebGUI::Pluggable; use List::Util qw{sum}; =head1 NAME Package WebGUI::Shop::Tax =head1 DESCRIPTION This package manages tax information, and calculates taxes on a shopping cart. It isn't a classic object in that the only data it contains is a WebGUI::Session object, but it does provide several methods for handling the information in the tax tables. =head1 SYNOPSIS use WebGUI::Shop::Tax; my $tax = WebGUI::Shop::Tax->new($session); =head1 METHODS These subroutines are available from this package: =cut readonly session => my %session; ##------------------------------------------------------------------- #sub appendSkuForm { # my $self = shift; # my $assetId = shift; # my $form = shift; # my $db = $self->session->db; # # my $values = $db->buildHashRef( 'select name, value from skuTaxConfiguration where assetId=?', [ # $assetId, # ] ); # # my $definition = $self->getDriver->skuFormDefinition; # foreach my $fieldName (keys %{ $definition }) { # $form->dynamicField( # %{ $definition->{ $fieldName } }, # name => $fieldName, # value => $values->{ $fieldName }, # ); # } #} #------------------------------------------------------------------- =head2 calculate ( $cart ) Calculate the tax for the contents of the cart. =head3 cart An instanciated cart object. =cut sub calculate { my $self = shift; my $cart = shift; WebGUI::Error::InvalidParam->throw(error => 'Must pass in a WebGUI::Shop::Cart object') unless ref($cart) eq 'WebGUI::Shop::Cart'; my $book = $cart->getAddressBook; # Fetch the default shipping address for each item in the cart that hasn't set its own. my $shippingAddress = $book->getAddress( $cart->get('shippingAddressId') ) if $cart->get('shippingAddressId'); my $driver = $self->getDriver; my $tax = 0; foreach my $item (@{ $cart->getItems }) { my $sku = $item->getSku; my $quantity = $item->get('quantity'); my $unitPrice = $sku->getPrice; # Check if this cart item overrides the shipping address. If it doesn't, use the default shipping address. my $itemAddress = $shippingAddress; if (defined $item->get('shippingAddressId')) { $itemAddress = $book->getAddress($item->get('shippingAddressId')); } my $taxRate = $driver->getTaxRate( $sku, $itemAddress ); # Calc the monetary tax for the given quantity of this item and add it to the total. $tax += $unitPrice * $quantity * $taxRate / 100; } return $tax; } #------------------------------------------------------------------- =head2 getDriver ( [ $session ] ) Return an instance of the enabled tax driver. This method can be invoked both as class or instance method. If you invoke this method as a class method you must pass a WebGUI::Session object. =head3 session A WebGUI::Session object. Required in class context, optional in instance context. =cut sub getDriver { my $self = shift; my $session = shift || $self->session; WebGUI::Error::InvalidObject->throw( expected => "WebGUI::Session", got => (ref $session), error => "Need a session." ) unless $session && $session->isa( 'WebGUI::Session' ); my $className = $session->setting->get( 'activeTaxPlugin' ); my $driver = eval { WebGUI::Pluggable::instanciate( $className, 'new', [ $session ] ); }; if ($@) { $session->log->error("Can't instanciate tax driver [$className] because $@"); return undef; } return $driver; } #------------------------------------------------------------------- =head2 new ( $session ) Constructor for the WebGUI::Shop::Tax. Returns a WebGUI::Shop::Tax object. =cut sub new { my $class = shift; my $session = shift; WebGUI::Error::InvalidObject->throw( expected => "WebGUI::Session", got => (ref $session), error => "Need a session." ) unless $session && $session->isa( 'WebGUI::Session' ); my $self = {}; bless $self, $class; register $self; $session{ id $self } = $session; return $self; } #------------------------------------------------------------------- =head2 session ( ) Accessor for the session object. Returns the session object. =cut #------------------------------------------------------------------- =head2 www_do ( ) Allows tax drivers to define their own www_ methods. Pass the www_ method that must be executed in the 'do' form var. =cut sub www_do { my $self = shift; my $session = $self->session; my $taxDriver = $self->getDriver; my $method = 'www_' . $session->form->process( 'do' ); return "Invalid method name" unless $method =~ m{ ^[a-zA-Z0-9_]+$ }xms; if ( $taxDriver->can( $method ) ) { my $output = eval{ $taxDriver->$method }; if ($@) { $session->log->error("An error occurred while executing method [$method] on active tax driver: $@"); return "An error occurred while executing a method on a tax driver. Please consult the webgui log."; } else { return $output || $self->www_manage; } } return "Cannot call method [$method] on active tax driver."; } #------------------------------------------------------------------- =head2 www_manage ( $status_message ) User interface to manage taxes. Provides a list of current taxes, and forms for adding new tax info, exporting and importing sets of taxes, and deleting individual tax data. =head3 $status_message A message to display to the user. This is usually a problem that was found during import. =cut sub www_manage { my $self = shift; my $status_message = shift; my $session = $self->session; my $admin = WebGUI::Shop::Admin->new( $session ); return $session->privilege->insufficient unless $admin->canManage; my ($style, $url) = $session->quick( qw(style url) ); my $i18n = WebGUI::International->new( $session, 'Tax' ); my $activePlugin = $session->setting->get( 'activeTaxPlugin' ); my $plugins = $session->config->get( 'taxDrivers' ); my %options = map { $_ => $_ } @{ $plugins }; my $pluginSwitcher = '
'.$i18n->get('Active tax plugin') . '' . WebGUI::Form::formHeader( $session ) . WebGUI::Form::hidden( $session, { name => 'shop', value => 'tax' } ) . WebGUI::Form::hidden( $session, { name => 'method', value => 'setActivePlugin' } ) . $i18n->get('Active tax plugin') . ' ' . WebGUI::Form::selectBox( $session, { name => 'className', value => $activePlugin, options => \%options } ) . WebGUI::Form::submit( $session, { value => $i18n->get('Switch') } ) . WebGUI::Form::formFooter( $session ) . '
' ; # my $output; # if ($status_message) { # $output = qq{
$status_message
}; # } my $taxDriver = $self->getDriver; my $output = $pluginSwitcher . '
Plugin configuration' . $taxDriver->getConfigurationScreen . '
' ; return $admin->getAdminConsole->render($output, $i18n->get('taxes', 'Shop')); } #------------------------------------------------------------------- =head2 www_setActivePlugin ( ) Displays a warning that informs users that they're about to change the active taxing plugin. Includes a confirm and cancel button. =cut sub www_setActivePlugin { my $self = shift; my $session = $self->session; my $admin = WebGUI::Shop::Admin->new( $session ); return $session->privilege->insufficient unless $admin->canManage; my $i18n = WebGUI::International->new( $session, 'Tax' ); my $message = $i18n->get('Stern tax warning'); my $proceedForm = WebGUI::Form::formHeader( $session ) . WebGUI::Form::hidden( $session, { name => 'shop', value => 'tax' } ) . WebGUI::Form::hidden( $session, { name => 'method', value => 'setActivePluginConfirm' } ) . WebGUI::Form::hidden( $session, { name => 'className', value => $session->form->process('className') } ) . WebGUI::Form::submit( $session, { value => $i18n->get('Proceed') } ) . WebGUI::Form::formFooter( $session ); my $cancelForm = WebGUI::Form::formHeader( $session ) . WebGUI::Form::hidden( $session, { name => 'shop', value => 'tax' } ) . WebGUI::Form::hidden( $session, { name => 'method', value => 'manage' } ) . WebGUI::Form::submit( $session, { value => $i18n->get('cancel','WebGUI'), extras => 'class="backwardButton"' } ) . WebGUI::Form::formFooter( $session ); my $output = $message . $proceedForm . $cancelForm; return $admin->getAdminConsole->render( $output, $i18n->get('Switch tax plugin') ); } #------------------------------------------------------------------- =head2 www_setActivePluginConfirm ( ) Actually changes the active tax driver. =cut sub www_setActivePluginConfirm { my $self = shift; my $session = $self->session; my $admin = WebGUI::Shop::Admin->new( $session ); return $session->privilege->insufficient unless $admin->canManage; my $className = $session->form->process( 'className', 'className' ); #### TODO: Check aginst list of available plugins. $session->setting->set( 'activeTaxPlugin', $className ); return $self->www_manage; } 1;