diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 44a96566d..857aaf0b4 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -25,6 +25,8 @@ - fixed #10190: Matrix 2.0 - Strange characters in "Show Ratings" box - rfe: #10002: User Manager, view User's Profile (perlDreamer Consulting) - rfe: #10073: Account system to be able to return XML/JSON (perlDreamer Consulting) + - Replaced the tax system with a pluggable one. Included are a Generic plugin + (which works the same as the old system) and a plugin for EU merchants ( Martin Kamerbeek / Oqapi ) 7.7.3 - fixed #10094: double explanation in thread help diff --git a/docs/upgrades/upgrade_7.7.3-7.7.4.pl b/docs/upgrades/upgrade_7.7.3-7.7.4.pl index dc94bb5fb..bbd227525 100644 --- a/docs/upgrades/upgrade_7.7.3-7.7.4.pl +++ b/docs/upgrades/upgrade_7.7.3-7.7.4.pl @@ -36,6 +36,9 @@ extendSchedulerFields($session); allMaintenanceSingleton($session); unsetPackageFlags($session); +installPluggableTax( $session ); + + finish($session); # this line required #---------------------------------------------------------------------------- @@ -149,6 +152,60 @@ sub _getAnswer{ return $answer; } +#---------------------------------------------------------------------------- +sub installPluggableTax { + my $session = shift; + my $db = $session->db; + print "\tInstall tables for pluggable tax system..." unless $quiet; + + # Rename table for the Generic tax plugin + $db->write( 'alter table tax rename tax_generic_rates' ); + + # Create tax driver table + $db->write( 'create table taxDriver (className char(255) not null primary key, options mediumtext)' ); + + # Table for storing EU VAT numbers. + $db->write( <config->set( 'taxDrivers', [ + 'WebGUI::Shop::TaxDriver::Generic', + 'WebGUI::Shop::TaxDriver::EU', + ] ); + + # Add a setting to store the active tax plugin. + $session->setting->add( 'activeTaxPlugin', 'WebGUI::Shop::TaxDriver::Generic' ); + + # Add column to sku for storing each sku's tax configuration. + $db->write( "alter table sku add column taxConfiguration mediumtext " ); + + # Migrate the tax overrides of skus into the tax configuration column. + # Don't use getLineage because this has to be done for each revision. + my $sth = $db->read( "select assetId, revisionDate, overrideTaxRate, taxRateOverride from sku" ); + while (my $row = $sth->hashRef) { + my $config = { + overrideTaxRate => $row->{ overrideTaxRate } || 0, + taxRateOverride => $row->{ taxRateOverride } || 0, + }; + + $db->write( 'update sku set taxConfiguration=? where assetId=? and revisionDate=?', [ + to_json( $config ), + $row->{ assetId }, + $row->{ revisionDate }, + ]); + } + $sth->finish; + + print "Done.\n" unless $quiet; +} diff --git a/lib/WebGUI/Account/Shop.pm b/lib/WebGUI/Account/Shop.pm index 98cf169fd..fdd545565 100644 --- a/lib/WebGUI/Account/Shop.pm +++ b/lib/WebGUI/Account/Shop.pm @@ -53,6 +53,9 @@ sub appendCommonVars { $var->{ 'view_sales_url' } = $self->getUrl( 'module=shop;do=viewSales' ); $var->{ 'viewSalesIsActive' } = $method eq 'viewSales'; + + $var->{ 'manage_tax_url' } = $self->getUrl( 'module=shop;do=manageTaxData' ); + $var->{ 'manageTaxIsActive' } = $method eq 'manageTaxData'; } #------------------------------------------------------------------- @@ -201,6 +204,17 @@ sub www_managePurchases { return $self->processTemplate($var,$session->setting->get("shopMyPurchasesTemplateId")); } +#------------------------------------------------------------------- +sub www_manageTaxData { + my $self = shift; + my $session = $self->session; + + my $userScreen = WebGUI::Shop::Tax->new( $session )->getDriver->getUserScreen; + + + return $userScreen; +} + #------------------------------------------------------------------- =head2 www_view ( ) diff --git a/lib/WebGUI/Asset/Sku.pm b/lib/WebGUI/Asset/Sku.pm index 573034010..2d081e69e 100644 --- a/lib/WebGUI/Asset/Sku.pm +++ b/lib/WebGUI/Asset/Sku.pm @@ -20,7 +20,7 @@ use base 'WebGUI::Asset'; use WebGUI::International; use WebGUI::Inbox; use WebGUI::Shop::Cart; - +use JSON qw{ from_json to_json }; =head1 NAME @@ -102,6 +102,7 @@ sub definition { my $definition = shift; my %properties; tie %properties, 'Tie::IxHash'; + my $i18n = WebGUI::International->new($session, "Asset_Sku"); %properties = ( description => { @@ -125,20 +126,6 @@ sub definition { label => $i18n->get("display title"), hoverHelp => $i18n->get("display title help") }, - overrideTaxRate => { - tab => "shop", - fieldType => "yesNo", - defaultValue => 0, - label => $i18n->get("override tax rate"), - hoverHelp => $i18n->get("override tax rate help") - }, - taxRateOverride => { - tab => "shop", - fieldType => "float", - defaultValue => 0.00, - label => $i18n->get("tax rate override"), - hoverHelp => $i18n->get("tax rate override help") - }, vendorId => { tab => "shop", fieldType => "vendor", @@ -146,6 +133,11 @@ sub definition { label => $i18n->get("vendor"), hoverHelp => $i18n->get("vendor help") }, + taxConfiguration => { + noFormPost => 1, + fieldType => 'hidden', + defaultValue => '{}', + }, ); push(@{$definition}, { assetName=>$i18n->get('assetName'), @@ -206,6 +198,31 @@ sub getConfiguredTitle { return $self->getTitle; } +#------------------------------------------------------------------- +sub getEditForm { + my $self = shift; + my $session = $self->session; + + my $tabform = $self->SUPER::getEditForm; + + # Let the tax system add the form fields that are required by the active tax plugin for configuring the sku tax. + # WebGUI::Shop::Tax->new( $session )->appendSkuForm( $self->getId, $tabform->getTab('shop') ); + + my $taxDriver = WebGUI::Shop::Tax->getDriver( $session ); + my $definition = $taxDriver->skuFormDefinition; + my $config = $self->getTaxConfiguration( $taxDriver->className ); + my $shop = $tabform->getTab( 'shop' ); + + foreach my $fieldName ( keys %{ $definition } ) { + $shop->dynamicField( + %{ $definition->{ $fieldName } }, + name => $fieldName, + value => $config->{ $fieldName }, + ); + } + + return $tabform; +} #------------------------------------------------------------------- @@ -287,6 +304,20 @@ sub getRecurInterval { return undef; } +#------------------------------------------------------------------- +sub getTaxConfiguration { + my $self = shift; + my $namespace = shift; + + my $configs = eval { from_json( $self->getValue('taxConfiguration') ) }; + if ($@) { + $self->session->log->error( 'Tax configuration of asset ' . $self->getId . ' appears to be corrupt. :' . $@ ); + return undef; + } + + return $configs->{ $namespace } +} + #------------------------------------------------------------------- =head2 getTaxRate ( ) @@ -528,6 +559,20 @@ sub onRemoveFromCart { return undef; } +#------------------------------------------------------------------- +sub processPropertiesFromFormPost { + my $self = shift; + + my $output = $self->SUPER::processPropertiesFromFormPost( @_ ); + + my $taxDriver = WebGUI::Shop::Tax->new( $self->session )->getDriver; + $self->session->log->fatal( 'Could not instanciate tax driver.' ) unless $taxDriver; + + $self->setTaxConfiguration( $taxDriver->className, $taxDriver->processSkuFormPost ); + + return $output; +} + #------------------------------------------------------------------- =head2 processStyle ( output ) @@ -546,6 +591,28 @@ sub processStyle { return $self->getParent->processStyle($output); } +#------------------------------------------------------------------- +sub setTaxConfiguration { + my $self = shift; + my $namespace = shift; + my $configuration = shift; + + # Fetch current tax configurations + my $configs = eval { from_json( $self->getValue('taxConfiguration') ) }; + if ($@) { + $self->session->log->error( 'Tax configuration of asset ' . $self->getId . ' is corrupt.' ); + return undef; + } + + # Apply the new configuration for the given driver... + $configs->{ $namespace } = $configuration; + + # ...and persist it to the db. + $self->update( { + taxConfiguration => to_json( $configs ), + } ); +} + #------------------------------------------------------------------- =head2 www_view ( ) diff --git a/lib/WebGUI/Shop/Cart.pm b/lib/WebGUI/Shop/Cart.pm index e4d803f58..f776114bf 100644 --- a/lib/WebGUI/Shop/Cart.pm +++ b/lib/WebGUI/Shop/Cart.pm @@ -820,7 +820,7 @@ sub www_view { $self->update({shippingAddressId=>''}); } - + # if there is no shipping address we can't check out if (WebGUI::Error->caught) { $var{shippingPrice} = $var{tax} = $self->formatCurrency(0); @@ -830,7 +830,6 @@ sub www_view { else { $var{hasShippingAddress} = 1; $var{shippingAddress} = $address->getHtmlFormatted; - $var{tax} = $self->calculateTaxes; my $ship = WebGUI::Shop::Ship->new($self->session); my $options = $ship->getOptions($self); my %formOptions = (); @@ -843,7 +842,10 @@ sub www_view { $var{shippingPrice} = ($self->get("shipperId") ne "") ? $options->{$self->get("shipperId")}{price} : $options->{$defaultOption}{price}; $var{shippingPrice} = $self->formatCurrency($var{shippingPrice}); } - + + # Tax variables + $var{tax} = $self->calculateTaxes; + # POS variables $var{isCashier} = WebGUI::Shop::Admin->new($session)->isCashier; $var{posLookupForm} = WebGUI::Form::email($session, {name=>"posEmail"}) diff --git a/lib/WebGUI/Shop/Tax.pm b/lib/WebGUI/Shop/Tax.pm index 14071e2d1..a64e5aa25 100644 --- a/lib/WebGUI/Shop/Tax.pm +++ b/lib/WebGUI/Shop/Tax.pm @@ -3,12 +3,9 @@ package WebGUI::Shop::Tax; use strict; use Class::InsideOut qw{ :std }; -use WebGUI::Text; -use WebGUI::Storage; use WebGUI::Exception::Shop; use WebGUI::Shop::Admin; -use WebGUI::Shop::Cart; -use WebGUI::Shop::CartItem; +use WebGUI::Pluggable; use List::Util qw{sum}; =head1 NAME @@ -21,10 +18,6 @@ This package manages tax information, and calculates taxes on a shopping cart. 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. -Taxes are accumulated through increasingly specific geographic information. For example, you can -specify the sales tax for a whole country, then the additional sales tax for a state in the country, -all the way down to a single code inside of a city. - =head1 SYNOPSIS use WebGUI::Shop::Tax; @@ -39,65 +32,36 @@ These subroutines are available from this package: readonly session => my %session; -#------------------------------------------------------------------- - -=head2 add ( [$params] ) - -Add tax information to the table. Returns the taxId of the newly created tax information. - -=head3 $params - -A hash ref of the geographic and rate information. The country and taxRate parameters -must have defined values. - -=head4 country - -The country this tax information applies to. - -=head4 state - -The state this tax information applies to. state and country together are unique. - -=head4 city - -The ciy this tax information applies to. Cities are unique with state and country information. - -=head4 code - -The postal code this tax information applies to. codes are unique with state and country information. - -=head4 taxRate - -This is the tax rate for the location, as specified by the geographical -fields country, state, city and/or code. The tax rate is stored as -a percentage, like 5.5 . - -=cut - -sub add { - my $self = shift; - my $params = shift; - - WebGUI::Error::InvalidParam->throw(error => 'Must pass in a hashref of params') - unless ref($params) eq 'HASH'; - WebGUI::Error::InvalidParam->throw(error => "Missing required information.", param => 'country') - unless exists($params->{country}) and $params->{country}; - WebGUI::Error::InvalidParam->throw(error => "Missing required information.", param => 'taxRate') - unless exists($params->{taxRate}) and defined $params->{taxRate}; - - $params->{taxId} = 'new'; - my $id = $self->session->db->setRow('tax', 'taxId', $params); - return $id; -} +##------------------------------------------------------------------- +#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. The tax rate is calculated off -of the shipping address stored in the cart. If an item in the cart has an alternate -address, that is used instead. Finally, if the item in the cart has a Sku with a tax -rate override, that rate overrides all. Returns 0 if no shipping address has been attached to the cart yet. +Calculate the tax for the contents of the cart. + +=head3 cart + +An instanciated cart object. =cut @@ -107,241 +71,60 @@ sub calculate { WebGUI::Error::InvalidParam->throw(error => 'Must pass in a WebGUI::Shop::Cart object') unless ref($cart) eq 'WebGUI::Shop::Cart'; my $book = $cart->getAddressBook; - return 0 if $cart->get('shippingAddressId') eq ""; - my $address = $book->getAddress($cart->get('shippingAddressId')); - my $tax = 0; - ##Fetch the tax data for the cart address so it doesn't have to look it up for every item - ##in the cart with that address. - my $cartTaxables = $self->getTaxRates($address); + + # 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 $unitPrice = $sku->getPrice; - my $quantity = $item->get('quantity'); - ##Check for an item specific shipping address - my $taxables; + 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')) { - my $itemAddress = $book->getAddress($item->get('shippingAddressId')); - $taxables = $self->getTaxRates($itemAddress); + $itemAddress = $book->getAddress($item->get('shippingAddressId')); } - else { - $taxables = $cartTaxables; - } - ##Check for a SKU specific tax override rate - my $skuTaxRate = $sku->getTaxRate(); - my $itemTax; - if (defined $skuTaxRate) { - $itemTax = $skuTaxRate; - } - else { - $itemTax = sum(@{$taxables}); - } - $itemTax /= 100; - $tax += $unitPrice * $quantity * $itemTax; + + 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 delete ( [$params] ) +=head2 getDriver ( [ $session ] ) -Deletes data from the tax table by taxId. +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 $params +=head3 session -A hashref containing the taxId of the data to delete from the table. - -=head4 taxId - -The taxId of the data to delete from the table. +A WebGUI::Session object. Required in class context, optional in instance context. =cut -sub delete { - my $self = shift; - my $params = shift; - WebGUI::Error::InvalidParam->throw(error => 'Must pass in a hashref of params') - unless ref($params) eq 'HASH'; - WebGUI::Error::InvalidParam->throw(error => "Hash ref must contain a taxId key with a defined value") - unless exists($params->{taxId}) and defined $params->{taxId}; - $self->session->db->write('delete from tax where taxId=?', [$params->{taxId}]); - return; -} - -#------------------------------------------------------------------- - -=head2 exportTaxData ( ) - -Creates a tab deliniated file containing all the information from -the tax table. Returns a temporary WebGUI::Storage object containing -the file. The file will be named "siteTaxData.csv". - -=cut - -sub exportTaxData { - my $self = shift; - my $taxIterator = $self->getItems; - my @columns = grep { $_ ne 'taxId' } $taxIterator->getColumnNames; - my $taxData = WebGUI::Text::joinCSV(@columns) . "\n"; - while (my $taxRow = $taxIterator->hashRef() ) { - my @taxData = @{ $taxRow }{@columns}; - foreach my $column (@taxData) { - $column =~ tr/,/|/; ##Convert to the alternation syntax for the text file - } - $taxData .= WebGUI::Text::joinCSV(@taxData) . "\n"; +sub getDriver { + my $self = shift; + my $session = shift || $self->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; } - my $storage = WebGUI::Storage->createTemp($self->session); - $storage->addFileFromScalar('siteTaxData.csv', $taxData); - return $storage; -} -#------------------------------------------------------------------- - -=head2 getAllItems ( ) - -Returns an arrayref of hashrefs, where each hashref is the data for one row of -tax data. taxId is dropped from the dataset. - -=cut - -sub getAllItems { - my $self = shift; - my $taxes = $self->session->db->buildArrayRefOfHashRefs('select country,state,city,code,taxRate from tax order by country, state'); - return $taxes; -} - -#------------------------------------------------------------------- - -=head2 getItems ( ) - -Returns a WebGUI::SQL::Result object for accessing all of the data in the tax table. This -is a convenience method for listing and/or exporting tax data. - -=cut - -sub getItems { - my $self = shift; - my $result = $self->session->db->read('select * from tax order by country, state'); - return $result; -} - -#------------------------------------------------------------------- - -=head2 getTaxRates ( $address ) - -Given a WebGUI::Shop::Address object, return all rates associated with the address as an arrayRef. - -=cut - -sub getTaxRates { - my $self = shift; - my $address = shift; - WebGUI::Error::InvalidObject->throw(error => 'Need an address.', expected=>'WebGUI::Shop::Address', got=>(ref $address)) - unless ref($address) eq 'WebGUI::Shop::Address'; - my $country = $address->get('country'); - my $state = $address->get('state'); - my $city = $address->get('city'); - my $code = $address->get('code'); - my $result = $self->session->db->buildArrayRef( - q{ - select taxRate from tax where find_in_set(?, country) - and (state='' or find_in_set(?, state)) - and (city='' or find_in_set(?, city)) - and (code='' or find_in_set(?, code)) - }, - [ $country, $state, $city, $code, ]); - return $result; -} - -#------------------------------------------------------------------- - -=head2 importTaxData ( $filePath ) - -Import tax information from the specified file in CSV format. The -first line of the file should contain only the name of the columns, in -any order. It may not contain any comments. - -These are the column names, each is required: - -=over 4 - -=item * - -country - -=item * - -state - -=item * - -city - -=item * - -code - -=item * - -taxRate - -=back - -The following lines will contain tax information. Blank -lines and anything following a '#' sign will be ignored from -the second line of the file, on to the end. - -Returns 1 if the import has taken place. This is to help you know -if old data has been deleted and new has been inserted. If an error is -detected, it will throw exceptions. - -=head3 $filePath - -The path to a file with data to import into the Product system. - -=cut - -sub importTaxData { - my $self = shift; - my $filePath = shift; - WebGUI::Error::InvalidParam->throw(error => q{Must provide the path to a file}) - unless $filePath; - WebGUI::Error::InvalidFile->throw(error => qq{File could not be found}, brokenFile => $filePath) - unless -e $filePath; - WebGUI::Error::InvalidFile->throw(error => qq{File is not readable}, brokenFile => $filePath) - unless -r $filePath; - open my $table, '<', $filePath or - WebGUI::Error->throw(error => qq{Unable to open $filePath for reading: $!\n}); - my $headers; - $headers = <$table>; - chomp $headers; - my @headers = WebGUI::Text::splitCSV($headers); - WebGUI::Error::InvalidFile->throw(error => qq{Bad header found in the CSV file}, brokenFile => $filePath) - unless (join(q{-}, sort @headers) eq 'city-code-country-state-taxRate') - and (scalar @headers == 5); - my @taxData = (); - my $line = 1; - while (my $taxRow = <$table>) { - chomp $taxRow; - $taxRow =~ s/\s*#.+$//; - next unless $taxRow; - local $_; - my @taxRow = map { tr/|/,/; $_; } WebGUI::Text::splitCSV($taxRow); - WebGUI::Error::InvalidFile->throw(error => qq{Error found in the CSV file}, brokenFile => $filePath, brokenLine => $line) - unless scalar @taxRow == 5; - push @taxData, [ @taxRow ]; - } - ##Okay, if we got this far, then the data looks fine. - return unless scalar @taxData; - $self->session->db->beginTransaction; - $self->session->db->write('delete from tax'); - foreach my $taxRow (@taxData) { - my %taxRow; - @taxRow{ @headers } = @{ $taxRow }; ##Must correspond 1:1, or else... - $self->add(\%taxRow); - } - $self->session->db->commit; - return 1; + return $driver; } #------------------------------------------------------------------- @@ -362,6 +145,8 @@ sub new { return $self; } + + #------------------------------------------------------------------- =head2 session ( ) @@ -372,148 +157,35 @@ Accessor for the session object. Returns the session object. #------------------------------------------------------------------- -=head2 www_deleteTax ( ) +=head2 www_do ( ) -Delete a row of tax information, using the form variable taxId as -the id of the row to delete. +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_deleteTax { - my $self = shift; - my $session = $self->session; - my $admin = WebGUI::Shop::Admin->new($session); - return $session->privilege->insufficient - unless $admin->canManage; - my $taxId = $session->form->get('taxId'); - $self->delete({ taxId => $taxId }); - return $self->www_manage; -} - -#------------------------------------------------------------------- - -=head2 www_addTax ( ) - -Add new tax information into the database, via the UI. - -=cut - -sub www_addTax { +sub www_do { my $self = shift; my $session = $self->session; - my $admin = WebGUI::Shop::Admin->new($session); - return $session->privilege->insufficient - unless $admin->canManage; - my $params; - my ($form) = $session->quick('form'); - $params->{country} = $form->get('country', 'text'); - $params->{state} = $form->get('state', 'text'); - $params->{city} = $form->get('city', 'text'); - $params->{code} = $form->get('code', 'text'); - $params->{taxRate} = $form->get('taxRate', 'float'); - $self->add($params); - return $self->www_manage; -} -#------------------------------------------------------------------- + my $taxDriver = $self->getDriver; + my $method = 'www_' . $session->form->process( 'do' ); -=head2 www_exportTax ( ) + return "Invalid method name" unless $method =~ m{ ^[a-zA-Z0-9_]+$ }xms; -Export the entire tax table as a CSV file the user can download. + if ( $taxDriver->can( $method ) ) { + my $output = eval{ $taxDriver->$method }; -=cut - -sub www_exportTax { - my $self = shift; - my $session = $self->session; - my $admin = WebGUI::Shop::Admin->new($session); - return $session->privilege->insufficient - unless $admin->canManage; - my $storage = $self->exportTaxData(); - $self->session->http->setRedirect($storage->getUrl($storage->getFiles->[0])); - return "redirect"; -} - -#------------------------------------------------------------------- - -=head2 www_getTaxesAsJson ( ) - -Servers side pagination for tax data that is sent as JSON back to the browser to be -displayed in a YUI DataTable. - -=cut - -sub www_getTaxesAsJson { - my ($self) = @_; - my $session = $self->session; - my $admin = WebGUI::Shop::Admin->new($session); - return $session->privilege->insufficient - unless $admin->canManage; - my ($db, $form) = $session->quick(qw(db form)); - my $startIndex = $form->get('startIndex') || 0; - my $numberOfResults = $form->get('results') || 25; - my %goodKeys = qw/country 1 state 1 city 1 code 1 'tax rate' 1/; - my $sortKey = $form->get('sortKey'); - $sortKey = $goodKeys{$sortKey} == 1 ? $sortKey : 'country'; - my $sortDir = $form->get('sortDir'); - $sortDir = lc($sortDir) eq 'desc' ? 'desc' : 'asc'; - my @placeholders = (); - my $sql = 'select SQL_CALC_FOUND_ROWS * from tax'; - my $keywords = $form->get("keywords"); - if ($keywords ne "") { - $db->buildSearchQuery(\$sql, \@placeholders, $keywords, [qw{country state city code}]) - } - push(@placeholders, $startIndex, $numberOfResults); - $sql .= sprintf (" order by %s limit ?,?","$sortKey $sortDir"); - my %results = (); - my @records = (); - my $sth = $db->read($sql, \@placeholders); - while (my $record = $sth->hashRef) { - push(@records,$record); - } - $results{'recordsReturned'} = $sth->rows()+0; - $sth->finish; - $results{'records'} = \@records; - $results{'totalRecords'} = $db->quickScalar('select found_rows()')+0; ##Convert to numeric - $results{'startIndex'} = $startIndex; - $results{'sort'} = undef; - $results{'dir'} = $sortDir; - $session->http->setMimeType('application/json'); - return JSON::to_json(\%results); -} - -#------------------------------------------------------------------- - -=head2 www_importTax ( ) - -Import new tax data from a file provided by the user. This will replace the current -data with the new data. - -=cut - -sub www_importTax { - my $self = shift; - my $session = $self->session; - my $admin = WebGUI::Shop::Admin->new($session); - return $session->privilege->insufficient - unless $admin->canManage; - my $storage = WebGUI::Storage->create($session); - my $taxFile = $storage->addFileFromFormPost('importFile', 1); - eval { - $self->importTaxData($storage->getPath($taxFile)) if $taxFile; - }; - my ($exception, $status_message); - if ($exception = Exception::Class->caught('WebGUI::Error::InvalidFile')) { - $status_message = sprintf 'A problem was found with your file: %s', - $exception->error; - if ($exception->brokenLine) { - $status_message .= sprintf ' on line %d', $exception->brokenLine; + 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; } } - elsif ($exception = Exception::Class->caught()) { - $status_message = sprintf 'A problem happened during the import: %s', $exception->error; - } - return $self->www_manage($status_message); + + return "Cannot call method [$method] on active tax driver."; } #------------------------------------------------------------------- @@ -531,154 +203,107 @@ 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; - ##YUI specific datatable CSS - my ($style, $url) = $session->quick(qw(style url)); - $style->setLink($url->extras('/yui/build/fonts/fonts-min.css'), {rel=>'stylesheet', type=>'text/css'}); - $style->setLink($url->extras('yui/build/datatable/assets/skins/sam/datatable.css'), {rel=>'stylesheet', type => 'text/CSS'}); - $style->setLink($url->extras('yui/build/paginator/assets/skins/sam/paginator.css'), {rel=>'stylesheet', type => 'text/CSS'}); - $style->setScript($url->extras('/yui/build/utilities/utilities.js'), {type=>'text/javascript'}); - $style->setScript($url->extras('yui/build/json/json-min.js'), {type => 'text/javascript'}); - $style->setScript($url->extras('yui/build/paginator/paginator-min.js'), {type => 'text/javascript'}); - $style->setScript($url->extras('yui/build/datasource/datasource-min.js'), {type => 'text/javascript'}); - ##YUI Datatable - $style->setScript($url->extras('yui/build/datatable/datatable-min.js'), {type => 'text/javascript'}); - ##Default CSS - $style->setRawHeadTags(''); - my $i18n=WebGUI::International->new($session, 'Tax'); + my $self = shift; + my $status_message = shift; + my $session = $self->session; + my $admin = WebGUI::Shop::Admin->new( $session ); - my $exportForm = WebGUI::Form::formHeader($session,{action => $url->page('shop=tax;method=exportTax')}) - . WebGUI::Form::submit($session,{value=>$i18n->get('export tax','Shop'), extras=>q{style="float: left;"} }) - . WebGUI::Form::formFooter($session); - my $importForm = WebGUI::Form::formHeader($session,{action => $url->page('shop=tax;method=importTax')}) - . WebGUI::Form::submit($session,{value=>$i18n->get('import tax','Shop'), extras=>q{style="float: left;"} }) - . q{} - . WebGUI::Form::formFooter($session); + return $session->privilege->insufficient unless $admin->canManage; - my $addForm = WebGUI::HTMLForm->new($session,action=>$url->page('shop=tax;method=addTax')); - $addForm->text( - label => $i18n->get('country'), - hoverHelp => $i18n->get('country help'), - name => 'country', - ); - $addForm->text( - label => $i18n->get('state'), - hoverHelp => $i18n->get('state help'), - name => 'state', - ); - $addForm->text( - label => $i18n->get('city'), - hoverHelp => $i18n->get('city help'), - name => 'city', - ); - $addForm->text( - label => $i18n->get('code'), - hoverHelp => $i18n->get('code help'), - name => 'code', - ); - $addForm->float( - label => $i18n->get('tax rate'), - hoverHelp => $i18n->get('tax rate help'), - name => 'taxRate', - ); - $addForm->submit( - value => $i18n->get('add a tax'), - ); - my $output; - if ($status_message) { - $output = < -$status_message - -EOSM - } - - $output .= q| - - -
- -
-
|.$addForm->print.q|
-
|.$exportForm.$importForm.q|
-
+ my ($style, $url) = $session->quick( qw(style url) ); + my $i18n = WebGUI::International->new( $session, 'Tax' ); - - -|; - 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 $message = + 'Changing the active tax plugin will change the way tax is calulated on all products you sell. ' + . 'Are you really sure you want to switch?'; + + 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 => '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 => 'Cancel', extras => 'class="backwardButton"' } ) + . WebGUI::Form::formFooter( $session ); + + my $output = $message . $proceedForm . $cancelForm; + return $admin->getAdminConsole->render( $output, '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; diff --git a/lib/WebGUI/i18n/English/Account_Shop.pm b/lib/WebGUI/i18n/English/Account_Shop.pm index 666d643a3..d6fc4ae8d 100644 --- a/lib/WebGUI/i18n/English/Account_Shop.pm +++ b/lib/WebGUI/i18n/English/Account_Shop.pm @@ -54,6 +54,12 @@ our $I18N = { lastUpdated => 0, }, + 'manage tax label' => { + message => q{Manage Tax Settings}, + lastUpdated => 0, + context => q{Label for the manage tax tab}, + }, + }; 1; diff --git a/lib/WebGUI/i18n/English/Asset_Sku.pm b/lib/WebGUI/i18n/English/Asset_Sku.pm index fdcdffead..158e606f5 100644 --- a/lib/WebGUI/i18n/English/Asset_Sku.pm +++ b/lib/WebGUI/i18n/English/Asset_Sku.pm @@ -69,29 +69,7 @@ our $I18N = { context => q|help for vendor field| }, - 'override tax rate' => { - message => q|Override tax rate?|, - lastUpdated => 0, - context => q|A yes/no field asking whether to override tax rate.| - }, - 'override tax rate help' => { - message => q|Would you like to override the default tax rate for this item? Usually used in locales that have special or no tax on life essential items like food and clothing.|, - lastUpdated => 0, - context => q|help for override tax rate field| - }, - - 'tax rate override' => { - message => q|Tax Rate Override|, - lastUpdated => 0, - context => q|a field containing the percentage to use to calculate tax for this item| - }, - - 'tax rate override help' => { - message => q|What is the new percentage that should be used to calculate tax on this item?|, - lastUpdated => 0, - context => q|help for tax rate override field| - }, 'add to cart' => { message => q|Add To Cart|, diff --git a/lib/WebGUI/i18n/English/Tax.pm b/lib/WebGUI/i18n/English/Tax.pm index 7add09e43..4975700fa 100644 --- a/lib/WebGUI/i18n/English/Tax.pm +++ b/lib/WebGUI/i18n/English/Tax.pm @@ -73,6 +73,31 @@ our $I18N = { lastUpdated => 1206395083, }, + 'override tax rate' => { + message => q|Override tax rate?|, + lastUpdated => 0, + context => q|A yes/no field asking whether to override tax rate.| + }, + + 'override tax rate help' => { + message => q|Would you like to override the default tax rate for this item? Usually used in locales that have special or no tax on life essential items like food and clothing.|, + lastUpdated => 0, + context => q|help for override tax rate field| + }, + + 'tax rate override' => { + message => q|Tax Rate Override|, + lastUpdated => 0, + context => q|a field containing the percentage to use to calculate tax for this item| + }, + + 'tax rate override help' => { + message => q|What is the new percentage that should be used to calculate tax on this item?|, + lastUpdated => 0, + context => q|help for tax rate override field| + }, + + }; 1;