From 3d4361f8ae6eeff3b8e3bcb3fcd704a686299521 Mon Sep 17 00:00:00 2001
From: Kyle M Hall <kyle@bywatersolutions.com>
Date: Fri, 23 May 2014 11:54:26 -0400
Subject: [PATCH] Bug 12461 - Add patron clubs feature

This features would add the ability to create clubs which patrons may be
enrolled in. It would be particularly useful for tracking summer reading
programs, book clubs and other such clubs.

Test Plan:
1) Apply this patch
2) Run updatedatabase.pl
3) Ensure your staff user has the new 'Patron clubs' permissions
4) Under the tools menu, click the "Patron clubs" link
5) Create a new club template
   * Here you can add fields that can be filled out at the time
     a new club is created based on the template, or a new enrollment
     is created for a given club based on the template.
6) Create a new club based on that template
7) Attempt to enroll a patron in that club
8) Create a club with email required set
9) Attempt to enroll a patron without an email address in that club
10) Create a club that is enrollable from the OPAC
11) Attempt to enroll a patron in that club
12) Attempt to cancel a club enrollment from the OPAC
13) Attempt to cancel a club enrollment from the staff interface
---
 C4/Auth.pm                                         |    5 +-
 C4/Members.pm                                      |   19 +-
 Koha/Borrower.pm                                   |   64 +++++
 Koha/Branch.pm                                     |   52 ++++
 Koha/Branches.pm                                   |   58 +++++
 Koha/Club.pm                                       |   92 +++++++
 Koha/Club/Enrollment.pm                            |   80 ++++++
 Koha/Club/Enrollment/Field.pm                      |   56 +++++
 Koha/Club/Enrollment/Fields.pm                     |   60 +++++
 Koha/Club/Enrollments.pm                           |   60 +++++
 Koha/Club/Field.pm                                 |   66 +++++
 Koha/Club/Fields.pm                                |   60 +++++
 Koha/Club/Template.pm                              |   75 ++++++
 Koha/Club/Template/EnrollmentField.pm              |   54 ++++
 Koha/Club/Template/EnrollmentFields.pm             |   60 +++++
 Koha/Club/Template/Field.pm                        |   54 ++++
 Koha/Club/Template/Fields.pm                       |   60 +++++
 Koha/Club/Templates.pm                             |   60 +++++
 Koha/Clubs.pm                                      |   94 +++++++
 Koha/Schema/Result/AuthorisedValue.pm              |    4 +-
 Koha/Schema/Result/Borrower.pm                     |   21 ++-
 Koha/Schema/Result/Branch.pm                       |   49 ++++-
 Koha/Schema/Result/Club.pm                         |  197 +++++++++++++++
 Koha/Schema/Result/ClubEnrollment.pm               |  201 +++++++++++++++
 Koha/Schema/Result/ClubEnrollmentField.pm          |  112 +++++++++
 Koha/Schema/Result/ClubField.pm                    |  112 +++++++++
 Koha/Schema/Result/ClubTemplate.pm                 |  195 +++++++++++++++
 Koha/Schema/Result/ClubTemplateEnrollmentField.pm  |  119 +++++++++
 Koha/Schema/Result/ClubTemplateField.pm            |  119 +++++++++
 Koha/Template/Plugin/AuthorisedValues.pm           |    6 +
 Koha/Template/Plugin/Borrowers.pm                  |    4 +
 Koha/Template/Plugin/Branches.pm                   |    7 +
 Koha/Template/Plugin/Koha.pm                       |    5 +
 circ/circulation.pl                                |    4 +
 clubs/clubs-add-modify.pl                          |  104 ++++++++
 clubs/clubs.pl                                     |   50 ++++
 clubs/patron-clubs-tab.pl                          |   55 +++++
 clubs/patron-enroll.pl                             |   50 ++++
 clubs/templates-add-modify.pl                      |  150 ++++++++++++
 installer/data/mysql/en/mandatory/userflags.sql    |    3 +-
 .../data/mysql/en/mandatory/userpermissions.sql    |    5 +-
 installer/data/mysql/kohastructure.sql             |  178 ++++++++++++++
 installer/data/mysql/updatedatabase.pl             |  154 ++++++++++++
 .../prog/en/modules/circ/circulation.tt            |   23 ++
 .../intranet-tmpl/prog/en/modules/circ/renew.tt    |    4 +-
 .../prog/en/modules/clubs/clubs-add-modify.tt      |  142 +++++++++++
 .../intranet-tmpl/prog/en/modules/clubs/clubs.tt   |  206 ++++++++++++++++
 .../prog/en/modules/clubs/patron-clubs-tab.tt      |  102 ++++++++
 .../prog/en/modules/clubs/patron-enroll.tt         |   66 +++++
 .../prog/en/modules/clubs/templates-add-modify.tt  |  257 ++++++++++++++++++++
 .../prog/en/modules/members/moremember.tt          |   22 ++
 .../prog/en/modules/tools/tools-home.tt            |    5 +
 .../bootstrap/en/modules/clubs/clubs-tab.tt        |  102 ++++++++
 .../opac-tmpl/bootstrap/en/modules/clubs/enroll.tt |   66 +++++
 .../opac-tmpl/bootstrap/en/modules/opac-user.tt    |   21 ++
 members/moremember.pl                              |    2 +
 opac/clubs/clubs-tab.pl                            |   52 ++++
 opac/clubs/enroll.pl                               |   49 ++++
 opac/opac-user.pl                                  |    5 +-
 opac/svc/club/cancel_enrollment                    |   47 ++++
 opac/svc/club/enroll                               |   77 ++++++
 svc/club/cancel_enrollment                         |   46 ++++
 svc/club/delete                                    |   48 ++++
 svc/club/enroll                                    |   74 ++++++
 svc/club/template/delete                           |   49 ++++
 65 files changed, 4468 insertions(+), 30 deletions(-)
 create mode 100644 Koha/Branch.pm
 create mode 100644 Koha/Branches.pm
 create mode 100644 Koha/Club.pm
 create mode 100644 Koha/Club/Enrollment.pm
 create mode 100644 Koha/Club/Enrollment/Field.pm
 create mode 100644 Koha/Club/Enrollment/Fields.pm
 create mode 100644 Koha/Club/Enrollments.pm
 create mode 100644 Koha/Club/Field.pm
 create mode 100644 Koha/Club/Fields.pm
 create mode 100644 Koha/Club/Template.pm
 create mode 100644 Koha/Club/Template/EnrollmentField.pm
 create mode 100644 Koha/Club/Template/EnrollmentFields.pm
 create mode 100644 Koha/Club/Template/Field.pm
 create mode 100644 Koha/Club/Template/Fields.pm
 create mode 100644 Koha/Club/Templates.pm
 create mode 100644 Koha/Clubs.pm
 create mode 100644 Koha/Schema/Result/Club.pm
 create mode 100644 Koha/Schema/Result/ClubEnrollment.pm
 create mode 100644 Koha/Schema/Result/ClubEnrollmentField.pm
 create mode 100644 Koha/Schema/Result/ClubField.pm
 create mode 100644 Koha/Schema/Result/ClubTemplate.pm
 create mode 100644 Koha/Schema/Result/ClubTemplateEnrollmentField.pm
 create mode 100644 Koha/Schema/Result/ClubTemplateField.pm
 create mode 100755 clubs/clubs-add-modify.pl
 create mode 100755 clubs/clubs.pl
 create mode 100755 clubs/patron-clubs-tab.pl
 create mode 100755 clubs/patron-enroll.pl
 create mode 100755 clubs/templates-add-modify.pl
 create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/clubs/clubs-add-modify.tt
 create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/clubs/clubs.tt
 create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/clubs/patron-clubs-tab.tt
 create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/clubs/patron-enroll.tt
 create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/clubs/templates-add-modify.tt
 create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/modules/clubs/clubs-tab.tt
 create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/modules/clubs/enroll.tt
 create mode 100755 opac/clubs/clubs-tab.pl
 create mode 100755 opac/clubs/enroll.pl
 create mode 100755 opac/svc/club/cancel_enrollment
 create mode 100755 opac/svc/club/enroll
 create mode 100755 svc/club/cancel_enrollment
 create mode 100755 svc/club/delete
 create mode 100755 svc/club/enroll
 create mode 100755 svc/club/template/delete

diff --git a/C4/Auth.pm b/C4/Auth.pm
index 9a5d6b4..cbaa12b 100644
--- a/C4/Auth.pm
+++ b/C4/Auth.pm
@@ -218,7 +218,7 @@ sub get_template_and_user {
         my $all_perms = get_all_subpermissions();
 
         my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
-          editcatalogue updatecharges management tools editauthorities serials reports acquisition);
+          editcatalogue updatecharges management tools editauthorities serials reports acquisition clubs);
 
         # We are going to use the $flags returned by checkauth
         # to create the template's parameters that will indicate
@@ -242,8 +242,9 @@ sub get_template_and_user {
             $template->param( CAN_user_staffaccess      => 1 );
             $template->param( CAN_user_plugins          => 1 );
             $template->param( CAN_user_coursereserves   => 1 );
-            foreach my $module ( keys %$all_perms ) {
+            $template->param( CAN_user_clubs            => 1 );
 
+            foreach my $module ( keys %$all_perms ) {
                 foreach my $subperm ( keys %{ $all_perms->{$module} } ) {
                     $template->param( "CAN_user_${module}_${subperm}" => 1 );
                 }
diff --git a/C4/Members.pm b/C4/Members.pm
index 3a99446..fe60e6d 100644
--- a/C4/Members.pm
+++ b/C4/Members.pm
@@ -42,6 +42,7 @@ use Koha::Borrower::Debarments qw(IsDebarred);
 use Text::Unaccent qw( unac_string );
 use Koha::AuthUtils qw(hash_password);
 use Koha::Database;
+use Koha::Borrowers;
 use Module::Load;
 if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
     load Koha::NorwegianPatronDB, qw( NLUpdateHashedPIN NLEncryptPIN NLSync );
@@ -1528,20 +1529,10 @@ addresses.
 
 sub GetFirstValidEmailAddress {
     my $borrowernumber = shift;
-    my $dbh = C4::Context->dbh;
-    my $sth = $dbh->prepare( "SELECT email, emailpro, B_email FROM borrowers where borrowernumber = ? ");
-    $sth->execute( $borrowernumber );
-    my $data = $sth->fetchrow_hashref;
 
-    if ($data->{'email'}) {
-       return $data->{'email'};
-    } elsif ($data->{'emailpro'}) {
-       return $data->{'emailpro'};
-    } elsif ($data->{'B_email'}) {
-       return $data->{'B_email'};
-    } else {
-       return '';
-    }
+    my $borrower = Koha::Borrowers->find( $borrowernumber );
+
+    return $borrower->FirstValidEmailAddress();
 }
 
 =head2 GetNoticeEmailAddress
diff --git a/Koha/Borrower.pm b/Koha/Borrower.pm
index 93426bf..c0c3009 100644
--- a/Koha/Borrower.pm
+++ b/Koha/Borrower.pm
@@ -23,6 +23,9 @@ use Carp;
 
 use Koha::Database;
 
+use Koha::Clubs;
+use Koha::Club::Enrollments;
+
 use base qw(Koha::Object);
 
 =head1 NAME
@@ -35,6 +38,67 @@ Koha::Borrower - Koha Borrower Object class
 
 =cut
 
+=head3 FirstValidEmailAddress
+
+=cut
+
+sub FirstValidEmailAddress {
+    my ($self) = @_;
+
+    return $self->email() || $self->emailpro() || $self->b_email() || q{};
+}
+
+=head3 GetClubEnrollments
+
+=cut
+
+sub GetClubEnrollments {
+    my ($self) = @_;
+
+    return Koha::Club::Enrollments->search( { borrowernumber => $self->borrowernumber(), date_canceled => undef } );
+}
+
+=head3 GetClubEnrollmentsCount
+
+=cut
+
+sub GetClubEnrollmentsCount {
+    my ($self) = @_;
+
+    my $e = $self->GetClubEnrollments();
+
+    return $e->count();
+}
+
+=head3 GetEnrollableClubs
+
+=cut
+
+sub GetEnrollableClubs {
+    my ( $self, $is_enrollable_from_opac ) = @_;
+
+    my $params;
+    $params->{is_enrollable_from_opac} = $is_enrollable_from_opac
+      if $is_enrollable_from_opac;
+    $params->{is_email_required} = 0 unless $self->FirstValidEmailAddress();
+
+    $params->{borrower} = $self;
+
+    return Koha::Clubs->GetEnrollable($params);
+}
+
+=head3 GetEnrollableClubsCount
+
+=cut
+
+sub GetEnrollableClubsCount {
+    my ( $self, $is_enrollable_from_opac ) = @_;
+
+    my $e = $self->GetEnrollableClubs($is_enrollable_from_opac);
+
+    return $e->count();
+}
+
 =head3 type
 
 =cut
diff --git a/Koha/Branch.pm b/Koha/Branch.pm
new file mode 100644
index 0000000..01f41f5e
--- /dev/null
+++ b/Koha/Branch.pm
@@ -0,0 +1,52 @@
+package Koha::Branch;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use base qw(Koha::Object);
+
+=head1 NAME
+
+Koha::Branch - Koha Branch Object class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'Branch';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Branches.pm b/Koha/Branches.pm
new file mode 100644
index 0000000..cacf85f
--- /dev/null
+++ b/Koha/Branches.pm
@@ -0,0 +1,58 @@
+package Koha::Branches;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use Koha::Branch;
+
+use base qw(Koha::Objects);
+
+=head1 NAME
+
+Koha::Branches
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'Branch';
+}
+
+sub object_class {
+    return 'Koha::Branch';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Club.pm b/Koha/Club.pm
new file mode 100644
index 0000000..61651a1
--- /dev/null
+++ b/Koha/Club.pm
@@ -0,0 +1,92 @@
+package Koha::Club;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use Koha::Club::Templates;
+use Koha::Club::Fields;
+use Koha::Branches;
+
+use base qw(Koha::Object);
+
+=head1 NAME
+
+Koha::Club - Koha Club Object class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 club_template
+
+=cut
+
+sub club_template {
+    my ($self) = @_;
+
+    return unless $self->club_template_id();
+
+    return Koha::Club::Templates->find( $self->club_template_id() );
+}
+
+=head3 club_fields
+
+=cut
+
+sub club_fields {
+    my ($self) = @_;
+
+    return unless $self->id();
+
+    return Koha::Club::Fields->search( { club_id => $self->id() } );
+}
+
+=head3 club_fields
+
+=cut
+
+sub branch {
+    my ($self) = @_;
+
+    return unless $self->branchcode();
+
+    return Koha::Branches->find( $self->branchcode() );
+}
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'Club';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Club/Enrollment.pm b/Koha/Club/Enrollment.pm
new file mode 100644
index 0000000..5729252
--- /dev/null
+++ b/Koha/Club/Enrollment.pm
@@ -0,0 +1,80 @@
+package Koha::Club::Enrollment;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+use Koha::Clubs;
+
+use base qw(Koha::Object);
+
+=head1 NAME
+
+Koha::Club::Enrollment
+
+Represents a "pattern" on which many clubs can be created.
+In this way we can directly compare different clubs of the same 'template'
+for statistical purposes.
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 cancel
+
+=cut
+
+sub cancel {
+    my ( $self ) = @_;
+
+    $self->_result()->update( { date_canceled => \'NOW()' } );
+
+    return $self;
+}
+
+=cut
+
+=head3 club
+
+=cut
+
+sub club {
+    my ( $self ) = @_;
+    return Koha::Clubs->find( $self->club_id() );
+}
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'ClubEnrollment';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Club/Enrollment/Field.pm b/Koha/Club/Enrollment/Field.pm
new file mode 100644
index 0000000..9d8580f
--- /dev/null
+++ b/Koha/Club/Enrollment/Field.pm
@@ -0,0 +1,56 @@
+package Koha::Club::Enrollment::Field;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use base qw(Koha::Object);
+
+=head1 NAME
+
+Koha::Club::Enrollment::Field
+
+Represents a "pattern" on which many clubs can be created.
+In this way we can directly compare different clubs of the same 'template'
+for statistical purposes.
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'ClubEnrollmentField';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Club/Enrollment/Fields.pm b/Koha/Club/Enrollment/Fields.pm
new file mode 100644
index 0000000..a96b4b5
--- /dev/null
+++ b/Koha/Club/Enrollment/Fields.pm
@@ -0,0 +1,60 @@
+package Koha::Club::Enrollment::Fields;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use Koha::Club::Enrollment::Field;
+
+use base qw(Koha::Objects);
+
+=head1 NAME
+
+Koha::Club::Enrollment::Fields
+
+This object represents a collection of club enrollemnt fields.
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'ClubEnrollmentField';
+}
+
+sub object_class {
+    return 'Koha::Club::Enrollment::Field';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Club/Enrollments.pm b/Koha/Club/Enrollments.pm
new file mode 100644
index 0000000..c0c7a6a
--- /dev/null
+++ b/Koha/Club/Enrollments.pm
@@ -0,0 +1,60 @@
+package Koha::Club::Enrollments;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use Koha::Club::Enrollment;
+
+use base qw(Koha::Objects);
+
+=head1 NAME
+
+Koha::Club::Enrollments
+
+This object represents a collection of club templates.
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'ClubEnrollment';
+}
+
+sub object_class {
+    return 'Koha::Club::Enrollment';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Club/Field.pm b/Koha/Club/Field.pm
new file mode 100644
index 0000000..30b5764
--- /dev/null
+++ b/Koha/Club/Field.pm
@@ -0,0 +1,66 @@
+package Koha::Club::Field;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use Koha::Club::Template::Fields;
+
+use base qw(Koha::Object);
+
+=head1 NAME
+
+Koha::Club::Field
+
+Represents the value set at creation time for a Koha::Club::Template::Field
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 club_template_field
+
+=cut
+
+sub club_template_field {
+    my ( $self ) = @_;
+
+    return Koha::Club::Template::Fields->find( $self->club_template_field_id );
+}
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'ClubField';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Club/Fields.pm b/Koha/Club/Fields.pm
new file mode 100644
index 0000000..31513fd
--- /dev/null
+++ b/Koha/Club/Fields.pm
@@ -0,0 +1,60 @@
+package Koha::Club::Fields;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use Koha::Club::Field;
+
+use base qw(Koha::Objects);
+
+=head1 NAME
+
+Koha::Club::Fields
+
+Represents a collection of club fields.
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'ClubField';
+}
+
+sub object_class {
+    return 'Koha::Club::Field';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Club/Template.pm b/Koha/Club/Template.pm
new file mode 100644
index 0000000..0addf23
--- /dev/null
+++ b/Koha/Club/Template.pm
@@ -0,0 +1,75 @@
+package Koha::Club::Template;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use Koha::Club::Template::Fields;
+use Koha::Club::Template::EnrollmentFields;
+
+use base qw(Koha::Object);
+
+=head1 NAME
+
+Koha::Club::Template
+
+Represents a "pattern" on which many clubs can be created.
+In this way we can directly compare different clubs of the same 'template'
+for statistical purposes.
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 club_template_fields
+
+=cut
+
+sub club_template_fields {
+    my ($self) = @_;
+
+    return Koha::Club::Template::Fields->search( { club_template_id => $self->id() } );
+}
+
+sub club_template_enrollment_fields {
+    my ($self) = @_;
+
+    return Koha::Club::Template::EnrollmentFields->search( { club_template_id => $self->id() } );
+}
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'ClubTemplate';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Club/Template/EnrollmentField.pm b/Koha/Club/Template/EnrollmentField.pm
new file mode 100644
index 0000000..aabcdc4
--- /dev/null
+++ b/Koha/Club/Template/EnrollmentField.pm
@@ -0,0 +1,54 @@
+package Koha::Club::Template::EnrollmentField;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use base qw(Koha::Object);
+
+=head1 NAME
+
+Koha::Club::Template::EnrollemntField
+
+Represents a club field that is only set at the time a patron is enrolled
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'ClubTemplateEnrollmentField';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Club/Template/EnrollmentFields.pm b/Koha/Club/Template/EnrollmentFields.pm
new file mode 100644
index 0000000..1642121
--- /dev/null
+++ b/Koha/Club/Template/EnrollmentFields.pm
@@ -0,0 +1,60 @@
+package Koha::Club::Template::EnrollmentFields;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use Koha::Club::Template::EnrollmentField;
+
+use base qw(Koha::Objects);
+
+=head1 NAME
+
+Koha::Club::Template::EnrollemntFields
+
+Represents a colleciton of club fields that are only set at the time a patron is enrolled
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'ClubTemplateEnrollmentField';
+}
+
+sub object_class {
+    return 'Koha::Club::Template::EnrollmentField';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Club/Template/Field.pm b/Koha/Club/Template/Field.pm
new file mode 100644
index 0000000..4d7ed23
--- /dev/null
+++ b/Koha/Club/Template/Field.pm
@@ -0,0 +1,54 @@
+package Koha::Club::Template::Field;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use base qw(Koha::Object);
+
+=head1 NAME
+
+Koha::Club::Template::Field
+
+Represents a club field that is set when the club is created
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'ClubTemplateField';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Club/Template/Fields.pm b/Koha/Club/Template/Fields.pm
new file mode 100644
index 0000000..d02c831
--- /dev/null
+++ b/Koha/Club/Template/Fields.pm
@@ -0,0 +1,60 @@
+package Koha::Club::Template::Fields;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use Koha::Club::Template::Field;
+
+use base qw(Koha::Objects);
+
+=head1 NAME
+
+Koha::Club::Template::Fields
+
+Represents a collection of club fields that are set when the club is created
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'ClubTemplateField';
+}
+
+sub object_class {
+    return 'Koha::Club::Template::Field';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Club/Templates.pm b/Koha/Club/Templates.pm
new file mode 100644
index 0000000..cbce680
--- /dev/null
+++ b/Koha/Club/Templates.pm
@@ -0,0 +1,60 @@
+package Koha::Club::Templates;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use Koha::Club::Template;
+
+use base qw(Koha::Objects);
+
+=head1 NAME
+
+Koha::Club::Templates
+
+This object represents a collection of club templates.
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'ClubTemplate';
+}
+
+sub object_class {
+    return 'Koha::Club::Template';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Clubs.pm b/Koha/Clubs.pm
new file mode 100644
index 0000000..0106944
--- /dev/null
+++ b/Koha/Clubs.pm
@@ -0,0 +1,94 @@
+package Koha::Clubs;
+
+# Copyright ByWater Solutions 2014
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use Koha::Club;
+
+use base qw(Koha::Objects);
+
+=head1 NAME
+
+Koha::Clubs - Koha Clubs Object class
+
+This object represents a collection of clubs a patron may enroll in.
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 GetEnrollable
+
+=cut
+
+sub GetEnrollable {
+    my ( $self, $params ) = @_;
+
+    # We need to filter out all the already enrolled in clubs
+    my $borrower = $params->{borrower};
+    if ($borrower) {
+        delete( $params->{borrower} );
+        my @enrollments = $borrower->GetClubEnrollments();
+        if (@enrollments) {
+            $params->{'me.id'} = { -not_in => [ map { $_->club()->id() } @enrollments ] };
+        }
+    }
+
+    my $rs = $self->_resultset()->search( $params, { prefetch => 'club_template' } );
+
+    if (wantarray) {
+        my $class = ref($self) ? ref($self) : $self;
+
+        return $class->_wrap( $rs->all() );
+
+    }
+    else {
+        my $class = ref($self) ? ref($self) : $self;
+
+        return $class->_new_from_dbic($rs);
+    }
+}
+
+=cut
+
+=head3 type
+
+=cut
+
+sub type {
+    return 'Club';
+}
+
+sub object_class {
+    return 'Koha::Club';
+}
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
+
+1;
diff --git a/Koha/Schema/Result/AuthorisedValue.pm b/Koha/Schema/Result/AuthorisedValue.pm
index 4818378..b2837df 100644
--- a/Koha/Schema/Result/AuthorisedValue.pm
+++ b/Koha/Schema/Result/AuthorisedValue.pm
@@ -123,8 +123,8 @@ __PACKAGE__->has_many(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.07039 @ 2015-02-05 15:20:11
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:GS7UBpk66HAhBptwrpKR7Q
+# Created by DBIx::Class::Schema::Loader v0.07040 @ 2015-01-12 09:54:50
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:IzI/WpMbwvfZ8BpHtRCsiQ
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/Koha/Schema/Result/Borrower.pm b/Koha/Schema/Result/Borrower.pm
index e9f8838..7c73244 100644
--- a/Koha/Schema/Result/Borrower.pm
+++ b/Koha/Schema/Result/Borrower.pm
@@ -798,6 +798,21 @@ __PACKAGE__->belongs_to(
   { is_deferrable => 1, on_delete => "RESTRICT", on_update => "RESTRICT" },
 );
 
+=head2 club_enrollments
+
+Type: has_many
+
+Related object: L<Koha::Schema::Result::ClubEnrollment>
+
+=cut
+
+__PACKAGE__->has_many(
+  "club_enrollments",
+  "Koha::Schema::Result::ClubEnrollment",
+  { "foreign.borrowernumber" => "self.borrowernumber" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
 =head2 course_instructors
 
 Type: has_many
@@ -1154,9 +1169,9 @@ Composing rels: L</aqorder_users> -> ordernumber
 __PACKAGE__->many_to_many("ordernumbers", "aqorder_users", "ordernumber");
 
 
-# Created by DBIx::Class::Schema::Loader v0.07039 @ 2015-04-27 16:08:40
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Z50zYBD3Hqlv5/EnoLnyZw
+# Created by DBIx::Class::Schema::Loader v0.07040 @ 2015-01-12 09:56:17
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Wy9KSEiYp/SxYsw+xW0x1g
 
 
-# You can replace this text with custom content, and it will be preserved on regeneration
+# You can replace this text with custom code or comments, and it will be preserved on regeneration
 1;
diff --git a/Koha/Schema/Result/Branch.pm b/Koha/Schema/Result/Branch.pm
index 106123a..5f64962 100644
--- a/Koha/Schema/Result/Branch.pm
+++ b/Koha/Schema/Result/Branch.pm
@@ -337,6 +337,51 @@ __PACKAGE__->has_many(
   { cascade_copy => 0, cascade_delete => 0 },
 );
 
+=head2 club_enrollments
+
+Type: has_many
+
+Related object: L<Koha::Schema::Result::ClubEnrollment>
+
+=cut
+
+__PACKAGE__->has_many(
+  "club_enrollments",
+  "Koha::Schema::Result::ClubEnrollment",
+  { "foreign.branchcode" => "self.branchcode" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
+=head2 club_templates
+
+Type: has_many
+
+Related object: L<Koha::Schema::Result::ClubTemplate>
+
+=cut
+
+__PACKAGE__->has_many(
+  "club_templates",
+  "Koha::Schema::Result::ClubTemplate",
+  { "foreign.branchcode" => "self.branchcode" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
+=head2 clubs
+
+Type: has_many
+
+Related object: L<Koha::Schema::Result::Club>
+
+=cut
+
+__PACKAGE__->has_many(
+  "clubs",
+  "Koha::Schema::Result::Club",
+  { "foreign.branchcode" => "self.branchcode" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
 =head2 collections
 
 Type: has_many
@@ -513,8 +558,8 @@ Composing rels: L</branchrelations> -> categorycode
 __PACKAGE__->many_to_many("categorycodes", "branchrelations", "categorycode");
 
 
-# Created by DBIx::Class::Schema::Loader v0.07039 @ 2014-11-06 15:26:36
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:CGNPB/MkGLOihDThj43/4A
+# Created by DBIx::Class::Schema::Loader v0.07040 @ 2015-01-12 09:56:17
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:lvjA8x8u5RKeZQ0+uiLm7A
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/Koha/Schema/Result/Club.pm b/Koha/Schema/Result/Club.pm
new file mode 100644
index 0000000..b7af227
--- /dev/null
+++ b/Koha/Schema/Result/Club.pm
@@ -0,0 +1,197 @@
+use utf8;
+package Koha::Schema::Result::Club;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+=head1 NAME
+
+Koha::Schema::Result::Club
+
+=cut
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+=head1 TABLE: C<clubs>
+
+=cut
+
+__PACKAGE__->table("clubs");
+
+=head1 ACCESSORS
+
+=head2 id
+
+  data_type: 'integer'
+  is_auto_increment: 1
+  is_nullable: 0
+
+=head2 club_template_id
+
+  data_type: 'integer'
+  is_foreign_key: 1
+  is_nullable: 0
+
+=head2 name
+
+  data_type: 'tinytext'
+  is_nullable: 0
+
+=head2 description
+
+  data_type: 'text'
+  is_nullable: 1
+
+=head2 date_start
+
+  data_type: 'date'
+  datetime_undef_if_invalid: 1
+  is_nullable: 1
+
+=head2 date_end
+
+  data_type: 'date'
+  datetime_undef_if_invalid: 1
+  is_nullable: 1
+
+=head2 branchcode
+
+  data_type: 'varchar'
+  is_foreign_key: 1
+  is_nullable: 1
+  size: 11
+
+=head2 date_created
+
+  data_type: 'timestamp'
+  datetime_undef_if_invalid: 1
+  default_value: current_timestamp
+  is_nullable: 0
+
+=head2 date_updated
+
+  data_type: 'timestamp'
+  datetime_undef_if_invalid: 1
+  is_nullable: 1
+
+=cut
+
+__PACKAGE__->add_columns(
+  "id",
+  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
+  "club_template_id",
+  { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+  "name",
+  { data_type => "tinytext", is_nullable => 0 },
+  "description",
+  { data_type => "text", is_nullable => 1 },
+  "date_start",
+  { data_type => "date", datetime_undef_if_invalid => 1, is_nullable => 1 },
+  "date_end",
+  { data_type => "date", datetime_undef_if_invalid => 1, is_nullable => 1 },
+  "branchcode",
+  { data_type => "varchar", is_foreign_key => 1, is_nullable => 1, size => 11 },
+  "date_created",
+  {
+    data_type => "timestamp",
+    datetime_undef_if_invalid => 1,
+    default_value => \"current_timestamp",
+    is_nullable => 0,
+  },
+  "date_updated",
+  {
+    data_type => "timestamp",
+    datetime_undef_if_invalid => 1,
+    is_nullable => 1,
+  },
+);
+
+=head1 PRIMARY KEY
+
+=over 4
+
+=item * L</id>
+
+=back
+
+=cut
+
+__PACKAGE__->set_primary_key("id");
+
+=head1 RELATIONS
+
+=head2 branchcode
+
+Type: belongs_to
+
+Related object: L<Koha::Schema::Result::Branch>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "branchcode",
+  "Koha::Schema::Result::Branch",
+  { branchcode => "branchcode" },
+  {
+    is_deferrable => 1,
+    join_type     => "LEFT",
+    on_delete     => "RESTRICT",
+    on_update     => "RESTRICT",
+  },
+);
+
+=head2 club_enrollments
+
+Type: has_many
+
+Related object: L<Koha::Schema::Result::ClubEnrollment>
+
+=cut
+
+__PACKAGE__->has_many(
+  "club_enrollments",
+  "Koha::Schema::Result::ClubEnrollment",
+  { "foreign.club_id" => "self.id" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
+=head2 club_fields
+
+Type: has_many
+
+Related object: L<Koha::Schema::Result::ClubField>
+
+=cut
+
+__PACKAGE__->has_many(
+  "club_fields",
+  "Koha::Schema::Result::ClubField",
+  { "foreign.club_id" => "self.id" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
+=head2 club_template
+
+Type: belongs_to
+
+Related object: L<Koha::Schema::Result::ClubTemplate>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "club_template",
+  "Koha::Schema::Result::ClubTemplate",
+  { id => "club_template_id" },
+  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07040 @ 2015-01-12 09:56:17
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:nMwJy/qR8aWu12hQq4rORQ
+
+
+# You can replace this text with custom content, and it will be preserved on regeneration
+1;
diff --git a/Koha/Schema/Result/ClubEnrollment.pm b/Koha/Schema/Result/ClubEnrollment.pm
new file mode 100644
index 0000000..177527e
--- /dev/null
+++ b/Koha/Schema/Result/ClubEnrollment.pm
@@ -0,0 +1,201 @@
+use utf8;
+package Koha::Schema::Result::ClubEnrollment;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+=head1 NAME
+
+Koha::Schema::Result::ClubEnrollment
+
+=cut
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+=head1 TABLE: C<club_enrollments>
+
+=cut
+
+__PACKAGE__->table("club_enrollments");
+
+=head1 ACCESSORS
+
+=head2 id
+
+  data_type: 'integer'
+  is_auto_increment: 1
+  is_nullable: 0
+
+=head2 club_id
+
+  data_type: 'integer'
+  is_foreign_key: 1
+  is_nullable: 0
+
+=head2 borrowernumber
+
+  data_type: 'integer'
+  is_foreign_key: 1
+  is_nullable: 0
+
+=head2 date_enrolled
+
+  data_type: 'timestamp'
+  datetime_undef_if_invalid: 1
+  default_value: current_timestamp
+  is_nullable: 0
+
+=head2 date_canceled
+
+  data_type: 'timestamp'
+  datetime_undef_if_invalid: 1
+  is_nullable: 1
+
+=head2 date_created
+
+  data_type: 'timestamp'
+  datetime_undef_if_invalid: 1
+  default_value: '0000-00-00 00:00:00'
+  is_nullable: 0
+
+=head2 date_updated
+
+  data_type: 'timestamp'
+  datetime_undef_if_invalid: 1
+  is_nullable: 1
+
+=head2 branchcode
+
+  data_type: 'varchar'
+  is_foreign_key: 1
+  is_nullable: 1
+  size: 11
+
+=cut
+
+__PACKAGE__->add_columns(
+  "id",
+  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
+  "club_id",
+  { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+  "borrowernumber",
+  { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+  "date_enrolled",
+  {
+    data_type => "timestamp",
+    datetime_undef_if_invalid => 1,
+    default_value => \"current_timestamp",
+    is_nullable => 0,
+  },
+  "date_canceled",
+  {
+    data_type => "timestamp",
+    datetime_undef_if_invalid => 1,
+    is_nullable => 1,
+  },
+  "date_created",
+  {
+    data_type => "timestamp",
+    datetime_undef_if_invalid => 1,
+    default_value => "0000-00-00 00:00:00",
+    is_nullable => 0,
+  },
+  "date_updated",
+  {
+    data_type => "timestamp",
+    datetime_undef_if_invalid => 1,
+    is_nullable => 1,
+  },
+  "branchcode",
+  { data_type => "varchar", is_foreign_key => 1, is_nullable => 1, size => 11 },
+);
+
+=head1 PRIMARY KEY
+
+=over 4
+
+=item * L</id>
+
+=back
+
+=cut
+
+__PACKAGE__->set_primary_key("id");
+
+=head1 RELATIONS
+
+=head2 borrowernumber
+
+Type: belongs_to
+
+Related object: L<Koha::Schema::Result::Borrower>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "borrowernumber",
+  "Koha::Schema::Result::Borrower",
+  { borrowernumber => "borrowernumber" },
+  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
+);
+
+=head2 branchcode
+
+Type: belongs_to
+
+Related object: L<Koha::Schema::Result::Branch>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "branchcode",
+  "Koha::Schema::Result::Branch",
+  { branchcode => "branchcode" },
+  {
+    is_deferrable => 1,
+    join_type     => "LEFT",
+    on_delete     => "SET NULL",
+    on_update     => "CASCADE",
+  },
+);
+
+=head2 club
+
+Type: belongs_to
+
+Related object: L<Koha::Schema::Result::Club>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "club",
+  "Koha::Schema::Result::Club",
+  { id => "club_id" },
+  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
+);
+
+=head2 club_enrollment_fields
+
+Type: has_many
+
+Related object: L<Koha::Schema::Result::ClubEnrollmentField>
+
+=cut
+
+__PACKAGE__->has_many(
+  "club_enrollment_fields",
+  "Koha::Schema::Result::ClubEnrollmentField",
+  { "foreign.club_enrollment_id" => "self.id" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07040 @ 2015-01-12 09:56:17
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9ypc+smG/VlgtWW66PhvHQ
+
+
+# You can replace this text with custom content, and it will be preserved on regeneration
+1;
diff --git a/Koha/Schema/Result/ClubEnrollmentField.pm b/Koha/Schema/Result/ClubEnrollmentField.pm
new file mode 100644
index 0000000..6ba0fde
--- /dev/null
+++ b/Koha/Schema/Result/ClubEnrollmentField.pm
@@ -0,0 +1,112 @@
+use utf8;
+package Koha::Schema::Result::ClubEnrollmentField;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+=head1 NAME
+
+Koha::Schema::Result::ClubEnrollmentField
+
+=cut
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+=head1 TABLE: C<club_enrollment_fields>
+
+=cut
+
+__PACKAGE__->table("club_enrollment_fields");
+
+=head1 ACCESSORS
+
+=head2 id
+
+  data_type: 'integer'
+  is_auto_increment: 1
+  is_nullable: 0
+
+=head2 club_enrollment_id
+
+  data_type: 'integer'
+  is_foreign_key: 1
+  is_nullable: 0
+
+=head2 club_template_enrollment_field_id
+
+  data_type: 'integer'
+  is_foreign_key: 1
+  is_nullable: 0
+
+=head2 value
+
+  data_type: 'text'
+  is_nullable: 0
+
+=cut
+
+__PACKAGE__->add_columns(
+  "id",
+  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
+  "club_enrollment_id",
+  { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+  "club_template_enrollment_field_id",
+  { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+  "value",
+  { data_type => "text", is_nullable => 0 },
+);
+
+=head1 PRIMARY KEY
+
+=over 4
+
+=item * L</id>
+
+=back
+
+=cut
+
+__PACKAGE__->set_primary_key("id");
+
+=head1 RELATIONS
+
+=head2 club_enrollment
+
+Type: belongs_to
+
+Related object: L<Koha::Schema::Result::ClubEnrollment>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "club_enrollment",
+  "Koha::Schema::Result::ClubEnrollment",
+  { id => "club_enrollment_id" },
+  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
+);
+
+=head2 club_template_enrollment_field
+
+Type: belongs_to
+
+Related object: L<Koha::Schema::Result::ClubTemplateEnrollmentField>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "club_template_enrollment_field",
+  "Koha::Schema::Result::ClubTemplateEnrollmentField",
+  { id => "club_template_enrollment_field_id" },
+  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07040 @ 2015-01-12 09:56:17
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:2ANAs3mh3i/kd3Qxrcd5IA
+
+
+# You can replace this text with custom content, and it will be preserved on regeneration
+1;
diff --git a/Koha/Schema/Result/ClubField.pm b/Koha/Schema/Result/ClubField.pm
new file mode 100644
index 0000000..eab0f4b
--- /dev/null
+++ b/Koha/Schema/Result/ClubField.pm
@@ -0,0 +1,112 @@
+use utf8;
+package Koha::Schema::Result::ClubField;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+=head1 NAME
+
+Koha::Schema::Result::ClubField
+
+=cut
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+=head1 TABLE: C<club_fields>
+
+=cut
+
+__PACKAGE__->table("club_fields");
+
+=head1 ACCESSORS
+
+=head2 id
+
+  data_type: 'integer'
+  is_auto_increment: 1
+  is_nullable: 0
+
+=head2 club_template_field_id
+
+  data_type: 'integer'
+  is_foreign_key: 1
+  is_nullable: 0
+
+=head2 club_id
+
+  data_type: 'integer'
+  is_foreign_key: 1
+  is_nullable: 0
+
+=head2 value
+
+  data_type: 'text'
+  is_nullable: 1
+
+=cut
+
+__PACKAGE__->add_columns(
+  "id",
+  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
+  "club_template_field_id",
+  { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+  "club_id",
+  { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+  "value",
+  { data_type => "text", is_nullable => 1 },
+);
+
+=head1 PRIMARY KEY
+
+=over 4
+
+=item * L</id>
+
+=back
+
+=cut
+
+__PACKAGE__->set_primary_key("id");
+
+=head1 RELATIONS
+
+=head2 club
+
+Type: belongs_to
+
+Related object: L<Koha::Schema::Result::Club>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "club",
+  "Koha::Schema::Result::Club",
+  { id => "club_id" },
+  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
+);
+
+=head2 club_template_field
+
+Type: belongs_to
+
+Related object: L<Koha::Schema::Result::ClubTemplateField>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "club_template_field",
+  "Koha::Schema::Result::ClubTemplateField",
+  { id => "club_template_field_id" },
+  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07040 @ 2015-01-12 09:56:17
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:m4GLLIVIHgRpRhCGLh12DQ
+
+
+# You can replace this text with custom content, and it will be preserved on regeneration
+1;
diff --git a/Koha/Schema/Result/ClubTemplate.pm b/Koha/Schema/Result/ClubTemplate.pm
new file mode 100644
index 0000000..2c6f37a
--- /dev/null
+++ b/Koha/Schema/Result/ClubTemplate.pm
@@ -0,0 +1,195 @@
+use utf8;
+package Koha::Schema::Result::ClubTemplate;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+=head1 NAME
+
+Koha::Schema::Result::ClubTemplate
+
+=cut
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+=head1 TABLE: C<club_templates>
+
+=cut
+
+__PACKAGE__->table("club_templates");
+
+=head1 ACCESSORS
+
+=head2 id
+
+  data_type: 'integer'
+  is_auto_increment: 1
+  is_nullable: 0
+
+=head2 name
+
+  data_type: 'tinytext'
+  is_nullable: 0
+
+=head2 description
+
+  data_type: 'text'
+  is_nullable: 1
+
+=head2 is_enrollable_from_opac
+
+  data_type: 'tinyint'
+  default_value: 0
+  is_nullable: 0
+
+=head2 is_email_required
+
+  data_type: 'tinyint'
+  default_value: 0
+  is_nullable: 0
+
+=head2 branchcode
+
+  data_type: 'varchar'
+  is_foreign_key: 1
+  is_nullable: 1
+  size: 10
+
+=head2 date_created
+
+  data_type: 'timestamp'
+  datetime_undef_if_invalid: 1
+  default_value: current_timestamp
+  is_nullable: 0
+
+=head2 date_updated
+
+  data_type: 'timestamp'
+  datetime_undef_if_invalid: 1
+  is_nullable: 1
+
+=head2 is_deletable
+
+  data_type: 'tinyint'
+  default_value: 1
+  is_nullable: 0
+
+=cut
+
+__PACKAGE__->add_columns(
+  "id",
+  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
+  "name",
+  { data_type => "tinytext", is_nullable => 0 },
+  "description",
+  { data_type => "text", is_nullable => 1 },
+  "is_enrollable_from_opac",
+  { data_type => "tinyint", default_value => 0, is_nullable => 0 },
+  "is_email_required",
+  { data_type => "tinyint", default_value => 0, is_nullable => 0 },
+  "branchcode",
+  { data_type => "varchar", is_foreign_key => 1, is_nullable => 1, size => 10 },
+  "date_created",
+  {
+    data_type => "timestamp",
+    datetime_undef_if_invalid => 1,
+    default_value => \"current_timestamp",
+    is_nullable => 0,
+  },
+  "date_updated",
+  {
+    data_type => "timestamp",
+    datetime_undef_if_invalid => 1,
+    is_nullable => 1,
+  },
+  "is_deletable",
+  { data_type => "tinyint", default_value => 1, is_nullable => 0 },
+);
+
+=head1 PRIMARY KEY
+
+=over 4
+
+=item * L</id>
+
+=back
+
+=cut
+
+__PACKAGE__->set_primary_key("id");
+
+=head1 RELATIONS
+
+=head2 branchcode
+
+Type: belongs_to
+
+Related object: L<Koha::Schema::Result::Branch>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "branchcode",
+  "Koha::Schema::Result::Branch",
+  { branchcode => "branchcode" },
+  {
+    is_deferrable => 1,
+    join_type     => "LEFT",
+    on_delete     => "SET NULL",
+    on_update     => "CASCADE",
+  },
+);
+
+=head2 club_template_enrollment_fields
+
+Type: has_many
+
+Related object: L<Koha::Schema::Result::ClubTemplateEnrollmentField>
+
+=cut
+
+__PACKAGE__->has_many(
+  "club_template_enrollment_fields",
+  "Koha::Schema::Result::ClubTemplateEnrollmentField",
+  { "foreign.club_template_id" => "self.id" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
+=head2 club_template_fields
+
+Type: has_many
+
+Related object: L<Koha::Schema::Result::ClubTemplateField>
+
+=cut
+
+__PACKAGE__->has_many(
+  "club_template_fields",
+  "Koha::Schema::Result::ClubTemplateField",
+  { "foreign.club_template_id" => "self.id" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
+=head2 clubs
+
+Type: has_many
+
+Related object: L<Koha::Schema::Result::Club>
+
+=cut
+
+__PACKAGE__->has_many(
+  "clubs",
+  "Koha::Schema::Result::Club",
+  { "foreign.club_template_id" => "self.id" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07040 @ 2015-01-12 09:56:17
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:j5aiACVUGzrdng4+jt6mfg
+
+1;
diff --git a/Koha/Schema/Result/ClubTemplateEnrollmentField.pm b/Koha/Schema/Result/ClubTemplateEnrollmentField.pm
new file mode 100644
index 0000000..3b3907d
--- /dev/null
+++ b/Koha/Schema/Result/ClubTemplateEnrollmentField.pm
@@ -0,0 +1,119 @@
+use utf8;
+package Koha::Schema::Result::ClubTemplateEnrollmentField;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+=head1 NAME
+
+Koha::Schema::Result::ClubTemplateEnrollmentField
+
+=cut
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+=head1 TABLE: C<club_template_enrollment_fields>
+
+=cut
+
+__PACKAGE__->table("club_template_enrollment_fields");
+
+=head1 ACCESSORS
+
+=head2 id
+
+  data_type: 'integer'
+  is_auto_increment: 1
+  is_nullable: 0
+
+=head2 club_template_id
+
+  data_type: 'integer'
+  is_foreign_key: 1
+  is_nullable: 0
+
+=head2 name
+
+  data_type: 'tinytext'
+  is_nullable: 0
+
+=head2 description
+
+  data_type: 'text'
+  is_nullable: 1
+
+=head2 authorised_value_category
+
+  data_type: 'varchar'
+  is_nullable: 1
+  size: 16
+
+=cut
+
+__PACKAGE__->add_columns(
+  "id",
+  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
+  "club_template_id",
+  { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+  "name",
+  { data_type => "tinytext", is_nullable => 0 },
+  "description",
+  { data_type => "text", is_nullable => 1 },
+  "authorised_value_category",
+  { data_type => "varchar", is_nullable => 1, size => 16 },
+);
+
+=head1 PRIMARY KEY
+
+=over 4
+
+=item * L</id>
+
+=back
+
+=cut
+
+__PACKAGE__->set_primary_key("id");
+
+=head1 RELATIONS
+
+=head2 club_enrollment_fields
+
+Type: has_many
+
+Related object: L<Koha::Schema::Result::ClubEnrollmentField>
+
+=cut
+
+__PACKAGE__->has_many(
+  "club_enrollment_fields",
+  "Koha::Schema::Result::ClubEnrollmentField",
+  { "foreign.club_template_enrollment_field_id" => "self.id" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
+=head2 club_template
+
+Type: belongs_to
+
+Related object: L<Koha::Schema::Result::ClubTemplate>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "club_template",
+  "Koha::Schema::Result::ClubTemplate",
+  { id => "club_template_id" },
+  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07040 @ 2015-01-12 09:56:17
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:KGo2mEIAkTVYSPsOLoaBCg
+
+
+# You can replace this text with custom content, and it will be preserved on regeneration
+1;
diff --git a/Koha/Schema/Result/ClubTemplateField.pm b/Koha/Schema/Result/ClubTemplateField.pm
new file mode 100644
index 0000000..ecbca85
--- /dev/null
+++ b/Koha/Schema/Result/ClubTemplateField.pm
@@ -0,0 +1,119 @@
+use utf8;
+package Koha::Schema::Result::ClubTemplateField;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+=head1 NAME
+
+Koha::Schema::Result::ClubTemplateField
+
+=cut
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+=head1 TABLE: C<club_template_fields>
+
+=cut
+
+__PACKAGE__->table("club_template_fields");
+
+=head1 ACCESSORS
+
+=head2 id
+
+  data_type: 'integer'
+  is_auto_increment: 1
+  is_nullable: 0
+
+=head2 club_template_id
+
+  data_type: 'integer'
+  is_foreign_key: 1
+  is_nullable: 0
+
+=head2 name
+
+  data_type: 'tinytext'
+  is_nullable: 0
+
+=head2 description
+
+  data_type: 'text'
+  is_nullable: 1
+
+=head2 authorised_value_category
+
+  data_type: 'varchar'
+  is_nullable: 1
+  size: 16
+
+=cut
+
+__PACKAGE__->add_columns(
+  "id",
+  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
+  "club_template_id",
+  { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+  "name",
+  { data_type => "tinytext", is_nullable => 0 },
+  "description",
+  { data_type => "text", is_nullable => 1 },
+  "authorised_value_category",
+  { data_type => "varchar", is_nullable => 1, size => 16 },
+);
+
+=head1 PRIMARY KEY
+
+=over 4
+
+=item * L</id>
+
+=back
+
+=cut
+
+__PACKAGE__->set_primary_key("id");
+
+=head1 RELATIONS
+
+=head2 club_fields
+
+Type: has_many
+
+Related object: L<Koha::Schema::Result::ClubField>
+
+=cut
+
+__PACKAGE__->has_many(
+  "club_fields",
+  "Koha::Schema::Result::ClubField",
+  { "foreign.club_template_field_id" => "self.id" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
+=head2 club_template
+
+Type: belongs_to
+
+Related object: L<Koha::Schema::Result::ClubTemplate>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "club_template",
+  "Koha::Schema::Result::ClubTemplate",
+  { id => "club_template_id" },
+  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07040 @ 2015-01-12 09:56:17
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:P73ABSn2tzlTYbD3nov21g
+
+
+# You can replace this text with custom content, and it will be preserved on regeneration
+1;
diff --git a/Koha/Template/Plugin/AuthorisedValues.pm b/Koha/Template/Plugin/AuthorisedValues.pm
index 36ddce1..afc10f7 100644
--- a/Koha/Template/Plugin/AuthorisedValues.pm
+++ b/Koha/Template/Plugin/AuthorisedValues.pm
@@ -38,6 +38,12 @@ sub GetAuthValueDropbox {
     return C4::Koha::GetAuthvalueDropbox($category, $default);
 }
 
+sub Categories {
+    my ( $self ) = @_;
+
+    return GetAuthorisedValueCategories();
+}
+
 1;
 
 =head1 NAME
diff --git a/Koha/Template/Plugin/Borrowers.pm b/Koha/Template/Plugin/Borrowers.pm
index b8f3de5..39112f7 100644
--- a/Koha/Template/Plugin/Borrowers.pm
+++ b/Koha/Template/Plugin/Borrowers.pm
@@ -48,4 +48,8 @@ sub IsDebarred {
     return Koha::Borrower::Debarments::IsDebarred($borrower->{borrowernumber});
 }
 
+sub HasValidEmailAddress {
+
+}
+
 1;
diff --git a/Koha/Template/Plugin/Branches.pm b/Koha/Template/Plugin/Branches.pm
index f197e99..a4790a9 100644
--- a/Koha/Template/Plugin/Branches.pm
+++ b/Koha/Template/Plugin/Branches.pm
@@ -25,6 +25,13 @@ use base qw( Template::Plugin );
 use C4::Koha;
 use C4::Context;
 
+sub GetBranches {
+    my ($self) = @_;
+
+    my $dbh = C4::Context->dbh;
+    return $dbh->selectall_arrayref( "SELECT * FROM branches", { Slice => {} } );
+}
+
 sub GetName {
     my ( $self, $branchcode ) = @_;
 
diff --git a/Koha/Template/Plugin/Koha.pm b/Koha/Template/Plugin/Koha.pm
index ecb6266..f52ba67 100644
--- a/Koha/Template/Plugin/Koha.pm
+++ b/Koha/Template/Plugin/Koha.pm
@@ -60,4 +60,9 @@ sub Version {
     };
 }
 
+sub UserEnv {
+    my ( $self, $key ) = @_;
+    my $userenv = C4::Context->userenv;
+    return $userenv ? $userenv->{$key} : undef;
+}
 1;
diff --git a/circ/circulation.pl b/circ/circulation.pl
index ab47895..fdd171f 100755
--- a/circ/circulation.pl
+++ b/circ/circulation.pl
@@ -46,6 +46,7 @@ use C4::Members::Attributes qw(GetBorrowerAttributes);
 use Koha::Borrower::Debarments qw(GetDebarments IsDebarred);
 use Koha::DateUtils;
 use Koha::Database;
+use Koha::Borrowers;
 
 use Date::Calc qw(
   Today
@@ -580,6 +581,8 @@ $template->param( picture => 1 ) if $picture;
 
 my $canned_notes = GetAuthorisedValues("BOR_NOTES");
 
+my $schema = Koha::Database->new()->schema();
+
 $template->param(
     debt_confirmed            => $debt_confirmed,
     SpecifyDueDate            => $duedatespec_allow,
@@ -588,6 +591,7 @@ $template->param(
     canned_bor_notes_loop     => $canned_notes,
     debarments                => GetDebarments({ borrowernumber => $borrowernumber }),
     todaysdate                => dt_from_string()->set(hour => 23)->set(minute => 59),
+    borrower                  => Koha::Borrowers->find( $borrowernumber ),
 );
 
 output_html_with_http_headers $query, $cookie, $template->output;
diff --git a/clubs/clubs-add-modify.pl b/clubs/clubs-add-modify.pl
new file mode 100755
index 0000000..1775951
--- /dev/null
+++ b/clubs/clubs-add-modify.pl
@@ -0,0 +1,104 @@
+#!/usr/bin/perl
+
+# Copyright 2013 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+
+use CGI;
+
+use C4::Auth;
+use C4::Output;
+use Koha::Database;
+use Koha::DateUtils qw(dt_from_string);
+use Koha::Clubs;
+use Koha::Club::Fields;
+
+my $cgi = new CGI;
+
+my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+    {
+        template_name   => 'clubs/clubs-add-modify.tt',
+        query           => $cgi,
+        type            => 'intranet',
+        authnotrequired => 0,
+        flagsrequired   => { clubs => 'edit_clubs' },
+    }
+);
+
+my $schema = Koha::Database->new()->schema();
+
+my $id = $cgi->param('id');
+my $club = $id ? Koha::Clubs->find($id) : Koha::Club->new();
+$template->param( stored => $id ? 'updated' : 'stored' ) if $cgi->param('name');
+
+my $club_template_id = $cgi->param('club_template_id');
+my $club_template = $club->club_template() || Koha::Club::Templates->find($club_template_id);
+$club_template_id ||= $club_template->id();
+
+my $date_start = $cgi->param('date_start');
+$date_start = $date_start ? dt_from_string($date_start) : undef;
+my $date_end = $cgi->param('date_end');
+$date_end = $date_end ? dt_from_string($date_end) : undef;
+
+if ( $cgi->param('name') ) {    # Update or create club
+    $club->set(
+        {
+            club_template_id => $cgi->param('club_template_id') || undef,
+            name             => $cgi->param('name')             || undef,
+            description      => $cgi->param('description')      || undef,
+            branchcode       => $cgi->param('branchcode')       || undef,
+            date_start       => $date_start,
+            date_end         => $date_end,
+            date_updated     => dt_from_string(),
+        }
+    )->store();
+
+    my @club_template_field_id = $cgi->param('club_template_field_id');
+    my @club_field_id          = $cgi->param('club_field_id');
+    my @club_field             = $cgi->param('club_field');
+
+    for ( my $i = 0 ; $i < @club_template_field_id ; $i++ ) {
+        my $club_template_field_id = $club_template_field_id[$i] || undef;
+        my $club_field_id          = $club_field_id[$i]          || undef;
+        my $club_field             = $club_field[$i]             || undef;
+
+        my $field =
+          $club_field_id
+          ? Koha::Club::Fields->find($club_field_id)
+          : Koha::Club::Field->new();
+
+        $field->set(
+            {
+                club_id                => $club->id(),
+                club_template_field_id => $club_template_field_id,
+                value                  => $club_field,
+            }
+        )->store();
+    }
+
+    $id ||= $club->id();
+}
+
+$club = Koha::Clubs->find($id);
+
+$template->param(
+    club_template => $club_template,
+    club          => $club,
+);
+
+output_html_with_http_headers( $cgi, $cookie, $template->output );
diff --git a/clubs/clubs.pl b/clubs/clubs.pl
new file mode 100755
index 0000000..1636ce5
--- /dev/null
+++ b/clubs/clubs.pl
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+
+# Copyright 2013 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+
+use CGI;
+
+use C4::Auth;
+use C4::Output;
+
+use Koha::Clubs;
+use Koha::Club::Templates;
+
+my $cgi = new CGI;
+
+my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+    {
+        template_name   => "clubs/clubs.tt",
+        query           => $cgi,
+        type            => "intranet",
+        authnotrequired => 0,
+        flagsrequired   => { clubs => '*' },
+    }
+);
+
+my @club_templates = Koha::Club::Templates->search();
+my @clubs          = Koha::Clubs->search();
+
+$template->param(
+    club_templates => \@club_templates,
+    clubs          => \@clubs,
+);
+
+output_html_with_http_headers( $cgi, $cookie, $template->output );
diff --git a/clubs/patron-clubs-tab.pl b/clubs/patron-clubs-tab.pl
new file mode 100755
index 0000000..68da462
--- /dev/null
+++ b/clubs/patron-clubs-tab.pl
@@ -0,0 +1,55 @@
+#!/usr/bin/perl
+
+# Copyright 2013 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+
+use CGI;
+
+use C4::Auth;
+use C4::Output;
+
+use Koha::Borrowers;
+use Koha::Club::Enrollments;
+
+my $cgi = new CGI;
+
+my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+    {
+        template_name   => "clubs/patron-clubs-tab.tt",
+        query           => $cgi,
+        type            => "intranet",
+        authnotrequired => 0,
+        flagsrequired   => { clubs => '*' },
+    }
+);
+
+my $borrowernumber = $cgi->param('borrowernumber');
+
+my $borrower = Koha::Borrowers->find($borrowernumber);
+
+my @enrollments = $borrower->GetClubEnrollments();
+my @clubs       = $borrower->GetEnrollableClubs();
+
+$template->param(
+    enrollments    => \@enrollments,
+    clubs          => \@clubs,
+    borrowernumber => $borrowernumber
+);
+
+output_html_with_http_headers( $cgi, $cookie, $template->output );
diff --git a/clubs/patron-enroll.pl b/clubs/patron-enroll.pl
new file mode 100755
index 0000000..bc83627
--- /dev/null
+++ b/clubs/patron-enroll.pl
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+
+# Copyright 2013 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+
+use CGI;
+
+use C4::Auth;
+use C4::Output;
+use Koha::Clubs;
+
+my $cgi = new CGI;
+
+my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+    {
+        template_name   => "clubs/patron-enroll.tt",
+        query           => $cgi,
+        type            => "intranet",
+        authnotrequired => 0,
+        flagsrequired   => { clubs => '*' },
+    }
+);
+
+my $id             = $cgi->param('id');
+my $borrowernumber = $cgi->param('borrowernumber');
+
+my $club = Koha::Clubs->find($id);
+
+$template->param(
+    club           => $club,
+    borrowernumber => $borrowernumber,
+);
+
+output_html_with_http_headers( $cgi, $cookie, $template->output );
diff --git a/clubs/templates-add-modify.pl b/clubs/templates-add-modify.pl
new file mode 100755
index 0000000..a181359
--- /dev/null
+++ b/clubs/templates-add-modify.pl
@@ -0,0 +1,150 @@
+#!/usr/bin/perl
+
+# Copyright 2013 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+
+use CGI;
+
+use C4::Auth;
+use C4::Output;
+
+use Koha::DateUtils qw(dt_from_string);
+use Koha::Club::Templates;
+use Koha::Club::Template::Fields;
+use Koha::Club::Template::EnrollmentFields;
+
+use Koha::Database;
+my $schema = Koha::Database->new()->schema();
+
+my $cgi = new CGI;
+
+my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+    {
+        template_name   => 'clubs/templates-add-modify.tt',
+        query           => $cgi,
+        type            => 'intranet',
+        authnotrequired => 0,
+        flagsrequired   => { clubs => 'edit_templates' },
+    }
+);
+
+my $id = $cgi->param('id');
+
+my $club_template;
+
+if ( $cgi->param('name') ) {    # Update or create club
+    if ($id) {
+        $club_template = Koha::Club::Templates->find($id);
+        $template->param( stored => 'updated' );
+    }
+    else {
+        $club_template = Koha::Club::Template->new();
+        $template->param( stored => 'created' );
+    }
+
+    $club_template->set(
+        {
+            id          => $id                        || undef,
+            name        => $cgi->param('name')        || undef,
+            description => $cgi->param('description') || undef,
+            branchcode  => $cgi->param('branchcode')  || undef,
+            date_updated            => dt_from_string(),
+            is_email_required       => $cgi->param('is_email_required') ? 1 : 0,
+            is_enrollable_from_opac => $cgi->param('is_enrollable_from_opac')
+            ? 1
+            : 0,
+        }
+    )->store();
+
+    $id ||= $club_template->id();
+
+    # Update club creation fields
+    my @field_id                        = $cgi->param('club_template_field_id');
+    my @field_name                      = $cgi->param('club_template_field_name');
+    my @field_description               = $cgi->param('club_template_field_description');
+    my @field_authorised_value_category = $cgi->param('club_template_field_authorised_value_category');
+
+    my @field_delete = $cgi->param('club_template_field_delete');
+
+    for ( my $i = 0 ; $i < @field_id ; $i++ ) {
+        my $field_id                        = $field_id[$i];
+        my $field_name                      = $field_name[$i];
+        my $field_description               = $field_description[$i];
+        my $field_authorised_value_category = $field_authorised_value_category[$i];
+
+        my $field =
+          $field_id
+          ? Koha::Club::Template::Fields->find($field_id)
+          : Koha::Club::Template::Field->new();
+
+        if ( grep( /^$field_id$/, @field_delete ) ) {
+            $field->delete();
+        }
+        else {
+            $field->set(
+                {
+                    club_template_id          => $id,
+                    name                      => $field_name,
+                    description               => $field_description,
+                    authorised_value_category => $field_authorised_value_category,
+                }
+            )->store();
+        }
+    }
+
+    # Update club enrollment fields
+    @field_id                        = $cgi->param('club_template_enrollment_field_id');
+    @field_name                      = $cgi->param('club_template_enrollment_field_name');
+    @field_description               = $cgi->param('club_template_enrollment_field_description');
+    @field_authorised_value_category = $cgi->param('club_template_enrollment_field_authorised_value_category');
+
+    @field_delete = $cgi->param('club_template_enrollment_field_delete');
+
+    for ( my $i = 0 ; $i < @field_id ; $i++ ) {
+        my $field_id                        = $field_id[$i];
+        my $field_name                      = $field_name[$i];
+        my $field_description               = $field_description[$i];
+        my $field_authorised_value_category = $field_authorised_value_category[$i];
+
+        my $field =
+          $field_id
+          ? Koha::Club::Template::EnrollmentFields->find($field_id)
+          : Koha::Club::Template::EnrollmentField->new();
+
+        if ( grep( /^$field_id$/, @field_delete ) ) {
+            $field->delete();
+        }
+        else {
+            $field->set(
+                {
+                    id                        => $field_id,
+                    club_template_id          => $id,
+                    name                      => $field_name,
+                    description               => $field_description,
+                    authorised_value_category => $field_authorised_value_category,
+                }
+            )->store();
+        }
+    }
+}
+
+$club_template ||= Koha::Club::Templates->find($id);
+$template->param( club_template => $club_template );
+
+output_html_with_http_headers( $cgi, $cookie, $template->output );
diff --git a/installer/data/mysql/en/mandatory/userflags.sql b/installer/data/mysql/en/mandatory/userflags.sql
index 40ca2b6..0a75cda 100644
--- a/installer/data/mysql/en/mandatory/userflags.sql
+++ b/installer/data/mysql/en/mandatory/userflags.sql
@@ -18,5 +18,6 @@ INSERT INTO userflags (bit, flag, flagdesc, defaulton) VALUES
 (17,'staffaccess','Allow staff members to modify permissions for other staff members',0),
 (18,'coursereserves','Course reserves',0),
 (19, 'plugins', 'Koha plugins', '0'),
-(20, 'lists', 'Lists', 0)
+(20, 'lists', 'Lists', 0),
+(21, 'clubs', 'Patron clubs', '0')
 ;
diff --git a/installer/data/mysql/en/mandatory/userpermissions.sql b/installer/data/mysql/en/mandatory/userpermissions.sql
index 7b50d51..c40e0f8 100644
--- a/installer/data/mysql/en/mandatory/userpermissions.sql
+++ b/installer/data/mysql/en/mandatory/userpermissions.sql
@@ -73,5 +73,8 @@ INSERT INTO permissions (module_bit, code, description) VALUES
    (19, 'tool', 'Use tool plugins'),
    (19, 'report', 'Use report plugins'),
    (19, 'configure', 'Configure plugins'),
-   (20, 'delete_public_lists', 'Delete public lists')
+   (20, 'delete_public_lists', 'Delete public lists'),
+   (21, 'edit_templates', 'Create and update club templates'),
+   (21, 'edit_clubs', 'Create and update clubs'),
+   (21, 'enroll', 'Enroll patrons in clubs')
 ;
diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql
index dd6bac9..3a08b75 100644
--- a/installer/data/mysql/kohastructure.sql
+++ b/installer/data/mysql/kohastructure.sql
@@ -3519,6 +3519,184 @@ CREATE TABLE discharges (
   CONSTRAINT borrower_discharges_ibfk1 FOREIGN KEY (borrower) REFERENCES borrowers (borrowernumber) ON DELETE CASCADE ON UPDATE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 
+--
+-- Table structure for table 'clubs'
+--
+
+DROP TABLE IF EXISTS clubs;
+CREATE TABLE IF NOT EXISTS clubs (
+  id int(11) NOT NULL AUTO_INCREMENT,
+  club_template_id int(11) NOT NULL,
+  `name` tinytext NOT NULL,
+  description text,
+  date_start date DEFAULT NULL,
+  date_end date DEFAULT NULL,
+  branchcode varchar(11) DEFAULT NULL,
+  date_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  date_updated timestamp NULL DEFAULT NULL,
+  PRIMARY KEY (id),
+  KEY club_template_id (club_template_id),
+  KEY branchcode (branchcode)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table 'club_enrollments'
+--
+
+DROP TABLE IF EXISTS club_enrollments;
+CREATE TABLE IF NOT EXISTS club_enrollments (
+  id int(11) NOT NULL AUTO_INCREMENT,
+  club_id int(11) NOT NULL,
+  borrowernumber int(11) NOT NULL,
+  date_enrolled timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  date_canceled timestamp NULL DEFAULT NULL,
+  date_created timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  date_updated timestamp NULL DEFAULT NULL,
+  branchcode varchar(11) DEFAULT NULL,
+  PRIMARY KEY (id),
+  KEY club_id (club_id),
+  KEY borrowernumber (borrowernumber),
+  KEY branchcode (branchcode)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table 'club_enrollment_fields'
+--
+
+DROP TABLE IF EXISTS club_enrollment_fields;
+CREATE TABLE IF NOT EXISTS club_enrollment_fields (
+  id int(11) NOT NULL AUTO_INCREMENT,
+  club_enrollment_id int(11) NOT NULL,
+  club_template_enrollment_field_id int(11) NOT NULL,
+  `value` text NOT NULL,
+  PRIMARY KEY (id),
+  KEY club_enrollment_id (club_enrollment_id),
+  KEY club_template_enrollment_field_id (club_template_enrollment_field_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table 'club_fields'
+--
+
+DROP TABLE IF EXISTS club_fields;
+CREATE TABLE IF NOT EXISTS club_fields (
+  id int(11) NOT NULL AUTO_INCREMENT,
+  club_template_field_id int(11) NOT NULL,
+  club_id int(11) NOT NULL,
+  `value` text,
+  PRIMARY KEY (id),
+  KEY club_template_field_id (club_template_field_id),
+  KEY club_id (club_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table 'club_templates'
+--
+
+DROP TABLE IF EXISTS club_templates;
+CREATE TABLE IF NOT EXISTS club_templates (
+  id int(11) NOT NULL AUTO_INCREMENT,
+  `name` tinytext NOT NULL,
+  description text,
+  is_enrollable_from_opac tinyint(1) NOT NULL DEFAULT '0',
+  is_email_required tinyint(1) NOT NULL DEFAULT '0',
+  branchcode varchar(10) DEFAULT NULL,
+  date_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  date_updated timestamp NULL DEFAULT NULL,
+  is_deletable tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY (id),
+  KEY branchcode (branchcode)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table 'club_template_enrollment_fields'
+--
+
+DROP TABLE IF EXISTS club_template_enrollment_fields;
+CREATE TABLE IF NOT EXISTS club_template_enrollment_fields (
+  id int(11) NOT NULL AUTO_INCREMENT,
+  club_template_id int(11) NOT NULL,
+  `name` tinytext NOT NULL,
+  description text,
+  authorised_value_category varchar(16) DEFAULT NULL,
+  PRIMARY KEY (id),
+  KEY club_template_id (club_template_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table 'club_template_fields'
+--
+
+DROP TABLE IF EXISTS club_template_fields;
+CREATE TABLE IF NOT EXISTS club_template_fields (
+  id int(11) NOT NULL AUTO_INCREMENT,
+  club_template_id int(11) NOT NULL,
+  `name` tinytext NOT NULL,
+  description text,
+  authorised_value_category varchar(16) DEFAULT NULL,
+  PRIMARY KEY (id),
+  KEY club_template_id (club_template_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Constraints for table `clubs`
+--
+ALTER TABLE `clubs`
+  ADD CONSTRAINT clubs_ibfk_1 FOREIGN KEY (club_template_id) REFERENCES club_templates (id) ON DELETE CASCADE ON UPDATE CASCADE,
+  ADD CONSTRAINT clubs_ibfk_2 FOREIGN KEY (branchcode) REFERENCES branches (branchcode);
+
+--
+-- Constraints for table `club_enrollments`
+--
+ALTER TABLE `club_enrollments`
+  ADD CONSTRAINT club_enrollments_ibfk_1 FOREIGN KEY (club_id) REFERENCES clubs (id) ON DELETE CASCADE ON UPDATE CASCADE,
+  ADD CONSTRAINT club_enrollments_ibfk_2 FOREIGN KEY (borrowernumber) REFERENCES borrowers (borrowernumber) ON DELETE CASCADE ON UPDATE CASCADE,
+  ADD CONSTRAINT club_enrollments_ibfk_3 FOREIGN KEY (branchcode) REFERENCES branches (branchcode) ON DELETE SET NULL ON UPDATE CASCADE;
+
+--
+-- Constraints for table `club_enrollment_fields`
+--
+ALTER TABLE `club_enrollment_fields`
+  ADD CONSTRAINT club_enrollment_fields_ibfk_1 FOREIGN KEY (club_enrollment_id) REFERENCES club_enrollments (id) ON DELETE CASCADE ON UPDATE CASCADE,
+  ADD CONSTRAINT club_enrollment_fields_ibfk_2 FOREIGN KEY (club_template_enrollment_field_id) REFERENCES club_template_enrollment_fields (id) ON DELETE CASCADE ON UPDATE CASCADE;
+
+--
+-- Constraints for table `club_fields`
+--
+ALTER TABLE `club_fields`
+  ADD CONSTRAINT club_fields_ibfk_3 FOREIGN KEY (club_template_field_id) REFERENCES club_template_fields (id) ON DELETE CASCADE ON UPDATE CASCADE,
+  ADD CONSTRAINT club_fields_ibfk_4 FOREIGN KEY (club_id) REFERENCES clubs (id) ON DELETE CASCADE ON UPDATE CASCADE;
+
+--
+-- Constraints for table `club_templates`
+--
+ALTER TABLE `club_templates`
+  ADD CONSTRAINT club_templates_ibfk_1 FOREIGN KEY (branchcode) REFERENCES branches (branchcode) ON DELETE SET NULL ON UPDATE CASCADE;
+
+--
+-- Constraints for table `club_template_enrollment_fields`
+--
+ALTER TABLE `club_template_enrollment_fields`
+  ADD CONSTRAINT club_template_enrollment_fields_ibfk_1 FOREIGN KEY (club_template_id) REFERENCES club_templates (id) ON DELETE CASCADE ON UPDATE CASCADE;
+
+--
+-- Constraints for table `club_template_fields`
+--
+ALTER TABLE `club_template_fields`
+  ADD CONSTRAINT club_template_fields_ibfk_1 FOREIGN KEY (club_template_id) REFERENCES club_templates (id) ON DELETE CASCADE ON UPDATE CASCADE;
+
 /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
 /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
 /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl
index 2274251..883aa80 100755
--- a/installer/data/mysql/updatedatabase.pl
+++ b/installer/data/mysql/updatedatabase.pl
@@ -10399,6 +10399,160 @@ $DBversion = "3.19.00.039";
 if ( CheckVersion($DBversion) ) {
     print "Upgrade to $DBversion done (Koha 3.20 beta)\n";
     SetVersion ($DBversion);
+   SetVersion ($DBversion);
+}
+
+$DBversion = "3.19.00.XXX";
+if ( CheckVersion($DBversion) ) {
+    $dbh->do("INSERT INTO userflags (bit, flag, flagdesc, defaulton) VALUES ('21', 'clubs', 'Patron clubs', '0')");
+
+    $dbh->do("
+        INSERT INTO permissions (module_bit, code, description) VALUES
+        (21, 'edit_templates', 'Create and update club templates'),
+        (21, 'edit_clubs', 'Create and update clubs'),
+        (21, 'enroll', 'Enroll patrons in clubs')
+    ");
+
+    $dbh->do("
+        CREATE TABLE clubs (
+            id int(11) NOT NULL AUTO_INCREMENT,
+            club_template_id int(11) NOT NULL,
+            `name` tinytext NOT NULL,
+            description text,
+            date_start date DEFAULT NULL,
+            date_end date DEFAULT NULL,
+            branchcode varchar(11) DEFAULT NULL,
+            date_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+            date_updated timestamp NULL DEFAULT NULL,
+            PRIMARY KEY (id),
+            KEY club_template_id (club_template_id),
+            KEY branchcode (branchcode)
+        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+    ");
+
+    $dbh->do("
+        CREATE TABLE club_enrollments (
+            id int(11) NOT NULL AUTO_INCREMENT,
+            club_id int(11) NOT NULL,
+            borrowernumber int(11) NOT NULL,
+            date_enrolled timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+            date_canceled timestamp NULL DEFAULT NULL,
+            date_created timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+            date_updated timestamp NULL DEFAULT NULL,
+            branchcode varchar(11) DEFAULT NULL,
+            PRIMARY KEY (id),
+            KEY club_id (club_id),
+            KEY borrowernumber (borrowernumber),
+            KEY branchcode (branchcode)
+        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+    ");
+
+    $dbh->do("
+        CREATE TABLE club_enrollment_fields (
+            id int(11) NOT NULL AUTO_INCREMENT,
+            club_enrollment_id int(11) NOT NULL,
+            club_template_enrollment_field_id int(11) NOT NULL,
+            `value` text NOT NULL,
+            PRIMARY KEY (id),
+            KEY club_enrollment_id (club_enrollment_id),
+            KEY club_template_enrollment_field_id (club_template_enrollment_field_id)
+        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+    ");
+
+    $dbh->do("
+        CREATE TABLE club_fields (
+            id int(11) NOT NULL AUTO_INCREMENT,
+            club_template_field_id int(11) NOT NULL,
+            club_id int(11) NOT NULL,
+            `value` text,
+            PRIMARY KEY (id),
+            KEY club_template_field_id (club_template_field_id),
+            KEY club_id (club_id)
+        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+    ");
+
+    $dbh->do("
+        CREATE TABLE club_templates (
+            id int(11) NOT NULL AUTO_INCREMENT,
+            `name` tinytext NOT NULL,
+            description text,
+            is_enrollable_from_opac tinyint(1) NOT NULL DEFAULT '0',
+            is_email_required tinyint(1) NOT NULL DEFAULT '0',
+            branchcode varchar(10) CHARACTER SET utf8 DEFAULT NULL,
+            date_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+            date_updated timestamp NULL DEFAULT NULL,
+            is_deletable tinyint(1) NOT NULL DEFAULT '1',
+            PRIMARY KEY (id),
+            KEY branchcode (branchcode)
+        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+    ");
+
+    $dbh->do("
+        CREATE TABLE club_template_enrollment_fields (
+            id int(11) NOT NULL AUTO_INCREMENT,
+            club_template_id int(11) NOT NULL,
+            `name` tinytext NOT NULL,
+            description text,
+            authorised_value_category varchar(16) DEFAULT NULL,
+            PRIMARY KEY (id),
+            KEY club_template_id (club_template_id)
+        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+    ");
+
+    $dbh->do("
+        CREATE TABLE club_template_fields (
+            id int(11) NOT NULL AUTO_INCREMENT,
+            club_template_id int(11) NOT NULL,
+            `name` tinytext NOT NULL,
+            description text,
+            authorised_value_category varchar(16) DEFAULT NULL,
+            PRIMARY KEY (id),
+            KEY club_template_id (club_template_id)
+        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+    ");
+
+    $dbh->do("
+        ALTER TABLE `clubs`
+            ADD CONSTRAINT clubs_ibfk_1 FOREIGN KEY (club_template_id) REFERENCES club_templates (id) ON DELETE CASCADE ON UPDATE CASCADE,
+            ADD CONSTRAINT clubs_ibfk_2 FOREIGN KEY (branchcode) REFERENCES branches (branchcode);
+    ");
+
+    $dbh->do("
+        ALTER TABLE `club_enrollments`
+            ADD CONSTRAINT club_enrollments_ibfk_1 FOREIGN KEY (club_id) REFERENCES clubs (id) ON DELETE CASCADE ON UPDATE CASCADE,
+            ADD CONSTRAINT club_enrollments_ibfk_2 FOREIGN KEY (borrowernumber) REFERENCES borrowers (borrowernumber) ON DELETE CASCADE ON UPDATE CASCADE,
+            ADD CONSTRAINT club_enrollments_ibfk_3 FOREIGN KEY (branchcode) REFERENCES branches (branchcode) ON DELETE SET NULL ON UPDATE CASCADE;
+    ");
+
+    $dbh->do("
+        ALTER TABLE `club_enrollment_fields`
+            ADD CONSTRAINT club_enrollment_fields_ibfk_1 FOREIGN KEY (club_enrollment_id) REFERENCES club_enrollments (id) ON DELETE CASCADE ON UPDATE CASCADE,
+            ADD CONSTRAINT club_enrollment_fields_ibfk_2 FOREIGN KEY (club_template_enrollment_field_id) REFERENCES club_template_enrollment_fields (id) ON DELETE CASCADE ON UPDATE CASCADE;
+    ");
+
+    $dbh->do("
+        ALTER TABLE `club_fields`
+            ADD CONSTRAINT club_fields_ibfk_3 FOREIGN KEY (club_template_field_id) REFERENCES club_template_fields (id) ON DELETE CASCADE ON UPDATE CASCADE,
+            ADD CONSTRAINT club_fields_ibfk_4 FOREIGN KEY (club_id) REFERENCES clubs (id) ON DELETE CASCADE ON UPDATE CASCADE;
+    ");
+
+    $dbh->do("
+        ALTER TABLE `club_templates`
+            ADD CONSTRAINT club_templates_ibfk_1 FOREIGN KEY (branchcode) REFERENCES branches (branchcode) ON DELETE SET NULL ON UPDATE CASCADE;
+    ");
+
+    $dbh->do("
+        ALTER TABLE `club_template_enrollment_fields`
+            ADD CONSTRAINT club_template_enrollment_fields_ibfk_1 FOREIGN KEY (club_template_id) REFERENCES club_templates (id) ON DELETE CASCADE ON UPDATE CASCADE;
+    ");
+
+    $dbh->do("
+        ALTER TABLE `club_template_fields`
+            ADD CONSTRAINT club_template_fields_ibfk_1 FOREIGN KEY (club_template_id) REFERENCES club_templates (id) ON DELETE CASCADE ON UPDATE CASCADE;
+    ");
+
+   print "Upgrade to $DBversion done (Bug 12461 - Add patron clubs feature)\n";
+   SetVersion ($DBversion);
 }
 
 # DEVELOPER PROCESS, search for anything to execute in the db_update directory
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt
index 35447b7..2bd50e0 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt
@@ -77,6 +77,13 @@ $(document).ready(function() {
         }
     });
 
+    if ( $('#clubs-tab').length ) {
+        $('#clubs-tab-link').on('click', function() {
+            $('#clubs-tab').text(_("Loading..."));
+            $('#clubs-tab').load('/cgi-bin/koha/clubs/patron-clubs-tab.pl?borrowernumber=[% borrowernumber %]');
+        });
+    }
+
     [% IF !( CircAutoPrintQuickSlip == 'clear' ) %]
         // listen submit to trigger qslip on empty checkout
         $('#mainform').bind('submit',function() {
@@ -823,6 +830,16 @@ No patron matched <span class="ex">[% message %]</span>
         [% END %]
     </li>
 
+    [% SET enrollments = borrower.GetClubEnrollmentsCount %]
+    [% SET enrollable  = borrower.GetEnrollableClubsCount %]
+    [% IF CAN_user_clubs && ( enrollable || enrollments ) %]
+        <li>
+            <a id="clubs-tab-link" href="#clubs-tab">
+                Clubs ([% enrollments %]/[% enrollable %])
+            </a>
+        </li>
+    [% END %]
+
     [% IF relatives_issues_count %]
         <li><a id="relatives-issues-tab" href="#relatives-issues">Relatives' checkouts</a></li>
     [% END %]
@@ -862,6 +879,12 @@ No patron matched <span class="ex">[% message %]</span>
     </div>
 [% END %]
 
+[% IF CAN_user_clubs %]
+    <div id="clubs-tab">
+        Loading...
+    </div>
+[% END %]
+
 [% INCLUDE borrower_debarments.inc %]
 
 <div id="reserves">
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/renew.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/renew.tt
index 930bb06..5a33706 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/renew.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/renew.tt
@@ -67,7 +67,7 @@
 
                             [% ELSIF error == "auto_too_soon" %]
 
-                                <p>[% item.biblio.title | $EncodeUTF8 %] [% item.biblioitem.subtitle | $EncodeUTF8 %] ( [% item.barcode %] ) has been scheduled for automatic renewal and cannot be renewed before [% soonestrenewdate | $KohaDates %]. </p>
+                                <p>[% item.biblio.title %] [% item.biblioitem.subtitle %] ( [% item.barcode %] ) has been scheduled for automatic renewal and cannot be renewed before [% soonestrenewdate | $KohaDates %]. </p>
 
                                 [% IF Koha.Preference('AllowRenewalLimitOverride') %]
                                     <form method="post" action="/cgi-bin/koha/circ/renew.pl">
@@ -79,7 +79,7 @@
 
                             [% ELSIF error == "auto_renew" %]
 
-                                <p>[% item.biblio.title | $EncodeUTF8 %] [% item.biblioitem.subtitle | $EncodeUTF8 %] ( [% item.barcode %] ) has been scheduled for automatic renewal. </p>
+                                <p>[% item.biblio.title %] [% item.biblioitem.subtitle %] ( [% item.barcode %] ) has been scheduled for automatic renewal. </p>
 
                                 [% IF Koha.Preference('AllowRenewalLimitOverride') %]
                                     <form method="post" action="/cgi-bin/koha/circ/renew.pl">
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/clubs-add-modify.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/clubs-add-modify.tt
new file mode 100644
index 0000000..bbf1b48
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/clubs-add-modify.tt
@@ -0,0 +1,142 @@
+[% USE KohaDates %]
+[% USE Branches %]
+[% USE AuthorisedValues %]
+[% SET AuthorisedValuesCategories = AuthorisedValues.Categories %]
+[% INCLUDE 'doc-head-open.inc' %]
+<title>Koha &rsaquo; Tools &rsaquo; Patron clubs &rsaquo; Club</title>
+[% INCLUDE 'doc-head-close.inc' %]
+[% INCLUDE 'calendar.inc' %]
+
+<script type="text/javascript">
+//<![CDATA[
+
+function CheckForm() {
+  if ( !$("#club-name").val() ) {
+    alert( _("Name is a required field!")  );
+    return false;
+  }
+
+  return true;
+}
+
+//]]>
+</script>
+
+</head>
+
+<body id="clubs_add_modify" class="clubs">
+[% INCLUDE 'header.inc' %]
+[% INCLUDE 'cat-search.inc' %]
+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> &rsaquo; <a href="clubs.pl">Patron clubs</a> &rsaquo; Add / modify club</div>
+
+<div class="yui-t7">
+    <div class="yui-main">
+        [% IF stored %]
+            <div class="alert">
+                <p>Your club was [% IF stored == 'updated' %] updated [% ELSE %] saved [% END %]</p>
+                <a href="clubs.pl">Return to patron clubs</a>
+            </div>
+        [% END %]
+
+        <form method="post" onsubmit="return CheckForm()">
+            <input type="hidden" name="id" value="[% club.id %]" />
+            <input type="hidden" name="club_template_id" value="[% club_template.id %]" />
+
+            <fieldset class="rows">
+
+                <legend>
+                    [% IF club %]
+                        Modify club <i>[% club.name %]</i>
+                    [% ELSE %]
+                        Create a new <i>[% club_template.name %]</i> club
+                    [% END %]
+                </legend>
+
+                <ol>
+                    <li>
+                        <label class="required" for="name">Name:</label>
+                        <input id="club-name" name="name" type="text" value="[% club.name %]" />
+                    </li>
+
+                    <li>
+                        <label for="description">Description:</label>
+                        <input id="club-template-name" name="description" type="text" value="[% club.description %]" />
+                    </li>
+
+                    <li>
+                        <label for="date_start">Start date:</label>
+                        <input name="date_start" id="from" size="10" readonly="readonly" class="datepickerfrom" value="[% club.date_start | $KohaDates %]">
+                    </li>
+
+                    <li>
+                        <label for="date_end">End date:</label>
+                        <input name="date_end" id="to" size="10" readonly="readonly" class="datepickerto" value="[% club.date_end | $KohaDates %]" >
+                    </li>
+
+                    <li>
+                        <label for="name">Branch:</label>
+                        <select name="branchcode" id="club-template-branchcode">
+                            <option value="">&nbsp</option>
+                            [% FOREACH b IN Branches.GetBranches() %]
+                                [% IF b.branchcode == club.branch.branchcode %]
+                                    <option value="[% b.branchcode %]" selected="selected">[% b.branchname %]</option>
+                                [% ELSE %]
+                                    <option value="[% b.branchcode %]">[% b.branchname %]</option>
+                                [% END %]
+                            [% END %]
+                        </select>
+                    </li>
+
+                    [% IF club %]
+                        [% FOREACH f IN club.club_fields %]
+                            <li>
+                                <input type="hidden" name="club_template_field_id" value="[% f.club_template_field.id %]" />
+                                <input type="hidden" name="club_field_id" value="[% f.id %]" />
+
+                                <label for="club_field">[% f.club_template_field.name %]</label>
+                                [% IF f.club_template_field.authorised_value_category %]
+                                    <select name="club_field">
+                                        [% FOREACH a IN AuthorisedValues.Get( f.club_template_field.authorised_value_category ) %]
+                                            [% IF a.authorised_value == f.value %]
+                                                <option value="[% a.authorised_value %]" selected="selected">[% a.lib %]</option>
+                                            [% ELSE %]
+                                                <option value="[% a.authorised_value %]">[% a.lib %]</option>
+                                            [% END %]
+                                        [% END %]
+                                    </select>
+                                [% ELSE %]
+                                    <input type="text" name="club_field" value="[% f.value %]" />
+                                [% END %]
+                            </li>
+                        [% END %]
+                    [% ELSE %]
+                        [% FOREACH f IN club_template.club_template_fields %]
+                            <li>
+                                <input type="hidden" name="club_template_field_id" value="[% f.id %]" />
+
+                                <label for="club_field">[% f.name %]</label>
+                                [% IF f.authorised_value_category %]
+                                    <select name="club_field">
+                                        [% FOREACH a IN AuthorisedValues.Get( f.authorised_value_category ) %]
+                                            <option value="[% a.authorised_value %]">[% a.lib %]</option>
+                                        [% END %]
+                                    </select>
+                                [% ELSE %]
+                                    <input type="text" name="club_field" />
+                                [% END %]
+                            </li>
+                        [% END %]
+                    [% END %]
+
+                </ol>
+
+            </fieldset>
+
+            <input type="submit" class="btn" value="Save" />
+
+            <a href="clubs.pl" class="cancel">Cancel</a>
+        </form>
+    </div>
+</div>
+
+[% INCLUDE 'intranet-bottom.inc' %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/clubs.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/clubs.tt
new file mode 100644
index 0000000..0ec9bfb
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/clubs.tt
@@ -0,0 +1,206 @@
+[% USE Branches %]
+[% USE Koha %]
+[% INCLUDE 'doc-head-open.inc' %]
+<title>Koha &rsaquo; Tools &rsaquo; Patron clubs</title>
+[% INCLUDE 'doc-head-close.inc' %]
+
+<link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
+[% INCLUDE 'datatables.inc' %]
+
+<script type="text/javascript">
+//<![CDATA[
+    $(document).ready(function() {
+        tTable = $('#club-templates-table').dataTable($.extend(true, {}, dataTablesDefaults, {
+            "aoColumnDefs": [
+                { "aTargets": [ -1, -2 ], "bSortable": false, "bSearchable": false },
+            ]
+        } ));
+
+        cTable = $('#clubs-table').dataTable($.extend(true, {}, dataTablesDefaults, {
+            "aoColumnDefs": [
+                { "aTargets": [ -1, -2 ], "bSortable": false, "bSearchable": false },
+            ]
+        } ));
+    });
+
+    function ConfirmDeleteTemplate( id, name, a ) {
+        if ( confirm( _("Are you sure you want to delete the club template") + name + "?" ) ) {
+            $.ajax({
+                type: "POST",
+                url: '/cgi-bin/koha/svc/club/template/delete',
+                data: { id: id },
+                success: function( data ) {
+                    if ( data.success ) {
+                        tTable.fnDeleteRow(a.closest("tr")[0]);
+                    } else {
+                        alert(_("Unable to delete template!"));
+                    }
+                },
+                dataType: 'json'
+            });
+        }
+    }
+
+    function ConfirmDeleteClub( id, name, a ) {
+        if ( confirm( _("Are you sure you want to delete the club ") + name + "?" ) ) {
+            $.ajax({
+                type: "POST",
+                url: '/cgi-bin/koha/svc/club/delete',
+                data: { id: id },
+                success: function( data ) {
+                    if ( data.success ) {
+                        cTable.fnDeleteRow(a.closest("tr")[0]);
+                    } else {
+                        alert(_("Unable to delete club!"));
+                    }
+                },
+                dataType: 'json'
+            });
+        }
+    }
+//]]>
+</script>
+
+</head>
+
+<body id="clubs_clubs" class="clubs">
+[% INCLUDE 'header.inc' %]
+[% INCLUDE 'cat-search.inc' %]
+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> &rsaquo; Patron clubs</div>
+
+<div class="yui-t7">
+    <div class="yui-main">
+        <h1>Patron clubs</h1>
+
+
+        <h3>Club templates</h3>
+
+        [% IF CAN_user_clubs_edit_templates %]
+            <div class="btn-group">
+                <a class="btn btn-small" href="templates-add-modify.pl"><i class="icon-plus"></i> New club template</a>
+            </div>
+        [% END %]
+
+        <table id="club-templates-table">
+            <thead>
+                <tr>
+                    <th>Name</th>
+                    <th>Description</th>
+                    <th>Public enrollment</th>
+                    <th>Email required</th>
+                    <th>Branch</th>
+                    <th>&nbsp;</th>
+                    <th>&nbsp;</th>
+                </tr>
+            </thead>
+
+            <tbody>
+                [% FOREACH t IN club_templates %]
+                    <tr>
+                        <td>[% t.name %]</td>
+                        <td>[% t.description %]</td>
+                        <td>
+                            [% IF t.is_enrollable_from_opac %]
+                                Yes
+                            [% ELSE %]
+                                No
+                            [% END %]
+                        </td>
+                        <td>
+                            [% IF t.is_email_required %]
+                                Yes
+                            [% ELSE %]
+                                No
+                            [% END %]
+                        </td>
+                        <td>[% Branches.GetName( t.branchcode ) %]</td>
+                        <td>
+                            [% IF CAN_user_clubs_edit_templates %]
+                                <a class="btn btn-mini" href="templates-add-modify.pl?id=[% t.id %]">
+                                    <i class="icon-edit"></i> Edit
+                                </a>
+                            [% END %]
+                        </td>
+                        <td>
+                            [% IF CAN_user_clubs_edit_templates %]
+                                <a class="btn btn-mini" href="#" onclick='ConfirmDeleteTemplate([% t.id %], "[% t.name | html %]", $(this) ); return false;'>
+                                    <i class="icon-trash"></i> Delete
+                                </a>
+                            [% END %]
+                        </td>
+                    </tr>
+                [% END %]
+            </tbody>
+        </table>
+
+        <h3>Clubs</h3>
+
+        [% IF CAN_user_clubs_edit_clubs %]
+            <div class="btn-group">
+                <div class="btn-group">
+                    <button class="btn btn-small dropdown-toggle" data-toggle="dropdown"><i class="icon-plus"></i> New club <span class="caret"></span></button>
+                    <ul class="dropdown-menu">
+                        [% FOREACH t IN club_templates %]
+                            <li><a href="/cgi-bin/koha/clubs/clubs-add-modify.pl?club_template_id=[% t.id %]">[% t.name %]</a></li>
+                        [% END %]
+                    </ul>
+                </div>
+            </div>
+        [% END %]
+
+        <table id="clubs-table">
+            <thead>
+                <tr>
+                    <th>Name</th>
+                    <th>Template</th>
+                    <th>Description</th>
+                    <th>Public enrollment</th>
+                    <th>Email required</th>
+                    <th>Branch</th>
+                    <th>&nbsp;</th>
+                    <th>&nbsp;</th>
+                </tr>
+            </thead>
+
+            <tbody>
+                [% FOREACH c IN clubs %]
+                    <tr>
+                        <td>[% c.name %]</td>
+                        <td>[% c.club_template.name %]</td>
+                        <td>[% c.description %]</td>
+                        <td>
+                            [% IF c.club_template.is_enrollable_from_opac %]
+                                Yes
+                            [% ELSE %]
+                                No
+                            [% END %]
+                        </td>
+                        <td>
+                            [% IF c.club_template.is_email_required %]
+                                Yes
+                            [% ELSE %]
+                                No
+                            [% END %]
+                        </td>
+                        <td>[% Branches.GetName( c.branchcode ) %]</td>
+                        <td>
+                            [% IF CAN_user_clubs_edit_clubs %]
+                                <a class="btn btn-mini" href="clubs-add-modify.pl?id=[% c.id %]">
+                                    <i class="icon-edit"></i> Edit
+                                </a>
+                            [% END %]
+                        </td>
+                        <td>
+                            [% IF CAN_user_clubs_edit_clubs %]
+                                <a class="btn btn-mini" href="#" onclick='ConfirmDeleteClub([% c.id %], "[% c.name | html %]", $(this) ); return false;'>
+                                    <i class="icon-trash"></i> Delete
+                                </a>
+                            [% END %]
+                        </td>
+                    </tr>
+                [% END %]
+            </tbody>
+        </table>
+    </div>
+</div>
+[% INCLUDE 'intranet-bottom.inc' %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/patron-clubs-tab.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/patron-clubs-tab.tt
new file mode 100644
index 0000000..089717f
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/patron-clubs-tab.tt
@@ -0,0 +1,102 @@
+[% USE KohaDates %]
+
+[% IF enrollments %]
+    <table>
+        <thead>
+            <tr>
+                <th colspan="4">
+                    Clubs currently enrolled in
+                </th>
+            </tr>
+            <tr>
+                <th>Name</th>
+                <th>Description</th>
+                <th>Date enrolled</th>
+                [% IF CAN_user_clubs_enroll %]<th>&nbsp;</th>[% END %]
+            </tr>
+        </thead>
+
+        <tbody>
+            [% FOREACH e IN enrollments %]
+                <tr>
+                    <td>[% e.club.name %]</td>
+                    <td>[% e.club.description %]</td>
+                    <td>[% e.date_enrolled | $KohaDates %]</td>
+                    [% IF CAN_user_clubs_enroll %]
+                        <td>
+                            <a class="btn btn-mini" onclick="cancelEnrollment( [% e.id %] )">
+                                <i class="icon-remove"></i> Cancel
+                            </a>
+                        </td>
+                    [% END %]
+                </tr>
+            [% END %]
+        </tbody>
+    </table>
+[% END %]
+
+[% IF clubs %]
+    <table>
+        <thead>
+            <tr>
+                <th colspan="3">
+                    Clubs not enrolled in
+                </th>
+            </tr>
+            <tr>
+                <th>Name</th>
+                <th>Description</th>
+                [% IF CAN_user_clubs_enroll %]<th>&nbsp;</th>[% END %]
+            </tr>
+        </thead>
+
+        <tbody>
+            [% FOREACH c IN clubs %]
+                <tr>
+                    <td>[% c.name %]</td>
+                    <td>[% c.description %]</td>
+                    [% IF CAN_user_clubs_enroll %]
+                        <td>
+                            <a class="btn btn-mini" onclick="loadEnrollmentForm([% c.id %])">
+                                <i class="icon-plus"></i> Enroll
+                            </a>
+                        </td>
+                    [% END %]
+                </tr>
+            [% END %]
+        </tbody>
+    </table>
+[% END %]
+
+[% IF CAN_user_clubs_enroll %]
+<script type="text/javascript">
+function loadEnrollmentForm( id ) {
+    $("body").css("cursor", "progress");
+    $('#clubs-tab').load('/cgi-bin/koha/clubs/patron-enroll.pl?borrowernumber=[% borrowernumber %]&id=' + id, function() {
+        $("body").css("cursor", "default");
+    });
+
+    return false;
+}
+
+function cancelEnrollment( id ) {
+    $("body").css("cursor", "progress");
+    $.ajax({
+        type: "POST",
+        url: '/cgi-bin/koha/svc/club/cancel_enrollment',
+        data: { id: id },
+        success: function( data ) {
+            if ( data.success ) {
+                $('#clubs-tab').load('/cgi-bin/koha/clubs/patron-clubs-tab.pl?borrowernumber=[% borrowernumber %]', function() {
+                    $("body").css("cursor", "default");
+                });
+            } else {
+                alert(_("Unable to cancel enrollment!"));
+            }
+        },
+        dataType: 'json'
+    });
+    return false;
+}
+</script>
+[% END %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/patron-enroll.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/patron-enroll.tt
new file mode 100644
index 0000000..d605e5d
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/patron-enroll.tt
@@ -0,0 +1,66 @@
+[% USE AuthorisedValues %]
+[% SET AuthorisedValuesCategories = AuthorisedValues.Categories %]
+
+<h3>
+    Enroll in <i>[% club.name %]</i>
+</h3>
+
+<div class="container">
+    <form id="patron-enrollment-form">
+        <input type="hidden" name="id" value="[% club.id %]" />
+        <input type="hidden" name="borrowernumber" value="[% borrowernumber %]" />
+        <fieldset class="rows">
+            <ol>
+                [% FOREACH f IN club.club_template.club_template_enrollment_fields %]
+                    <li>
+                        <label>[% f.name %]</label>
+                        [% IF f.authorised_value_category %]
+                            <select name="[% f.id %]">
+                                [% FOREACH a IN AuthorisedValues.Get( f.club_template_field.authorised_value_category ) %]
+                                    <option value="[% a.authorised_value %]">[% a.lib %]</option>
+                                [% END %]
+                            </select>
+                        [% ELSE %]
+                            <input type="text" name="[% f.id %]" />
+                        [% END %]
+                        <span class="hint">[% f.description %]</span>
+                    </li>
+                [% END %]
+
+                <li>
+                    <a href="#" class="btn" onclick="addEnrollment(); return false;"><i class="icon-plus"></i> Enroll</a>
+                    <a href="#" onclick="showClubs(); return false;">Cancel</a>
+                </li>
+            </ol>
+        </fieldset>
+    </form>
+</div>
+
+<script type="text/javascript">
+function addEnrollment() {
+    $("body").css("cursor", "progress");
+    $.ajax({
+        type: "POST",
+        url: '/cgi-bin/koha/svc/club/enroll',
+        data: $( "#patron-enrollment-form" ).serialize(),
+        success: function( data ) {
+            if ( data.success ) {
+                $('#clubs-tab').load('/cgi-bin/koha/clubs/patron-clubs-tab.pl?borrowernumber=[% borrowernumber %]&id=[% club.id %]', function() {
+                    $("body").css("cursor", "default");
+                });
+            } else {
+                alert(_("Unable to create enrollment!"));
+            }
+        },
+        dataType: 'json'
+    });
+    return false;
+}
+
+function showClubs() {
+    $("body").css("cursor", "progress");
+    $('#clubs-tab').load('/cgi-bin/koha/clubs/patron-clubs-tab.pl?borrowernumber=[% borrowernumber %]&id=[% club.id %]', function() {
+        $("body").css("cursor", "default");
+    });
+}
+</script>
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/templates-add-modify.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/templates-add-modify.tt
new file mode 100644
index 0000000..c2cfa21
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/clubs/templates-add-modify.tt
@@ -0,0 +1,257 @@
+[% USE Branches %]
+[% USE AuthorisedValues %]
+[% SET AuthorisedValuesCategories = AuthorisedValues.Categories %]
+[% INCLUDE 'doc-head-open.inc' %]
+<title>Koha &rsaquo; Tools &rsaquo; Patron clubs &rsaquo; Club template</title>
+[% INCLUDE 'doc-head-close.inc' %]
+
+<script type="text/javascript">
+//<![CDATA[
+
+function CheckForm() {
+  if ( !$("#club-template-name").val() ) {
+    alert( _("Name is a required field!")  );
+    return false;
+  }
+
+  return true;
+}
+
+//]]>
+</script>
+
+</head>
+
+<body id="clubs_templates_add_modify" class="clubs">
+[% INCLUDE 'header.inc' %]
+[% INCLUDE 'cat-search.inc' %]
+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> &rsaquo; <a href="clubs.pl">Patron clubs</a> &rsaquo; Add / modify club template</div>
+
+<div class="yui-t7">
+    <div class="yui-main">
+        [% IF stored %]
+            <div class="alert">
+                <p>Your club template was [% IF stored == 'updated' %] updated [% ELSE %] saved [% END %]</p>
+                <a href="clubs.pl">Return to patron clubs</a>
+            </div>
+        [% END %]
+
+        <form method="post" onsubmit="return CheckForm()">
+            <input type="hidden" name="id" value="[% club_template.id %]" />
+
+            <fieldset class="rows">
+
+                <legend>
+                    [% IF club_template %]
+                        Modify club template <i>[% club_template.name %]</i>
+                    [% ELSE %]
+                        Create a new club template
+                    [% END %]
+                </legend>
+
+                <ol>
+                    <li>
+                        <label class="required" for="name">Name:</label>
+                        <input id="club-template-name" name="name" type="text" value="[% club_template.name %]" />
+                    </li>
+
+                    <li>
+                        <label for="description">Description:</label>
+                        <input id="club-template-description" name="description" type="text" value="[% club_template.description %]" />
+                    </li>
+
+                    <li>
+                        <label for="name">Allow public enrollment:</label>
+                        [% IF club_template.is_enrollable_from_opac %]
+                            <input type="checkbox" id="club-template-is-enrollable-from-opac" name="is_enrollable_from_opac" checked="checked" />
+                        [% ELSE %]
+                            <input type="checkbox" id="club-template-is-enrollable-from-opac" name="is_enrollable_from_opac" />
+                        [% END %]
+                        <span class="hint">If a template allows public enrollment, patrons can enroll in a club based on this template from the public catalog.</span>
+                    </li>
+
+                    <li>
+                        <label for="name">Require valid email address:</label>
+                        [% IF club_template.is_email_required %]
+                            <input type="checkbox" id="club-template-is-email-required" name="is_email_required" checked="checked" />
+                        [% ELSE %]
+                            <input type="checkbox" id="club-template-is-email-required" name="is_email_required" />
+                        [% END %]
+                        <span class="hint">If set, a club based on this template can only be enrolled in by patrons with a valid email address.</span>
+                    </li>
+
+                    <li>
+                        <label for="name">Branch:</label>
+                        <select name="branchcode" id="club-template-branchcode">
+                            <option value="">&nbsp</option>
+                            [% FOREACH b IN Branches.GetBranches() %]
+                                [% IF b.branchcode == club_template.branchcode %]
+                                    <option value="[% b.branchcode %]" selected="selected">[% b.branchname %]</option>
+                                [% ELSE %]
+                                    <option value="[% b.branchcode %]">[% b.branchname %]</option>
+                                [% END %]
+                            [% END %]
+                        </select>
+                        <span class="hint">If set, only librarians logged in with this branch will be able to modify this club template.</span>
+                    </li>
+
+                </ol>
+
+                <h2>Club fields:</h2>
+                <span class="hint">These fields will be used in the creation of clubs based on this template</span>
+                <span id="club-template-fields">
+                    [% FOREACH f IN club_template.club_template_fields %]
+                        <ul>
+                            <input type="hidden" name="club_template_field_id" value="[% f.id %]" />
+                            <li>
+                                <label for="field-name-[% f.id %]">Name:</label>
+                                <input name="club_template_field_name" id="field-name-[% f.id %]" value="[% f.name %]" />
+                            </li>
+
+                            <li>
+                                <label for="field-description-[% f.id %]">Description:</label>
+                                <input name="club_template_field_description" id="field-description-[% f.id %]" value="[% f.description %]" />
+                            </li>
+
+                            <li>
+                                <label for="field-description-[% f.id %]">Authorised value category:</label>
+                                <select name="club_template_field_authorised_value_category" id="field-authorised-value-category-[% f.id %]">
+                                    <option value="">&nbsp;</option>
+                                    [% FOREACH c IN AuthorisedValuesCategories %]
+                                        [% IF f.authorised_value_category == c %]
+                                            <option selected="selected" value="[% c %]">[% c %]</option>
+                                        [% ELSE %]
+                                            <option value="[% c %]">[% c %]</option>
+                                        [% END %]
+                                    [% END %]
+                                </select>
+                            </li>
+
+                            <li>
+                                <label for="field-delete-[% f.id %]">Delete field:</label>
+                                <input type="checkbox" name="club_template_field_delete" id="field-delete-[% f.id %]" value="[% f.id %]" />
+                            </li>
+
+                            <hr/>
+                        </ul>
+                    [% END %]
+                </span>
+                <a href="#" class="btn" onclick="$('#new-field-template').clone().attr('id','').show().appendTo('#club-template-fields'); return false;">
+                    Add new field
+                </a>
+
+                <h2>Enrollment fields:</h2>
+                <span class="hint">These fields will be used when enrolling a patron in a club based on this template</span>
+                <span id="club-template-enrollment-fields">
+                    [% FOREACH f IN club_template.club_template_enrollment_fields %]
+                        <ul>
+                            <input type="hidden" name="club_template_enrollment_field_id" value="[% f.id %]" />
+                            <li>
+                                <label for="enrollment-field-name-[% f.id %]">Name:</label>
+                                <input name="club_template_enrollment_field_name" id="enrollment-field-name-[% f.id %]" value="[% f.name %]" />
+                            </li>
+
+                            <li>
+                                <label for="enrollment-field-description-[% f.id %]">Description:</label>
+                                <input name="club_template_enrollment_field_description" id="enrollment-field-description-[% f.id %]" value="[% f.description %]" />
+                            </li>
+
+                            <li>
+                                <label for="enrollment-field-description-[% f.id %]">Authorised value category:</label>
+                                <select name="club_template_enrollment_field_authorised_value_category" id="enrollment-field-authorised-value-category-[% f.id %]">
+                                    <option value="">&nbsp;</option>
+                                    [% FOREACH c IN AuthorisedValuesCategories %]
+                                        [% IF f.authorised_value_category == c %]
+                                            <option selected="selected" value="[% c %]">[% c %]</option>
+                                        [% ELSE %]
+                                            <option value="[% c %]">[% c %]</option>
+                                        [% END %]
+                                    [% END %]
+                                </select>
+                            </li>
+
+                            <li>
+                                <label for="enrollment-field-delete-[% f.id %]">Delete field:</label>
+                                <input type="checkbox" name="club_template_enrollment_field_delete" id="enrollment-field-delete-[% f.id %]" value="[% f.id %]" />
+                            </li>
+
+                            <hr/>
+                        </ul>
+                    [% END %]
+                </span>
+                <a href="#" class="btn" onclick="$('#new-enrollment-field-template').clone().attr('id','').show().appendTo('#club-template-enrollment-fields'); return false;">
+                    Add new field
+                </a>
+
+            </fieldset>
+
+            <input type="hidden" name="id" value="[% club_template.id %]" />
+
+            <input type="submit" class="btn" value="Save" />
+
+            <a href="clubs.pl" class="cancel">Cancel</a>
+        </form>
+    </div>
+</div>
+
+<span id="new-field-template" style="display:none">
+    <ul>
+        <input type="hidden" name="club_template_field_id" value="" />
+
+        <li>
+            <label for="club_template_field_name">Name:</label>
+            <input name="club_template_field_name" />
+        </li>
+
+        <li>
+            <label for="club_template_field_description">Description:</label>
+            <input name="club_template_field_description" />
+        </li>
+
+        <li>
+            <label for="club_template_field_authorised_value_category">Authorised value category:</label>
+            <select name="club_template_field_authorised_value_category">
+                <option value="">&nbsp;</option>
+                [% FOREACH c IN AuthorisedValuesCategories %]
+                    <option value="[% c %]">[% c %]</option>
+                [% END %]
+            </select>
+        </li>
+
+        <a href="#" onclick="$(this).parent().remove(); return false;">Cancel</a>
+
+        <hr/>
+    </ul>
+</span>
+
+<span id="new-enrollment-field-template" style="display:none">
+    <ul>
+        <input type="hidden" name="club_template_enrollment_field_id" value="" />
+
+        <li>
+            <label for="club_template_enrollment_field_name">Name:</label>
+            <input name="club_template_enrollment_field_name" />
+        </li>
+
+        <li>
+            <label for="club_template_enrollment_field_description">Description:</label>
+            <input name="club_template_enrollment_field_description" />
+        </li>
+
+        <li>
+            <label for="club_template_enrollment_field_authorised_value_category">Authorised value category:</label>
+            <select name="club_template_enrollment_field_authorised_value_category">
+                <option value="">&nbsp;</option>
+                [% FOREACH c IN AuthorisedValuesCategories %]
+                    <option value="[% c %]">[% c %]</option>
+                [% END %]
+            </select>
+        </li>
+
+        <a href="#" onclick="$(this).parent().remove(); return false;">Cancel</a>
+
+        <hr/>
+    </ul>
+</span>
+
+[% INCLUDE 'intranet-bottom.inc' %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt
index 9fc1482..0a0b420 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt
@@ -46,6 +46,13 @@ var MSG_EXPORT_SELECT_CHECKOUTS = _("You must select checkout(s) to export");
 columns_settings = [% ColumnsSettings.GetColumns( 'member', 'moremember', 'issues-table', 'json' ) %]
 
 $(document).ready(function() {
+    if ( $('#clubs-tab').length ) {
+        $('#clubs-tab-link').on('click', function() {
+            $('#clubs-tab').text(_("Loading..."));
+            $('#clubs-tab').load('/cgi-bin/koha/clubs/patron-clubs-tab.pl?borrowernumber=[% borrowernumber %]');
+        });
+    }
+
     $('#finesholdsissues').tabs({
         // Correct table sizing for tables hidden in tabs
         // http://www.datatables.net/examples/api/tabs_and_scrolling.html
@@ -435,6 +442,15 @@ function validate1(date) {
             [% END %]
         </li>
         <li><a id="debarments-tab-link" href="#reldebarments">[% debarments.size %] Restrictions</a></li>
+        [% SET enrollments = borrower.GetClubEnrollmentsCount %]
+        [% SET enrollable  = borrower.GetEnrollableClubsCount %]
+        [% IF CAN_user_clubs && ( enrollments || enrollable ) %]
+            <li>
+                <a id="clubs-tab-link" href="#clubs-tab">
+                    Clubs ([% enrollments %]/[% enrollable %])
+                </a>
+            </li>
+        [% END %]
     </ul>
 
 [% INCLUDE "checkouts-table.inc" %]
@@ -467,6 +483,12 @@ function validate1(date) {
     [% END %]
 </div>
 
+[% IF CAN_user_clubs && ( enrollments || enrollable ) %]
+    <div id="clubs-tab">
+        Loading...
+    </div>
+[% END %]
+
 [% INCLUDE borrower_debarments.inc %]
 
 <div id="reserves">
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt
index e722610..a9a10a7 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt
@@ -20,6 +20,11 @@
     <dd>Manage lists of patrons.</dd>
     [% END %]
 
+    [% IF (CAN_user_clubs) %]
+    <dt><a href="/cgi-bin/koha/clubs/clubs.pl">Patron clubs</a>
+    <dd>Manage patron clubs..</dd>
+    [% END %]
+
 [% IF ( CAN_user_tools_moderate_comments ) %]
     <dt><a href="/cgi-bin/koha/reviews/reviewswaiting.pl">Comments</a> [% IF ( pendingcomments ) %]<span class="holdcount"><a href="/cgi-bin/koha/reviews/reviewswaiting.pl">[% pendingcomments %]</a></span>[% END %]</dt>
 	<dd>Moderate patron comments. </dd>
diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/clubs/clubs-tab.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/clubs/clubs-tab.tt
new file mode 100644
index 0000000..71714a7
--- /dev/null
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/clubs/clubs-tab.tt
@@ -0,0 +1,102 @@
+[% USE KohaDates %]
+
+[% IF enrollments %]
+    <table id="clubs-table-enrolled" class="table table-bordered table-striped">
+        <thead>
+            <tr>
+                <th colspan="4">
+                    Clubs you are currently enrolled in
+                </th>
+            </tr>
+            <tr>
+                <th>Name</th>
+                <th>Description</th>
+                <th>Date enrolled</th>
+                <th>&nbsp;</th>
+            </tr>
+        </thead>
+
+        <tbody>
+            [% FOREACH e IN enrollments %]
+                <tr>
+                    <td>[% e.club.name %]</td>
+                    <td>[% e.club.description %]</td>
+                    <td>[% e.date_enrolled | $KohaDates %]</td>
+                    [% IF e.club.club_template.is_enrollable_from_opac %]
+                        <td>
+                            <a class="btn btn-mini" onclick="cancelEnrollment( [% e.id %] )">
+                                <i class="icon-remove"></i> Cancel
+                            </a>
+                        </td>
+                    [% END %]
+                </tr>
+            [% END %]
+        </tbody>
+    </table>
+[% END %]
+
+[% IF clubs %]
+    <table id="clubs-table-unenrolled" class="table table-bordered table-striped">
+        <thead>
+            <tr>
+                <th colspan="3">
+                    Clubs you can enroll in
+                </th>
+            </tr>
+            <tr>
+                <th>Name</th>
+                <th>Description</th>
+                <th>&nbsp;</th>
+            </tr>
+        </thead>
+
+        <tbody>
+            [% FOREACH c IN clubs %]
+                <tr>
+                    <td>[% c.name %]</td>
+                    <td>[% c.description %]</td>
+                    <td>
+                        [% IF borrower.FirstValidEmailAddress %]
+                            <a class="btn btn-mini" onclick="loadEnrollmentForm([% c.id %])">
+                                <i class="icon-plus"></i> Enroll
+                            </a>
+                        [% ELSE %]
+                            <span class="hint">You must have an email address to enroll</span>
+                        [% END %]
+                    </td>
+                </tr>
+            [% END %]
+        </tbody>
+    </table>
+[% END %]
+
+<script type="text/javascript">
+function loadEnrollmentForm( id ) {
+    $("body").css("cursor", "progress");
+    $('#opac-user-clubs').load('/cgi-bin/koha/clubs/enroll.pl?borrowernumber=[% borrower.borrowernumber %]&id=' + id, function() {
+        $("body").css("cursor", "default");
+    });
+
+    return false;
+}
+
+function cancelEnrollment( id ) {
+    $("body").css("cursor", "progress");
+    $.ajax({
+        type: "POST",
+        url: '/cgi-bin/koha/svc/club/cancel_enrollment',
+        data: { id: id },
+        success: function( data ) {
+            if ( data.success ) {
+                $('#opac-user-clubs').load('/cgi-bin/koha/clubs/clubs-tab.pl?borrowernumber=[% borrower.borrowernumber %]', function() {
+                    $("body").css("cursor", "default");
+                });
+            } else {
+                alert(_("Unable to cancel enrollment!"));
+            }
+        },
+        dataType: 'json'
+    });
+    return false;
+}
+</script>
diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/clubs/enroll.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/clubs/enroll.tt
new file mode 100644
index 0000000..c621dd1
--- /dev/null
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/clubs/enroll.tt
@@ -0,0 +1,66 @@
+[% USE AuthorisedValues %]
+[% SET AuthorisedValuesCategories = AuthorisedValues.Categories %]
+
+<h3>
+    Enroll in <i>[% club.name %]</i>
+</h3>
+
+<div class="container">
+    <form id="patron-enrollment-form">
+        <input type="hidden" name="id" value="[% club.id %]" />
+        <input type="hidden" name="borrowernumber" value="[% borrowernumber %]" />
+        <fieldset class="rows">
+            <ol>
+                [% FOREACH f IN club.club_template.club_template_enrollment_fields %]
+                    <li>
+                        <label>[% f.name %]</label>
+                        [% IF f.authorised_value_category %]
+                            <select name="[% f.id %]">
+                                [% FOREACH a IN AuthorisedValues.Get( f.authorised_value_category ) %]
+                                    <option value="[% a.authorised_value %]">[% a.lib %]</option>
+                                [% END %]
+                            </select>
+                        [% ELSE %]
+                            <input type="text" name="[% f.id %]" />
+                        [% END %]
+                        <span class="hint">[% f.description %]</span>
+                    </li>
+                [% END %]
+
+                <li>
+                    <a href="#" class="btn" onclick="addEnrollment(); return false;"><i class="icon-plus"></i> Enroll</a>
+                    <a href="#" onclick="showClubs(); return false;">Cancel</a>
+                </li>
+            </ol>
+        </fieldset>
+    </form>
+</div>
+
+<script type="text/javascript">
+function addEnrollment() {
+    $("body").css("cursor", "progress");
+    $.ajax({
+        type: "POST",
+        url: '/cgi-bin/koha/svc/club/enroll',
+        data: $( "#patron-enrollment-form" ).serialize(),
+        success: function( data ) {
+            if ( data.success ) {
+                $('#opac-user-clubs').load('/cgi-bin/koha/clubs/clubs-tab.pl?borrowernumber=[% borrowernumber %]&id=[% club.id %]', function() {
+                    $("body").css("cursor", "default");
+                });
+            } else {
+                alert(_("Unable to create enrollment!"));
+            }
+        },
+        dataType: 'json'
+    });
+    return false;
+}
+
+function showClubs() {
+    $("body").css("cursor", "progress");
+    $('#opac-user-clubs').load('/cgi-bin/koha/clubs/clubs-tab.pl?borrowernumber=[% borrowernumber %]&id=[% club.id %]', function() {
+        $("body").css("cursor", "default");
+    });
+}
+</script>
diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt
index 85c985d..d3b8d5f 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt
@@ -111,6 +111,14 @@
                                 [% IF ( BORROWER_INF.amountlessthanzero ) %]<li><a href="#opac-user-fines">Credits ([% BORROWER_INF.amountoutstanding %])</a></li>[% END %]
                             [% END %]
                             [% IF ( waiting_count ) %][% IF ( BORROWER_INF.atdestination ) %]<li><a href="#opac-user-waiting">Waiting ([% waiting_count %])</a></li>[% END %][% END %]
+                            [% IF borrower.GetClubEnrollmentsCount || borrower.GetEnrollableClubsCount(1) %]
+                                <li>
+                                    <a id="opac-user-clubs-tab-link" href="#opac-user-clubs">
+                                        Clubs ([% borrower.GetClubEnrollmentsCount %]/[% borrower.GetEnrollableClubsCount(1) %])
+                                    </a>
+                                </li>
+                            [% END %]
+
                             [% IF ( reserves_count ) %]<li><a href="#opac-user-holds">Holds ([% reserves_count %])</a></li>[% END %]
                         </ul>
 
@@ -280,6 +288,12 @@
                             [% END # IF issues_count %]
                         </div> <!-- / .opac-user-checkouts -->
 
+                        [% IF borrower.GetClubEnrollmentsCount || borrower.GetEnrollableClubsCount(1) %]
+                            <div id="opac-user-clubs">
+                                Loading...
+                            </div>
+                        [% END %]
+
                         [% IF ( OPACFinesTab ) %]
                             <!-- FINES BOX -->
                             [% IF ( BORROWER_INF.amountoverfive ) %]
@@ -733,6 +747,13 @@
             [% END %]
 
             $( ".suspend-until" ).datepicker({ minDate: 1 }); // Require that "until date" be in the future
+
+            if ( $('#opac-user-clubs').length ) {
+                $('#opac-user-clubs-tab-link').on('click', function() {
+                    $('#opac-user-clubs').text(_("Loading..."));
+                    $('#opac-user-clubs').load('/cgi-bin/koha/clubs/clubs-tab.pl?borrowernumber=[% borrowernumber %]');
+                });
+            }
         });
         //]]>
     </script>
diff --git a/members/moremember.pl b/members/moremember.pl
index 3a54ec9..61fef5c 100755
--- a/members/moremember.pl
+++ b/members/moremember.pl
@@ -62,6 +62,7 @@ if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preferen
 use DateTime;
 use Koha::DateUtils;
 use Koha::Database;
+use Koha::Borrowers;
 
 use vars qw($debug);
 
@@ -363,6 +364,7 @@ $template->param(
     PatronsPerPage => C4::Context->preference("PatronsPerPage") || 20,
     relatives_issues_count => $relatives_issues_count,
     relatives_borrowernumbers => \@relatives,
+    borrower => Koha::Borrowers->find( $borrowernumber ),
 );
 
 output_html_with_http_headers $input, $cookie, $template->output;
diff --git a/opac/clubs/clubs-tab.pl b/opac/clubs/clubs-tab.pl
new file mode 100755
index 0000000..8cb7163
--- /dev/null
+++ b/opac/clubs/clubs-tab.pl
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+
+# Copyright 2013 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+
+use CGI;
+
+use C4::Auth;
+use C4::Output;
+use Koha::Borrowers;
+
+my $cgi = new CGI;
+
+my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+    {
+        template_name   => "clubs/clubs-tab.tt",
+        query           => $cgi,
+        type            => "opac",
+        authnotrequired => 0,
+    }
+);
+
+my $borrowernumber = $cgi->param('borrowernumber');
+
+my $borrower = Koha::Borrowers->find($borrowernumber);
+
+my @enrollments = $borrower->GetClubEnrollments();
+my @clubs = $borrower->GetEnrollableClubs( my $opac = 1 );
+
+$template->param(
+    enrollments => \@enrollments,
+    clubs       => \@clubs,
+    borrower    => $borrower,
+);
+
+output_html_with_http_headers( $cgi, $cookie, $template->output );
diff --git a/opac/clubs/enroll.pl b/opac/clubs/enroll.pl
new file mode 100755
index 0000000..e6d6adc
--- /dev/null
+++ b/opac/clubs/enroll.pl
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+
+# Copyright 2013 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+
+use CGI;
+
+use C4::Auth;
+use C4::Output;
+use Koha::Clubs;
+
+my $cgi = new CGI;
+
+my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+    {
+        template_name   => "clubs/enroll.tt",
+        query           => $cgi,
+        type            => "opac",
+        authnotrequired => 0,
+    }
+);
+
+my $id             = $cgi->param('id');
+my $borrowernumber = $cgi->param('borrowernumber');
+
+my $club = Koha::Clubs->find($id);
+
+$template->param(
+    club           => $club,
+    borrowernumber => $borrowernumber,
+);
+
+output_html_with_http_headers( $cgi, $cookie, $template->output );
diff --git a/opac/opac-user.pl b/opac/opac-user.pl
index 18dfec6..71dcfdf 100755
--- a/opac/opac-user.pl
+++ b/opac/opac-user.pl
@@ -36,6 +36,7 @@ use C4::Letters;
 use C4::Branch; # GetBranches
 use Koha::DateUtils;
 use Koha::Borrower::Debarments qw(IsDebarred);
+use Koha::Borrowers;
 
 use constant ATTRIBUTE_SHOW_BARCODE => 'SHOW_BCODE';
 
@@ -389,12 +390,10 @@ $template->param(
     patronupdate => $patronupdate,
     OpacRenewalAllowed => C4::Context->preference("OpacRenewalAllowed"),
     userview => 1,
-);
-
-$template->param(
     SuspendHoldsOpac => C4::Context->preference('SuspendHoldsOpac'),
     AutoResumeSuspendedHolds => C4::Context->preference('AutoResumeSuspendedHolds'),
     OpacHoldNotes => C4::Context->preference('OpacHoldNotes'),
+    borrower => Koha::Borrowers->find($borrowernumber),
 );
 
 output_html_with_http_headers $query, $cookie, $template->output;
diff --git a/opac/svc/club/cancel_enrollment b/opac/svc/club/cancel_enrollment
new file mode 100755
index 0000000..be922dd
--- /dev/null
+++ b/opac/svc/club/cancel_enrollment
@@ -0,0 +1,47 @@
+#!/usr/bin/perl
+
+# Copyright 2014 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+use Modern::Perl;
+
+use CGI;
+use JSON qw(to_json);
+
+use C4::Auth qw(check_cookie_auth);
+use Koha::Club::Enrollments;
+
+my $cgi = new CGI;
+
+my ( $auth_status, $sessionID ) =
+  check_cookie_auth( $cgi->cookie('CGISESSID') );
+if ( $auth_status ne "ok" ) {
+    exit 0;
+}
+
+my $borrowernumber = C4::Context->userenv->{'number'};
+
+my $id = $cgi->param('id');
+
+my $enrollment = Koha::Club::Enrollments->find($id);
+$enrollment->cancel();
+
+binmode STDOUT, ':encoding(UTF-8)';
+print $cgi->header( -type => 'text/plain', -charset => 'UTF-8' );
+
+print to_json( { success => $enrollment ? 1 : 0 } );
diff --git a/opac/svc/club/enroll b/opac/svc/club/enroll
new file mode 100755
index 0000000..8291c8b
--- /dev/null
+++ b/opac/svc/club/enroll
@@ -0,0 +1,77 @@
+#!/usr/bin/perl
+
+# Copyright 2014 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+use Modern::Perl;
+
+use CGI;
+use JSON qw(to_json);
+
+use C4::Auth qw(check_cookie_auth);
+use Koha::Club::Enrollment::Field;
+use Koha::Club::Enrollment;
+use Koha::Clubs;
+
+my $cgi = new CGI;
+
+my ( $auth_status, $sessionID ) =
+  check_cookie_auth( $cgi->cookie('CGISESSID') );
+if ( $auth_status ne "ok" ) {
+    exit 0;
+}
+
+my $borrowernumber = C4::Context->userenv->{'number'};
+
+my $id = $cgi->param('id');
+
+my $enrollment;
+if ( $borrowernumber && $id ) {
+    my $club = Koha::Clubs->find($id);
+
+    if ( $club->club_template()->is_enrollable_from_opac() ) {
+        $enrollment = Koha::Club::Enrollment->new()->set(
+            {
+                club_id        => $club->id(),
+                borrowernumber => $borrowernumber,
+                date_enrolled  => \'NOW()',
+                date_created   => \'NOW()',
+                branchcode     => C4::Context->userenv
+                ? C4::Context->userenv->{'branch'}
+                : undef,
+            }
+        )->store();
+
+        my @enrollment_fields = $club->club_template()->club_template_enrollment_fields();
+
+        foreach my $e (@enrollment_fields) {
+            Koha::Club::Enrollment::Field->new()->set(
+                {
+                    club_enrollment_id                => $enrollment->id(),
+                    club_template_enrollment_field_id => $e->id(),
+                    value                             => $cgi->param( $e->id() ),
+                }
+            )->store();
+        }
+    }
+}
+
+binmode STDOUT, ':encoding(UTF-8)';
+print $cgi->header( -type => 'text/plain', -charset => 'UTF-8' );
+
+print to_json( { success => $enrollment ? 1 : 0 } );
diff --git a/svc/club/cancel_enrollment b/svc/club/cancel_enrollment
new file mode 100755
index 0000000..4416537
--- /dev/null
+++ b/svc/club/cancel_enrollment
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+
+# Copyright 2014 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+use Modern::Perl;
+
+use CGI;
+use JSON qw(to_json);
+
+use C4::Auth qw(check_cookie_auth);
+
+use Koha::Club::Enrollments;
+
+my $cgi = new CGI;
+
+my ( $auth_status, $sessionID ) =
+  check_cookie_auth( $cgi->cookie('CGISESSID'), { clubs => 'enroll' } );
+if ( $auth_status ne "ok" ) {
+    exit 0;
+}
+
+my $id = $cgi->param('id');
+
+my $enrollment = Koha::Club::Enrollments->find($id);
+$enrollment->cancel() if $enrollment;
+
+binmode STDOUT, ':encoding(UTF-8)';
+print $cgi->header( -type => 'text/plain', -charset => 'UTF-8' );
+
+print to_json( { success => $enrollment ? 1 : 0 } );
diff --git a/svc/club/delete b/svc/club/delete
new file mode 100755
index 0000000..c254a5e
--- /dev/null
+++ b/svc/club/delete
@@ -0,0 +1,48 @@
+#!/usr/bin/perl
+
+# Copyright 2014 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+use Modern::Perl;
+
+use CGI;
+use JSON qw(to_json);
+
+use C4::Auth qw(check_cookie_auth);
+use Koha::Clubs;
+
+my $cgi = new CGI;
+
+my ( $auth_status, $sessionID ) = check_cookie_auth( $cgi->cookie('CGISESSID'), { clubs => 'edit_clubs' } );
+if ( $auth_status ne "ok" ) {
+    exit 0;
+}
+
+my $success = 0;
+
+my $id = $cgi->param('id');
+
+my $club = Koha::Clubs->find($id);
+if ($club) {
+    $success = $club->delete();
+}
+
+binmode STDOUT, ':encoding(UTF-8)';
+print $cgi->header( -type => 'text/plain', -charset => 'UTF-8' );
+
+print to_json( { success => $success ? 1 : 0 } );
diff --git a/svc/club/enroll b/svc/club/enroll
new file mode 100755
index 0000000..ee6c085
--- /dev/null
+++ b/svc/club/enroll
@@ -0,0 +1,74 @@
+#!/usr/bin/perl
+
+# Copyright 2014 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+use Modern::Perl;
+
+use CGI;
+use JSON qw(to_json);
+
+use C4::Auth qw(check_cookie_auth);
+use Koha::Club::Enrollment::Fields;
+use Koha::Club::Enrollments;
+use Koha::Clubs;
+
+my $cgi = new CGI;
+
+my ( $auth_status, $sessionID ) =
+  check_cookie_auth( $cgi->cookie('CGISESSID'), { clubs => 'enroll' } );
+if ( $auth_status ne "ok" ) {
+    exit 0;
+}
+
+my $id             = $cgi->param('id');
+my $borrowernumber = $cgi->param('borrowernumber');
+
+my $club = Koha::Clubs->find($id);
+
+my $enrollment;
+if ($club) {
+    $enrollment = Koha::Club::Enrollment->new(
+        {
+            club_id        => $club->id(),
+            borrowernumber => $borrowernumber,
+            date_enrolled  => \'NOW()',
+            date_created   => \'NOW()',
+            branchcode     => C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef,
+        }
+    )->store();
+
+    if ($enrollment) {
+        my @enrollment_fields = $club->club_template()->club_template_enrollment_fields();
+
+        foreach my $e (@enrollment_fields) {
+            my $club_enrollment_field = Koha::Club::Enrollment::Field->new(
+                {
+                    club_enrollment_id                => $enrollment->id(),
+                    club_template_enrollment_field_id => $e->id(),
+                    value                             => $cgi->param( $e->id() ),
+                }
+            )->store();
+        }
+    }
+}
+
+binmode STDOUT, ':encoding(UTF-8)';
+print $cgi->header( -type => 'text/plain', -charset => 'UTF-8' );
+
+print to_json( { success => $enrollment ? 1 : 0 } );
diff --git a/svc/club/template/delete b/svc/club/template/delete
new file mode 100755
index 0000000..2948dc4
--- /dev/null
+++ b/svc/club/template/delete
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+
+# Copyright 2014 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+use Modern::Perl;
+
+use CGI;
+use JSON qw(to_json);
+
+use C4::Auth qw(check_cookie_auth);
+
+use Koha::Club::Templates;
+
+my $cgi = new CGI;
+
+my ( $auth_status, $sessionID ) = check_cookie_auth( $cgi->cookie('CGISESSID'), { clubs => 'edit_templates' } );
+if ( $auth_status ne "ok" ) {
+    exit 0;
+}
+
+my $success = 0;
+
+my $id = $cgi->param('id');
+
+my $club_template = Koha::Club::Templates->find($id);
+if ($club_template) {
+    $success = $club_template->delete();
+}
+
+binmode STDOUT, ':encoding(UTF-8)';
+print $cgi->header( -type => 'text/plain', -charset => 'UTF-8' );
+
+print to_json( { success => $success ? 1 : 0 } );
-- 
1.7.2.5