From 16bd779fd4082b7ead360601bbf2eabfbeae0308 Mon Sep 17 00:00:00 2001 From: JT Smith Date: Sat, 15 Nov 2008 01:26:16 +0000 Subject: [PATCH] - Added Cashier/Point of Sale mode for the Shop. - Added the notion of a default address to the Shop's address book. --- docs/changelog/7.x.x.txt | 2 + docs/upgrades/upgrade_7.6.3-7.6.4.pl | 24 +++++++-- lib/WebGUI/Help/Shop.pm | 21 ++++++++ lib/WebGUI/Shop/AddressBook.pm | 56 ++++++++++++++++++++- lib/WebGUI/Shop/Admin.pm | 28 ++++++++++- lib/WebGUI/Shop/Cart.pm | 75 +++++++++++++++++++++++++--- lib/WebGUI/Shop/PayDriver/Cash.pm | 22 ++++---- lib/WebGUI/Shop/Transaction.pm | 17 +++++-- lib/WebGUI/i18n/English/Shop.pm | 42 ++++++++++++++++ 9 files changed, 263 insertions(+), 24 deletions(-) diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 0b0d09a40..4922087c0 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -2,6 +2,8 @@ - Brand new Survey system. Make sure to export your old results as they will not be imported, only the surveys themselves. - Made syndicated content asset use cache. + - Added Cashier/Point of Sale mode for the Shop. + - Added the notion of a default address to the Shop's address book. - fixed #8837: When you move an asset to a new version, only the current version is moved, instead of all of them. - Added version tag modes: multiple tags per user, single tag per user, site wide version tag and auto commit (thanks to Long Term Results B.V.) - fixed #9076: Thingy broken in latest beta, Save and Close buttons missing diff --git a/docs/upgrades/upgrade_7.6.3-7.6.4.pl b/docs/upgrades/upgrade_7.6.3-7.6.4.pl index a4ddfe176..9f6b23a0c 100644 --- a/docs/upgrades/upgrade_7.6.3-7.6.4.pl +++ b/docs/upgrades/upgrade_7.6.3-7.6.4.pl @@ -32,10 +32,29 @@ my $session = start(); # this line required migrateSurvey($session); addVersionTagMode($session); +addPosMode($session); finish($session); # this line required +#---------------------------------------------------------------------------- +sub addPosMode { + my $session = shift; + + print qq{\tAdding Point of Sale mode to the Shop...} if !$quiet; + + my $db = $session->db(); + my $setting = $session->setting(); + + $setting->add("groupIdCashier","3"); + $db->write(q{ALTER TABLE cart drop column couponId}); + $db->write(q{ALTER TABLE cart add column posUserId char(22) binary}); + $db->write(q{ALTER TABLE transaction add column cashierUserId char(22) binary}); + $db->write(q{update transaction set cashierUserId=userId}); + $db->write(q{ALTER TABLE addressBook add column defaultAddressId char(22) binary}); + + print qq{Finished\n} if !$quiet; +} #---------------------------------------------------------------------------- # This method add support for versionTagMode @@ -43,7 +62,7 @@ finish($session); # this line required sub addVersionTagMode { my $session = shift; - print q{Adding support for versionTagMode...} if !$quiet; + print qq{\tAdding support for versionTagMode...} if !$quiet; my $db = $session->db(); my $setting = $session->setting(); @@ -75,12 +94,11 @@ sub addVersionTagMode { # sub migrateSurvey{ my $session = shift; - print "Migrating surveys to new survey system..." unless $quiet; + print "\tMigrating surveys to new survey system..." unless $quiet; _moveOldSurveyTables($session); _addSurveyTables($session); - print "\n"; my $surveys = $session->db->buildArrayRefOfHashRefs( "SELECT * FROM Survey_old s diff --git a/lib/WebGUI/Help/Shop.pm b/lib/WebGUI/Help/Shop.pm index 6c6075a9f..80702ba98 100644 --- a/lib/WebGUI/Help/Shop.pm +++ b/lib/WebGUI/Help/Shop.pm @@ -114,6 +114,18 @@ our $HELP = { name => "shippingAddress", description => "shippingAddress help", }, + { + name => "isCashier", + }, + { + name => "posLookupForm", + }, + { + name => "posUsername", + }, + { + name => "posUserId", + }, ], }, { @@ -226,6 +238,11 @@ our $HELP = { description => "editButton help", required => 1, }, + { + name => "defaultButton", + description => "defaultButton help", + required => 1, + }, { name => "deleteButton", description => "deleteButton help", @@ -278,6 +295,10 @@ our $HELP = { name => "state", description => "state help", }, + { + name => "organization", + description => "organization help", + }, { name => "city", description => "city help", diff --git a/lib/WebGUI/Shop/AddressBook.pm b/lib/WebGUI/Shop/AddressBook.pm index 1c36b9182..289d5a9d3 100644 --- a/lib/WebGUI/Shop/AddressBook.pm +++ b/lib/WebGUI/Shop/AddressBook.pm @@ -174,6 +174,34 @@ sub getAddresses { #------------------------------------------------------------------- +=head2 getDefaultAddress () + +Returns the default address for this address book if there is one. Otherwise throws a WebGUI::Error::ObjectNotFound exception. + +=cut + +sub getDefaultAddress { + my ($self) = @_; + my $id = $self->get('defaultAddressId'); + if ($id ne '') { + my $address = eval { $self->getAddress($id) }; + my $e; + if ($e = WebGUI::Error->caught('WebGUI::Error::ObjectNotFound')) { + $self->update({defaultAddressId=>''}); + $e->rethrow; + } + elsif ($e = WebGUI::Error->caught) { + $e->rethrow; + } + else { + return $address; + } + } + return undef; +} + +#------------------------------------------------------------------- + =head2 getId () Returns the unique id for this cart. @@ -288,12 +316,16 @@ Assign the user that owns this address book. Assign the session, by id, that owns this address book. Will automatically be set to "" if a user owns it. +=head4 defaultAddressId + +The id of the address to be made the default for this address book. + =cut sub update { my ($self, $newProperties) = @_; my $id = id $self; - foreach my $field (qw(userId sessionId)) { + foreach my $field (qw(userId sessionId defaultAddressId)) { $properties{$id}{$field} = (exists $newProperties->{$field}) ? $newProperties->{$field} : $properties{$id}{$field}; } ##Having both a userId and sessionId will confuse create. @@ -319,6 +351,20 @@ sub www_deleteAddress { #------------------------------------------------------------------- +=head2 www_defaultAddress ( ) + +Makes an address be the default. + +=cut + +sub www_defaultAddress { + my $self = shift; + $self->update({defaultAddressId=>$self->session->form->get("addressId")}); + return $self->www_view; +} + +#------------------------------------------------------------------- + =head2 www_editAddress () Allows a user to edit an address in their address book. @@ -467,6 +513,7 @@ sub www_view { push(@addresses, { %{$address->get}, address => $address->getHtmlFormatted, + isDefault => ($self->get('defaultAddressId') eq $address->getId), deleteButton => WebGUI::Form::formHeader($session) .WebGUI::Form::hidden($session, {name=>"shop", value=>"address"}) .WebGUI::Form::hidden($session, {name=>"method", value=>"deleteAddress"}) @@ -481,6 +528,13 @@ sub www_view { .$self->formatCallbackForm($form->get('callback')) .WebGUI::Form::submit($session, {value=>$i18n->get("edit")}) .WebGUI::Form::formFooter($session), + defaultButton => WebGUI::Form::formHeader($session) + .WebGUI::Form::hidden($session, {name=>"shop", value=>"address"}) + .WebGUI::Form::hidden($session, {name=>"method", value=>"defaultAddress"}) + .WebGUI::Form::hidden($session, {name=>"addressId", value=>$address->getId}) + .$self->formatCallbackForm($form->get('callback')) + .WebGUI::Form::submit($session, {value=>$i18n->get("default")}) + .WebGUI::Form::formFooter($session), useButton => WebGUI::Form::formHeader($session,{action=>$callback->{url}}) .$callbackForm .WebGUI::Form::hidden($session, {name=>"addressId", value=>$address->getId}) diff --git a/lib/WebGUI/Shop/Admin.pm b/lib/WebGUI/Shop/Admin.pm index ce2e7ea2b..b8fc9eb26 100644 --- a/lib/WebGUI/Shop/Admin.pm +++ b/lib/WebGUI/Shop/Admin.pm @@ -74,6 +74,24 @@ sub getAdminConsole { #------------------------------------------------------------------- +=head2 isCashier ( [ $user ] ) + +Determine whether or not a user is a cashier + +=head3 $user + +An optional WebGUI::User object. If this is not used, it uses the current session user object. + +=cut + +sub isCashier { + my $self = shift; + my $user = shift || $self->session->user; + return $user->isInGroup( $self->session->setting->get('groupIdCashier')); +} + +#------------------------------------------------------------------- + =head2 new ( session ) Constructor. @@ -127,6 +145,12 @@ sub www_editSettings { label => $i18n->get('who can manage'), hoverHelp => $i18n->get('who can manage help'), ); + $form->group( + name => "groupIdCashier", + value => $setting->get("groupIdCashier"), + label => $i18n->get('who is a cashier'), + hoverHelp => $i18n->get('who is a cashier help'), + ); $form->template( name => "shopCartTemplateId", value => $setting->get("shopCartTemplateId"), @@ -182,7 +206,9 @@ sub www_editSettingsSave { shopCartTemplateId shopAddressBookTemplateId shopAddressTemplateId)) { $setting->set($template, $form->get($template, "template")); } - $setting->set("groupIdAdminCommerce", $form->get("groupIdAdminCommerce", "group")); + foreach my $group (qw(groupIdCashier groupIdAdminCommerce)) { + $setting->set($group, $form->get($group, "group")); + } return $self->www_editSettings(); } diff --git a/lib/WebGUI/Shop/Cart.pm b/lib/WebGUI/Shop/Cart.pm index 17d7e4410..d76db0db8 100644 --- a/lib/WebGUI/Shop/Cart.pm +++ b/lib/WebGUI/Shop/Cart.pm @@ -13,6 +13,7 @@ use WebGUI::Shop::CartItem; use WebGUI::Shop::Credit; use WebGUI::Shop::Ship; use WebGUI::Shop::Tax; +use WebGUI::User; =head1 NAME @@ -83,7 +84,7 @@ sub calculateShopCreditDeduction { unless (defined $total) { $total = $self->calculateTotal } - return $self->formatCurrency(WebGUI::Shop::Credit->new($self->session)->calculateDeduction($total)); + return $self->formatCurrency(WebGUI::Shop::Credit->new($self->session, $self->get('posUserId'))->calculateDeduction($total)); } #------------------------------------------------------------------- @@ -344,6 +345,22 @@ sub getItemsByAssetId { #------------------------------------------------------------------- +=head2 getPosUser + +Returns the userId of the user making a purchase. If there is a cashier and the cashier has specified a user, then that user will be returned. Otherwise, if it's a direct sale then $session->user will be returned. + +=cut + +sub getPosUser { + my $self = shift; + if ($self->get('posUserId') ne "") { + return WebGUI::User->new($self->session, $self->get('posUserId')); + } + return $self->session->user; +} + +#------------------------------------------------------------------- + =head2 getShipper () Returns the WebGUI::Shop::ShipDriver object that is attached to this cart for shipping. @@ -365,7 +382,13 @@ Returns the WebGUI::Shop::Address object that is attached to this cart for shipp sub getShippingAddress { my $self = shift; - return $self->getAddressBook->getAddress($self->get("shippingAddressId")); + my $book = $self->getAddressBook; + if ($self->get("shippingAddressId")) { + return $book->getAddress($self->get("shippingAddressId")); + } + my $address = $book->getDefaultAddress; + $self->update({shippingAddressId=>$address->getId}); + return $address; } #------------------------------------------------------------------- @@ -533,6 +556,10 @@ The unique id for a shipping address attached to this cart. The unique id of the configured shipping driver that will be used to ship these goods. +=head4 posUserId + +The ID of a user being checked out, if they're being checked out by a cashier. + =cut sub update { @@ -541,7 +568,7 @@ sub update { WebGUI::Error::InvalidParam->throw(error=>"Need a properties hash ref."); } my $id = id $self; - foreach my $field (qw(shippingAddressId shipperId)) { + foreach my $field (qw(shippingAddressId posUserId shipperId)) { $properties{$id}{$field} = (exists $newProperties->{$field}) ? $newProperties->{$field} : $properties{$id}{$field}; } $self->session->db->setRow("cart","cartId",$properties{$id}); @@ -616,6 +643,31 @@ sub www_continueShopping { #------------------------------------------------------------------- +=head2 www_lookupPosUser ( ) + +Adds a Point of Sale user to the cart. + +=cut + +sub www_lookupPosUser { + my $self = shift; + my $session = $self->session; + my $email = $session->form->get('posEmail','email'); + my $user = WebGUI::User->newByEmail($session, $email); + unless (defined $user) { + $user = WebGUI::User->newByUsername($session, $email); + unless (defined $user) { + $user = WebGUI::User->new($session, "new"); + $user->username($email); + $user->profileField('email', $email); + } + } + $self->update({posUserId=>$user->userId}); + return $self->www_view; +} + +#------------------------------------------------------------------- + =head2 www_removeItem ( ) Remove an item from the cart and then display the cart again. @@ -756,7 +808,7 @@ sub www_view { my $address = eval { $self->getShippingAddress }; if (WebGUI::Error->caught("WebGUI::Error::ObjectNotFound")) { # choose another address cuz we've got a problem - $self->update({shippingAddressId=>""}); + $self->update({shippingAddressId=>''}); } # if there is no shipping address we can't check out @@ -782,13 +834,22 @@ sub www_view { $var{shippingPrice} = $self->formatCurrency($var{shippingPrice}); } + # POS variables + $var{isCashier} = WebGUI::Shop::Admin->new($session)->isCashier; + $var{posLookupForm} = WebGUI::Form::email($session, {name=>"posEmail"}) + .WebGUI::Form::submit($session, {value=>$i18n->get('search for email'), + extras=>q|onclick="this.form.method.value='lookupPosUser';this.form.submit;"|}); + my $posUser = $self->getPosUser; + $var{posUsername} = $posUser->username; + $var{posUserId} = $posUser->userId; + # calculate price adjusted for in-store credit $var{totalPrice} = $var{subtotalPrice} + $var{shippingPrice} + $var{tax}; - my $credit = WebGUI::Shop::Credit->new($session); + my $credit = WebGUI::Shop::Credit->new($session, $posUser->userId); $var{inShopCreditAvailable} = $credit->getSum; $var{inShopCreditDeduction} = $credit->calculateDeduction($var{totalPrice}); - $var{totalPrice} = $self->formatCurrency($var{totalPrice} + $var{inShopCreditDeduction}); - + $var{totalPrice} = $self->formatCurrency($var{totalPrice} + $var{inShopCreditDeduction}); + # render the cart my $template = WebGUI::Asset::Template->new($session, $session->setting->get("shopCartTemplateId")); return $session->style->userStyle($template->process(\%var)); diff --git a/lib/WebGUI/Shop/PayDriver/Cash.pm b/lib/WebGUI/Shop/PayDriver/Cash.pm index 1c0fd2f97..da5ef4b10 100644 --- a/lib/WebGUI/Shop/PayDriver/Cash.pm +++ b/lib/WebGUI/Shop/PayDriver/Cash.pm @@ -105,7 +105,18 @@ Optionally supply this variable which will set the payment address to this addre sub www_getCredentials { my ($self, $addressId) = @_; my $session = $self->session; - $addressId = $session->form->process('addressId') if ($addressId eq ""); + + # Process address from address book if passed + $addressId = $session->form->process( 'addressId' ); + my $address; + if ( $addressId ) { + $address = eval{ $self->getAddress( $addressId ) }; + } + else { + $address = $self->getCart->getShippingAddress; + } + my $billingAddressHtml = $address->getHtmlFormatted; + # Generate the json string that defines where the address book posts the selected address my $callbackParams = { url => $session->url->page, @@ -126,18 +137,11 @@ sub www_getCredentials { . WebGUI::Form::submit( $session, { value => 'Choose billing address' } ) . WebGUI::Form::formFooter( $session); - # Get billing address - my $billingAddress = eval { $self->getAddress($addressId) }; - - my $billingAddressHtml; - if ($billingAddress) { - $billingAddressHtml = $billingAddress->getHtmlFormatted; - } # Generate 'Proceed' button my $proceedButton = WebGUI::Form::formHeader( $session ) . $self->getDoFormTags('pay') - . WebGUI::Form::hidden($session, {name=>"addressId", value=>$addressId}) + . WebGUI::Form::hidden($session, {name=>"addressId", value=>$address->getId}) . WebGUI::Form::submit( $session, { value => 'Pay' } ) . WebGUI::Form::formFooter( $session); diff --git a/lib/WebGUI/Shop/Transaction.pm b/lib/WebGUI/Shop/Transaction.pm index f9599cacd..c96ca22ed 100644 --- a/lib/WebGUI/Shop/Transaction.pm +++ b/lib/WebGUI/Shop/Transaction.pm @@ -15,6 +15,7 @@ use WebGUI::Shop::AddressBook; use WebGUI::Shop::Credit; use WebGUI::Shop::TransactionItem; use WebGUI::Shop::Pay; +use WebGUI::User; =head1 NAME @@ -154,8 +155,14 @@ sub create { WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error=>"Need a session."); } my $transactionId = $session->id->generate; - $session->db->write('insert into transaction (transactionId, userId, username, dateOfPurchase) values (?,?,?,now())', - [$transactionId, $session->user->userId, $session->user->username]); + my $cashier = $session->user; + my $posUser = $cashier; + my $cart = $properties->{cart}; + if (defined $cart) { + $posUser = $cart->getPosUser; + } + $session->db->write('insert into transaction (transactionId, userId, username, cashierUserId, dateOfPurchase) values (?,?,?,?,now())', + [$transactionId, $posUser->userId, $posUser->username, $cashier->userId]); my $self = $class->new($session, $transactionId); $self->update($properties); return $self; @@ -917,6 +924,7 @@ sub www_view { } $output .= q{}; } + my $cashier = WebGUI::User->new($session, $transaction->get('cashierUserId')); $output .= q{ @@ -931,6 +939,9 @@ sub www_view { + + + @@ -1121,7 +1132,7 @@ sub www_viewMy { unless (defined $transaction) { $transaction = $class->new($session, $session->form->get('transactionId')); } - return $session->insufficient unless ($transaction->get('userId') eq $session->user->userId); + return $session->privilege->insufficient unless ($transaction->get('userId') eq $session->user->userId || WebGUI::Shop::Admin->new($session)->isCashier); my $i18n = WebGUI::International->new($session, 'Shop'); my ($style, $url) = $session->quick(qw(style url)); my %var = ( diff --git a/lib/WebGUI/i18n/English/Shop.pm b/lib/WebGUI/i18n/English/Shop.pm index f09991936..87317f953 100644 --- a/lib/WebGUI/i18n/English/Shop.pm +++ b/lib/WebGUI/i18n/English/Shop.pm @@ -9,6 +9,36 @@ our $I18N = { context => q|vendor label|, }, + 'cashier' => { + message => q|Cashier|, + lastUpdated => 0, + context => q|transaction label|, + }, + + 'order for' => { + message => q|Order For|, + lastUpdated => 0, + context => q|cart label, as in "This is an order for John Smith"|, + }, + + 'search for email' => { + message => q|Search for Email Address|, + lastUpdated => 0, + context => q|cart button label|, + }, + + 'who is a cashier' => { + message => q|Who is a cashier?|, + lastUpdated => 0, + context => q|shop admin setting|, + }, + + 'who is a cashier help' => { + message => q|Cashiers are able to make purchases on behalf of another user by typing the email address of the user into the cart.|, + lastUpdated => 0, + context => q|help for shop admin setting|, + }, + 'organization' => { message => q|Organization|, lastUpdated => 0, @@ -309,6 +339,12 @@ our $I18N = { context => q|a help description|, }, + 'defaultButton help' => { + message => q|A button that will allow the user to set an address as a default.|, + lastUpdated => 0, + context => q|a help description|, + }, + 'deleteButton help' => { message => q|A button that will allow the user to delete an existing address.|, lastUpdated => 0, @@ -765,6 +801,12 @@ our $I18N = { context => q|a button in the address book| }, + 'default' => { + message => q|Set Default|, + lastUpdated => 0, + context => q|a button in the address book| + }, + 'edit' => { message => q|Edit|, lastUpdated => 0,
}. $i18n->get("username") .q{}. $transaction->get('username') .q{
}. $i18n->get("cashier") .q{}. $cashier->username .q{
}. $i18n->get("amount") .q{}. sprintf("%.2f", $transaction->get('amount')) .q{