Forgot to 'svn add' a bunch of files.

This commit is contained in:
Martin Kamerbeek 2009-04-17 13:35:20 +00:00
parent 2e4ce42b76
commit b21c5f4389
4 changed files with 1327 additions and 0 deletions

View file

@ -0,0 +1,220 @@
package WebGUI::Shop::TaxDriver;
use strict;
use Class::InsideOut qw{ :std };
use JSON qw{ from_json to_json };
readonly session => my %session;
readonly messages => my %messages;
private options => my %options;
#-----------------------------------------------------------
sub appendTaxDetailVars {
my $self = shift;
my $var = shift;
return $var;
}
#-----------------------------------------------------------
sub canManage {
my $self = shift;
my $admin = WebGUI::Shop::Admin->new( $self->session );
return $admin->canManage;
}
#-----------------------------------------------------------
=head2 className {
Returns the class name of your plugin. You must overload this method in you own plugin.
=cut
sub className {
my $self = shift;
$self->session->log->fatal( "Tax plugin ($self) is required to overload the className method" );
}
#-----------------------------------------------------------
=head2 get ( [ property ] )
Returns the value of the requested configuration property. Returns a hash ref of all property/value pairs when no
specific property is passed.
=head3 property
The property whose value should be returned.
=cut
sub get {
my $self = shift;
my $key = shift;
my $options = $options{ id $self };
# Return safe copy of options hash if no key is passed.
return { %{ $options } } unless $key;
# Return option if key is passed.
return $options->{ $key } if exists $options->{ $key };
# Key does not exist.
$self->session->log->warn( "Non-existant option [$key] was queried by tax plugin $self" );
return undef;
}
#-----------------------------------------------------------
=head2 getConfigurationScreen ( )
Returns the configuration screen that contains the configuration options for this plugin in the admin console.
=cut
sub getConfigurationScreen {
return 'This plugin has no configuration options';
}
#-----------------------------------------------------------
=head2 getTaxRate ( sku, [ address ] )
Returns the tax rate in percents (eg. 19 for a rate of 19%) for the given sku and shipping address. Your tax driver
must overload this method.
Note that address is optional and that it's up to your plugin to handle that case.
=head3 sku
The sku for which the tax rate must be determined. Should be a WebGUI::Asset::Sku::* instance.
=head3 address
Optional, the shipping address for which to calculate the tax. Must be an instance of WebGUI::Shop::Address.
=head
=cut
sub getTaxRate {
my $self = shift;
$self->session->log->fatal("Tax plugin ". $self->className ." is required to overload getTaxRate");
}
#-----------------------------------------------------------
=head2 getUserScreen ( )
Returns the screen for entering per user configuration for this tax driver.
=cut
sub getUserScreen {
return 'There are no tax options to configure.';
}
#-----------------------------------------------------------
=head2 skuFormDefinition ( )
Returns a hash ref containing the form defintion for the per sku options for this tax driver.
=cut
sub skuFormDefinition {
return {};
}
#-------------------------------------------------------------------
=head2 new ( $session )
Constructor
=head3 session
Instanciated WebGUI::Session object.
=cut
sub new {
my $class = shift;
my $session = shift;
my $self = {};
bless $self, $class;
register $self;
my $id = id $self;
$session{ $id } = $session;
$messages{ $id } = [];
# Load plugin configuration
my $optionsJSON = $session->db->quickScalar( 'select options from taxDriver where className=?', [
$self->className,
] );
$options{ $id } = $optionsJSON ? from_json( $optionsJSON ) : {};
return $self;
}
#-------------------------------------------------------------------
=head2 processSkuFormPost ( )
Processes the form parameters defined in the skuFormDefinition method and returns a hash ref containing the result.
=cut
sub processSkuFormPost {
my $self = shift;
my $form = $self->session->form;
my $configuration = {};
my $definition = $self->skuFormDefinition;
foreach my $fieldName ( keys %{ $definition } ) {
my ($fieldType, $defaultValue) = @{ $definition->{ $fieldName } }{ qw{ fieldType defaultValue } };
$configuration->{ $fieldName } = $form->process( $fieldName, $fieldType, $defaultValue );
}
return $configuration;
}
#-----------------------------------------------------------
=head2 update ( properties )
Updates the properties of the tax driver according to those passed.
=head3 properties
Hash ref containing the properties to set.
=cut
sub update {
my $self = shift;
my $update = shift;
my $db = $self->session->db;
# update local options hash
$options{ id $self } = { %{ $options{ id $self } }, %{ $update } };
# Persist to db
$db->write( 'replace into taxDriver (className, options) values (?,?)', [
$self->className,
to_json( $options{ id $self } ),
] );
}
1;

View file

@ -0,0 +1,419 @@
package WebGUI::Shop::TaxDriver::EU;
use strict;
use SOAP::Lite;
use WebGUI::Content::Account;
use WebGUI::TabForm;
use WebGUI::Utility qw{ isIn };
use base qw{ WebGUI::Shop::TaxDriver };
my $EU_COUNTRIES = {
AT => 'Austria',
BE => 'Belgium',
BG => 'Bulgaria',
CY => 'Cyprus',
CZ => 'Czech Republic',
DE => 'Germany',
DK => 'Denmark',
EE => 'Estonia',
EL => 'Greece',
ES => 'Spain',
FI => 'Finland',
FR => 'France ',
GB => 'United Kingdom',
HU => 'Hungary',
IE => 'Ireland',
IT => 'Italy',
LT => 'Lithuania',
LU => 'Luxembourg',
LV => 'Latvia',
MT => 'Malta',
NL => 'Netherlands',
PL => 'Poland',
PT => 'Portugal',
RO => 'Romania',
SE => 'Sweden',
SI => 'Slovenia',
SK => 'Slovakia',
};
#-------------------------------------------------------------------
sub className {
return 'WebGUI::Shop::TaxDriver::EU';
}
#-------------------------------------------------------------------
sub getConfigurationScreen {
my $self = shift;
my $session = $self->session;
my $taxGroups = $self->get( 'taxGroups' ) || [];
# General setting form
my $f = WebGUI::HTMLForm->new( $session );
$f->hidden(
name => 'shop',
value => 'tax',
);
$f->hidden(
name => 'method',
value => 'do',
);
$f->hidden(
name => 'do',
value => 'saveConfiguration',
);
$f->selectBox(
name => 'shopCountry',
value => $self->get( 'shopCountry' ),
label => 'Residential country',
hoverHelp => 'The country where your shop resides.',
options => $EU_COUNTRIES,
);
$f->submit;
my $general = $f->print;
# VAT groups manager
my $vatGroups = '<b>VAT groups</b><br />';
$vatGroups .= q{<table><thead><tr><th>Group name</th><th>Rate</th></tr></thead><tbody>};
foreach my $group ( @{ $taxGroups} ) {
my $deleteUrl = $session->url->page('shop=tax;method=do;do=deleteGroup;groupId=' . $group->{ id });
my $makeDefaultUrl = $session->url->page('shop=tax;method=do;do=setDefaultGroup;groupId=' . $group->{ id });
$vatGroups .=
q{<tr><td>}
. join( '</td><td>',
$group->{ name } . ( $group->{ id } eq $self->get( 'defaultGroup' ) ? '<i>(default)</i>' : '' ),
$group->{ rate },
qq{<a href="$deleteUrl">delete</a>},
qq{<a href="$makeDefaultUrl">Set as default group</a>},
)
. q{</td></tr>};
}
$vatGroups .= q{</tbody></table>};
$vatGroups .=
WebGUI::Form::formHeader( $session )
. WebGUI::Form::hidden( $session, { name => 'shop', value => 'tax' } )
. WebGUI::Form::hidden( $session, { name => 'method', value => 'do' } )
. WebGUI::Form::hidden( $session, { name => 'do', value => 'addGroup' } )
. 'Name '
. WebGUI::Form::text( $session, { name => 'name' } )
. ' Rate '
. WebGUI::Form::float( $session, { name => 'rate' } )
. '%'
. WebGUI::Form::submit( $session, { value => 'Add' } )
. WebGUI::Form::formFooter( $session );
# Wrap output in a YUI Tab widget.
my ($style, $url) = $session->quick( qw{ style url } );
$style->setLink($self->{_css},{rel=>"stylesheet", rel=>"stylesheet",type=>"text/css"});
$style->setLink($url->extras('/yui/build/fonts/fonts-min.css'),{type=>"text/css", rel=>"stylesheet"});
$style->setLink($url->extras('/yui/build/tabview/assets/skins/sam/tabview.css'),{type=>"text/css", rel=>"stylesheet"});
$style->setLink($url->extras('/yui/build/container/assets/container.css'),{ type=>'text/css', rel=>"stylesheet" });
$style->setLink($url->extras('/hoverhelp.css'),{ type=>'text/css', rel=>"stylesheet" });
$style->setScript($url->extras('/yui/build/utilities/utilities.js'),{ type=>'text/javascript' });
$style->setScript($url->extras('/yui/build/container/container-min.js'),{ type=>'text/javascript' });
$style->setScript($url->extras('/yui/build/tabview/tabview-min.js'),{ type=>'text/javascript' });
$style->setScript($url->extras('/hoverhelp.js'),{ type=>'text/javascript' });
my $output = <<EOHTML;
<div class="yui-skin-sam">
<div id="webguiTabForm" class="yui-navset">
<ul class="yui-nav">
<li class="selected"><a href="#tab1" ><em>General configuration</em></a></li>
<li ><a href="#tab2" ><em>VAT Groups</em></a></li>
</ul>
<div class="yui-content">
<div id="tab1">$general</div>
<div id="tab2">$vatGroups</div>
</div>
</div>
</div>
<script type="text/javascript"> var tabView = new YAHOO.widget.TabView('webguiTabForm'); </script>
EOHTML
return $output;
}
#-------------------------------------------------------------------
sub getCountryCode {
my $self = shift;
my $countryName = shift;
# Do reverse lookup on eu countries hash
return { reverse %{ $EU_COUNTRIES } }->{ $countryName };
}
#-------------------------------------------------------------------
sub getCountryName {
my $self = shift;
my $countryCode = shift;
return $EU_COUNTRIES->{ $countryCode };
}
#-------------------------------------------------------------------
sub getGroupRate {
my $self = shift;
my $taxGroupId = shift;
my $taxGroups = $self->get( 'taxGroups' );
my ($group) = grep { $_->{ id } eq $taxGroupId } @{ $taxGroups };
return $group->{ rate };
}
#-------------------------------------------------------------------
sub getUserScreen {
my $self = shift;
my $url = $self->session->url;
my $output = '<b>VAT Numbers</b><br />'
. '<table><thead><tr><th>Country</th><th>VAT Number</th></tr></thead><tbody>';
foreach my $number ( @{ $self->getVATNumbers } ) {
my $deleteUrl = $url->page('shop=tax;method=do;do=deleteVATNumber;vatNumber='.$number->{ vatNumber });
$output .=
'<tr><td>'
. join( '</td><td>',
$self->getCountryName( $number->{ countryCode } ),
$number->{ vatNumber },
$number->{ name },
$number->{ address },
$number->{ approved },
qq{<a href="$deleteUrl">delete</a>},
)
. '</td></tr>'
;
}
$output .= '</tbody></table>';
my $f = WebGUI::HTMLForm->new( $self->session );
$f->hidden(
name => 'shop',
value => 'tax',
);
$f->hidden(
name => 'method',
value => 'do',
);
$f->hidden(
name => 'do',
value => 'addVATNumber',
);
$f->text(
name => 'vatNumber',
label => 'VAT Number',
);
$f->submit(
value => 'Add',
);
$output .= $f->print;
return $output;
}
#-------------------------------------------------------------------
sub getTaxRate {
my $self = shift;
my $sku = shift;
my $address = shift;
my $config = $sku->getTaxConfiguration( $self->className );
# Fetch the tax group from the sku. If the sku has none, use the default tax group.
my $taxGroupId = $config->{ taxGroup } || $self->get( 'defaultGroup' );
my $taxRate = $self->getGroupRate( $taxGroupId );
# No shipping address yet. Return group tax rate.
return $taxRate unless defined $address;
# Shipping address outside EU? That means exporting so no VAT.
my $country = $self->getCountryCode( $address->get( 'country' ) );
return 0 unless defined $country;
# Shipping address in same country as shop? Pay VAT;
return $taxRate if $country eq $self->get('shopCountry');
# Customer has VAT number in shipping country? Exempt from paying VAT.
return 0 if $self->hasVATNumber( $country );
# Customer has no VAT number and resides in EU. Pay VAT;
return $taxRate;
}
#-------------------------------------------------------------------
sub getVATNumbers {
my $self = shift;
my $countryCode = shift;
my $session = $self->session;
my $sql = 'select * from tax_eu_vatNumbers where userId=?';
my $placeHolders = [ $session->user->userId ];
if ( $countryCode ) {
$sql .= ' and countryCode=?';
push @{ $placeHolders }, $countryCode;
}
my $numbers = $session->db->buildArrayRefOfHashRefs( $sql, $placeHolders );
return $numbers;
}
#-------------------------------------------------------------------
sub hasVATNumber {
my $self = shift;
my $countryCode = shift;
my $numbers = $self->getVATNumbers( $countryCode );
return 0 unless @{ $numbers };
return $numbers->[0]->{ approved };
}
#-------------------------------------------------------------------
sub skuFormDefinition {
my $self = shift;
my $taxGroups = $self->get( 'taxGroups' );
# If no tax groups are defined there's no need to add a form element.
return {} unless $taxGroups;
my %options =
map { $_->{ id } => "$_->{ name } ($_->{ rate } \%)" }
@{ $taxGroups };
tie my %definition, 'Tie::IxHash', (
taxGroup => {
fieldType => 'selectBox',
label => 'Tax group',
options => \%options,
}
);
return \%definition;
}
#-------------------------------------------------------------------
sub www_addGroup {
my $self = shift;
my $form = $self->session->form;
return $self->session->privilege->insufficient unless $self->canManage;
my $groups = $self->get( 'taxGroups' ) || [];
my $name = $form->process( 'name' );
my $rate = $form->process( 'rate' );
my $id = $self->session->id->generate;
push @{ $groups }, {
name => $name,
rate => $rate,
id => $id,
};
$self->update( { taxGroups => $groups } );
return '';
}
#-------------------------------------------------------------------
sub www_addVATNumber {
my $self = shift;
my $session = $self->session;
my ($db, $form) = $session->quick( qw{ db form } );
return $session->privilege->insufficient if $session->user->isVisitor;
my $vatNumber = uc $form->process( 'vatNumber' );
my ($countryCode, $number) = $vatNumber =~ m/^([A-Z]{2})([A-Z0-9]+)$/;
return 'Illegal country code' unless isIn( $countryCode, keys %{ $EU_COUNTRIES } );
return 'You already have a VAT number for this country.' if @{ $self->getVATNumbers( $countryCode ) };
# Check VAT number via SOAP interface.
# TODO: Handle timeouts.
my $soap = SOAP::Lite->service('http://ec.europa.eu/taxation_customs/vies/api/checkVatPort?wsdl');
my $isValid = ( $soap->checkVat( $countryCode, $number ) )[ 3 ] || 0;
# Write the code to the db.
$db->write( 'replace into tax_eu_vatNumbers (userId,countryCode,vatNumber,approved) values (?,?,?,?)', [
$self->session->user->userId,
$countryCode,
$vatNumber,
$isValid,
] );
my $instance = WebGUI::Content::Account->createInstance($session,"shop");
return $instance->displayContent( $instance->callMethod("manageTaxData", [], $session->user->userId) );
}
#-------------------------------------------------------------------
sub www_deleteGroup {
my $self = shift;
my $form = $self->session->form;
return $self->session->privilege->insufficient unless $self->canManage;
my $taxGroups = $self->get( 'taxGroups' );
my $removeGroupId = $form->process( 'groupId' );
my @newGroups = grep { $_->{ id } ne $removeGroupId } @{ $taxGroups };
$self->update( { taxGroups => \@newGroups } );
return '';
}
#-------------------------------------------------------------------
sub www_deleteVATNumber {
my $self = shift;
my $session = $self->session;
return $session->privilege->insufficient unless $session->user->isVisitor;
$session->db->write( 'delete from tax_eu_vatNumbers where userId=? and vatNumber=?', [
$session->user->userId,
$session->form->process( 'vatNumber' ),
] );
my $instance = WebGUI::Content::Account->createInstance($session,"shop");
return $instance->displayContent( $instance->callMethod("manageTaxData", [], $session->user->userId) );
}
#-------------------------------------------------------------------
sub www_saveConfiguration {
my $self = shift;
my $form = $self->session->form;
return $self->session->privilege->insufficient unless $self->canManage;
$self->update( {
shopCountry => $form->process( 'shopCountry', 'selectBox' ),
} );
return '';
}
#-------------------------------------------------------------------
sub www_setDefaultGroup {
my $self = shift;
my $form = $self->session->form;
return $self->session->privilege->insufficient unless $self->canManage;
$self->update( {
defaultGroup => $form->process( 'groupId' ),
} );
return '';
}
1;

View file

@ -0,0 +1,688 @@
package WebGUI::Shop::TaxDriver::Generic;
use strict;
use WebGUI::Text;
use WebGUI::Storage;
use WebGUI::Exception::Shop;
use List::Util qw{ sum };
use base qw{ WebGUI::Shop::TaxDriver };
=head1 NAME
Package WebGUI::Shop::TaxDriver::Generic
=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.
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;
my $tax = WebGUI::Shop::Tax->new($session);
=head1 METHODS
These subroutines are available from this package:
=cut
#-------------------------------------------------------------------
=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_generic_rates', 'taxId', $params);
return $id;
}
#-------------------------------------------------------------------
=head2 getTaxRate ( sku, address )
Returns the tax rate for the given sku with the given shipping address.
=head3 sku
An instanciated WebGUI::Asset::Sku object.
=head3 address
An instanciated WebGUI::Shop::Address object containing the shipping address for the sku.
=cut
sub getTaxRate {
my $self = shift;
my $sku = shift;
my $address = shift;
my $session = $self->session;
my $config = $sku->getTaxConfiguration( $self->className );
# Check params
WebGUI::Error::InvalidParam->throw(error => 'Must pass in a WebGUI::Asset::Sku object')
unless $sku->isa( 'WebGUI::Asset::Sku' );
WebGUI::Error::InvalidParam->throw(error => 'Must pass in a WebGUI::Shop::Address object')
if $address && !$address->isa( 'WebGUI::Shop::Address' );
# Check if the sku has a tax rate override, and return that if it has.
if ( $config->{ overrideTaxRate } ) {
return $config->{ taxRateOverride };
}
# No tax rate override, so tax is calculated from the tax tables.
# If no address is supplied yet, return 0%
return 0 unless defined $address;
# Fetch the taxes for this address and cache it for later use.
my $taxables = $session->stow->get( 'genericTaxables_' . $address->getId );
unless ($taxables) {
$taxables = $self->getTaxRates($address);
$session->stow->set( 'genericTaxables_' . $address->getId, $taxables );
}
# Check for a SKU specific tax override rate
my $itemTax = sum @{ $taxables };
return $itemTax;
}
#-------------------------------------------------------------------
sub className {
return 'WebGUI::Shop::TaxDriver::Generic';
}
#-------------------------------------------------------------------
=head2 delete ( [$params] )
Deletes data from the tax table by taxId.
=head3 $params
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.
=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_generic_rates 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";
}
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_generic_rates 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_generic_rates 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_generic_rates 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_generic_rates');
foreach my $taxRow (@taxData) {
my %taxRow;
@taxRow{ @headers } = @{ $taxRow }; ##Must correspond 1:1, or else...
$self->add(\%taxRow);
}
$self->session->db->commit;
return 1;
}
#-------------------------------------------------------------------
sub skuFormDefinition {
my $self = shift;
my $i18n = WebGUI::International->new( $self->session, 'Tax' );
tie my %definition, 'Tie::IxHash', (
overrideTaxRate => {
fieldType => "yesNo",
defaultValue => 0,
label => $i18n->get("override tax rate"),
hoverHelp => $i18n->get("override tax rate help")
},
taxRateOverride => {
fieldType => "float",
defaultValue => 0.00,
label => $i18n->get("tax rate override"),
hoverHelp => $i18n->get("tax rate override help")
},
);
return \%definition;
}
#-------------------------------------------------------------------
=head2 www_deleteTax ( )
Delete a row of tax information, using the form variable taxId as
the id of the row to delete.
=cut
sub www_deleteTax {
my $self = shift;
my $session = $self->session;
return $session->privilege->insufficient unless $self->canManage;
my $taxId = $session->form->get('taxId');
$self->delete({ taxId => $taxId });
return '';
}
#-------------------------------------------------------------------
=head2 www_addTax ( )
Add new tax information into the database, via the UI.
=cut
sub www_addTax {
my $self = shift;
my $session = $self->session;
return $session->privilege->insufficient unless $self->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 '';
}
#-------------------------------------------------------------------
=head2 www_exportTax ( )
Export the entire tax table as a CSV file the user can download.
=cut
sub www_exportTax {
my $self = shift;
my $session = $self->session;
return $session->privilege->insufficient unless $self->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;
return $session->privilege->insufficient unless $self->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_generic_rates';
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;
return $session->privilege->insufficient unless $self->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;
}
}
elsif ($exception = Exception::Class->caught()) {
$status_message = sprintf 'A problem happened during the import: %s', $exception->error;
}
$session->stow->set( 'tax_message', $status_message );
return '';
}
#-------------------------------------------------------------------
=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 getConfigurationScreen {
my $self = shift;
my $session = $self->session;
my $status_message = $session->stow->get( 'tax_message' );
return $session->privilege->insufficient unless $self->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('<style type="text/css"> #paging a { color: #0000de; } #search, #export form { display: inline; } </style>');
my $i18n=WebGUI::International->new($session, 'Tax');
my $exportForm = WebGUI::Form::formHeader($session,{action => $url->page('shop=tax;method=do;do=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=do;do=importTax')})
. WebGUI::Form::submit($session,{value=>$i18n->get('import tax','Shop'), extras=>q{style="float: left;"} })
. q{<input type="file" name="importFile" size="10" />}
. WebGUI::Form::formFooter($session);
my $addForm = WebGUI::HTMLForm->new($session,action=>$url->page('shop=tax;method=do;do=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 = <<EOSM;
<div class="error">
$status_message
</div>
EOSM
}
$output .= q|
<div class="yui-skin-sam">
<div id="search"><form id="keywordSearchForm"><input type="text" name="keywords" id="keywordsField" /><input type="submit" value="|.$i18n->get(364, 'WebGUI').q|" /></form></div>
<div id="dynamicdata"></div>
<div id="adding">|.$addForm->print.q|</div>
<div id="importExport">|.$exportForm.$importForm.q|</div>
</div>
<script type="text/javascript">
var taxtable = function() {
// Column definitions
formatDeleteTaxId = function(elCell, oRecord, oColumn, orderNumber) {
elCell.innerHTML = '<a href="|.$url->page(q{shop=tax;method=do;do=deleteTax}).q|;taxId='+oRecord.getData('taxId')+'">|.$i18n->get('delete').q|</a>';
};
var myColumnDefs = [ // sortable:true enables sorting
{key:"country", label:"|.$i18n->get('country').q|", sortable: true},
{key:"state", label:"|.$i18n->get('state').q|", sortable: true},
{key:"city", label:"|.$i18n->get('city').q|", sortable: true},
{key:"code", label:"|.$i18n->get('code').q|", sortable: true},
{key:"taxRate", label:"|.$i18n->get('tax rate').q|"},
{key:"taxId", label:"", formatter:formatDeleteTaxId}
];
// DataSource instance
var myDataSource = new YAHOO.util.DataSource("|.$url->page('shop=tax;method=do;do=getTaxesAsJson;').q|");
myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
myDataSource.responseSchema = {
resultsList: "records",
fields: [
{key:"country", parser:"string"},
{key:"state", parser:"string"},
{key:"city", parser:"string"},
{key:"code", parser:"string"},
{key:"taxRate", parser:"number"},
{key:"taxId", parser:"string"}
],
metaFields: {
totalRecords: "totalRecords" // Access to value in the server response
}
};
// DataTable configuration
var myConfigs = {
initialRequest: 'startIndex=0;results=25', // Initial request for first page of data
dynamicData: true, // Enables dynamic server-driven data
sortedBy : {key:"country", dir:YAHOO.widget.DataTable.CLASS_ASC}, // Sets UI initial sort arrow
paginator: new YAHOO.widget.Paginator({ rowsPerPage:25 }) // Enables pagination
};
// DataTable instance
var myDataTable = new YAHOO.widget.DataTable("dynamicdata", myColumnDefs, myDataSource, myConfigs);
// Update totalRecords on the fly with value from server to allow pagination
myDataTable.handleDataReturnPayload = function(oRequest, oResponse, oPayload) {
oPayload.totalRecords = oResponse.meta.totalRecords;
return oPayload;
}
//Setup the form to submit an AJAX request back to the site.
YAHOO.util.Dom.get('keywordSearchForm').onsubmit = function () {
var state = myDataTable.getState();
state.pagination.recordOffset = 0;
myDataSource.sendRequest('keywords=' + YAHOO.util.Dom.get('keywordsField').value + ';startIndex=0;results=25', {success: myDataTable.onDataReturnInitializeTable, scope:myDataTable, argument:state});
return false;
};
return {
ds: myDataSource,
dt: myDataTable
};
}();
</script>
|;
return $output;
}
1;