- Added Cashier/Point of Sale mode for the Shop.
- Added the notion of a default address to the Shop's address book.
This commit is contained in:
parent
47419b9602
commit
16bd779fd4
9 changed files with 263 additions and 24 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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{</div>};
|
||||
}
|
||||
my $cashier = WebGUI::User->new($session, $transaction->get('cashierUserId'));
|
||||
$output .= q{
|
||||
<table class="transactionDetail">
|
||||
<tr>
|
||||
|
|
@ -931,6 +939,9 @@ sub www_view {
|
|||
<tr>
|
||||
<th>}. $i18n->get("username") .q{</th><td><a href="}.$url->page('op=editUser;uid='.$transaction->get('userId')).q{">}. $transaction->get('username') .q{</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>}. $i18n->get("cashier") .q{</th><td><a href="}.$url->page('op=editUser;uid='.$cashier->userId).q{">}. $cashier->username .q{</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>}. $i18n->get("amount") .q{</th><td><b>}. sprintf("%.2f", $transaction->get('amount')) .q{</b></td>
|
||||
</tr>
|
||||
|
|
@ -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 = (
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue