web interface for importing and exporting products. Additional tests for the import/export API

This commit is contained in:
Colin Kuskie 2008-06-05 04:10:48 +00:00
parent 9a9e94a0de
commit 994da561a5
7 changed files with 264 additions and 88 deletions

View file

@ -21,6 +21,7 @@ use WebGUI::Shop::AddressBook;
use WebGUI::Shop::Cart; use WebGUI::Shop::Cart;
use WebGUI::Shop::Credit; use WebGUI::Shop::Credit;
use WebGUI::Shop::Pay; use WebGUI::Shop::Pay;
use WebGUI::Shop::Products;
use WebGUI::Shop::Ship; use WebGUI::Shop::Ship;
use WebGUI::Shop::Tax; use WebGUI::Shop::Tax;
use WebGUI::Shop::Transaction; use WebGUI::Shop::Transaction;
@ -189,6 +190,27 @@ sub www_pay {
#------------------------------------------------------------------- #-------------------------------------------------------------------
=head2 www_products ()
Hand off to the tax system.
=cut
sub www_products {
my $session = shift;
my $output = undef;
my $method = "www_".$session->form->get("method");
if ($method ne "www_" && WebGUI::Shop::Products->can($method)) {
$output = $WebGUI::Shop::Products->$method($session);
}
else {
WebGUI::Error::MethodNotFound->throw(error=>"Couldn't call non-existant method $method", method=>$method);
}
return $output;
}
#-------------------------------------------------------------------
=head2 www_ship () =head2 www_ship ()
Hand off to the shipper. Hand off to the shipper.

View file

@ -141,26 +141,32 @@ sub importProducts {
} }
##Okay, if we got this far, then the data looks fine. ##Okay, if we got this far, then the data looks fine.
return unless scalar @productData; return unless scalar @productData;
my $fetchProductId = $session->db->prepare('select assetId from Product where mastersku=? order by revisionDate DESC limit 1'); my $fetchProductId = $session->db->prepare('select p.assetId from Product as p join sku as s on p.assetId=s.assetId and p.revisionDate=s.revisionDate where s.sku=? order by p.revisionDate DESC limit 1');
my $node = WebGUI::Asset::Sku::Product->getProductImportNode($session); my $node = WebGUI::Asset::Sku::Product->getProductImportNode($session);
@headers = map { $_ eq 'shortdescription' ? 'shortdesc' : $_ } @headers;
PRODUCT: foreach my $productRow (@productData) { PRODUCT: foreach my $productRow (@productData) {
my %productRow; my %productRow;
##Order the data according to the headers, in whatever order they exist. ##Order the data according to the headers, in whatever order they exist.
@productRow{ @headers } = @{ $productRow }; @productRow{ @headers } = @{ $productRow };
$fetchProductId->execute([$productRow->{mastersku}]); $fetchProductId->execute([$productRow{mastersku}]);
my ($assetId) = $fetchProductId->hashRef->{assetId}; my $asset = $fetchProductId->hashRef;
##If the assetId exists, we update data for it ##If the assetId exists, we update data for it
if ($assetId) { if ($asset->{assetId}) {
$session->log->warn("Modifying an existing product: $productRow{sku} = $asset->{assetId}\n");
my $assetId = $asset->{assetId};
my $product = WebGUI::Asset->newPending($session, $assetId); my $product = WebGUI::Asset->newPending($session, $assetId);
if ($productRow{title} ne $product->getTitle) { if ($productRow{title} ne $product->getTitle) {
$product->update({ title => $product->fixTitle($productRow{title}) }); $product->update({ title => $product->fixTitle($productRow{title}) });
} }
##Error handling for locked assets ##Error handling for locked assets
$session->log->warn("Product is locked") if $product->isLocked;
delete $productRow{ title };
delete $productRow{ mastersku };
next PRODUCT if $product->isLocked; next PRODUCT if $product->isLocked;
my $collaterals = $product->getAllCollateral('variantsJSON'); my $collaterals = $product->getAllCollateral('variantsJSON');
my $collateralSet = 0; my $collateralSet = 0;
ROW: foreach my $collateral (@{ $collaterals }) { ROW: foreach my $collateral (@{ $collaterals }) {
next ROW unless $collateral->{sku} eq $productRow->{sku}; next ROW unless $collateral->{sku} eq $productRow{sku};
@{ $collateral}{@headers} = @productRow{ @headers }; @{ $collateral}{@headers} = @productRow{ @headers };
$product->setCollateral('variantsJSON', 'variantId', $collateral->{variantId}, $collateral); $product->setCollateral('variantsJSON', 'variantId', $collateral->{variantId}, $collateral);
$collateralSet=1; $collateralSet=1;
@ -172,12 +178,96 @@ sub importProducts {
} }
else { else {
##Insert a new product; ##Insert a new product;
$session->log->warn("Making a new product: $productRow{sku}\n");
my $newProduct = $node->addChild({className => 'WebGUI::Asset::Sku::Product'}); my $newProduct = $node->addChild({className => 'WebGUI::Asset::Sku::Product'});
$newProduct->update({ title => $newProduct->fixTitle($productRow{title}) }); $newProduct->update({
title => $newProduct->fixTitle($productRow{title}),
sku => $productRow{mastersku},
});
delete $productRow{ title };
delete $productRow{ mastersku };
$newProduct->setCollateral('variantsJSON', 'variantId', 'new', \%productRow); $newProduct->setCollateral('variantsJSON', 'variantId', 'new', \%productRow);
$newProduct->commit;
} }
} }
return 1; return 1;
} }
#-------------------------------------------------------------------
=head2 www_exportProducts ( )
Export all product SKUs as a CSV file. Returns a WebGUI::Storage
object containg the product file, named 'siteProductData.csv'.
=cut
sub www_exportProducts {
my $self = shift;
my $session = $self->session;
my $admin = WebGUI::Shop::Admin->new($session);
return $session->privilege->insufficient
unless $admin->canManage;
my $storage = $self->exportProducts();
$self->session->http->setRedirect($storage->getUrl($storage->getFiles->[0]));
return "redirect";
}
#-------------------------------------------------------------------
=head2 www_importProducts ( )
Import new product data from a file provided by the user. This will create new products
or alter existing products.
=cut
sub www_importProducts {
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 $productFile = $storage->addFileFromFormPost('importFile', 1);
$self->importProducts($storage->getPath($productFile)) if $productFile;
return $self->www_manage;
}
#-------------------------------------------------------------------
=head2 www_manage ( )
User interface to synchronize product data. Provides an interface for
exporting all products on the site, and importing sets of products.
=cut
sub www_manage {
my $self = 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));
##Default CSS
$style->setRawHeadTags('<style type="text/css"> #paging a { color: #0000de; } #search, #export form { display: inline; } </style>');
my $i18n=WebGUI::International->new($session, 'Shop');
my $exportForm = WebGUI::Form::formHeader($session,{action => $url->page('shop=products;method=exportProducts')})
. WebGUI::Form::submit($session,{value=>$i18n->get('export'), extras=>q{style="float: left;"} })
. WebGUI::Form::formFooter($session);
my $importForm = WebGUI::Form::formHeader($session,{action => $url->page('shop=products;method=importProducts')})
. WebGUI::Form::submit($session,{value=>$i18n->get('import'), extras=>q{style="float: left;"} })
. q{<input type="file" name="importFile" size="10" />}
. WebGUI::Form::formFooter($session);
my $output =sprintf <<EODIV, $exportForm, $importForm;
<div id="importExport">%s%s</div>
EODIV
return $admin->getAdminConsole->render($output, $i18n->get('products'));
}
1; 1;

View file

@ -529,10 +529,10 @@ sub www_manage {
my $i18n=WebGUI::International->new($session, 'Tax'); my $i18n=WebGUI::International->new($session, 'Tax');
my $exportForm = WebGUI::Form::formHeader($session,{action => $url->page('shop=tax;method=exportTax')}) my $exportForm = WebGUI::Form::formHeader($session,{action => $url->page('shop=tax;method=exportTax')})
. WebGUI::Form::submit($session,{value=>$i18n->get('export'), extras=>q{style="float: left;"} }) . WebGUI::Form::submit($session,{value=>$i18n->get('export','Shop'), extras=>q{style="float: left;"} })
. WebGUI::Form::formFooter($session); . WebGUI::Form::formFooter($session);
my $importForm = WebGUI::Form::formHeader($session,{action => $url->page('shop=tax;method=importTax')}) my $importForm = WebGUI::Form::formHeader($session,{action => $url->page('shop=tax;method=importTax')})
. WebGUI::Form::submit($session,{value=>$i18n->get('import'), extras=>q{style="float: left;"} }) . WebGUI::Form::submit($session,{value=>$i18n->get('import','Shop'), extras=>q{style="float: left;"} })
. q{<input type="file" name="importFile" size="10" />} . q{<input type="file" name="importFile" size="10" />}
. WebGUI::Form::formFooter($session); . WebGUI::Form::formFooter($session);

View file

@ -591,6 +591,12 @@ our $I18N = {
context => q|admin function label| context => q|admin function label|
}, },
'products' => {
message => q|Products|,
lastUpdated => 0,
context => q|admin function label|
},
'is a required field' => { 'is a required field' => {
message => q|%s is a required field.|, message => q|%s is a required field.|,
lastUpdated => 0, lastUpdated => 0,
@ -891,6 +897,17 @@ our $I18N = {
context => q|Period name for a yearly subscription.| context => q|Period name for a yearly subscription.|
}, },
'import' => {
message => q|Import|,
lastUpdated => 1212550974,
context => q|Label for bringing data into the Shop (Tax, Product, etc.)|
},
'export' => {
message => q|Export|,
lastUpdated => 1212550978,
context => q|Label for taking data out of the Shop (Tax, Product, etc.)|,
},
}; };
1; 1;

View file

@ -33,18 +33,6 @@ our $I18N = {
context => q|The amount that a person is charged to buy something, a percentage of the price.|, context => q|The amount that a person is charged to buy something, a percentage of the price.|,
}, },
'export' => {
message => q|Export|,
lastUpdated => 1206307669,
context => q|To ship a copy of the tax data out of the server.|,
},
'import' => {
message => q|Import|,
lastUpdated => 1206390280,
context => q|To bring in new tax data that replaces the current data.|,
},
'delete' => { 'delete' => {
message => q|delete|, message => q|delete|,
lastUpdated => 1206385749, lastUpdated => 1206385749,

View file

@ -33,7 +33,7 @@ my $session = WebGUI::Test->session;
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
# Tests # Tests
my $tests = 14; my $tests = 22;
plan tests => 1 + $tests; plan tests => 1 + $tests;
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
@ -134,9 +134,68 @@ SKIP: {
'importProducts: error handling for a file with a missing header', 'importProducts: error handling for a file with a missing header',
); );
my $pass = WebGUI::Shop::Products::importProducts(
$session,
WebGUI::Test->getTestCollateralPath('productTables/goodProductTable.csv'),
);
ok($pass, 'Products imported');
my $count = $session->db->quickScalar('select count(*) from Product');
is($count, 2, 'two products were imported');
my $soda = WebGUI::Asset::Sku->newBySku($session, 'soda');
isa_ok($soda, 'WebGUI::Asset::Sku::Product');
is($soda->getTitle(), 'Sweet Soda-bottled in Oregon', 'Title set correctly for soda');
my $sodaCollateral = $soda->getAllCollateral('variantsJSON');
cmp_deeply(
$sodaCollateral,
[
{
sku => 'soda-sweet',
shortdesc => 'Sweet Soda',
price => 0.95,
weight => 0.95,
quantity => 500,
variantId => ignore(),
},
],
'collateral set correctly for soda'
);
my $shirt = WebGUI::Asset::Sku->newBySku($session, 't-shirt');
isa_ok($shirt, 'WebGUI::Asset::Sku::Product');
is($shirt->getTitle(), 'Colored T-Shirts', 'Title set correctly for t-shirt');
my $shirtCollateral = $shirt->getAllCollateral('variantsJSON');
cmp_deeply(
$shirtCollateral,
[
{
sku => 'red-t-shirt',
shortdesc => 'Red T-Shirt',
price => '5.00',
weight => '1.33',
quantity => '1000',
variantId => ignore(),
},
{
sku => 'blue-t-shirt',
shortdesc => 'Blue T-Shirt',
price => '5.25',
weight => '1.33',
quantity => '2000',
variantId => ignore(),
},
],
'collateral set correctly for shirt'
);
} }
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
# Cleanup # Cleanup
END { END {
my $getAProduct = WebGUI::Asset::Sku::Product->getIsa($session);
while (my $product = $getAProduct->()) {
$product->purge;
}
} }

View file

@ -1,4 +1,4 @@
mastersku,sku,title,shortdescription,price,weight,quantity mastersku,sku,title,shortdescription,price,weight,quantity
t-shirt,red-t-shirt,Red T-Shirt,Red T-Shirt,5.00,1.33,1000 t-shirt,red-t-shirt,Colored T-Shirts,Red T-Shirt,5.00,1.33,1000
t-shirt,blue-t-shirt,Blue T-Shirt,Blue T-Shirt,5.25,1.33,2000 t-shirt,blue-t-shirt,Colored T-Shirts,Blue T-Shirt,5.25,1.33,2000
soda,soda-sweet,Sweet Soda-bottled in Oregon,Sweet Soda,0.95,0.95,500 soda,soda-sweet,Sweet Soda-bottled in Oregon,Sweet Soda,0.95,0.95,500

1 mastersku sku title shortdescription price weight quantity
2 t-shirt red-t-shirt Red T-Shirt Colored T-Shirts Red T-Shirt 5.00 1.33 1000
3 t-shirt blue-t-shirt Blue T-Shirt Colored T-Shirts Blue T-Shirt 5.25 1.33 2000
4 soda soda-sweet Sweet Soda-bottled in Oregon Sweet Soda 0.95 0.95 500