site/cgi-bin/modules/BSE/TB/AuditLog.pm
site/cgi-bin/modules/BSE/TB/BackgroundTask.pm
site/cgi-bin/modules/BSE/TB/BackgroundTasks.pm
+site/cgi-bin/modules/BSE/TB/Coupon.pm
+site/cgi-bin/modules/BSE/TB/Coupons.pm
+site/cgi-bin/modules/BSE/TB/CouponTier.pm
+site/cgi-bin/modules/BSE/TB/CouponTiers.pm
site/cgi-bin/modules/BSE/TB/File.pm
site/cgi-bin/modules/BSE/TB/FileAccessLog.pm
site/cgi-bin/modules/BSE/TB/FileAccessLogEntry.pm
site/templates/admin/catalog.tmpl # embedded in the shopadmin catalog/product display
site/templates/admin/catalog_custom.tmpl
site/templates/admin/changepw.tmpl
+site/templates/admin/coupons/add.tmpl
+site/templates/admin/coupons/delete.tmpl
+site/templates/admin/coupons/edit.tmpl
+site/templates/admin/coupons/list.tmpl
site/templates/admin/edit_0.tmpl
site/templates/admin/edit_1.tmpl
site/templates/admin/edit_catalog.tmpl
drop table if exists bse_tag_categories;
drop table if exists bse_tag_members;
drop table if exists bse_tags;
+drop table if exists bse_coupon_tiers;
+drop table if exists bse_coupons;
-- represents sections, articles
DROP TABLE IF EXISTS article;
-- true if the order was paid manually
paid_manually integer not null default 0,
+ coupon_code varchar(40) not null default '',
+ coupon_discount real not null default 0,
+
primary key (id),
index order_cchash(ccNumberHash),
index order_userId(userId, orderDate)
enabled integer not null default 0,
default_value integer,
index product_order(product_id, display_order)
-) type=innodb;
+) engine=innodb;
drop table if exists bse_product_option_values;
create table bse_product_option_values (
value varchar(255) not null,
display_order integer not null,
index option_order(product_option_id, display_order)
-) type=innodb;
+) engine=innodb;
drop table if exists bse_order_item_options;
create table bse_order_item_options (
display varchar(80) not null,
display_order integer not null,
index item_order(order_item_id, display_order)
-) type=innodb;
+) engine=innodb;
drop table if exists bse_owned_files;
create table bse_owned_files (
ftype varchar(20) not null default 'img',
index owner(file_type, owner_id)
-) type = InnoDB;
+) engine = InnoDB;
-- a generic selection of files from a pool
create table bse_selected_files (
display_order integer not null default -1,
unique only_one(owner_id, owner_type, file_id)
-) type = InnoDB;
+) engine = InnoDB;
drop table if exists bse_price_tiers;
create table bse_price_tiers (
to_date date null,
display_order integer null null
-);
+) engine=innodb;
drop table if exists bse_price_tier_prices;
expires datetime not null,
unique ip_address(ip_address, type)
-) type=innodb;
\ No newline at end of file
+) engine=innodb;
+
+create table bse_coupons (
+ id integer not null auto_increment primary key,
+
+ code varchar(40) not null,
+
+ description text not null,
+
+ `release` date not null,
+
+ expiry date not null,
+
+ discount_percent real not null,
+
+ campaign varchar(20) not null,
+
+ last_modified datetime not null,
+
+ untiered integer not null default 0,
+
+ unique codes(code)
+) engine=InnoDB;
+
+create table bse_coupon_tiers (
+ id integer not null auto_increment primary key,
+
+ coupon_id integer not null,
+
+ tier_id integer not null,
+
+ unique (coupon_id, tier_id),
+
+ foreign key (coupon_id) references bse_coupons(id)
+ on delete cascade on update restrict,
+
+ foreign key (tier_id) references bse_price_tiers(id)
+ on delete cascade on update restrict
+) engine=InnoDB;
\ No newline at end of file
use strict;
use Scalar::Util;
-our $VERSION = "1.003";
+our $VERSION = "1.004";
=head1 NAME
$self->_enter_cart;
}
+ $self->{coupon_code} = $self->{req}->session->{cart_coupon_code};
+ defined $self->{coupon_code} or $self->{coupon_code} = "";
+
return $self;
}
Return the total cost of the items in the cart.
+This does not include shipping costs and is not discounted.
+
=cut
sub total_cost {
return $total_cost;
}
+=item discounted_product_cost
+
+Cost of products with an product discount taken into account.
+
+Note: this rounds thr total B<down>.
+
+=cut
+
+sub discounted_product_cost {
+ my ($self) = @_;
+
+ my $cost = $self->total_cost;
+
+ if ($self->coupon_active) {
+ $cost -= $cost * $self->coupon_code_discount_pc / 100;
+ }
+
+ return int($cost);
+}
+
+=item product_cost_discount
+
+Return any amount taken off the product cost.
+
+=cut
+
+sub product_cost_discount {
+ my ($self) = @_;
+
+ return $self->total_cost - $self->discounted_product_cost;
+}
+
=item set_shipping_cost()
Set the cost of shipping.
sub total {
my ($self) = @_;
- return $self->total_cost() + $self->shipping_cost();
+ my $cost = 0;
+
+ $cost += $self->discounted_product_cost;
+
+ $cost += $self->shipping_cost;
+
+ $cost += $self->custom_cost;
+
+ return $cost;
+}
+
+=item coupon_code
+
+The current coupon code.
+
+=cut
+
+sub coupon_code {
+ my ($self) = @_;
+
+ return $self->{coupon_code};
+}
+
+=item set_coupon_code()
+
+Used by the shop to set the coupon code.
+
+=cut
+
+sub set_coupon_code {
+ my ($self, $code) = @_;
+
+ $code =~ s/\A\s+//;
+ $code =~ s/\s+\z//;
+ $self->{coupon_code} = $code;
+ delete $self->{coupon_valid};
+ $self->{req}->session->{cart_coupon_code} = $code;
+}
+
+=item coupon_code_discount_pc
+
+The percentage discount for the current coupon code, if that code is
+valid and the contents of the cart are valid for that coupon code.
+
+=cut
+
+sub coupon_code_discount_pc {
+ my ($self) = @_;
+
+ $self->coupon_valid
+ or return 0;
+
+ return $self->{coupon_check}{coupon}->discount_percent;
+}
+
+=item coupon_valid
+
+Return true if the current coupon code is valid
+
+=cut
+
+sub coupon_valid {
+ my ($self) = @_;
+
+ unless ($self->{coupon_check}) {
+ if (length $self->{coupon_code}) {
+ require BSE::TB::Coupons;
+ my ($coupon) = BSE::TB::Coupons->getBy(code => $self->{coupon_code});
+ print STDERR "Searching for coupon '$self->{coupon_code}'\n";
+ my %check =
+ (
+ coupon => $coupon,
+ valid => 0,
+ );
+ #print STDERR " coupon $coupon\n";
+ #print STDERR "released ", 0+ $coupon->is_released, " expired ",
+ # 0+$coupon->is_expired, " valid ", 0+$coupon->is_valid, "\n" if $coupon;
+ if ($coupon && $coupon->is_valid) {
+ $check{valid} = 1;
+ $check{active} = 1;
+ my %tiers = map { $_ => 1 } $coupon->tiers;
+ ITEM:
+ for my $item ($self->items) {
+ my $applies = 1;
+ if ($item->tier_id) {
+ #print STDERR "tier ", $item->tier_id, " tiers ", join(",", keys %tiers), "\n";
+ if (!$tiers{$item->tier_id}) {
+ $applies = 0;
+ }
+ }
+ else {
+ if (!$coupon->untiered) {
+ $applies = 0;
+ }
+ }
+ $item->{coupon_applies} = $applies;
+ $applies or $check{active} = 0;
+ }
+ }
+ $self->{coupon_check} = \%check;
+ }
+ else {
+ $self->{coupon_check} =
+ {
+ valid => 0,
+ active => 0,
+ };
+ }
+ }
+
+ return $self->{coupon_check}{valid};
+}
+
+=item coupon_active
+
+Return true if the current coupon is active, ie. both valid and the
+cart has products of all the right tiers.
+
+=cut
+
+sub coupon_active {
+ my ($self) = @_;
+
+ $self->coupon_valid
+ or return 0;
+
+ return $self->{coupon_check}{active};
+}
+
+=item coupon
+
+The current coupon object, if and only if the coupon code is valid.
+
+=cut
+
+sub coupon {
+ my ($self) = @_;
+
+ $self->coupon_valid
+ or return;
+
+ $self->{coupon_check}{coupon};
+}
+
+=item custom_cost
+
+Return any custom cost specified by a custom class.
+
+=cut
+
+sub custom_cost {
+ my ($self) = @_;
+
+ unless (exists $self->{custom_cost}) {
+ my $obj = BSE::CfgInfo::custom_class($self->{req}->cfg);
+ $self->{custom_cost} =
+ $obj->total_extras(scalar $self->items, scalar $self->products,
+ $self->{custom_state}, $self->{req}->cfg, $self->{stage});
+ }
+
+ return $self->{custom_cost};
}
=item have_sales_files
return 1;
}
-=head1 need_logon_message
+=item need_logon_message
Returns a list with the error message and message id of the reason the
user needs to logon for this cart.
return join(", ", map "$_->{desc}: $_->{display}", @$options);
}
+=item coupon_applies
+
+Returns true if the current coupon code applies to the item.
+
+=cut
+
+sub coupon_applies {
+ my ($self) = @_;
+
+ $self->{cart}->coupon_valid
+ or return 0;
+
+ return $self->{coupon_applies};
+}
+
=item session
The session object of the seminar session
use BSE::Util::HTML;
use Carp qw(cluck confess);
-our $VERSION = "1.023";
+our $VERSION = "1.024";
=head1 NAME
$value = join("", $cgi->param($name));
}
}
+ elsif ($field->{htmltype} eq "multicheck") {
+ $value = [ $cgi->param($name) ];
+ }
elsif ($field->{type} && $field->{type} eq "date" && !$opts{api}) {
($value) = $cgi->param($name);
require DevHelp::Date;
payment_types order_item_opts
PAYMENT_CC PAYMENT_CHEQUE PAYMENT_CALLME PAYMENT_MANUAL PAYMENT_PAYPAL/;
-our $VERSION = "1.008";
+our $VERSION = "1.009";
our %EXPORT_TAGS =
(
my $what = $_[0] || 'retailPrice';
$current_item->extended($what);
},
- total => sub { $cart->total_cost },
+ total => sub { $cart->total },
$ito->make
(
plural => "options",
location => [ \&tag_location, \$current_item, \$location ],
ifHaveSaleFiles => [ have_sales_files => $cart ],
custom_class($cfg)
- ->checkout_actions($acts, $cart->items, $cart->products, $req->session->{custom}, $q, $cfg),
+ ->checkout_actions($acts, scalar $cart->items, scalar $cart->products, $req->session->{custom}, $q, $cfg),
);
}
--- /dev/null
+package BSE::TB::Coupon;
+use strict;
+use Squirrel::Row;
+our @ISA = qw/Squirrel::Row/;
+use BSE::TB::CouponTiers;
+
+=head1 NAME
+
+our $VERSION = "1.000";
+
+BSE::TB::Coupon - shop coupon objects
+
+=head1 SYNOPSIS
+
+ use BSE::TB::Coupons;
+
+ my $coupon = BSE::TB::Coupons->make(...);
+
+=head1 DESCRIPTION
+
+Represents shop coupons.
+
+=head1 METHODS
+
+=over
+
+=cut
+
+sub columns {
+ return qw/id code description release expiry discount_percent campaign last_modified untiered/;
+}
+
+sub table {
+ "bse_coupons";
+}
+
+sub defaults {
+ require BSE::Util::SQL;
+ return
+ (
+ last_modified => BSE::Util::SQL::now_sqldatetime(),
+ untiered => 1,
+ );
+}
+
+=item tiers
+
+Return the tier ids for a coupon.
+
+=cut
+
+sub tiers {
+ my ($self) = @_;
+
+ return BSE::TB::CouponTiers->getColumnBy
+ (
+ tier_id =>
+ [
+ coupon_id => $self->id
+ ]
+ );
+}
+
+=item tier_objects
+
+Return tier objects for each of the tiers this coupon is valid for.
+
+=cut
+
+sub tier_objects {
+ my ($self) = @_;
+
+ require BSE::TB::PriceTiers;
+ return BSE::TB::PriceTiers->getSpecial(forCoupon => $self->id);
+}
+
+=item set_tiers(\@tiers)
+
+Set the tiers for a coupon.
+
+=cut
+
+sub set_tiers {
+ my ($self, $tiers) = @_;
+
+ my %current = map { $_->tier_id => $_ }
+ BSE::TB::CouponTiers->getBy2
+ (
+ [
+ coupon_id => $self->id
+ ]
+ );
+
+ my %keep = map { $_->tier_id => $_ } grep $_, delete @current{@$tiers};
+
+ $_->remove for keys %current;
+
+ for my $tier_id (grep !$keep{$_}, @$tiers) {
+ BSE::TB::CouponTiers->make
+ (
+ coupon_id => $self->id,
+ tier_id => $tier_id
+ );
+ }
+
+ 1;
+}
+
+sub remove {
+ my ($self) = @_;
+
+ my @tiers = BSE::TB::CouponTiers->getBy2
+ (
+ [
+ coupon_id => $self->id
+ ]
+ );
+ $_->remove for @tiers;
+
+ $self->SUPER::remove();
+}
+
+sub json_data {
+ my ($self) = @_;
+
+ my $data = $self->data_only;
+ $data->{tiers} = [ $self->tiers ];
+
+ return $data;
+}
+
+=item is_expired
+
+Returns true if the coupon has expired.
+
+=cut
+
+sub is_expired {
+ my ($self) = @_;
+
+ require BSE::Util::SQL;
+ return BSE::Util::SQL::now_sqldate() gt $self->expiry;
+}
+
+=item is_released
+
+Returns true if the coupon has been released.
+
+=cut
+
+sub is_released {
+ my ($self) = @_;
+
+ require BSE::Util::SQL;
+ return $self->release le BSE::Util::SQL::now_sqldate();
+}
+
+=item is_valid
+
+Returns true if the coupon is both released and unexpired.
+
+=cut
+
+sub is_valid {
+ my ($self) = @_;
+
+ return $self->is_released && !$self->is_expired;
+}
+
+sub fields {
+ my ($class) = @_;
+
+ my %fields =
+ (
+ code =>
+ {
+ description => "Coupon Code",
+ required => 1,
+ width => 20,
+ maxlength => 40,
+ htmltype => "text",
+ rules => "dh_one_line;coupon_code",
+ },
+ description =>
+ {
+ description => "Description",
+ required => 1,
+ width => 80,
+ htmltype => "text",
+ rules => "dh_one_line",
+ },
+ release =>
+ {
+ description => "Release Date",
+ required => 1,
+ width => 10,
+ htmltype => "text",
+ type => "date",
+ rules => "date",
+ },
+ expiry =>
+ {
+ description => "Expiry Date",
+ required => 1,
+ width => 10,
+ htmltype => "text",
+ type => "date",
+ rules => "date",
+ },
+ discount_percent =>
+ {
+ description => "Discount %",
+ required => 1,
+ width => 5,
+ htmltype => "text",
+ rules => "coupon_percent",
+ units => "%",
+ },
+ campaign =>
+ {
+ description => "Campaign",
+ width => 20,
+ maxlength => 20,
+ htmltype => "text",
+ rules => "dh_one_line",
+ },
+ tiers =>
+ {
+ description => "Price Tiers",
+ htmltype => "multicheck",
+ select =>
+ {
+ values => sub {
+ require BSE::TB::PriceTiers;
+ BSE::TB::PriceTiers->getColumnsBy
+ (
+ [ qw(id description) ],
+ [ ],
+ { order => "display_order asc" },
+ );
+ },
+ id => "id",
+ label => "description",
+ },
+ },
+ untiered =>
+ {
+ description => "Untiered",
+ htmltype => "checkbox",
+ units => "Applies to untiered products too",
+ default => 1,
+ },
+ );
+
+ require BSE::Validate;
+ return BSE::Validate::bse_configure_fields(\%fields, BSE::Cfg->single, "bse coupon validation");
+}
+
+sub rules {
+ return
+ {
+ coupon_code =>
+ {
+ match => qr/\A[a-zA-Z0-9]+\z/,
+ error => '$n can only contain letters and digits',
+ },
+ coupon_percent =>
+ {
+ real => '0 - 100',
+ },
+ };
+}
+
+1;
+
+=back
+
+=head1 AUTHOR
+
+Tony Cook <tony@develop-help.com>
+
+=cut
--- /dev/null
+package BSE::TB::CouponTier;
+use strict;
+use Squirrel::Row;
+our @ISA = qw/Squirrel::Row/;
+
+our $VERSION = "1.000";
+
+sub columns {
+ return qw/id coupon_id tier_id/;
+}
+
+sub table {
+ "bse_coupon_tiers";
+}
+
+1;
--- /dev/null
+package BSE::TB::CouponTiers;
+use strict;
+use Squirrel::Table;
+our @ISA = qw(Squirrel::Table);
+use BSE::TB::CouponTier;
+
+our $VERSION = "1.000";
+
+sub rowClass {
+ return 'BSE::TB::CouponTier';
+}
+
+1;
--- /dev/null
+package BSE::TB::Coupons;
+use strict;
+use Squirrel::Table;
+our @ISA = qw(Squirrel::Table);
+use BSE::TB::Coupon;
+
+our $VERSION = "1.000";
+
+sub rowClass {
+ return 'BSE::TB::Coupon';
+}
+
+sub make {
+ my ($self, %opts) = @_;
+
+ my $tiers = delete $opts{tiers};
+
+ my $coupon = $self->SUPER::make(%opts);
+
+ if ($tiers) {
+ $coupon->set_tiers($tiers);
+ }
+
+ return $coupon;
+}
+
+1;
use BSE::Arrows;
use BSE::Shop::Util qw(:payment order_item_opts nice_options payment_types);
use BSE::CfgInfo qw(cfg_dist_image_uri);
+use BSE::Util::SQL qw/now_sqldate sql_to_date date_to_sql sql_date sql_datetime/;
+use BSE::Util::Valid qw/valid_date/;
-our $VERSION = "1.019";
+our $VERSION = "1.020";
my %actions =
(
product_detail => '',
product_list => '',
paypal_refund => 'bse_shop_order_refund_paypal',
+ coupon_list => 'bse_shop_coupon_list',
+ coupon_addform => 'bse_shop_coupon_add',
+ coupon_add => 'bse_shop_coupon_add',
+ coupon_edit => 'bse_shop_coupon_edit',
+ coupon_save => 'bse_shop_coupon_edit',
+ coupon_deleteform => 'bse_shop_coupon_delete',
+ coupon_delete => 'bse_shop_coupon_delete',
);
sub actions {
''
}
+my %csrfp =
+ (
+ coupon_add => { token => "admin_bse_coupon_add", target => "coupon_addform" },
+ coupon_save => { token => "admin_bse_coupon_edit", target => "coupon_edit" },
+ coupon_delete => { token => "admin_bse_coupon_delete", target => "coupon_delete" },
+ );
+
+sub csrfp_tokens {
+ \%csrfp;
+}
+
#####################
# product management
my @catalogs = sort { $b->{displayOrder} <=> $a->{displayOrder} }
grep $_->{generator} eq 'Generate::Catalog', Articles->children($shopid);
my $catalog_index = -1;
- $message ||= $cgi->param('m') || $cgi->param('message') || '';
+ $message = $req->message($message);
if (defined $cgi->param('showstepkids')) {
$session->{showstepkids} = $cgi->param('showstepkids');
}
my $from = $cgi->param('from');
my $to = $cgi->param('to');
- use BSE::Util::SQL qw/now_sqldate sql_to_date date_to_sql sql_date/;
- use BSE::Util::Valid qw/valid_date/;
my $today = now_sqldate();
for my $what ($from, $to) {
if (defined $what) {
return $req->get_refresh($url);
}
+my %coupon_sorts =
+ (
+ expiry => "expiry desc",
+ release => "release desc",
+ code => "code asc",
+ );
+
+=item coupon_list
+
+Display a list of coupons.
+
+Accepts two optional parameters:
+
+=over
+
+=item *
+
+C<sort> which can be any of:
+
+=over
+
+=item *
+
+C<expiry> - sort by expiry date descending
+
+=item *
+
+C<release> - sort by release date descending
+
+=item *
+
+C<code> - sort by code ascending
+
+=back
+
+The default and fallback for unknown values is C<expiry>.
+
+=item *
+
+C<all> - if a true value, returns all coupons, otherwise only coupons
+modified in the last 60 days, or with a release or expiry date in the
+last 60 days are returned.
+
+=back
+
+Allows standard admin tags and variables with the following additional
+variable:
+
+=over
+
+=item *
+
+C<coupons> - an array of coupons
+
+=item *
+
+C<coupons_all> - true if all coupons were requested
+
+=item *
+
+C<coupons_sort> - the
+
+=back
+
+In ajax context returns:
+
+ {
+ success => 1,
+ coupons => [ coupon, ... ]
+ }
+
+where each coupon is a hash containing the coupon data, and the key
+tiers is a list of tier ids.
+
+Template: F<admin/coupons/list>
+
+=cut
+
+sub req_coupon_list {
+ my ($self, $req) = @_;
+
+ my $sort = $req->cgi->param('sort') || 'expiry';
+ $sort =~ /^(expiry|code|release)/ or $sort = 'expiry';
+ my $all = $req->cgi->param('all') || 0;
+ my @cond;
+ unless ($all) {
+ my $past_60_days = sql_datetime(time() - 60 * 86_400);
+ @cond =
+ (
+ [ or =>
+ [ '>', last_modified => $past_60_days ],
+ [ '>', expiry => $past_60_days ],
+ [ '>', release => $past_60_days ],
+ ]
+ );
+ }
+ my $scode = $req->cgi->param('scode');
+ if ($scode) {
+ if ($scode =~ /^=(.*)/) {
+ push @cond, [ '=', code => $1 ];
+ }
+ else {
+ push @cond, [ 'like', code => $scode . '%' ];
+ }
+ }
+ require BSE::TB::Coupons;
+ my @coupons = BSE::TB::Coupons->getBy2
+ (
+ \@cond,
+ { order => $coupon_sorts{$sort} }
+ );
+
+ if ($req->is_ajax) {
+ return $req->json_content
+ (
+ success => 1,
+ coupons => [ map $_->json_data, @coupons ],
+ );
+ }
+
+ $req->set_variable(coupons => \@coupons);
+ $req->set_variable(coupons_all => $all);
+ $req->set_variable(coupons_sort => $sort);
+
+ my %acts = $req->admin_tags;
+
+ return $req->dyn_response('admin/coupons/list', \%acts);
+}
+
+=item coupon_addform
+
+Display a form for adding new coupons.
+
+Template: F<admin/coupons/add>
+
+=cut
+
+sub req_coupon_addform {
+ my ($self, $req, $errors) = @_;
+
+ my %acts = $req->admin_tags;
+
+ $req->message($errors);
+
+ require BSE::TB::Coupons;
+ $req->set_variable(fields => BSE::TB::Coupon->fields);
+ $req->set_variable(coupon => undef);
+ $req->set_variable(errors => $errors || {});
+
+ return $req->dyn_response("admin/coupons/add", \%acts);
+}
+
+=item coupon_add
+
+Add a new coupon.
+
+Accepts coupon fields.
+
+Tiers are accepted as separate values for the tiers field.
+
+CSRF token: C<admin_bse_coupon_add>
+
+=cut
+
+sub req_coupon_add {
+ my ($self, $req) = @_;
+
+ require BSE::TB::Coupons;
+ my $fields = BSE::TB::Coupon->fields;
+ my %errors;
+ $req->validate(fields => $fields, errors => \%errors,
+ rules => BSE::TB::Coupon->rules);
+
+ my $values = $req->cgi_fields(fields => $fields);
+
+ unless ($errors{code}) {
+ my ($other) = BSE::TB::Coupons->getBy(code => $values->{code});
+ $other
+ and $errors{code} = "msg:bse/admin/shop/coupons/adddup:$values->{code}";
+ }
+
+ if (keys %errors) {
+ $req->is_ajax
+ and return $req->field_error(\%errors);
+ return $self->req_coupon_addform($req, \%errors);
+ }
+
+ my $coupon = BSE::TB::Coupons->make(%$values);
+
+ if ($req->is_ajax) {
+ return $req->json_content
+ (
+ success => 1,
+ coupon => $coupon->json_data,
+ );
+ }
+ else {
+ $req->flash_notice("msg:bse/admin/shop/coupons/add", [ $coupon ]);
+
+ return $req->get_def_refresh($req->cfg->admin_url2("shopadmin", "coupon_list"));
+ }
+}
+
+sub _get_coupon {
+ my ($self, $req, $rresult) = @_;
+
+ my $cgi = $req->cgi;
+ my $id = $cgi->param("id");
+ require BSE::TB::Coupons;
+ my $coupon;
+ if ($id) {
+ $coupon = BSE::TB::Coupons->getByPkey($id);
+ }
+ else {
+ my $code = $cgi->param("code");
+ if ($code) {
+ ($coupon) = BSE::TB::Coupons->getBy(code => $code);
+ }
+ }
+ unless ($coupon) {
+ $$rresult = $self->req_coupon_list($req, { id => "Missing id or code" });
+ return;
+ }
+
+ return $coupon;
+}
+
+sub _get_coupon_id {
+ my ($self, $req, $rresult) = @_;
+
+ my $cgi = $req->cgi;
+ my $id = $cgi->param("id");
+ require BSE::TB::Coupons;
+ my $coupon;
+ if ($id) {
+ $coupon = BSE::TB::Coupons->getByPkey($id);
+ }
+ unless ($coupon) {
+ $$rresult = $self->req_coupon_list($req, { id => "Missing id or code" });
+ return;
+ }
+
+ return $coupon;
+}
+
+=item coupon_edit
+
+Edit a coupon.
+
+Requires C<id> as a coupon id to edit.
+
+Template: F<admin/coupons/edit>
+
+=cut
+
+sub req_coupon_edit {
+ my ($self, $req, $errors) = @_;
+
+ my $result;
+ my $coupon = $self->_get_coupon_id($req, \$result)
+ or return $result;
+
+ my %acts = $req->admin_tags;
+
+ $req->message($errors);
+
+ require BSE::TB::Coupons;
+ $req->set_variable(fields => BSE::TB::Coupon->fields);
+ $req->set_variable(coupon => $coupon);
+ $req->set_variable(errors => $errors || {});
+
+ return $req->dyn_response("admin/coupons/edit", \%acts);
+}
+
+=item coupon_save
+
+Save changes to a coupon, accepts:
+
+=over
+
+=item *
+
+C<id> - id of the coupon to save.
+
+=item *
+
+other coupon fields.
+
+=back
+
+CSRF token: C<admin_bse_coupon_save>
+
+=cut
+
+sub req_coupon_save {
+ my ($self, $req) = @_;
+
+ my $result;
+ my $coupon = $self->_get_coupon_id($req, \$result)
+ or return $result;
+
+ require BSE::TB::Coupons;
+ my $fields = BSE::TB::Coupon->fields;
+ my %errors;
+ $req->validate(fields => $fields, errors => \%errors,
+ rules => BSE::TB::Coupon->rules);
+
+ my $values = $req->cgi_fields(fields => $fields);
+
+ unless ($errors{code}) {
+ my ($other) = BSE::TB::Coupons->getBy(code => $values->{code});
+ $other && $other->id != $coupon->id
+ and $errors{code} = "msg:bse/admin/shop/coupons/editdup:$values->{code}";
+ }
+
+ if (keys %errors) {
+ $req->is_ajax
+ and return $req->field_error(\%errors);
+ return $self->req_coupon_edit($req, \%errors);
+ }
+
+ my $tiers = delete $values->{tiers};
+ for my $key (keys %$values) {
+ $coupon->set($key => $values->{$key});
+ }
+ $coupon->set_tiers($tiers);
+ $coupon->save;
+
+ if ($req->is_ajax) {
+ return $req->json_content
+ (
+ success => 1,
+ coupon => $coupon->json_data,
+ );
+ }
+ else {
+ $req->flash_notice("msg:bse/admin/shop/coupons/save", [ $coupon ]);
+
+ return $req->get_def_refresh($req->cfg->admin_url2("shopadmin", "coupon_list"));
+ }
+}
+
+=item coupon_deleteform
+
+Prompt for deletion of a coupon
+
+Requires C<id> as a coupon id to elete.
+
+Template: F<admin/coupons/delete>
+
+=cut
+
+sub req_coupon_deleteform {
+ my ($self, $req) = @_;
+
+ my $result;
+ my $coupon = $self->_get_coupon_id($req, \$result)
+ or return $result;
+
+ my %acts = $req->admin_tags;
+
+ require BSE::TB::Coupons;
+ $req->set_variable(fields => BSE::TB::Coupon->fields);
+ $req->set_variable(coupon => $coupon);
+
+ return $req->dyn_response("admin/coupons/delete", \%acts);
+}
+
+=item coupon_delete
+
+Delete a coupon
+
+Requires C<id> as a coupon id to delete.
+
+CSRF token: C<admin_bse_coupon_delete>
+
+=cut
+
+sub req_coupon_delete {
+ my ($self, $req) = @_;
+
+ my $result;
+ my $coupon = $self->_get_coupon_id($req, \$result)
+ or return $result;
+
+ my $code = $coupon->code;
+ $coupon->remove;
+
+ if ($req->is_ajax) {
+ return $req->json_content(success => 1);
+ }
+ else {
+ $req->flash_notice("msg:bse/admin/shop/coupons/delete", [ $code ]);
+
+ return $req->get_def_refresh($req->cfg->admin_url2("shopadmin", "coupon_list"));
+ }
+}
+
#####################
# utilities
# perhaps some of these belong in a class...
-# format an ANSI SQL date for display
-sub money_to_cents {
- my $money = shift;
-
- $$money =~ /^\s*(\d+(\.\d*)|\.\d+)/
- or return undef;
- return $$money = sprintf("%.0f ", $$money * 100);
-}
# convert an epoch time to sql format
sub epoch_to_sql {
use BSE::Util::Secure qw(make_secret);
use BSE::Template;
-our $VERSION = "1.038";
+our $VERSION = "1.039";
+
+=head1 NAME
+
+BSE::UI::Shop - implements the shop for BSE
+
+=head1 DESCRIPTION
+
+BSE::UI::Shop implements the shop for BSE.
+
+=head1 TARGETS
+
+=over
+
+=cut
use constant MSG_SHOP_CART_FULL => 'Your shopping cart is full, please remove an item and try adding an item again';
$class->_refresh_cart($req);
- my @cart = @{$req->session->{cart} || []};
- my @cart_prods;
- my @items = $class->_build_items($req, \@cart_prods);
- my $item_index = -1;
- my @options;
- my $option_index;
-
my $cart = $req->cart("cart");
- $req->session->{custom} ||= {};
- my %custom_state = %{$req->session->{custom}};
my $cust_class = custom_class($req->cfg);
- $cust_class->enter_cart(\@cart, \@cart_prods, \%custom_state, $req->cfg);
+ # $req->session->{custom} ||= {};
+ # my %custom_state = %{$req->session->{custom}};
+
+ # $cust_class->enter_cart(\@cart, \@cart_prods, \%custom_state, $req->cfg);
$msg = '' unless defined $msg;
$msg = escape_html($msg);
my %acts;
%acts =
(
- $cust_class->cart_actions(\%acts, $cart->items, $cart->products,
+ $cust_class->cart_actions(\%acts, scalar $cart->items, scalar $cart->products,
$cart->custom_state, $req->cfg),
shop_cart_tags(\%acts, $cart, $req, 'cart'),
basic_tags(\%acts),
my $old = $req->session->{cart};;
$req->session->{cart} = [];
+ delete $req->session->{cart_coupon_code};
my $refresh = $req->cgi->param('r');
unless ($refresh) {
ifNeedDelivery => $need_delivery,
);
$req->session->{custom} = $cart->custom_state;
- my $tmp = $acts{total};
- $acts{total} =
- sub {
- my $total = &$tmp();
- $total += $shipping_cost if $total and $shipping_cost;
- return $total;
- };
return $req->response('checkoutnew', \%acts);
}
$errors and $payment = $cgi->param('paymentType');
defined $payment or $payment = $payment_types[0];
+ $cart->set_shipping_cost($order->{shipping_cost});
+
my %acts;
%acts =
(
}
$req->set_variable(ordercart => $cart);
$req->set_variable(order => $order);
+ $req->set_variable(is_order => !!$order_id);
return $req->response('checkoutpay', \%acts);
}
$items[$item_index]{units} * $items[$item_index]{$what};
},
order => sub { escape_html($order->{$_[0]}) },
- _format =>
- sub {
- my ($value, $fmt) = @_;
- if ($fmt =~ /^m(\d+)/) {
- return sprintf("%$1s", sprintf("%.2f", $value/100));
- }
- elsif ($fmt =~ /%/) {
- return sprintf($fmt, $value);
- }
- },
iterate_options_reset => sub { $option_index = -1 },
iterate_options => sub { ++$option_index < @options },
option => sub { escape_html($options[$option_index]{$_[0]}) },
$class->update_quantities($req);
$req->session->{order_info_confirmed} = 0;
- return $class->req_cart($req);
+
+ my $refresh = $req->cgi->param('r');
+ unless ($refresh) {
+ $refresh = $req->user_url(shop => 'cart');
+ }
+
+ return $req->get_refresh($refresh);
}
sub req_recalculate {
}
}
}
- my ($coupon) = $cgi->param("coupon");
- if (defined $coupon) {
- $session->{cart_coupon} = $coupon;
- }
@cart = grep { $_->{units} != 0 } @cart;
$session->{cart} = \@cart;
$session->{custom} ||= {};
my %custom_state = %{$session->{custom}};
custom_class($cfg)->recalc($cgi, \@cart, [], \%custom_state, $cfg);
$session->{custom} = \%custom_state;
+
+ my ($coupon) = $cgi->param("coupon");
+ if (defined $coupon) {
+ my $cart = $req->cart;
+ $cart->set_coupon_code($coupon);
+ }
}
sub _build_items {
1;
-__END__
-
-=head1 NAME
-
-shop.pl - implements the shop for BSE
-
-=head1 DESCRIPTION
-
-shop.pl implements the shop for BSE.
+=back
=head1 TAGS
=back
-You can also use "|format" at the end of a field to perform some
-simple formatting. Eg. <:order total |m6:> or <:order id |%06d:>.
-
-=over 4
-
-=item m<number>
-
-Formats the value as a <number> wide money value.
-
-=item %<format>
-
-Performs sprintf() formatting on the value. Eg. %06d will format 25
-as 000025.
-
-=back
-
=head2 Mailed order tags
These tags are used in the emails sent to the user to confirm an order
=over 4
-=item iterate ... items
+=item *
+
+C<iterate> ... C<items>
Iterates over the items in the order.
-=item item I<field>
+=item *
+
+C<item> I<field>
Access to the given field in the order item.
-=item product I<field>
+=item *
+
+C<product> I<field>
Access to the product field for the current order item.
-=item order I<field>
+=item *
+
+C<order> I<field>
Access to fields of the order.
-=item extended I<field>
+=item *
+
+C<extended> I<field>
The product of the I<field> in the current item and it's quantity.
-=item money I<tag> I<parameters>
+=item *
+
+C<money> I<tag> I<parameters>
Formats the given field as a money value.
=over 4
-=item m<number>
+=item *
+
+m<number>
Format the value as a I<number> wide money value.
-=item %<format>
+=item *
+
+%<format>
Performs sprintf formatting on the value.
-=item <number>
+=item *
+
+<number>
Left justifies the value in a I<number> wide field.
The order email sent to the site administrator has a couple of extra
fields:
-=over 4
+=over
-=item cardNumber
+=item *
+
+cardNumber
The credit card number of the user's credit card.
-=item cardExpiry
+=item *
+
+cardExpiry
The entered expiry date for the user's credit card.
Monetary values should typically be used with <:money order ...:>
-=over 4
-
-=item id
-
-The order id or order number.
-
-=item delivFirstName
-
-=item delivLastName
-
-=item delivStreet
-
-=item delivSuburb
-
-=item delivState
-
-=item delivPostCode
-
-=item delivCountry
-
-Delivery information for the order.
-
-=item billFirstName
-
-=item billLastName
-
-=item billStreet
+=over
-=item billSuburb
+=item *
-=item billState
+id
-=item billPostCode
+The order id or order number.
-=item billCountry
+=item *
-Billing information for the order.
+delivFirstName, delivLastName, delivStreet, delivSuburb, delivState,
+delivPostCode, delivCountry - Delivery information for the order.
-=item telephone
+=item *
-=item facsimile
+billFirstName, billLastName, billStreet, billSuburb, billState,
+billPostCode, billCountry - Billing information for the order.
-=item emailAddress
+=item *
-Contact information for the order.
+telephone, facsimile, emailAddress - Contact information for the
+order.
-=item total
+=item *
-Total price of the order.
+total - Total price of the order.
-=item wholesaleTotal
+=item *
-Wholesale cost of the total. Your costs, if you entered wholesale
-prices for the products.
+wholesaleTotal - Wholesale cost of the total. Your costs, if you
+entered wholesale prices for the products.
-=item gst
+=item *
-GST (in Australia) payable on the order, if you entered GST for the products.
+gst - GST (in Australia) payable on the order, if you entered GST for
+the products.
-=item orderDate
+=item *
-When the order was made.
+orderDate - When the order was made.
-=item filled
+=item *
-Whether or not the order has been filled. This can be used with the
-order_filled target in shopadmin.pl for tracking filled orders.
+filled - Whether or not the order has been filled. This can be used
+with the order_filled target in shopadmin.pl for tracking filled
+orders.
-=item whenFilled
+=item *
-The time and date when the order was filled.
+whenFilled - The time and date when the order was filled.
-=item whoFilled
+=item *
-The user who marked the order as filled.
+whoFilled - The user who marked the order as filled.
-=item paidFor
+=item *
-Whether or not the order has been paid for. This can be used with a
-custom purchasing handler to mark the product as paid for. You can
-then filter the order list to only display paid for orders.
+paidFor - Whether or not the order has been paid for. This can be
+used with a custom purchasing handler to mark the product as paid for.
+You can then filter the order list to only display paid for orders.
-=item paymentReceipt
+=item *
-A custom payment handler can fill this with receipt information.
+paymentReceipt - A custom payment handler can fill this with receipt
+information.
-=item randomId
+=item *
-Generated by the prePurchase target, this can be used as a difficult
-to guess identifier for orders, when working with custom payment
-handlers.
+randomId - Generated by the prePurchase target, this can be used as a
+difficult to guess identifier for orders, when working with custom
+payment handlers.
-=item cancelled
+=item *
-This can be used by a custom payment handler to mark an order as
-cancelled if the user starts processing an order without completing
-payment.
+cancelled - This can be used by a custom payment handler to mark an
+order as cancelled if the user starts processing an order without
+completing payment.
=back
=head2 Order item fields
-=over 4
+=over
-=item productId
+=item *
-The product id of this item.
+productId - The product id of this item.
-=item orderId
+=item *
-The order Id.
+orderId - The order Id.
-=item units
+=item *
-The number of units for this item.
+units - The number of units for this item.
-=item price
+=item *
-The price paid for the product.
+price - The price paid for the product.
-=item wholesalePrice
+=item *
-The wholesale price for the product.
+wholesalePrice - The wholesale price for the product.
-=item gst
+=item *
-The gst for the product.
+gst - The gst for the product.
-=item options
+=item *
-A comma separated list of options specified for this item. These
-correspond to the option names in the product.
+options - A comma separated list of options specified for this item.
+These correspond to the option names in the product.
=back
=over
-=item iterator ... options
+=item *
+
+C<iterator> ... <options>
within an item, iterates over the options for this item in the cart.
Sets the item tag.
-=item option field
+=item *
+
+C<option> I<field>
Retrieves the given field from the option, possible field names are:
=over
-=item id
+=item *
-The type/identifier for this option. eg. msize for a male clothing
-size field.
+id - The type/identifier for this option. eg. msize for a male
+clothing size field.
-=item value
+=item *
-The underlying value of the option, eg. XL.
+value - The underlying value of the option, eg. XL.
-=item desc
+=item *
-The description of the field from the product options hash. If the
-description isn't defined this is the same as the id. eg. Size.
+desc - The description of the field from the product options hash. If
+the description isn't defined this is the same as the id. eg. Size.
-=item label
+=item *
-The description of the value from the product options hash.
+label - The description of the value from the product options hash.
eg. "Extra large".
=back
-=item ifOptions
+=item *
-A conditional tag, true if the current cart item has any options.
+ifOptions - A conditional tag, true if the current cart item has any
+options.
-=item options
+=item *
-A simple rendering of the options as a parenthesized comma-separated
-list.
+options - A simple rendering of the options as a parenthesized
+comma-separated list.
=back
id: bse/admin/shop/saveorder/badstage
description: Invalid order stage, $1 is the supplied stage id
+id: bse/admin/shop/coupons/
+description: Message for coupon management
+
+id: bse/admin/shop/coupons/adddup
+description: Displayed on a duplicate code when adding a coupon (%1 - coupon code)
+
+id: bse/admin/shop/coupons/add
+description: Flashed when a coupon is added successfully (%1 coupon)
+
+id: bse/admin/shop/coupons/editdup
+description: Displayed on a duplicate code when editing a coupon (%1 - coupon code)
+
+id: bse/admin/shop/coupons/save
+description: Flashed when a coupon is saved (%1 coupon)
+
+id: bse/admin/shop/coupons/delete
+description: Flashed when a coupon is deleted (%1 coupon code)
+
id: bse/admin/logon/
description: Logon tool messages
id: bse/admin/shop/saveorder/badstage
message: Unknown order stage '%1:s'
+id: bse/admin/shop/coupons/adddup
+message: Duplicate code '%1:s'
+
+id: bse/admin/shop/coupons/add
+message: Added new coupon '%1:{code}s'
+
+id: bse/admin/shop/coupons/editdup
+message: Duplicate code '%1:s'
+
+id: bse/admin/shop/coupons/save
+message: Saved coupon '%1:{code}s'
+
+id: bse/admin/shop/coupons/delete
+message: Deleted coupon '%1:s'
+
id: bse/admin/logon/logoff
message: Administrative user '%1:s' logged off
select bs.*, us.* from admin_base bs, admin_users us
where bs.id = us.base_id and us.password_type = ?
SQL
+
+name: PriceTiers.forCoupon
+sql_statement: <<SQL
+select t.*
+from bse_price_tiers t, bse_coupon_tiers c
+where c.coupon_id = ?
+ and c.tier_id = t.id
+SQL
--- /dev/null
+<:wrap admin/base.tmpl title => "Shop: Add Coupon Code", bodyid => "coupon_add":>
+<h1>Shop: Add Coupon Code</h1>
+<p>| <a href="<:= cfg.admin_url("menu") :>">Admin Menu</a> |
+<:.if request.user_can("bse_shop_coupon_list") -:>
+<a href="<:= cfg.admin_url2("shopadmin", "coupon_list") :>">Return to coupon list</a> |
+<:.end if-:>
+</p>
+<:.call "messages"-:>
+<:.set object = coupon -:>
+<form action="<:= cfg.admin_url("shopadmin") :>" method="post">
+ <:csrfp admin_bse_coupon_add hidden:>
+ <fieldset>
+ <legend>Coupon Details</legend>
+ <:.call "field", "name":"code", "autofocus":1 :>
+ <:.call "field", "name":"description" :>
+ <:.call "field", "name":"release" :>
+ <:.call "field", "name":"expiry" :>
+ <:.call "field", "name":"discount_percent" :>
+ <:.call "field", "name":"untiered" :>
+ <:.call "field", "name":"campaign" :>
+ </fieldset>
+ <:.call "fieldset", "name":"tiers" :>
+ <p class="buttons">
+ <input type="submit" name="a_coupon_add" value="Add Coupon">
+ </p>
+</form>
--- /dev/null
+<:wrap admin/base.tmpl title => "Shop: Delete Coupon Code", bodyid => "coupon_delete":>
+<h1>Shop: Delete Coupon <span><:= coupon.code -:></span></h1>
+<p>| <a href="<:= cfg.admin_url("menu") :>">Admin Menu</a> |
+<:.if request.user_can("bse_shop_coupon_list") -:>
+<a href="<:= cfg.admin_url2("shopadmin", "coupon_list") :>">Return to coupon list</a> |
+<:.end if-:>
+</p>
+<:.call "messages"-:>
+<:.set object = coupon -:>
+<form action="<:= cfg.admin_url("shopadmin") :>" method="post">
+ <:csrfp admin_bse_coupon_delete hidden:>
+ <input type="hidden" name="id" value="<:= coupon.id :>">
+ <p class="warning">Deleting a coupon cannot be reversed.</p>
+ <fieldset>
+ <legend>Coupon Details</legend>
+ <:.call "fieldro", "name":"code", "autofocus":1 :>
+ <:.call "fieldro", "name":"description" :>
+ <:.call "fieldro", "name":"release" :>
+ <:.call "fieldro", "name":"expiry" :>
+ <:.call "fieldro", "name":"discount_percent" :>
+ <:.call "fieldro", "name":"campaign" :>
+ </fieldset>
+ <:.call "fieldsetro", "name":"tiers" :>
+ <p class="buttons">
+ <input type="submit" name="a_coupon_delete" value="Delete Coupon">
+ </p>
+</form>
--- /dev/null
+<:wrap admin/base.tmpl title => "Shop: Edit Coupon Code", bodyid => "coupon_edit":>
+<h1>Shop: Edit Coupon <span><:= coupon.code -:></span></h1>
+<p>| <a href="<:= cfg.admin_url("menu") :>">Admin Menu</a> |
+<:.if request.user_can("bse_shop_coupon_list") -:>
+<a href="<:= cfg.admin_url2("shopadmin", "coupon_list") :>">Return to coupon list</a> |
+<:.end if-:>
+</p>
+<:.call "messages"-:>
+<:.set object = coupon -:>
+<form action="<:= cfg.admin_url("shopadmin") :>" method="post">
+ <:csrfp admin_bse_coupon_edit hidden:>
+ <input type="hidden" name="id" value="<:= coupon.id :>">
+ <fieldset>
+ <legend>Coupon Details</legend>
+ <:.call "field", "name":"code", "autofocus":1 :>
+ <:.call "field", "name":"description" :>
+ <:.call "field", "name":"release" :>
+ <:.call "field", "name":"expiry" :>
+ <:.call "field", "name":"discount_percent" :>
+ <:.call "field", "name":"untiered" :>
+ <:.call "field", "name":"campaign" :>
+ </fieldset>
+ <:.call "fieldset", "name":"tiers" :>
+ <p class="buttons">
+ <input type="submit" name="a_coupon_save" value="Save Coupon">
+ </p>
+</form>
--- /dev/null
+<:wrap admin/base.tmpl title => "Shop: Coupon List", bodyid => "coupon_list":>
+<h1>Shop: Coupon List</h1>
+<p>| <a href="<:= cfg.admin_url("menu") :>">Admin Menu</a> |
+<:.if request.user_can("bse_shop_coupon_add") -:>
+<a href="<:= cfg.admin_url2("shopadmin", "coupon_addform") :>">Add a coupon</a> |
+<:.end if-:>
+</p>
+
+<:.call "messages"-:>
+<form action="<:= cfg.admin_url("shopadmin") :>">
+<:.set object = 0 -:>
+<:.set errors = {} -:>
+<fieldset>
+ <legend>Filter/sort</legend>
+ <:.call "inlinefield",
+ "name":"all",
+ "field":{
+ "description": "Show all",
+ "htmltype": "checkbox",
+ "default": coupons_all
+ } -:>
+ <:.call "inlinefield",
+ "name":"sort",
+ "field":{
+ "description": "Sort",
+ "htmltype": "select",
+ "default": coupons_sort,
+ "select":
+ {
+ "values":
+ [
+ { "id": "expiry", "desc":"By Expiry date" },
+ { "id": "release", "desc":"By Release date" },
+ { "id": "code", "desc":"By code" }
+ ],
+ "id":"id",
+ "label":"desc"
+ }
+ } -:>
+ <:.call "inlinefield",
+ "name": "scode",
+ "field": {
+ "description": "Search code",
+ "units": "(=code to search for exact code, otherwise prefix)",
+ "maxlength": 40,
+ "size": 20
+ } -:>
+</fieldset>
+<p class="buttons"><input type="submit" name="a_coupon_list" value="Sort/Filtter"></p>
+</form>
+
+<table>
+ <tr>
+ <th class="col_id">Id</th>
+ <th class="col_code">Code</th>
+ <th class="col_description">Description</th>
+ <th class="col_release">Release</th>
+ <th class="col_expiry">Expires</th>
+ <th class="col_discount">Discount</th>
+ <th class="col_tiers">Tiers</th>
+ <th class="col_campaign">Campaign</th>
+ <th class="col_actions"></th>
+ </tr>
+
+<:.if coupons.size -:>
+ <:.for coupon in coupons -:>
+ <:.set classes = [ loop.even ? "even" : "odd" ] -:>
+ <:.if coupon.is_expired -:>
+ <:% classes.push("expired") -:>
+ <:.elsif coupon.is_released -:>
+ <:% classes.push("released") -:>
+ <:.end if -:>
+ <:.set tier_names = [] -:>
+ <:.for tier in [ coupon.tier_objects ] -:>
+ <:% tier_names.push(tier.description) -:>
+ <:.end for -:>
+ <tr class="<:= classes.join(" ") :>">
+ <td class="col_id"><a href="<:= cfg.admin_url2("shopadmin", "details", { "id": coupon.id }) :>"><:= coupon.id :></a></td>
+ <td class="col_code"><:= coupon.code :></td>
+ <td class="col_description"><:= coupon.description :></td>
+ <td class="col_release"><:= bse.date("%d/%m/%Y", coupon.release) :></td>
+ <td class="col_expiry"><:= bse.date("%d/%m/%Y", coupon.expiry) :></td>
+ <td class="col_discount"><:= coupon.discount_percent :>%</td>
+ <td class="col_tiers"><:= tier_names.size ? tier_names.join(", ") : "(none)" :></td>
+ <td class="col_campaign"><:= coupon.campaign :></td>
+ <td class="col_actions">
+ <:.if request.user_can("bse_shop_coupon_edit") -:>
+ <a href="<:= cfg.admin_url2("shopadmin", "coupon_edit", { "id": coupon.id }) :>">Edit</a>
+ <:.end if -:>
+ <:.if request.user_can("bse_shop_coupon_delete") -:>
+ <a href="<:= cfg.admin_url2("shopadmin", "coupon_deleteform", { "id": coupon.id }) :>">Delete</a>
+ <:.end if -:>
+ </td>
+ </tr>
+ <:.end for -:>
+<:.else -:>
+ <tr class="nothing">
+ <td colspan="8">No coupons are currently defined</td>
+ </tr>
+<:.end if -:>
+</table>
<li><a href="<:= cfg.admin_url("add", { "parentid":3 }) | html :>">Add catalog</a></li>
+<:.if request.user_can("bse_shop_coupon_list") -:>
+<li><a href="<:= cfg.admin_url2("shopadmin", "coupon_list") :>">Coupons</a></li>
+<:.end if -:>
+
</ul>
<:.if request.user_can("bse_admin_import") :>
</table>
<table border="0" cellspacing="0" cellpadding="1" width="100%" bgcolor="#666666">
<tr valign="middle" align="center">
- <td width="100%">
+ <td width="100%">
<table width="100%" border="0" cellspacing="1" cellpadding="1" bgcolor="#EEEEEE">
<tr valign="middle" align="center" bgcolor="#666666">
+<:.set cart = request.cart -:>
+<:.if cart.coupon_valid and !cart.coupon_active -:>
+ <td></td>
+<:.end if -:>
<td width="100%" align="left" height="18"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF"><b>Item:</b></font> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF">(All
prices in AUD – includes GST and shipping costs where applicable)</font></td>
<td nowrap height="18"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF"><b>Qty:</b></font> </td>
<:.set options = item.option_list -:>
<:.set session = item.session -:>
<tr valign="middle" align="center" bgcolor="#FFFFFF">
+<:.if cart.coupon_valid and !cart.coupon_active -:>
+ <td>
+<:= item.coupon_applies ? "Y" : "N" -:>
+ </td>
+<:.end if -:>
<td width="100%" align="left"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><a href="<:= item.link | html:>"><:= item.product.description | html :> <:.if options.size:>(<:.for option in options:><:= loop.index ? ", " : "" :><:= option.desc | html:>:
<:= option.display |html :><:.end for:>)<:.end if -:></a><:.if item.session_id:>(session at <:= session.location.description | html:> <:= bse.date("%H:%M %d/%m/%Y", session.when_at) -:>)<:.end if:></font></td>
<td nowrap align="center">
</tr>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td>Coupon code: <input type="text" name="coupon" value="<:= cart.coupon_code -:>">
+<:.if cart.coupon_active -:>
+Coupon active
+<:.elsif cart.coupon_valid -:>
+<:.if request.siteuser -:>
+Your cart contains items the code isn't valid for
+<:.else -:>
+You need to logon
+<:.end if -:>
+<:.elsif cart.coupon_code ne "" -:>
+Unknown coupon code
+<:.end if -:>
+</td>
+<:.if cart.coupon_active -:>
+ <td height="20"> </td>
+ <td height="20" bgcolor="#666666"> </td>
+ <td align="CENTER" height="20" bgcolor="#666666" NOWRAP><font size="2" face="Verdana, Arial, Helvetica, sans-serif" color="#FFFFFF">
+ <b>DISCOUNT</b></font></td>
+ <td height="20" bgcolor="#666666"> </td>
+<:.else -:>
+ <td colspan="6"></td>
+<:.end if -:>
+ </tr>
+<:.if cart.coupon_active -:>
+ <tr>
+ <td colspan="2"> </td>
+ <td height="20" style="border-left: 1px solid #666666"> </td>
+ <td align="CENTER">$<:= bse.number("money", cart.product_cost_discount) -:></td>
+ <td height="20" style="border-right: 1px solid #666666"> </td>
+ </tr>
+<:.end if -:>
<tr>
<td> </td>
<td height="20"> </td>
<td width="100%">
<table width="100%" border="0" cellspacing="1" cellpadding="2" bgcolor="#EEEEEE">
<tr valign="middle" align="center" bgcolor="#666666">
+<:.set cart = request.cart -:>
+<:.if cart.coupon_valid and !cart.coupon_active -:>
+ <td></td>
+<:.end if -:>
<td width="100%" align="left" height="18"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF"><b>Item:</b></font> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF">(All
prices in AUD – includes GST and shipping costs where applicable)</font></td>
<td nowrap height="18"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF"><b>Qty:</b></font> </td>
<td height="18"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF"><b>Price:</b></font> </td>
</tr>
- <:iterator begin items:>
- <tr valign="middle" align="center" bgcolor="#FFFFFF">
- <td width="100%" align="left"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><a href="<:item link:>"><:item
- description:> <:options:></a><:ifItem session_id:>(session at <:location description:> <:date "%H:%M %d/%m/%Y" session when_at:>)<:or:><:eif:></font></td>
- <td nowrap align="center"><font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><:item
- units:></font></td>
- <td align="right"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><b>$<:
- money item price :></b></font></td>
- </tr>
- <:iterator end items:>
+ <:-.set items = request.cart.items -:>
+ <:.if items.size -:>
+ <:.for item in items -:>
+ <:.set options = item.option_list -:>
+ <:.set session = item.session -:>
+ <tr valign="middle" align="center" bgcolor="#FFFFFF">
+<:.if cart.coupon_valid and !cart.coupon_active -:>
+ <td>
+<:= item.coupon_applies ? "Y" : "N" -:>
+ </td>
+<:.end if -:>
+ <td width="100%" align="left"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><a href="<:= item.link | html:>"><:= item.product.description | html :> <:.if options.size:>(<:.for option in options:><:= loop.index ? ", " : "" :><:= option.desc | html:>:
+ <:= option.display |html :><:.end for:>)<:.end if -:></a><:.if item.session_id:>(session at <:= session.location.description | html:> <:= bse.date("%H:%M %d/%m/%Y", session.when_at) -:>)<:.end if:></font></td>
+ <td nowrap align="center">
+ <input type="text" name="quantity_<:= loop.index :>" size="2" value="<:= item.units :>">
+ </td>
+ <td align="right"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><b>$<:= bse.number("money", item.price) | html :></b></font></td>
+ </tr>
+ <:.end for -:>
+ <:.else -:>
+ <tr valign="middle" align="center" bgcolor="#FFFFFF">
+ <td width="100%" height="20" align="center" colspan="4"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">You have no items in your shopping cart!</font></td>
+ </tr>
+ <:.end if -:>
<:if Shipping_cost:>
<tr valign="middle" align="center" bgcolor="#FFFFFF">
<td colspan=2 width="100%" align="left"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2">Shipping charges (for <:shipping_method:><:if Delivery_in:>, delivery in <:delivery_in:> days<:or Delivery_in:><:eif Delivery_in:>)</font></td>
</tr>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td>Coupon code: <input type="text" name="coupon" value="<:= cart.coupon_code -:>">
+<:.if cart.coupon_active -:>
+Coupon active
+<:.elsif cart.coupon_valid -:>
+<:.if request.siteuser -:>
+Your cart contains items the code isn't valid for
+<:.else -:>
+You need to logon
+<:.end if -:>
+<:.elsif cart.coupon_code ne "" -:>
+Unknown coupon code
+<:.end if -:>
+</td>
+<:.if cart.coupon_active -:>
+ <td height="20"> </td>
+ <td height="20" bgcolor="#666666"> </td>
+ <td align="CENTER" height="20" bgcolor="#666666" NOWRAP><font size="2" face="Verdana, Arial, Helvetica, sans-serif" color="#FFFFFF">
+ <b>DISCOUNT</b></font></td>
+ <td height="20" bgcolor="#666666"> </td>
+<:.else -:>
+ <td colspan="6"></td>
+<:.end if -:>
+ </tr>
+<:.if cart.coupon_active -:>
+ <tr>
+ <td colspan="2"> </td>
+ <td height="20" style="border-left: 1px solid #666666"> </td>
+ <td align="CENTER">$<:= bse.number("money", cart.product_cost_discount) -:></td>
+ <td height="20" style="border-right: 1px solid #666666"> </td>
+ </tr>
+<:.end if -:>
<tr>
<td> </td>
<td height="20"> </td>
<td nowrap height="18"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF"><b>Qty:</b></font> </td>
<td height="18"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2" color="#FFFFFF"><b>Price:</b></font> </td>
</tr>
- <:iterator begin items:>
- <tr valign="middle" align="center" bgcolor="#FFFFFF">
- <td width="100%" align="left"> <a href="<:item link:>"><font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><:item
- description:> <:options:></font></a></td>
- <td nowrap align="center"><font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><:item
- units:></font></td>
- <td align="right"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><b>$<:
- money item price :></b></font></td>
- </tr>
- <:iterator end items:>
+ <:-.set items = [ ordercart.items ] -:>
+ <:.if items.size -:>
+ <:.for item in items -:>
+ <:.set options = item.option_list -:>
+ <:.set session = item.session -:>
+ <tr valign="middle" align="center" bgcolor="#FFFFFF">
+ <td width="100%" align="left"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><a href="<:= item.link | html:>"><:= item.product.description | html :> <:.if options.size:>(<:.for option in options:><:= loop.index ? ", " : "" :><:= option.desc | html:>:
+ <:= option.display |html :><:.end for:>)<:.end if -:></a><:.if item.session_id:>(session at <:= session.location.description | html:> <:= bse.date("%H:%M %d/%m/%Y", session.when_at) -:>)<:.end if:></font></td>
+ <td nowrap align="center">
+ <input type="text" name="quantity_<:= loop.index :>" size="2" value="<:= item.units :>">
+ </td>
+ <td align="right"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2"><b>$<:= bse.number("money", item.price) | html :></b></font></td>
+ </tr>
+ <:.end for -:>
+ <:.else -:>
+ <tr valign="middle" align="center" bgcolor="#FFFFFF">
+ <td width="100%" height="20" align="center" colspan="4"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">You have no items in your shopping cart!</font></td>
+ </tr>
+ <:.end if -:>
<:if Shipping_cost:>
<tr valign="middle" align="center" bgcolor="#FFFFFF">
<td colspan=2 width="100%" align="left"> <font face="Verdana, Arial, Helvetica, sans-serif" size="-2">Shipping charges (for <:shipping_method:><:if Delivery_in:>, delivery in <:delivery_in:> days<:or Delivery_in:><:eif Delivery_in:>)</font></td>
</td>
</tr>
</table>
-<table width="100%" border="0" cellspacing="0" cellpadding="0">
+<table width="100%" border="0" cellspacing="0" cellpadding="0">
+<:.if is_order ? order.coupon_code ne "" : ordercart.coupon_active -:>
+ <tr>
+ <td>Coupon code: <:= ordercart.coupon_code -:>
+</td>
+ <td height="20"> </td>
+ <td height="20" bgcolor="#666666"> </td>
+ <td align="CENTER" height="20" bgcolor="#666666" NOWRAP><font size="2" face="Verdana, Arial, Helvetica, sans-serif" color="#FFFFFF">
+ <b>DISCOUNT</b></font></td>
+ <td height="20" bgcolor="#666666"> </td>
+ </tr>
+ <tr>
+ <td colspan="2"> </td>
+ <td height="20" style="border-left: 1px solid #666666"> </td>
+ <td align="CENTER">$<:= bse.number("money", ordercart.product_cost_discount) -:></td>
+ <td height="20" style="border-right: 1px solid #666666"> </td>
+ </tr>
+<:.end if -:>
<tr>
<td> </td>
<td height="20"> </td>
Table admin_base
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column type;char(1);NO;NULL;
Index PRIMARY;1;[id]
Table admin_groups
-Engine MyISAM
+Engine InnoDB
Column base_id;int(11);NO;NULL;
Column name;varchar(80);NO;NULL;
Column description;varchar(255);NO;NULL;
Index PRIMARY;1;[base_id]
Index name;1;[name]
Table admin_membership
-Engine MyISAM
+Engine InnoDB
Column user_id;int(11);NO;NULL;
Column group_id;int(11);NO;NULL;
Index PRIMARY;1;[user_id;group_id]
Table admin_perms
-Engine MyISAM
+Engine InnoDB
Column object_id;int(11);NO;NULL;
Column admin_id;int(11);NO;NULL;
Column perm_map;varchar(255);YES;NULL;
Index PRIMARY;1;[object_id;admin_id]
Table admin_users
-Engine MyISAM
+Engine InnoDB
Column base_id;int(11);NO;NULL;
Column logon;varchar(60);NO;NULL;
Column name;varchar(255);NO;NULL;
Index PRIMARY;1;[base_id]
Index logon;1;[logon]
Table article
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column parentid;int(11);NO;0;
Column displayOrder;int(11);NO;0;
Index article_level_index;0;[level;id]
Index article_parentId_index;0;[parentid]
Table article_files
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column articleId;int(11);NO;NULL;
Column displayName;varchar(255);NO;;
Column file_handler;varchar(20);NO;;
Index PRIMARY;1;[id]
Table bse_admin_ui_state
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column user_id;int(11);NO;NULL;
Column name;varchar(80);NO;NULL;
Column val;text;NO;NULL;
Index PRIMARY;1;[id]
Table bse_article_file_meta
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column file_id;int(11);NO;NULL;
Column name;varchar(20);NO;NULL;
Index PRIMARY;1;[id]
Index file_name;1;[file_id;name]
Table bse_article_groups
-Engine MyISAM
+Engine InnoDB
Column article_id;int(11);NO;NULL;
Column group_id;int(11);NO;NULL;
Index PRIMARY;1;[article_id;group_id]
Table bse_audit_log
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column when_at;datetime;NO;NULL;
Column facility;varchar(20);NO;bse;
Index ba_what;0;[facility;component;module;function]
Index ba_when;0;[when_at]
Table bse_background_tasks
-Engine MyISAM
+Engine InnoDB
Column id;varchar(20);NO;NULL;
Column description;varchar(80);NO;NULL;
Column modname;varchar(80);NO;;
Column last_completion;datetime;YES;NULL;
Column long_desc;text;YES;NULL;
Index PRIMARY;1;[id]
+Table bse_coupon_tiers
+Engine InnoDB
+Column id;int(11);NO;NULL;auto_increment
+Column coupon_id;int(11);NO;NULL;
+Column tier_id;int(11);NO;NULL;
+Index PRIMARY;1;[id]
+Index coupon_id;1;[coupon_id;tier_id]
+Index tier_id;0;[tier_id]
+Table bse_coupons
+Engine InnoDB
+Column id;int(11);NO;NULL;auto_increment
+Column code;varchar(40);NO;NULL;
+Column description;text;NO;NULL;
+Column release;date;NO;NULL;
+Column expiry;date;NO;NULL;
+Column discount_percent;double;NO;NULL;
+Column campaign;varchar(20);NO;NULL;
+Column last_modified;datetime;NO;NULL;
+Column untiered;int(11);NO;0;
+Index PRIMARY;1;[id]
+Index codes;1;[code]
Table bse_file_access_log
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column when_at;datetime;NO;NULL;
Column siteuser_id;int(11);NO;NULL;
Index by_user;0;[siteuser_id;when_at]
Index by_when_at;0;[when_at]
Table bse_file_notifies
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column owner_type;char(1);NO;NULL;
Column owner_id;int(11);NO;NULL;
Index by_owner;0;[owner_type;owner_id]
Index by_time;0;[owner_type;when_at]
Table bse_file_subscriptions
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;
Column siteuser_id;int(11);NO;NULL;
Column category;varchar(20);NO;NULL;
Index PRIMARY;1;[id]
Index ip_address;1;[ip_address;type]
Table bse_locations
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column description;varchar(255);NO;NULL;
Column room;varchar(40);NO;NULL;
Column disabled;int(11);NO;0;
Index PRIMARY;1;[id]
Table bse_msg_base
-Engine MyISAM
+Engine InnoDB
Column id;varchar(80);NO;NULL;
Column description;text;NO;NULL;
Column formatting;varchar(5);NO;none;
Column multiline;int(11);NO;0;
Index PRIMARY;1;[id]
Table bse_msg_defaults
-Engine MyISAM
+Engine InnoDB
Column id;varchar(80);NO;NULL;
Column language_code;varchar(10);NO;;
Column priority;int(11);NO;0;
Column message;text;NO;NULL;
Index PRIMARY;1;[id;language_code;priority]
Table bse_msg_managed
-Engine MyISAM
+Engine InnoDB
Column id;varchar(80);NO;NULL;
Column language_code;varchar(10);NO;;
Column message;text;NO;NULL;
Index PRIMARY;1;[id]
Index item_order;0;[order_item_id;display_order]
Table bse_owned_files
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column owner_type;char(1);NO;NULL;
Column owner_id;int(11);NO;NULL;
Index PRIMARY;1;[id]
Index by_owner_category;0;[owner_type;owner_id;category]
Table bse_price_tier_prices
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column tier_id;int(11);NO;NULL;
Column product_id;int(11);NO;NULL;
Index PRIMARY;1;[id]
Index tier_product;1;[tier_id;product_id]
Table bse_price_tiers
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column description;text;NO;NULL;
Column group_id;int(11);YES;NULL;
Index PRIMARY;1;[id]
Index only_one;1;[owner_id;owner_type;file_id]
Table bse_seminar_bookings
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column session_id;int(11);NO;NULL;
Column siteuser_id;int(11);NO;NULL;
Index session_id;1;[session_id;siteuser_id]
Index siteuser_id;0;[siteuser_id]
Table bse_seminar_sessions
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column seminar_id;int(11);NO;NULL;
Column location_id;int(11);NO;NULL;
Index seminar_id;1;[seminar_id;location_id;when_at]
Index seminar_id_2;0;[seminar_id]
Table bse_seminars
-Engine MyISAM
+Engine InnoDB
Column seminar_id;int(11);NO;NULL;
Column duration;int(11);NO;NULL;
Index PRIMARY;1;[seminar_id]
Table bse_siteuser_groups
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column name;varchar(80);NO;NULL;
Index PRIMARY;1;[id]
Table bse_siteuser_images
-Engine MyISAM
+Engine InnoDB
Column siteuser_id;int(11);NO;NULL;
Column image_id;varchar(20);NO;NULL;
Column filename;varchar(80);NO;NULL;
Column alt;varchar(255);NO;NULL;
Index PRIMARY;1;[siteuser_id;image_id]
Table bse_siteuser_membership
-Engine MyISAM
+Engine InnoDB
Column group_id;int(11);NO;NULL;
Column siteuser_id;int(11);NO;NULL;
Index PRIMARY;1;[group_id;siteuser_id]
Index siteuser_id;0;[siteuser_id]
Table bse_siteusers
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column idUUID;varchar(40);NO;NULL;
Column userId;varchar(40);NO;NULL;
Index idUUID;1;[idUUID]
Index userId;1;[userId]
Table bse_subscriptions
-Engine MyISAM
+Engine InnoDB
Column subscription_id;int(11);NO;NULL;auto_increment
Column text_id;varchar(20);NO;NULL;
Column title;varchar(255);NO;NULL;
Index PRIMARY;1;[subscription_id]
Index text_id;1;[text_id]
Table bse_tag_categories
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column cat;varchar(80);NO;NULL;
Column owner_type;char(2);NO;NULL;
Index PRIMARY;1;[id]
Index cat;1;[cat;owner_type]
Table bse_tag_category_deps
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column cat_id;int(11);NO;NULL;
Column depname;varchar(160);NO;NULL;
Index PRIMARY;1;[id]
Index cat_dep;1;[cat_id;depname]
Table bse_tag_members
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column owner_type;char(2);NO;NULL;
Column owner_id;int(11);NO;NULL;
Index art_tag;1;[owner_id;tag_id]
Index by_tag;0;[tag_id]
Table bse_tags
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column owner_type;char(2);NO;NULL;
Column cat;varchar(80);NO;NULL;
Index PRIMARY;1;[id]
Index cat_val;1;[owner_type;cat;val]
Table bse_user_subscribed
-Engine MyISAM
+Engine InnoDB
Column subscription_id;int(11);NO;NULL;
Column siteuser_id;int(11);NO;NULL;
Column started_at;date;NO;NULL;
Column max_lapsed;int(11);NO;NULL;
Index PRIMARY;1;[subscription_id;siteuser_id]
Table bse_wishlist
-Engine MyISAM
+Engine InnoDB
Column user_id;int(11);NO;NULL;
Column product_id;int(11);NO;NULL;
Column display_order;int(11);NO;NULL;
Index PRIMARY;1;[user_id;product_id]
Table email_blacklist
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column email;varchar(127);NO;NULL;
Column why;varchar(80);NO;NULL;
Index PRIMARY;1;[id]
Index email;1;[email]
Table email_requests
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column email;varchar(127);NO;NULL;
Column genEmail;varchar(127);NO;NULL;
Index email;1;[email]
Index genEmail;1;[genEmail]
Table image
-Engine MyISAM
+Engine InnoDB
Column id;mediumint(8) unsigned;NO;NULL;auto_increment
Column articleId;int(11);NO;NULL;
Column image;varchar(255);NO;;
Column ftype;varchar(20);NO;img;
Index PRIMARY;1;[id]
Table order_item
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column productId;int(11);NO;NULL;
Column orderId;int(11);NO;NULL;
Index PRIMARY;1;[id]
Index order_item_order;0;[orderId;id]
Table orders
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column delivFirstName;varchar(127);NO;;
Column delivLastName;varchar(127);NO;;
Column stage;varchar(20);NO;;
Column ccPAN;varchar(4);NO;;
Column paid_manually;int(11);NO;0;
+Column coupon_code;varchar(40);NO;;
+Column coupon_discount;double;NO;0;
Index PRIMARY;1;[id]
Index order_cchash;0;[ccNumberHash]
Index order_userId;0;[userId;orderDate]
Table other_parents
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column parentId;int(11);NO;NULL;
Column childId;int(11);NO;NULL;
Index childId;0;[childId;childDisplayOrder]
Index parentId;1;[parentId;childId]
Table product
-Engine MyISAM
+Engine InnoDB
Column articleId;int(11);NO;NULL;
Column summary;varchar(255);NO;NULL;
Column leadTime;int(11);NO;0;
Column height;int(11);NO;0;
Index PRIMARY;1;[articleId]
Table searchindex
-Engine MyISAM
+Engine InnoDB
Column id;varbinary(200);NO;;
Column articleIds;varchar(255);NO;;
Column sectionIds;varchar(255);NO;;
Column scores;varchar(255);NO;;
Index PRIMARY;1;[id]
Table sessions
-Engine MyISAM
+Engine InnoDB
Column id;char(32);NO;NULL;
Column a_session;blob;YES;NULL;
Column whenChanged;timestamp;NO;CURRENT_TIMESTAMP;on update CURRENT_TIMESTAMP
Index PRIMARY;1;[id]
Table sql_statements
-Engine MyISAM
+Engine InnoDB
Column name;varchar(80);NO;NULL;
Column sql_statement;text;NO;NULL;
Index PRIMARY;1;[name]
Table subscribed_users
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column subId;int(11);NO;NULL;
Column userId;int(11);NO;NULL;
Index PRIMARY;1;[id]
Index subId;1;[subId;userId]
Table subscription_types
-Engine MyISAM
+Engine InnoDB
Column id;int(11);NO;NULL;auto_increment
Column name;varchar(80);NO;NULL;
Column title;varchar(64);NO;NULL;
site/cgi-bin/modules/BSE/UI/AdminShop.pm multiple occurrence of link target 'script' 1
site/cgi-bin/modules/BSE/UI/Background.pm =item without previous =over 1
site/cgi-bin/modules/BSE/UI/Formmail.pm =item without previous =over 1
-site/cgi-bin/modules/BSE/UI/Shop.pm =item without previous =over 1
-site/cgi-bin/modules/BSE/UI/Shop.pm multiple occurrence of link target '%<format>' 1
-site/cgi-bin/modules/BSE/UI/Shop.pm multiple occurrence of link target 'gst' 1
-site/cgi-bin/modules/BSE/UI/Shop.pm multiple occurrence of link target 'id' 1
site/cgi-bin/modules/BSE/UI/Shop.pm multiple occurrence of link target 'item field' 1
-site/cgi-bin/modules/BSE/UI/Shop.pm multiple occurrence of link target 'm<number>' 1
-site/cgi-bin/modules/BSE/UI/Shop.pm multiple occurrence of link target 'options' 1
-site/cgi-bin/modules/BSE/UI/Shop.pm multiple occurrence of link target 'order field' 1
-site/cgi-bin/modules/BSE/UI/Shop.pm multiple occurrence of link target 'product field' 1
-site/cgi-bin/modules/BSE/UI/Shop.pm multiple occurrence of link target 'total' 1
site/cgi-bin/modules/BSE/Util/SQL.pm =over on line 37 without closing =back 1
site/cgi-bin/modules/BSE/Util/SQL.pm Verbatim paragraph in NAME section 1
site/cgi-bin/modules/BSE/Util/Secure.pm =over on line 25 without closing =back (at head1) 1